Skip to content
Draft
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
05e3fb5
WIP add inlining annotation + pass
acl-cqc Mar 16, 2026
51a4e89
tests
acl-cqc Mar 16, 2026
9e9e722
Simplify cycle check, only need first cycle
acl-cqc Mar 17, 2026
30d924f
inline always funcs leaves first
acl-cqc Mar 17, 2026
ef247fe
Respect entrypoint? Oooph!
acl-cqc Mar 17, 2026
2a70f5b
Strange derive_more, make compile
acl-cqc Mar 23, 2026
865b6dd
Don't RemDeadFuncs (be selective); skip cycles for only-called-once
acl-cqc Mar 23, 2026
4ad40ea
Fix test: inlined func is removed
acl-cqc Mar 23, 2026
896e36a
TODO test entrypoint_scope
acl-cqc Mar 23, 2026
07871d5
cycles return iter, clippy
acl-cqc Mar 23, 2026
849bf73
more TODOs
acl-cqc Mar 24, 2026
eecf9b7
rm doclink
acl-cqc Mar 25, 2026
d1d1615
Merge remote-tracking branch 'origin/main' into acl/inline
acl-cqc Apr 28, 2026
1dd388a
Move annotation
acl-cqc Apr 28, 2026
c25a2cd
Remove only-called-once, simplify test
acl-cqc Apr 28, 2026
2bd976d
Simplify, clarify/comment
acl-cqc Apr 28, 2026
6fd9f0e
simplify: inline do_inline
acl-cqc Apr 28, 2026
5bbabec
renaming, re-export
acl-cqc Apr 28, 2026
0e3ad97
entrypoint_scope test
acl-cqc Apr 28, 2026
9fd49cf
test cycle of one always and one not
acl-cqc Apr 28, 2026
66e02c1
Add python (needs test)
acl-cqc Apr 28, 2026
677e48e
clippy
acl-cqc Apr 28, 2026
9284958
doc
acl-cqc Apr 28, 2026
312f07b
fmt + missing,
acl-cqc Apr 28, 2026
dbcf612
first python test
acl-cqc Apr 28, 2026
4554462
test failure, fiddling with imports, including type: ignore
acl-cqc Apr 28, 2026
8151e43
ruff format
acl-cqc Apr 28, 2026
a521495
improve tket-py rs comment
acl-cqc Apr 28, 2026
ce8d5b7
Declare InlineAlwaysError in passes.pyi
acl-cqc Apr 29, 2026
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
9 changes: 9 additions & 0 deletions tket-py/src/passes.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Passes for optimising circuits.

