Skip to content

Commit 72cef5d

Browse files
authored
Move LUP decomposition based circuit construction logic from GF2MulK to SynthesizeLRCircuit bloq (#1726)
The decomposition in `GF2MulK` works for any LR circuit and so it's better to have it in the `SynthesizeLRCircuit` bloq. Both `GF2MulK` and `GF2Square` will now have an explicit decomposition.
1 parent 998d29f commit 72cef5d

4 files changed

Lines changed: 61 additions & 41 deletions

File tree

qualtran/bloqs/gf_arithmetic/gf2_multiplication.py

Lines changed: 54 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,47 @@ def __attrs_post_init__(self):
7171

7272
@cached_property
7373
def signature(self) -> 'Signature':
74-
n, _ = self.matrix.shape
75-
return Signature([Register('q', QBit(), shape=(n,))])
74+
return Signature([Register('q', QBit(), shape=(self.n,))])
75+
76+
@cached_property
77+
def n(self) -> SymbolicInt:
78+
return self.matrix.shape[0]
79+
80+
@cached_property
81+
def lup(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
82+
"""Returns the LUP decomposition of the matrix representing the operation.
83+
84+
If m_x is irreducible, then the operation y := (y*f_x)%m_x can be represented
85+
by a full rank matrix that can be decomposed into PLU where L and U are lower
86+
and upper traingular matricies and P is a permutation matrix.
87+
"""
88+
assert isinstance(self.matrix, np.ndarray)
89+
P, L, U = GF(2)(self.matrix).plu_decompose()
90+
return np.asarray(L, dtype=int), np.asarray(U, dtype=int), np.asarray(P, dtype=int)
91+
92+
def build_composite_bloq(self, bb: 'BloqBuilder', *, q: 'SoquetT') -> Dict[str, 'SoquetT']:
93+
assert isinstance(q, np.ndarray) # make mypy happy
94+
L, U, P = self.lup
95+
if is_symbolic(self.n):
96+
raise DecomposeTypeError(f"Symbolic decomposition isn't supported for {self}")
97+
98+
for i in range(self.n):
99+
for j in range(i + 1, self.n):
100+
if U[i, j]:
101+
q[j], q[i] = bb.add(CNOT(), ctrl=q[j], target=q[i])
102+
103+
for i in reversed(range(self.n)):
104+
for j in reversed(range(i)):
105+
if L[i, j]:
106+
q[j], q[i] = bb.add(CNOT(), ctrl=q[j], target=q[i])
107+
108+
column = [*range(self.n)]
109+
for i in range(self.n):
110+
for j in range(i + 1, self.n):
111+
if P[i, column[j]]:
112+
q[i], q[j] = q[j], q[i]
113+
column[i], column[j] = column[j], column[i]
114+
return {'q': q}
76115

77116
def on_classical_vals(self, *, q: 'ClassicalValT') -> Dict[str, 'ClassicalValT']:
78117
if is_symbolic(self.matrix):
@@ -91,7 +130,14 @@ def build_call_graph(
91130
self, ssa: 'SympySymbolAllocator'
92131
) -> Union['BloqCountDictT', Set['BloqCountT']]:
93132
n = self.matrix.shape[0]
94-
return {CNOT(): ceil(n**2 / log2(n))}
133+
if is_symbolic(n):
134+
return {CNOT(): ceil(n**2)}
135+
L, U, _ = self.lup
136+
# The number of cnots is the number of non zero off-diagnoal entries in L and U.
137+
cnots = np.sum(L) + np.sum(U) - 2 * self.n
138+
if cnots:
139+
return {CNOT(): cnots}
140+
return {}
95141

96142
def adjoint(self) -> 'SynthesizeLRCircuit':
97143
return attrs.evolve(self, is_adjoint=not self.is_adjoint)
@@ -402,21 +448,15 @@ def from_polynomials(
402448
return GF2MulK(dtype=qgf, const=sum(2 ** int(i) for i in f_x.nonzero_degrees))
403449

404450
@cached_property
405-
def lup(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
406-
"""Returns the LUP decomposition of the matrix representing the operation.
407-
408-
If m_x is irreducible, then the operation y := (y*f_x)%m_x can be represented
409-
by a full rank matrix that can be decomposed into PLU where L and U are lower
410-
and upper traingular matricies and P is a permutation matrix.
411-
"""
451+
def reduction_matrix_q(self) -> np.ndarray:
452+
"""Returns the matrix representing the operation."""
412453
n = int(self.n)
413454
matrix = np.zeros((n, n), dtype=int)
414455
for i in range(n):
415456
p = self._const * self.galois_field(2**i)
416457
for j, v in enumerate(reversed(self.qgf.to_bits(p))):
417458
matrix[j, i] = v
418-
P, L, U = GF(2)(matrix).plu_decompose()
419-
return np.asarray(L, dtype=int), np.asarray(U, dtype=int), np.asarray(P, dtype=int)
459+
return matrix
420460

421461
@cached_property
422462
def signature(self) -> 'Signature':
@@ -430,41 +470,17 @@ def on_classical_vals(self, g) -> Dict[str, 'ClassicalValT']:
430470
return {'g': g * self._const}
431471

432472
def build_composite_bloq(self, bb: 'BloqBuilder', g: 'Soquet') -> Dict[str, 'SoquetT']:
433-
L, U, P = self.lup
434-
if is_symbolic(self.n):
435-
raise DecomposeTypeError(f"Symbolic decomposition isn't supported for {self}")
436-
437473
g_arr = bb.split(g)
438474
g_arr = g_arr[::-1]
439-
for i in range(self.n):
440-
for j in range(i + 1, self.n):
441-
if U[i, j]:
442-
g_arr[j], g_arr[i] = bb.add(CNOT(), ctrl=g_arr[j], target=g_arr[i])
443-
444-
for i in reversed(range(self.n)):
445-
for j in reversed(range(i)):
446-
if L[i, j]:
447-
g_arr[j], g_arr[i] = bb.add(CNOT(), ctrl=g_arr[j], target=g_arr[i])
448-
449-
column = [*range(self.n)]
450-
for i in range(self.n):
451-
for j in range(i + 1, self.n):
452-
if P[i, column[j]]:
453-
g_arr[i], g_arr[j] = g_arr[j], g_arr[i]
454-
column[i], column[j] = column[j], column[i]
475+
g_arr = bb.add(SynthesizeLRCircuit(self.reduction_matrix_q), q=g_arr)
455476
g_arr = g_arr[::-1]
456477
g = bb.join(g_arr, dtype=self.qgf)
457478
return {'g': g}
458479

459480
def build_call_graph(
460481
self, ssa: 'SympySymbolAllocator'
461482
) -> Union['BloqCountDictT', Set['BloqCountT']]:
462-
L, U, _ = self.lup
463-
# The number of cnots is the number of non zero off-diagnoal entries in L and U.
464-
cnots = np.sum(L) + np.sum(U) - 2 * self.n
465-
if cnots:
466-
return {CNOT(): cnots}
467-
return {}
483+
return {SynthesizeLRCircuit(self.reduction_matrix_q): 1}
468484

469485

470486
@bloq_example

qualtran/bloqs/gf_arithmetic/gf2_multiplication_test.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,12 @@ def test_gf2_multiplication_symbolic(bloq_autotester):
5252
def test_synthesize_lr_circuit(m: int):
5353
matrix = GF2MulMBUC(m).reduction_matrix_q
5454
bloq = SynthesizeLRCircuit(matrix)
55+
cbloq = bloq.decompose_bloq()
5556
bloq_adj = bloq.adjoint()
5657
QGFM, GFM = QGF(2, m), GF(2**m)
5758
for i in GFM.elements:
5859
bloq_out = bloq.call_classically(q=np.array(QGFM.to_bits(i)))[0]
60+
assert np.all(bloq_out == cbloq.call_classically(q=np.array(QGFM.to_bits(i)))[0])
5961
bloq_adj_out = bloq_adj.call_classically(q=bloq_out)[0]
6062
assert isinstance(bloq_adj_out, np.ndarray)
6163
assert i == QGFM.from_bits([*bloq_adj_out])
@@ -66,10 +68,12 @@ def test_synthesize_lr_circuit(m: int):
6668
def test_synthesize_lr_circuit_slow(m):
6769
matrix = GF2MulMBUC(m).reduction_matrix_q
6870
bloq = SynthesizeLRCircuit(matrix)
71+
cbloq = bloq.decompose_bloq()
6972
bloq_adj = bloq.adjoint()
7073
QGFM, GFM = QGF(2, m), GF(2**m)
7174
for i in GFM.elements:
7275
bloq_out = bloq.call_classically(q=np.array(QGFM.to_bits(i)))[0]
76+
assert np.all(bloq_out == cbloq.call_classically(q=np.array(QGFM.to_bits(i)))[0])
7377
bloq_adj_out = bloq_adj.call_classically(q=bloq_out)[0]
7478
assert isinstance(bloq_adj_out, np.ndarray)
7579
assert i == QGFM.from_bits([*bloq_adj_out])

qualtran/bloqs/gf_arithmetic/gf2_square.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def squaring_matrix(self) -> np.ndarray:
6969
r"""$m \times m$ matrix that maps the input $x^{i}$ to $x^{2 * i} % P(x)$"""
7070
m = int(self.bitsize)
7171
f = self.qgf.gf_type.irreducible_poly
72-
M = np.zeros((m, m))
72+
M = np.zeros((m, m), dtype=int)
7373
alpha = [0] * m
7474
for i in range(m):
7575
# x ** (2 * i) % f

qualtran/bloqs/gf_arithmetic/gf2_square_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from qualtran.bloqs.gf_arithmetic.gf2_square import _gf2_square_symbolic, _gf16_square, GF2Square
2020
from qualtran.resource_counting import get_cost_value, QECGatesCost
21-
from qualtran.symbolics import ceil, log2
21+
from qualtran.symbolics import ceil
2222
from qualtran.testing import assert_consistent_classical_action
2323

2424

@@ -41,7 +41,7 @@ def test_gf2_square_resource():
4141
bloq = _gf2_square_symbolic.make()
4242
m = bloq.bitsize
4343
assert get_cost_value(bloq, QECGatesCost()).total_t_count() == 0
44-
expected_expr = ceil(m**2 / log2(m))
44+
expected_expr = ceil(m**2)
4545
assert isinstance(expected_expr, sympy.Expr)
4646
assert sympy.simplify(get_cost_value(bloq, QECGatesCost()).clifford - expected_expr) == 0
4747

0 commit comments

Comments
 (0)