Skip to content

Commit b334f47

Browse files
committed
Bloqify syntax basics
1 parent 8e493cf commit b334f47

6 files changed

Lines changed: 432 additions & 17 deletions

File tree

qualtran/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,6 @@
105105

106106
from ._infra.bloq_example import BloqExample, bloq_example, BloqDocSpec
107107

108+
from .bloqify_syntax import bloqify
109+
108110
# --------------------------------------------------------------------------------------------------

qualtran/_infra/composite_bloq.py

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@
6262
if TYPE_CHECKING:
6363
import cirq
6464

65+
import qualtran as qlt
66+
import qualtran.dtype as qdt
6567
from qualtran.bloqs.bookkeeping.auto_partition import Unused
6668
from qualtran.cirq_interop._cirq_to_bloq import CirqQuregInT, CirqQuregT
6769
from qualtran.drawing import WireSymbol
@@ -467,7 +469,7 @@ def final_soqs(self) -> Dict[str, _SoquetT]:
467469

468470
def copy(self) -> 'CompositeBloq':
469471
"""Create a copy of this composite bloq by re-building it."""
470-
bb, _ = BloqBuilder.from_signature(self.signature)
472+
bb, _ = BloqBuilder.from_signature(self.signature, bloq_key=self.bloq_key)
471473
soq_map = bb.initial_soq_map(self.signature.lefts())
472474

473475
for binst, in_soqs, old_out_soqs in self.iter_bloqsoqs():
@@ -641,6 +643,11 @@ def __str__(self):
641643
return self.bloq_key
642644
return f'CompositeBloq([{len(self.bloq_instances)} subbloqs...])'
643645

646+
def __repr__(self):
647+
if self.bloq_key is not None:
648+
return f'CompositeBloq(..., bloq_key={self.bloq_key!r})'
649+
return f'CompositeBloq([{len(self.bloq_instances)} subbloqs...])'
650+
644651

