use std::collections::{HashMap, HashSet};
use std::io::BufReader;
use cool_asserts::assert_matches;
use hugr::builder::{
Container, Dataflow, DataflowHugr, DataflowSubContainer, FunctionBuilder, HugrBuilder,
ModuleBuilder, SubContainer,
};
use hugr::extension::prelude::{UnwrapBuilder, bool_t, option_type, qb_t};
use hugr::std_extensions::arithmetic::float_types::{ConstF64, float64_type};
use rayon::iter::ParallelIterator;
use std::sync::Arc;
use super::TKETDecode;
use crate::TketOp;
use crate::extension::TKET1_EXTENSION_ID;
use crate::extension::bool::{BoolOp, ConstBool, bool_type};
use crate::extension::rotation::{ConstRotation, RotationOp, rotation_type};
use crate::extension::sympy::SympyOpDef;
use crate::metadata;
use crate::serialize::pytket::PytketEncodeError;
use crate::serialize::pytket::extension::{CoreDecoder, OpaqueTk1Op, PreludeEmitter};
use crate::serialize::pytket::{
DecodeInsertionTarget, DecodeOptions, EncodeOptions, EncodedCircuit, PytketDecodeError,
PytketDecodeErrorInner, PytketDecoderConfig, PytketEncodeOpError, PytketEncoderConfig,
default_decoder_config, default_encoder_config,
};
use hugr::hugr::hugrmut::HugrMut;
use hugr::ops::handle::FuncID;
use hugr::ops::{OpParent, OpType, Value};
use hugr::std_extensions::arithmetic::float_ops::FloatOps;
use hugr::types::{Signature, SumType};
use hugr::{Hugr, HugrView};
use itertools::Itertools;
use rstest::{fixture, rstest};
use tket_json_rs::circuit_json::{self, SerialCircuit};
use tket_json_rs::optype;
use tket_json_rs::register;
use tket_json_rs::register::{ElementId, Qubit};
const EMPTY_CIRCUIT: &str = r#"{
"phase": "0",
"bits": [["c", [0]]],
"qubits": [["q", [0]], ["q", [1]]],
"commands": [],
"implicit_permutation": [[["q", [0]], ["q", [0]]], [["q", [1]], ["q", [1]]]]
}"#;
const SIMPLE_JSON: &str = r#"{
"phase": "0",
"bits": [],
"qubits": [["q", [0]], ["q", [1]]],
"commands": [
{"args": [["q", [0]]], "op": {"type": "H"}},
{"args": [["q", [0]], ["q", [1]]], "op": {"type": "CX"}}
],
"implicit_permutation": [[["q", [0]], ["q", [0]]], [["q", [1]], ["q", [1]]]]
}"#;
const SIMPLE_MEASURE: &str = r#"{
"phase": "0.0",
"bits": [["c", [0]], ["c", [1]]],
"qubits": [["q", [0]], ["q", [1]]],
"commands": [
{"args": [["q", [0]]], "op": {"type": "H"}},
{"args": [["q", [0]], ["q", [1]]], "op": {"type": "CX"}},
{"args": [["q", [0]], ["c", [0]]], "op": {"type": "Measure"}},
{"args": [["q", [1]], ["c", [1]]], "op": {"type": "Measure"}}
],
"created_qubits": [],
"discarded_qubits": [],
"implicit_permutation": [[["q", [0]], ["q", [0]]], [["q", [1]], ["q", [1]]]]
}"#;
const MULTI_REGISTER: &str = r#"{
"phase": "0",
"bits": [],
"qubits": [["q", [2]], ["q", [1]], ["my_qubits", [2]]],
"commands": [
{"args": [["my_qubits", [2]]], "op": {"type": "H"}},
{"args": [["q", [2]], ["q", [1]]], "op": {"type": "CX"}}
],
"implicit_permutation": []
}"#;
const UNKNOWN_OP: &str = r#"{
"phase": "1/2",
"bits": [["c", [0]], ["c", [1]]],
"qubits": [["q", [0]], ["q", [1]], ["q", [2]]],
"commands": [
{"args": [["q", [0]], ["q", [1]], ["q", [2]]], "op": {"type": "CSWAP"}},
{"args": [["q", [1]], ["c", [1]]], "op": {"type": "Measure"}}
],
"created_qubits": [],
"discarded_qubits": [],
"implicit_permutation": [[["q", [0]], ["q", [0]]], [["q", [1]], ["q", [1]]], [["q", [2]], ["q", [2]]]]
}"#;
const SMALL_PARAMETERIZED: &str = r#"{
"phase": "0.0",
"bits": [],
"qubits": [["q", [0]]],
"commands": [
{"args":[["q",[0]]],"op":{"params":["(pi) / (2)"],"type":"Rz"}}
],
"created_qubits": [],
"discarded_qubits": [],
"implicit_permutation": [[["q", [0]], ["q", [0]]]]
}"#;
const PARAMETERIZED: &str = r#"{
"phase": "0.0",
"bits": [],
"qubits": [["q", [0]], ["q", [1]]],
"commands": [
{"args":[["q",[0]]],"op":{"type":"H"}},
{"args":[["q",[1]],["q",[0]]],"op":{"type":"CX"}},
{"args":[["q",[0]]],"op":{"params":["((pi) / (2)) / (pi)"],"type":"Rz"}},
{"args": [["q", [0]]], "op": {"params": ["(3.141596) / (pi)", "alpha", "((pi) / (4)) / (pi)"], "type": "TK1"}}
],
"created_qubits": [],
"discarded_qubits": [],
"implicit_permutation": [[["q", [0]], ["q", [0]]], [["q", [1]], ["q", [1]]]]
}"#;
const BARRIER: &str = r#"{
"phase": "0.0",
"bits": [["c", [0]], ["c", [1]]],
"qubits": [["q", [0]], ["q", [1]], ["q", [2]]],
"commands": [
{"args": [["q", [0]], ["q", [1]]], "op": {"type": "CX"}},
{"args": [["q", [1]], ["q", [2]]], "op": {"type": "Barrier", "data": "Something invalid"}},
{"args": [["q", [2]], ["c", [1]]], "op": {"type": "Measure"}}
],
"created_qubits": [],
"discarded_qubits": [],
"implicit_permutation": [[["q", [0]], ["q", [0]]], [["q", [1]], ["q", [1]]], [["q", [2]], ["q", [2]]]]
}"#;
const IMPLICIT_PERMUTATION: &str = r#"{
"phase": "0.0",
"bits": [["c", [0]], ["c", [1]]],
"qubits": [["q", [0]], ["q", [1]], ["q", [2]]],
"commands": [
{"args": [["q", [0]], ["q", [1]]], "op": {"type": "CX"}}
],
"created_qubits": [],
"discarded_qubits": [],
"implicit_permutation": [[["q", [0]], ["q", [1]]], [["q", [1]], ["q", [2]]], [["q", [2]], ["q", [0]]]]
}"#;
fn validate_serial_circ(circ: &SerialCircuit) {
for command in &circ.commands {
for arg in &command.args {
assert!(
circ.qubits.contains(®ister::Qubit::from(arg.clone()))
|| circ.bits.contains(®ister::Bit::from(arg.clone())),
"Circuit command {command:?} has an invalid argument '{arg:?}'"
);
}
}
let perm: HashMap<register::ElementId, register::ElementId> = circ
.implicit_permutation
.iter()
.map(|p| (p.0.clone().id, p.1.clone().id))
.collect();
for (key, value) in &perm {
let valid_qubits = circ.qubits.contains(®ister::Qubit::from(key.clone()))
&& circ.qubits.contains(®ister::Qubit::from(value.clone()));
let valid_bits = circ.bits.contains(®ister::Bit::from(key.clone()))
&& circ.bits.contains(®ister::Bit::from(value.clone()));
assert!(
valid_qubits || valid_bits,
"Circuit has an invalid permutation '{key:?} -> {value:?}'"
);
}
assert_eq!(
perm.len(),
circ.implicit_permutation.len(),
"Circuit has duplicate permutations",
);
assert_eq!(
HashSet::<®ister::ElementId>::from_iter(perm.values()).len(),
perm.len(),
"Circuit has duplicate values in permutations"
);
}
fn compare_serial_circs(a: &SerialCircuit, b: &SerialCircuit) {
assert_eq!(a.name, b.name);
assert_eq!(a.phase, b.phase);
assert_eq!(&a.qubits, &b.qubits);
assert_eq!(a.commands.len(), b.commands.len());
let bits_a: HashSet<_> = a.bits.iter().collect();
let bits_b: HashSet<_> = b.bits.iter().collect();
assert!(
bits_b.is_superset(&bits_a),
"Some bit IDs in original circuit are missing the roundtrip. Original: [{}], Roundtrip: [{}]",
bits_a.iter().join(", "),
bits_b.iter().join(", "),
);
#[derive(PartialEq, Eq, Hash, Debug)]
struct CommandInfo {
op_type: tket_json_rs::OpType,
params: Vec<String>,
n_args: usize,
}
impl From<&tket_json_rs::circuit_json::Command> for CommandInfo {
fn from(command: &tket_json_rs::circuit_json::Command) -> Self {
CommandInfo {
op_type: command.op.op_type,
params: command.op.params.clone().unwrap_or_default(),
n_args: command.args.len(),
}
}
}
let a_command_count: HashMap<CommandInfo, usize> = a.commands.iter().map_into().counts();
let b_command_count: HashMap<CommandInfo, usize> = b.commands.iter().map_into().counts();
for (a, &count_a) in &a_command_count {
let count_b = b_command_count.get(a).copied().unwrap_or_default();
assert_eq!(
count_a, count_b,
"command {a:?} appears {count_a} times in rhs and {count_b} times in lhs.\ncounts for a: {a_command_count:#?}\ncounts for b: {b_command_count:#?}"
);
}
assert_eq!(a_command_count.len(), b_command_count.len());
}
#[fixture]
fn circ_preset_qubits() -> Hugr {
let input_t = vec![qb_t()];
let output_t = vec![qb_t(), qb_t()];
let mut h = FunctionBuilder::new("preset_qubits", Signature::new(input_t, output_t)).unwrap();
let [qb0] = h.input_wires_arr();
let [qb1] = h.add_dataflow_op(TketOp::QAlloc, []).unwrap().outputs_arr();
let [qb0, qb1] = h
.add_dataflow_op(TketOp::CZ, [qb0, qb1])
.unwrap()
.outputs_arr();
let mut hugr = h.finish_hugr_with_outputs([qb0, qb1]).unwrap();
hugr.set_metadata::<metadata::QubitRegisters>(
hugr.entrypoint(),
vec![
ElementId(String::from("q"), vec![2]),
ElementId(String::from("q"), vec![10]),
ElementId(String::from("q"), vec![8]),
]
.into_iter()
.map(Qubit::from)
.collect_vec(),
);
hugr
}
#[fixture]
fn circ_preset_bits() -> Hugr {
let input_t = vec![bool_type()];
let output_t = vec![bool_type(), bool_type(), bool_type()];
let mut h = FunctionBuilder::new("preset_bits", Signature::new(input_t, output_t)).unwrap();
let [b0] = h.input_wires_arr();
let b1 = h.add_load_value(ConstBool::new(false));
let [b_and] = h
.add_dataflow_op(BoolOp::and, [b0, b1])
.unwrap()
.outputs_arr();
let mut hugr = h.finish_hugr_with_outputs([b0, b_and, b0]).unwrap();
hugr.set_metadata::<metadata::BitRegisters>(
hugr.entrypoint(),
vec![ElementId(String::from("b"), vec![1])]
.into_iter()
.map(register::Bit::from)
.collect_vec(),
);
hugr
}
#[fixture]
fn circ_parameterized() -> Hugr {
let input_t = vec![qb_t(), rotation_type(), rotation_type(), rotation_type()];
let output_t = vec![qb_t()];
let mut h = FunctionBuilder::new("parameterized", Signature::new(input_t, output_t)).unwrap();
let [q, r0, r1, r2] = h.input_wires_arr();
let [q] = h
.add_dataflow_op(TketOp::Rx, [q, r0])
.unwrap()
.outputs_arr();
let [q] = h
.add_dataflow_op(TketOp::Ry, [q, r1])
.unwrap()
.outputs_arr();
let [q] = h
.add_dataflow_op(TketOp::Rz, [q, r2])
.unwrap()
.outputs_arr();
let mut hugr = h.finish_hugr_with_outputs([q]).unwrap();
hugr.set_metadata::<metadata::InputParameters>(
hugr.entrypoint(),
vec!["alpha".to_string(), "beta".to_string()],
);
hugr
}
#[fixture]
fn circ_tk1_ops() -> Hugr {
let input_t = vec![qb_t(), qb_t()];
let output_t = vec![qb_t(), qb_t()];
let mut h = FunctionBuilder::new("tk1_ops", Signature::new(input_t, output_t)).unwrap();
let [q1, q2] = h.input_wires_arr();
let mut tk1op = tket_json_rs::circuit_json::Operation::default();
tk1op.op_type = tket_json_rs::optype::OpType::CH;
tk1op.n_qb = Some(2);
let op: OpType = OpaqueTk1Op::new_from_op(&tk1op, 2, 0)
.as_extension_op()
.into();
let [q1, q2] = h.add_dataflow_op(op, [q1, q2]).unwrap().outputs_arr();
h.finish_hugr_with_outputs([q1, q2]).unwrap()
}
#[fixture]
fn circ_unsupported_subtree() -> Hugr {
let input_t = vec![];
let output_t = vec![qb_t()];
let mut h =
FunctionBuilder::new("unsupported_subtree", Signature::new(input_t, output_t)).unwrap();
let [maybe_q] = h
.add_dataflow_op(TketOp::TryQAlloc, [])
.unwrap()
.outputs_arr();
let [q] = h
.build_unwrap_sum(1, option_type([qb_t()]), maybe_q)
.unwrap();
h.finish_hugr_with_outputs([q]).unwrap()
}
#[fixture]
fn circ_recursive() -> Hugr {
let input_t = vec![qb_t()];
let output_t = vec![qb_t()];
let mut h = FunctionBuilder::new("recursive", Signature::new(input_t, output_t)).unwrap();
let func: FuncID<true> = h.container_node().into();
let [q] = h.input_wires_arr();
let [q] = h.call(&func, &[], [q]).unwrap().outputs_arr();
h.finish_hugr_with_outputs([q]).unwrap()
}
#[fixture]
fn circ_global_defs() -> Hugr {
let input_t = vec![qb_t()];
let output_t = vec![qb_t()];
let mut h = FunctionBuilder::new(
"global_param",
Signature::new(input_t.clone(), output_t.clone()),
)
.unwrap();
let (rot_const, func_decl) = {
let mut module = h.module_root_builder();
let rot_const = module.add_constant(Value::from(ConstRotation::new(0.2).unwrap()));
let func_decl = module
.declare("func", Signature::new(input_t, output_t).into())
.unwrap();
(rot_const, func_decl)
};
let [q] = h.input_wires_arr();
let rot = h.load_const(&rot_const);
let [q] = h
.add_dataflow_op(TketOp::Rx, [q, rot])
.unwrap()
.outputs_arr();
let [q] = h.call(&func_decl, &[], [q]).unwrap().outputs_arr();
h.finish_hugr_with_outputs([q]).unwrap()
}
#[fixture]
fn circ_non_local() -> Hugr {
let input_t = vec![qb_t(), rotation_type()];
let inner_input_t = vec![qb_t()];
let output_t = vec![qb_t()];
let mut h =
FunctionBuilder::new("non_local", Signature::new(input_t, output_t.clone())).unwrap();
let [q, rot] = h.input_wires_arr();
let [q] = {
let mut dfg = h
.dfg_builder(Signature::new(inner_input_t, output_t), [q])
.unwrap();
let [q] = dfg.input_wires_arr();
let [q] = dfg
.add_dataflow_op(TketOp::Rx, [q, rot])
.unwrap()
.outputs_arr();
dfg.set_outputs([q]).unwrap();
dfg.finish_sub_container().unwrap()
}
.outputs_arr();
h.finish_hugr_with_outputs([q]).unwrap()
}
#[fixture]
fn circ_measure_ancilla() -> Hugr {
let input_t = vec![qb_t()];
let output_t = vec![bool_t(), bool_t()];
let mut h = FunctionBuilder::new("meas_ancilla", Signature::new(input_t, output_t)).unwrap();
let [qb] = h.input_wires_arr();
let [anc] = h.add_dataflow_op(TketOp::QAlloc, []).unwrap().outputs_arr();
let [qb, meas_qb] = h
.add_dataflow_op(TketOp::Measure, [qb])
.unwrap()
.outputs_arr();
let [anc, meas_anc] = h
.add_dataflow_op(TketOp::Measure, [anc])
.unwrap()
.outputs_arr();
let [] = h
.add_dataflow_op(TketOp::QFree, [qb])
.unwrap()
.outputs_arr();
let [] = h
.add_dataflow_op(TketOp::QFree, [anc])
.unwrap()
.outputs_arr();
h.finish_hugr_with_outputs([meas_qb, meas_anc]).unwrap()
}
#[fixture]
fn circ_add_angles_symbolic() -> (Hugr, String) {
let input_t = vec![qb_t(), rotation_type(), rotation_type()];
let output_t = vec![qb_t()];
let mut h =
FunctionBuilder::new("add_angles_symbolic", Signature::new(input_t, output_t)).unwrap();
let [qb, f1, f2] = h.input_wires_arr();
let [f12] = h
.add_dataflow_op(RotationOp::radd, [f1, f2])
.unwrap()
.outputs_arr();
let [qb] = h
.add_dataflow_op(TketOp::Rx, [qb, f12])
.unwrap()
.outputs_arr();
let circ = h.finish_hugr_with_outputs([qb]).unwrap();
(circ, "(f0) + (f1)".to_string())
}
#[fixture]
fn circ_add_angles_constants() -> (Hugr, String) {
let qb_row = vec![qb_t()];
let mut h = FunctionBuilder::new(
"add_angles_constants",
Signature::new(qb_row.clone(), qb_row),
)
.unwrap();
let qb = h.input_wires().next().unwrap();
let point2 = h.add_load_value(ConstRotation::new(0.2).unwrap());
let point3 = h.add_load_value(ConstRotation::new(0.3).unwrap());
let point5 = h
.add_dataflow_op(RotationOp::radd, [point2, point3])
.unwrap()
.out_wire(0);
let qbs = h
.add_dataflow_op(TketOp::Rx, [qb, point5])
.unwrap()
.outputs();
let circ = h.finish_hugr_with_outputs(qbs).unwrap();
(circ, "(0.2) + (0.3)".to_string())
}
#[fixture]
fn circ_complex_angle_computation() -> (Hugr, String) {
let input_t = vec![qb_t(), rotation_type(), rotation_type()];
let output_t = vec![qb_t()];
let mut h = FunctionBuilder::new(
"complex_angle_computation",
Signature::new(input_t, output_t),
)
.unwrap();
let [qb, r0, r1] = h.input_wires_arr();
let point2 = h.add_load_value(ConstRotation::new(0.2).unwrap());
let sympy = h
.add_dataflow_op(SympyOpDef.with_expr("cos(pi)".to_string()), [])
.unwrap()
.out_wire(0);
let added_rot = h
.add_dataflow_op(RotationOp::radd, [sympy, point2])
.unwrap()
.out_wire(0);
let f0 = h
.add_dataflow_op(RotationOp::to_halfturns, [r0])
.unwrap()
.out_wire(0);
let f1 = h
.add_dataflow_op(RotationOp::to_halfturns, [r1])
.unwrap()
.out_wire(0);
let fpow = h
.add_dataflow_op(FloatOps::fpow, [f0, f1])
.unwrap()
.out_wire(0);
let rpow = h
.add_dataflow_op(RotationOp::from_halfturns_unchecked, [fpow])
.unwrap()
.out_wire(0);
let final_rot = h
.add_dataflow_op(RotationOp::radd, [rpow, added_rot])
.unwrap()
.out_wire(0);
let qbs = h
.add_dataflow_op(TketOp::Rx, [qb, final_rot])
.unwrap()
.outputs();
let circ = h.finish_hugr_with_outputs(qbs).unwrap();
(circ, "((f0) ** (f1)) + ((cos(pi)) + (0.2))".to_string())
}
#[fixture]
fn circ_nested_dfgs() -> Hugr {
let input_t = vec![qb_t()];
let output_t = vec![bool_t()];
let mut h =
FunctionBuilder::new("nested_dfgs", Signature::new(input_t, output_t.clone())).unwrap();
let [qb] = h.input_wires_arr();
let rot = h.add_load_value(ConstRotation::new(0.5).unwrap());
let inner_dfg = {
let mut inner_dfg = h
.dfg_builder(
Signature::new(vec![qb_t(), rotation_type()], output_t),
[qb, rot],
)
.unwrap();
let [qb, rot] = inner_dfg.input_wires_arr();
let [qb] = inner_dfg
.add_dataflow_op(TketOp::Rx, [qb, rot])
.unwrap()
.outputs_arr();
let [bool] = inner_dfg
.add_dataflow_op(TketOp::MeasureFree, [qb])
.unwrap()
.outputs_arr();
let [bool] = inner_dfg
.add_dataflow_op(BoolOp::read, [bool])
.unwrap()
.outputs_arr();
inner_dfg.finish_with_outputs([bool]).unwrap()
};
let [bool] = inner_dfg.outputs_arr();
h.finish_hugr_with_outputs([bool]).unwrap()
}
#[fixture]
fn circ_independent_subgraph() -> Hugr {
let input_t = vec![qb_t(), qb_t(), option_type([bool_t()]).into()];
let output_t = vec![qb_t(), qb_t(), bool_t()];
let mut h =
FunctionBuilder::new("independent_subgraph", Signature::new(input_t, output_t)).unwrap();
let [q1, q2, maybe_b] = h.input_wires_arr();
let [q1, q2] = h
.add_dataflow_op(TketOp::CX, [q1, q2])
.unwrap()
.outputs_arr();
let [maybe_b] = h
.build_unwrap_sum(1, option_type([bool_t()]), maybe_b)
.unwrap();
h.finish_hugr_with_outputs([q1, q2, maybe_b]).unwrap()
}
#[fixture]
fn circ_unsupported_io_wire() -> Hugr {
let input_t = vec![qb_t(), qb_t(), option_type([qb_t()]).into()];
let output_t = vec![qb_t(), qb_t(), option_type([qb_t()]).into()];
let mut h = FunctionBuilder::new(
"unsupported_input_to_output",
Signature::new(input_t, output_t),
)
.unwrap();
let [q1, q2, maybe_q] = h.input_wires_arr();
let [q1, q2] = h
.add_dataflow_op(TketOp::CX, [q1, q2])
.unwrap()
.outputs_arr();
h.finish_hugr_with_outputs([q1, q2, maybe_q]).unwrap()
}
#[fixture]
fn circ_order_edge() -> Hugr {
let input_t = vec![qb_t(), qb_t()];
let output_t = vec![qb_t(), qb_t()];
let mut h = FunctionBuilder::new("order_edge", Signature::new(input_t, output_t)).unwrap();
let [q1, q2] = h.input_wires_arr();
let cx1 = h.add_dataflow_op(TketOp::CX, [q1, q2]).unwrap();
let [q1, q2] = cx1.outputs_arr();
let cx2 = h.add_dataflow_op(TketOp::CX, [q1, q2]).unwrap();
let [q1, q2] = cx2.outputs_arr();
let cx3 = h.add_dataflow_op(TketOp::CX, [q1, q2]).unwrap();
let [q1, q2] = cx3.outputs_arr();
h.set_order(&cx1, &cx3);
h.finish_hugr_with_outputs([q1, q2]).unwrap()
}
#[fixture]
fn circ_bool_conversion() -> Hugr {
let input_t = vec![bool_t(), bool_type()];
let output_t = vec![bool_t(), bool_type()];
let mut h = FunctionBuilder::new("bool_conversion", Signature::new(input_t, output_t)).unwrap();
let [native_b0, tket_b1] = h.input_wires_arr();
let [tket_b0] = h
.add_dataflow_op(BoolOp::make_opaque, [native_b0])
.unwrap()
.outputs_arr();
let [native_b1] = h
.add_dataflow_op(BoolOp::read, [tket_b1])
.unwrap()
.outputs_arr();
h.finish_hugr_with_outputs([native_b1, tket_b0]).unwrap()
}
#[fixture]
fn circ_unsupported_extras_in_circ_box() -> Hugr {
let input_t = vec![option_type([bool_t()]).into(), option_type([qb_t()]).into()];
let output_t = vec![bool_t(), option_type([qb_t()]).into()];
let mut h = FunctionBuilder::new(
"unsupported_extras_in_circ_box",
Signature::new(input_t.clone(), output_t.clone()),
)
.unwrap();
let [maybe_b, maybe_q] = h.input_wires_arr();
let [maybe_b, maybe_q] = {
let mut nested = h
.dfg_builder(Signature::new(input_t, output_t), [maybe_b, maybe_q])
.unwrap();
let [maybe_b, maybe_q] = nested.input_wires_arr();
let [maybe_b] = nested
.build_unwrap_sum(1, option_type([bool_t()]), maybe_b)
.unwrap();
nested
.finish_with_outputs([maybe_b, maybe_q])
.unwrap()
.outputs_arr()
};
h.finish_hugr_with_outputs([maybe_b, maybe_q]).unwrap()
}
#[fixture]
fn circ_output_parameter_wire() -> Hugr {
let input_t = vec![];
let output_t = vec![float64_type(), rotation_type()];
let mut h =
FunctionBuilder::new("output_parameter_wire", Signature::new(input_t, output_t)).unwrap();
let pi = h.add_load_value(ConstF64::new(std::f64::consts::PI));
let two = h.add_load_value(ConstF64::new(2.0));
let two_pi = h
.add_dataflow_op(FloatOps::fmul, [pi, two])
.unwrap()
.out_wire(0);
let two_pi_rotation = h
.add_dataflow_op(RotationOp::from_halfturns_unchecked, [two_pi])
.unwrap()
.out_wire(0);
h.finish_hugr_with_outputs([two_pi, two_pi_rotation])
.unwrap()
}
#[fixture]
fn circ_complex_param_type() -> Hugr {
let input_t = vec![];
let output_t = vec![SumType::new_tuple(vec![float64_type()]).into()];
let mut h =
FunctionBuilder::new("complex_param_type", Signature::new(input_t, output_t)).unwrap();
let float64 = h.add_load_value(ConstF64::new(1.0));
let float_tuple = h.make_tuple([float64]).unwrap();
h.finish_hugr_with_outputs([float_tuple]).unwrap()
}
#[fixture]
fn circ_unsupported_subgraph_no_registers() -> Hugr {
let input_t = vec![qb_t()];
let output_t = vec![qb_t(), rotation_type()];
let mut h = FunctionBuilder::new(
"unsupported_subgraph_no_registers",
Signature::new(input_t, output_t),
)
.unwrap();
let [q] = h.input_wires_arr();
let func1 = {
let call_input_t = vec![];
let call_output_t = vec![float64_type()];
h.module_root_builder()
.declare("func1", Signature::new(call_input_t, call_output_t).into())
.unwrap()
};
let func2 = {
h.module_root_builder()
.declare("func2", Signature::new_endo(vec![rotation_type()]).into())
.unwrap()
};
let call = h.call(&func1, &[], []).unwrap();
let [f] = call.outputs_arr();
let [rot] = h
.add_dataflow_op(RotationOp::from_halfturns_unchecked, [f])
.unwrap()
.outputs_arr();
let [q] = h
.add_dataflow_op(TketOp::Rz, [q, rot])
.unwrap()
.outputs_arr();
let [rot2] = h.call(&func2, &[], [rot]).unwrap().outputs_arr();
h.finish_hugr_with_outputs([q, rot2]).unwrap()
}
#[fixture]
fn circ_discard_first_qubit() -> Hugr {
let input_t = vec![qb_t(), qb_t()];
let output_t = vec![qb_t()];
let mut h =
FunctionBuilder::new("discard_first_qubit", Signature::new(input_t, output_t)).unwrap();
let [q1, q2] = h.input_wires_arr();
h.add_dataflow_op(TketOp::MeasureFree, [q1]).unwrap();
let [q2] = h.add_dataflow_op(TketOp::X, [q2]).unwrap().outputs_arr();
h.finish_hugr_with_outputs([q2]).unwrap()
}
fn check_no_tk1_ops(hugr: &Hugr) {
for node in hugr.entry_descendants() {
let Some(op) = hugr.get_optype(node).as_extension_op() else {
continue;
};
if op.extension_id() == &TKET1_EXTENSION_ID {
let payload = match op.args().first() {
Some(t) => t.to_string(),
None => "no payload".to_string(),
};
panic!(
"{} found in circuit with payload '{payload}'",
op.qualified_id()
);
}
}
}
#[rstest]
#[case::simple(SIMPLE_JSON, 2, 2, false)]
#[case::simple_measure(SIMPLE_MEASURE, 4, 2, false)]
#[case::multi_register(MULTI_REGISTER, 2, 3, false)]
#[case::unknown_op(UNKNOWN_OP, 2, 3, true)]
#[case::small_parametrized(SMALL_PARAMETERIZED, 1, 1, false)]
#[case::parametrized(PARAMETERIZED, 4, 2, true)] #[case::barrier(BARRIER, 3, 3, false)]
#[case::implicit_permutation(IMPLICIT_PERMUTATION, 1, 3, false)]
fn json_roundtrip(
#[case] circ_s: &str,
#[case] num_commands: usize,
#[case] num_qubits: usize,
#[case] has_tk1_ops: bool,
) {
let ser: circuit_json::SerialCircuit = serde_json::from_str(circ_s).unwrap();
assert_eq!(ser.commands.len(), num_commands);
let hugr: Hugr = ser.decode(DecodeOptions::new()).unwrap();
assert_eq!(crate::Circuit::new(&hugr).qubit_count(), num_qubits);
if !has_tk1_ops {
check_no_tk1_ops(&hugr);
}
let reser: SerialCircuit = SerialCircuit::encode(&hugr, EncodeOptions::new()).unwrap();
validate_serial_circ(&reser);
compare_serial_circs(&ser, &reser);
}
#[rstest]
#[cfg_attr(miri, ignore)] #[case::barenco_tof_10("../test_files/pytket/barenco_tof_10.json")]
fn json_file_roundtrip(#[case] circ: impl AsRef<std::path::Path>) {
let reader = BufReader::new(std::fs::File::open(circ).unwrap());
let ser: circuit_json::SerialCircuit = serde_json::from_reader(reader).unwrap();
let hugr: Hugr = ser.decode(DecodeOptions::new()).unwrap();
check_no_tk1_ops(&hugr);
let reser: SerialCircuit = SerialCircuit::encode(&hugr, EncodeOptions::new()).unwrap();
validate_serial_circ(&reser);
compare_serial_circs(&ser, &reser);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CircuitRoundtripTestConfig {
Default,
NoStd,
}
impl CircuitRoundtripTestConfig {
fn decoder_config(&self) -> PytketDecoderConfig {
match self {
CircuitRoundtripTestConfig::Default => default_decoder_config(),
CircuitRoundtripTestConfig::NoStd => {
let mut config = PytketDecoderConfig::new();
config.add_decoder(CoreDecoder);
config.add_decoder(PreludeEmitter);
config.add_type_translator(PreludeEmitter);
config
}
}
}
fn encoder_config<H: HugrView>(&self) -> PytketEncoderConfig<H> {
match self {
CircuitRoundtripTestConfig::Default => default_encoder_config(),
CircuitRoundtripTestConfig::NoStd => {
let mut config = PytketEncoderConfig::new();
config.add_emitter(PreludeEmitter);
config.add_type_translator(PreludeEmitter);
config
}
}
}
}
#[rstest]
fn encoded_circuit_attributes(circ_measure_ancilla: Hugr) {
let hugr = circ_measure_ancilla;
let encode_options = EncodeOptions::new().with_subcircuits(true);
let encoded = EncodedCircuit::new(&hugr, encode_options).unwrap_or_else(|e| panic!("{e}"));
assert!(encoded.contains_circuit(hugr.entrypoint()));
assert_eq!(encoded.len(), 1);
assert!(!encoded.is_empty());
let (region, serial_circ) = encoded.iter().exactly_one().ok().unwrap();
assert_eq!(region, hugr.entrypoint());
assert_eq!(serial_circ.commands.len(), 2);
let par_sum: usize = encoded
.par_iter()
.map(|(_, circ)| circ.commands.len())
.sum();
assert_eq!(par_sum, 2);
}
#[rstest]
#[case::meas_ancilla(circ_measure_ancilla(), CircuitRoundtripTestConfig::Default)]
#[case::preset_qubits(circ_preset_qubits(), CircuitRoundtripTestConfig::Default)]
#[case::preset_bits(circ_preset_bits(), CircuitRoundtripTestConfig::Default)]
#[case::preset_parameterized(circ_parameterized(), CircuitRoundtripTestConfig::Default)]
#[should_panic(expected = "Cannot encode subgraphs with nested structure")]
#[case::nested_dfgs(circ_nested_dfgs(), CircuitRoundtripTestConfig::Default)]
#[case::tk1_ops(circ_tk1_ops(), CircuitRoundtripTestConfig::Default)]
#[case::missing_decoders(circ_measure_ancilla(), CircuitRoundtripTestConfig::NoStd)]
fn circuit_standalone_roundtrip(#[case] hugr: Hugr, #[case] config: CircuitRoundtripTestConfig) {
let circ_signature = hugr
.entrypoint_optype()
.inner_function_type()
.expect("Dataflow entrypoint")
.into_owned();
let decode_options = DecodeOptions::new()
.with_signature(circ_signature.clone())
.with_config(config.decoder_config());
let encode_options = EncodeOptions::new()
.with_subcircuits(true)
.with_config(config.encoder_config());
let encoded = EncodedCircuit::new_standalone(&hugr, encode_options.clone())
.unwrap_or_else(|e| panic!("{e}"));
assert!(encoded.contains_circuit(hugr.entrypoint()));
assert_eq!(encoded.len(), 1);
let extracted_from_circ = encoded
.reassemble(
hugr.entrypoint(),
Some("main".to_string()),
decode_options.clone(),
)
.unwrap_or_else(|e| panic!("{e}"));
extracted_from_circ
.validate()
.unwrap_or_else(|e| panic!("{e}"));
let ser: &SerialCircuit = &encoded[hugr.entrypoint()];
let deser: Hugr = ser.decode(decode_options).unwrap_or_else(|e| panic!("{e}"));
deser.validate().unwrap_or_else(|e| panic!("{e}"));
let deser_sig = deser
.entrypoint_optype()
.inner_function_type()
.expect("Dataflow entrypoint")
.into_owned();
assert_eq!(
&circ_signature.input, &deser_sig.input,
"Input signature mismatch\n Expected: {}\n Actual: {}",
&circ_signature, &deser_sig
);
assert_eq!(
&circ_signature.output, &deser_sig.output,
"Output signature mismatch\n Expected: {}\n Actual: {}",
&circ_signature, &deser_sig
);
let reser = SerialCircuit::encode(&deser, encode_options).unwrap();
validate_serial_circ(&reser);
compare_serial_circs(ser, &reser);
}
#[rstest]
#[case::unsupported_subtree(circ_unsupported_subtree())]
#[case::global_defs(circ_global_defs())]
#[case::recursive(circ_recursive())]
fn reject_standalone_complex_subgraphs(#[case] hugr: Hugr) {
let try_encoded = EncodedCircuit::new_standalone(&hugr, EncodeOptions::new());
assert_matches!(
try_encoded,
Err(PytketEncodeError::OpEncoding(
PytketEncodeOpError::UnsupportedStandaloneSubgraph { .. }
))
);
}
#[rstest]
fn fail_on_modified_hugr(circ_tk1_ops: Hugr) {
let encoded = EncodedCircuit::new(&circ_tk1_ops, EncodeOptions::new().with_subcircuits(true))
.unwrap_or_else(|e| panic!("{e}"));
let mut a_new_hugr = ModuleBuilder::new();
a_new_hugr
.declare("decl", Signature::new_endo(vec![qb_t()]).into())
.unwrap();
let mut a_new_hugr = a_new_hugr.finish_hugr().unwrap();
let try_reassemble = encoded.reassemble_inplace(&mut a_new_hugr, None);
assert_matches!(
try_reassemble,
Err(PytketDecodeError {
inner: PytketDecodeErrorInner::IncompatibleTargetRegion { .. },
..
})
);
}
#[rstest]
#[case::meas_ancilla(circ_measure_ancilla(), 1, CircuitRoundtripTestConfig::Default)]
#[case::preset_qubits(circ_preset_qubits(), 1, CircuitRoundtripTestConfig::Default)]
#[case::preset_bits(circ_preset_bits(), 1, CircuitRoundtripTestConfig::Default)]
#[case::preset_parameterized(circ_parameterized(), 1, CircuitRoundtripTestConfig::Default)]
#[case::nested_dfgs(circ_nested_dfgs(), 2, CircuitRoundtripTestConfig::Default)]
#[case::flat_opaque(circ_tk1_ops(), 1, CircuitRoundtripTestConfig::Default)]
#[case::unsupported_subtree(circ_unsupported_subtree(), 3, CircuitRoundtripTestConfig::Default)]
#[case::global_defs(circ_global_defs(), 1, CircuitRoundtripTestConfig::Default)]
#[case::recursive(circ_recursive(), 1, CircuitRoundtripTestConfig::Default)]
#[case::independent_subgraph(circ_independent_subgraph(), 3, CircuitRoundtripTestConfig::Default)]
#[case::unsupported_io_wire(circ_unsupported_io_wire(), 1, CircuitRoundtripTestConfig::Default)]
#[case::order_edge(circ_order_edge(), 1, CircuitRoundtripTestConfig::Default)]
#[case::bool_conversion(circ_bool_conversion(), 1, CircuitRoundtripTestConfig::Default)]
#[case::complex_param_type(circ_complex_param_type(), 1, CircuitRoundtripTestConfig::Default)]
#[case::unsupported_extras_in_circ_box(
circ_unsupported_extras_in_circ_box(),
4,
CircuitRoundtripTestConfig::Default
)]
#[case::output_parameter_wire(circ_output_parameter_wire(), 1, CircuitRoundtripTestConfig::Default)]
#[case::non_local(circ_non_local(), 2, CircuitRoundtripTestConfig::Default)]
#[case::unsupported_subgraph_no_registers(
circ_unsupported_subgraph_no_registers(),
1,
CircuitRoundtripTestConfig::Default
)]
#[case::discard_first_qubit(circ_discard_first_qubit(), 1, CircuitRoundtripTestConfig::Default)]
fn encoded_circuit_roundtrip(
#[case] hugr: Hugr,
#[case] num_circuits: usize,
#[case] config: CircuitRoundtripTestConfig,
) {
let circ_signature = hugr
.entrypoint_optype()
.inner_function_type()
.expect("Dataflow entrypoint")
.into_owned();
let encode_options = EncodeOptions::new()
.with_subcircuits(true)
.with_config(config.encoder_config());
let encoded = EncodedCircuit::new(&hugr, encode_options).unwrap_or_else(|e| panic!("{e}"));
assert!(encoded.contains_circuit(hugr.entrypoint()));
assert_eq!(encoded.len(), num_circuits);
let mut deser = hugr.clone();
encoded
.reassemble_inplace(&mut deser, Some(Arc::new(config.decoder_config())))
.unwrap_or_else(|e| panic!("{e}"));
deser.validate().unwrap_or_else(|e| panic!("{e}"));
let deser_sig = deser
.entrypoint_optype()
.inner_function_type()
.expect("Dataflow entrypoint")
.into_owned();
assert_eq!(
&circ_signature.input, &deser_sig.input,
"Input signature mismatch\n Expected: {}\n Actual: {}",
&circ_signature, &deser_sig
);
assert_eq!(
&circ_signature.output, &deser_sig.output,
"Output signature mismatch\n Expected: {}\n Actual: {}",
&circ_signature, &deser_sig
);
}
#[rstest]
#[case::symbolic(circ_add_angles_symbolic())]
#[case::constants(circ_add_angles_constants())]
#[case::complex(circ_complex_angle_computation())]
fn test_add_angle_serialise(#[case] circ_add_angles: (Hugr, String)) {
let (hugr, expected) = circ_add_angles;
let ser: SerialCircuit = SerialCircuit::encode(&hugr, EncodeOptions::new()).unwrap();
assert_eq!(ser.commands.len(), 1);
assert_eq!(ser.commands[0].op.op_type, optype::OpType::Rx);
assert_eq!(ser.commands[0].op.params, Some(vec![expected]));
let deser: Hugr = ser.decode(DecodeOptions::new()).unwrap();
let reser = SerialCircuit::encode(&deser, EncodeOptions::new()).unwrap();
validate_serial_circ(&reser);
compare_serial_circs(&ser, &reser);
}
#[rstest]
fn test_inplace_decoding() {
let serial: circuit_json::SerialCircuit = serde_json::from_str(SIMPLE_JSON).unwrap();
let mut builder = ModuleBuilder::new();
let func1 = serial
.decode_into(
builder.hugr_mut(),
DecodeInsertionTarget::Function { fn_name: None },
DecodeOptions::new(),
)
.unwrap();
let circ_signature = builder
.hugr()
.get_optype(func1)
.inner_function_type()
.unwrap()
.into_owned();
let dfg = {
let mut fn_build = builder
.define_function("func2", circ_signature.clone())
.unwrap();
let fn2_node = fn_build.container_node();
let [inp, out] = fn_build.io();
let dfg = serial
.decode_into(
fn_build.hugr_mut(),
DecodeInsertionTarget::Region { parent: fn2_node },
DecodeOptions::new(),
)
.unwrap();
for inp_idx in 0..circ_signature.input_count() {
fn_build.hugr_mut().connect(inp, inp_idx, dfg, inp_idx);
}
for out_idx in 0..circ_signature.output_count() {
fn_build.hugr_mut().connect(dfg, out_idx, out, out_idx);
}
dfg
};
let hugr = builder.finish_hugr().unwrap();
assert!(hugr.get_optype(func1).is_func_defn());
assert!(hugr.get_optype(dfg).is_dfg());
}
#[rstest]
#[case::qubits_in_qubits_out(Signature::new_endo(vec![qb_t(), qb_t()]))]
#[case::qubits_in_bits_out(Signature::new(vec![qb_t(), qb_t()], vec![bool_t(), bool_t()]))]
#[case::nothing_in_all_out(Signature::new(vec![], vec![qb_t(), qb_t(), bool_t(), bool_t()]))]
#[case::nothing_in_nothing_out(Signature::new(vec![], vec![]))]
#[case::params_in_all_out(Signature::new(vec![rotation_type(), float64_type()], vec![qb_t(), qb_t(), bool_t(), bool_t()]))]
fn test_decoding_signature(#[case] signature: Signature) {
let serial: circuit_json::SerialCircuit = serde_json::from_str(SIMPLE_MEASURE).unwrap();
let options = DecodeOptions::default().with_signature(signature);
let hugr = serial.decode(options).unwrap();
hugr.validate().unwrap();
let measure_op_count = hugr
.children(hugr.entrypoint())
.filter(|&child| {
hugr.get_optype(child)
.as_extension_op()
.is_some_and(|op| op.unqualified_id() == "Measure")
})
.count();
assert_eq!(measure_op_count, 2);
}
#[rstest]
fn test_qubit_elision() {
let ser: circuit_json::SerialCircuit = serde_json::from_str(EMPTY_CIRCUIT).unwrap();
assert_eq!(ser.commands.len(), 0);
let hugr: Hugr = ser
.decode(DecodeOptions::new().with_signature(Signature::new_endo(vec![])))
.unwrap();
assert_eq!(crate::Circuit::new(&hugr).qubit_count(), 0);
check_no_tk1_ops(&hugr);
assert_eq!(crate::Circuit::new(&hugr).num_operations(), 0);
}