Skip to content

Commit 6d2568a

Browse files
committed
feat!: More flexible pytket encoding
1 parent 5ae0ab9 commit 6d2568a

File tree

25 files changed

+2470
-989
lines changed

25 files changed

+2470
-989
lines changed

tket2-py/src/circuit.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ create_py_exception!(
7676
);
7777

7878
create_py_exception!(
79-
tket2::serialize::pytket::TK1ConvertError,
79+
tket2::serialize::pytket::Tk1ConvertError,
8080
PyTK1ConvertError,
8181
"Error type for the conversion between tket2 and tket1 operations."
8282
);

tket2/Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,17 @@ itertools = { workspace = true }
5050
petgraph = { workspace = true }
5151
portmatching = { workspace = true, optional = true, features = ["serde"] }
5252
derive_more = { workspace = true, features = [
53-
"error",
53+
"debug",
5454
"display",
55+
"error",
5556
"from",
5657
"into",
58+
"sum",
59+
"add",
5760
] }
5861
hugr = { workspace = true }
5962
hugr-core = { workspace = true }
60-
portgraph = { workspace = true, features = ["serde"] }
63+
portgraph = { workspace = true, features = ["serde", "petgraph"] }
6164
strum = { workspace = true, features = ["derive"] }
6265
fxhash = { workspace = true }
6366
indexmap = { workspace = true }

tket2/src/extension.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
use std::sync::Arc;
66

