Skip to content

Commit

Permalink
Merge branch 'main' into fix-t-complexity-for-gqsp-examples
Browse files Browse the repository at this point in the history
  • Loading branch information
anurudhp authored May 14, 2024
2 parents 278354c + a4a0f92 commit c8479c7
Show file tree
Hide file tree
Showing 19 changed files with 1,134 additions and 233 deletions.
55 changes: 42 additions & 13 deletions qualtran/_infra/data_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@

import attrs
import numpy as np
import sympy
from fxpmath import Fxp
from numpy.typing import NDArray

from qualtran.symbolics import is_symbolic, SymbolicInt


class QDType(metaclass=abc.ABCMeta):
"""This defines the abstract interface for quantum data types."""
Expand Down Expand Up @@ -89,7 +90,11 @@ def assert_valid_classical_val(self, val: Any, debug_str: str = 'val'):
debug_str: Optional debugging information to use in exception messages.
"""

def iteration_length_or_zero(self) -> Union[int, sympy.Expr]:
@abc.abstractmethod
def is_symbolic(self) -> bool:
"""Returns True if this qdtype is parameterized with symbolic objects."""

def iteration_length_or_zero(self) -> SymbolicInt:
"""Safe version of iteration length.
Returns the iteration_length if the type has it or else zero.
Expand Down Expand Up @@ -130,6 +135,9 @@ def assert_valid_classical_val(self, val: int, debug_str: str = 'val'):
if not (val == 0 or val == 1):
raise ValueError(f"Bad {self} value {val} in {debug_str}")

def is_symbolic(self) -> bool:
return False

def to_bits(self, x) -> List[int]:
"""Yields individual bits corresponding to binary representation of x"""
self.assert_valid_classical_val(x)
Expand All @@ -154,7 +162,7 @@ def __str__(self):
class QAny(QDType):
"""Opaque bag-of-qbits type."""

bitsize: Union[int, sympy.Expr]
bitsize: SymbolicInt

@property
def num_qubits(self):
Expand All @@ -171,6 +179,9 @@ def from_bits(self, bits: Sequence[int]) -> int:
# TODO: Raise an error once usage of `QAny` is minimized across the library
return QUInt(self.bitsize).from_bits(bits)

def is_symbolic(self) -> bool:
return is_symbolic(self.bitsize)

def assert_valid_classical_val(self, val, debug_str: str = 'val'):
pass

Expand All @@ -188,12 +199,15 @@ class QInt(QDType):
bitsize: The number of qubits used to represent the integer.
"""

bitsize: Union[int, sympy.Expr]
bitsize: SymbolicInt

@property
def num_qubits(self):
return self.bitsize

def is_symbolic(self) -> bool:
return is_symbolic(self.bitsize)

def get_classical_domain(self) -> Iterable[int]:
max_val = 1 << (self.bitsize - 1)
return range(-max_val, max_val)
Expand Down Expand Up @@ -240,7 +254,7 @@ class QIntOnesComp(QDType):
bitsize: The number of qubits used to represent the integer.
"""

bitsize: Union[int, sympy.Expr]
bitsize: SymbolicInt

def __attrs_post_init__(self):
if isinstance(self.bitsize, int):
Expand All @@ -251,6 +265,9 @@ def __attrs_post_init__(self):
def num_qubits(self):
return self.bitsize

def is_symbolic(self) -> bool:
return is_symbolic(self.bitsize)

def to_bits(self, x: int) -> List[int]:
"""Yields individual bits corresponding to binary representation of x"""
self.assert_valid_classical_val(x)
Expand Down Expand Up @@ -286,12 +303,15 @@ class QUInt(QDType):
bitsize: The number of qubits used to represent the integer.
"""

bitsize: Union[int, sympy.Expr]
bitsize: SymbolicInt

@property
def num_qubits(self):
return self.bitsize

def is_symbolic(self) -> bool:
return is_symbolic(self.bitsize)

def get_classical_domain(self) -> Iterable[Any]:
return range(2**self.bitsize)

Expand Down Expand Up @@ -371,11 +391,11 @@ class BoundedQUInt(QDType):
iteration_length: The length of the iteration range.
"""

bitsize: Union[int, sympy.Expr]
iteration_length: Union[int, sympy.Expr] = attrs.field()
bitsize: SymbolicInt
iteration_length: SymbolicInt = attrs.field()

