Skip to content

Commit 518614f

Browse files
add a discussion section in rotation_synthesis/README.md about digits of precision and add a parameter to control global phase of to_quirk (#1754)
- Adds a discussion section in rotation_synthesis/README.md about how to select the digits of precision - updates to_quirk and related methods to have a `allow_global_phase` parameter that affects which quirk gates are used (e.g. `T` vs `Rz(pi/4)`)
1 parent 2436c55 commit 518614f

File tree

6 files changed

+109
-28
lines changed

6 files changed

+109
-28
lines changed

qualtran/rotation_synthesis/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,15 @@ expected number of T gates is 18.982
116116
>>> 'actual diamond distance: %e'%mixed_fallback.diamond_norm_distance_to_rz(theta, config)
117117
'actual diamond distance: 5.397363e-10'
118118
```
119+
120+
## Effect of digits of precision
121+
The number of digits of precision used (i.e. rs.with_dps(digits_of_precision)) affects the result of the synthesis as follows:
122+
123+
- very low => A math error will be raised by one of the checks|
124+
- low => A valid solution may be missed, in other words you get either a solution that has more T gates or None|
125+
- just right => A correct solution that has a number of T gates on par with the current state of the art|
126+
- high => same solution as above but in more time|
127+
128+
Essentially, the code will either return a valid synthesis or None. If the code returns a result then it may be improved by increasing the number of digits of precision and if the code returns None then we need to increase the number of digits of precisions or `max_n` or both.
129+
130+
As a rule of thumb, the number of digits of precision should be close to $10\log_{10}{1/\epsilon}$ (obtained experimentally). This works for large $\epsilon$ and is an upperbound for very small $\epsilon$, for example for $\epsilon=10^{-50}$ we need 400 digits and for $\epsilon=10^{-100}$ we need 800 digits.

qualtran/rotation_synthesis/channels/channel.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -132,19 +132,28 @@ def to_cirq(self, fmt: str = "xz", qs: Optional[Sequence[cirq.Qid]] = None) -> c
132132
q = cirq.q(0)
133133
return cirq.Circuit(ctr.to_cirq(self.to_matrix(), fmt, q))
134134

135-
def to_quirk(self, fmt: str = "xz") -> str:
135+
def to_quirk(self, fmt: str = "xz", allow_global_phase: bool = True) -> str:
136136
"""Retruns a quirk link representing the channel operation.
137137
138138
Args:
139139
fmt: The gates to use (see the documentation of to_sequence).
140+
allow_global_phase: whether the result can have a global phase or not.
141+
If this is False, then each gate (except H) gets replaced by SU2 version:
142+
- S -> Rz(pi/2)
143+
- T -> Rz(pi/4)
144+
- X -> Rx(-pi)
145+
- Y -> Ry(-pi)
146+
- Z -> Rz(-pi)
147+
Then a prefix composed of SXSX that corrects the phase difference caused by H gates
148+
is added.
140149
Returns:
141150
A quirk link.
142151
Raises:
143152
ValueError: If twirl=True
144153
"""
145154
if self.twirl:
146155
raise ValueError("to_quirk is not supported when twirl=True")
147-
gates = ctr.to_quirk(self.to_matrix(), fmt)
156+
gates = ctr.to_quirk(self.to_matrix(), fmt, allow_global_phase)
148157
cols = '[' + ','.join(f'[{g}]' for g in gates) + ']'
149158
return "https://algassert.com/quirk#circuit={\"cols\":%s}" % cols
150159

@@ -277,30 +286,43 @@ def to_cirq(self, fmt: str = "xz", qs: Optional[Sequence[cirq.Qid]] = None) -> c
277286
),
278287
)
279288

280-
def to_quirk(self, fmt: str = "xz") -> str:
289+
def to_quirk(self, fmt: str = "xz", allow_global_phase: bool = True) -> str:
281290
"""Retruns a quirk link representing the channel operation.
282291
283292
Args:
284293
fmt: The gates to use (see the documentation of to_sequence).
294+
allow_global_phase: whether the result can have a global phase or not.
295+
If this is False then each gate (except H) gets replaced by SU2 version:
296+
- S -> Rz(pi/2)
297+
- T -> Rz(pi/4)
298+
- X -> Rx(-pi)
299+
- Y -> Ry(-pi)
300+
- Z -> Rz(-pi)
301+
Then a prefix composed of SXSX that corrects the phase difference caused by H gates
302+
is added.
285303
Returns:
286304
A quirk link.
287305
"""
288306
correction = self.correction
289307
if not isinstance(correction, UnitaryChannel):
290308
raise ValueError(f"to_quirk is not supported for correction of type {type(correction)}")
291-
rot = ctr.to_quirk(self.rotation.to_matrix(), fmt)
292-
cor = ctr.to_quirk(correction.to_matrix(), fmt)
309+
rot = ctr.to_quirk(self.rotation.to_matrix(), fmt, allow_global_phase)
310+
cor = ctr.to_quirk(correction.to_matrix(), fmt, allow_global_phase)
311+
if allow_global_phase:
312+
xgate = '"X"'
313+
else:
314+
xgate = '{"id":"Rzft","arg":"-pi"}'
293315
first_row = []
294316
second_row = []
295317
# CNOT
296318
first_row.append("\"\"")
297-
second_row.append("\"X\"")
319+
second_row.append(xgate)
298320
# rotation
299321
first_row.extend(rot)
300322
second_row.extend("1" for _ in rot)
301323
# CNOT
302324
first_row.append("\"\"")
303-
second_row.append("\"X\"")
325+
second_row.append(xgate)
304326
# measure
305327
first_row.append("1")
306328
second_row.append("\"Measure\"")

qualtran/rotation_synthesis/matrix/clifford_t_repr.py

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,26 +52,32 @@ def _xyz_sequence(matrix: su2_ct.SU2CliffordT) -> tuple[str, ...]:
5252
]
5353

5454

55-
def _xz_sequence(matrix: su2_ct.SU2CliffordT, use_hs: bool = True) -> Optional[tuple[str, ...]]:
55+
def _xz_sequence(
56+
matrix: su2_ct.SU2CliffordT, use_hs: bool = True, prv: str = 'dummy'
57+
) -> Optional[tuple[str, ...]]:
5658
if matrix.det() == 2:
5759
return clifford(matrix)
5860
cliffords = [su2_ct.ISqrt2]
5961
if use_hs:
6062
cliffords.append(su2_ct.HSqrt2)
6163
cliffords.append(su2_ct.HSqrt2 @ su2_ct.SSqrt2)
6264
candidates = []
65+
pref = prv.removesuffix('*')
6366
for name, t in _T_list:
67+
if name.startswith(pref):
68+
continue
6469
for c in cliffords:
6570
new = c.adjoint() @ matrix
6671
new = t.adjoint() @ new
6772
new = new.scale_down()
6873
if new is None or not new.is_valid():
6974
continue
70-
seq = _xz_sequence(new, False)
75+
seq = _xz_sequence(new, False, name)
7176
if seq is None:
7277
continue
7378
gates = cast(tuple[str, ...], c.gates)
7479
candidates.append(seq + (name,) + gates)
80+
break
7581
if not candidates:
7682
return None
7783
return min(candidates, key=len)
@@ -113,19 +119,34 @@ def to_sequence(matrix: su2_ct.SU2CliffordT, fmt: str = 'xyz') -> tuple[str, ...
113119
}
114120

115121

116-
def _to_quirk_name(name: str) -> str:
117-
if name == "I":
118-
return "1"
119-
if name in ("X", "Y", "Z", "H"):
120-
return "\"" + name + "\""
121-
if name == "S":
122-
return "\"Z^½\""
123-
if name == "S*":
124-
return "\"Z^-½\""
125-
if name.startswith("T"):
126-
if name.endswith("*"):
127-
return "\"" + name[1].upper() + "^-¼" + "\""
128-
return "\"" + name[1].upper() + "^¼" + "\""
122+
def _to_quirk_name(name: str, allow_global_phase: bool = False) -> str:
123+
if allow_global_phase:
124+
if name == "I":
125+
return "1"
126+
if name in ("X", "Y", "Z", "H"):
127+
return "\"" + name + "\""
128+
if name == "S":
129+
return "\"Z^½\""
130+
if name == "S*":
131+
return "\"Z^-½\""
132+
if name.startswith("T"):
133+
if name.endswith("*"):
134+
return "\"" + name[1].upper() + "^-¼" + "\""
135+
return "\"" + name[1].upper() + "^¼" + "\""
136+
else:
137+
if name == "I":
138+
return "1"
139+
if name == "H":
140+
return "\"H\""
141+
if name in ("X", "Y", "Z"):
142+
return '{"id":"R%sft","arg":"-pi"}' % (name.lower())
143+
if name == "S":
144+
return '{"id":"Rzft","arg":"pi/2"}'
145+
if name == "S*":
146+
return '{"id":"Rzft","arg":"-pi/2"}'
147+
if name.startswith("T"):
148+
angle = ['pi/4', '-pi/4'][name.endswith('*')]
149+
return '{"id":"R%sft","arg":"%s"}' % (name[1].lower(), angle)
129150
raise ValueError(f"{name=} is not supported")
130151

131152

@@ -145,13 +166,29 @@ def to_cirq(
145166
return tuple(_CIRQ_GATE_MAP[g](q) for g in to_sequence(matrix, fmt))
146167

147168

148-
def to_quirk(matrix: su2_ct.SU2CliffordT, fmt: str) -> tuple[str, ...]:
169+
def to_quirk(
170+
matrix: su2_ct.SU2CliffordT, fmt: str, allow_global_phase: bool = False
171+
) -> tuple[str, ...]:
149172
"""Retruns a representation of the matrix as a sequence of quirk symbols.
150173
151174
Args:
152175
matrix: The matrix to represent.
153176
fmt: The gates to use (see the documentation of to_sequence).
177+
allow_global_phase: whether the result can have a global phase or not.
178+
If this is set then each gate (except H) gets replaced by SU2 version:
179+
- S -> Rz(pi/2)
180+
- T -> Rz(pi/4)
181+
- X -> Rx(-pi)
182+
- Y -> Ry(-pi)
183+
- Z -> Rz(-pi)
184+
Then a prefix composed of SXSX that corrects the phase difference caused by H gates
185+
is added.
154186
Returns:
155187
A tuple quirk symbols.
156188
"""
157-
return tuple(_to_quirk_name(name) for name in to_sequence(matrix, fmt))
189+
sequence = to_sequence(matrix, fmt)
190+
phase_correction = tuple[str, ...]()
191+
if not allow_global_phase:
192+
phase = sum(g == 'H' for g in sequence) % 4
193+
phase_correction = ('"Z^½"', '"X"', '"Z^½"', '"X"') * phase
194+
return phase_correction + tuple(_to_quirk_name(name, allow_global_phase) for name in sequence)

qualtran/rotation_synthesis/matrix/clifford_t_repr_test.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,18 @@ def test_to_xyz_seq(g):
4343

4444

4545
@pytest.mark.parametrize("g", _make_random_su(50, 5, random_cliffords=True, seed=0))
46-
def test_to_xz_seq(g):
46+
def test_to_xz_seq(g: su2_ct.SU2CliffordT):
47+
g = g.rescale()
4748
seq = ctr.to_sequence(g, 'xz')
4849
assert not any('Ty' in g for g in seq)
50+
first_t = None
51+
for i in range(len(seq)):
52+
if seq[i].startswith('T'):
53+
first_t = i
54+
break
55+
if first_t is not None:
56+
ts = 'Tx', 'Tx*', 'Tz', 'Tz*'
57+
assert all(s in ts for s in seq[first_t:-2]), f'{seq=}'
4958
got = su2_ct.SU2CliffordT.from_sequence(seq)
5059
assert got == g
5160

qualtran/rotation_synthesis/matrix/su2_ct.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,12 @@ def rescale(self) -> 'SU2CliffordT':
221221
while u.det() > 2 * zsqrt2.LAMBDA_KLIUCHNIKOV:
222222
if not all(a.is_divisible_by(zw.LAMBDA_KLIUCHNIKOV) for a in u.matrix.flat):
223223
break
224-
u = SU2CliffordT(
224+
new_u = SU2CliffordT(
225225
[[x // zw.LAMBDA_KLIUCHNIKOV for x in row] for row in u.matrix], u.gates
226226
)
227+
if not new_u.is_valid():
228+
break
229+
u = new_u
227230
return u
228231

229232
def num_t_gates(self) -> int:

qualtran/rotation_synthesis/protocols/clifford_t_synthesis.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@ def _solve(
6262
m = n
6363
real_bound_fn = protocol.make_real_bound_fn(m, config)
6464
if not real_bound_fn(p):
65-
# if verbose:
66-
# print('skip', p)
6765
bad += new
6866
if verbose and bad % 10**5 == 0:
6967
print(f"at {n=}", "seen", bad, "out of", total, "points, ratio =", bad / total)

0 commit comments

Comments
 (0)