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

feat!: Classical op params #56

Merged
merged 3 commits into from
Jul 9, 2024
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
86 changes: 81 additions & 5 deletions src/circuit_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub struct Matrix<T = f64> {
/// The units used in a [`ClassicalExp`].
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash)]
#[serde(untagged)]
#[non_exhaustive]
pub enum ClassicalExpUnit {
/// Unsigned 32-bit integer.
U32(u32),
Expand Down Expand Up @@ -79,15 +80,76 @@ pub struct Conditional {
pub value: u32,
}

/// Additional fields for classical operations,
/// which only act on Bits classically.
//
// Note: The order of the variants here is important.
// Serde will return the first matching variant when deserializing,
// so CopyBits and SetBits must come after other variants that
// define `values` and `n_i`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bit puzzled by this, does the serialization not include the name?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name is in Operation::type, but this field is not directly linked to it.
If type was a closed set we would be able to link classical and type more easily, but as it is now we just have a string field where some special values cause the classical enum to be of some type.
It's probably still possible to do something, but it'd be a big refactor of the Operation definition.

And to be pedantic, the schema does not link specific names with their required fields. It only lists a set of names for which classical must be defined. The Classical object itself is just a oneOf set of options.

https://github.com/CQCL/tket/blob/a47578ec5d79bb5caf23ef2edee3f587bc3c7d14/schemas/circuit_v1.json#L340-L359

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
#[serde(untagged)]
#[non_exhaustive]
pub enum Classical {
/// Multi-bit operation.
MultiBit {
/// The inner operation.
op: Box<Operation>,
/// Multiplier on underlying op for MultiBitOp.
n: u32,
},
/// A range predicate.
RangePredicate {
/// Number of pure input wires to the RangePredicate.
n_i: u32,
/// The inclusive minimum of the RangePredicate.
lower: u64,
/// The inclusive maximum of the RangePredicate.
upper: u64,
},
/// ExplicitModifierOp/ExplicitPredicateOp.
Explicit {
/// Number of pure input wires to the ExplicitModifierOp/ExplicitPredicateOp.
n_i: u32,
/// Name of classical ExplicitModifierOp/ExplicitPredicateOp (e.g. AND).
name: String,
/// Truth table of ExplicitModifierOp/ExplicitPredicateOp.
values: Vec<bool>,
},
/// ClassicalTransformOp
ClassicalTransform {
/// Number of input/output wires.
n_io: u32,
/// Name of classical ClassicalTransformOp (e.g. ClassicalCX).
name: String,
/// Truth table of ClassicalTransformOp.
values: Vec<u32>,
},
/// CopyBitsOp.
CopyBits {
/// Number of input wires to the CopyBitsOp.
n_i: u32,
},
/// SetBitsOp.
SetBits {
/// List of bools that SetBitsOp sets bits to.
values: Vec<bool>,
},
}

/// Serializable operation descriptor.
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct Operation<P = String> {
/// The type of operation.
#[serde(rename = "type")]
pub op_type: OpType,
/// Number of input and output qubits.
#[serde(skip_serializing_if = "Option::is_none")]
pub n_qb: Option<u32>,
/// Additional string stored in the op
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<String>,
/// Expressions for the parameters of the operation.
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Vec<P>>,
Expand All @@ -101,6 +163,9 @@ pub struct Operation<P = String> {
/// A QASM-style classical condition for the operation.
#[serde(skip_serializing_if = "Option::is_none")]
pub conditional: Option<Conditional>,
/// Data for commands which only act on Bits classically.
#[serde(skip_serializing_if = "Option::is_none")]
pub classical: Option<Box<Classical>>,
}

/// Operation applied in a circuit, with defined arguments.
Expand Down Expand Up @@ -137,6 +202,21 @@ pub struct SerialCircuit<P = String> {
pub implicit_permutation: Vec<Permutation>,
}

impl<P> Default for Operation<P> {
fn default() -> Self {
Self {
op_type: Default::default(),
n_qb: None,
data: None,
params: None,
op_box: None,
signature: None,
conditional: None,
classical: None,
}
}
}

impl<P> Operation<P> {
/// Returns a default-initialized Operation with the given type.
///
Expand All @@ -145,11 +225,7 @@ impl<P> Operation<P> {
pub fn from_optype(op_type: OpType) -> Self {
Self {
op_type,
n_qb: None,
params: None,
op_box: None,
signature: None,
conditional: None,
..Operation::default()
}
}
}
3 changes: 2 additions & 1 deletion src/optype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use strum::EnumString;

/// Operation types in a quantum circuit.
#[cfg_attr(feature = "pyo3", pyclass(name = "RsOpType"))]
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash, EnumString)]
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq, Eq, Hash, EnumString)]
#[non_exhaustive]
pub enum OpType {
/// Quantum input node of the circuit
Expand Down Expand Up @@ -280,6 +280,7 @@ pub enum OpType {

/// Identity
#[allow(non_camel_case_types)]
#[default]
noop,

/// Measure a qubit, producing a classical output
Expand Down
144 changes: 144 additions & 0 deletions tests/data/classical.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
{
"bits": [
[
"c",
[
0
]
],
[
"c",
[
1
]
]
],
"commands": [
{
"args": [
[
"c",
[
0
]
],
[
"c",
[
1
]
]
],
"op": {
"type": "MultiBit",
"classical": {
"op": {
"type": "SetBits",
"classical": {
"values": [
true
]
}
},
"n": 2
}
}
},
{
"args": [
[
"c",
[
0
]
],
[
"c",
[
1
]
]
],
"op": {
"type": "ClassicalTransform",
"classical": {
"name": "ClassicalCX",
"n_io": 2,
"values": [
0,
1,
3,
2
]
}
}
},
{
"args": [
[
"c",
[
0
]
],
[
"c",
[
1
]
]
],
"op": {
"type": "CopyBits",
"classical": {
"n_i": 2
}
}
}
],
"implicit_permutation": [
[
[
"q",
[
0
]
],
[
"q",
[
0
]
]
],
[
[
"q",
[
1
]
],
[
"q",
[
1
]
]
]
],
"phase": "0.0",
"qubits": [
[
"q",
[
0
]
],
[
"q",
[
1
]
]
]
}
2 changes: 0 additions & 2 deletions tests/data/diagonal-box.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@
}
}
],
"created_qubits": [],
"discarded_qubits": [],
"implicit_permutation": [
[
[
Expand Down
3 changes: 2 additions & 1 deletion tests/data/simple.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
]
],
"op": {
"type": "H"
"type": "H",
"data": "Custom data"
}
},
{
Expand Down
7 changes: 4 additions & 3 deletions tests/roundtrip.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
//! Roundtrip tests
use assert_json_diff::assert_json_include;
use assert_json_diff::assert_json_eq;
use rstest::rstest;
use serde_json::Value;
use tket_json_rs::SerialCircuit;

const SIMPLE: &str = include_str!("data/simple.json");
const CLASSICAL: &str = include_str!("data/classical.json");
const DIAGONAL: &str = include_str!("data/diagonal-box.json");

#[rstest]
#[case::simple(SIMPLE, 4)]
#[case::classical(CLASSICAL, 3)]
#[case::diagonal_box(DIAGONAL, 1)]
fn roundtrip(#[case] json: &str, #[case] num_commands: usize) {
let initial_json: Value = serde_json::from_str(json).unwrap();
Expand All @@ -17,8 +19,7 @@ fn roundtrip(#[case] json: &str, #[case] num_commands: usize) {
assert_eq!(ser.commands.len(), num_commands);

let reencoded_json = serde_json::to_value(&ser).unwrap();
// Do a partial comparison. The re-encoded circuit does not include "created_qubits" nor "discarded_qubits".
assert_json_include!(actual: initial_json, expected: reencoded_json);
assert_json_eq!(reencoded_json, initial_json);

let reser: SerialCircuit = serde_json::from_value(reencoded_json).unwrap();

Expand Down
Loading