Skip to content

Commit

Permalink
feat: Decode pytket op parameters (#644)
Browse files Browse the repository at this point in the history
This is an alternative to #635, using pure-rust instead of calling to
python so we can keep the pytket decoder on the `tket2` crate.

Improves parsing of pytket operation parameters by defining a grammar
and parser for sympy expressions using `pest` (based on the [calculator
example](https://pest.rs/book/examples/calculator.html) for infix
operation precedence).

- Unrecognized operations are still put inside an opaque `SympyOp`, but
that should be easy to change in the future.
- I tested multiple sympy expressions to ensure we are able to parse
them, but unrecognized ones will also fallback to `SympyOp`.

This is still missing routing parameters to hugr inputs (#628), as it is
blocked by CQCL/hugr#1562.

drive-by: Move the pytket parameter encoding/decoding routines to
`::serialize::pytket::param::{de,en}code`.

Closes #637.
  • Loading branch information
aborgna-q authored Oct 10, 2024
1 parent a837837 commit ccf53b5
Show file tree
Hide file tree
Showing 14 changed files with 722 additions and 166 deletions.
124 changes: 124 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ num-complex = "0.4"
num-rational = "0.4"
num_cpus = "1.16.0"
peak_alloc = "0.2.0"
pest = "2.7.13"
pest_derive = "2.7.13"
petgraph = { version = "0.6.3", default-features = false }
priority-queue = "2.1.1"
rayon = "1.5"
Expand Down
2 changes: 2 additions & 0 deletions tket2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ chrono = { workspace = true }
bytemuck = { workspace = true }
crossbeam-channel = { workspace = true }
tracing = { workspace = true }
pest = { workspace = true }
pest_derive = { workspace = true }
zstd = { workspace = true, optional = true }
# Required to acces the `Package` type.
# Remove once https://github.com/CQCL/hugr/issues/1530 is fixed.
Expand Down
2 changes: 0 additions & 2 deletions tket2/src/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -717,8 +717,6 @@ mod tests {
assert_eq!(circ.circuit_signature().input_count(), qubits + bits);
assert_eq!(circ.circuit_signature().output_count(), qubits + bits);
assert_eq!(circ.qubit_count(), qubits);
assert_eq!(circ.num_operations(), 3);
assert_eq!(circ.operations().count(), 3);

assert_eq!(circ.units().count(), qubits + bits);
assert_eq!(circ.nonlinear_units().count(), bits);
Expand Down
47 changes: 2 additions & 45 deletions tket2/src/serialize/pytket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
mod decoder;
mod encoder;
mod op;
mod param;

use hugr::std_extensions::arithmetic::float_types::ConstF64;
use hugr::types::Type;

use hugr::Node;
Expand All @@ -20,14 +20,13 @@ use std::hash::{Hash, Hasher};
use std::path::Path;
use std::{fs, io};

use hugr::ops::{NamedOp, OpType, Value};
use hugr::ops::{NamedOp, OpType};

use derive_more::{Display, Error, From};
use tket_json_rs::circuit_json::{self, SerialCircuit};
use tket_json_rs::optype::OpType as SerialOpType;

use crate::circuit::Circuit;
use crate::extension::rotation::ConstRotation;

use self::decoder::Tk1Decoder;
use self::encoder::Tk1Encoder;
Expand Down Expand Up @@ -282,48 +281,6 @@ pub enum TK1ConvertError {
FileLoadError(io::Error),
}

/// Try to interpret a TKET1 parameter as a constant value.
///
/// Angle parameters in TKET1 are encoded as a number of half-turns,
/// whereas HUGR uses radians.
#[inline]
fn try_param_to_constant(param: &str) -> Option<Value> {
fn parse_val(n: &str) -> Option<f64> {
n.parse::<f64>().ok()
}

let half_turns = if let Some(f) = parse_val(param) {
f
} else if param.split('/').count() == 2 {
// TODO: Use the rational types from `Hugr::extensions::rotation`
let (n, d) = param.split_once('/').unwrap();
let n = parse_val(n)?;
let d = parse_val(d)?;
n / d
} else {
return None;
};

ConstRotation::new(half_turns).ok().map(Into::into)
}

/// Convert a HUGR rotation or float constant to a TKET1 parameter.
///
/// Angle parameters in TKET1 are encoded as a number of half-turns,
/// whereas HUGR uses radians.
#[inline]
fn try_constant_to_param(val: &Value) -> Option<String> {
if let Some(const_angle) = val.get_custom_value::<ConstRotation>() {
let half_turns = const_angle.half_turns();
Some(half_turns.to_string())
} else if let Some(const_float) = val.get_custom_value::<ConstF64>() {
let float = const_float.value();
Some(float.to_string())
} else {
None
}
}

/// A hashed register, used to identify registers in the [`Tk1Decoder::register_wire`] map,
/// avoiding string and vector clones on lookup.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
Expand Down
Loading

0 comments on commit ccf53b5

Please sign in to comment.