Skip to content

Commit 7b5c8d5

Browse files
authored
[open-systems] Phased classical simulation (#1590)
This pull request is the second in a series for supporting open system (classical data & measurement), roadmap item #445. ~~This branch is based on #1584~~ ## `Bloq.basis_state_phase` This new method can be overridden for phased-classical gates. In this PR, I show how `CZ` can support this new method. This leaves the basis states alone but applies a phase when `11` is encountered. ## `PhasedClassicalSimState(ClassicalSimState)` I refactored the classical simulator into a (mutable) class with initialization `.from_cbloq(...)`, binst-by-binst stepping `.step()`, and finalization `.finalize()`. These can be combined with `.simulate()`, which is the new backend for the familiar entry points `Bloq.call_classically(...)` and friends. Doing ordinary classical simulation will raise an error if it encounters a bloq with a non-trivial phase. The phased classical simulator will raise an error if it encounters a bloq without classical action; otherwise it will propagate phase. If a bloq does not specifically annotate a phase (but does indeed have classical action), the trivial +1 phase is assumed. ## Non-changes (yet) This PR does not directly support any "open systems" things. The ultimate goal is to support fuzz-testing measurement-based uncomputation. Since MBUC involves tracking phases, this PR is a necessary but not sufficient pre-requisite. ## Related - This PR unblocks #1527 - #1499 easier to test - https://arxiv.org/abs/2105.13410 has a ton of cool circuit constructions that can be tested with this
1 parent 0f5c35f commit 7b5c8d5

7 files changed

Lines changed: 603 additions & 140 deletions

File tree

qualtran/_infra/bloq.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
GeneralizerT,
6161
SympySymbolAllocator,
6262
)
63-
from qualtran.simulation.classical_sim import ClassicalValT
63+
from qualtran.simulation.classical_sim import ClassicalValRetT, ClassicalValT
6464

6565

6666
def _decompose_from_build_composite_bloq(bloq: 'Bloq') -> 'CompositeBloq':
@@ -184,13 +184,13 @@ def adjoint(self) -> 'Bloq':
184184

