Skip to content

feat: Add emitters for tket-qsystem #1039

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

Merged
merged 5 commits into from
Aug 15, 2025
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
2 changes: 2 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions tket-qsystem/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ required-features = ["cli"]
[dependencies]
hugr.workspace = true
tket = { path = "../tket", version = "0.13.2" }
tket-json-rs = { workspace = true }
lazy_static.workspace = true
serde = { workspace = true, features = ["derive"] }
smol_str.workspace = true
Expand All @@ -47,6 +48,7 @@ anyhow = { workspace = true, optional = true }
cool_asserts.workspace = true
petgraph.workspace = true
rstest.workspace = true
serde_json.workspace = true

[lints]
workspace = true
17 changes: 9 additions & 8 deletions tket-qsystem/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
//! Provides a preparation and validation workflow for Hugrs targeting
//! Quantinuum H-series quantum computers.

#[cfg(feature = "cli")]
pub mod cli;
pub mod extension;
#[cfg(feature = "llvm")]
pub mod llvm;
mod lower_drops;
pub mod pytket;
pub mod replace_bools;

use derive_more::{Display, Error, From};
use hugr::{
algorithms::{
Expand All @@ -25,14 +34,6 @@ use extension::{
#[cfg(feature = "llvm")]
use hugr::llvm::utils::inline_constant_functions;

#[cfg(feature = "cli")]
pub mod cli;
pub mod extension;
#[cfg(feature = "llvm")]
pub mod llvm;
mod lower_drops;
pub mod replace_bools;

/// Modify a [hugr::Hugr] into a form that is acceptable for ingress into a
/// Q-System. Returns an error if this cannot be done.
///
Expand Down
42 changes: 42 additions & 0 deletions tket-qsystem/src/pytket.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! Encoder/decoder definitions for translating tket-qsystem operations to/from legacy Pytket circuits.

mod futures;
mod qsystem;

pub use futures::FutureEmitter;
use hugr::HugrView;
pub use qsystem::QSystemEmitter;
use tket::serialize::pytket::{
default_decoder_config, default_encoder_config, PytketDecoderConfig, PytketEncoderConfig,
};

/// Default pytket decoder configuration for [`Circuit`][tket::Circuit]s with
/// native qsystem operations.
///
/// Contains a list of custom decoders that define translations of legacy tket
/// primitives into HUGR operations.
pub fn qsystem_decoder_config() -> PytketDecoderConfig {
let mut config = default_decoder_config();
config.add_decoder(QSystemEmitter);

config.add_type_translator(FutureEmitter);

config
}

/// Default pytket encoder configuration for [`Circuit`][tket::Circuit]s with
/// native qsystem operations.
///
/// Contains emitters for std and tket operations.
pub fn qsystem_encoder_config<H: HugrView>() -> PytketEncoderConfig<H> {
let mut config = default_encoder_config();
config.add_emitter(QSystemEmitter);
config.add_emitter(FutureEmitter);

config.add_type_translator(FutureEmitter);

config
}

#[cfg(test)]
mod tests;
84 changes: 84 additions & 0 deletions tket-qsystem/src/pytket/futures.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! Encoder and decoder for floating point operations.

use hugr::extension::simple_op::MakeExtensionOp;
use hugr::extension::ExtensionId;
use hugr::ops::ExtensionOp;
use hugr::types::Term;
use hugr::HugrView;
use itertools::Itertools;
use tket::serialize::pytket::encoder::EncodeStatus;
use tket::serialize::pytket::extension::{PytketTypeTranslator, RegisterCount};
use tket::serialize::pytket::{
PytketEmitter, PytketEncodeError, PytketEncoderContext, TypeTranslatorSet,
};
use tket::Circuit;

use crate::extension::futures::{self, FutureOpDef};

/// Emitter for [futures](crate::extension::futures) operations and types.
///
/// The `Future<T>` type is treated as a transparent wrapper when translated to
/// pytket circuits, and operations dealing with futures do not produce pytket
/// commands.
#[derive(Debug, Clone, Default)]
pub struct FutureEmitter;

impl<H: HugrView> PytketEmitter<H> for FutureEmitter {
fn extensions(&self) -> Option<Vec<ExtensionId>> {
Some(vec![futures::EXTENSION_ID])
}

fn op_to_pytket(
&self,
node: H::Node,
op: &ExtensionOp,
circ: &Circuit<H>,
encoder: &mut PytketEncoderContext<H>,
) -> Result<EncodeStatus, PytketEncodeError<H::Node>> {
let Ok(rot_op) = FutureOpDef::from_extension_op(op) else {
return Ok(EncodeStatus::Unsupported);
};

match rot_op {
FutureOpDef::Read => {
// Transparent map
encoder.emit_transparent_node(node, circ, |ps| ps.input_params.to_vec())?;
Ok(EncodeStatus::Success)
}
FutureOpDef::Dup => {
// Register the same input values for each output.
let values = encoder.get_input_values(node, circ)?;
let outputs = circ.hugr().node_outputs(node).collect_vec();
let out0 = hugr::Wire::new(node, outputs[0]);
let out1 = hugr::Wire::new(node, outputs[1]);

encoder.values.register_wire(out0, values.clone(), circ)?;
encoder.values.register_wire(out1, values, circ)?;

Ok(EncodeStatus::Success)
}
FutureOpDef::Free => Ok(EncodeStatus::Success),
}
}
}

impl PytketTypeTranslator for FutureEmitter {
fn extensions(&self) -> Vec<ExtensionId> {
vec![futures::EXTENSION_ID]
}

fn type_to_pytket(
&self,
typ: &hugr::types::CustomType,
type_translators: &TypeTranslatorSet,
) -> Option<RegisterCount> {
if typ.name() != futures::FUTURE_TYPE_NAME.as_str() {
return None;
}
let Some(Term::Runtime(inner_ty)) = typ.args().first() else {
return None;
};

type_translators.type_to_pytket(inner_ty)
}
}
141 changes: 141 additions & 0 deletions tket-qsystem/src/pytket/qsystem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//! Encoder/decoder for [qsystem::EXTENSION][use crate::extension::qsystem::EXTENSION] operations.

use std::sync::Arc;

use hugr::extension::simple_op::MakeExtensionOp;
use hugr::extension::ExtensionId;
use hugr::ops::ExtensionOp;
use hugr::HugrView;
use tket::serialize::pytket::decoder::{
DecodeStatus, LoadedParameter, PytketDecoderContext, TrackedBit, TrackedQubit,
};
use tket::serialize::pytket::encoder::EncodeStatus;
use tket::serialize::pytket::extension::PytketDecoder;
use tket::serialize::pytket::{
PytketDecodeError, PytketEmitter, PytketEncodeError, PytketEncoderContext,
};
use tket::Circuit;
use tket_json_rs::optype::OpType as PytketOptype;

use crate::extension;
use crate::extension::qsystem::{QSystemOp, RuntimeBarrierDef};

/// Encoder for [futures](crate::extension::futures) operations.
#[derive(Debug, Clone, Default)]
pub struct QSystemEmitter;

impl<H: HugrView> PytketEmitter<H> for QSystemEmitter {
fn extensions(&self) -> Option<Vec<ExtensionId>> {
Some(vec![extension::qsystem::EXTENSION_ID])
}

fn op_to_pytket(
&self,
node: H::Node,
op: &ExtensionOp,
circ: &Circuit<H>,
encoder: &mut PytketEncoderContext<H>,
) -> Result<EncodeStatus, PytketEncodeError<H::Node>> {
if let Ok(tket_op) = QSystemOp::from_extension_op(op) {
self.encode_qsystem_op(node, tket_op, circ, encoder)
} else if let Ok(sympy_op) = RuntimeBarrierDef::from_extension_op(op) {
self.encode_runtime_barrier_op(node, sympy_op, circ, encoder)
} else {
Ok(EncodeStatus::Unsupported)
}
}
}

impl QSystemEmitter {
/// Encode a tket operation into a pytket operation.
fn encode_qsystem_op<H: HugrView>(
&self,
node: H::Node,
qsystem_op: QSystemOp,
circ: &Circuit<H>,
encoder: &mut PytketEncoderContext<H>,
) -> Result<EncodeStatus, PytketEncodeError<H::Node>> {
let serial_op = match qsystem_op {
QSystemOp::Measure => PytketOptype::Measure,
// "Lazy" operations are translated as eager measurements in pytket,
// as there is no `Future<T>` type there.
QSystemOp::LazyMeasure => PytketOptype::Measure,
QSystemOp::Rz => PytketOptype::Rz,
QSystemOp::PhasedX => PytketOptype::PhasedX,
QSystemOp::ZZPhase => PytketOptype::ZZPhase,
QSystemOp::Reset => PytketOptype::Reset,
QSystemOp::QFree => {
// Mark the qubit inputs as explored and forget about them.
encoder.get_input_values(node, circ)?;
return Ok(EncodeStatus::Success);
}
QSystemOp::LazyMeasureReset | QSystemOp::MeasureReset => {
// These may require a pytket measurement followed by a reset.
return Ok(EncodeStatus::Unsupported);
}
QSystemOp::LazyMeasureLeaked => {
// No equivalent pytket operation.
return Ok(EncodeStatus::Unsupported);
}
QSystemOp::TryQAlloc => {
// Pytket circuits don't support the optional type returned by `TryQAlloc`.
return Ok(EncodeStatus::Unsupported);
}
};

// Most operations map directly to a pytket one.
encoder.emit_node(serial_op, node, circ)?;

Ok(EncodeStatus::Success)
}

fn encode_runtime_barrier_op<H: HugrView>(
&self,
node: H::Node,
_runtime_barrier_op: RuntimeBarrierDef,
circ: &Circuit<H>,
encoder: &mut PytketEncoderContext<H>,
) -> Result<EncodeStatus, PytketEncodeError<H::Node>> {
encoder.emit_node(PytketOptype::Barrier, node, circ)?;

Ok(EncodeStatus::Success)
}
}

impl PytketDecoder for QSystemEmitter {
fn op_types(&self) -> Vec<PytketOptype> {
// Process native optypes that are not supported by the `TketOp` emitter.
vec![
PytketOptype::PhasedX,
PytketOptype::ZZPhase,
PytketOptype::ZZMax,
]
}

fn op_to_hugr<'h>(
&self,
op: &tket_json_rs::circuit_json::Operation,
qubits: &[TrackedQubit],
bits: &[TrackedBit],
params: &[Arc<LoadedParameter>],
_opgroup: Option<&str>,
decoder: &mut PytketDecoderContext<'h>,
) -> Result<DecodeStatus, PytketDecodeError> {
let op = match op.op_type {
PytketOptype::PhasedX => QSystemOp::PhasedX,
PytketOptype::ZZPhase => QSystemOp::ZZPhase,
PytketOptype::ZZMax => {
// This is a ZZPhase with a 1/2 angle.
let param = decoder.load_parameter("pi/2");
decoder.add_node_with_wires(QSystemOp::ZZPhase, qubits, bits, &[param])?;
return Ok(DecodeStatus::Success);
}
_ => {
return Ok(DecodeStatus::Unsupported);
}
};
decoder.add_node_with_wires(op, qubits, bits, params)?;

Ok(DecodeStatus::Success)
}
}
Loading
Loading