From eb60089ec5daa4fa8bccd559b53bd15f827c2e2f Mon Sep 17 00:00:00 2001 From: tachard Date: Fri, 13 Feb 2026 19:40:04 +0100 Subject: [PATCH 01/14] Changes Partition to accept an input dtype --- qualtran/bloqs/bookkeeping/partition.py | 69 ++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/qualtran/bloqs/bookkeeping/partition.py b/qualtran/bloqs/bookkeeping/partition.py index 8cebbb5809..1452741b65 100644 --- a/qualtran/bloqs/bookkeeping/partition.py +++ b/qualtran/bloqs/bookkeeping/partition.py @@ -13,12 +13,13 @@ # limitations under the License. import abc from functools import cached_property -from typing import Dict, List, Sequence, Tuple, TYPE_CHECKING +from typing import Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING import numpy as np import sympy from attrs import evolve, field, frozen, validators from numpy.typing import NDArray +import warnings from qualtran import ( Bloq, @@ -28,6 +29,7 @@ ConnectionT, DecomposeTypeError, QAny, + QUInt, QDType, Register, Side, @@ -46,6 +48,26 @@ from qualtran.simulation.classical_sim import ClassicalValT +class LegacyPartitionWarning(DeprecationWarning): + """Warnings for legacy Partition usage, when declaring only n.""" + + pass + + +def _constrain_qany_reg(reg: Register): + """Changes the dtype of a register to note break legacy code + + This function should be bound to dissapear + """ + if isinstance(reg.dtype, QAny): + warnings.warn( + f"Doing classical casting with QAny ({reg=}) is ambiguous, transforming it as QUInt for legacy purposes", + category=LegacyPartitionWarning, + ) + return evolve(reg, dtype=QUInt(reg.dtype.bitsize)) + return reg + + class _PartitionBase(_BookkeepingBloq, metaclass=abc.ABCMeta): """Generalized paritioning functionality.""" @@ -53,9 +75,9 @@ class _PartitionBase(_BookkeepingBloq, metaclass=abc.ABCMeta): @abc.abstractmethod def n(self) -> SymbolicInt: ... - @cached_property - def lumped_dtype(self) -> QDType: - return QAny(bitsize=self.n) + @property + @abc.abstractmethod + def lumped_dtype(self) -> QDType: ... @property @abc.abstractmethod @@ -124,6 +146,7 @@ def _classical_partition(self, x: 'ClassicalValT') -> Dict[str, 'ClassicalValT'] xbits = self.lumped_dtype.to_bits(x) start = 0 for reg in self._regs: + reg = _constrain_qany_reg(reg) size = int(np.prod(reg.shape + (reg.bitsize,))) bits_reg = xbits[start : start + size] if reg.shape == (): @@ -138,6 +161,7 @@ def _classical_partition(self, x: 'ClassicalValT') -> Dict[str, 'ClassicalValT'] def _classical_unpartition_to_bits(self, **vals: 'ClassicalValT') -> NDArray[np.uint8]: out_vals: list[NDArray[np.uint8]] = [] for reg in self._regs: + reg = _constrain_qany_reg(reg) reg_val = np.asanyarray(vals[reg.name]) bitstrings = reg.dtype.to_bits_array(reg_val.ravel()) out_vals.append(bitstrings.ravel()) @@ -168,6 +192,7 @@ class Partition(_PartitionBase): Args: n: The total bitsize of the un-partitioned register regs: Registers to partition into. The `side` attribute is ignored. + dtype_in: Type of the un-partitioned register, this is partition: `False` means un-partition instead. Registers: @@ -175,15 +200,39 @@ class Partition(_PartitionBase): [user spec]: The registers provided by the `regs` argument. RIGHT by default. """ - n: SymbolicInt + n: Optional[SymbolicInt] regs: Tuple[Register, ...] = field( converter=lambda x: x if isinstance(x, tuple) else tuple(x), validator=validators.min_len(1) ) - partition: bool = True + dtype_in: Optional[QDType] = field(default=None) + partition: bool = field(default=True) def __attrs_post_init__(self): + match (self.n, self.dtype_in): + case (None, None): + raise ValueError("Provide exactly n or dtype_in") + case (None, dt): + object.__setattr__(self, "n", dt.num_qubits) + case (n, None): + warnings.warn( + "Partition: By not setting dtype_in you could encounter errors when running " + "assert_consistent_classical_action", + category=LegacyPartitionWarning, + ) + case (n, dt): + if n != dt.num_qubits: + raise ValueError(f"{dt=} should have size {n=}, currently {dt.num_qubits=}") + warnings.warn( + "Specifying both n and dtype_in is redundant", + category=UserWarning, + stacklevel=1, + ) self._validate() + @property + def lumped_dtype(self) -> QDType: + return QUInt(bitsize=self.n) if self.dtype_in is None else self.dtype_in + @property def _regs(self) -> Sequence[Register]: return self.regs @@ -228,6 +277,10 @@ class Split2(_PartitionBase): def n(self) -> SymbolicInt: return self.n1 + self.n2 + @property + def lumped_dtype(self) -> QDType: + return QUInt(bitsize=self.n) + @property def partition(self) -> bool: return True @@ -289,6 +342,10 @@ class Join2(_PartitionBase): def n(self) -> SymbolicInt: return self.n1 + self.n2 + @property + def lumped_dtype(self) -> QDType: + return QUInt(bitsize=self.n) + @property def partition(self) -> bool: return False From 3f3f2a7d9dcae0dbe4fae2809f3d33ad03e12184 Mon Sep 17 00:00:00 2001 From: tachard Date: Fri, 13 Feb 2026 19:44:41 +0100 Subject: [PATCH 02/14] Changes pytest settigs so it ignores LegacyPartitionWarning --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 7b15489557..f8e9768800 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ skip_glob = ["qualtran/protos/*"] [tool.pytest.ini_options] filterwarnings = [ 'ignore::DeprecationWarning:quimb.linalg.approx_spectral:', + 'ignore::qualtran.bloqs.bookkeeping.partition.LegacyPartitionWarning', 'ignore:.*standard platformdirs.*:DeprecationWarning:jupyter_client.*' ] # we define classes like TestBloq etc. which pytest tries to collect, From 2405da0642e575a11402294fc984a8639e75f8fe Mon Sep 17 00:00:00 2001 From: tachard Date: Fri, 13 Feb 2026 20:10:33 +0100 Subject: [PATCH 03/14] Mypy made me add a lot of assertions + pylint --- qualtran/bloqs/bookkeeping/partition.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/qualtran/bloqs/bookkeeping/partition.py b/qualtran/bloqs/bookkeeping/partition.py index 1452741b65..532d00f2ef 100644 --- a/qualtran/bloqs/bookkeeping/partition.py +++ b/qualtran/bloqs/bookkeeping/partition.py @@ -51,8 +51,6 @@ class LegacyPartitionWarning(DeprecationWarning): """Warnings for legacy Partition usage, when declaring only n.""" - pass - def _constrain_qany_reg(reg: Register): """Changes the dtype of a register to note break legacy code @@ -73,7 +71,7 @@ class _PartitionBase(_BookkeepingBloq, metaclass=abc.ABCMeta): @property @abc.abstractmethod - def n(self) -> SymbolicInt: ... + def n(self) -> Optional[SymbolicInt]: ... @property @abc.abstractmethod @@ -120,6 +118,8 @@ def my_tensors( ) -> List['qtn.Tensor']: import quimb.tensor as qtn + if self.n is None: + raise DecomposeTypeError(f"cannot compute tensors with unknown n for {self}") if is_symbolic(self.n): raise DecomposeTypeError(f"cannot compute tensors for symbolic {self}") @@ -211,15 +211,18 @@ def __attrs_post_init__(self): match (self.n, self.dtype_in): case (None, None): raise ValueError("Provide exactly n or dtype_in") - case (None, dt): - object.__setattr__(self, "n", dt.num_qubits) case (n, None): warnings.warn( "Partition: By not setting dtype_in you could encounter errors when running " "assert_consistent_classical_action", category=LegacyPartitionWarning, ) + case (None, dt): + assert dt is not None + object.__setattr__(self, "n", dt.num_qubits) case (n, dt): + assert dt is not None + assert n is not None # for mypy if n != dt.num_qubits: raise ValueError(f"{dt=} should have size {n=}, currently {dt.num_qubits=}") warnings.warn( @@ -231,7 +234,11 @@ def __attrs_post_init__(self): @property def lumped_dtype(self) -> QDType: - return QUInt(bitsize=self.n) if self.dtype_in is None else self.dtype_in + if self.dtype_in is not None: + return self.dtype_in + + assert self.n is not None + return QUInt(bitsize=self.n) @property def _regs(self) -> Sequence[Register]: From a4e79de47a7ccc7db1f72c0b19c40bf3e4142066 Mon Sep 17 00:00:00 2001 From: tachard Date: Sun, 15 Feb 2026 00:15:08 +0100 Subject: [PATCH 04/14] linter --- qualtran/bloqs/bookkeeping/partition.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qualtran/bloqs/bookkeeping/partition.py b/qualtran/bloqs/bookkeeping/partition.py index 532d00f2ef..ed7f103e05 100644 --- a/qualtran/bloqs/bookkeeping/partition.py +++ b/qualtran/bloqs/bookkeeping/partition.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import abc +import warnings from functools import cached_property from typing import Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING @@ -19,7 +20,6 @@ import sympy from attrs import evolve, field, frozen, validators from numpy.typing import NDArray -import warnings from qualtran import ( Bloq, @@ -29,8 +29,8 @@ ConnectionT, DecomposeTypeError, QAny, - QUInt, QDType, + QUInt, Register, Side, Signature, From 4848dae2b1c3d11f473c456bfb479a886d760892 Mon Sep 17 00:00:00 2001 From: tachard Date: Sun, 15 Feb 2026 21:00:23 +0100 Subject: [PATCH 05/14] Adds test + small fixes --- qualtran/bloqs/bookkeeping/partition.py | 30 ++++++++++++++------ qualtran/bloqs/bookkeeping/partition_test.py | 16 +++++++++-- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/qualtran/bloqs/bookkeeping/partition.py b/qualtran/bloqs/bookkeeping/partition.py index ed7f103e05..18791c339f 100644 --- a/qualtran/bloqs/bookkeeping/partition.py +++ b/qualtran/bloqs/bookkeeping/partition.py @@ -66,6 +66,17 @@ def _constrain_qany_reg(reg: Register): return reg +def _regs_to_tuple(x): + if x is None: + return None + return x if isinstance(x, tuple) else tuple(x) + + +def _not_none(_inst, attr, value): + if value is None: + raise ValueError(f"{attr.name} cannot be None") + + class _PartitionBase(_BookkeepingBloq, metaclass=abc.ABCMeta): """Generalized paritioning functionality.""" @@ -190,9 +201,11 @@ class Partition(_PartitionBase): """Partition a generic index into multiple registers. Args: - n: The total bitsize of the un-partitioned register + n: The total bit-size of the un-partitioned register. Required if `dtype_in` is None. + Deprecated. Kept for backward compatibility. Use `dtype_in` instead whenever possible. regs: Registers to partition into. The `side` attribute is ignored. - dtype_in: Type of the un-partitioned register, this is + dtype_in: Type of the un-partitioned register. Required if `n` is None. If None, + the type is inferred as `QUInt(n)`. partition: `False` means un-partition instead. Registers: @@ -200,9 +213,9 @@ class Partition(_PartitionBase): [user spec]: The registers provided by the `regs` argument. RIGHT by default. """ - n: Optional[SymbolicInt] - regs: Tuple[Register, ...] = field( - converter=lambda x: x if isinstance(x, tuple) else tuple(x), validator=validators.min_len(1) + n: Optional[SymbolicInt] = field(default=None) + regs: Optional[Tuple[Register, ...]] = field( + converter=_regs_to_tuple, validator=(_not_none, validators.min_len(1)), default=None ) dtype_in: Optional[QDType] = field(default=None) partition: bool = field(default=True) @@ -218,7 +231,7 @@ def __attrs_post_init__(self): category=LegacyPartitionWarning, ) case (None, dt): - assert dt is not None + assert dt is not None # for mypy object.__setattr__(self, "n", dt.num_qubits) case (n, dt): assert dt is not None @@ -230,6 +243,7 @@ def __attrs_post_init__(self): category=UserWarning, stacklevel=1, ) + self._validate() @property @@ -237,7 +251,7 @@ def lumped_dtype(self) -> QDType: if self.dtype_in is not None: return self.dtype_in - assert self.n is not None + assert self.n is not None # for mypy return QUInt(bitsize=self.n) @property @@ -255,7 +269,7 @@ def signature(self) -> 'Signature': ) def adjoint(self): - return evolve(self, partition=not self.partition) + return evolve(self, n=None, dtype_in=self.lumped_dtype, partition=not self.partition) @frozen diff --git a/qualtran/bloqs/bookkeeping/partition_test.py b/qualtran/bloqs/bookkeeping/partition_test.py index c6c3236332..7be26b864c 100644 --- a/qualtran/bloqs/bookkeeping/partition_test.py +++ b/qualtran/bloqs/bookkeeping/partition_test.py @@ -20,7 +20,7 @@ import pytest from attrs import frozen -from qualtran import Bloq, BloqBuilder, QAny, QGF, Register, Signature, Soquet, SoquetT +from qualtran import Bloq, BloqBuilder, QAny, QGF, QInt, QUInt, Register, Signature, Soquet, SoquetT from qualtran._infra.gate_with_registers import get_named_qubits from qualtran.bloqs.basic_gates import CNOT from qualtran.bloqs.bookkeeping import Partition @@ -37,10 +37,22 @@ def test_partition(bloq_autotester): def test_partition_check(): with pytest.raises(ValueError): _ = Partition(n=0, regs=()) + with pytest.raises(ValueError): + _ = Partition(n=10, regs=None) + with pytest.raises(ValueError): + _ = Partition(dtype_in=QUInt(10)) with pytest.raises(ValueError): _ = Partition(n=1, regs=(Register('x', QAny(2)),)) with pytest.raises(ValueError): _ = Partition(n=4, regs=(Register('x', QAny(1)), Register('x', QAny(3)))) + with pytest.raises(ValueError): + _ = Partition(n=10) + + regs = (Register("xx", QUInt(4)), Register("yy", QInt(6))) + with pytest.raises(ValueError): + _ = Partition(regs=regs) + with pytest.raises(ValueError): + _ = Partition(n=11, regs=regs) @frozen @@ -57,7 +69,7 @@ def signature(self) -> Signature: def build_composite_bloq(self, bb: 'BloqBuilder', test_regs: 'SoquetT') -> Dict[str, 'Soquet']: bloq_regs = self.test_bloq.signature - partition = Partition(self.bitsize, bloq_regs) # type: ignore[arg-type] + partition = Partition(dtype_in=QUInt(self.bitsize), regs=bloq_regs) # type: ignore[arg-type] out_regs = bb.add(partition, x=test_regs) out_regs = bb.add(self.test_bloq, **{reg.name: sp for reg, sp in zip(bloq_regs, out_regs)}) test_regs = bb.add( From d0e067f29ebe17407f3451a999033f72aa8b7ddf Mon Sep 17 00:00:00 2001 From: tachard Date: Sun, 15 Feb 2026 21:38:51 +0100 Subject: [PATCH 06/14] Modified stuff for mypy, cleaner now --- qualtran/bloqs/bookkeeping/partition.py | 50 +++++++++++-------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/qualtran/bloqs/bookkeeping/partition.py b/qualtran/bloqs/bookkeeping/partition.py index 18791c339f..9db5785e34 100644 --- a/qualtran/bloqs/bookkeeping/partition.py +++ b/qualtran/bloqs/bookkeeping/partition.py @@ -14,7 +14,7 @@ import abc import warnings from functools import cached_property -from typing import Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING +from typing import Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING, cast import numpy as np import sympy @@ -221,42 +221,34 @@ class Partition(_PartitionBase): partition: bool = field(default=True) def __attrs_post_init__(self): - match (self.n, self.dtype_in): - case (None, None): - raise ValueError("Provide exactly n or dtype_in") - case (n, None): - warnings.warn( - "Partition: By not setting dtype_in you could encounter errors when running " - "assert_consistent_classical_action", - category=LegacyPartitionWarning, - ) - case (None, dt): - assert dt is not None # for mypy - object.__setattr__(self, "n", dt.num_qubits) - case (n, dt): - assert dt is not None - assert n is not None # for mypy - if n != dt.num_qubits: - raise ValueError(f"{dt=} should have size {n=}, currently {dt.num_qubits=}") - warnings.warn( - "Specifying both n and dtype_in is redundant", - category=UserWarning, - stacklevel=1, + if self.n is None and self.dtype_in is None: + raise ValueError(f"Provide exactly n or dtype_in {self.n=}, {self.dtype_in=}") + elif self.n is not None and self.dtype_in is None: + warnings.warn( + "Partition: By not setting dtype_in you could encounter errors when running " + "assert_consistent_classical_action", + category=LegacyPartitionWarning, + ) + elif self.n is None and self.dtype_in is not None: + object.__setattr__(self, "n", self.dtype_in.num_qubits) + elif self.n is not None and self.dtype_in is not None: + if self.n != self.dtype_in.num_qubits: + raise ValueError( + f"{self.dtype_in=} should have size {self.n=}, currently {self.dtype_in.num_qubits=}" ) + warnings.warn( + "Specifying both n and dtype_in is redundant", category=UserWarning, stacklevel=1 + ) self._validate() @property def lumped_dtype(self) -> QDType: - if self.dtype_in is not None: - return self.dtype_in - - assert self.n is not None # for mypy - return QUInt(bitsize=self.n) + return QUInt(bitsize=cast(SymbolicInt, self.n)) if self.dtype_in is None else self.dtype_in @property def _regs(self) -> Sequence[Register]: - return self.regs + return cast(Tuple[Register, ...], self.regs) @cached_property def signature(self) -> 'Signature': @@ -265,7 +257,7 @@ def signature(self) -> 'Signature': return Signature( [Register('x', self.lumped_dtype, side=lumped)] - + [evolve(reg, side=partitioned) for reg in self.regs] + + [evolve(reg, side=partitioned) for reg in self._regs] ) def adjoint(self): From 10b3e0667825b2afbc8537a1a55de5637173b8fa Mon Sep 17 00:00:00 2001 From: tachard Date: Sun, 15 Feb 2026 21:50:10 +0100 Subject: [PATCH 07/14] linter --- qualtran/bloqs/bookkeeping/partition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qualtran/bloqs/bookkeeping/partition.py b/qualtran/bloqs/bookkeeping/partition.py index 9db5785e34..b0440f25ca 100644 --- a/qualtran/bloqs/bookkeeping/partition.py +++ b/qualtran/bloqs/bookkeeping/partition.py @@ -14,7 +14,7 @@ import abc import warnings from functools import cached_property -from typing import Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING, cast +from typing import cast, Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING import numpy as np import sympy From 8ebb73f65f87be1bd47292166521561007c7cc62 Mon Sep 17 00:00:00 2001 From: tachard Date: Sun, 15 Feb 2026 21:57:04 +0100 Subject: [PATCH 08/14] Notebook change --- qualtran/bloqs/bookkeeping/partition.ipynb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qualtran/bloqs/bookkeeping/partition.ipynb b/qualtran/bloqs/bookkeeping/partition.ipynb index 65ff47688d..9080931b62 100644 --- a/qualtran/bloqs/bookkeeping/partition.ipynb +++ b/qualtran/bloqs/bookkeeping/partition.ipynb @@ -39,8 +39,9 @@ "Partition a generic index into multiple registers.\n", "\n", "#### Parameters\n", - " - `n`: The total bitsize of the un-partitioned register\n", + " - `n`: The total bit-size of the un-partitioned register. Required if `dtype_in` is None. Deprecated. Kept for backward compatibility. Use `dtype_in` instead whenever possible.\n", " - `regs`: Registers to partition into. The `side` attribute is ignored.\n", + " - `dtype_in`: Type of the un-partitioned register. Required if `n` is None. If None, the type is inferred as `QUInt(n)`.\n", " - `partition`: `False` means un-partition instead. \n", "\n", "#### Registers\n", From f8f03592cb1f639c24f7286b195d6795227c0f00 Mon Sep 17 00:00:00 2001 From: tachard Date: Fri, 13 Feb 2026 19:47:40 +0100 Subject: [PATCH 09/14] Changes QAny to have no implicit encoding --- qualtran/_infra/data_types.py | 83 ++++++++++++++++--- qualtran/_infra/data_types_test.py | 28 ++++++- qualtran/bloqs/basic_gates/z_basis.py | 4 +- qualtran/bloqs/bookkeeping/cast.py | 22 ++++- qualtran/bloqs/bookkeeping/join.py | 10 +++ qualtran/bloqs/bookkeeping/split.py | 12 +++ .../qubitization/select_hubbard_test.py | 2 +- qualtran/bloqs/mcmt/ctrl_spec_and.py | 21 ++++- .../tensor/_tensor_from_classical.py | 8 +- 9 files changed, 165 insertions(+), 25 deletions(-) diff --git a/qualtran/_infra/data_types.py b/qualtran/_infra/data_types.py index fe570ea7ee..82669ff85f 100644 --- a/qualtran/_infra/data_types.py +++ b/qualtran/_infra/data_types.py @@ -236,7 +236,7 @@ def assert_valid_classical_val_array(self, val_array: NDArray, debug_str: str = def is_symbolic(self) -> bool: """Returns True if this dtype is parameterized with symbolic objects.""" - return is_symbolic(self._bit_encoding.bitsize) + return is_symbolic(self.num_bits) def iteration_length_or_zero(self) -> SymbolicInt: """Safe version of iteration length. @@ -358,6 +358,76 @@ def __str__(self) -> str: return 'CBit()' +@attrs.frozen +class _Any(BitEncoding[int]): + """Bag of Qubits of a given bitsize. + + Here (and throughout Qualtran), we use a big-endian bit convention. The most significant + bit is at index 0. + """ + + bitsize: SymbolicInt + + def get_domain(self) -> Iterable[int]: + raise TypeError(f"Ambiguous domain for {self}. Please use a more specific type.") + + def to_bits(self, x: int) -> List[int]: + if is_symbolic(self.bitsize): + raise ValueError(f"Cannot compute bits for symbolic {self.bitsize=}") + if x == 0: + return [0] * int(self.bitsize) + + raise TypeError( + f"Ambiguous encoding for {self} when encoding non zero value {x=}. Please use a more specific type." + ) + + def to_bits_array(self, x_array: NDArray[np.integer]) -> NDArray[np.uint8]: # TODO + if is_symbolic(self.bitsize): + raise ValueError(f"Cannot compute bits for symbolic {self.bitsize=}") + + values = np.atleast_1d(x_array) + if values.size == 0: + return np.zeros((values.shape[0], int(self.bitsize)), dtype=np.uint8) + + if not np.all(values == 0): + raise TypeError( + f"Ambiguous encoding for {self} when encoding non zero values {values=}. Please use a more specific type." + ) + + return np.zeros((values.shape[0], int(self.bitsize)), dtype=np.uint8) + + def from_bits(self, bits: Sequence[int]) -> int: + if all(x == 0 for x in bits): + return 0 + + raise TypeError( + f"Ambiguous value for {self} when bits ({bits}) are non zero. Please use a more specific type." + ) + + def from_bits_array(self, bits_array: NDArray[np.uint8]) -> NDArray[np.uint64]: # TODO + bitstrings = np.atleast_2d(bits_array) + if bitstrings.shape[1] != self.bitsize: + raise ValueError(f"Input bitsize {bitstrings.shape[1]} does not match {self.bitsize=}") + + if bitstrings.size == 0: + return np.zeros(bitstrings.shape[0], dtype=np.uint64) + + if not np.all(bitstrings == 0): + raise TypeError( + f"Ambiguous value for {self} when bits are non zero ({bits_array}). Please use a more specific type." + ) + + return np.zeros(bitstrings.shape[0], dtype=np.uint64) + + def assert_valid_val(self, val: int, debug_str: str = 'val') -> None: + pass + + def assert_valid_val_array( + self, val_array: NDArray[np.integer], debug_str: str = 'val' + ) -> None: + pass + + @attrs.frozen class QAny(QDType[Any]): """Opaque bag-of-qubits type.""" @@ -366,7 +436,7 @@ class QAny(QDType[Any]): @property def _bit_encoding(self) -> BitEncoding[Any]: - return _UInt(self.bitsize) + return _Any(self.bitsize) def __attrs_post_init__(self): if is_symbolic(self.bitsize): @@ -375,15 +445,6 @@ def __attrs_post_init__(self): if not isinstance(self.bitsize, int): raise ValueError(f"Bad bitsize for QAny: {self.bitsize}") - def get_classical_domain(self) -> Iterable[Any]: - raise TypeError(f"Ambiguous domain for {self}. Please use a more specific type.") - - def assert_valid_classical_val(self, val: Any, debug_str: str = 'val'): - pass - - def assert_valid_classical_val_array(self, val_array: NDArray, debug_str: str = 'val'): - pass - @attrs.frozen class _Int(BitEncoding[int]): diff --git a/qualtran/_infra/data_types_test.py b/qualtran/_infra/data_types_test.py index 355ed49d57..a9ab9cd227 100644 --- a/qualtran/_infra/data_types_test.py +++ b/qualtran/_infra/data_types_test.py @@ -441,10 +441,32 @@ def test_qbit_to_and_from_bits(): assert_to_and_from_bits_array_consistent(QBit(), [0, 1]) -def test_qany_to_and_from_bits(): - assert list(QAny(4).to_bits(10)) == [1, 0, 1, 0] +def test_qany_to_bits(): + with pytest.raises(TypeError, match=r"Ambiguous encoding"): + QAny(4).to_bits(10) - assert_to_and_from_bits_array_consistent(QAny(4), range(16)) + +def test_qany_from_bits_only_all_zeros(): + assert QAny(4).from_bits([0, 0, 0, 0]) == 0 + + with pytest.raises(TypeError, match=r"Ambiguous value"): + QAny(4).from_bits([1, 0, 0, 0]) + + +def test_qany_to_bits_array(): + enc = QAny(4) + assert np.all(enc.to_bits_array(np.array([0, 0])) == 0) + + with pytest.raises(TypeError, match=r"Ambiguous encoding"): + enc.to_bits_array(np.array([1])) + + +def test_qany_from_bits_array(): + enc = QAny(4) + assert np.all(enc.from_bits_array(np.zeros((2, 4), dtype=np.uint8)) == 0) + + with pytest.raises(TypeError, match=r"Ambiguous value"): + enc.from_bits_array(np.array([[1, 0, 0, 0]], dtype=np.uint8)) def test_qintonescomp_to_and_from_bits(): diff --git a/qualtran/bloqs/basic_gates/z_basis.py b/qualtran/bloqs/basic_gates/z_basis.py index 48231b5d50..a315cb503e 100644 --- a/qualtran/bloqs/basic_gates/z_basis.py +++ b/qualtran/bloqs/basic_gates/z_basis.py @@ -43,7 +43,7 @@ ConnectionT, CtrlSpec, DecomposeTypeError, - QAny, + QUInt, QBit, QDType, Register, @@ -479,7 +479,7 @@ def check(self, attribute, val): def dtype(self) -> QDType: if self.bitsize == 1: return QBit() - return QAny(self.bitsize) + return QUInt(self.bitsize) @cached_property def signature(self) -> Signature: diff --git a/qualtran/bloqs/bookkeeping/cast.py b/qualtran/bloqs/bookkeeping/cast.py index 9fd7bee565..41b95fa5f2 100644 --- a/qualtran/bloqs/bookkeeping/cast.py +++ b/qualtran/bloqs/bookkeeping/cast.py @@ -17,6 +17,7 @@ import attrs import numpy as np from attrs import frozen +import warnings from qualtran import ( Bloq, @@ -26,6 +27,8 @@ CompositeBloq, ConnectionT, DecomposeTypeError, + QAny, + QUInt, QCDType, QDType, Register, @@ -33,6 +36,7 @@ Signature, ) from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq +from qualtran.bloqs.bookkeeping.partition import LegacyPartitionWarning from qualtran.symbolics import is_symbolic if TYPE_CHECKING: @@ -120,7 +124,23 @@ def my_tensors( ] def on_classical_vals(self, reg: int) -> Dict[str, 'ClassicalValT']: - res = self.out_dtype.from_bits(self.inp_dtype.to_bits(reg)) + if isinstance(self.inp_dtype, QAny) or isinstance(self.out_dtype, QAny): + warnings.warn( + "Doing classical casting with QAny is ambiguous, transforming it as QUInt for legacy purposes", + category=LegacyPartitionWarning, + ) + match (self.inp_dtype, self.out_dtype): + case (QAny(), _): + res = self.out_dtype.from_bits(QUInt(self.inp_dtype.bitsize).to_bits(reg)) + case (_, QAny()): + res = QUInt(self.out_dtype.bitsize).from_bits(self.inp_dtype.to_bits(reg)) + case (QAny(), QAny()): + res = QUInt(self.out_dtype.bitsize).from_bits( + QUInt(self.inp_dtype.bitsize).to_bits(reg) + ) + case _: + res = self.out_dtype.from_bits(self.inp_dtype.to_bits(reg)) + return {'reg': res} def as_cirq_op(self, qubit_manager, reg: 'CirqQuregT') -> Tuple[None, Dict[str, 'CirqQuregT']]: diff --git a/qualtran/bloqs/bookkeeping/join.py b/qualtran/bloqs/bookkeeping/join.py index b194add90d..7fe1f03e27 100644 --- a/qualtran/bloqs/bookkeeping/join.py +++ b/qualtran/bloqs/bookkeeping/join.py @@ -16,9 +16,11 @@ import numpy as np from attrs import field, frozen +import warnings from qualtran import ( Bloq, + QAny, bloq_example, BloqDocSpec, CompositeBloq, @@ -32,6 +34,7 @@ Signature, ) from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq +from qualtran.bloqs.bookkeeping.partition import LegacyPartitionWarning from qualtran.drawing import directional_text_box, Text, WireSymbol if TYPE_CHECKING: @@ -99,6 +102,13 @@ def my_tensors( ] def on_classical_vals(self, reg: 'NDArray[np.uint]') -> Dict[str, int]: + if isinstance(self.dtype, QAny): + warnings.warn( + "Doing classical operations with QAny is ambiguous, returning a QUInt for legacy purposes", + category=LegacyPartitionWarning, + ) + return {'reg': QUInt(self.dtype.bitsize).from_bits(reg.tolist())} + return {'reg': self.dtype.from_bits(reg.tolist())} def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': diff --git a/qualtran/bloqs/bookkeeping/split.py b/qualtran/bloqs/bookkeeping/split.py index ab02e2e6f4..be4f82fcd8 100644 --- a/qualtran/bloqs/bookkeeping/split.py +++ b/qualtran/bloqs/bookkeeping/split.py @@ -18,6 +18,8 @@ import numpy as np from attrs import field, frozen from numpy.typing import NDArray +import warnings + from qualtran import ( Bloq, @@ -27,6 +29,8 @@ ConnectionT, DecomposeTypeError, QBit, + QAny, + QUInt, QDType, QUInt, Register, @@ -34,6 +38,7 @@ Signature, ) from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq +from qualtran.bloqs.bookkeeping.partition import LegacyPartitionWarning from qualtran.drawing import directional_text_box, Text, WireSymbol if TYPE_CHECKING: @@ -92,6 +97,13 @@ def as_pl_op(self, wires: 'Wires') -> 'Operation': return None def on_classical_vals(self, reg: int) -> Dict[str, 'ClassicalValT']: + if isinstance(self.dtype, QAny): + warnings.warn( + "Doing classical operations with QAny is ambiguous, returning a QUInt for legacy purposes", + category=LegacyPartitionWarning, + ) + return {'reg': np.asarray(QUInt(self.dtype.bitsize).to_bits(reg))} + return {'reg': np.asarray(self.dtype.to_bits(reg))} def my_tensors( diff --git a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py index c3307b5f56..5d7caedae8 100644 --- a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py +++ b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py @@ -124,7 +124,7 @@ def test_hubbard_spin_up_z_classical(): onehot[the_x + M * the_y] = 1 # The bloqs deal with one monolithic target register. - system = QAny(2 * N).from_bits(all_ones + onehot) + system = QUInt(2 * N).from_bits(all_ones + onehot) # Go through all possible x,y selection indices and see if a phase is applied. negative_phases = [] diff --git a/qualtran/bloqs/mcmt/ctrl_spec_and.py b/qualtran/bloqs/mcmt/ctrl_spec_and.py index 3fa7edd03c..e4ebb29b73 100644 --- a/qualtran/bloqs/mcmt/ctrl_spec_and.py +++ b/qualtran/bloqs/mcmt/ctrl_spec_and.py @@ -16,7 +16,8 @@ import numpy as np import sympy -from attrs import frozen +from attrs import frozen, evolve +import warnings from qualtran import ( Bloq, @@ -44,6 +45,7 @@ if TYPE_CHECKING: from qualtran import BloqBuilder, SoquetT from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator + from qualtran.bloqs.bookkeeping.partition import LegacyPartitionWarning @frozen @@ -125,9 +127,20 @@ def _flat_cvs(self) -> Union[tuple[int, ...], HasLength]: return HasLength(self.n_ctrl_qubits) flat_cvs: list[int] = [] - for reg, cv in zip(self.control_registers, self.ctrl_spec.cvs): - assert isinstance(cv, np.ndarray) - flat_cvs.extend(reg.dtype.to_bits_array(cv.ravel()).ravel()) + for reg, cvs in zip(self.control_registers, self.ctrl_spec.cvs): + assert isinstance(cvs, np.ndarray) + + if isinstance(reg.dtype, QAny) and not np.all(cvs == 0): + warnings.warn( + f"Asking for a non zero controll value ({cvs}) for a QAny ({reg=}) is ambiguous, " + "transforming QAny in QUInt for legacy purposes", + category=LegacyPartitionWarning, + ) + reg = evolve(reg, dtype=QUInt(reg.dtype.bitsize)) + else: + print(reg.dtype, cvs) + + flat_cvs.extend(reg.dtype.to_bits_array(cvs.ravel()).ravel()) return tuple(flat_cvs) def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> dict[str, 'SoquetT']: diff --git a/qualtran/simulation/tensor/_tensor_from_classical.py b/qualtran/simulation/tensor/_tensor_from_classical.py index bf87b44b32..253071dc9f 100644 --- a/qualtran/simulation/tensor/_tensor_from_classical.py +++ b/qualtran/simulation/tensor/_tensor_from_classical.py @@ -16,11 +16,13 @@ import numpy as np from numpy.typing import NDArray +from qualtran.bloqs.bookkeeping.partition import _constrain_qany_reg if TYPE_CHECKING: import quimb.tensor as qtn - from qualtran import Bloq, ConnectionT, Register + from qualtran import Bloq, ConnectionT, Register, QAny, QUInt + from qualtran.bloqs.bookkeeping.partition import LegacyPartitionWarning from qualtran.simulation.classical_sim import ClassicalValT @@ -55,7 +57,7 @@ def _bloq_to_dense_via_classical_action(bloq: 'Bloq') -> NDArray: assert np.size(last) == 0 input_kwargs = { - reg.name: _bits_to_classical_reg_data(reg, bits) + reg.name: _bits_to_classical_reg_data(_constrain_qany_reg(reg), bits) for reg, bits in zip(bloq.signature.lefts(), inputs_t) } output_args = bloq.call_classically(**input_kwargs) @@ -63,7 +65,7 @@ def _bloq_to_dense_via_classical_action(bloq: 'Bloq') -> NDArray: if output_args: output_t = np.concatenate( [ - reg.dtype.to_bits_array(np.asarray(vals)).flat + _constrain_qany_reg(reg).dtype.to_bits_array(np.asarray(vals)).flat for reg, vals in zip(bloq.signature.rights(), output_args) ] ) From 9984b1e200f7a8c848ed9006b75eb29d7f71b2e2 Mon Sep 17 00:00:00 2001 From: tachard Date: Fri, 13 Feb 2026 20:14:43 +0100 Subject: [PATCH 10/14] mypy + pylint --- qualtran/bloqs/basic_gates/z_basis.py | 2 +- qualtran/bloqs/bookkeeping/cast.py | 4 ++-- qualtran/bloqs/bookkeeping/join.py | 4 ++-- qualtran/bloqs/bookkeeping/split.py | 6 ++---- qualtran/bloqs/mcmt/ctrl_spec_and.py | 6 +++--- qualtran/simulation/tensor/_tensor_from_classical.py | 3 ++- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/qualtran/bloqs/basic_gates/z_basis.py b/qualtran/bloqs/basic_gates/z_basis.py index a315cb503e..a2e7c54327 100644 --- a/qualtran/bloqs/basic_gates/z_basis.py +++ b/qualtran/bloqs/basic_gates/z_basis.py @@ -43,9 +43,9 @@ ConnectionT, CtrlSpec, DecomposeTypeError, - QUInt, QBit, QDType, + QUInt, Register, Side, Signature, diff --git a/qualtran/bloqs/bookkeeping/cast.py b/qualtran/bloqs/bookkeeping/cast.py index 41b95fa5f2..535a5387b8 100644 --- a/qualtran/bloqs/bookkeeping/cast.py +++ b/qualtran/bloqs/bookkeeping/cast.py @@ -11,13 +11,13 @@ # 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 warnings from functools import cached_property from typing import Dict, List, Tuple, TYPE_CHECKING import attrs import numpy as np from attrs import frozen -import warnings from qualtran import ( Bloq, @@ -28,9 +28,9 @@ ConnectionT, DecomposeTypeError, QAny, - QUInt, QCDType, QDType, + QUInt, Register, Side, Signature, diff --git a/qualtran/bloqs/bookkeeping/join.py b/qualtran/bloqs/bookkeeping/join.py index 7fe1f03e27..f2fe52cb93 100644 --- a/qualtran/bloqs/bookkeeping/join.py +++ b/qualtran/bloqs/bookkeeping/join.py @@ -11,21 +11,21 @@ # 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 warnings from functools import cached_property from typing import cast, Dict, List, Optional, Tuple, TYPE_CHECKING import numpy as np from attrs import field, frozen -import warnings from qualtran import ( Bloq, - QAny, bloq_example, BloqDocSpec, CompositeBloq, ConnectionT, DecomposeTypeError, + QAny, QBit, QDType, QUInt, diff --git a/qualtran/bloqs/bookkeeping/split.py b/qualtran/bloqs/bookkeeping/split.py index be4f82fcd8..485f2752fa 100644 --- a/qualtran/bloqs/bookkeeping/split.py +++ b/qualtran/bloqs/bookkeeping/split.py @@ -12,14 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import warnings from functools import cached_property from typing import cast, Dict, List, Optional, Tuple, TYPE_CHECKING import numpy as np from attrs import field, frozen from numpy.typing import NDArray -import warnings - from qualtran import ( Bloq, @@ -28,9 +27,8 @@ CompositeBloq, ConnectionT, DecomposeTypeError, - QBit, QAny, - QUInt, + QBit, QDType, QUInt, Register, diff --git a/qualtran/bloqs/mcmt/ctrl_spec_and.py b/qualtran/bloqs/mcmt/ctrl_spec_and.py index e4ebb29b73..772325a303 100644 --- a/qualtran/bloqs/mcmt/ctrl_spec_and.py +++ b/qualtran/bloqs/mcmt/ctrl_spec_and.py @@ -11,13 +11,13 @@ # 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 warnings from functools import cached_property from typing import Optional, TYPE_CHECKING, Union import numpy as np import sympy -from attrs import frozen, evolve -import warnings +from attrs import evolve, frozen from qualtran import ( Bloq, @@ -44,8 +44,8 @@ if TYPE_CHECKING: from qualtran import BloqBuilder, SoquetT - from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.bloqs.bookkeeping.partition import LegacyPartitionWarning + from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator @frozen diff --git a/qualtran/simulation/tensor/_tensor_from_classical.py b/qualtran/simulation/tensor/_tensor_from_classical.py index 253071dc9f..4d4c84681c 100644 --- a/qualtran/simulation/tensor/_tensor_from_classical.py +++ b/qualtran/simulation/tensor/_tensor_from_classical.py @@ -16,12 +16,13 @@ import numpy as np from numpy.typing import NDArray + from qualtran.bloqs.bookkeeping.partition import _constrain_qany_reg if TYPE_CHECKING: import quimb.tensor as qtn - from qualtran import Bloq, ConnectionT, Register, QAny, QUInt + from qualtran import Bloq, ConnectionT, QAny, QUInt, Register from qualtran.bloqs.bookkeeping.partition import LegacyPartitionWarning from qualtran.simulation.classical_sim import ClassicalValT From 1e3990f70f127435062f37a43bc1a246f71ce079 Mon Sep 17 00:00:00 2001 From: tachard Date: Mon, 16 Feb 2026 10:18:05 +0100 Subject: [PATCH 11/14] Small changes --- qualtran/_infra/data_types.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qualtran/_infra/data_types.py b/qualtran/_infra/data_types.py index 82669ff85f..6652b10191 100644 --- a/qualtran/_infra/data_types.py +++ b/qualtran/_infra/data_types.py @@ -236,7 +236,7 @@ def assert_valid_classical_val_array(self, val_array: NDArray, debug_str: str = def is_symbolic(self) -> bool: """Returns True if this dtype is parameterized with symbolic objects.""" - return is_symbolic(self.num_bits) + return is_symbolic(self._bit_encoding.bitsize) def iteration_length_or_zero(self) -> SymbolicInt: """Safe version of iteration length. @@ -381,7 +381,7 @@ def to_bits(self, x: int) -> List[int]: f"Ambiguous encoding for {self} when encoding non zero value {x=}. Please use a more specific type." ) - def to_bits_array(self, x_array: NDArray[np.integer]) -> NDArray[np.uint8]: # TODO + def to_bits_array(self, x_array: NDArray[np.integer]) -> NDArray[np.uint8]: if is_symbolic(self.bitsize): raise ValueError(f"Cannot compute bits for symbolic {self.bitsize=}") @@ -404,7 +404,7 @@ def from_bits(self, bits: Sequence[int]) -> int: f"Ambiguous value for {self} when bits ({bits}) are non zero. Please use a more specific type." ) - def from_bits_array(self, bits_array: NDArray[np.uint8]) -> NDArray[np.uint64]: # TODO + def from_bits_array(self, bits_array: NDArray[np.uint8]) -> NDArray[np.uint64]: bitstrings = np.atleast_2d(bits_array) if bitstrings.shape[1] != self.bitsize: raise ValueError(f"Input bitsize {bitstrings.shape[1]} does not match {self.bitsize=}") From 57d3bd587eba0ac89864172affe9d5f73983bacd Mon Sep 17 00:00:00 2001 From: tachard Date: Mon, 16 Feb 2026 10:38:29 +0100 Subject: [PATCH 12/14] Small change 2 --- qualtran/bloqs/mcmt/ctrl_spec_and.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qualtran/bloqs/mcmt/ctrl_spec_and.py b/qualtran/bloqs/mcmt/ctrl_spec_and.py index 772325a303..db1a19cd50 100644 --- a/qualtran/bloqs/mcmt/ctrl_spec_and.py +++ b/qualtran/bloqs/mcmt/ctrl_spec_and.py @@ -137,8 +137,6 @@ def _flat_cvs(self) -> Union[tuple[int, ...], HasLength]: category=LegacyPartitionWarning, ) reg = evolve(reg, dtype=QUInt(reg.dtype.bitsize)) - else: - print(reg.dtype, cvs) flat_cvs.extend(reg.dtype.to_bits_array(cvs.ravel()).ravel()) return tuple(flat_cvs) From 7ad61bbbe6dfef1cff3d4a1b19663d1f6d0b02c5 Mon Sep 17 00:00:00 2001 From: tachard Date: Mon, 16 Feb 2026 11:22:45 +0100 Subject: [PATCH 13/14] linter --- .../chemistry/hubbard_model/qubitization/select_hubbard_test.py | 2 +- qualtran/bloqs/mcmt/ctrl_spec_and.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py index 5d7caedae8..6cde7fca4c 100644 --- a/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py +++ b/qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py @@ -17,7 +17,7 @@ import pytest import qualtran.testing as qlt_testing -from qualtran import QAny, QUInt +from qualtran import QUInt from qualtran.bloqs.chemistry.hubbard_model.qubitization import ( HubbardMajorannaOperator, HubbardSpinUpZ, diff --git a/qualtran/bloqs/mcmt/ctrl_spec_and.py b/qualtran/bloqs/mcmt/ctrl_spec_and.py index db1a19cd50..30e3467d8f 100644 --- a/qualtran/bloqs/mcmt/ctrl_spec_and.py +++ b/qualtran/bloqs/mcmt/ctrl_spec_and.py @@ -41,10 +41,10 @@ from qualtran.drawing import directional_text_box, Text, WireSymbol from qualtran.resource_counting.generalizers import ignore_split_join from qualtran.symbolics import HasLength, is_symbolic, SymbolicInt +from qualtran.bloqs.bookkeeping.partition import LegacyPartitionWarning if TYPE_CHECKING: from qualtran import BloqBuilder, SoquetT - from qualtran.bloqs.bookkeeping.partition import LegacyPartitionWarning from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator From 0728c694ec3f206401fee3834594834d5ee6aed3 Mon Sep 17 00:00:00 2001 From: tachard Date: Mon, 16 Feb 2026 11:52:32 +0100 Subject: [PATCH 14/14] linter bis --- qualtran/bloqs/mcmt/ctrl_spec_and.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qualtran/bloqs/mcmt/ctrl_spec_and.py b/qualtran/bloqs/mcmt/ctrl_spec_and.py index 30e3467d8f..138dce22d1 100644 --- a/qualtran/bloqs/mcmt/ctrl_spec_and.py +++ b/qualtran/bloqs/mcmt/ctrl_spec_and.py @@ -36,12 +36,11 @@ Side, Signature, ) -from qualtran.bloqs.bookkeeping.partition import Partition +from qualtran.bloqs.bookkeeping.partition import LegacyPartitionWarning, Partition from qualtran.bloqs.mcmt.and_bloq import And, MultiAnd from qualtran.drawing import directional_text_box, Text, WireSymbol from qualtran.resource_counting.generalizers import ignore_split_join from qualtran.symbolics import HasLength, is_symbolic, SymbolicInt -from qualtran.bloqs.bookkeeping.partition import LegacyPartitionWarning if TYPE_CHECKING: from qualtran import BloqBuilder, SoquetT