quantic-rust 0.1.0__cp313-cp313-macosx_10_12_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,218 @@
1
+ """
2
+ Quantic-Rust: High-Performance Quantum Computing Library
3
+
4
+ A Rust-powered quantum circuit synthesis, optimization, and error correction library.
5
+
6
+ Algorithms are based on papers:
7
+ - "A graph-state based synthesis framework for Clifford isometries"
8
+ by T. Goubault de Brugière, S. Martiel, and C. Vuillot
9
+ - "Faster and shorter synthesis of Hamiltonian simulation circuits"
10
+ by T. Goubault de Brugère, and S. Martiel
11
+ """
12
+
13
+ from typing import List, Tuple, Union, Any
14
+
15
+ import networkx as nx
16
+
17
+ from .quantic_rust import (
18
+ Metric,
19
+ graph_state_synthesis as rust_gs,
20
+ stabilizer_state_synthesis as rust_ss,
21
+ pauli_network_synthesis as rust_pauli_network,
22
+ codiagonalization as rust_codiag,
23
+ codiagonalization_sswise as rust_sswise,
24
+ isometry_synthesis as rust_isometry,
25
+ extract_rotations as rust_extract_rotations,
26
+ zhang_rotation_optimization,
27
+ )
28
+ from .utils import convert_circuit
29
+
30
+ __version__ = "0.1.0"
31
+ __all__ = [
32
+ "Metric",
33
+ "pauli_network_synthesis",
34
+ "graph_state_synthesis",
35
+ "stabilizer_state_synthesis",
36
+ "codiagonalization",
37
+ "clifford_isometry_synthesis",
38
+ "clifford_synthesis",
39
+ "extract_rotations",
40
+ ]
41
+
42
+ _Circuit = List[Tuple[str, List[int]]]
43
+
44
+
45
+ def pauli_network_synthesis(
46
+ paulis: list[str],
47
+ metric: Metric,
48
+ preserve_order: bool = True,
49
+ nshuffles: int = 0,
50
+ skip_sort: bool = False,
51
+ fix_clifford: bool = False,
52
+ check: bool = False,
53
+ ) -> _Circuit:
54
+ """
55
+ Synthesize a circuit implementing a Pauli network for a given set of Pauli operators.
56
+
57
+ Args:
58
+ paulis (list): List of Pauli operators (as strings).
59
+ metric (:class:`.Metric`): The metric to minimize.
60
+ preserve_order (optional, bool): Whether to preserve the order of the Pauli operators.
61
+ Default is True.
62
+ nshuffles (optional, int): Number of qbit ordering shuffles to perform. Default is 0.
63
+ skip_sort (optional, bool): Whether to skip sorting Pauli operators by hamming weight.
64
+ If set to True, the algorithm might fail to converge (use with caution). Default is False.
65
+ fix_clifford (optional, bool): Whether to fix the final Clifford operator. Default is False.
66
+ check (optional, bool): Whether to check the validity of the circuit. Default is False.
67
+
68
+ Returns:
69
+ list: The synthesized circuit.
70
+ """
71
+ if paulis:
72
+ return rust_pauli_network(
73
+ paulis, metric, preserve_order, check, nshuffles, skip_sort, fix_clifford
74
+ )
75
+ return []
76
+
77
+
78
+ def graph_state_synthesis(
79
+ graph: nx.Graph, metric: Metric, syndrome_iter: int = 1
80
+ ) -> _Circuit:
81
+ """
82
+ Synthesize a circuit preparing a given graph state specified by a networkx graph.
83
+
84
+ Args:
85
+ graph (:class:`network.Graph`): A graph (networkx object).
86
+ metric (:class:`.Metric`): The metric to minimize.
87
+ syndrome_iter (optional, int): The number of syndrome decoding iteration used
88
+ in the ISD solver (for `Metric.COUNT` only). Default is 1.
89
+ Returns:
90
+ list: The synthesized circuit.
91
+ """
92
+ syndrome_iter = syndrome_iter or 1
93
+ adj = nx.to_numpy_array(graph) > 0.5
94
+ return rust_gs(adj.tolist(), metric, syndrome_iter)
95
+
96
+
97
+ def stabilizer_state_synthesis(
98
+ stabilizers: list[str], metric: Metric, syndrome_iter: int = 1
99
+ ) -> _Circuit:
100
+ """
101
+ Synthesize a circuit preparing a given graph state specified by a networkx graph.
102
+
103
+ Args:
104
+ stabilizers (list): A list of strings representing the stabilizers.
105
+ metric (:class:`.Metric`): The metric to minimize.
106
+ syndrome_iter (optional, int): The number of syndrome decoding iteration used
107
+ in the ISD solver (for `Metric.COUNT` only). Default is 1.
108
+ Returns:
109
+ list: The synthesized circuit.
110
+ """
111
+ return rust_ss(stabilizers, metric, syndrome_iter)
112
+
113
+
114
+ def codiagonalization(
115
+ paulis: list[str], metric: Metric, syndrome_iter: int = 1
116
+ ) -> _Circuit:
117
+ """
118
+ Synthesize a circuit preparing a codiagonalization Clifford circuit for a given set of
119
+ pairwise commuting Pauli operators.
120
+
121
+ Args:
122
+ paulis (list): List of the Pauli operators to co-diagonalize (as strings).
123
+ metric (:class:`.Metric`): The metric to minimize.
124
+ syndrome_iter (optional, int): The number of syndrome decoding iteration used
125
+ in the ISD solver (for `Metric.COUNT` only). Default is 1.
126
+
127
+ Returns:
128
+ list: The synthesized circuit.
129
+ """
130
+ syndrome_iter = syndrome_iter or 1
131
+ return rust_codiag(paulis, metric, syndrome_iter)
132
+
133
+
134
+ def codiagonalization_sswise(paulis: list[str]) -> _Circuit:
135
+ """
136
+ Synthesize a circuit preparing a codiagonalization Clifford circuit for a given set of
137
+ pairwise commuting Pauli operators.
138
+
139
+ This is the heuristic of the paper "A Generic Compilation Strategy for the Unitary
140
+ Coupled Cluster Ansatz".
141
+ It is here for comparison purposes with the `codiagonalization` method .
142
+
143
+ Args:
144
+ paulis (list): List of the Pauli operators to co-diagonalize (as strings).
145
+
146
+ Returns:
147
+ list: The synthesized circuit.
148
+ """
149
+ return rust_sswise(paulis, 2)
150
+
151
+
152
+ def clifford_isometry_synthesis(
153
+ logicals: list[str], stabilisers: list[str], metric: Metric, syndrome_iter: int = 1
154
+ ) -> _Circuit:
155
+ """
156
+ Synthesize a Clifford circuit implementing a target Clifford isometry specified by
157
+ its logical operators and its stabilizers.
158
+ The tableau is specified by a list of operators (as strings). The first (resp. second) half
159
+ of the list corresponding to the images of `Zi` (resp. `Xi`) operators.
160
+
161
+ Args:
162
+ logicals (list): The logical operators.
163
+ stabilisers (list): The stabilizers.
164
+ metric (:class:`.Metric`): The metric to minimize.
165
+ syndrome_iter (optional, int): The number of syndrome decoding iteration used in the
166
+ ISD solver in the metric=`Metric.COUNT` case only. Default is 1.
167
+
168
+ Returns:
169
+ list: The synthesized circuit.
170
+ """
171
+ syndrome_iter = syndrome_iter or 1
172
+ return rust_isometry(logicals, stabilisers, metric, syndrome_iter)
173
+
174
+
175
+ def clifford_synthesis(
176
+ logicals: list[str], metric: Metric, syndrome_iter: int = 1
177
+ ) -> _Circuit:
178
+ """
179
+ Synthesize a Clifford circuit implementing a target Clifford operator specified by its
180
+ tableau.
181
+ The tableau is specified by a list of operators (as strings). The first (resp. second) half
182
+ of the list corresponding to the images of `Zi` (resp. `Xi`) operators.
183
+
184
+ Args:
185
+ logicals (list): The tableau.
186
+ metric (:class:`.Metric`): The metric to minimize.
187
+ syndrome_iter (optional, int): The number of syndrome decoding iteration used in
188
+ the ISD solver in the metric=`Metric.COUNT` case only. Default is 1.
189
+
190
+ Returns:
191
+ list: The synthesized circuit.
192
+ """
193
+ syndrome_iter = syndrome_iter or 1
194
+ return rust_isometry(logicals, [], metric, syndrome_iter)
195
+
196
+
197
+ def extract_rotations(
198
+ circuit: List[
199
+ Union[Tuple[str, List[int]], Tuple[str, List[int], Any]]
200
+ ],
201
+ optimize: bool = True,
202
+ ):
203
+ """
204
+ Converts a circuit into a sequence of Pauli rotations and a final Clifford operator.
205
+ The circuit is specified as a sequence of tuples:
206
+ - ("CX", [q0, q1]) for a CNOT gate
207
+ - ("H", [q0]) for a Hadamard gate
208
+ - ("S", [q0]) for a S gate
209
+ - ("RZ", [q0], angle) for a RZ gate with angle in radian
210
+
211
+ This gate set is exactly universal for QC.
212
+ """
213
+ circuit_no_angles, angles = convert_circuit(circuit)
214
+ nqubits = max(max(g[1]) for g in circuit) + 1
215
+ rotations, final_clifford_tableau = rust_extract_rotations(
216
+ circuit_no_angles, [str(a) for a in angles], nqubits, optimize
217
+ )
218
+ return rotations, final_clifford_tableau
quantic_rust/utils.py ADDED
@@ -0,0 +1,72 @@
1
+ """
2
+ Utility functions for quantic_rust's circuits
3
+ """
4
+
5
+ from typing import List, Tuple
6
+
7
+ import numpy as np
8
+
9
+ _Circuit = List[Tuple[str, List[int]]]
10
+
11
+
12
+ def entangling_depth(circ: _Circuit) -> int:
13
+ """
14
+ Computes the entangling depth of a circuit.
15
+ Only checks the number of qubits of the gate to decide if it is entangling or not.
16
+
17
+ Arguments:
18
+ circ: A circuit.
19
+
20
+ Returns:
21
+ int: the entangling depth
22
+ """
23
+ depths = {}
24
+ for _, qbits in circ:
25
+ if len(qbits) > 1:
26
+ gate_depth = max(depths.get(qbits[0], 0), depths.get(qbits[1], 0)) + 1
27
+ for qbit in qbits:
28
+ depths[qbit] = gate_depth
29
+ return max(depths.values()) if depths else 0
30
+
31
+
32
+ def entangling_count(circ: _Circuit) -> int:
33
+ """
34
+ Count the number of entangling gates in a circuit.
35
+ Only checks the number of qubits of the gate to decide if it is entangling or not.
36
+
37
+ Arguments:
38
+ circ: A circuit.
39
+
40
+ Returns:
41
+ int: the entangling count
42
+ """
43
+ return sum(len(qbits) == 2 for _, qbits in circ)
44
+
45
+
46
+ def convert_circuit(circuit: list) -> (list, list):
47
+ """
48
+ Converts a circuit into a CNOT + H + S + RZ(theta) circuit with theta non Clifford (i.e. != kpi/2)
49
+ """
50
+ new_circuit = []
51
+ angles = []
52
+ for gate in circuit:
53
+ if gate[0] in ["CX", "CZ", "H", "S", "Sd", "X", "Z", "SqrtX", "SqrtXd"]:
54
+ new_circuit.append(gate)
55
+ continue
56
+ assert gate[0] == "RZ"
57
+ if isinstance(gate[2], str):
58
+ new_circuit.append(gate[:2])
59
+ angles.append(gate[2])
60
+ continue
61
+ if np.isclose(0.0, gate[2] % (np.pi / 2)) or np.isclose(
62
+ np.pi / 2, gate[2] % (np.pi / 2)
63
+ ):
64
+ mult, offset = np.divmod(gate[2], np.pi / 2)
65
+ if np.isclose(offset, np.pi / 2):
66
+ mult += 1
67
+ for _ in mult:
68
+ new_circuit.append(("S", gate[1]))
69
+ continue
70
+ new_circuit.append(gate[:2])
71
+ angles.append(gate[2])
72
+ return new_circuit, angles