Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update json decoder #34

Merged
merged 5 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
# symengine = { path = "./ext/symengine.rs", features = ["serde"] }
lazy_static = "1.4.0"
cgmath = "0.18.0"
num-rational = "0.4"
Expand All @@ -25,11 +24,12 @@ serde_json = "1.0"
downcast-rs = "1.2.0"
portgraph = "0.7.0"
priority-queue = "1.3.0"
quantinuum-hugr = { git = "https://github.com/CQCL-DEV/hugr", tag = "v0.0.0-alpha.3" }
quantinuum-hugr = { git = "https://github.com/CQCL-DEV/hugr", tag = "v0.0.0-alpha.4" }
smol_str = "0.2.0"
typetag = "0.2.8"
itertools = "0.11.0"
petgraph = { version = "0.6.3", default-features = false }
serde_yaml = "0.9.22"

[features]
pyo3 = ["dep:pyo3", "tket-json-rs/pyo3", "tket-json-rs/tket2ops", "portgraph/pyo3", "quantinuum-hugr/pyo3"]
Expand Down
2 changes: 1 addition & 1 deletion pyrs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ portgraph = { version = "0.7.0", features = ["pyo3"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tket-json-rs = { git = "https://github.com/CQCL/tket-json-rs", features = ["pyo3"] }
quantinuum-hugr = { version = "0.1.0", git="https://github.com/CQCL-DEV/hugr", tag = "v0.0.0-alpha.3" }
quantinuum-hugr = { git = "https://github.com/CQCL-DEV/hugr", tag = "v0.0.0-alpha.4" }
6 changes: 2 additions & 4 deletions pyrs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
use hugr::Hugr;
use portgraph::graph::{Direction, NodeIndex};
use pyo3::create_exception;
use pyo3::exceptions::PyException;
use pyo3::prelude::*;
use tket2::json::json_convert::load_serial;
use tket2::json::TKET1Decode;
use tket_json_rs::circuit_json::SerialCircuit;
use tket_json_rs::optype::OpType;

create_exception!(pyrs, PyValidateError, PyException);

#[pyfunction]
fn check_soundness(c: Py<PyAny>) -> PyResult<()> {
let ser_c = SerialCircuit::_from_tket1(c);
let hugr: Hugr = load_serial(ser_c);
let hugr: Hugr = ser_c.decode().unwrap();
println!("{}", hugr.dot_string());
hugr.validate()
.map_err(|e| PyValidateError::new_err(e.to_string()))
Expand Down
4 changes: 3 additions & 1 deletion src/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ pub mod command;
// TODO: Move TKET1's custom op definition to tket-rs (or hugr?)
//mod tk1ops;

use crate::utils::QB;

use self::command::{Command, CommandIterator, Unit};

use hugr::ops::OpTrait;
Expand Down Expand Up @@ -46,7 +48,7 @@ pub trait Circuit<'circ> {
fn qubits(&self) -> Vec<Unit> {
self.units()
.iter()
.filter(|(_, typ)| typ == &LinearType::Qubit.into())
.filter(|(_, typ)| typ == &QB)
.map(|(unit, _)| *unit)
.collect()
}
Expand Down
56 changes: 55 additions & 1 deletion src/json.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,57 @@
//! Json serialization and deserialization.

pub mod json_convert;
mod decoder;
//mod encoder;
pub mod op;

#[cfg(test)]
mod tests;

use hugr::ops::OpType;
use hugr::Hugr;

use thiserror::Error;
use tket_json_rs::circuit_json::SerialCircuit;
use tket_json_rs::optype::OpType as JsonOpType;

use self::decoder::JsonDecoder;

/// A JSON-serialized TKET1 circuit that can be converted to a [`Hugr`].
pub trait TKET1Decode: Sized {
/// The error type for decoding.
type DecodeError;
/// Convert the serialized circuit to a [`Hugr`].
fn decode(self) -> Result<Hugr, Self::DecodeError>;
}

impl TKET1Decode for SerialCircuit {
type DecodeError = OpConvertError;

fn decode(self) -> Result<Hugr, Self::DecodeError> {
let mut decoder = JsonDecoder::new(&self);

if !self.phase.is_empty() {
// TODO - add a phase gate
// let phase = Param::new(serialcirc.phase);
// decoder.add_phase(phase);
}

// TODO: Check the implicit permutation in the serialized circuit.

for com in self.commands {
decoder.add_command(com);
}
Ok(decoder.finish())
}
}

/// Error type for conversion between `Op` and `OpType`.
#[derive(Debug, Error)]
pub enum OpConvertError {
/// The serialized operation is not supported.
#[error("Unsupported serialized operation: {0:?}")]
UnsupportedSerializedOp(JsonOpType),
/// The serialized operation is not supported.
#[error("Cannot serialize operation: {0:?}")]
UnsupportedOpSerialization(OpType),
}
160 changes: 160 additions & 0 deletions src/json/decoder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//! Intermediate structure for converting decoding [`SerialCircuit`]s into [`Hugr`]s.

use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::mem;

use hugr::builder::{AppendWire, CircuitBuilder, Container, DFGBuilder, Dataflow, DataflowHugr};
use hugr::ops::ConstValue;
use hugr::types::Signature;
use hugr::{Hugr, Wire};

use serde_json::json;
use tket_json_rs::circuit_json;
use tket_json_rs::circuit_json::SerialCircuit;

use super::op::JsonOp;
use crate::utils::{BIT, QB};

/// The state of an in-progress [`DFGBuilder`] being built from a [`SerialCircuit`].
///
/// Mostly used to define helper internal methods.
#[derive(Debug, PartialEq)]
pub(super) struct JsonDecoder {
/// The Hugr being built.
pub hugr: DFGBuilder<Hugr>,
/// The dangling wires of the builder.
/// Used to generate [`CircuitBuilder`]s.
dangling_wires: Vec<Wire>,
/// A map from the json registers to flat wire indices.
register_wire: HashMap<RegisterHash, usize>,
/// The number of qubits in the circuit.
num_qubits: usize,
/// The number of bits in the circuit.
num_bits: usize,
}

impl JsonDecoder {
/// Initialize a new [`JsonDecoder`], using the metadata from a [`SerialCircuit`].
pub fn new(serialcirc: &SerialCircuit) -> Self {
let num_qubits = serialcirc.qubits.len();
let num_bits = serialcirc.bits.len();
// Map each (register name, index) pair to an offset in the signature.
let mut wire_map: HashMap<RegisterHash, usize> =
HashMap::with_capacity(num_bits + num_qubits);
for (i, register) in serialcirc
.qubits
.iter()
.chain(serialcirc.bits.iter())
.enumerate()
{
if register.1.len() != 1 {
// TODO: Support multi-index registers?
panic!("Register {} has more than one index", register.0);
}
wire_map.insert((register, 0).into(), i);
}
let sig = Signature::new_linear([vec![QB; num_qubits], vec![BIT; num_bits]].concat());

let mut dfg = DFGBuilder::new(sig.input, sig.output).unwrap();

dfg.set_metadata(json!({"name": serialcirc.name}));

let dangling_wires = dfg.input_wires().collect::<Vec<_>>();
JsonDecoder {
hugr: dfg,
dangling_wires,
register_wire: wire_map,
num_qubits,
num_bits,
}
}

/// Finish building the [`Hugr`].
pub fn finish(self) -> Hugr {
// TODO: Throw validation error?
self.hugr
.finish_hugr_with_outputs(self.dangling_wires)
.unwrap()
}

/// Add a [`Command`] from the serial circuit to the [`JsonDecoder`].
///
/// - [`Command`]: circuit_json::Command
pub fn add_command(&mut self, command: circuit_json::Command) {
let circuit_json::Command { op, args, .. } = command;
let params = op.params.clone().unwrap_or_default();
let num_qubits = args
.iter()
.take_while(|&arg| self.reg_wire(arg, 0) < self.num_qubits)
.count();
let num_bits = args.len() - num_qubits;
let op = JsonOp::new_from_op(op, num_qubits, num_bits);

let args: Vec<_> = args.into_iter().map(|reg| self.reg_wire(&reg, 0)).collect();

let param_wires: Vec<Wire> = params.iter().map(|p| self.get_param_wire(p)).collect();

let append_wires = args
.into_iter()
.map(AppendWire::I)
.chain(param_wires.into_iter().map(AppendWire::W));

self.with_circ_builder(|circ| {
circ.append_and_consume(&op, append_wires).unwrap();
});
}

/// Apply a function to the internal hugr builder viewed as a [`CircuitBuilder`].
fn with_circ_builder(&mut self, f: impl FnOnce(&mut CircuitBuilder<DFGBuilder<Hugr>>)) {
let mut circ = self.hugr.as_circuit(mem::take(&mut self.dangling_wires));
f(&mut circ);
self.dangling_wires = circ.finish();
}

/// Returns the wire carrying a parameter.
///
/// If the parameter is a constant, a constant definition is added to the Hugr.
///
/// TODO: If the parameter is a variable, returns the corresponding wire from the input.
fn get_param_wire(&mut self, param: &str) -> Wire {
if let Ok(f) = param.parse::<f64>() {
self.hugr.add_load_const(ConstValue::F64(f)).unwrap()
} else if param.split('/').count() == 2 {
// TODO: Use the rational types from `Hugr::extensions::rotation`
let (n, d) = param.split_once('/').unwrap();
let n = n.parse::<f64>().unwrap();
let d = d.parse::<f64>().unwrap();
self.hugr.add_load_const(ConstValue::F64(n / d)).unwrap()
} else {
// TODO: Pre-compute variables and add them to the input signature.
todo!("Variable parameters not yet supported")
}
}

/// Return the wire index for the `elem`th value of a given register.
///
/// Relies on TKET1 constraint that all registers have unique names.
fn reg_wire(&self, register: &circuit_json::Register, elem: usize) -> usize {
self.register_wire[&(register, elem).into()]
}
}

/// A hashed register, used to identify registers in the [`JsonDecoder::register_wire`] map,
/// avoiding string clones on lookup.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct RegisterHash {
hash: u64,
}

impl From<(&circuit_json::Register, usize)> for RegisterHash {
fn from((reg, elem): (&circuit_json::Register, usize)) -> Self {
let mut hasher = DefaultHasher::new();
reg.0.hash(&mut hasher);
reg.1[elem].hash(&mut hasher);
Self {
hash: hasher.finish(),
}
}
}
Loading