645652
def _create_binst_graph(
646653
cxns: Iterable[Connection], nodes: Iterable[BloqInstance] = ()
@@ -1098,7 +1105,7 @@ def build_composite_bloq(self, bb: BloqBuilder, q0, q1):
10981105
by the framework or by the `BloqBuilder.from_signature(s)` factory method.
10991106
"""
11001107

1101-
def __init__(self, add_registers_allowed: bool = True):
1108+
def __init__(self, add_registers_allowed: bool = True, *, bloq_key: Optional[str] = None):
11021109
# To be appended to:
11031110
self._cxns: List[Connection] = []
11041111
self._regs: List[Register] = []
@@ -1113,6 +1120,8 @@ def __init__(self, add_registers_allowed: bool = True):
11131120
# Whether we can call `add_register` and do non-strict `finalize()`.
11141121
self.add_register_allowed = add_registers_allowed
11151122

1123+
self._bloq_key = bloq_key
1124+
11161125
def add_register_from_dtype(
11171126
self, reg: Union[str, Register], dtype: Optional[QCDType] = None
11181127
) -> Union[None, QVarT]:
@@ -1135,7 +1144,7 @@ def add_register_from_dtype(
11351144
from qualtran.symbolics import is_symbolic
11361145

11371146
if not self.add_register_allowed:
1138-
raise ValueError(
1147+
raise BloqError(
11391148
"This BloqBuilder was constructed from pre-specified registers. "
11401149
"Ad hoc addition of more registers is not allowed."
11411150
)
@@ -1208,15 +1217,19 @@ def add_register(
12081217

12091218
@classmethod
12101219
def from_signature(
1211-
cls, signature: Signature, add_registers_allowed: bool = False
1220+
cls,
1221+
signature: Signature,
1222+
add_registers_allowed: bool = False,
1223+
*,
1224+
bloq_key: Optional[str] = None,
12121225
) -> Tuple['BloqBuilder', Dict[str, QVarT]]:
12131226
"""Construct a BloqBuilder with a pre-specified signature.
12141227
12151228
This is safer if e.g. you're decomposing an existing Bloq and need the signatures
12161229
to match. This constructor is used by `Bloq.decompose_bloq()`.
12171230
"""
12181231
# Initial construction: allow register addition for the following loop.
1219-
bb = cls(add_registers_allowed=True)
1232+
bb = cls(add_registers_allowed=True, bloq_key=bloq_key)
12201233

12211234
initial_soqs: Dict[str, QVarT] = {}
12221235
for reg in signature:
@@ -1454,7 +1467,12 @@ def add(self, bloq: Bloq, **in_soqs: SoquetInT):
14541467
be unpacked with tuple unpacking. In this final case, the ordering is according
14551468
to `bloq.signature` and irrespective of the order of `**in_soqs`.
14561469
"""
1457-
outs = self.add_t(bloq, **in_soqs)
1470+
try:
1471+
outs = self.add_t(bloq, **in_soqs)
1472+
except BloqError as be:
1473+
# Error source shown as `bb.add(...)`
1474+
raise BloqError(*be.args) from None
1475+
14581476
if len(outs) == 0:
14591477
return None
14601478
if len(outs) == 1:
@@ -1539,17 +1557,53 @@ def add_from(self, bloq: Bloq, **in_soqs: SoquetInT) -> Tuple['QVarT', ...]:
15391557
fsoqs = _map_soqs(cbloq.final_soqs(), soq_map)
15401558
return tuple(fsoqs[reg.name] for reg in cbloq.signature.rights())
15411559

1560+
def _change_THRU_to_LEFT(self, reg_name: str):
1561+
"""Used during loose `finalize` to force LEFT registers."""
1562+
for reg_i, reg in enumerate(self._regs):
1563+
if reg.name == reg_name:
1564+
break
1565+
else:
1566+
raise AssertionError(f"{reg_name} doesn't exist in the registers.")
1567+
1568+
if reg.side != Side.THRU:
1569+
raise ValueError(f"{reg} is supposed to be a THRU register.")
1570+
1571+
new_reg = attrs.evolve(reg, side=Side.LEFT)
1572+
1573+
# Replace in `self._available`
1574+
soqs_to_replace = []
1575+
for soq in self._available:
1576+
if soq.binst is LeftDangle and soq.reg == reg:
1577+
soqs_to_replace.append(soq)
1578+
for soq in soqs_to_replace:
1579+
self._available.remove(soq)
1580+
self._available.add(attrs.evolve(soq, reg=new_reg))
1581+
1582+
# Replace in `self._cxns`
1583+
for j in range(len(self._cxns)):
1584+
cxn = self._cxns[j]
1585+
if cxn.left.reg == reg:
1586+
new_cxn = attrs.evolve(cxn, left=attrs.evolve(cxn.left, reg=new_reg))
1587+
self._cxns[j] = new_cxn
1588+
1589+
# Replace in `self._regs`
1590+
self._regs[reg_i] = new_reg
1591+
15421592
def finalize(self, **final_soqs: SoquetInT) -> CompositeBloq:
15431593
"""Finish building a CompositeBloq and return the immutable CompositeBloq.
15441594
15451595
This method is similar to calling `add()` but instead of adding a new Bloq,
15461596
it configures the final "dangling" soquets that serve as the outputs for
15471597
the composite bloq as a whole.
15481598
1549-
If `self.add_registers_allowed` is set to `True`, additional register
1550-
names passed to this function will be added as RIGHT registers. Otherwise,
1551-
this method validates the provided `final_soqs` against our list of RIGHT
1552-
(and THRU) registers.
1599+
If `self.add_registers_allowed` is set to `False`, the kwqargs to this method must
1600+
exactly match the signature configured for this bloq builder.
1601+
Otherwise:
1602+
- surplus arguments (with register names not in our signature) will be interpreted as
1603+
output quantum variables and we'll add a corresponding RIGHT register.
1604+
- missing arguments (i.e. a register name was introduced somewhere but
1605+
no quantum variable was provided for it), we will set that register to be a
1606+
LEFT register.
15531607
15541608
Args:
15551609
**final_soqs: Keyword arguments mapping the composite bloq's register names to
@@ -1577,6 +1631,12 @@ def _infer_shaped_dtype(soq: SoquetT) -> Tuple['QCDType', Tuple[int, ...]]:
15771631
dtype, shape = _infer_shaped_dtype(np.asarray(soq))
15781632
self._regs.append(Register(name=name, dtype=dtype, shape=shape, side=Side.RIGHT))
15791633

1634+
# If a soquet is missing, we're charitable and consider it de-allocated, see docstring.
1635+
deletable_right_reg_names = [reg.name for reg in self._regs if reg.side == Side.THRU]
1636+
for name in deletable_right_reg_names:
1637+
if name not in final_soqs:
1638+
self._change_THRU_to_LEFT(reg_name=name)
1639+
15801640
return self._finalize_strict(**final_soqs)
15811641

15821642
def _finalize_strict(self, **final_soqs: SoquetInT) -> CompositeBloq:
@@ -1592,16 +1652,25 @@ def _fin(idxed_soq: _QVar, reg: Register, idx: Tuple[int, ...]):
15921652
# close over `RightDangle`
15931653
return self._add_cxn(RightDangle, idxed_soq, reg, idx)
15941654

1655+
if self._bloq_key:
1656+
debug_str = f'finalization of {self._bloq_key}'
1657+
else:
1658+
debug_str = 'finalization'
15951659
_process_soquets(
1596-
registers=signature.rights(), debug_str='Finalizing', in_soqs=final_soqs, func=_fin
1660+
registers=signature.rights(), debug_str=debug_str, in_soqs=final_soqs, func=_fin
15971661
)
15981662
if self._available:
1599-
raise BloqError(
1600-
f"During finalization, {self._available} Soquets were not used."
1601-
) from None
1663+
if self._bloq_key is None:
1664+
ctx = ''
1665+
else:
1666+
ctx = f' of {self._bloq_key}'
1667+
raise BloqError(f"During finalization{ctx}, {self._available} Soquets were not used.")
16021668

16031669
return CompositeBloq(
1604-
connections=self._cxns, signature=signature, bloq_instances=self._binsts
1670+
connections=self._cxns,
1671+
signature=signature,
1672+
bloq_instances=self._binsts,
1673+
bloq_key=self._bloq_key,
16051674
)
16061675

16071676
def allocate(
@@ -1653,3 +1722,6 @@ def join(self, soqs: SoquetInT, dtype: Optional[QDType] = None) -> 'Soquet':
16531722
dtype = QAny(n)
16541723

16551724
return self.add(Join(dtype=dtype), reg=soqs)
1725+
1726+
def in_register(self, name: str, dtype: QCDType, shape=()) -> Union[None, QVarT]:
1727+
return self.add_register_from_dtype(Register(name=name, dtype=dtype, shape=shape))

qualtran/_infra/composite_bloq_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ def test_finalize_missing_args():
312312
x2, y2 = bb.add(TestTwoBitOp(), ctrl=x, target=y)
313313

314314
bb.add_register_allowed = False
315-
with pytest.raises(BloqError, match=r"During Finalizing, we expected a value for 'x'\."):
315+
with pytest.raises(BloqError, match=r"During finalization, we expected a value for 'x'\."):
316316
bb.finalize(y=y2)
317317

318318

@@ -321,7 +321,7 @@ def test_finalize_strict_too_many_args():
321321
x2, y2 = bb.add(TestTwoBitOp(), ctrl=x, target=y)
322322

323323
bb.add_register_allowed = False
324-
with pytest.raises(BloqError, match=r'Finalizing does not accept Soquets.*z.*'):
324+
with pytest.raises(BloqError, match=r'finalization does not accept Soquets.*z.*'):
325325
bb.finalize(x=x2, y=y2, z=_Soquet(RightDangle, Register('asdf', QBit())))
326326

327327

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
from ._infra import bloqify

0 commit comments

Comments
 (0)