def __attrs_post_init__(self):
if isinstance(self.bitsize, int):
if not self.is_symbolic():
if self.iteration_length > 2**self.bitsize:
raise ValueError(
"BoundedQUInt iteration length is too large for given bitsize. "
Expand All @@ -386,6 +406,9 @@ def __attrs_post_init__(self):
def _default_iteration_length(self):
return 2**self.bitsize

def is_symbolic(self) -> bool:
return is_symbolic(self.bitsize, self.iteration_length)

@property
def num_qubits(self):
return self.bitsize
Expand Down Expand Up @@ -446,16 +469,16 @@ class QFxp(QDType):
number of integer bits is reduced by 1.
"""

bitsize: Union[int, sympy.Expr]
num_frac: Union[int, sympy.Expr]
bitsize: SymbolicInt
num_frac: SymbolicInt
signed: bool = False

@property
def num_qubits(self):
return self.bitsize

@property
def num_int(self) -> Union[int, sympy.Expr]:
def num_int(self) -> SymbolicInt:
return self.bitsize - self.num_frac - int(self.signed)

@property
Expand All @@ -466,6 +489,9 @@ def fxp_dtype_str(self) -> str:
def _fxp_dtype(self) -> Fxp:
return Fxp(None, dtype=self.fxp_dtype_str)

def is_symbolic(self) -> bool:
return is_symbolic(self.bitsize, self.num_frac)

def to_bits(self, x: Union[float, Fxp]) -> List[int]:
"""Yields individual bits corresponding to binary representation of x"""
self._assert_valid_classical_val(x)
Expand Down Expand Up @@ -539,12 +565,15 @@ class QMontgomeryUInt(QDType):
[Montgomery modular multiplication](https://en.wikipedia.org/wiki/Montgomery_modular_multiplication)
"""

bitsize: Union[int, sympy.Expr]
bitsize: SymbolicInt

@property
def num_qubits(self):
return self.bitsize

def is_symbolic(self) -> bool:
return is_symbolic(self.bitsize)

def get_classical_domain(self) -> Iterable[Any]:
return range(2**self.bitsize)

Expand Down
10 changes: 10 additions & 0 deletions qualtran/_infra/data_types_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import pytest
import sympy

from qualtran.symbolics import is_symbolic

from .data_types import (
BoundedQUInt,
check_dtypes_consistent,
Expand All @@ -39,6 +41,7 @@ def test_qint():
qint_8 = QInt(n)
assert qint_8.num_qubits == n
assert str(qint_8) == 'QInt(x)'
assert is_symbolic(QInt(sympy.Symbol('x')))


def test_qint_ones():
Expand All @@ -50,6 +53,7 @@ def test_qint_ones():
n = sympy.symbols('x')
qint_8 = QIntOnesComp(n)
assert qint_8.num_qubits == n
assert is_symbolic(QIntOnesComp(sympy.Symbol('x')))


def test_quint():
Expand All @@ -62,6 +66,7 @@ def test_quint():
n = sympy.symbols('x')
qint_8 = QUInt(n)
assert qint_8.num_qubits == n
assert is_symbolic(QUInt(sympy.Symbol('x')))


def test_bounded_quint():
Expand All @@ -77,6 +82,9 @@ def test_bounded_quint():
qint_8 = BoundedQUInt(n, l)
assert qint_8.num_qubits == n
assert qint_8.iteration_length == l
assert is_symbolic(BoundedQUInt(sympy.Symbol('x'), 2))
assert is_symbolic(BoundedQUInt(2, sympy.Symbol('x')))
assert is_symbolic(BoundedQUInt(*sympy.symbols('x y')))


def test_qfxp():
Expand Down Expand Up @@ -105,6 +113,7 @@ def test_qfxp():
qfp = QFxp(b, f, True)
assert qfp.num_qubits == b
assert qfp.num_int == b - f - 1
assert is_symbolic(QFxp(*sympy.symbols('x y')))


def test_qmontgomeryuint():
Expand All @@ -116,6 +125,7 @@ def test_qmontgomeryuint():
n = sympy.symbols('x')
qmontgomeryuint_8 = QMontgomeryUInt(n)
assert qmontgomeryuint_8.num_qubits == n
assert is_symbolic(QMontgomeryUInt(sympy.Symbol('x')))


@pytest.mark.parametrize('qdtype', [QBit(), QInt(4), QUInt(4), BoundedQUInt(3, 5)])
Expand Down
19 changes: 17 additions & 2 deletions qualtran/_infra/registers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
import enum
import itertools
from collections import defaultdict
from typing import Dict, Iterable, Iterator, List, overload, Tuple, Union
from typing import cast, Dict, Iterable, Iterator, List, overload, Tuple, Union

import attrs
import numpy as np
import sympy
from attrs import field, frozen

from qualtran.symbolics import is_symbolic, SymbolicInt

from .data_types import QAny, QBit, QDType


Expand Down Expand Up @@ -63,7 +65,7 @@ class Register:

name: str
dtype: QDType
shape: Tuple[int, ...] = field(
_shape: Tuple[SymbolicInt, ...] = field(
default=tuple(), converter=lambda v: (v,) if isinstance(v, int) else tuple(v)
)
side: Side = Side.THRU
Expand All @@ -72,6 +74,19 @@ def __attrs_post_init__(self):
if not isinstance(self.dtype, QDType):
raise ValueError(f'dtype must be a QDType: found {type(self.dtype)}')

def is_symbolic(self) -> bool:
return is_symbolic(self.dtype, *self._shape)

@property
def shape_symbolic(self) -> Tuple[SymbolicInt, ...]:
return self._shape

@property
def shape(self) -> Tuple[int, ...]:
if is_symbolic(*self._shape):
raise ValueError(f"{self} is symbolic. Cannot get real-valued shape.")
return cast(Tuple[int, ...], self._shape)

@property
def bitsize(self) -> int:
return self.dtype.num_qubits
Expand Down
11 changes: 10 additions & 1 deletion qualtran/_infra/registers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
import cirq
import numpy as np
import pytest
import sympy

from qualtran import BoundedQUInt, QAny, QBit, QInt, Register, Side, Signature
from qualtran._infra.gate_with_registers import get_named_qubits
from qualtran.symbolics import is_symbolic


def test_register():
Expand Down Expand Up @@ -193,6 +195,13 @@ def test_dtypes_converter():
r1 = Register("my_reg", QBit())
r2 = Register("my_reg", QBit())
assert r1 == r2
r2 = Register("my_reg", QAny(5))
r1 = Register("my_reg", QAny(5))
r2 = Register("my_reg", QInt(5))
assert r1 != r2


def test_is_symbolic():
r = Register("my_reg", QAny(sympy.Symbol("x")))
assert is_symbolic(r)
r = Register("my_reg", QAny(2), shape=sympy.symbols("x y"))
assert is_symbolic(r)
4 changes: 2 additions & 2 deletions qualtran/bloqs/chemistry/trotter/hubbard/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@
where $z_{p\sigma} = (2 n_{p\sigma} - 1)$.
For Trotterization we assume the plaquette splitting from the
For Trotterization we assume the plaquette splitting from the
[reference](https://arxiv.org/abs/2012.09238).
The plaquette splitting rewrites $H_h$ as a sum of $H_h^p$ and $H_h^g$ (for pink and gold
The plaquette splitting rewrites $H_h$ as a sum of $H_h^p$ and $H_h^g$ (for pink and gold
respectively) which when combined tile the entire lattice. Each plaquette
contains four sites and paritions the lattice such that each edge of the lattice
belongs to a single plaquette. Each term within a grouping commutes so that the
Expand Down
14 changes: 8 additions & 6 deletions qualtran/bloqs/chemistry/trotter/hubbard/hopping.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ class HoppingPlaquette(Bloq):
$$
\sum_{i,j} [R_{\mathrm{plaq}}]_{i,j} a_{i\sigma}^\dagger a_{j\sigma}
$$
where the non-zero sub-bloq of R_{\mathrm{plaq}} is
where the non-zero sub-bloq of $R_{\mathrm{plaq}}$ is
$$
R_{\mathrm{plaq}} =
R_{\mathrm{plaq}} =
\begin{bmatrix}
0 & 1 & 0 & 1 \\
1 & 0 & 1 & 0 \\
Expand All @@ -55,7 +55,7 @@ class HoppingPlaquette(Bloq):
Args:
kappa: The scalar prefactor appearing in the definition of the unitary.
Usually a combination of the timestep and the hopping parameter $\tau$.
Usually a combination of the timestep and the hopping parameter $\tau$.
eps: The precision of the single qubit rotations.
Registers:
Expand All @@ -78,7 +78,7 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
# page 14, discussion after E13
# There are 4 flanking f-gates and a e^{iXX}e^{iYY} rotation, which can
# be rotated to single rotation + cliffords.
return {(TwoBitFFFT(0, 1), 4), (Rz(self.kappa, eps=self.eps), 2)}
return {(TwoBitFFFT(0, 1, eps=self.eps), 4), (Rz(self.kappa, eps=self.eps), 2)}


@frozen
Expand Down Expand Up @@ -116,7 +116,7 @@ class HoppingTile(Bloq):
pink: bool = True

def __attrs_post_init__(self):
if self.length % 2 != 0:
if isinstance(self.length, int) and self.length % 2 != 0:
raise ValueError('Only even length lattices are supported')

def short_name(self) -> str:
Expand All @@ -129,7 +129,9 @@ def signature(self) -> Signature:

def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
# Page 5, text after Eq. 22. There are L^2 / 4 plaquettes of a given colour and x2 for spin.
return {(HoppingPlaquette(kappa=self.tau * self.angle, eps=self.eps), self.length**2 // 2)}
return {
(HoppingPlaquette(kappa=self.tau * self.angle, eps=self.eps), self.length**2 // 2)
}


@bloq_example
Expand Down
Loading

0 comments on commit c8479c7

Please sign in to comment.