Skip to content

Commit

Permalink
feat: Support classical expressions (#86)
Browse files Browse the repository at this point in the history
Closes #83 

~~The json schema has not been updated yet (see
CQCL/tket#1654), so the format may not be
completely correct.~~

Edit: json schema is now merged CQCL/tket#1660

---------

Co-authored-by: Alec Edgington <[email protected]>
  • Loading branch information
aborgna-q and cqc-alec authored Nov 6, 2024
1 parent bd5fcc4 commit aeb1930
Show file tree
Hide file tree
Showing 12 changed files with 735 additions and 15 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pythonize = { workspace = true, optional = true }
strum = { workspace = true, features = ["derive"] }

[dev-dependencies]
itertools = { workspace = true }
pyo3 = { workspace = true }
rstest = { workspace = true }
assert-json-diff = { workspace = true }
Expand All @@ -37,6 +38,7 @@ name = "integration"
path = "tests/lib.rs"

[workspace.dependencies]
itertools = "0.13.0"
pyo3 = "0.22.2"
pythonize = "0.22.0"
rstest = "0.23.0"
Expand Down
9 changes: 9 additions & 0 deletions src/circuit_json.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Contains structs for serializing and deserializing TKET circuits to and from
//! JSON.
use crate::clexpr::ClExpr;
use crate::opbox::OpBox;
use crate::optype::OpType;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -168,6 +169,12 @@ pub struct Operation<P = String> {
#[serde(rename = "box")]
#[serde(skip_serializing_if = "Option::is_none")]
pub op_box: Option<OpBox>,
/// Classical expression.
///
/// Required if the operation is of type [`OpType::ClExpr`].
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "expr")]
pub classical_expr: Option<ClExpr>,
/// The pre-computed signature.
#[serde(skip_serializing_if = "Option::is_none")]
pub signature: Option<Vec<String>>,
Expand Down Expand Up @@ -240,6 +247,7 @@ impl<P> Default for Operation<P> {
data: None,
params: None,
op_box: None,
classical_expr: None,
signature: None,
conditional: None,
classical: None,
Expand Down Expand Up @@ -289,6 +297,7 @@ impl<P> Operation<P> {
.params
.map(|params| params.into_iter().map(f).collect()),
op_box: self.op_box,
classical_expr: self.classical_expr,
signature: self.signature,
conditional: self.conditional,
classical: self.classical,
Expand Down
87 changes: 87 additions & 0 deletions src/clexpr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! Classical expressions
pub mod op;
pub mod operator;

use operator::ClOperator;
use serde::de::SeqAccess;
use serde::ser::SerializeSeq;
use serde::{Deserialize, Serialize};

/// Data encoding a classical expression.
///
/// A classical expression operates over multi-bit registers and/or individual bits,
/// which are identified here by their individual bit positions.
///
/// This is included in a [`Operation`] when the operation is a [`OpType::ClExpr`].
///
/// [`Operation`]: crate::circuit_json::Operation
/// [`OpType::ClExpr`]: crate::optype::OpType::ClExpr
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ClExpr {
/// Mapping between bit variables in the expression and the position of the
/// corresponding bit in the `args` list.
pub bit_posn: Vec<(u32, u32)>,
/// The encoded expression.
pub expr: ClOperator,
/// The input bits of the expression.
pub reg_posn: Vec<InputClRegister>,
/// The output bits of the expression.
pub output_posn: ClRegisterBits,
}

/// An input register for a classical expression.
///
/// Contains the input index as well as the bits that are part of the register.
///
/// Serialized as a list with two elements: the index and the bits.
#[derive(Debug, Default, PartialEq, Clone)]
pub struct InputClRegister {
/// The index of the register variable in the expression.
pub index: u32,
/// The sequence of positions of bits comprising the register variable.
pub bits: ClRegisterBits,
}

/// The sequence of positions of bits in the output.
///
/// Registers are little-endian, so the first bit is the least significant.
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ClRegisterBits(pub Vec<u32>);

impl Serialize for InputClRegister {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut seq = serializer.serialize_seq(Some(2))?;
seq.serialize_element(&self.index)?;
seq.serialize_element(&self.bits)?;
seq.end()
}
}

impl<'de> Deserialize<'de> for InputClRegister {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct Visitor;

impl<'de_vis> serde::de::Visitor<'de_vis> for Visitor {
type Value = InputClRegister;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a list of two elements: the index and the bits")
}

fn visit_seq<A: SeqAccess<'de_vis>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let index = seq
.next_element::<u32>()?
.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
let bits = seq
.next_element::<ClRegisterBits>()?
.ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
Ok(InputClRegister { index, bits })
}
}

deserializer.deserialize_seq(Visitor)
}
}
73 changes: 73 additions & 0 deletions src/clexpr/op.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//! Classical expression operations.
use serde::{Deserialize, Serialize};
use strum::EnumString;

