Skip to content

Commit 38d1c6f

Browse files
authored
feat: add qsystem op for measure leaked (#924)
Closes: #905 Corresponding eldarion PR: quantinuum-dev/eldarion#309
1 parent 77677ee commit 38d1c6f

13 files changed

+344
-12
lines changed

justfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ recompile-eccs:
5252
gen-extensions:
5353
cargo run -p tket2-hseries gen-extensions -o tket2-exts/src/tket2_exts/data
5454

55+
# Interactively update snapshot tests (requires `cargo-insta`)
56+
update-snapshots:
57+
cargo insta review
58+
5559
# Runs a rust and a python command, depending on the `language` variable.
5660
#
5761
# If `language` is set to `rust` or `python`, only run the command for that language.

tket2-exts/src/tket2_exts/data/tket2/qsystem.json

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "0.4.0",
2+
"version": "0.4.1",
33
"name": "tket2.qsystem",
44
"types": {},
55
"operations": {
@@ -37,6 +37,47 @@
3737
},
3838
"binary": false
3939
},
40+
"LazyMeasureLeaked": {
41+
"extension": "tket2.qsystem",
42+
"name": "LazyMeasureLeaked",
43+
"description": "Measure a qubit (return 0 or 1) or detect leakage (return 2).",
44+
"signature": {
45+
"params": [],
46+
"body": {
47+
"input": [
48+
{
49+
"t": "Q"
50+
}
51+
],
52+
"output": [
53+
{
54+
"t": "Opaque",
55+
"extension": "tket2.futures",
56+
"id": "Future",
57+
"args": [
58+
{
59+
"tya": "Type",
60+
"ty": {
61+
"t": "Opaque",
62+
"extension": "arithmetic.int.types",
63+
"id": "int",
64+
"args": [
65+
{
66+
"tya": "BoundedNat",
67+
"n": 6
68+
}
69+
],
70+
"bound": "C"
71+
}
72+
}
73+
],
74+
"bound": "A"
75+
}
76+
]
77+
}
78+
},
79+
"binary": false
80+
},
4081
"LazyMeasureReset": {
4182
"extension": "tket2.qsystem",
4283
"name": "LazyMeasureReset",

tket2-hseries/src/extension/qsystem.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use hugr::{
2121
arithmetic::{
2222
float_ops::FloatOps,
2323
float_types::{float64_type, ConstF64, EXTENSION as FLOAT_TYPES},
24+
int_types::int_type,
2425
},
2526
collections::array::{array_type_parametric, ArrayOpBuilder},
2627
},
@@ -45,7 +46,7 @@ pub use lower::{check_lowered, lower_tk2_op, LowerTk2Error, LowerTket2ToQSystemP
4546
/// The "tket2.qsystem" extension id.
4647
pub const EXTENSION_ID: ExtensionId = ExtensionId::new_unchecked("tket2.qsystem");
4748
/// The "tket2.qsystem" extension version.
48-
pub const EXTENSION_VERSION: Version = Version::new(0, 4, 0);
49+
pub const EXTENSION_VERSION: Version = Version::new(0, 4, 1);
4950

5051
lazy_static! {
5152
/// The "tket2.qsystem" extension.
@@ -95,6 +96,7 @@ pub enum QSystemOp {
9596
QFree,
9697
Reset,
9798
MeasureReset,
99+
LazyMeasureLeaked,
98100
}
99101

100102
impl MakeOpDef for QSystemOp {
@@ -108,6 +110,7 @@ impl MakeOpDef for QSystemOp {
108110
let two_qb_row = TypeRow::from(vec![qb_t(), qb_t()]);
109111
match self {
110112
LazyMeasure => Signature::new(qb_t(), future_type(bool_t())),
113+
LazyMeasureLeaked => Signature::new(qb_t(), future_type(int_type(6))),
111114
LazyMeasureReset => Signature::new(qb_t(), vec![qb_t(), future_type(bool_t())]),
112115
Reset => Signature::new(one_qb_row.clone(), one_qb_row),
113116
ZZPhase => Signature::new(vec![qb_t(), qb_t(), float64_type()], two_qb_row),
@@ -144,6 +147,9 @@ impl MakeOpDef for QSystemOp {
144147
QSystemOp::QFree => "Free a qubit (lose track of it).",
145148
QSystemOp::Reset => "Reset a qubit to the Z |0> eigenstate.",
146149
QSystemOp::MeasureReset => "Measure a qubit and reset it to the Z |0> eigenstate.",
150+
QSystemOp::LazyMeasureLeaked => {
151+
"Measure a qubit (return 0 or 1) or detect leakage (return 2)."
152+
}
147153
QSystemOp::LazyMeasureReset => {
148154
"Lazily measure a qubit and reset it to the Z |0> eigenstate."
149155
}
@@ -227,6 +233,13 @@ pub trait QSystemOpBuilder: Dataflow + UnwrapBuilder + ArrayOpBuilder {
227233
.out_wire(0))
228234
}
229235

236+
/// Add a "tket2.qsystem.LazyMeasureLeaked" op.
237+
fn add_lazy_measure_leaked(&mut self, qb: Wire) -> Result<Wire, BuildError> {
238+
Ok(self
239+
.add_dataflow_op(QSystemOp::LazyMeasureLeaked, [qb])?
240+
.out_wire(0))
241+
}
242+
230243
/// Add a "tket2.qsystem.LazyMeasureReset" op.
231244
fn add_lazy_measure_reset(&mut self, qb: Wire) -> Result<[Wire; 2], BuildError> {
232245
Ok(self
@@ -568,6 +581,19 @@ mod test {
568581
assert_matches!(hugr.validate(), Ok(_));
569582
}
570583

584+
#[test]
585+
fn leaked() {
586+
let hugr = {
587+
let mut func_builder =
588+
FunctionBuilder::new("leaked", Signature::new(qb_t(), vec![int_type(6)])).unwrap();
589+
let [qb] = func_builder.input_wires_arr();
590+
let lazy_i = func_builder.add_lazy_measure_leaked(qb).unwrap();
591+
let [i] = func_builder.add_read(lazy_i, int_type(6)).unwrap();
592+
func_builder.finish_hugr_with_outputs([i]).unwrap()
593+
};
594+
assert_matches!(hugr.validate(), Ok(_));
595+
}
596+
571597
#[test]
572598
fn all_ops() {
573599
let hugr = {

tket2-hseries/src/llvm/futures.rs

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use anyhow::{anyhow, Result};
55
use hugr::extension::prelude::bool_t;
66
use hugr::extension::simple_op::MakeExtensionOp;
77
use hugr::ops::{ExtensionOp, Value};
8+
use hugr::std_extensions::arithmetic::int_types::INT_TYPES;
89
use hugr::types::TypeArg;
910
use hugr::{HugrView, Node};
1011
use hugr_llvm::custom::CodegenExtension;
@@ -36,10 +37,13 @@ impl CodegenExtension for FuturesCodegenExtension {
3637
(futures::EXTENSION_ID, FUTURE_TYPE_NAME.to_owned()),
3738
|session, hugr_type| {
3839
match (hugr_type.name().as_str(), hugr_type.args()) {
39-
// For now, we only support future bools
40+
// For now, we only support future bools and ints
4041
("Future", [TypeArg::Type { ty }]) if *ty == bool_t() => {
4142
Ok(future_type(session.iw_context()))
4243
}
44+
("Future", [TypeArg::Type { ty }]) if *ty == INT_TYPES[6] => {
45+
Ok(future_type(session.iw_context()))
46+
}
4347
_ => Err(anyhow!(
4448
"FuturesCodegenExtension: Unsupported type: {}",
4549
hugr_type
@@ -74,6 +78,10 @@ impl<'c, H: HugrView<Node = Node>> FuturesEmitter<'c, '_, '_, H> {
7478
self.iw_context().bool_type()
7579
}
7680

81+
fn ll_uint_type(&self) -> IntType<'c> {
82+
self.iw_context().i64_type()
83+
}
84+
7785
fn builder(&self) -> &Builder<'c> {
7886
self.0.builder()
7987
}
@@ -101,10 +109,19 @@ impl<'c, H: HugrView<Node = Node>> FuturesEmitter<'c, '_, '_, H> {
101109
self.0.get_extern_func("___read_future_bool", func_type)
102110
}
103111

112+
fn get_func_read_uint(&self) -> Result<FunctionValue<'c>> {
113+
let func_type = self
114+
.ll_uint_type()
115+
.fn_type(&[self.ll_future_type().into()], false);
116+
self.0.get_extern_func("___read_future_uint", func_type)
117+
}
118+
104119
fn emit(&mut self, args: EmitOpArgs<'c, '_, ExtensionOp, H>) -> Result<()> {
105-
let op = FutureOp::from_optype(&args.node().generalise()).unwrap().op;
106-
match op {
107-
FutureOpDef::Dup => {
120+
let future_op = FutureOp::from_optype(&args.node().generalise()).unwrap();
121+
let op = &future_op.op;
122+
let typ = &future_op.typ;
123+
match (op, typ) {
124+
(FutureOpDef::Dup, _) => {
108125
let [arg] = args
109126
.inputs
110127
.try_into()
@@ -114,7 +131,7 @@ impl<'c, H: HugrView<Node = Node>> FuturesEmitter<'c, '_, '_, H> {
114131
.build_call(func, &[arg.into()], "inc_refcount")?;
115132
args.outputs.finish(self.builder(), [arg, arg])
116133
}
117-
FutureOpDef::Free => {
134+
(FutureOpDef::Free, _) => {
118135
let [arg] = args
119136
.inputs
120137
.try_into()
@@ -124,7 +141,7 @@ impl<'c, H: HugrView<Node = Node>> FuturesEmitter<'c, '_, '_, H> {
124141
.build_call(func, &[arg.into()], "dec_refcount")?;
125142
args.outputs.finish(self.builder(), [])
126143
}
127-
FutureOpDef::Read => {
144+
(FutureOpDef::Read, ty) if *ty == bool_t() => {
128145
let [arg] = args
129146
.inputs
130147
.try_into()
@@ -146,29 +163,57 @@ impl<'c, H: HugrView<Node = Node>> FuturesEmitter<'c, '_, '_, H> {
146163
.build_select(result_i1, true_val, false_val, "measure")?;
147164
args.outputs.finish(self.builder(), [result])
148165
}
166+
(FutureOpDef::Read, ty) if *ty == INT_TYPES[6] => {
167+
let [arg] = args
168+
.inputs
169+
.try_into()
170+
.map_err(|_| anyhow!("Read expects a single input"))?;
171+
let read_func = self.get_func_read_uint()?;
172+
let result = self
173+
.builder()
174+
.build_call(read_func, &[arg.into()], "read_uint")?
175+
.try_as_basic_value()
176+
.unwrap_left();
177+
let dec_refcount_func = self.get_func_dec_refcount()?;
178+
self.builder()
179+
.build_call(dec_refcount_func, &[arg.into()], "dec_refcount")?;
180+
args.outputs.finish(self.builder(), [result])
181+
}
182+
_ => Err(anyhow!(
183+
"Unsupported future operation: {:?} with type: {}",
184+
op,
185+
typ
186+
)),
149187
}
150188
}
151189
}
152190

153191
#[cfg(test)]
154192
mod test {
155193
use hugr::extension::simple_op::MakeRegisteredOp;
194+
use hugr::std_extensions::arithmetic::int_types::int_type;
156195
use hugr_llvm::check_emission;
157196
use hugr_llvm::test::llvm_ctx;
158197
use hugr_llvm::test::single_op_hugr;
159198
use hugr_llvm::test::TestContext;
160199

161200
use super::*;
162201
#[rstest::rstest]
163-
#[case::read(1,FutureOp { op: FutureOpDef::Read, typ: bool_t() })]
164-
#[case::dup(2,FutureOp { op: FutureOpDef::Dup, typ: bool_t() })]
165-
#[case::free(3,FutureOp { op: FutureOpDef::Free, typ: bool_t() })]
202+
#[case::read_bool(1,FutureOp { op: FutureOpDef::Read, typ: bool_t() })]
203+
#[case::dup_bool(2,FutureOp { op: FutureOpDef::Dup, typ: bool_t() })]
204+
#[case::free_bool(3,FutureOp { op: FutureOpDef::Free, typ: bool_t() })]
205+
#[case::read_int(4,FutureOp { op: FutureOpDef::Read, typ: int_type(6) })]
206+
#[case::dup_int(5,FutureOp { op: FutureOpDef::Dup, typ: int_type(6) })]
207+
#[case::free_int(6,FutureOp { op: FutureOpDef::Free, typ: int_type(6) })]
166208
fn emit_futures_codegen(
167209
#[case] _i: i32,
168210
#[with(_i)] mut llvm_ctx: TestContext,
169211
#[case] op: FutureOp,
170212
) {
171-
llvm_ctx.add_extensions(|ceb| ceb.add_extension(FuturesCodegenExtension));
213+
llvm_ctx.add_extensions(|ceb| {
214+
ceb.add_extension(FuturesCodegenExtension)
215+
.add_default_int_extensions()
216+
});
172217
let hugr = single_op_hugr(op.to_extension_op().unwrap().into());
173218
check_emission!(hugr, llvm_ctx);
174219
}

tket2-hseries/src/llvm/qsystem.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ enum RuntimeFunction {
5252
QAlloc,
5353
QFree,
5454
Measure,
55+
LazyMeasureLeaked,
5556
LazyMeasure,
5657
Reset,
5758
}
@@ -65,6 +66,7 @@ impl RuntimeFunction {
6566
RuntimeFunction::QAlloc => "___qalloc",
6667
RuntimeFunction::QFree => "___qfree",
6768
RuntimeFunction::Measure => "___measure",
69+
RuntimeFunction::LazyMeasureLeaked => "___lazy_measure_leaked",
6870
RuntimeFunction::LazyMeasure => "___lazy_measure",
6971
RuntimeFunction::Reset => "___reset",
7072
}
@@ -94,6 +96,9 @@ impl RuntimeFunction {
9496
RuntimeFunction::QAlloc => qb_type.fn_type(&[], false),
9597
RuntimeFunction::QFree => iwc.void_type().fn_type(&[qb_type.into()], false),
9698
RuntimeFunction::Measure => iwc.bool_type().fn_type(&[qb_type.into()], false),
99+
RuntimeFunction::LazyMeasureLeaked => {
100+
future_type(iwc).fn_type(&[qb_type.into()], false)
101+
}
97102
RuntimeFunction::LazyMeasure => future_type(iwc).fn_type(&[qb_type.into()], false),
98103
RuntimeFunction::Reset => iwc.void_type().fn_type(&[qb_type.into()], false),
99104
}
@@ -220,6 +225,28 @@ impl<PCG: PreludeCodegen> QSystemCodegenExtension<PCG> {
220225
)?;
221226
args.outputs.finish(builder, [result])
222227
}
228+
// Measure qubit in Z basis or detect leakage, not forcing to a boolean
229+
QSystemOp::LazyMeasureLeaked => {
230+
let builder = context.builder();
231+
let [qb] = args
232+
.inputs
233+
.try_into()
234+
.map_err(|_| anyhow!("LazyMeasureLeaked expects one input"))?;
235+
let result = builder
236+
.build_call(
237+
self.runtime_func(context, RuntimeFunction::LazyMeasureLeaked)?,
238+
&[qb.into()],
239+
"lazy_measure_leaked",
240+
)?
241+
.try_as_basic_value()
242+
.unwrap_left();
243+
builder.build_call(
244+
self.runtime_func(context, RuntimeFunction::QFree)?,
245+
&[qb.into()],
246+
"qfree",
247+
)?;
248+
args.outputs.finish(builder, [result])
249+
}
223250
QSystemOp::LazyMeasureReset => {
224251
let builder = context.builder();
225252
let [qb] = args
@@ -336,6 +363,7 @@ mod test {
336363
#[case::qfree(7, QSystemOp::QFree)]
337364
#[case::reset(8, QSystemOp::Reset)]
338365
#[case::measure_reset(9, QSystemOp::MeasureReset)]
366+
#[case::lazy_measure_leaked(10, QSystemOp::LazyMeasureLeaked)]
339367
fn emit_qsystem_codegen(
340368
#[case] _i: i32,
341369
#[with(_i)] mut llvm_ctx: TestContext,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
source: tket2-hseries/src/llvm/futures.rs
3+
expression: mod_str
4+
---
5+
; ModuleID = 'test_context'
6+
source_filename = "test_context"
7+
8+
define i64 @_hl.main.1(i64 %0) {
9+
alloca_block:
10+
br label %entry_block
11+
12+
entry_block: ; preds = %alloca_block
13+
%read_uint = call i64 @___read_future_uint(i64 %0)
14+
call void @___dec_future_refcount(i64 %0)
15+
ret i64 %read_uint
16+
}
17+
18+
declare i64 @___read_future_uint(i64)
19+
20+
declare void @___dec_future_refcount(i64)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
source: tket2-hseries/src/llvm/futures.rs
3+
expression: mod_str
4+
---
5+
; ModuleID = 'test_context'
6+
source_filename = "test_context"
7+
8+
define { i64, i64 } @_hl.main.1(i64 %0) {
9+
alloca_block:
10+
br label %entry_block
11+
12+
entry_block: ; preds = %alloca_block
13+
call void @___inc_future_refcount(i64 %0)
14+
%mrv = insertvalue { i64, i64 } undef, i64 %0, 0
15+
%mrv6 = insertvalue { i64, i64 } %mrv, i64 %0, 1
16+
ret { i64, i64 } %mrv6
17+
}
18+
19+
declare void @___inc_future_refcount(i64)

0 commit comments

Comments
 (0)