Skip to content

Commit 8512deb

Browse files
Add Rotation Synthesis subpackage (#1749)
The Rotation-Synthesis subpackage implements the state of the art rotation synthesis protocols for compiling $SU(2)$ unitaries into clifford+T gates. The current PR has the code for synthesis Z-rotations and I will send another PR for arbitrary axis rotations. Please refer to `qualtran/rotation_synthesis/README.md` for usage examples.
1 parent 3f5f954 commit 8512deb

41 files changed

Lines changed: 5215 additions & 1 deletion

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

dev_tools/conf/mypy.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ follow_imports = silent
2121
ignore_missing_imports = true
2222

2323
# Non-Google
24-
[mypy-sympy.*,matplotlib.*,proto.*,pandas.*,scipy.*,freezegun.*,mpl_toolkits.*,networkx.*,ply.*,astroid.*,pytest.*,_pytest.*,pylint.*,setuptools.*,qiskit.*,quimb.*,pylatex.*,filelock.*,sortedcontainers.*,tqdm.*,plotly.*,dash.*,tensorflow_docs.*,fxpmath.*,ipywidgets.*,cachetools.*,pydot.*,nbformat.*,nbconvert.*,openfermion.*,pennylane.*]
24+
[mypy-sympy.*,matplotlib.*,proto.*,pandas.*,scipy.*,freezegun.*,mpl_toolkits.*,networkx.*,ply.*,astroid.*,pytest.*,_pytest.*,pylint.*,setuptools.*,qiskit.*,quimb.*,pylatex.*,filelock.*,sortedcontainers.*,tqdm.*,plotly.*,dash.*,tensorflow_docs.*,fxpmath.*,ipywidgets.*,cachetools.*,pydot.*,nbformat.*,nbconvert.*,openfermion.*,pennylane.*,mpmath.*]
2525
follow_imports = silent
2626
ignore_missing_imports = true
2727

dev_tools/requirements/deps/runtime.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,7 @@ protobuf
4646
# typing
4747
typing_extensions>=4.10.0
4848

49+
# rotation_synthesis
50+
mpmath
51+
4952
# Note: use `pipreqs` to generate a list of dependencies based on the imports in our files.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Rotation Synthesis
2+
3+
Rotation-Synthesis implements the state of the art rotation synthesis protocols for compiling $SU(2)$ unitaries into clifford+T gates.
4+
5+
6+
## Minimal Examples
7+
8+
### Common Setup
9+
The examples assume the following lines have been executed.
10+
11+
```py3
12+
>>> import mpmath
13+
>>> from qualtran.rotation_synthesis import math_config as mc
14+
>>> from qualtran.rotation_synthesis.protocols import clifford_t_synthesis as cts
15+
>>> # 300 digits of precision is enough for eps as low as 10^-30.
16+
>>> # for big enough eps (>= 10^-10) prefer dps ~100 to reduce the runtime
17+
>>> # A high dps increases the runtime but a low dps risks missing solutions.
18+
>>> config = mc.with_dps(300)
19+
>>> theta = 0.1 # theta = 0.1 rad
20+
>>> eps = mpmath.mpf(1e-8) # epsilon = 1e-8
21+
```
22+
23+
### Diagonal Protocol
24+
25+
```py3
26+
>>> diagonal = cts.diagonal_unitary_approx(theta=theta, eps=eps, max_n=400, config=config)
27+
>>> diagonal
28+
Unitary(p=ZW(coords=(1509861905169903736208, 1174754259083368969221, 151491500481353346337, -960512924518388809952)), q=ZW(coords=(-4263827284335, -6564144819191, -5019275344346, -534182446068)), n=80, twirl=False)
29+
30+
>>> 'number of T gates: %d'%diagonal.n # Number of T gates used
31+
'number of T gates: 80'
32+
33+
>>> 'actual diamond distance: %e'%diagonal.diamond_norm_distance_to_rz(theta, config)
34+
'actual diamond distance: 8.854162e-09'
35+
36+
>>> diagonal.to_matrix() # Print matrix form
37+
SU2CliffordT(matrix=array([[ZW(coords=(1509861905169903736208, 1174754259083368969221, 151491500481353346337, -960512924518388809952)),
38+
ZW(coords=(4263827284335, -534182446068, -5019275344346, -6564144819191))],
39+
[ZW(coords=(-4263827284335, -6564144819191, -5019275344346, -534182446068)),
40+
ZW(coords=(1509861905169903736208, 960512924518388809952, -151491500481353346337, -1174754259083368969221))]],
41+
dtype=object), gates=())
42+
43+
>>> diagonal.to_matrix().to_sequence() # Print gate names
44+
('S', 'H', 'Tz', 'Tx', 'Ty', 'Tz', 'Tx', 'Tz', 'Tx', 'Tz', 'Ty', 'Tz', 'Ty', 'Tx', 'Tz', 'Tx', 'Ty', 'Tx', 'Ty', 'Tx', 'Tz', 'Ty', 'Tz', 'Ty', 'Tz', 'Ty', 'Tz', 'Ty', 'Tx', 'Tz', 'Ty', 'Tz', 'Ty', 'Tz', 'Tx', 'Tz', 'Ty', 'Tz', 'Ty', 'Tx', 'Ty', 'Tz', 'Tx', 'Tz', 'Ty', 'Tx', 'Ty', 'Tx', 'Tz', 'Tx', 'Ty', 'Tz', 'Ty', 'Tx', 'Ty', 'Tx', 'Ty', 'Tz', 'Tx', 'Tz', 'Ty', 'Tz', 'Ty', 'Tz', 'Tx', 'Ty', 'Tz', 'Tx', 'Tz', 'Ty', 'Tx', 'Ty', 'Tz', 'Tx', 'Ty', 'Tx', 'Ty', 'Tx', 'Ty', 'Tz', 'Ty', 'Tz')
45+
```
46+
47+
48+
### Fallback (a.k.a Repeat-Until-Success (RUS)) Protocol
49+
50+
```py3
51+
>>> fallback = cts.fallback_protocol(theta, eps, success_probability=0.99, max_n=200, config=config)
52+
>>> fallback
53+
ProjectiveChannel(rotation=Unitary(p=ZW(coords=(239242444, 186143567, 24004313, -152196342)), q=ZW(coords=(11711997, 17295059, 12746910, 731794)), n=32, twirl=False), correction=Unitary(p=ZW(coords=(113393374677276065, 8759190556798315, -101006008596441546, -151603257795059376)), q=ZW(coords=(-13979001969, 2234592382, 17139192822, 22003886555)), n=65, twirl=False))
54+
55+
>>> print('expected number of T gates is %.3f divided into\n%d T gates used in the projective step and\n%d T gates used in the correction step which gets executed with probability %.6f'%(fallback.expected_num_ts(config), fallback.rotation.n, fallback.correction.n, 1 - fallback.success_probability(config)))
56+
expected number of T gates is 32.335 divided into
57+
32 T gates used in the projective step and
58+
65 T gates used in the correction step which gets executed with probability 0.005156
59+
60+
>>> 'actual diamond distance: %e'%fallback.diamond_norm_distance_to_rz(theta, config)
61+
'actual diamond distance: 8.358389e-09'
62+
```
63+
64+
65+
### Mixed Diagonal Protocol
66+
67+
```py3
68+
>>> mixed_diagonal = cts.mixed_diagonal_protocol(theta, eps, max_n=300, config=config)
69+
>>> mixed_diagonal
70+
ProbabilisticChannel(c1=Unitary(p=ZW(coords=(111279824866, 86577736003, 11159583589, -70795701541)), q=ZW(coords=(2905774, -827421, -4075924, -4936806)), n=42, twirl=True), c2=Unitary(p=ZW(coords=(32592863748, 25359568315, 3270981699, -20733701634)), q=ZW(coords=(-261947, -1668702, -2097954, -1298253)), n=40, twirl=True), probability=mpf('0.322751359448635267114326370859256829689458745026081702686971426030895682599474514910816660886778364762073662905955339813205441513151138367919667045726178096784779764992341606213189667302147688085007915854895131323239141860542004025041462985374870176351764143883625569021092174011359447964257700140487645'))
71+
72+
>>> print('expected number of T gates is %.3f '%(mixed_diagonal.expected_num_ts(config)))
73+
expected number of T gates is 40.646
74+
75+
>>> 'actual diamond distance: %e'%mixed_diagonal.diamond_norm_distance_to_rz(theta, config)
76+
'actual diamond distance: 9.336685e-09'
77+
```
78+
79+
80+
### Mixed Fallback Protocol
81+
82+
83+
```py3
84+
>>> mixed_fallback = cts.mixed_fallback_protocol(theta, eps, success_probability=0.99, max_n=300, config=config)
85+
>>> mixed_fallback
86+
ProbabilisticChannel(c1=ProjectiveChannel(rotation=Unitary(p=ZW(coords=(81907, 63728, 8218, -52106)), q=ZW(coords=(-1154, 985, 2547, 2617)), n=19, twirl=False), correction=ProbabilisticChannel(c1=Unitary(p=ZW(coords=(-263391098, -737624638, -779767669, -365133375)), q=ZW(coords=(-33461, 136222, 226108, 183543)), n=34, twirl=True), c2=Unitary(p=ZW(coords=(-77065381, -216007734, -228415686, -107020827)), q=ZW(coords=(-69300, -100383, -72663, -2378)), n=32, twirl=True), probability=mpf('0.045325686665484547940062502757622971144169613023747430900082177527088724087189898382813946924494697326621166855704833076057576200695347588135413318392991377437019817846667142336189311546588693427566709307270541970137781795585088761789630584519324449933375058208979540812676838676732065743027397323424595'))), c2=ProjectiveChannel(rotation=Unitary(p=ZW(coords=(23978, 18657, 2407, -15253)), q=ZW(coords=(-956, -1084, -577, 268)), n=17, twirl=False), correction=ProbabilisticChannel(c1=Unitary(p=ZW(coords=(-1373879826, -510348019, 652138736, 1432611464)), q=ZW(coords=(-189284, 278185, 582697, 545873)), n=35, twirl=True), c2=Unitary(p=ZW(coords=(-402460569, -149610333, 190879607, 419554862)), q=ZW(coords=(-69300, -31083, 25342, 66922)), n=33, twirl=True), probability=mpf('0.525040802635724724816097410427482676809933478764171151369355514657076375140052487037506999832799609679457396659076441254494844846449599140703045579066152308112059014940560931326583883941250186082725677082913867579961221230890451563733787938393583123811396292716244823945079739397111073899481930671270029'))), probability=mpf('0.972077582319464271880109129184631244429176680508313282994368258672700642062726782223015417086951750128434612291521852730028085519115453255242801194500490513953599832009476998145136699780645059022125864590137472901641917927185402638252104301881958927225784570356720997394377273869421589887668348541958305'))
87+
88+
>>> print('expected number of T gates is %.3f '%(mixed_fallback.expected_num_ts(config)))
89+
expected number of T gates is 18.982
90+
91+
>>> 'actual diamond distance: %e'%mixed_fallback.diamond_norm_distance_to_rz(theta, config)
92+
'actual diamond distance: 5.397363e-10'
93+
```
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from qualtran.rotation_synthesis.math_config import NumpyConfig, with_dps
16+
from qualtran.rotation_synthesis.protocols.clifford_t_synthesis import (
17+
diagonal_unitary_approx,
18+
fallback_protocol,
19+
mixed_diagonal_protocol,
20+
mixed_fallback_protocol,
21+
)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from typing import Union
16+
17+
import mpmath
18+
import numpy as np
19+
from typing_extensions import TypeIs
20+
21+
# mypy has a bug where it doesn't understand numbers.* https://github.com/python/mypy/issues/3186
22+
# So we define our own types
23+
Real = Union[float, np.floating, mpmath.ctx_mp_python.mpf]
24+
Integral = Union[int, np.integer]
25+
Complex = Union[complex, np.complexfloating, mpmath.ctx_mp_python.mpc]
26+
27+
28+
def is_int(x) -> TypeIs[Integral]:
29+
return isinstance(x, (int, np.integer))
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from qualtran.rotation_synthesis.channels.channel import (
16+
Channel,
17+
ProbabilisticChannel,
18+
ProjectiveChannel,
19+
UnitaryChannel,
20+
)

0 commit comments

Comments
 (0)