/// List of supported classical expressions.
///
/// Corresponds to `pytket.circuit.ClOp`.
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq, Eq, Hash, EnumString)]
#[non_exhaustive]
pub enum ClOp {
/// Invalid operation
#[default]
INVALID,

/// Bitwise AND
BitAnd,
/// Bitwise OR
BitOr,
/// Bitwise XOR
BitXor,
/// Bitwise equality
BitEq,
/// Bitwise inequality
BitNeq,
/// Bitwise NOT
BitNot,
/// Constant zero bit
BitZero,
/// Constant one bit
BitOne,

/// Registerwise AND
RegAnd,
/// Registerwise OR
RegOr,
/// Registerwise XOR
RegXor,
/// Registerwise equality
RegEq,
/// Registerwise inequality
RegNeq,
/// Registerwise NOT
RegNot,
/// Constant all-zeros register
RegZero,
/// Constant all-ones register
RegOne,
/// Integer less-than comparison
RegLt,
/// Integer greater-than comparison
RegGt,
/// Integer less-than-or-equal comparison
RegLeq,
/// Integer greater-than-or-equal comparison
RegGeq,
/// Integer addition
RegAdd,
/// Integer subtraction
RegSub,
/// Integer multiplication
RegMul,
/// Integer division
RegDiv,
/// Integer exponentiation
RegPow,
/// Left shift
RegLsh,
/// Right shift
RegRsh,
/// Integer negation
RegNeg,
}
78 changes: 78 additions & 0 deletions src/clexpr/operator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//! A tree of operators forming a classical expression.
use serde::{Deserialize, Serialize};

use super::op::ClOp;

/// A node in a classical expression tree.
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ClOperator {
/// The operation to be performed.
pub op: ClOp,
/// The arguments to the operation.
pub args: Vec<ClArgument>,
}

/// An argument to a classical expression operation.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(tag = "type", content = "input")]
pub enum ClArgument {
/// A terminal argument.
#[serde(rename = "term")]
Terminal(ClTerminal),
/// A sub-expression.
#[serde(rename = "expr")]
Expression(Box<ClOperator>),
}

/// A terminal argument in a classical expression operation.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(tag = "type", content = "term")]
pub enum ClTerminal {
/// A terminal argument.
#[serde(rename = "var")]
Variable(ClVariable),
/// A constant integer.
#[serde(rename = "int")]
Int(u64),
}

/// A variable terminal argument in a classical expression operation.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Hash)]
#[non_exhaustive]
#[serde(tag = "type", content = "var")]
pub enum ClVariable {
/// A register variable.
#[serde(rename = "reg")]
Register {
/// The register index.
index: u32,
},
/// A constant bit.
#[serde(rename = "bit")]
Bit {
/// The bit index.
index: u32,
},
}

impl Default for ClArgument {
fn default() -> Self {
ClArgument::Terminal(ClTerminal::default())
}
}

impl Default for ClTerminal {
fn default() -> Self {
ClTerminal::Int(0)
}
}

impl Default for ClVariable {
fn default() -> Self {
ClVariable::Register { index: 0 }
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! [TKET](https://github.com/CQCL/tket) quantum compiler.
pub mod circuit_json;
pub mod clexpr;
pub mod opbox;
pub mod optype;
#[cfg(feature = "pyo3")]
Expand Down
2 changes: 2 additions & 0 deletions src/opbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ pub enum OpBox {
control_state: u32,
},
/// Holding box for abstract expressions on Bits.
///
/// Deprecated in favour of [`OpType::ClExpr`].
ClassicalExpBox {
id: BoxID,
n_i: u32,
Expand Down
11 changes: 11 additions & 0 deletions src/optype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,8 @@ pub enum OpType {

/// See [`ClassicalExpBox`]
///
/// Deprecated. Use [`OpType::ClExpr`] instead.
///
/// [`ClassicalExpBox`]: crate::opbox::OpBox::ClassicalExpBox
ClassicalExpBox,

Expand Down Expand Up @@ -547,4 +549,13 @@ pub enum OpType {
///
/// [`DiagonalBox`]: crate::opbox::OpBox::DiagonalBox
DiagonalBox,

/// Classical expression.
///
/// An operation of this type is accompanied by a [`ClExpr`] object.
///
/// This is a replacement of the deprecated [`OpType::ClassicalExpBox`].
///
/// [`ClExpr`]: crate::clexpr::ClExpr
ClExpr,
}
Loading

0 comments on commit aeb1930

Please sign in to comment.