185185
def on_classical_vals(
186186
self, **vals: Union['sympy.Symbol', 'ClassicalValT']
187-
) -> Mapping[str, 'ClassicalValT']:
187+
) -> Mapping[str, 'ClassicalValRetT']:
188188
"""How this bloq operates on classical data.
189189
190190
Override this method if your bloq represents classical, reversible logic. For example:
191191
quantum circuits composed of X and C^nNOT gates are classically simulable.
192192
193-
Bloq definers should override this method. If you already have an instance of a `Bloq`,
193+
Bloq authors should override this method. If you already have an instance of a `Bloq`,
194194
consider calling `call_clasically(**vals)` which will do input validation before
195195
calling this function.
196196
@@ -215,6 +215,25 @@ def on_classical_vals(
215215
except NotImplementedError as e:
216216
raise NotImplementedError(f"{self} does not support classical simulation: {e}") from e
217217

218+
def basis_state_phase(self, **vals: 'ClassicalValT') -> Union[complex, None]:
219+
"""How this bloq phases classical basis states.
220+
221+
Override this method if your bloq represents classical logic with basis-state
222+
dependent phase factors. This corresponds to bloqs whose matrix representation
223+
(in the standard basis) is a generalized permutation matrix: a permutation matrix
224+
where each entry can be +1, -1 or any complex number with unit absolute value.
225+
Alternatively, this corresponds to bloqs composed from classical operations
226+
(X, CNOT, Toffoli, ...) and diagonal operations (T, CZ, CCZ, ...).
227+
228+
Bloq authors should override this method. If you are using an instantiated bloq object,
229+
call TODO and not this method directly.
230+
231+
If this method is implemented, `on_classical_vals` must also be implemented.
232+
If `on_classical_vals` is implemented but this method is not implemented, it is assumed
233+
that the bloq does not alter the phase.
234+
"""
235+
return None
236+
218237
def call_classically(
219238
self, **vals: Union['sympy.Symbol', 'ClassicalValT']
220239
) -> Tuple['ClassicalValT', ...]:

qualtran/_infra/controlled.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
from qualtran.cirq_interop import CirqQuregT
4848
from qualtran.drawing import WireSymbol
4949
from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator
50-
from qualtran.simulation.classical_sim import ClassicalValT
50+
from qualtran.simulation.classical_sim import ClassicalValRetT, ClassicalValT
5151

5252
ControlBit: TypeAlias = int
5353
"""A control bit, either 0 or 1."""
@@ -432,7 +432,7 @@ def signature(self) -> 'Signature':
432432
# Prepend register(s) corresponding to `ctrl_spec`.
433433
return Signature(self.ctrl_regs + tuple(self.subbloq.signature))
434434

435-
def on_classical_vals(self, **vals: 'ClassicalValT') -> Mapping[str, 'ClassicalValT']:
435+
def on_classical_vals(self, **vals: 'ClassicalValT') -> Mapping[str, 'ClassicalValRetT']:
436436
"""Classical action of controlled bloqs.
437437
438438
This involves conditionally doing the classical action of `subbloq`. All implementers
@@ -453,6 +453,18 @@ def on_classical_vals(self, **vals: 'ClassicalValT') -> Mapping[str, 'ClassicalV
453453

454454
return vals
455455

456+
def basis_state_phase(self, **vals: 'ClassicalValT') -> Union[complex, None]:
457+
"""Phasing action of controlled bloqs.
458+
459+
This involves conditionally doing the phasing action of `subbloq`. All implementers
460+
of `_ControlledBase` should provide a decomposition that satisfies this phase funciton.
461+
"""
462+
ctrl_vals = [vals[reg_name] for reg_name in self.ctrl_reg_names]
463+
other_vals = {reg.name: vals[reg.name] for reg in self.subbloq.signature}
464+
if self.ctrl_spec.is_active(*ctrl_vals):
465+
return self.subbloq.basis_state_phase(**other_vals)
466+
return None
467+
456468
def _tensor_data(self):
457469
"""Dense tensor encoding a controlled unitary.
458470

qualtran/bloqs/basic_gates/z_basis.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252

5353
from qualtran.cirq_interop import CirqQuregT
5454
from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator
55+
from qualtran.simulation.classical_sim import ClassicalValT
5556

5657
_ZERO = np.array([1, 0], dtype=np.complex128)
5758
_ONE = np.array([0, 1], dtype=np.complex128)
@@ -359,6 +360,15 @@ def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlled
359360
self, ctrl_spec, current_ctrl_bit=1, bloq_with_ctrl=self, ctrl_reg_name='q1'
360361
)
361362

363+
def on_classical_vals(self, **vals: 'ClassicalValT') -> Dict[str, 'ClassicalValT']:
364+
# Diagonal, but causes phases: see `basis_state_phase`
365+
return vals
366+
367+
def basis_state_phase(self, q1: int, q2: int) -> Optional[complex]:
368+
if q1 == 1 and q2 == 1:
369+
return -1
370+
return 1
371+
362372

363373
@bloq_example
364374
def _cz() -> CZ:

qualtran/bloqs/basic_gates/z_basis_test.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
IntState,
2525
OneEffect,
2626
OneState,
27+
XGate,
2728
ZeroEffect,
2829
ZeroState,
2930
ZGate,
@@ -245,3 +246,33 @@ def test_cz_manual():
245246

246247
assert ZGate().controlled() == CZ()
247248
assert t_complexity(cz) == TComplexity(clifford=1)
249+
250+
with pytest.raises(ValueError, match='.*phase.*'):
251+
cz.call_classically(q1=1, q2=1)
252+
253+
254+
def test_cz_phased_classical():
255+
cz = CZ()
256+
from qualtran.simulation.classical_sim import do_phased_classical_simulation
257+
258+
final_vals, phase = do_phased_classical_simulation(cz, {'q1': 0, 'q2': 1})
259+
assert final_vals['q1'] == 0
260+
assert final_vals['q2'] == 1
261+
assert phase == 1
262+
263+
final_vals, phase = do_phased_classical_simulation(cz, {'q1': 1, 'q2': 1})
264+
assert final_vals['q1'] == 1
265+
assert final_vals['q2'] == 1
266+
assert phase == -1
267+
268+
bb = BloqBuilder()
269+
q1 = bb.add(ZeroState())
270+
q2 = bb.add(ZeroState())
271+
q1 = bb.add(XGate(), q=q1)
272+
q2 = bb.add(XGate(), q=q2)
273+
q1, q2 = bb.add(CZ(), q1=q1, q2=q2)
274+
cbloq = bb.finalize(q1=q1, q2=q2)
275+
final_vals, phase = do_phased_classical_simulation(cbloq, {})
276+
assert final_vals['q1'] == 1
277+
assert final_vals['q2'] == 1
278+
assert phase == -1

0 commit comments

Comments
 (0)