Skip to content

Commit bd97492

Browse files
authored
Bloqify syntax basics (#1839)
"tracing style" bloq construction. This is the basics. More syntactic sugar, docs, and examples to follow.
1 parent 8e493cf commit bd97492

File tree

6 files changed

+431
-17
lines changed

6 files changed

+431
-17
lines changed

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: 86 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,11 @@ 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+
raise BloqError(*be.args) from be
1474+
14581475
if len(outs) == 0:
14591476
return None
14601477
if len(outs) == 1:
@@ -1539,17 +1556,53 @@ def add_from(self, bloq: Bloq, **in_soqs: SoquetInT) -> Tuple['QVarT', ...]:
15391556
fsoqs = _map_soqs(cbloq.final_soqs(), soq_map)
15401557
return tuple(fsoqs[reg.name] for reg in cbloq.signature.rights())
15411558

1559+
def _change_THRU_to_LEFT(self, reg_name: str):
1560+
"""Used during loose `finalize` to force LEFT registers."""
1561+
for reg_i, reg in enumerate(self._regs):
1562+
if reg.name == reg_name:
1563+
break
1564+
else:
1565+
raise AssertionError(f"{reg_name} doesn't exist in the registers.")
1566+
1567+
if reg.side != Side.THRU:
1568+
raise ValueError(f"{reg} is supposed to be a THRU register.")
1569+
1570+
new_reg = attrs.evolve(reg, side=Side.LEFT)
1571+
1572+
# Replace in `self._available`
1573+
soqs_to_replace = []
1574+
for soq in self._available:
1575+
if soq.binst is LeftDangle and soq.reg == reg:
1576+
soqs_to_replace.append(soq)
1577+
for soq in soqs_to_replace:
1578+
self._available.remove(soq)
1579+
self._available.add(attrs.evolve(soq, reg=new_reg))
1580+
1581+
# Replace in `self._cxns`
1582+
for j in range(len(self._cxns)):
1583+
cxn = self._cxns[j]
1584+
if cxn.left.reg == reg:
1585+
new_cxn = attrs.evolve(cxn, left=attrs.evolve(cxn.left, reg=new_reg))
1586+
self._cxns[j] = new_cxn
1587+
1588+
# Replace in `self._regs`
1589+
self._regs[reg_i] = new_reg
1590+
15421591
def finalize(self, **final_soqs: SoquetInT) -> CompositeBloq:
15431592
"""Finish building a CompositeBloq and return the immutable CompositeBloq.
15441593
15451594
This method is similar to calling `add()` but instead of adding a new Bloq,
15461595
it configures the final "dangling" soquets that serve as the outputs for
15471596
the composite bloq as a whole.
15481597
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.
1598+
If `self.add_registers_allowed` is set to `False`, the kwqargs to this method must
1599+
exactly match the signature configured for this bloq builder.
1600+
Otherwise:
1601+
- surplus arguments (with register names not in our signature) will be interpreted as
1602+
output quantum variables and we'll add a corresponding RIGHT register.
1603+
- missing arguments (i.e. a register name was introduced somewhere but
1604+
no quantum variable was provided for it), we will set that register to be a
1605+
LEFT register.
15531606
15541607
Args:
15551608
**final_soqs: Keyword arguments mapping the composite bloq's register names to
@@ -1577,6 +1630,12 @@ def _infer_shaped_dtype(soq: SoquetT) -> Tuple['QCDType', Tuple[int, ...]]:
15771630
dtype, shape = _infer_shaped_dtype(np.asarray(soq))
15781631
self._regs.append(Register(name=name, dtype=dtype, shape=shape, side=Side.RIGHT))
15791632

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

15821641
def _finalize_strict(self, **final_soqs: SoquetInT) -> CompositeBloq:
@@ -1592,16 +1651,25 @@ def _fin(idxed_soq: _QVar, reg: Register, idx: Tuple[int, ...]):
15921651
# close over `RightDangle`
15931652
return self._add_cxn(RightDangle, idxed_soq, reg, idx)
15941653

1654+
if self._bloq_key:
1655+
debug_str = f'finalization of {self._bloq_key}'
1656+
else:
1657+
debug_str = 'finalization'
15951658
_process_soquets(
1596-
registers=signature.rights(), debug_str='Finalizing', in_soqs=final_soqs, func=_fin
1659+
registers=signature.rights(), debug_str=debug_str, in_soqs=final_soqs, func=_fin
15971660
)
15981661
if self._available:
1599-
raise BloqError(
1600-
f"During finalization, {self._available} Soquets were not used."
1601-
) from None
1662+
if self._bloq_key is None:
1663+
ctx = ''
1664+
else:
1665+
ctx = f' of {self._bloq_key}'
1666+
raise BloqError(f"During finalization{ctx}, {self._available} Soquets were not used.")
16021667

16031668
return CompositeBloq(
1604-
connections=self._cxns, signature=signature, bloq_instances=self._binsts
1669+
connections=self._cxns,
1670+
signature=signature,
1671+
bloq_instances=self._binsts,
1672+
bloq_key=self._bloq_key,
16051673
)
16061674

16071675
def allocate(
@@ -1653,3 +1721,6 @@ def join(self, soqs: SoquetInT, dtype: Optional[QDType] = None) -> 'Soquet':
16531721
dtype = QAny(n)
16541722

16551723
return self.add(Join(dtype=dtype), reg=soqs)
1724+
1725+
def in_register(self, name: str, dtype: QCDType, shape=()) -> Union[None, QVarT]:
1726+
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)