pub mod chunks;
mod inline_always;
mod inline_funcs;
mod scope;
pub mod tket1;
Expand All @@ -27,12 +28,14 @@ pub fn module(py: Python<'_>) -> PyResult<Bound<'_, PyModule>> {
m.add_function(wrap_pyfunction!(greedy_depth_reduce, &m)?)?;
m.add_function(wrap_pyfunction!(badger_optimise, &m)?)?;
m.add_function(wrap_pyfunction!(normalize_guppy, &m)?)?;
m.add_function(wrap_pyfunction!(self::inline_always::inline_always, &m)?)?;
m.add_function(wrap_pyfunction!(self::inline_funcs::inline_functions, &m)?)?;
m.add_class::<self::chunks::PyCircuitChunks>()?;
m.add_function(wrap_pyfunction!(self::chunks::chunks, &m)?)?;
m.add_function(wrap_pyfunction!(self::tket1::tket1_pass, &m)?)?;
m.add_function(wrap_pyfunction!(resolve_modifiers, &m)?)?;
m.add("PullForwardError", py.get_type::<PyPullForwardError>())?;
m.add("InlineAlwaysError", py.get_type::<PyInlineAlwaysError>())?;
m.add(
"InlineFunctionsError",
py.get_type::<PyInlineFunctionsError>(),
Expand All @@ -59,6 +62,12 @@ create_py_exception!(
"Errors from the modifer resolver pass."
);

create_py_exception!(
tket::passes::inline_always::InlineAlwaysError,
PyInlineAlwaysError,
"Error from `InlineAlwaysPass`"
);

create_py_exception!(
tket::passes::inline_funcs::InlineFuncsError,
PyInlineFunctionsError,
Expand Down
28 changes: 28 additions & 0 deletions tket-py/src/passes/inline_always.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//! Python bindings for the `InlineFunctions` pass.

use pyo3::prelude::*;

use tket::passes::{ComposablePass, WithScope};

use super::PyPassScope;
use crate::state::CompilationState;
use crate::utils::ConvertPyErr;

/// Inline acyclic function calls below the selected scope.
///
/// Parameters:
/// - heuristic: Heuristic used to choose which non-recursive functions to
/// inline. Defaults to `tket.passes.MaxSize(64)`.
/// - follow_inline_hints: Whether to follow compiler hints for inlining
/// functions.
#[pyfunction]
#[pyo3(signature = (circ, *, scope = None))]
pub(super) fn inline_always(
circ: &mut CompilationState,
scope: Option<PyPassScope>,
) -> PyResult<()> {
let py_scope = scope.unwrap_or_default();
let pass = tket::passes::InlineAlwaysPass::default_with_scope(py_scope.scope);
pass.run(&mut circ.hugr).convert_pyerrs()?;
Ok(())
}
49 changes: 49 additions & 0 deletions tket-py/test/test_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from tket.passes import (
_badger_optimise,
_greedy_depth_reduce,
InlineAlwaysError,
InlineAlwaysPass,
InlineFunctions,
inline_funcs,
NormalizeGuppy,
Expand All @@ -23,6 +25,7 @@
from tket.passes import PytketHugrPass
from pytket.passes import CliffordSimp, SquashRzPhasedX, SequencePass
from hugr.build.base import Hugr
import hugr.tys as tys

import numpy as np
import pytest
Expand Down Expand Up @@ -285,6 +288,52 @@ def test_modifier_execution() -> None:
np.testing.assert_allclose(computed_statevector, expected_statevector)


@pytest.mark.parametrize("annotate", [True, False])
def test_inline_always(annotate: bool) -> None:
import hugr.ops as ops
from hugr.build.dfg import Dfg

d = Dfg(tys.Tuple(tys.Qubit, tys.Qubit))

f_id = d.module_root_builder().define_function(
"id",
[tys.Qubit],
)
f_id.set_outputs(f_id.input_node[0])

if annotate:
f_id.metadata["tket.inline"] = "always"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should export the metadata key from rust, as hugr-py/rust/metadata.rs does, but we don't have a module set up for that yet


(tup,) = d.inputs()
(q1, q2) = d.add(ops.UnpackTuple()(tup))
call1 = d.call(f_id, q1)
call2 = d.call(f_id, q2)
(tup,) = d.add(ops.MakeTuple()(call1, call2))

d.set_outputs(tup)

# validate(d.hugr)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess there is no way to do this here?


InlineAlwaysPass()(d.hugr)
# validate(d.hugr)
assert _count_ops(d.hugr, "Call") == 0 if annotate else 2


def test_inline_always_cycle() -> None:
from hugr.build.function import Module

mod = Module()

f_recursive = mod.define_function("recurse", [tys.Qubit])
f_recursive.declare_outputs([tys.Qubit])
call = f_recursive.call(f_recursive, f_recursive.input_node[0])
f_recursive.set_outputs(call)

f_recursive.metadata["tket.inline"] = "always"
with pytest.raises(InlineAlwaysError):
InlineAlwaysPass()(mod.hugr)


def test_inline_functions() -> None:
hugr = _hugr_from_path("test_files/guppy_examples/fn_calls.hugr")

Expand Down
7 changes: 7 additions & 0 deletions tket-py/tket/_tket/passes.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ def normalize_guppy(
- remove_redundant_order_edges: Whether to remove redundant order edges.
"""

def inline_always(
circ: CompilationState,
*,
scope: PassScope = GlobalScope.PRESERVE_PUBLIC,
) -> None:
"""Inline functions marked with the `inline="always"` decorator below the selected scope."""

def inline_functions(
circ: CompilationState,
*,
Expand Down
37 changes: 36 additions & 1 deletion tket-py/tket/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from tket import _state
from . import inline_funcs
from .._tket import passes as _passes, optimiser as _optimiser

from .._tket.passes import InlineAlwaysError # type: ignore
from hugr.passes.composable import (
ComposablePass,
ComposedPass,
Expand All @@ -25,6 +25,8 @@
__all__ = [
"PytketHugrPass",
"PassResult",
"InlineAlwaysError",
"InlineAlwaysPass",
"InlineFuncsHeuristic",
"InlineFunctions",
"NormalizeGuppy",
Expand Down Expand Up @@ -147,6 +149,39 @@ def _run_tk(self, program: _state.CompilationState) -> _state.CompilationState:
return program


@dataclass
class InlineAlwaysPass(ComposablePass):
"""Inline functions marked with the `inline="always"` decorator below the selected scope."""

_scope: PassScope = GlobalScope.PRESERVE_PUBLIC

def run(self, hugr: Hugr, *, inplace: bool = True) -> PassResult:
return implement_pass_run(
self,
hugr=hugr,
inplace=inplace,
copy_call=lambda h: self._inline_always(h, inplace),
)

def with_scope(self, scope: PassScope) -> InlineAlwaysPass:
"""Set the scope of this pass and return self."""
self._scope = scope
return self

def _inline_always(self, hugr: Hugr, inplace: bool) -> PassResult:
tk_program = _state.CompilationState.from_python(hugr)

_passes.inline_always(
tk_program._inner,
scope=self._scope,
)

package = tk_program.to_python()
return PassResult.for_pass(
self, hugr=package.modules[0], inplace=inplace, result=None
)


@dataclass
class InlineFunctions(ComposablePass):
"""Inline acyclic function calls below the selected scope.
Expand Down
18 changes: 18 additions & 0 deletions tket/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ impl Metadata for MaxQubits {
type Type<'hugr> = u32;
}

/// Metadata that may be supplied for a function to indicate
/// that/when calls to it should be inlined.
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum InlineAnnotation {
/// Always inline calls to this function.
///
/// If this cannot be done, an error will be raised.
Always,
}

impl Metadata for InlineAnnotation {
type Type<'hugr> = Self;

const KEY: &'static str = "tket.inline";
}

/// Metadata key for traced rewrites that were applied during circuit transformation.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CircuitRewriteTraces;
Expand Down
2 changes: 2 additions & 0 deletions tket/src/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ pub mod inline_dfgs;
pub use inline_dfgs::InlineDFGsPass;

// Inline function calls.
pub mod inline_always;
pub use inline_always::InlineAlwaysPass;
pub mod inline_funcs;
pub use inline_funcs::InlineFunctionsPass;
#[expect(deprecated)]
Expand Down
Loading
Loading