From 516547e6e36a052d1fbb800e5d1dc1d035683eee Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Mon, 19 Aug 2024 14:53:34 -0700 Subject: [PATCH 1/4] Restrict controlled from non-thru registers --- qualtran/Controlled.ipynb | 8 +++ qualtran/_infra/controlled.py | 5 ++ qualtran/_infra/controlled_test.py | 56 --------------------- qualtran/_infra/gate_with_registers_test.py | 2 +- 4 files changed, 14 insertions(+), 57 deletions(-) diff --git a/qualtran/Controlled.ipynb b/qualtran/Controlled.ipynb index a538f9e6e..9a495093d 100644 --- a/qualtran/Controlled.ipynb +++ b/qualtran/Controlled.ipynb @@ -241,6 +241,14 @@ "ccx3 = OnEach(n=3, gate=x).controlled(CtrlSpec(cvs=(1,1)))\n", "show_bloq(ccx3.decompose_bloq(), type='musical_score')" ] + }, + { + "cell_type": "markdown", + "id": "24aa3d20-7803-4480-b717-5e3a2d5e6ba3", + "metadata": {}, + "source": [ + "Only bloqs with all-THRU registers can be controlled. Otherwise, it's not clear what the equivalent \"identity\" operation is." + ] } ], "metadata": { diff --git a/qualtran/_infra/controlled.py b/qualtran/_infra/controlled.py index 042b22440..7a0d414fc 100644 --- a/qualtran/_infra/controlled.py +++ b/qualtran/_infra/controlled.py @@ -312,6 +312,11 @@ class Controlled(GateWithRegisters): subbloq: 'Bloq' ctrl_spec: 'CtrlSpec' + def __attrs_post_init__(self): + for reg in self.subbloq.signature: + if reg.side != Side.THRU: + raise ValueError(f"Cannot control non-thru bloqs. Found {reg} in {self.subbloq}") + @classmethod def make_ctrl_system(cls, bloq: 'Bloq', ctrl_spec: 'CtrlSpec') -> Tuple[Bloq, AddControlledT]: """A factory method for creating both the Controlled and the adder function. diff --git a/qualtran/_infra/controlled_test.py b/qualtran/_infra/controlled_test.py index 9c3f50532..20b898f27 100644 --- a/qualtran/_infra/controlled_test.py +++ b/qualtran/_infra/controlled_test.py @@ -385,62 +385,6 @@ def test_controlled_global_phase_tensor(): np.testing.assert_allclose(bloq.tensor_contract(), should_be) -@attrs.frozen -class TestCtrlStatePrepAnd(Bloq): - """Decomposes into a Controlled-AND gate + int effects & targets where ctrl is active. - - Tensor contraction should give the output state vector corresponding to applying an - `And(and_ctrl)`; assuming all the control bits are active. - """ - - ctrl_spec: CtrlSpec - and_ctrl: Tuple[int, int] - - @property - def signature(self) -> 'Signature': - return Signature([Register('x', QBit(), shape=(3,), side=Side.RIGHT)]) - - def build_composite_bloq(self, bb: 'BloqBuilder') -> Dict[str, 'SoquetT']: - one_or_zero = [ZeroState(), OneState()] - cbloq = Controlled(And(*self.and_ctrl), ctrl_spec=self.ctrl_spec) - - ctrl_soqs = {} - for reg, cvs in zip(cbloq.ctrl_regs, self.ctrl_spec.cvs): - soqs = np.empty(shape=reg.shape, dtype=object) - for idx in reg.all_idxs(): - soqs[idx] = bb.add(IntState(val=cvs[idx], bitsize=reg.dtype.num_qubits)) - ctrl_soqs[reg.name] = soqs - - and_ctrl = [bb.add(one_or_zero[cv]) for cv in self.and_ctrl] - - ctrl_soqs = bb.add_d(cbloq, **ctrl_soqs, ctrl=and_ctrl) - out_soqs = np.asarray([*ctrl_soqs.pop('ctrl'), ctrl_soqs.pop('target')]) # type: ignore[misc] - - for reg, cvs in zip(cbloq.ctrl_regs, self.ctrl_spec.cvs): - for idx in reg.all_idxs(): - ctrl_soq = np.asarray(ctrl_soqs[reg.name])[idx] - bb.add(IntEffect(val=cvs[idx], bitsize=reg.dtype.num_qubits), val=ctrl_soq) - return {'x': out_soqs} - - -def _verify_ctrl_tensor_for_and(ctrl_spec: CtrlSpec, and_ctrl: Tuple[int, int]): - cbloq = TestCtrlStatePrepAnd(ctrl_spec, and_ctrl) - bloq_tensor = cbloq.tensor_contract() - cirq_state_vector = GateHelper(And(*and_ctrl)).circuit.final_state_vector( - initial_state=and_ctrl + (0,) - ) - np.testing.assert_allclose(bloq_tensor, cirq_state_vector, atol=1e-8) - - -@pytest.mark.parametrize('ctrl_spec', interesting_ctrl_specs) -def test_controlled_tensor_for_and_bloq(ctrl_spec: CtrlSpec): - # Test AND gate with one-sided signature (aka controlled state preparation). - _verify_ctrl_tensor_for_and(ctrl_spec, (1, 1)) - _verify_ctrl_tensor_for_and(ctrl_spec, (1, 0)) - _verify_ctrl_tensor_for_and(ctrl_spec, (0, 1)) - _verify_ctrl_tensor_for_and(ctrl_spec, (0, 0)) - - def test_controlled_diagrams(): ctrl_gate = XPowGate(0.25).controlled() cirq.testing.assert_has_diagram( diff --git a/qualtran/_infra/gate_with_registers_test.py b/qualtran/_infra/gate_with_registers_test.py index a3735a889..7eb97d508 100644 --- a/qualtran/_infra/gate_with_registers_test.py +++ b/qualtran/_infra/gate_with_registers_test.py @@ -151,7 +151,7 @@ def test_gate_with_registers_decompose_from_context_auto_generated(): def test_non_unitary_controlled(): - bloq = BloqWithDecompose() + bloq = _TestGate() assert bloq.controlled(control_values=[0]) == Controlled(bloq, CtrlSpec(cvs=0)) From c5f301b039534362c7b306c2ec5d7896fc2efea8 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Mon, 19 Aug 2024 15:10:26 -0700 Subject: [PATCH 2/4] lint --- qualtran/_infra/controlled_test.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/qualtran/_infra/controlled_test.py b/qualtran/_infra/controlled_test.py index 20b898f27..abc61bdc2 100644 --- a/qualtran/_infra/controlled_test.py +++ b/qualtran/_infra/controlled_test.py @@ -11,9 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, List, Tuple, TYPE_CHECKING +from typing import List, TYPE_CHECKING -import attrs import cirq import numpy as np import pytest @@ -21,41 +20,31 @@ import qualtran.testing as qlt_testing from qualtran import ( Bloq, - BloqBuilder, CompositeBloq, Controlled, CtrlSpec, QBit, QInt, QUInt, - Register, - Side, - Signature, ) from qualtran._infra.gate_with_registers import get_named_qubits, merge_qubits from qualtran.bloqs.basic_gates import ( CSwap, GlobalPhase, - IntEffect, - IntState, - OneState, Swap, TwoBitCSwap, XGate, XPowGate, YGate, - ZeroState, ZGate, ) from qualtran.bloqs.for_testing import TestAtom, TestParallelCombo, TestSerialCombo -from qualtran.bloqs.mcmt import And -from qualtran.cirq_interop.testing import GateHelper from qualtran.drawing import get_musical_score_data from qualtran.drawing.musical_score import Circle, SoqData, TextBox from qualtran.simulation.tensor import cbloq_to_quimb, get_right_and_left_inds if TYPE_CHECKING: - from qualtran import SoquetT + pass def test_ctrl_spec(): From 05d6c5ea7dfd2557a23335700091be217938f02b Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Tue, 20 Aug 2024 17:18:13 -0700 Subject: [PATCH 3/4] format --- qualtran/_infra/controlled_test.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/qualtran/_infra/controlled_test.py b/qualtran/_infra/controlled_test.py index abc61bdc2..1db30d15f 100644 --- a/qualtran/_infra/controlled_test.py +++ b/qualtran/_infra/controlled_test.py @@ -18,15 +18,7 @@ import pytest import qualtran.testing as qlt_testing -from qualtran import ( - Bloq, - CompositeBloq, - Controlled, - CtrlSpec, - QBit, - QInt, - QUInt, -) +from qualtran import Bloq, CompositeBloq, Controlled, CtrlSpec, QBit, QInt, QUInt from qualtran._infra.gate_with_registers import get_named_qubits, merge_qubits from qualtran.bloqs.basic_gates import ( CSwap, From bca3a11c14cdc89f55677517206500307951cf19 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Fri, 30 Aug 2024 15:09:01 -0700 Subject: [PATCH 4/4] Move error condition to usages --- qualtran/_infra/controlled.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/qualtran/_infra/controlled.py b/qualtran/_infra/controlled.py index 64193e0cb..9f846966e 100644 --- a/qualtran/_infra/controlled.py +++ b/qualtran/_infra/controlled.py @@ -312,10 +312,12 @@ class Controlled(GateWithRegisters): subbloq: 'Bloq' ctrl_spec: 'CtrlSpec' - def __attrs_post_init__(self): + @cached_property + def _thru_registers_only(self) -> bool: for reg in self.subbloq.signature: if reg.side != Side.THRU: - raise ValueError(f"Cannot control non-thru bloqs. Found {reg} in {self.subbloq}") + return False + return True @classmethod def make_ctrl_system(cls, bloq: 'Bloq', ctrl_spec: 'CtrlSpec') -> Tuple[Bloq, AddControlledT]: @@ -368,6 +370,9 @@ def decompose_bloq(self) -> 'CompositeBloq': def build_composite_bloq( self, bb: 'BloqBuilder', **initial_soqs: 'SoquetT' ) -> Dict[str, 'SoquetT']: + if not self._thru_registers_only: + raise DecomposeTypeError(f"Cannot handle non-thru registers in {self.subbloq}") + # Use subbloq's decomposition but wire up the additional ctrl_soqs. from qualtran import CompositeBloq @@ -404,6 +409,8 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: return {(bloq.controlled(self.ctrl_spec), n) for bloq, n in sub_cg} def on_classical_vals(self, **vals: 'ClassicalValT') -> Dict[str, 'ClassicalValT']: + if not self._thru_registers_only: + raise ValueError(f"Cannot handle non-thru registers in {self}.") ctrl_vals = [vals[reg_name] for reg_name in self.ctrl_reg_names] other_vals = {reg.name: vals[reg.name] for reg in self.subbloq.signature} if self.ctrl_spec.is_active(*ctrl_vals): @@ -416,6 +423,8 @@ def on_classical_vals(self, **vals: 'ClassicalValT') -> Dict[str, 'ClassicalValT return vals def _tensor_data(self): + if not self._thru_registers_only: + raise ValueError(f"Cannot handle non-thru registers in {self}.") from qualtran.simulation.tensor._tensor_data_manipulation import ( active_space_for_ctrl_spec, eye_tensor_for_signature, @@ -445,7 +454,7 @@ def _unitary_(self): # to a unitary matrix. return self.tensor_contract() # Unable to determine the unitary effect. - return NotImplemented + raise ValueError(f"Cannot handle non-thru registers in {self}.") def my_tensors( self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT']