Qualtran icon indicating copy to clipboard operation
Qualtran copied to clipboard

Quantum Interfaces

Open mpharrigan opened this issue 2 years ago • 1 comments

There may be several constructions for a bloq that ostensibly "does the same thing". How do we define a quantum interface and provide multiple implementations? How do we swap out different implementations?

The "attribute" method involves using a bloq’s class attribute as a sort of enum to swap between different implementations. This has the potential for a combinatorial explosion if you want to be able to swap between different implementations of a low-level gate: each gate that uses it directly or indirectly will need to itself include a class attribute to select it. Instead, we need to develop a graph re-writer that can substitute one bloq for another. This is straightforward if you’ve decomposed your bloq down to the level where the substitution can occur; but if we’re substituting a low-level bloq and using high-level protocols: how do we make sure the protocols remain consistent without ruining the performance of the protocols?

Are any two bloqs with the same signature able to be swapped out? If we add additional data types will that be enough to describe a quantum interface? Can we just use Python’s inheritance system to validate quantum interfaces?

mpharrigan avatar Oct 25 '23 18:10 mpharrigan

For this, I was thinking there could be "decomposition patterns", which represent the result of a single step of a decomposition. For instance, say you've got a physical Add256 gate, so you'd like any logical Add(n>=256) bloqs to decompose into that plus whatever bookend gates when n > 256 is strict inequality. And you'd like Subtract(n>=256) bloqs to decompose similarly.

So ideally you'd be able to define a "decomposition pattern" something like

@decomposition_pattern
class WithAdd256:
  left: Bloq
  adder: Add256
  right: Bloq

Then you'd be able to register decomposition patterns on whatever bloq classes could support it. Something like

@register_decomposition(bloqs.Add, WithAdd256)
def _(add_bloq: bloqs.Add):
  if add_bloq.n >= 256
    return WithAdd256(_Left(add_bloq.n - 256), Add256(), _Right(add_bloq.n - 256))
  return None

@register_decomposition(bloqs.Subtract, WithAdd256)
def _(subtract_bloq: bloqs.Subtract):
  if subtract_bloq.n >= 256
    # Assuming there's some way to convert subtract gate into (left, Add256, right), just as an example
    return WithAdd256(_LeftSubtract(subtract_bloq.n - 256), Add256(), _RightSubtract(subtract_bloq.n - 256))
  return None

These registrations would be stored on the Bloq classes, probably in a private dict[decomposition_class, decomposition_method] field. So it'd allow Bloq classes to have multiple decomposition patterns registered to it. Users can create or register their own patterns, or qualtran could specify and register some common ones.

Then, the nice thing about this approach is that, with a little Python-foo in the decorators, it allows users to use pattern matching to prioritize their decomposition preferences. Like if they wanted to attempt decomposition into the Add256 first, then try an Add128, then try a Toffoli decomposition, and fall back to default, they could do something like the following. Note in the adder blocks, the left and right get further decomposed, but the adder does not. So it gives users more granular control of how to process each step of the decomposition.

def my_decompose(bloq):
  match bloq:
    case WithAdd256(left, adder, right):
      return my_decompose(left) + [adder] + my_decompose(right)
    case WithAdd128(left, adder, right):
      return my_decompose(left) + [adder] + my_decompose(right)
    case OnlyToffolis(toffolis):
      return toffolis
    case _:
      return [my_decompose(subbloq for subbloq in protocols.decompose_once(bloq)]

Curious if something like this is what you were thinking. Note this does nothing to guarantee that the registered decompositions actually match the bloq unitary, it's just a way for users to register and use custom decompositions and compose them flexibly.

daxfohl avatar Oct 03 '25 22:10 daxfohl