Skip to content

Commit 394a610

Browse files
CalMacCQaborgna-q
andauthored
feat: expose the QSystemPass to Python (#1556)
closes #1553 Driveby: now ncludes a fix to how we resolve extensions in Python. See [076c176](076c176). Prior to this fix the transformed HUGR contained Custom ops rather than QSystem ops as we would expect. Another driveby: There was a Boolean flag called `hide_funcs` which did not have a corresponding `with_*` method in Rust. I assume this was an oversight so I added a method in [739870d](https://github.com/Quantinuum/tket2/pull/1556/commits/739870dc8167dcb5ca911346a044d51c0e354b5a)[739870d](https://github.com/Quantinuum/tket2/pull/1556/commits/739870dc8167dcb5ca911346a044d51c0e354b5a). --------- Co-authored-by: Agustín Borgna <agustin.borgna@quantinuum.com>
1 parent 4fc5a25 commit 394a610

8 files changed

Lines changed: 157 additions & 6 deletions

File tree

3.88 KB
Binary file not shown.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# /// script
2+
# requires-python = ">=3.13"
3+
# dependencies = [
4+
# "guppylang ==0.21.13",
5+
# ]
6+
# ///
7+
from pathlib import Path
8+
from sys import argv
9+
10+
from guppylang import guppy
11+
from guppylang.std.quantum import qubit, h, cx, rz
12+
from guppylang.std.angles import pi
13+
14+
15+
@guppy
16+
def flat_quantum_func(q0: qubit, q1: qubit) -> None:
17+
h(q0)
18+
rz(q0, 3 * pi / 4)
19+
cx(q0, q1)
20+
21+
22+
program = flat_quantum_func.compile_function()
23+
Path(argv[0]).with_suffix(".hugr").write_bytes(program.to_bytes())

tket-py/src/passes.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ use std::{cmp::min, convert::TryInto, fs, num::NonZeroUsize, path::PathBuf};
1111

1212
use pyo3::prelude::*;
1313
use tket::optimiser::badger::BadgerOptions;
14-
use tket::passes;
1514
use tket::passes::composable::{ComposablePass, WithScope};
1615
use tket::{Circuit, TketOp, op_matches};
1716

17+
use tket::passes;
18+
1819
use crate::optimiser::PyBadgerOptimiser;
1920
use crate::state::CompilationState;
2021
use crate::utils::{ConvertPyErr, create_py_exception};
@@ -32,6 +33,7 @@ pub fn module(py: Python<'_>) -> PyResult<Bound<'_, PyModule>> {
3233
m.add_function(wrap_pyfunction!(self::chunks::chunks, &m)?)?;
3334
m.add_function(wrap_pyfunction!(self::tket1::tket1_pass, &m)?)?;
3435
m.add_function(wrap_pyfunction!(resolve_modifiers, &m)?)?;
36+
m.add_function(wrap_pyfunction!(qsystem_rebase_pass, &m)?)?;
3537
m.add("PullForwardError", py.get_type::<PyPullForwardError>())?;
3638
m.add(
3739
"InlineFunctionsError",
@@ -64,6 +66,13 @@ create_py_exception!(
6466
PyInlineFunctionsError,
6567
"Errors from the function inlining pass."
6668
);
69+
70+
create_py_exception!(
71+
tket_qsystem::QSystemPassError,
72+
PyQSystemPassError,
73+
"Errors from the QSystem rebase pass."
74+
);
75+
6776
/// Flatten the structure of a Guppy-generated program to enable additional optimisations.
6877
///
6978
/// This should normally be called first before other optimisations.
@@ -203,3 +212,26 @@ fn resolve_modifiers(circ: &mut CompilationState, scope: Option<PyPassScope>) ->
203212
pass.run(&mut circ.hugr).convert_pyerrs()?;
204213
Ok(())
205214
}
215+
216+
#[pyfunction]
217+
#[pyo3(signature=(circ, *, constant_fold = true, monomorphize = true, force_order = true, lazify = true, hide_funcs = true, scope = None))]
218+
fn qsystem_rebase_pass(
219+
circ: &mut CompilationState,
220+
constant_fold: bool,
221+
monomorphize: bool,
222+
force_order: bool,
223+
lazify: bool,
224+
hide_funcs: bool,
225+
scope: Option<PyPassScope>,
226+
) -> PyResult<()> {
227+
let py_scope = scope.unwrap_or_default();
228+
let qsystem_pass = tket_qsystem::QSystemPass::default_with_scope(py_scope.scope)
229+
.with_constant_fold(constant_fold)
230+
.with_monomorphize(monomorphize)
231+
.with_force_order(force_order)
232+
.with_lazify(lazify)
233+
.with_hide_funcs(hide_funcs);
234+
235+
qsystem_pass.run(&mut circ.hugr).convert_pyerrs()?;
236+
Ok(())
237+
}

tket-py/test/test_pass.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
from hypothesis.strategies._internal import SearchStrategy
2222
from hypothesis import given, settings
2323

24-
from tket.passes import PytketHugrPass
24+
25+
from tket.passes import PytketHugrPass, QSystemPass
2526
from pytket.passes import (
2627
CliffordSimp,
2728
SquashRzPhasedX,
@@ -323,3 +324,13 @@ def test_issue_1516() -> None:
323324
opt_hugr = opt(hugr, inplace=False)
324325

325326
CompilationState.from_python(opt_hugr).validate()
327+
328+
329+
def test_python_qsystem_pass() -> None:
330+
normalize = NormalizeGuppy()
331+
hugr = normalize(_hugr_from_path("test_files/guppy_examples/flat_quantum.hugr"))
332+
qsystem_pass = QSystemPass()
333+
qsystem_hugr = qsystem_pass(hugr)
334+
assert _count_ops(qsystem_hugr, "ZZPhase") == 1
335+
assert _count_ops(qsystem_hugr, "Custom") == 0
336+
assert _count_ops(qsystem_hugr, "tket.quantum") == 0

tket-py/tket/_state/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from hugr.envelope import EnvelopeConfig
88
from hugr.ext import ExtensionRegistry
9+
from tket_exts import tket_registry
910
from .._tket import state as _state
1011
from .build import CircBuild, Command
1112

@@ -97,11 +98,10 @@ def to_python(self) -> Package:
9798
"""Convert this CompilationState back to a python Hugr package."""
9899
# Convert the inner hugr to bytes and load it in Python.
99100
hugr_bytes = self._inner.to_bytes()
100-
package = Package.from_bytes(hugr_bytes, self._py_extensions)
101+
package = Package.from_bytes(hugr_bytes, tket_registry())
101102
if self._py_extensions is not None:
102103
# Resolve the extensions in the loaded package using the python registry, if needed.
103-
# TODO: Use the `package.resolve_extensions` for clarity once it's been released in `hugr-py 0.16.0`.
104-
package.used_extensions(self._py_extensions)
104+
package.resolve_extensions(self._py_extensions)
105105
return package
106106

107107
@staticmethod

tket-py/tket/_tket/passes.pyi

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,21 @@ def resolve_modifiers(
9999
:param circ: The input program as a CompilationState.
100100
:param scope: A scope to control how the pass is applied to HUGR regions.
101101
"""
102+
103+
def qsystem_rebase_pass(
104+
circ: CompilationState,
105+
constant_fold: bool = True,
106+
monomorphize: bool = True,
107+
force_order: bool = True,
108+
lazify: bool = True,
109+
hide_funcs: bool = True,
110+
scope: PassScope | None = None,
111+
) -> None:
112+
"""Runs a rust backed pass to convert quantum ops to qsystem ops.
113+
114+
:param constant_fold: Whether to perform constant folding.
115+
:param monomorphize: Whether to monomorphize generic functions.
116+
:param force_order: Whether to enforce total ordering of all HUGR operations.
117+
:param lazify: Whether to replace measurements with lazy measurements.
118+
:param hide_funcs: Make all HUGR functions private.
119+
"""

tket-py/tket/passes/__init__.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"InlineFunctions",
3030
"NormalizeGuppy",
3131
"ModifierResolverPass",
32+
"QSystemPass",
3233
]
3334

3435

@@ -294,3 +295,59 @@ def _run_tk(self, program: _state.CompilationState) -> _state.CompilationState:
294295
scope=self._scope,
295296
)
296297
return program
298+
299+
300+
@dataclass(kw_only=True)
301+
class QSystemPass(ComposablePass):
302+
"""A pass to convert quantum ops to qsystem ops.
303+
304+
Parameters:
305+
- constant_fold: Whether to perform constant folding.
306+
- monomorphize: Whether to monomorphize generic functions.
307+
- force_order: Whether to enforce total ordering of all HUGR operations.
308+
- lazify: Whether to replace measurements with lazy measurements.
309+
- hide_funcs: Whether to mark all functions as private.
310+
"""
311+
312+
constant_fold: bool = True
313+
monomorphize: bool = True
314+
force_order: bool = True
315+
lazify: bool = True
316+
hide_funcs: bool = True
317+
_scope: PassScope = GlobalScope.PRESERVE_PUBLIC
318+
319+
def run(self, hugr: Hugr, *, inplace: bool = True) -> PassResult:
320+
return implement_pass_run(
321+
self,
322+
hugr=hugr,
323+
inplace=inplace,
324+
copy_call=lambda h: self._qsystem_rebase(h, inplace),
325+
)
326+
327+
def with_scope(self, scope: PassScope) -> QSystemPass:
328+
"""Set the scope of this pass and return self."""
329+
self._scope = scope
330+
return self
331+
332+
def _qsystem_rebase(self, hugr: Hugr, inplace: bool) -> PassResult:
333+
tk_program = _state.CompilationState.from_python(hugr)
334+
335+
self._run_tk(tk_program)
336+
337+
package = tk_program.to_python()
338+
return PassResult.for_pass(
339+
self, hugr=package.modules[0], inplace=inplace, result=None
340+
)
341+
342+
def _run_tk(self, program: _state.CompilationState) -> _state.CompilationState:
343+
"""Run the pass in the CompilationState"""
344+
_passes.qsystem_rebase_pass(
345+
program._inner,
346+
constant_fold=self.constant_fold,
347+
monomorphize=self.monomorphize,
348+
force_order=self.force_order,
349+
lazify=self.lazify,
350+
hide_funcs=self.hide_funcs,
351+
scope=self._scope,
352+
)
353+
return program

tket-qsystem/src/lib.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,16 @@ impl QSystemPass {
140140
self
141141
}
142142

143+
/// Makes all functions private.
144+
///
145+
/// On by default
146+
///
147+
/// When enabled all functions are marked as private. This enables LLVM to drop functions which are not called.
148+
pub fn with_hide_funcs(mut self, hide_funcs: bool) -> Self {
149+
self.hide_funcs = hide_funcs;
150+
self
151+
}
152+
143153
/// Add order edges in the HUGR regions to force qubit frees to be as early
144154
/// as possible, quantum ops to be as early as possible, and Future::Reads
145155
/// to be as late as possible.
@@ -444,9 +454,9 @@ mod test {
444454
// Run again without hiding...
445455
let mut hugr_public = orig;
446456
QSystemPass {
447-
hide_funcs: false,
448457
..Default::default()
449458
}
459+
.with_hide_funcs(false)
450460
.run(&mut hugr_public)
451461
.unwrap();
452462

0 commit comments

Comments
 (0)