tket_json_rs/
opbox.rs

1//! Data definition for box operations.
2//!
3//! These definitions correspond to the operations' `box` attribute in the
4//! [`circuit_v1`](https://github.com/Quantinuum/tket/blob/develop/schemas/circuit_v1.json)
5//! schema.
6
7use std::collections::HashMap;
8use std::str::FromStr;
9
10use crate::circuit_json::{
11    ClassicalExp, CustomGate, Matrix, Operation, Permutation, SerialCircuit,
12};
13use crate::optype::OpType;
14use crate::register::{Bitstring, Qubit};
15#[cfg(feature = "schemars")]
16use schemars::JsonSchema;
17use serde::{Deserialize, Serialize};
18
19/// Unique identifier for an [`OpBox`].
20#[cfg_attr(feature = "schemars", derive(JsonSchema))]
21#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash)]
22pub struct BoxID(uuid::Uuid);
23
24impl BoxID {
25    /// Create a new [`BoxID`] with a random UUID.
26    pub fn new() -> Self {
27        BoxID(uuid::Uuid::new_v4())
28    }
29}
30
31impl FromStr for BoxID {
32    type Err = String;
33
34    fn from_str(s: &str) -> Result<Self, Self::Err> {
35        let uuid = uuid::Uuid::from_str(s).map_err(|e| e.to_string())?;
36        Ok(BoxID(uuid))
37    }
38}
39
40impl std::fmt::Display for BoxID {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        write!(f, "{}", self.0)
43    }
44}
45
46impl Default for BoxID {
47    fn default() -> Self {
48        BoxID::new()
49    }
50}
51
52/// Box for an operation, the enum variant names come from the names
53/// of the C++ operations and are renamed if the string corresponding
54/// to the operation is differently named when serializing.
55#[cfg_attr(feature = "schemars", derive(JsonSchema))]
56#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
57#[serde(tag = "type")]
58#[allow(missing_docs)]
59#[non_exhaustive]
60pub enum OpBox {
61    /// Operation defined as a circuit.
62    CircBox {
63        id: BoxID,
64        /// The circuit defining the operation.
65        circuit: SerialCircuit,
66    },
67    /// One-qubit operation defined as a unitary matrix.
68    Unitary1qBox {
69        id: BoxID,
70        /// 2x2 matrix of complex numbers
71        matrix: [[(f64, f64); 2]; 2],
72    },
73    /// Two-qubit operation defined as a unitary matrix.
74    Unitary2qBox {
75        id: BoxID,
76        /// 4x4 matrix of complex numbers
77        matrix: [[(f64, f64); 4]; 4],
78    },
79    /// Three-qubit operation defined as a unitary matrix.
80    Unitary3qBox {
81        id: BoxID,
82        /// 8x8 matrix of complex numbers
83        matrix: Box<[[(f64, f64); 8]; 8]>,
84    },
85    /// Two-qubit operation defined in terms of a hermitian matrix and a phase.
86    ExpBox {
87        id: BoxID,
88        /// 4x4 matrix of complex numbers
89        matrix: [[(f64, f64); 4]; 4],
90        /// Phase of the operation.
91        phase: f64,
92    },
93    /// Operation defined as the exponential of a tensor of Pauli operators.
94    PauliExpBox {
95        id: BoxID,
96        /// List of Pauli operators.
97        paulis: Vec<String>,
98        /// Symengine expression.
99        phase: String,
100        /// Config param for decomposition of Pauli exponentials.
101        #[serde(default)]
102        cx_config: String,
103    },
104    /// A pair of (not necessarily commuting) Pauli exponentials performed in sequence.
105    PauliExpPairBox {
106        id: BoxID,
107        /// List of List of Pauli operators.
108        paulis_pair: Vec<Vec<String>>,
109        /// List of Symengine expressions.
110        phase_pair: Vec<String>,
111        /// Config param for decomposition of Pauli exponentials.
112        cx_config: String,
113    },
114    /// Operation defined as a set of commuting exponentials of a tensor of Pauli operators.
115    PauliExpCommutingSetBox {
116        id: BoxID,
117        /// List of Symengine expressions.
118        pauli_gadgets: Vec<(Vec<String>, String)>,
119        /// Config param for decomposition of Pauli exponentials.
120        cx_config: String,
121    },
122    /// An unordered collection of Pauli exponentials that can be synthesised in
123    /// any order, causing a change in the unitary operation. Synthesis order
124    /// depends on the synthesis strategy chosen only.
125    TermSequenceBox {
126        id: BoxID,
127        /// List of Symengine expressions.
128        pauli_gadgets: Vec<(Vec<String>, String)>,
129        /// Synthesis strategy. See [`PauliSynthStrat`].
130        #[serde(default)]
131        synth_strategy: PauliSynthStrat,
132        /// Partition strategy. See [`PauliPartitionStrat`].
133        #[serde(default)]
134        partition_strategy: PauliPartitionStrat,
135        /// Graph colouring method. See [`GraphColourMethod`].
136        #[serde(default)]
137        graph_colouring: GraphColourMethod,
138        /// Configurations for CXs upon decompose phase gadgets.
139        #[serde(default)]
140        cx_config: CXConfigType,
141    },
142    /// An operation capable of representing arbitrary Circuits made up of CNOT
143    /// and RZ, as a PhasePolynomial plus a boolean matrix representing an
144    /// additional linear transformation.
145    PhasePolyBox {
146        id: BoxID,
147        /// Number of qubits.
148        n_qubits: u32,
149        /// Map from qubits to inputs.
150        qubit_indices: Vec<(Qubit, u32)>,
151        /// The phase polynomial definition.
152        /// Represented by a map from bitstring to expression of coefficient.
153        phase_polynomial: Vec<Vec<(Bitstring, String)>>,
154        /// The additional linear transformation.
155        linear_transformation: Matrix,
156    },
157    /// A user-defined assertion specified by a list of Pauli stabilisers.
158    StabiliserAssertionBox {
159        id: BoxID,
160        stabilisers: Vec<PauliStabiliser>,
161    },
162    /// A user-defined assertion specified by a 2x2, 4x4, or 8x8 projector matrix.
163    ProjectorAssertionBox { id: BoxID, matrix: Matrix },
164    /// A user-defined gate defined by a parametrised Circuit.
165    #[serde(alias = "Composite")]
166    CustomGate {
167        id: BoxID,
168        /// The gate defined as a circuit.
169        gate: CustomGate,
170        // Vec of Symengine Expr
171        params: Vec<String>,
172    },
173    /// Wraps another quantum op, adding control qubits.
174    QControlBox {
175        id: BoxID,
176        /// Number of control qubits.
177        n_controls: u32,
178        /// The operation to be controlled.
179        op: Box<Operation>,
180        /// The state of the control.
181        #[serde(default)]
182        control_state: u32,
183    },
184    /// Holding box for abstract expressions on Bits.
185    ///
186    /// Deprecated in favour of [`OpType::ClExpr`].
187    ClassicalExpBox {
188        id: BoxID,
189        n_i: u32,
190        n_io: u32,
191        n_o: u32,
192        exp: ClassicalExp,
193    },
194    /// Binary matrix form of a stabilizer tableau for unitary Clifford circuits.
195    UnitaryTableauBox {
196        id: BoxID,
197        /// The tableau.
198        tab: UnitaryTableau,
199    },
200    /// A user-defined multiplexor specified by a map from bitstrings to Operations.
201    MultiplexorBox {
202        id: BoxID,
203        op_map: Vec<(Bitstring, Operation)>,
204    },
205    /// A user-defined multiplexed rotation gate specified by a map from
206    /// bitstrings to Operations.
207    MultiplexedRotationBox {
208        id: BoxID,
209        op_map: Vec<(Bitstring, Operation)>,
210    },
211    /// A user-defined multiplexed rotation gate specified by a map from
212    /// bitstrings to Operations.
213    MultiplexedU2Box {
214        id: BoxID,
215        op_map: Vec<(Bitstring, Operation)>,
216        #[serde(default = "default_true")]
217        impl_diag: bool,
218    },
219    /// A user-defined multiplexed tensor product of U2 gates specified by a map
220    /// from bitstrings to lists of Op or a list of bitstring-list(Op s) pairs.
221    MultiplexedTensoredU2Box {
222        id: BoxID,
223        op_map: Vec<(Bitstring, Operation)>,
224    },
225    /// An operation that constructs a circuit to implement the specified
226    /// permutation of classical basis states.
227    ToffoliBox {
228        id: BoxID,
229        /// The classical basis state permutation.
230        permutation: Permutation,
231        // Synthesis strategy. See [`ToffoliBoxSynthStrat`].
232        strat: ToffoliBoxSynthStrat,
233        // The rotation axis of the multiplexors used in the decomposition. Can
234        // be either `Rx` or `Ry`. Only applicable to the
235        // [`ToffoliBoxSynthStrat::Matching`] strategy. Default to `Ry`.
236        #[serde(skip_serializing_if = "Option::is_none")]
237        #[serde(default)]
238        rotation_axis: Option<OpType>,
239    },
240    /// An operation composed of ‘action’, ‘compute’ and ‘uncompute’ circuits
241    ConjugationBox {
242        id: BoxID,
243        /// Reversible computation.
244        compute: Box<Operation>,
245        /// Internal operation to be applied.
246        action: Box<Operation>,
247        /// Reverse uncomputation.
248        #[serde(default)]
249        uncompute: Option<Box<Operation>>,
250    },
251    /// A placeholder operation that holds resource data. This box type cannot
252    /// be decomposed into a circuit. It only serves to record resource data for
253    /// a region of a circuit: for example, upper and lower bounds on gate
254    /// counts and depth. A circuit containing such a box cannot be executed.
255    DummyBox {
256        id: BoxID,
257        /// Number of qubits.
258        n_qubits: u32,
259        /// Number of bits.
260        n_bits: u32,
261        /// Dummy resource data.
262        resource_data: ResourceData,
263    },
264    /// A box for preparing quantum states using multiplexed-Ry and multiplexed-Rz gates.
265    StatePreparationBox {
266        id: BoxID,
267        /// Normalised statevector of complex numbers
268        statevector: Matrix,
269        /// Whether to implement the dagger of the state preparation circuit, default to false.
270        #[serde(default)]
271        is_inverse: bool,
272        /// Whether to explicitly set the state to zero initially (by default
273        /// the initial zero state is assumed and no explicit reset is applied).
274        #[serde(default)]
275        with_initial_reset: bool,
276    },
277    /// A box for synthesising a diagonal unitary matrix into a sequence of multiplexed-Rz gates.
278    DiagonalBox {
279        id: BoxID,
280        /// Diagonal entries.
281        diagonal: Matrix,
282        /// Indicates whether the multiplexed-Rz gates take the shape of an upper triangle or a lower triangle. Default to true.
283        #[serde(default = "default_true")]
284        upper_triangle: bool,
285    },
286}
287
288fn default_true() -> bool {
289    true
290}
291
292/// Strategies for synthesising ToffoliBoxes.
293#[cfg_attr(feature = "schemars", derive(JsonSchema))]
294#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
295#[non_exhaustive]
296pub enum ToffoliBoxSynthStrat {
297    /// Use multiplexors to perform parallel swaps on hypercubes.
298    Matching,
299    /// Use CnX gates to perform transpositions.
300    Cycle,
301}
302
303/// Strategies for synthesising PauliBoxes.
304#[cfg_attr(feature = "schemars", derive(JsonSchema))]
305#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Default)]
306#[non_exhaustive]
307pub enum PauliSynthStrat {
308    /// Synthesise gadgets individually.
309    Individual,
310    /// Synthesise gadgets using an efficient pairwise strategy from Cowtan et
311    /// al <https://arxiv.org/abs/1906.01734>.
312    Pairwise,
313    /// Synthesise gadgets in commuting sets.
314    #[default]
315    Sets,
316}
317
318/// Strategies for partitioning Pauli tensors.
319#[cfg_attr(feature = "schemars", derive(JsonSchema))]
320#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Default)]
321#[non_exhaustive]
322pub enum PauliPartitionStrat {
323    /// Build sets of Pauli tensors in which each qubit has the same Pauli or
324    /// Pauli.I. Requires no additional CX gates for diagonalisation.
325    NonConflictingSets,
326    /// Build sets of mutually commuting Pauli tensors. Requires O(n^2) CX gates to diagonalise.
327    #[default]
328    CommutingSets,
329}
330
331/// Available methods to perform graph colouring.
332#[cfg_attr(feature = "schemars", derive(JsonSchema))]
333#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Default)]
334#[non_exhaustive]
335pub enum GraphColourMethod {
336    /// Does not build the graph before performing the colouring; partitions
337    /// while iterating through the Pauli tensors in the input order.
338    #[default]
339    Lazy,
340    /// Builds the graph and then greedily colours by iterating through the
341    /// vertices, with the highest degree first.
342    LargestFirst,
343    /// Builds the graph and then systematically checks all possibilities until
344    /// it finds a colouring with the minimum possible number of colours. Such
345    /// colourings need not be unique. Exponential time in the worst case, but
346    /// often runs much faster.
347    Exhaustive,
348}
349
350/// Available configurations for CXs upon decompose phase gadgets
351#[cfg_attr(feature = "schemars", derive(JsonSchema))]
352#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Default)]
353#[non_exhaustive]
354pub enum CXConfigType {
355    /// Linear nearest neighbour CX sequence. Linear depth.
356    Snake,
357    /// Every CX has same target, linear depth, good for gate cancellation.
358    Star,
359    /// Balanced tree: logarithmic depth, harder to route.
360    #[default]
361    Tree,
362    /// Support for multi-qubit architectures, decomposing to 3-qubit XXPhase3
363    /// gates instead of CXs where possible.
364    MultiQGate,
365}
366
367/// A simple struct for Pauli strings with +/- phase, used to represent Pauli
368/// strings in a stabiliser subgroup.
369#[cfg_attr(feature = "schemars", derive(JsonSchema))]
370#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash)]
371pub struct PauliStabiliser {
372    coeff: bool,
373    string: Vec<String>,
374}
375
376/// An object holding resource data for use in a [`OpType::DummyBox`].
377#[cfg_attr(feature = "schemars", derive(JsonSchema))]
378#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
379pub struct ResourceData {
380    /// Dictionary of counts of selected [`OpType`]s.
381    pub op_type_count: HashMap<OpType, ResourceBounds>,
382    /// Overall gate depth.
383    pub gate_depth: ResourceBounds,
384    /// Dictionary of depths of selected [`OpType`]s.
385    pub op_type_depth: HashMap<OpType, ResourceBounds>,
386    /// Overall two-qubit-gate depth.
387    pub two_qubit_gate_depth: ResourceBounds,
388}
389
390/// Structure holding a minimum and maximum value of some resource, where both
391/// values are unsigned integers.
392#[cfg_attr(feature = "schemars", derive(JsonSchema))]
393#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
394pub struct ResourceBounds {
395    /// Minimum value.
396    pub min: u32,
397    /// Maximum value.
398    pub max: u32,
399}
400
401/// Binary matrix form of a stabilizer tableau for unitary Clifford circuits.
402#[cfg_attr(feature = "schemars", derive(JsonSchema))]
403#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash)]
404pub struct UnitaryTableau {
405    /// A symplectic tableau.
406    pub tab: SymplecticTableau,
407    /// Ordered naming of qubits in the tableau.
408    pub qubits: Vec<Qubit>,
409}
410
411/// Binary matrix form of a collection of Pauli strings.
412#[cfg_attr(feature = "schemars", derive(JsonSchema))]
413#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash)]
414pub struct SymplecticTableau {
415    /// Number of rows in the tableau.
416    #[serde(default)]
417    pub nrows: u32,
418    /// Number of columns in the tableau.
419    #[serde(default)]
420    pub nqubits: u32,
421    /// The X matrix.
422    pub xmat: Matrix<bool>,
423    /// The Z matrix.
424    pub zmat: Matrix<bool>,
425    /// The phase matrix.
426    pub phase: Matrix<bool>,
427}