diff --git a/qualtran/_infra/controlled.py b/qualtran/_infra/controlled.py index 31161271da..7285c345e5 100644 --- a/qualtran/_infra/controlled.py +++ b/qualtran/_infra/controlled.py @@ -39,7 +39,7 @@ if TYPE_CHECKING: import quimb.tensor as qtn - from qualtran import BloqBuilder, CompositeBloq, Soquet, SoquetT + from qualtran import BloqBuilder, CompositeBloq, ConnectionT, SoquetT from qualtran.cirq_interop import CirqQuregT from qualtran.drawing import WireSymbol from qualtran.resource_counting import BloqCountT, SympySymbolAllocator @@ -400,17 +400,7 @@ def on_classical_vals(self, **vals: 'ClassicalValT') -> Dict[str, 'ClassicalValT return vals - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): - import quimb.tensor as qtn - - from qualtran._infra.composite_bloq import _flatten_soquet_collection + def _tensor_data(self): from qualtran.simulation.tensor._tensor_data_manipulation import ( active_space_for_ctrl_spec, eye_tensor_for_signature, @@ -419,36 +409,35 @@ def add_my_tensors( # Create an identity tensor corresponding to the signature of current Bloq data = eye_tensor_for_signature(self.signature) - # Verify it has the right shape - in_ind = _flatten_soquet_collection(incoming[reg.name] for reg in self.signature.lefts()) - out_ind = _flatten_soquet_collection(outgoing[reg.name] for reg in self.signature.rights()) - assert data.shape == tuple(2**soq.reg.bitsize for ind in [out_ind, in_ind] for soq in ind) # Figure out the ctrl indexes for which the ctrl is "active" active_idx = active_space_for_ctrl_spec(self.signature, self.ctrl_spec) # Put the subbloq tensor at indices where ctrl is active. subbloq_shape = tensor_shape_from_signature(self.subbloq.signature) data[active_idx] = self.subbloq.tensor_contract().reshape(subbloq_shape) - # Add the data to the tensor network. - tn.add(qtn.Tensor(data=data, inds=out_ind + in_ind, tags=[self.pretty_name(), tag])) + return data def _unitary_(self): - if isinstance(self.subbloq, GateWithRegisters): - # subbloq is a cirq gate, use the cirq-style API to derive a unitary. - return cirq.unitary( - cirq.ControlledGate(self.subbloq, control_values=self.ctrl_spec.to_cirq_cv()) - ) - if all(reg.side == Side.THRU for reg in self.subbloq.signature): - # subbloq has only THRU registers, so the tensor contraction corresponds - # to a unitary matrix. - return self.tensor_contract() - # Unable to determine the unitary effect. - return NotImplemented + n = self.signature.n_qubits() + return self._tensor_data().reshape(2**n, 2**n) + + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: + import quimb.tensor as qtn + + from qualtran.simulation.tensor._dense import _order_incoming_outgoing_indices + + inds = _order_incoming_outgoing_indices( + self.signature, incoming=incoming, outgoing=outgoing + ) + data = self._tensor_data().reshape((2,) * len(inds)) + return [qtn.Tensor(data=data, inds=inds, tags=[str(self)])] def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': from qualtran.drawing import Text if reg is None: - return Text(f'C[{self.subbloq.wire_symbol(reg=None)}]') + return Text(f'C[{self.subbloq}]') if reg.name not in self.ctrl_reg_names: # Delegate to subbloq return self.subbloq.wire_symbol(reg, idx) diff --git a/qualtran/_infra/controlled_test.py b/qualtran/_infra/controlled_test.py index 55cd902926..19f52836b5 100644 --- a/qualtran/_infra/controlled_test.py +++ b/qualtran/_infra/controlled_test.py @@ -35,10 +35,12 @@ 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, @@ -50,6 +52,7 @@ 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 @@ -340,7 +343,7 @@ def test_notebook(): def _verify_ctrl_tensor_for_unitary(ctrl_spec: CtrlSpec, bloq: Bloq, gate: cirq.Gate): cbloq = Controlled(bloq, ctrl_spec) cgate = cirq.ControlledGate(gate, control_values=ctrl_spec.to_cirq_cv()) - np.testing.assert_array_equal(cbloq.tensor_contract(), cirq.unitary(cgate)) + np.testing.assert_allclose(cbloq.tensor_contract(), cirq.unitary(cgate), atol=1e-8) interesting_ctrl_specs = [ @@ -362,6 +365,26 @@ def test_controlled_tensor_for_unitary(ctrl_spec: CtrlSpec): _verify_ctrl_tensor_for_unitary(ctrl_spec, CSwap(3), CSwap(3)) +def test_controlled_tensor_without_decompose(): + ctrl_spec = CtrlSpec() + bloq = TwoBitCSwap() + cbloq = Controlled(bloq, ctrl_spec) + cgate = cirq.ControlledGate(cirq.CSWAP, control_values=ctrl_spec.to_cirq_cv()) + + tn = cbloq_to_quimb(cbloq.as_composite_bloq()) + # pylint: disable=unbalanced-tuple-unpacking + right, left = get_right_and_left_inds(tn, cbloq.signature) + # pylint: enable=unbalanced-tuple-unpacking + np.testing.assert_allclose(tn.to_dense(right, left), cirq.unitary(cgate), atol=1e-8) + np.testing.assert_allclose(cbloq.tensor_contract(), cirq.unitary(cgate), atol=1e-8) + + +def test_controlled_global_phase_tensor(): + bloq = GlobalPhase(1.0j).controlled() + should_be = np.diag([1, 1.0j]) + 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. diff --git a/qualtran/_infra/gate_with_registers.py b/qualtran/_infra/gate_with_registers.py index f13e9da280..1905ab4434 100644 --- a/qualtran/_infra/gate_with_registers.py +++ b/qualtran/_infra/gate_with_registers.py @@ -14,7 +14,6 @@ import abc from typing import ( - Any, cast, Collection, Dict, @@ -40,7 +39,7 @@ if TYPE_CHECKING: import quimb.tensor as qtn - from qualtran import AddControlledT, BloqBuilder, CtrlSpec, SoquetT + from qualtran import AddControlledT, BloqBuilder, ConnectionT, CtrlSpec, SoquetT from qualtran.cirq_interop import CirqQuregT from qualtran.drawing import WireSymbol @@ -507,22 +506,15 @@ def controlled( def _unitary_(self): return NotImplemented - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: 'Any', - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: if not self._unitary_.__qualname__.startswith('GateWithRegisters.'): - from qualtran.cirq_interop._cirq_to_bloq import _add_my_tensors_from_gate + from qualtran.cirq_interop._cirq_to_bloq import _my_tensors_from_gate - _add_my_tensors_from_gate( - self, self.signature, str(self), tn, tag, incoming=incoming, outgoing=outgoing - ) + return _my_tensors_from_gate(self, self.signature, incoming=incoming, outgoing=outgoing) else: - return super().add_my_tensors(tn, tag, incoming=incoming, outgoing=outgoing) + return super().my_tensors(incoming=incoming, outgoing=outgoing) def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: """Default diagram info that uses register names to name the boxes in multi-qubit gates. diff --git a/qualtran/bloqs/arithmetic/addition.py b/qualtran/bloqs/arithmetic/addition.py index 88543c8b31..882722ec78 100644 --- a/qualtran/bloqs/arithmetic/addition.py +++ b/qualtran/bloqs/arithmetic/addition.py @@ -11,11 +11,9 @@ # 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. -import itertools import math from functools import cached_property from typing import ( - Any, Dict, Iterable, Iterator, @@ -63,8 +61,6 @@ from qualtran.drawing import directional_text_box, Text if TYPE_CHECKING: - import quimb.tensor as qtn - from qualtran.drawing import WireSymbol from qualtran.resource_counting import BloqCountT, SympySymbolAllocator from qualtran.simulation.classical_sim import ClassicalValT @@ -125,28 +121,6 @@ def dtype(self): def signature(self): return Signature([Register("a", self.a_dtype), Register("b", self.b_dtype)]) - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): - import quimb.tensor as qtn - - if isinstance(self.a_dtype, QInt) or isinstance(self.b_dtype, QInt): - raise TypeError("Tensor contraction for addition is only supported for unsigned ints.") - N_a = 2**self.a_dtype.bitsize - N_b = 2**self.b_dtype.bitsize - inds = (incoming['a'], incoming['b'], outgoing['a'], outgoing['b']) - unitary = np.zeros((N_a, N_b, N_a, N_b), dtype=np.complex128) - # TODO: Add a value-to-index method on dtype to make this easier. - for a, b in itertools.product(range(N_a), range(N_b)): - unitary[a, b, a, int(math.fmod(a + b, N_b))] = 1 - - tn.add(qtn.Tensor(data=unitary, inds=inds, tags=[self.pretty_name(), tag])) - def decompose_bloq(self) -> 'CompositeBloq': return decompose_from_cirq_style_method(self) diff --git a/qualtran/bloqs/arithmetic/addition_test.py b/qualtran/bloqs/arithmetic/addition_test.py index 039008cfbf..858ea21f24 100644 --- a/qualtran/bloqs/arithmetic/addition_test.py +++ b/qualtran/bloqs/arithmetic/addition_test.py @@ -171,7 +171,7 @@ def test_addition_gate_counts(n: int): @pytest.mark.parametrize('a,b', itertools.product(range(2**3), repeat=2)) -def test_add_no_decompose(a, b): +def test_add_tensor_contract(a, b): num_bits = 5 bloq = Add(QUInt(num_bits)) @@ -184,7 +184,7 @@ def test_add_no_decompose(a, b): assert true_out_int == int(out_bin, 2) unitary = bloq.tensor_contract() - assert unitary[output_int, input_int] == 1 + np.testing.assert_allclose(unitary[output_int, input_int], 1) @pytest.mark.parametrize('a,b,num_bits', itertools.product(range(4), range(4), range(3, 5))) diff --git a/qualtran/bloqs/arithmetic/multiplication.py b/qualtran/bloqs/arithmetic/multiplication.py index e5ef7dd25f..01ae9f44b5 100644 --- a/qualtran/bloqs/arithmetic/multiplication.py +++ b/qualtran/bloqs/arithmetic/multiplication.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Dict, Iterable, Sequence, Set, TYPE_CHECKING, Union +from typing import Dict, Iterable, List, Sequence, Set, TYPE_CHECKING, Union import cirq import numpy as np @@ -22,6 +22,7 @@ Bloq, bloq_example, BloqDocSpec, + ConnectionT, GateWithRegisters, QFxp, QUInt, @@ -35,7 +36,6 @@ if TYPE_CHECKING: import quimb.tensor as qtn - from qualtran import SoquetT from qualtran.resource_counting import BloqCountT, SympySymbolAllocator from qualtran.simulation.classical_sim import ClassicalValT @@ -90,19 +90,12 @@ def __pow__(self, power): ) raise NotImplementedError("PlusEqualProduct.__pow__ defined only for powers +1/-1.") - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): - from qualtran.cirq_interop._cirq_to_bloq import _add_my_tensors_from_gate - - _add_my_tensors_from_gate( - self, self.signature, self.pretty_name(), tn, tag, incoming=incoming, outgoing=outgoing - ) + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: + from qualtran.cirq_interop._cirq_to_bloq import _my_tensors_from_gate + + return _my_tensors_from_gate(self, self.signature, incoming=incoming, outgoing=outgoing) def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: # TODO: The T-complexity here is approximate. @@ -169,25 +162,25 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: num_toff = self.bitsize * (self.bitsize - 1) return {(Toffoli(), num_toff)} - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn + n = self.bitsize N = 2**self.bitsize data = np.zeros((N, N, N**2), dtype=np.complex128) for x in range(N): data[x, x, x**2] = 1 trg = incoming['result'] if self.uncompute else outgoing['result'] - tn.add( - qtn.Tensor(data=data, inds=(incoming['a'], outgoing['a'], trg), tags=['Square', tag]) + inds = ( + [(incoming['a'], j) for j in range(n)] + + [(outgoing['a'], j) for j in range(n)] + + [(trg, j) for j in range(2 * n)] ) + data = data.reshape((2,) * (4 * n)) + return [qtn.Tensor(data=data, inds=inds, tags=[str(self)])] def adjoint(self) -> 'Bloq': return Square(self.bitsize, not self.uncompute) diff --git a/qualtran/bloqs/arithmetic/subtraction_test.py b/qualtran/bloqs/arithmetic/subtraction_test.py index d878d77f86..e1da186651 100644 --- a/qualtran/bloqs/arithmetic/subtraction_test.py +++ b/qualtran/bloqs/arithmetic/subtraction_test.py @@ -31,7 +31,7 @@ def test_subtract_bloq_decomposition(): c = (a - b) % 32 want[(a << 5) | c][a_b] = 1 got = gate.tensor_contract() - np.testing.assert_equal(got, want) + np.testing.assert_allclose(got, want) def test_subtract_bloq_validation(): diff --git a/qualtran/bloqs/basic_gates/cnot.py b/qualtran/bloqs/basic_gates/cnot.py index c92d49478c..62ae09258a 100644 --- a/qualtran/bloqs/basic_gates/cnot.py +++ b/qualtran/bloqs/basic_gates/cnot.py @@ -14,7 +14,7 @@ import itertools from functools import cached_property -from typing import Any, Dict, Iterable, Optional, Sequence, Tuple, TYPE_CHECKING +from typing import Dict, Iterable, List, Optional, Sequence, Tuple, TYPE_CHECKING import numpy as np from attrs import frozen @@ -26,6 +26,7 @@ BloqBuilder, BloqDocSpec, CompositeBloq, + ConnectionT, CtrlSpec, DecomposeTypeError, Register, @@ -69,35 +70,27 @@ def decompose_bloq(self) -> 'CompositeBloq': def adjoint(self) -> 'Bloq': return self - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, SoquetT], - outgoing: Dict[str, SoquetT], - ): - """Append tensors to `tn` that represent this operation. - - This bloq uses the factored form of CNOT composed of a COPY and XOR tensor joined - by an internal index. - - References: - [Lectures on Quantum Tensor Networks](https://arxiv.org/abs/1912.10049). Biamonte 2019. - """ + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: + # This bloq uses the factored form of CNOT composed of a COPY and XOR tensor joined + # by an internal index. + # [Lectures on Quantum Tensor Networks](https://arxiv.org/abs/1912.10049). Biamonte 2019. import quimb.tensor as qtn internal = qtn.rand_uuid() - tn.add( + return [ qtn.Tensor( - data=COPY, inds=(incoming['ctrl'], outgoing['ctrl'], internal), tags=['COPY', tag] - ) - ) - tn.add( + data=COPY, + inds=[(incoming['ctrl'], 0), (outgoing['ctrl'], 0), internal], + tags=['COPY'], + ), qtn.Tensor( - data=XOR, inds=(incoming['target'], outgoing['target'], internal), tags=['XOR'] - ) - ) + data=XOR, + inds=[(incoming['target'], 0), (outgoing['target'], 0), internal], + tags=['XOR'], + ), + ] def on_classical_vals(self, ctrl: int, target: int) -> Dict[str, 'ClassicalValT']: return {'ctrl': ctrl, 'target': (ctrl + target) % 2} diff --git a/qualtran/bloqs/basic_gates/global_phase.py b/qualtran/bloqs/basic_gates/global_phase.py index 5b23bd88cc..63bccebe8a 100644 --- a/qualtran/bloqs/basic_gates/global_phase.py +++ b/qualtran/bloqs/basic_gates/global_phase.py @@ -12,18 +12,33 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING +from typing import Dict, Iterable, List, Optional, Sequence, Tuple, TYPE_CHECKING import attrs import cirq +import numpy as np from attrs import frozen -from qualtran import bloq_example, BloqDocSpec, DecomposeTypeError +from qualtran import ( + AddControlledT, + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + ConnectionT, + CtrlSpec, + DecomposeTypeError, + SoquetT, +) from qualtran.cirq_interop import CirqGateAsBloqBase from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.symbolics import sconj, SymbolicComplex +from .rotation import ZPowGate + if TYPE_CHECKING: + import quimb.tensor as qtn + from qualtran import CompositeBloq @@ -49,6 +64,33 @@ def decompose_bloq(self) -> 'CompositeBloq': def adjoint(self) -> 'GlobalPhase': return attrs.evolve(self, coefficient=sconj(self.coefficient)) + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: + import quimb.tensor as qtn + + return [qtn.Tensor(data=self.coefficient, inds=[], tags=[str(self)])] + + def get_ctrl_system( + self, ctrl_spec: Optional['CtrlSpec'] = None + ) -> Tuple['Bloq', 'AddControlledT']: + + # Default logic for more than one control. + if not (ctrl_spec is None or ctrl_spec == CtrlSpec()): + return super().get_ctrl_system(ctrl_spec=ctrl_spec) + + exponent: float = np.real_if_close(np.log(self.coefficient) / (np.pi * 1.0j)).item() # type: ignore + bloq = ZPowGate(exponent=exponent, eps=self.eps) + + def _add_ctrled( + bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT'] + ) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]: + (ctrl,) = ctrl_soqs + ctrl = bb.add(bloq, q=ctrl) + return [ctrl], [] + + return bloq, _add_ctrled + def pretty_name(self) -> str: return 'GPhase' diff --git a/qualtran/bloqs/basic_gates/global_phase_test.py b/qualtran/bloqs/basic_gates/global_phase_test.py index 782cca9bdd..de6aa06f2e 100644 --- a/qualtran/bloqs/basic_gates/global_phase_test.py +++ b/qualtran/bloqs/basic_gates/global_phase_test.py @@ -22,12 +22,22 @@ def test_unitary(): random_state = np.random.RandomState(2) - for alpha in random_state.random(size=20): + for alpha in random_state.random(size=10): coefficient = np.exp(2j * np.pi * alpha) bloq = GlobalPhase(coefficient) np.testing.assert_allclose(cirq.unitary(bloq), coefficient) +def test_controlled(): + random_state = np.random.RandomState(2) + for alpha in random_state.random(size=10): + coefficient = np.exp(2j * np.pi * alpha) + bloq = GlobalPhase(coefficient).controlled() + np.testing.assert_allclose( + cirq.unitary(cirq.GlobalPhaseGate(coefficient).controlled()), bloq.tensor_contract() + ) + + def test_t_complexity(): assert GlobalPhase(1j).t_complexity() == TComplexity() diff --git a/qualtran/bloqs/basic_gates/hadamard.py b/qualtran/bloqs/basic_gates/hadamard.py index 6a834d7803..02a3bf3244 100644 --- a/qualtran/bloqs/basic_gates/hadamard.py +++ b/qualtran/bloqs/basic_gates/hadamard.py @@ -13,7 +13,7 @@ # limitations under the License. from functools import cached_property -from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING +from typing import Dict, List, Optional, Tuple, TYPE_CHECKING import numpy as np from attrs import frozen @@ -23,10 +23,10 @@ bloq_example, BloqDocSpec, CompositeBloq, + ConnectionT, DecomposeTypeError, Register, Signature, - SoquetT, ) from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Text, TextBox, WireSymbol @@ -65,17 +65,16 @@ def adjoint(self) -> 'Bloq': def decompose_bloq(self) -> 'CompositeBloq': raise DecomposeTypeError(f"{self} is atomic") - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn - tn.add(qtn.Tensor(data=_HADAMARD, inds=(outgoing['q'], incoming['q']), tags=["H", tag])) + return [ + qtn.Tensor( + data=_HADAMARD, inds=[(outgoing['q'], 0), (incoming['q'], 0)], tags=[str(self)] + ) + ] def as_cirq_op( self, qubit_manager: 'cirq.QubitManager', q: 'CirqQuregT' # type: ignore[type-var] diff --git a/qualtran/bloqs/basic_gates/identity.py b/qualtran/bloqs/basic_gates/identity.py index ae3e26fcf5..86f03ed1f7 100644 --- a/qualtran/bloqs/basic_gates/identity.py +++ b/qualtran/bloqs/basic_gates/identity.py @@ -13,7 +13,7 @@ # limitations under the License. from functools import cached_property -from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING +from typing import Dict, List, Optional, Tuple, TYPE_CHECKING import numpy as np from attrs import frozen @@ -23,10 +23,10 @@ bloq_example, BloqDocSpec, CompositeBloq, + ConnectionT, DecomposeTypeError, Register, Signature, - SoquetT, ) from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Text, TextBox, WireSymbol @@ -57,17 +57,16 @@ def adjoint(self) -> 'Bloq': def decompose_bloq(self) -> 'CompositeBloq': raise DecomposeTypeError(f"{self} is atomic") - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn - tn.add(qtn.Tensor(data=np.eye(2), inds=(outgoing['q'], incoming['q']), tags=["I", tag])) + return [ + qtn.Tensor( + data=np.eye(2), inds=[(outgoing['q'], 0), (incoming['q'], 0)], tags=[str(self)] + ) + ] def as_cirq_op( self, qubit_manager: 'cirq.QubitManager', q: 'CirqQuregT' # type: ignore[type-var] diff --git a/qualtran/bloqs/basic_gates/s_gate.py b/qualtran/bloqs/basic_gates/s_gate.py index 3dbb7020f4..a716d06fe9 100644 --- a/qualtran/bloqs/basic_gates/s_gate.py +++ b/qualtran/bloqs/basic_gates/s_gate.py @@ -13,13 +13,13 @@ # limitations under the License. from functools import cached_property -from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING +from typing import Dict, List, Optional, Tuple, TYPE_CHECKING import attrs import numpy as np from attrs import frozen -from qualtran import Bloq, bloq_example, BloqDocSpec, Register, Signature, SoquetT +from qualtran import Bloq, bloq_example, BloqDocSpec, ConnectionT, Register, Signature from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Text, TextBox, WireSymbol @@ -57,22 +57,15 @@ def signature(self) -> 'Signature': def _t_complexity_(self) -> 'TComplexity': return TComplexity(clifford=1) - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn data = _SMATRIX.conj().T if self.is_adjoint else _SMATRIX - tn.add( - qtn.Tensor( - data=data, inds=(outgoing['q'], incoming['q']), tags=[self.pretty_name(), tag] - ) - ) + return [ + qtn.Tensor(data=data, inds=[(outgoing['q'], 0), (incoming['q'], 0)], tags=[str(self)]) + ] def as_cirq_op( self, qubit_manager: 'cirq.QubitManager', q: 'CirqQuregT' # type:ignore[type-var] diff --git a/qualtran/bloqs/basic_gates/su2_rotation.py b/qualtran/bloqs/basic_gates/su2_rotation.py index 79022f6591..e0344084e9 100644 --- a/qualtran/bloqs/basic_gates/su2_rotation.py +++ b/qualtran/bloqs/basic_gates/su2_rotation.py @@ -12,14 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. from functools import cached_property -from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING +from typing import Dict, List, Optional, Tuple, TYPE_CHECKING import numpy as np import sympy from attrs import frozen from numpy.typing import NDArray -from qualtran import bloq_example, BloqDocSpec, GateWithRegisters, Register, Signature +from qualtran import bloq_example, BloqDocSpec, ConnectionT, GateWithRegisters, Register, Signature from qualtran.bloqs.basic_gates import GlobalPhase, Ry, ZPowGate from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Text, TextBox @@ -106,28 +106,18 @@ def from_matrix(mat: NDArray[np.complex_]) -> 'SU2RotationGate': return SU2RotationGate(theta, phi, lambd, alpha) - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn - tn.add( + return [ qtn.Tensor( data=self.rotation_matrix, - inds=(outgoing['q'], incoming['q']), - tags=[self.pretty_name(), tag], + inds=[(outgoing['q'], 0), (incoming['q'], 0)], + tags=[str(self)], ) - ) - - def _unitary_(self): - if self.is_symbolic(): - return None - return self.rotation_matrix + ] def build_composite_bloq(self, bb: 'BloqBuilder', q: 'SoquetT') -> Dict[str, 'SoquetT']: pi = sympy.pi if self.is_symbolic() else np.pi diff --git a/qualtran/bloqs/basic_gates/swap.py b/qualtran/bloqs/basic_gates/swap.py index 592b4378cd..5f65ac2c21 100644 --- a/qualtran/bloqs/basic_gates/swap.py +++ b/qualtran/bloqs/basic_gates/swap.py @@ -13,7 +13,7 @@ # limitations under the License. from functools import cached_property -from typing import Any, Dict, Iterator, Optional, Sequence, Set, Tuple, TYPE_CHECKING, Union +from typing import Dict, Iterator, List, Optional, Sequence, Set, Tuple, TYPE_CHECKING, Union import cirq import numpy as np @@ -26,6 +26,7 @@ bloq_example, BloqBuilder, BloqDocSpec, + ConnectionT, DecomposeTypeError, GateWithRegisters, Register, @@ -83,20 +84,15 @@ def as_cirq_op( def _t_complexity_(self) -> 'TComplexity': return TComplexity(clifford=1) - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn matrix = _swap_matrix() - out_inds = [outgoing['x'], outgoing['y']] - in_inds = [incoming['x'], incoming['y']] - tn.add(qtn.Tensor(data=matrix, inds=out_inds + in_inds, tags=["swap", tag])) + out_inds = [(outgoing['x'], 0), (outgoing['y'], 0)] + in_inds = [(incoming['x'], 0), (incoming['y'], 0)] + return [qtn.Tensor(data=matrix, inds=out_inds + in_inds, tags=[str(self)])] def on_classical_vals( self, x: 'ClassicalValT', y: 'ClassicalValT' @@ -152,20 +148,20 @@ def decompose_from_registers( yield [cirq.T(x), cirq.H(y)] yield [cirq.CNOT(y, x)] - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn + # TODO: https://github.com/quantumlib/Qualtran/issues/873. Since this bloq + # has a decomposition, it will be used (by default) whenever `bloq.tensor_contract()` + # is called on a bloq containing a TwoBitCSwap instead of this implementation. + # When this becomes a leaf bloq, this explicit tensor will be used. + matrix = _controlled_swap_matrix() - out_inds = [outgoing['ctrl'], outgoing['x'], outgoing['y']] - in_inds = [incoming['ctrl'], incoming['x'], incoming['y']] - tn.add(qtn.Tensor(data=matrix, inds=out_inds + in_inds, tags=["swap", tag])) + out_inds = [(outgoing['ctrl'], 0), (outgoing['x'], 0), (outgoing['y'], 0)] + in_inds = [(incoming['ctrl'], 0), (incoming['x'], 0), (incoming['y'], 0)] + return [qtn.Tensor(data=matrix, inds=out_inds + in_inds, tags=[str(self)])] def on_classical_vals( self, ctrl: 'ClassicalValT', x: 'ClassicalValT', y: 'ClassicalValT' diff --git a/qualtran/bloqs/basic_gates/swap_test.py b/qualtran/bloqs/basic_gates/swap_test.py index 26edaba0ce..7a46ce6214 100644 --- a/qualtran/bloqs/basic_gates/swap_test.py +++ b/qualtran/bloqs/basic_gates/swap_test.py @@ -62,6 +62,11 @@ def test_two_bit_swap_unitary_vs_cirq(): np.testing.assert_array_equal(swap.tensor_contract(), cirq.unitary(cirq.SWAP)) +def test_two_bit_cswap_unitary_vs_cirq(): + swap = TwoBitCSwap() + np.testing.assert_allclose(swap.tensor_contract(), cirq.unitary(cirq.CSWAP), atol=1e-8) + + def test_two_bit_swap_as_cirq_op(): q = cirq.LineQubit.range(2) expected_circuit = cirq.Circuit(cirq.SWAP(*q)) @@ -93,16 +98,16 @@ def _set_ctrl_two_bit_swap(ctrl_bit): def test_two_bit_cswap(): cswap = TwoBitCSwap() - np.testing.assert_array_equal(cswap.tensor_contract(), cirq.unitary(cirq.CSWAP)) + np.testing.assert_allclose(cswap.tensor_contract(), cirq.unitary(cirq.CSWAP), atol=1e-8) np.testing.assert_allclose( cswap.decompose_bloq().tensor_contract(), cirq.unitary(cirq.CSWAP), atol=1e-8 ) # Zero ctrl -- it's identity - np.testing.assert_array_equal(np.eye(4), _set_ctrl_two_bit_swap(0).tensor_contract()) + np.testing.assert_allclose(np.eye(4), _set_ctrl_two_bit_swap(0).tensor_contract(), atol=1e-8) # One ctrl -- it's swap - np.testing.assert_array_equal( - _swap_matrix().reshape(4, 4), _set_ctrl_two_bit_swap(1).tensor_contract() + np.testing.assert_allclose( + _swap_matrix().reshape(4, 4), _set_ctrl_two_bit_swap(1).tensor_contract(), atol=1e-8 ) # classical logic @@ -159,13 +164,15 @@ def test_cswap_unitary(): cswap = CSwap(bitsize=4) # Zero ctrl -- it's identity - np.testing.assert_array_equal(np.eye(2 ** (4 * 2)), _set_ctrl_swap(0, cswap).tensor_contract()) + np.testing.assert_allclose( + np.eye(2 ** (4 * 2)), _set_ctrl_swap(0, cswap).tensor_contract(), atol=1e-8 + ) # One ctrl -- it's multi-swap qubits = cirq.LineQubit.range(8) q_x, q_y = qubits[:4], qubits[4:] unitary = cirq.unitary(cirq.Circuit(cirq.SWAP(x, y) for x, y in zip(q_x, q_y))) - np.testing.assert_array_equal(unitary, _set_ctrl_swap(1, cswap).tensor_contract()) + np.testing.assert_allclose(unitary, _set_ctrl_swap(1, cswap).tensor_contract(), atol=1e-8) def test_cswap_classical(): diff --git a/qualtran/bloqs/basic_gates/t_gate.py b/qualtran/bloqs/basic_gates/t_gate.py index 2a4616fca5..b80f752ff2 100644 --- a/qualtran/bloqs/basic_gates/t_gate.py +++ b/qualtran/bloqs/basic_gates/t_gate.py @@ -13,13 +13,13 @@ # limitations under the License. from functools import cached_property -from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING +from typing import Dict, List, Optional, Tuple, TYPE_CHECKING import attrs import numpy as np from attrs import frozen -from qualtran import Bloq, bloq_example, BloqDocSpec, Register, Signature, SoquetT +from qualtran import Bloq, bloq_example, BloqDocSpec, ConnectionT, Register, Signature from qualtran.drawing import Text, TextBox, WireSymbol if TYPE_CHECKING: @@ -74,22 +74,15 @@ class TGate(Bloq): def signature(self) -> 'Signature': return Signature.build(q=1) - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn data = _TMATRIX.conj().T if self.is_adjoint else _TMATRIX - tn.add( - qtn.Tensor( - data=data, inds=(outgoing['q'], incoming['q']), tags=[self.pretty_name(), tag] - ) - ) + return [ + qtn.Tensor(data=data, inds=[(outgoing['q'], 0), (incoming['q'], 0)], tags=[str(self)]) + ] def adjoint(self) -> 'Bloq': return attrs.evolve(self, is_adjoint=not self.is_adjoint) diff --git a/qualtran/bloqs/basic_gates/toffoli.py b/qualtran/bloqs/basic_gates/toffoli.py index 3c8599c316..7c9d48fee8 100644 --- a/qualtran/bloqs/basic_gates/toffoli.py +++ b/qualtran/bloqs/basic_gates/toffoli.py @@ -13,13 +13,13 @@ # limitations under the License. import itertools from functools import cached_property -from typing import Any, Dict, Optional, Set, Tuple, TYPE_CHECKING, Union +from typing import Dict, List, Optional, Set, Tuple, TYPE_CHECKING, Union import numpy as np from attrs import frozen from numpy.typing import NDArray -from qualtran import Bloq, bloq_example, BloqDocSpec, QBit, Register, Signature, Soquet +from qualtran import Bloq, bloq_example, BloqDocSpec, Connection, QBit, Register, Signature from qualtran.bloqs.basic_gates import TGate from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.resource_counting import SympySymbolAllocator @@ -62,14 +62,11 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: def _t_complexity_(self): return TComplexity(t=4) - def add_my_tensors( + def my_tensors( self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, NDArray['Soquet']], # type: ignore[type-var] - outgoing: Dict[str, NDArray['Soquet']], # type: ignore[type-var] - ): + incoming: Dict[str, NDArray[Connection]], # type: ignore[type-var] + outgoing: Dict[str, NDArray[Connection]], # type: ignore[type-var] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn from qualtran.bloqs.basic_gates.cnot import XOR @@ -77,24 +74,26 @@ def add_my_tensors( # Set up the CTRL tensor which copies inputs to outputs and activates # when a==1 and b==1 internal = qtn.rand_uuid() - inds = ( - incoming['ctrl'][0], - incoming['ctrl'][1], - outgoing['ctrl'][0], - outgoing['ctrl'][1], + inds = [ + (incoming['ctrl'][0], 0), + (incoming['ctrl'][1], 0), + (outgoing['ctrl'][0], 0), + (outgoing['ctrl'][1], 0), internal, - ) + ] CTRL = np.zeros((2,) * 5, dtype=np.complex128) for a, b in itertools.product([0, 1], repeat=2): CTRL[a, b, a, b, int(a == 1 and b == 1)] = 1.0 # Wire up the CTRL tensor to XOR to flip `target` when active. - tn.add(qtn.Tensor(data=CTRL, inds=inds, tags=['COPY', tag])) - tn.add( + return [ + qtn.Tensor(data=CTRL, inds=inds, tags=['COPY']), qtn.Tensor( - data=XOR, inds=(incoming['target'], outgoing['target'], internal), tags=['XOR'] - ) - ) + data=XOR, + inds=[(incoming['target'], 0), (outgoing['target'], 0), internal], + tags=['XOR'], + ), + ] def on_classical_vals( self, ctrl: NDArray[np.integer], target: 'ClassicalValT' diff --git a/qualtran/bloqs/basic_gates/x_basis.py b/qualtran/bloqs/basic_gates/x_basis.py index 086b0bd41e..88e8ba7de0 100644 --- a/qualtran/bloqs/basic_gates/x_basis.py +++ b/qualtran/bloqs/basic_gates/x_basis.py @@ -13,7 +13,7 @@ # limitations under the License. from functools import cached_property -from typing import Any, Dict, Iterable, Optional, Sequence, Tuple, TYPE_CHECKING, Union +from typing import Dict, Iterable, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union import numpy as np from attrs import frozen @@ -24,6 +24,7 @@ bloq_example, BloqBuilder, BloqDocSpec, + ConnectionT, CtrlSpec, QBit, Register, @@ -72,24 +73,15 @@ def __attrs_post_init__(self): def signature(self) -> 'Signature': return Signature([Register('q', QBit(), side=Side.RIGHT if self.state else Side.LEFT)]) - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, SoquetT], - outgoing: Dict[str, SoquetT], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn side = outgoing if self.state else incoming - tn.add( - qtn.Tensor( - data=_MINUS if self.bit else _PLUS, - inds=(side['q'],), - tags=[self.pretty_name(), tag], - ) - ) + return [ + qtn.Tensor(data=_MINUS if self.bit else _PLUS, inds=[(side['q'], 0)], tags=[str(self)]) + ] def as_cirq_op( self, qubit_manager: 'cirq.QubitManager', **cirq_quregs: 'CirqQuregT' # type: ignore[type-var] @@ -222,21 +214,16 @@ def signature(self) -> 'Signature': def adjoint(self) -> 'Bloq': return self - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, SoquetT], - outgoing: Dict[str, SoquetT], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn - tn.add( + return [ qtn.Tensor( - data=_PAULIX, inds=(outgoing['q'], incoming['q']), tags=[self.pretty_name(), tag] + data=_PAULIX, inds=[(outgoing['q'], 0), (incoming['q'], 0)], tags=[str(self)] ) - ) + ] def get_ctrl_system( self, ctrl_spec: Optional['CtrlSpec'] = None diff --git a/qualtran/bloqs/basic_gates/y_gate.py b/qualtran/bloqs/basic_gates/y_gate.py index 9eb676edf4..b3ee7255e5 100644 --- a/qualtran/bloqs/basic_gates/y_gate.py +++ b/qualtran/bloqs/basic_gates/y_gate.py @@ -13,12 +13,12 @@ # limitations under the License. from functools import cached_property -from typing import Any, Dict, Tuple, TYPE_CHECKING +from typing import Dict, List, Tuple, TYPE_CHECKING import numpy as np from attrs import frozen -from qualtran import Bloq, Signature, SoquetT +from qualtran import Bloq, ConnectionT, Signature from qualtran.cirq_interop.t_complexity_protocol import TComplexity if TYPE_CHECKING: @@ -44,17 +44,16 @@ def signature(self) -> 'Signature': def adjoint(self) -> 'Bloq': return self - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, SoquetT], - outgoing: Dict[str, SoquetT], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn - tn.add(qtn.Tensor(data=_PAULIY, inds=(outgoing['q'], incoming['q']), tags=["Y", tag])) + return [ + qtn.Tensor( + data=_PAULIY, inds=[(outgoing['q'], 0), (incoming['q'], 0)], tags=[str(self)] + ) + ] def as_cirq_op( self, qubit_manager: 'cirq.QubitManager', q: 'CirqQuregT' diff --git a/qualtran/bloqs/basic_gates/z_basis.py b/qualtran/bloqs/basic_gates/z_basis.py index 222332ca21..cdb6e488ef 100644 --- a/qualtran/bloqs/basic_gates/z_basis.py +++ b/qualtran/bloqs/basic_gates/z_basis.py @@ -13,7 +13,7 @@ # limitations under the License. from functools import cached_property -from typing import Any, cast, Dict, Optional, Set, Tuple, TYPE_CHECKING, Union +from typing import cast, Dict, List, Optional, Set, Tuple, TYPE_CHECKING, Union import attrs import numpy as np @@ -27,6 +27,7 @@ BloqBuilder, BloqDocSpec, CompositeBloq, + ConnectionT, DecomposeTypeError, QAny, QBit, @@ -82,24 +83,15 @@ def signature(self) -> 'Signature': def decompose_bloq(self) -> CompositeBloq: raise DecomposeTypeError(f"{self} is atomic") - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, SoquetT], - outgoing: Dict[str, SoquetT], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn side = outgoing if self.state else incoming - tn.add( - qtn.Tensor( - data=_ONE if self.bit else _ZERO, - inds=(side['q'],), - tags=['1' if self.bit else '0', tag], - ) - ) + return [ + qtn.Tensor(data=_ONE if self.bit else _ZERO, inds=[(side['q'], 0)], tags=[str(self)]) + ] def on_classical_vals(self, *, q: Optional[int] = None) -> Dict[str, int]: """Return or consume 1 or 0 depending on `self.state` and `self.bit`. @@ -248,17 +240,16 @@ def adjoint(self) -> 'Bloq': def decompose_bloq(self) -> CompositeBloq: raise DecomposeTypeError(f"{self} is atomic") - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, SoquetT], - outgoing: Dict[str, SoquetT], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn - tn.add(qtn.Tensor(data=_PAULIZ, inds=(outgoing['q'], incoming['q']), tags=["Z", tag])) + return [ + qtn.Tensor( + data=_PAULIZ, inds=[(outgoing['q'], 0), (incoming['q'], 0)], tags=[str(self)] + ) + ] def as_cirq_op( self, qubit_manager: 'cirq.QubitManager', q: 'CirqQuregT' @@ -344,30 +335,6 @@ def build_composite_bloq(self, bb: 'BloqBuilder', **val: 'SoquetT') -> Dict[str, else: return self._build_composite_effect(bb, cast(Soquet, val['val']), bits) - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, SoquetT], - outgoing: Dict[str, SoquetT], - ): - import quimb.tensor as qtn - - if isinstance(self.bitsize, sympy.Expr): - raise ValueError(f'Symbolic bitsize {self.bitsize} not supported') - data = np.zeros(2**self.bitsize).reshape((2,) * self.bitsize) - bitstring = ints_to_bits(np.array([self.val]), w=self.bitsize)[0] - data[tuple(bitstring)] = 1 - data = data.reshape(-1) - - if self.state: - inds = (outgoing['val'],) - else: - inds = (incoming['val'],) - - tn.add(qtn.Tensor(data=data, inds=inds, tags=[f'{self.val}', tag])) - def on_classical_vals(self, *, val: Optional[int] = None) -> Dict[str, Union[int, sympy.Expr]]: if self.state: assert val is None @@ -405,6 +372,9 @@ class IntState(_IntVector): def __init__(self, val: Union[int, sympy.Expr], bitsize: Union[int, sympy.Expr]): self.__attrs_init__(val=val, bitsize=bitsize, state=True) + def __str__(self): + return f'IntState({self.val})' + @bloq_example def _int_state() -> IntState: @@ -430,6 +400,9 @@ class IntEffect(_IntVector): def __init__(self, val: int, bitsize: int): self.__attrs_init__(val=val, bitsize=bitsize, state=False) + def __str__(self): + return f'IntEffect({self.val})' + @bloq_example def _int_effect() -> IntEffect: diff --git a/qualtran/bloqs/block_encoding/lcu_block_encoding.py b/qualtran/bloqs/block_encoding/lcu_block_encoding.py index 107dc39ef0..a0dbc5c8e3 100644 --- a/qualtran/bloqs/block_encoding/lcu_block_encoding.py +++ b/qualtran/bloqs/block_encoding/lcu_block_encoding.py @@ -354,7 +354,6 @@ def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: SoquetT) -> Dict[str, def _extract_soqs(bloq: Bloq) -> Dict[str, 'SoquetT']: return {reg.name: soqs.pop(reg.name) for reg in bloq.signature.lefts()} - print('xxxx') soqs |= bb.add_d(self.prepare, **_extract_soqs(self.prepare)) soqs |= bb.add_d(self.select, **_extract_soqs(self.select)) soqs |= bb.add_d(self.prepare.adjoint(), **_extract_soqs(self.prepare.adjoint())) diff --git a/qualtran/bloqs/bookkeeping/allocate.py b/qualtran/bloqs/bookkeeping/allocate.py index 9538077395..f7775a8d7b 100644 --- a/qualtran/bloqs/bookkeeping/allocate.py +++ b/qualtran/bloqs/bookkeeping/allocate.py @@ -12,9 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. from functools import cached_property -from typing import Any, Dict, Tuple, TYPE_CHECKING +from typing import Dict, List, Tuple, TYPE_CHECKING -import numpy as np import sympy from attrs import frozen @@ -23,13 +22,13 @@ bloq_example, BloqDocSpec, CompositeBloq, + ConnectionT, DecomposeTypeError, QDType, QUInt, Register, Side, Signature, - SoquetT, ) from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq from qualtran.drawing import directional_text_box, Text, WireSymbol @@ -66,19 +65,17 @@ def adjoint(self) -> 'Bloq': def on_classical_vals(self) -> Dict[str, int]: return {'reg': 0} - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn - data = np.zeros(1 << self.dtype.num_qubits) - data[0] = 1 - tn.add(qtn.Tensor(data=data, inds=(outgoing['reg'],), tags=['Allocate', tag])) + from qualtran.bloqs.basic_gates.z_basis import _ZERO + + return [ + qtn.Tensor(data=_ZERO, inds=[(outgoing['reg'], i)], tags=[str(self)]) + for i in range(self.dtype.num_qubits) + ] def wire_symbol(self, reg: Register, idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': if reg is None: diff --git a/qualtran/bloqs/bookkeeping/cast.py b/qualtran/bloqs/bookkeeping/cast.py index 92c5fe62f4..7dce2a3d65 100644 --- a/qualtran/bloqs/bookkeeping/cast.py +++ b/qualtran/bloqs/bookkeeping/cast.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. from functools import cached_property -from typing import Any, Dict, Tuple, TYPE_CHECKING +from typing import Dict, List, Tuple, TYPE_CHECKING import attrs import numpy as np @@ -23,12 +23,12 @@ bloq_example, BloqDocSpec, CompositeBloq, + ConnectionT, DecomposeTypeError, QDType, Register, Side, Signature, - SoquetT, ) from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq @@ -81,23 +81,17 @@ def signature(self) -> Signature: def adjoint(self) -> 'Bloq': return Cast(inp_dtype=self.out_dtype, out_dtype=self.inp_dtype) - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn - tn.add( + return [ qtn.Tensor( - data=np.eye(2**self.inp_dtype.num_qubits, 2**self.inp_dtype.num_qubits), - inds=[outgoing['reg']] + [incoming['reg']], - tags=['Cast', tag], + data=np.eye(2), inds=[(outgoing['reg'], j), (incoming['reg'], j)], tags=[str(self)] ) - ) + for j in range(self.inp_dtype.num_qubits) + ] def on_classical_vals(self, reg: int) -> Dict[str, 'ClassicalValT']: # TODO: Actually cast the values https://github.com/quantumlib/Qualtran/issues/734 diff --git a/qualtran/bloqs/bookkeeping/cast_test.py b/qualtran/bloqs/bookkeeping/cast_test.py index 6fd50c6bef..94885e39db 100644 --- a/qualtran/bloqs/bookkeeping/cast_test.py +++ b/qualtran/bloqs/bookkeeping/cast_test.py @@ -26,9 +26,8 @@ def test_cast(bloq_autotester): def test_cast_tensor_contraction(): bloq = TestCastToFrom() - tn, _ = cbloq_to_quimb(bloq.decompose_bloq()) - assert len(tn.tensors) == 3 - assert tn.shape == (2**4,) * 4 + tn = cbloq_to_quimb(bloq.as_composite_bloq().flatten()) + assert tn.shape == (2,) * 4 * 4 def test_cast_classical_sim(): diff --git a/qualtran/bloqs/bookkeeping/free.py b/qualtran/bloqs/bookkeeping/free.py index 2aea1c11b9..f00851cd3c 100644 --- a/qualtran/bloqs/bookkeeping/free.py +++ b/qualtran/bloqs/bookkeeping/free.py @@ -13,9 +13,8 @@ # limitations under the License. from functools import cached_property -from typing import Any, Dict, Tuple, TYPE_CHECKING +from typing import Dict, List, Tuple, TYPE_CHECKING -import numpy as np import sympy from attrs import frozen @@ -24,13 +23,13 @@ bloq_example, BloqDocSpec, CompositeBloq, + ConnectionT, DecomposeTypeError, QDType, QUInt, Register, Side, Signature, - SoquetT, ) from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq from qualtran.drawing import directional_text_box, Text, WireSymbol @@ -75,19 +74,17 @@ def on_classical_vals(self, reg: int) -> Dict[str, 'ClassicalValT']: raise ValueError(f"Tried to free a non-zero register: {reg}.") return {} - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn - data = np.zeros(1 << self.dtype.num_qubits) - data[0] = 1 - tn.add(qtn.Tensor(data=data, inds=(incoming['reg'],), tags=['Free', tag])) + from qualtran.bloqs.basic_gates.z_basis import _ZERO + + return [ + qtn.Tensor(data=_ZERO, inds=[(incoming['reg'], i)], tags=[str(self)]) + for i in range(self.dtype.num_qubits) + ] def wire_symbol(self, reg: Register, idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': if reg is None: diff --git a/qualtran/bloqs/bookkeeping/join.py b/qualtran/bloqs/bookkeeping/join.py index 743c112d32..49d2674cb2 100644 --- a/qualtran/bloqs/bookkeeping/join.py +++ b/qualtran/bloqs/bookkeeping/join.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. from functools import cached_property -from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING +from typing import cast, Dict, List, Optional, Tuple, TYPE_CHECKING import numpy as np from attrs import field, frozen @@ -22,6 +22,7 @@ bloq_example, BloqDocSpec, CompositeBloq, + ConnectionT, DecomposeTypeError, QBit, QDType, @@ -29,7 +30,6 @@ Register, Side, Signature, - SoquetT, ) from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq from qualtran.drawing import directional_text_box, Text, WireSymbol @@ -81,27 +81,18 @@ def adjoint(self) -> 'Bloq': def as_cirq_op(self, qubit_manager, reg: 'CirqQuregT') -> Tuple[None, Dict[str, 'CirqQuregT']]: return None, {'reg': reg.reshape(self.dtype.num_qubits)} - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn - if not isinstance(incoming['reg'], np.ndarray): - raise ValueError('Incoming register must be a numpy array') - tn.add( - qtn.Tensor( - data=np.eye(2**self.dtype.num_qubits, 2**self.dtype.num_qubits).reshape( - (2,) * self.dtype.num_qubits + (2**self.dtype.num_qubits,) - ), - inds=incoming['reg'].tolist() + [outgoing['reg']], - tags=['Join', tag], - ) - ) + eye = np.eye(2) + incoming = cast('NDArray', incoming['reg']) + outgoing = outgoing['reg'] + return [ + qtn.Tensor(data=eye, inds=[(outgoing, i), (incoming[i], 0)], tags=[str(self)]) + for i in range(self.dtype.num_qubits) + ] def on_classical_vals(self, reg: 'NDArray[np.uint]') -> Dict[str, int]: return {'reg': bits_to_ints(reg)[0]} diff --git a/qualtran/bloqs/bookkeeping/partition.py b/qualtran/bloqs/bookkeeping/partition.py index 8a5560eb03..599b21806f 100644 --- a/qualtran/bloqs/bookkeeping/partition.py +++ b/qualtran/bloqs/bookkeeping/partition.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. from functools import cached_property -from typing import Any, Dict, Tuple, TYPE_CHECKING +from typing import Dict, List, Tuple, TYPE_CHECKING import attrs import numpy as np @@ -22,12 +22,12 @@ bloq_example, BloqDocSpec, CompositeBloq, + ConnectionT, DecomposeTypeError, QAny, Register, Side, Signature, - SoquetT, ) from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq from qualtran.drawing import directional_text_box, Text, WireSymbol @@ -87,38 +87,28 @@ def as_cirq_op(self, qubit_manager, **cirq_quregs) -> Tuple[None, Dict[str, 'Cir else: return None, {'x': np.concatenate([v.ravel() for _, v in cirq_quregs.items()])} - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn - unitary_shape = [] - soquets = [] - _incoming = incoming if self.partition else outgoing - _outgoing = outgoing if self.partition else incoming + grouped = incoming['x'] if self.partition else outgoing['x'] + partitioned = outgoing if self.partition else incoming + + partitioned_inds = [] for reg in self.regs: - for i in range(int(np.prod(reg.shape))): - unitary_shape.append(2**reg.bitsize) - outgoing_reg = _outgoing[reg.name] - if isinstance(outgoing_reg, np.ndarray): - soquets.append(outgoing_reg.ravel()[i]) - else: - soquets.append(outgoing_reg) - - tn.add( - qtn.Tensor( - data=np.eye(2**self.n, 2**self.n).reshape( - tuple(unitary_shape) + (2**self.n,) - ), - inds=soquets + [_incoming['x']], - tags=['Partition', tag], - ) - ) + part_ind = partitioned[reg.name] + for idx in reg.all_idxs(): + for j in range(reg.bitsize): + if isinstance(part_ind, np.ndarray): + partitioned_inds.append((part_ind[idx], j)) + else: + partitioned_inds.append((part_ind, j)) + + return [ + qtn.Tensor(data=np.eye(2), inds=[partitioned_inds[j], (grouped, j)], tags=[str(self)]) + for j in range(self.n) + ] def _classical_partition(self, x: 'ClassicalValT') -> Dict[str, 'ClassicalValT']: out_vals = {} diff --git a/qualtran/bloqs/bookkeeping/partition_test.py b/qualtran/bloqs/bookkeeping/partition_test.py index 8e7f548133..3c97cc465d 100644 --- a/qualtran/bloqs/bookkeeping/partition_test.py +++ b/qualtran/bloqs/bookkeeping/partition_test.py @@ -66,9 +66,12 @@ def test_partition_wrapper(): def test_partition_wrapper_tensor_contract(): bloq = TestPartition(test_bloq=TestMultiRegister()) - tn, _ = cbloq_to_quimb(bloq.decompose_bloq()) - assert len(tn.tensors) == 3 - assert tn.shape == (4096, 4096) + tn = cbloq_to_quimb(bloq.as_composite_bloq().flatten()) + assert tn.shape == (2,) * (2 * int(np.log2(4096))) + + tn = tn.rank_simplify() + for tensor in tn.tensors: + assert np.prod(tensor.shape) <= 2**4 def test_partition_wrapper_as_cirq_op(): diff --git a/qualtran/bloqs/bookkeeping/split.py b/qualtran/bloqs/bookkeeping/split.py index 14fef7a15b..f73a8adc80 100644 --- a/qualtran/bloqs/bookkeeping/split.py +++ b/qualtran/bloqs/bookkeeping/split.py @@ -13,16 +13,18 @@ # limitations under the License. from functools import cached_property -from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING +from typing import cast, Dict, List, Optional, Tuple, TYPE_CHECKING import numpy as np from attrs import field, frozen +from numpy.typing import NDArray from qualtran import ( Bloq, bloq_example, BloqDocSpec, CompositeBloq, + ConnectionT, DecomposeTypeError, QBit, QDType, @@ -30,7 +32,6 @@ Register, Side, Signature, - SoquetT, ) from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq from qualtran.drawing import directional_text_box, Text, WireSymbol @@ -89,27 +90,18 @@ def as_cirq_op(self, qubit_manager, reg: 'CirqQuregT') -> Tuple[None, Dict[str, def on_classical_vals(self, reg: int) -> Dict[str, 'ClassicalValT']: return {'reg': ints_to_bits(np.array([reg]), self.dtype.num_qubits)[0]} - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn - if not isinstance(outgoing['reg'], np.ndarray): - raise ValueError('Outgoing register must be a numpy array') - tn.add( - qtn.Tensor( - data=np.eye(2**self.dtype.num_qubits, 2**self.dtype.num_qubits).reshape( - (2,) * self.dtype.num_qubits + (2**self.dtype.num_qubits,) - ), - inds=outgoing['reg'].tolist() + [incoming['reg']], - tags=['Split', tag], - ) - ) + eye = np.eye(2) + incoming = incoming['reg'] + outgoing = cast(NDArray, outgoing['reg']) + return [ + qtn.Tensor(data=eye, inds=[(outgoing[i], 0), (incoming, i)], tags=[str(self)]) + for i in range(self.dtype.num_qubits) + ] def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': if reg is None: diff --git a/qualtran/bloqs/for_testing/atom.py b/qualtran/bloqs/for_testing/atom.py index 2a6515dac2..c7781083b0 100644 --- a/qualtran/bloqs/for_testing/atom.py +++ b/qualtran/bloqs/for_testing/atom.py @@ -13,13 +13,20 @@ # limitations under the License. from functools import cached_property -from typing import Any, Dict, Optional, TYPE_CHECKING +from typing import Dict, List, Optional, TYPE_CHECKING import attrs import numpy as np from attrs import frozen -from qualtran import Bloq, CompositeBloq, DecomposeTypeError, GateWithRegisters, Signature, SoquetT +from qualtran import ( + Bloq, + CompositeBloq, + ConnectionT, + DecomposeTypeError, + GateWithRegisters, + Signature, +) from qualtran.cirq_interop.t_complexity_protocol import TComplexity if TYPE_CHECKING: @@ -46,23 +53,18 @@ def signature(self) -> Signature: def decompose_bloq(self) -> 'CompositeBloq': raise DecomposeTypeError(f"{self} is atomic") - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn - tn.add( + return [ qtn.Tensor( data=np.array([[0, 1], [1, 0]]), - inds=(outgoing['q'], incoming['q']), - tags=[self.pretty_name(), tag], + inds=[(outgoing['q'], 0), (incoming['q'], 0)], + tags=[str(self)], ) - ) + ] def _t_complexity_(self) -> 'TComplexity': return TComplexity(100) @@ -92,26 +94,26 @@ def signature(self) -> Signature: def decompose_bloq(self) -> 'CompositeBloq': raise DecomposeTypeError(f"{self} is atomic") - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn _I = [[1, 0], [0, 1]] _NULL = [[0, 0], [0, 0]] _X = [[0, 1], [1, 0]] - tn.add( + return [ qtn.Tensor( data=np.array([[_I, _NULL], [_NULL, _X]]), - inds=(outgoing['ctrl'], incoming['ctrl'], outgoing['target'], incoming['target']), - tags=[self.pretty_name(), tag], + inds=[ + (outgoing['ctrl'], 0), + (incoming['ctrl'], 0), + (outgoing['target'], 0), + (incoming['target'], 0), + ], + tags=[str(self)], ) - ) + ] def pretty_name(self) -> str: return 'TestTwoBitOp' @@ -140,23 +142,12 @@ def signature(self) -> Signature: def decompose_bloq(self) -> 'CompositeBloq': raise DecomposeTypeError(f"{self} is atomic") - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): - import quimb.tensor as qtn + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: + from qualtran.cirq_interop._cirq_to_bloq import _my_tensors_from_gate - tn.add( - qtn.Tensor( - data=self._unitary_(), - inds=(outgoing['q'], incoming['q']), - tags=[self.pretty_name(), tag], - ) - ) + return _my_tensors_from_gate(self, self.signature, incoming=incoming, outgoing=outgoing) def _unitary_(self): return np.eye(2) diff --git a/qualtran/bloqs/for_testing/matrix_gate.py b/qualtran/bloqs/for_testing/matrix_gate.py index 459ee246ba..8fc7b6d40c 100644 --- a/qualtran/bloqs/for_testing/matrix_gate.py +++ b/qualtran/bloqs/for_testing/matrix_gate.py @@ -11,12 +11,15 @@ # 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 Tuple +from typing import Dict, List, Tuple, TYPE_CHECKING import numpy as np from attrs import field, frozen -from qualtran import GateWithRegisters, Signature +from qualtran import ConnectionT, GateWithRegisters, Signature + +if TYPE_CHECKING: + import quimb.tensor as qtn @frozen @@ -54,6 +57,21 @@ def random(cls, bitsize: int, *, random_state=None) -> 'MatrixGate': matrix = random_unitary(2**bitsize, random_state=random_state) return cls(bitsize, matrix) + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: + import quimb.tensor as qtn + + data = np.array(self.matrix).reshape((2,) * (self.bitsize * 2)) + return [ + qtn.Tensor( + data=data, + inds=[(outgoing['q'], j) for j in range(self.bitsize)] + + [(incoming['q'], j) for j in range(self.bitsize)], + tags=[str(self)], + ) + ] + def _unitary_(self): return np.array(self.matrix) diff --git a/qualtran/bloqs/for_testing/matrix_gate_test.py b/qualtran/bloqs/for_testing/matrix_gate_test.py index 3b04e87663..7aa95ce11c 100644 --- a/qualtran/bloqs/for_testing/matrix_gate_test.py +++ b/qualtran/bloqs/for_testing/matrix_gate_test.py @@ -25,3 +25,4 @@ def test_create_and_unitary(bitsize: int): gate = MatrixGate.random(bitsize, random_state=random_state) np.testing.assert_allclose(cirq.unitary(gate), gate.matrix) np.testing.assert_allclose(cirq.unitary(gate**-1), np.conj(gate.matrix).T) + np.testing.assert_allclose(gate.tensor_contract(), gate.matrix) diff --git a/qualtran/bloqs/mcmt/and_bloq.py b/qualtran/bloqs/mcmt/and_bloq.py index 3b93ce2419..1f9cbb5728 100644 --- a/qualtran/bloqs/mcmt/and_bloq.py +++ b/qualtran/bloqs/mcmt/and_bloq.py @@ -23,7 +23,7 @@ """ import itertools from functools import cached_property -from typing import Any, Dict, Iterable, Iterator, Optional, Set, Tuple, TYPE_CHECKING, Union +from typing import cast, Dict, Iterable, Iterator, List, Optional, Set, Tuple, TYPE_CHECKING, Union import attrs import cirq @@ -37,12 +37,12 @@ bloq_example, BloqDocSpec, CompositeBloq, + ConnectionT, GateWithRegisters, QBit, Register, Side, Signature, - Soquet, ) from qualtran.bloqs.basic_gates import TGate, XGate from qualtran.bloqs.bookkeeping import ArbitraryClifford @@ -119,14 +119,9 @@ def on_classical_vals( assert target == out return {'ctrl': ctrl} - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, NDArray[Soquet]], # type: ignore[type-var] - outgoing: Dict[str, NDArray[Soquet]], # type: ignore[type-var] - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn # Fill in our tensor using "and" logic. @@ -137,25 +132,24 @@ def add_my_tensors( else: data[c1, c2, c1, c2, 0] = 1 - # Here: uncompute just switches the direction of the target index. - if self.uncompute: - trg = incoming['target'] - else: - trg = outgoing['target'] + # uncompute just switches the direction of the target index. + trg = incoming['target'] if self.uncompute else outgoing['target'] - tn.add( + in_ctrls = cast(NDArray, incoming['ctrl']) + out_ctrls = cast(NDArray, outgoing['ctrl']) + return [ qtn.Tensor( data=data, - inds=( - incoming['ctrl'][0], - incoming['ctrl'][1], - outgoing['ctrl'][0], - outgoing['ctrl'][1], - trg, - ), - tags=['And', tag], + inds=[ + (in_ctrls[0], 0), + (in_ctrls[1], 0), + (out_ctrls[0], 0), + (out_ctrls[1], 0), + (trg, 0), + ], + tags=[str(self)], ) - ) + ] def pretty_name(self) -> str: dag = '†' if self.uncompute else '' diff --git a/qualtran/bloqs/mcmt/and_bloq_test.py b/qualtran/bloqs/mcmt/and_bloq_test.py index 654c42d96b..70d853579f 100644 --- a/qualtran/bloqs/mcmt/and_bloq_test.py +++ b/qualtran/bloqs/mcmt/and_bloq_test.py @@ -66,9 +66,9 @@ def test_truth_table(cv1, cv2): for cbloq, a, b in _iter_and_truth_table(cv1, cv2): vec = cbloq.tensor_contract() if (a == cv1) and (b == cv2): - np.testing.assert_allclose([0, 1], vec) + np.testing.assert_allclose([0, 1], vec, atol=1e-8) else: - np.testing.assert_allclose([1, 0], vec) + np.testing.assert_allclose([1, 0], vec, atol=1e-8) @pytest.mark.parametrize('cv2', [0, 1]) diff --git a/qualtran/bloqs/mcmt/multi_control_multi_target_pauli.py b/qualtran/bloqs/mcmt/multi_control_multi_target_pauli.py index 823e7affe3..4501a9b2d4 100644 --- a/qualtran/bloqs/mcmt/multi_control_multi_target_pauli.py +++ b/qualtran/bloqs/mcmt/multi_control_multi_target_pauli.py @@ -13,7 +13,7 @@ # limitations under the License. from functools import cached_property -from typing import Any, Dict, Iterator, Set, Tuple, TYPE_CHECKING, Union +from typing import Dict, Iterator, Set, Tuple, TYPE_CHECKING, Union import cirq import numpy as np @@ -38,8 +38,6 @@ from qualtran.symbolics import HasLength, SymbolicInt if TYPE_CHECKING: - import quimb.tensor as qtn - from qualtran.resource_counting import BloqCountT, SympySymbolAllocator from qualtran.simulation.classical_sim import ClassicalValT @@ -216,20 +214,6 @@ def _apply_unitary_(self, args: 'cirq.ApplyUnitaryArgs') -> np.ndarray: ) return cirq.apply_unitary(cpauli, args) - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): - from qualtran.cirq_interop._cirq_to_bloq import _add_my_tensors_from_gate - - _add_my_tensors_from_gate( - self, self.signature, self.pretty_name(), tn, tag, incoming=incoming, outgoing=outgoing - ) - def _has_unitary_(self) -> bool: return not is_symbolic(self.n_ctrls) diff --git a/qualtran/bloqs/phase_estimation/lp_resource_state_test.py b/qualtran/bloqs/phase_estimation/lp_resource_state_test.py index 96cf7420b6..2e374d929c 100644 --- a/qualtran/bloqs/phase_estimation/lp_resource_state_test.py +++ b/qualtran/bloqs/phase_estimation/lp_resource_state_test.py @@ -29,6 +29,7 @@ ignore_alloc_free, ignore_split_join, ) +from qualtran.simulation.tensor import initialize_from_zero def test_lprs_interim_auto(bloq_autotester): @@ -57,20 +58,72 @@ def get_resource_state(m: int) -> np.ndarray: return np.sqrt(2 / (1 + N)) * np.sin(np.pi * (1 + np.arange(N)) / (1 + N)) +def test_intermediate_resource_state_cirq_quick(): + n = 3 + bloq = LPRSInterimPrep(n) + state = GateHelper(bloq).circuit.final_state_vector() + np.testing.assert_allclose(state, get_interim_resource_state(n)) + + +def test_intermediate_resource_state_tensor_quick(): + n = 3 + bloq = LPRSInterimPrep(n) + state_prep = initialize_from_zero(bloq) + state_vec = state_prep.tensor_contract() + pytest.xfail("https://github.com/quantumlib/Qualtran/issues/1068") + np.testing.assert_allclose(state_vec, get_interim_resource_state(n)) + + +@pytest.mark.slow @pytest.mark.parametrize('n', [*range(1, 14, 2)]) -def test_intermediate_resource_state(n): +def test_intermediate_resource_state_cirq(n): bloq = LPRSInterimPrep(n) state = GateHelper(bloq).circuit.final_state_vector() np.testing.assert_allclose(state, get_interim_resource_state(n)) +@pytest.mark.slow +@pytest.mark.parametrize('n', [*range(1, 14, 2)]) +def test_intermediate_resource_state_tensor(n): + bloq = LPRSInterimPrep(n) + state_prep = initialize_from_zero(bloq) + state_vec = state_prep.tensor_contract() + pytest.xfail("https://github.com/quantumlib/Qualtran/issues/1068") + np.testing.assert_allclose(state_vec, get_interim_resource_state(n)) + + +def test_prepares_resource_state_cirq_quick(): + n = 3 + bloq = LPResourceState(n) + state = GateHelper(bloq).circuit.final_state_vector() + np.testing.assert_allclose(state, get_resource_state(n)) + + +def test_prepares_resource_state_tensor_quick(): + n = 3 + bloq = LPResourceState(n) + state_prep = initialize_from_zero(bloq) + state_vec = state_prep.tensor_contract() + np.testing.assert_allclose(state_vec, get_resource_state(n)) + + +@pytest.mark.slow @pytest.mark.parametrize('n', [*range(1, 14, 2)]) -def test_prepares_resource_state(n): +def test_prepares_resource_state_cirq(n): bloq = LPResourceState(n) state = GateHelper(bloq).circuit.final_state_vector() np.testing.assert_allclose(state, get_resource_state(n)) +@pytest.mark.slow +@pytest.mark.parametrize('n', [*range(1, 14, 2)]) +def test_prepares_resource_state_tensor(n): + bloq = LPResourceState(n) + state_prep = initialize_from_zero(bloq) + state_vec = state_prep.tensor_contract() + np.testing.assert_allclose(state_vec, get_resource_state(n)) + + @pytest.mark.parametrize('n', [*range(1, 14, 2)]) def test_t_complexity(n): bloq = LPResourceState(n) diff --git a/qualtran/bloqs/qft/two_bit_ffft.py b/qualtran/bloqs/qft/two_bit_ffft.py index e9db23e480..08a4d008d7 100644 --- a/qualtran/bloqs/qft/two_bit_ffft.py +++ b/qualtran/bloqs/qft/two_bit_ffft.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. from functools import cached_property -from typing import Any, Dict, Set, TYPE_CHECKING +from typing import Dict, List, Set, TYPE_CHECKING import numpy as np from attrs import frozen @@ -23,6 +23,7 @@ bloq_example, BloqBuilder, BloqDocSpec, + ConnectionT, QBit, Register, Signature, @@ -88,27 +89,21 @@ def signature(self) -> Signature: def pretty_name(self) -> str: return 'F(k, n)' - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: import quimb.tensor as qtn - out_inds = [outgoing['x'], outgoing['y']] - in_inds = [incoming['x'], incoming['y']] + # TODO: https://github.com/quantumlib/Qualtran/issues/873. This tensor definition + # isn't used by default since this isn't (yet) a "leaf bloq". + + out_inds = [(outgoing['x'], 0), (outgoing['y'], 0)] + in_inds = [(incoming['x'], 0), (incoming['y'], 0)] matrix = _fkn_matrix(self.k, self.n) matrix = matrix.conj().T if self.is_adjoint else matrix - tn.add( - qtn.Tensor( - data=matrix.reshape((2,) * 4), - inds=out_inds + in_inds, - tags=[self.pretty_name(), tag], - ) - ) + return [ + qtn.Tensor(data=matrix.reshape((2,) * 4), inds=out_inds + in_inds, tags=[str(self)]) + ] def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: return { diff --git a/qualtran/bloqs/qft/two_bit_ffft_test.py b/qualtran/bloqs/qft/two_bit_ffft_test.py index b45bf726e0..1fd5edf1be 100644 --- a/qualtran/bloqs/qft/two_bit_ffft_test.py +++ b/qualtran/bloqs/qft/two_bit_ffft_test.py @@ -14,7 +14,6 @@ import itertools import cirq -import numpy as np import pytest from qualtran import Bloq @@ -41,7 +40,5 @@ def all_t(bloq) -> Bloq: @pytest.mark.parametrize('k,n', itertools.product(range(0, 4), range(1, 4))) def test_tensors(k, n): # Eq. E11 in https://arxiv.org/pdf/2012.09238.pdf - from_tensors = TwoBitFFFT(k, n).tensor_contract() - np.testing.assert_allclose(from_tensors, _fkn_matrix(k, n)) from_decomp = TwoBitFFFT(k, n).decompose_bloq().tensor_contract() cirq.testing.assert_allclose_up_to_global_phase(from_decomp, _fkn_matrix(k, n), atol=1e-12) diff --git a/qualtran/bloqs/rotations/phase_gradient.py b/qualtran/bloqs/rotations/phase_gradient.py index 7d951de2ca..9b075dad59 100644 --- a/qualtran/bloqs/rotations/phase_gradient.py +++ b/qualtran/bloqs/rotations/phase_gradient.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. from functools import cached_property -from typing import Any, Dict, Iterator, Optional, Sequence, Set, Tuple, TYPE_CHECKING, Union +from typing import Dict, Iterator, List, Optional, Sequence, Set, Tuple, TYPE_CHECKING, Union import attrs import cirq @@ -22,7 +22,7 @@ from fxpmath import Fxp from numpy.typing import NDArray -from qualtran import GateWithRegisters, QBit, QFxp, Register, Side, Signature +from qualtran import ConnectionT, GateWithRegisters, QBit, QFxp, Register, Side, Signature from qualtran.bloqs.basic_gates import Hadamard, Toffoli from qualtran.bloqs.basic_gates.on_each import OnEach from qualtran.bloqs.basic_gates.rotation import CZPowGate, ZPowGate @@ -30,7 +30,7 @@ if TYPE_CHECKING: import quimb.tensor as qtn - from qualtran import Bloq, SoquetT + from qualtran import Bloq from qualtran.resource_counting import BloqCountT, SympySymbolAllocator from qualtran.simulation.classical_sim import ClassicalValT from qualtran.symbolics import SymbolicFloat, SymbolicInt @@ -263,19 +263,12 @@ def __pow__(self, power): return self.adjoint() raise NotImplementedError("AddIntoPhaseGrad.__pow__ defined only for powers +1/-1.") - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): - from qualtran.cirq_interop._cirq_to_bloq import _add_my_tensors_from_gate - - _add_my_tensors_from_gate( - self, self.signature, self.pretty_name(), tn, tag, incoming=incoming, outgoing=outgoing - ) + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: + from qualtran.cirq_interop._cirq_to_bloq import _my_tensors_from_gate + + return _my_tensors_from_gate(self, self.signature, incoming=incoming, outgoing=outgoing) def _fxp(x: float, n: 'SymbolicInt') -> Fxp: @@ -453,17 +446,3 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: num_additions_naive += 1 num_additions = min(num_additions_naive, num_additions) return {(AddIntoPhaseGrad(self.x_dtype.bitsize, self.phase_bitsize), num_additions)} - - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): - from qualtran.cirq_interop._cirq_to_bloq import _add_my_tensors_from_gate - - _add_my_tensors_from_gate( - self, self.signature, self.pretty_name(), tn, tag, incoming=incoming, outgoing=outgoing - ) diff --git a/qualtran/bloqs/rotations/phasing_via_cost_function_test.py b/qualtran/bloqs/rotations/phasing_via_cost_function_test.py index dbd71c47cb..ebb8bdc189 100644 --- a/qualtran/bloqs/rotations/phasing_via_cost_function_test.py +++ b/qualtran/bloqs/rotations/phasing_via_cost_function_test.py @@ -18,14 +18,17 @@ import numpy as np import pytest -from qualtran import Bloq, BloqBuilder, GateWithRegisters, Register, Signature, SoquetT +from qualtran import Bloq, BloqBuilder, BloqError, GateWithRegisters, Register, Signature, SoquetT from qualtran._infra.data_types import QFxp from qualtran.bloqs.arithmetic.hamming_weight import HammingWeightCompute from qualtran.bloqs.arithmetic.multiplication import Square from qualtran.bloqs.basic_gates import Hadamard from qualtran.bloqs.basic_gates.on_each import OnEach from qualtran.bloqs.rotations.phase_gradient import PhaseGradientState -from qualtran.bloqs.rotations.phasing_via_cost_function import PhasingViaCostFunction +from qualtran.bloqs.rotations.phasing_via_cost_function import ( + _square_via_zpow_phasing, + PhasingViaCostFunction, +) from qualtran.bloqs.rotations.quantum_variable_rotation import ( QvrInterface, QvrPhaseGradient, @@ -35,6 +38,10 @@ from qualtran.testing import assert_valid_bloq_decomposition +def test_square_via_zpow_phasing(bloq_autotester): + bloq_autotester(_square_via_zpow_phasing) + + @attrs.frozen class TestHammingWeightPhasing(GateWithRegisters): bitsize: int @@ -191,5 +198,9 @@ def test_square_phasing_via_phase_gradient( a = bb.add(OnEach(n, Hadamard()), q=a) a = bb.add(test_bloq, a=a) cbloq = bb.finalize(a=a) - hw_final_state = cbloq.tensor_contract() + try: + flat_cbloq = cbloq.flatten() + except BloqError: + pytest.xfail("https://github.com/quantumlib/Qualtran/issues/1069") + hw_final_state = flat_cbloq.tensor_contract() np.testing.assert_allclose(expected_final_state, hw_final_state, atol=eps) diff --git a/qualtran/cirq_interop/_bloq_to_cirq_test.py b/qualtran/cirq_interop/_bloq_to_cirq_test.py index 9d9ab0f2e4..c149bdfb23 100644 --- a/qualtran/cirq_interop/_bloq_to_cirq_test.py +++ b/qualtran/cirq_interop/_bloq_to_cirq_test.py @@ -11,14 +11,14 @@ # 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 Any, Dict, Tuple, TYPE_CHECKING +from typing import Dict, List, Tuple, TYPE_CHECKING import cirq import numpy as np import pytest from attrs import frozen -from qualtran import Bloq, BloqBuilder, Signature, Soquet, SoquetT +from qualtran import Bloq, BloqBuilder, ConnectionT, Signature, Soquet, SoquetT from qualtran._infra.gate_with_registers import get_named_qubits from qualtran.bloqs.basic_gates import Toffoli, XGate from qualtran.bloqs.factoring import ModExp @@ -44,17 +44,12 @@ def as_cirq_op( (y,) = y return cirq.SWAP(x, y), {'x': np.array([x]), 'y': np.array([y])} - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: from qualtran.bloqs.basic_gates import TwoBitSwap - TwoBitSwap().add_my_tensors(tn, tag, incoming=incoming, outgoing=outgoing) + return TwoBitSwap().my_tensors(incoming=incoming, outgoing=outgoing) def test_swap_two_bits_to_cirq(): @@ -94,15 +89,19 @@ class SwapTestWithOnlyTensorData(Bloq): def signature(self): return Signature.build(x=self.n, y=self.n) - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): - SwapTest(self.n).add_my_tensors(tn, tag, incoming=incoming, outgoing=outgoing) + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: + import quimb.tensor as qtn + + from qualtran.simulation.tensor._dense import _order_incoming_outgoing_indices + + inds = _order_incoming_outgoing_indices( + self.signature, incoming=incoming, outgoing=outgoing + ) + data = SwapTest(self.n).tensor_contract().reshape((2,) * len(inds)) + + return [qtn.Tensor(data=data, inds=inds)] @pytest.mark.parametrize('n', [1, 2, 3, 4]) diff --git a/qualtran/cirq_interop/_cirq_to_bloq.py b/qualtran/cirq_interop/_cirq_to_bloq.py index 004e1f9508..19415234f0 100644 --- a/qualtran/cirq_interop/_cirq_to_bloq.py +++ b/qualtran/cirq_interop/_cirq_to_bloq.py @@ -28,6 +28,7 @@ Bloq, BloqBuilder, CompositeBloq, + ConnectionT, Controlled, CtrlSpec, DecomposeNotImplementedError, @@ -102,22 +103,11 @@ def decompose_from_registers( except TypeError as e: raise DecomposeNotImplementedError(f"{self} does not declare a decomposition.") from e - def add_my_tensors( - self, - tn: 'qtn.TensorNetwork', - tag: Any, - *, - incoming: Dict[str, 'SoquetT'], - outgoing: Dict[str, 'SoquetT'], - ): - _add_my_tensors_from_gate( - self.cirq_gate, - self.signature, - str(self.cirq_gate), - tn=tn, - tag=tag, - incoming=incoming, - outgoing=outgoing, + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: + return _my_tensors_from_gate( + self.cirq_gate, self.signature, incoming=incoming, outgoing=outgoing ) def _t_complexity_(self) -> 'TComplexity': @@ -239,6 +229,39 @@ def _add_my_tensors_from_gate( tn.add(qtn.Tensor(data=unitary, inds=outgoing_list + incoming_list, tags=[short_name, tag])) +def _my_tensors_from_gate( + gate: cirq.Gate, + signature: Signature, + *, + incoming: Dict[str, 'ConnectionT'], + outgoing: Dict[str, 'ConnectionT'], +) -> List['qtn.Tensor']: + import quimb.tensor as qtn + + from qualtran.simulation.tensor._dense import _order_incoming_outgoing_indices + + if not cirq.has_unitary(gate): + raise NotImplementedError( + f"CirqGateAsBloq.my_tensors is only supported for unitary gates. " f"Found {gate}." + ) + + if any(reg.side != Side.THRU for reg in signature): + raise ValueError( + f"CirqGateAsBloq.my_tensors is only supported for " + f"gates with thru registers. Found {gate}." + ) + + if not signature: + raise ValueError( + f"CirqGateAsBloq.my_tensors requires a non-trivial signature. " f"Found {gate}." + ) + + n = cirq.num_qubits(gate) + unitary = cirq.unitary(gate).reshape((2,) * (2 * n)) + inds = _order_incoming_outgoing_indices(signature, incoming, outgoing) + return [qtn.Tensor(data=unitary, inds=inds, tags=str(gate))] + + @frozen(eq=False) class _QReg: """Used as a container for qubits that form a `Register` of a given bitsize. diff --git a/qualtran/cirq_interop/_cirq_to_bloq_test.py b/qualtran/cirq_interop/_cirq_to_bloq_test.py index 3e7c2d2a3f..64a7c3087d 100644 --- a/qualtran/cirq_interop/_cirq_to_bloq_test.py +++ b/qualtran/cirq_interop/_cirq_to_bloq_test.py @@ -89,7 +89,7 @@ def test_cirq_gate_as_bloq_tensor_contract_for_and_gate(): state_vector = cbloq.tensor_contract() assert np.isclose(state_vector[7], 1) - with pytest.raises(NotImplementedError, match="supported only for unitary gates"): + with pytest.raises(NotImplementedError, match=r".*only supported for unitary gates.*"): _ = CirqGateAsBloq(And(uncompute=True)).as_composite_bloq().tensor_contract() diff --git a/qualtran/simulation/xcheck_classical_quimb.ipynb b/qualtran/simulation/xcheck_classical_quimb.ipynb index 634f904167..80376caa3c 100644 --- a/qualtran/simulation/xcheck_classical_quimb.ipynb +++ b/qualtran/simulation/xcheck_classical_quimb.ipynb @@ -26,7 +26,9 @@ "outputs": [], "source": [ "from qualtran.drawing import show_bloq\n", - "from qualtran.simulation.xcheck_classical_quimb import flank_with_classical_vectors" + "from qualtran.simulation.xcheck_classical_quimb import flank_with_classical_vectors\n", + "\n", + "import numpy as np" ] }, { @@ -184,15 +186,73 @@ { "cell_type": "code", "execution_count": null, - "id": "82e374f9", + "id": "4f88f54d-abac-4545-87ef-ba1ff7f46048", "metadata": {}, "outputs": [], "source": [ "add_tt = flank_with_classical_vectors(add, {'a': 2, 'b': 3})\n", - "\n", - "assert add_tt.tensor_contract() == 1.0\n", "show_bloq(add_tt, type='musical_score')" ] + }, + { + "cell_type": "markdown", + "id": "0df5eced-4382-4c2c-af94-03d31118bd23", + "metadata": {}, + "source": [ + "Since `Add` is composed of other bloqs, we can contract the factorized network that comes from the \"flattened\" circuit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84a00ba8-455e-434b-b618-8ba44183bc8d", + "metadata": {}, + "outputs": [], + "source": [ + "add_flat = add_tt.as_composite_bloq().flatten()\n", + "\n", + "from qualtran.drawing import get_musical_score_data, draw_musical_score\n", + "draw_musical_score(get_musical_score_data(add_flat), max_width=20, max_height=100)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "176355a9-6477-4932-b696-6dcc15dcd39d", + "metadata": {}, + "outputs": [], + "source": [ + "np.testing.assert_allclose(add_tt.tensor_contract(), 1.0)\n", + "np.testing.assert_allclose(add_flat.tensor_contract(), 1.0)" + ] + }, + { + "cell_type": "markdown", + "id": "bdb1f3da-0837-40cd-a810-be3bc9bd4638", + "metadata": {}, + "source": [ + "### Large bitsize\n", + "\n", + "Since we're using Quimb to find an efficient contraction ordering, we can handle large-width, bounded-depth tensor networks." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b569db6-c6cf-46ad-a625-7c0c72b307a5", + "metadata": {}, + "outputs": [], + "source": [ + "add_large = Add(QUInt(bitsize=32))\n", + "add_large_tt = flank_with_classical_vectors(add_large, {'a': 5555, 'b': 6666})\n", + "add_large_flat = add_large_tt.flatten()\n", + "\n", + "from qualtran.simulation.tensor import cbloq_to_quimb_2\n", + "tn = cbloq_to_quimb_2(add_large_flat)\n", + "\n", + "print(f\"We only use {tn.contraction_width()} qubit's worth of RAM for a {add_large.signature.n_qubits()}-qubit gate.\")\n", + "tn.contract()" + ] } ], "metadata": { @@ -211,7 +271,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.11.8" } }, "nbformat": 4, diff --git a/qualtran/simulation/xcheck_classical_quimb_test.py b/qualtran/simulation/xcheck_classical_quimb_test.py index 4e73b036ec..cc2c07052b 100644 --- a/qualtran/simulation/xcheck_classical_quimb_test.py +++ b/qualtran/simulation/xcheck_classical_quimb_test.py @@ -45,7 +45,7 @@ def test_toffoli(): def test_add(): add = Add(QUInt(bitsize=5)) add_tt = flank_with_classical_vectors(add, {'a': 2, 'b': 3}) - assert add_tt.tensor_contract() == 1.0 + np.testing.assert_allclose(add_tt.tensor_contract(), 1.0) @pytest.mark.notebook