7-
use crate::serialize::pytket::OpaqueTk1Op;
7+
use crate::serialize::pytket::extension::OpaqueTk1Op;
88
use crate::Tk2Op;
99
use hugr::extension::simple_op::MakeOpDef;
1010
use hugr::extension::{
@@ -32,10 +32,10 @@ use sympy::SympyOpDef;
3232
pub const TKET1_EXTENSION_ID: ExtensionId = IdentList::new_unchecked("TKET1");
3333

3434
/// The name for opaque TKET1 operations.
35-
pub const TKET1_OP_NAME: SmolStr = SmolStr::new_inline("TKET1 Json Op");
35+
pub const TKET1_OP_NAME: SmolStr = SmolStr::new_inline("tk1op");
3636

3737
/// The ID of an opaque TKET1 operation metadata.
38-
pub const TKET1_PAYLOAD_NAME: SmolStr = SmolStr::new_inline("TKET1 Json Payload");
38+
pub const TKET1_PAYLOAD_NAME: SmolStr = SmolStr::new_inline("TKET1-json-payload");
3939

4040
/// Current version of the TKET 1 extension
4141
pub const TKET1_EXTENSION_VERSION: Version = Version::new(0, 1, 0);

tket2/src/extension/rotation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ lazy_static! {
3131
}
3232

3333
/// Identifier for the rotation type.
34-
const ROTATION_TYPE_ID: SmolStr = SmolStr::new_inline("rotation");
34+
pub const ROTATION_TYPE_ID: SmolStr = SmolStr::new_inline("rotation");
3535
/// Rotation type (as [CustomType])
3636
pub fn rotation_custom_type(extension_ref: &Weak<Extension>) -> CustomType {
3737
CustomType::new(

tket2/src/ops.rs

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::sync::{Arc, Weak};
22

33
use crate::extension::bool::bool_type;
44
use crate::extension::rotation::rotation_type;
5-
use crate::extension::sympy::{SympyOpDef, SYM_OP_ID};
5+
use crate::extension::sympy::SympyOpDef;
66
use crate::extension::{TKET2_EXTENSION, TKET2_EXTENSION_ID as EXTENSION_ID};
77
use hugr::ops::custom::ExtensionOp;
88
use hugr::types::Type;
@@ -14,7 +14,7 @@ use hugr::{
1414
},
1515
ops::OpType,
1616
type_row,
17-
types::{type_param::TypeArg, Signature},
17+
types::Signature,
1818
};
1919

2020
use derive_more::{Display, Error};
@@ -190,29 +190,6 @@ pub fn symbolic_constant_op(arg: String) -> OpType {
190190
SympyOpDef.with_expr(arg).into()
191191
}
192192

193-
/// match against a symbolic constant
194-
pub(crate) fn match_symb_const_op(op: &OpType) -> Option<String> {
195-
// Extract the symbol for a symbolic operation node.
196-
let symbol_from_typeargs = |args: &[TypeArg]| -> String {
197-
args.first()
198-
.and_then(|arg| match arg {
199-
TypeArg::String { arg } => Some(arg.clone()),
200-
_ => None,
201-
})
202-
.unwrap_or_else(|| panic!("Found an invalid type arg in a symbolic operation node."))
203-
};
204-
205-
if let OpType::ExtensionOp(e) = op {
206-
if e.def().name() == &SYM_OP_ID && e.def().extension_id() == &EXTENSION_ID {
207-
Some(symbol_from_typeargs(e.args()))
208-
} else {
209-
None
210-
}
211-
} else {
212-
None
213-
}
214-
}
215-
216193
#[cfg(test)]
217194
pub(crate) mod test {
218195

tket2/src/serialize/pytket.rs

Lines changed: 82 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22
33
mod decoder;
44
mod encoder;
5-
mod op;
6-
mod param;
5+
pub mod extension;
76

7+
pub use encoder::{default_encoder_config, Tk1EncoderConfig, Tk1EncoderContext};
8+
pub use extension::PytketEmitter;
9+
10+
use hugr::core::HugrNode;
811
use hugr::types::Type;
912

10-
use hugr::Node;
13+
use hugr::Wire;
1114
use itertools::Itertools;
12-
// Required for serialising ops in the tket1 hugr extension.
13-
pub(crate) use op::serialised::OpaqueTk1Op;
1415

1516
#[cfg(test)]
1617
mod tests;
@@ -29,8 +30,7 @@ use tket_json_rs::register::{Bit, ElementId, Qubit};
2930

3031
use crate::circuit::Circuit;
3132

32-
use self::decoder::Tk1Decoder;
33-
use self::encoder::Tk1Encoder;
33+
use self::decoder::Tk1DecoderContext;
3434

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

@@ -66,11 +66,11 @@ pub trait TKETDecode: Sized {
6666
}
6767

6868
impl TKETDecode for SerialCircuit {
69-
type DecodeError = TK1ConvertError;
70-
type EncodeError = TK1ConvertError;
69+
type DecodeError = Tk1ConvertError;
70+
type EncodeError = Tk1ConvertError;
7171

7272
fn decode(self) -> Result<Circuit, Self::DecodeError> {
73-
let mut decoder = Tk1Decoder::try_new(&self)?;
73+
let mut decoder = Tk1DecoderContext::try_new(&self)?;
7474

7575
if !self.phase.is_empty() {
7676
// TODO - add a phase gate
@@ -85,31 +85,29 @@ impl TKETDecode for SerialCircuit {
8585
}
8686

8787
fn encode(circ: &Circuit) -> Result<Self, Self::EncodeError> {
88-
let mut encoder = Tk1Encoder::new(circ)?;
89-
for com in circ.commands() {
90-
let optype = com.optype();
91-
encoder.add_command(com.clone(), optype)?;
92-
}
93-
Ok(encoder.finish(circ))
88+
let config = default_encoder_config();
89+
let mut encoder = Tk1EncoderContext::new(circ, config)?;
90+
encoder.run_encoder(circ)?;
91+
encoder.finish(circ)
9492
}
9593
}
9694

9795
/// Load a TKET1 circuit from a JSON file.
98-
pub fn load_tk1_json_file(path: impl AsRef<Path>) -> Result<Circuit, TK1ConvertError> {
96+
pub fn load_tk1_json_file(path: impl AsRef<Path>) -> Result<Circuit, Tk1ConvertError> {
9997
let file = fs::File::open(path)?;
10098
let reader = io::BufReader::new(file);
10199
load_tk1_json_reader(reader)
102100
}
103101

104102
/// Load a TKET1 circuit from a JSON reader.
105-
pub fn load_tk1_json_reader(json: impl io::Read) -> Result<Circuit, TK1ConvertError> {
103+
pub fn load_tk1_json_reader(json: impl io::Read) -> Result<Circuit, Tk1ConvertError> {
106104
let ser: SerialCircuit = serde_json::from_reader(json)?;
107105
let circ: Circuit = ser.decode()?;
108106
Ok(circ)
109107
}
110108

111109
/// Load a TKET1 circuit from a JSON string.
112-
pub fn load_tk1_json_str(json: &str) -> Result<Circuit, TK1ConvertError> {
110+
pub fn load_tk1_json_str(json: &str) -> Result<Circuit, Tk1ConvertError> {
113111
let reader = json.as_bytes();
114112
load_tk1_json_reader(reader)
115113
}
@@ -122,7 +120,7 @@ pub fn load_tk1_json_str(json: &str) -> Result<Circuit, TK1ConvertError> {
122120
///
123121
/// Returns an error if the circuit is not flat or if it contains operations not
124122
/// supported by pytket.
125-
pub fn save_tk1_json_file(circ: &Circuit, path: impl AsRef<Path>) -> Result<(), TK1ConvertError> {
123+
pub fn save_tk1_json_file(circ: &Circuit, path: impl AsRef<Path>) -> Result<(), Tk1ConvertError> {
126124
let file = fs::File::create(path)?;
127125
let writer = io::BufWriter::new(file);
128126
save_tk1_json_writer(circ, writer)
@@ -136,7 +134,7 @@ pub fn save_tk1_json_file(circ: &Circuit, path: impl AsRef<Path>) -> Result<(),
136134
///
137135
/// Returns an error if the circuit is not flat or if it contains operations not
138136
/// supported by pytket.
139-
pub fn save_tk1_json_writer(circ: &Circuit, w: impl io::Write) -> Result<(), TK1ConvertError> {
137+
pub fn save_tk1_json_writer(circ: &Circuit, w: impl io::Write) -> Result<(), Tk1ConvertError> {
140138
let serial_circ = SerialCircuit::encode(circ)?;
141139
serde_json::to_writer(w, &serial_circ)?;
142140
Ok(())
@@ -150,25 +148,30 @@ pub fn save_tk1_json_writer(circ: &Circuit, w: impl io::Write) -> Result<(), TK1
150148
///
151149
/// Returns an error if the circuit is not flat or if it contains operations not
152150
/// supported by pytket.
153-
pub fn save_tk1_json_str(circ: &Circuit) -> Result<String, TK1ConvertError> {
151+
pub fn save_tk1_json_str(circ: &Circuit) -> Result<String, Tk1ConvertError> {
154152
let mut buf = io::BufWriter::new(Vec::new());
155153
save_tk1_json_writer(circ, &mut buf)?;
156154
let bytes = buf.into_inner().unwrap();
157155
Ok(String::from_utf8(bytes)?)
158156
}
159157

160-
/// Error type for conversion between `Op` and `OpType`.
161-
#[derive(Display, Debug, Error, From)]
158+
/// Error type for conversion between pytket operations and tket2 ops.
159+
#[derive(Display, derive_more::Debug, Error, From)]
162160
#[non_exhaustive]
163-
pub enum OpConvertError {
161+
#[debug(bounds(N: HugrNode))]
162+
pub enum OpConvertError<N = hugr::Node> {
164163
/// The serialized operation is not supported.
165-
#[display("Unsupported serialized pytket operation: {_0:?}")]
166-
#[error(ignore)] // `_0` is not the error source
167-
UnsupportedSerializedOp(SerialOpType),
164+
#[display("Unsupported serialized pytket operation: {op:?}")]
165+
UnsupportedSerializedOp {
166+
/// The serialized operation.
167+
op: SerialOpType,
168+
},
168169
/// The serialized operation is not supported.
169-
#[display("Cannot serialize tket2 operation: {_0:?}")]
170-
#[error(ignore)] // `_0` is not the error source
171-
UnsupportedOpSerialization(OpType),
170+
#[display("Cannot serialize tket2 operation: {op}")]
171+
UnsupportedOpSerialization {
172+
/// The operation.
173+
op: OpType,
174+
},
172175
/// The operation has non-serializable inputs.
173176
#[display(
174177
"Operation {} in {node} has an unsupported input of type {typ}.",
@@ -180,7 +183,7 @@ pub enum OpConvertError {
180183
/// The operation name.
181184
optype: OpType,
182185
/// The node.
183-
node: Node,
186+
node: N,
184187
},
185188
/// The operation has non-serializable outputs.
186189
#[display(
@@ -193,7 +196,7 @@ pub enum OpConvertError {
193196
/// The operation name.
194197
optype: OpType,
195198
/// The node.
196-
node: Node,
199+
node: N,
197200
},
198201
/// A parameter input could not be evaluated.
199202
#[display(
@@ -206,7 +209,7 @@ pub enum OpConvertError {
206209
/// The operation with the missing input param.
207210
optype: OpType,
208211
/// The node.
209-
node: Node,
212+
node: N,
210213
},
211214
/// The operation has output-only qubits.
212215
/// This is not currently supported by the encoder.
@@ -217,7 +220,7 @@ pub enum OpConvertError {
217220
/// The operation name.
218221
optype: OpType,
219222
/// The node.
220-
node: Node,
223+
node: N,
221224
},
222225
/// The opaque tket1 operation had an invalid type parameter.
223226
#[display("Opaque TKET1 operation had an invalid type parameter. {error}")]
@@ -256,15 +259,42 @@ pub enum OpConvertError {
256259
/// The given of parameters.
257260
args: Vec<ElementId>,
258261
},
262+
/// A node parameter output could not be evaluated.
263+
#[display("Could not compute output parameter #{out_index} for operation {} given inputs [{}].", op.name(), params.iter().join(", "))]
264+
CannotComputeParams {
265+
/// The operation being encoded
266+
op: OpType,
267+
/// The input parameters.
268+
params: Vec<String>,
269+
/// The output index that could not be computed.
270+
out_index: usize,
271+
},
272+
/// Tried to query the values associated with an unexplored wire.
273+
///
274+
/// This reflects a bug in the operation encoding logic of an operation.
275+
#[display("Could not find values associated with wire {wire}.")]
276+
WireHasNoValues {
277+
/// The wire that has no values.
278+
wire: Wire<N>,
279+
},
280+
/// Tried to add values to an already registered wire.
281+
///
282+
/// This reflects a bug in the operation encoding logic of an operation.
283+
#[display("Tried to register values for wire {wire}, but it already has associated values.")]
284+
WireAlreadyHasValues {
285+
/// The wire that already has values.
286+
wire: Wire<N>,
287+
},
259288
}
260289

261-
/// Error type for conversion between `Op` and `OpType`.
262-
#[derive(Debug, Display, Error, From)]
290+
/// Error type for conversion between tket2 ops and pytket operations.
291+
#[derive(derive_more::Debug, Display, Error, From)]
263292
#[non_exhaustive]
264-
pub enum TK1ConvertError {
293+
#[debug(bounds(N: HugrNode))]
294+
pub enum Tk1ConvertError<N = hugr::Node> {
265295
/// Operation conversion error.
266296
#[from]
267-
OpConversionError(OpConvertError),
297+
OpConversionError(OpConvertError<N>),
268298
/// The circuit has non-serializable inputs.
269299
#[display("Circuit contains non-serializable input of type {typ}.")]
270300
NonSerializableInputs {
@@ -291,6 +321,19 @@ pub enum TK1ConvertError {
291321
#[display("Unable to load pytket json file. {_0}")]
292322
#[from]
293323
FileLoadError(io::Error),
324+
/// Custom user-defined error raised while encoding an operation.
325+
#[display("Error while encoding operation: {msg}")]
326+
CustomError {
327+
/// The custom error message
328+
msg: String,
329+
},
330+
}
331+
332+
impl<N> Tk1ConvertError<N> {
333+
/// Create a new error with a custom message.
334+
pub fn custom(msg: impl Into<String>) -> Self {
335+
Self::CustomError { msg: msg.into() }
336+
}
294337
}
295338

296339
/// A hashed register, used to identify registers in the [`Tk1Decoder::register_wire`] map,

0 commit comments

Comments
 (0)