Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix t_complexity for symbolic ham. sim. by gqsp example #944

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from functools import cached_property
from typing import cast, Dict, Tuple, TYPE_CHECKING, Union
from typing import cast, Dict, Set, Tuple, TYPE_CHECKING, Union

import numpy as np
from attrs import field, frozen
Expand All @@ -29,6 +29,7 @@

if TYPE_CHECKING:
from qualtran import BloqBuilder, SoquetT
from qualtran.resource_counting import BloqCountT, SympySymbolAllocator


@frozen
Expand Down Expand Up @@ -176,6 +177,21 @@ def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str

return soqs

def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
if self.is_symbolic():
from qualtran.bloqs.basic_gates.su2_rotation import SU2RotationGate

d = self.degree
return {
(self.walk_operator.prepare, 1),
(self.walk_operator.prepare.adjoint(), 1),
(self.walk_operator.controlled(control_values=[0]), d),
(self.walk_operator.adjoint().controlled(), d),
(SU2RotationGate.arbitrary(ssa), 2 * d + 1),
}

return super().build_call_graph(ssa)


@bloq_example
def _hubbard_time_evolution_by_gqsp() -> HamiltonianSimulationByGQSP:
Expand All @@ -192,9 +208,8 @@ def _symbolic_hamsim_by_gqsp() -> HamiltonianSimulationByGQSP:

from qualtran.bloqs.hubbard_model import get_walk_operator_for_hubbard_model

walk_op = get_walk_operator_for_hubbard_model(2, 2, 1, 1)

t, inv_eps = sympy.symbols("t N")
tau, t, inv_eps = sympy.symbols(r"\tau t \epsilon^{-1}", positive=True)
fdmalone marked this conversation as resolved.
Show resolved Hide resolved
walk_op = get_walk_operator_for_hubbard_model(2, 2, tau, 4 * tau)
symbolic_hamsim_by_gqsp = HamiltonianSimulationByGQSP(walk_op, t=t, precision=1 / inv_eps)
return symbolic_hamsim_by_gqsp

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import numpy as np
import pytest
import scipy
import sympy
from numpy.typing import NDArray

from qualtran.bloqs.for_testing.matrix_gate import MatrixGate
Expand All @@ -26,6 +27,8 @@
verify_generalized_qsp,
)
from qualtran.bloqs.qubitization_walk_operator import QubitizationWalkOperator
from qualtran.cirq_interop.t_complexity_protocol import TComplexity
from qualtran.resource_counting import big_O
from qualtran.symbolics import Shaped

from .hamiltonian_simulation_by_gqsp import (
Expand Down Expand Up @@ -100,3 +103,8 @@ def test_hamiltonian_simulation_by_gqsp(
def test_hamiltonian_simulation_by_gqsp_t_complexity():
hubbard_time_evolution_by_gqsp = _hubbard_time_evolution_by_gqsp.make()
_ = hubbard_time_evolution_by_gqsp.t_complexity()

symbolic_hamsim_by_gqsp = _symbolic_hamsim_by_gqsp()
tau, t, inv_eps = sympy.symbols(r"\tau t \epsilon^{-1}", positive=True)
T = big_O(tau * t + sympy.log(inv_eps) / sympy.log(sympy.log(inv_eps)))
assert symbolic_hamsim_by_gqsp.t_complexity() == TComplexity(t=T, clifford=T, rotations=T) # type: ignore[arg-type]
3 changes: 2 additions & 1 deletion qualtran/bloqs/hubbard_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
from qualtran.bloqs.state_preparation.prepare_uniform_superposition import (
PrepareUniformSuperposition,
)
from qualtran.symbolics.math_funcs import acos, ssqrt

if TYPE_CHECKING:
from qualtran.symbolics import SymbolicFloat
Expand Down Expand Up @@ -308,7 +309,7 @@ def decompose_from_registers(
temp = quregs['temp']

N = self.x_dim * self.y_dim * 2
yield cirq.Ry(rads=2 * np.arccos(np.sqrt(self.t * N / self.l1_norm_of_coeffs))).on(*V)
yield cirq.Ry(rads=2 * acos(ssqrt(self.t * N / self.l1_norm_of_coeffs))).on(*V)
yield cirq.Ry(rads=2 * np.arccos(np.sqrt(1 / 5))).on(*U).controlled_by(*V)
yield PrepareUniformSuperposition(self.x_dim).on_registers(controls=[], target=p_x)
yield PrepareUniformSuperposition(self.y_dim).on_registers(controls=[], target=p_y)
Expand Down
83 changes: 34 additions & 49 deletions qualtran/bloqs/util_bloqs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

"""Bloqs for virtual operations and register reshaping."""

import abc
from functools import cached_property
from typing import Any, Dict, Iterable, Optional, Sequence, Set, Tuple, TYPE_CHECKING, Union

Expand Down Expand Up @@ -50,8 +50,32 @@
from qualtran.simulation.classical_sim import ClassicalValT


class _BookkeepingBloq(Bloq, metaclass=abc.ABCMeta):
"""Base class for utility bloqs used for bookkeeping.

This bloq:
- has trivial controlled versions, which pass through the control register.
- does not affect T complexity.
"""

def get_ctrl_system(
mpharrigan marked this conversation as resolved.
Show resolved Hide resolved
self, ctrl_spec: Optional['CtrlSpec'] = None
) -> Tuple['Bloq', 'AddControlledT']:
def add_controlled(
bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT']
) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]:
# ignore `ctrl_soq` and pass it through for bookkeeping operation.
out_soqs = bb.add_t(self, **in_soqs)
return ctrl_soqs, out_soqs

return self, add_controlled

def _t_complexity_(self) -> 'TComplexity':
return TComplexity()


@frozen
class Split(Bloq):
class Split(_BookkeepingBloq):
"""Split a bitsize `n` register into a length-`n` array-register.

Attributes:
Expand All @@ -75,9 +99,6 @@ def adjoint(self) -> 'Bloq':
def as_cirq_op(self, qubit_manager, reg: 'CirqQuregT') -> Tuple[None, Dict[str, 'CirqQuregT']]:
return None, {'reg': reg.reshape((self.dtype.num_qubits, 1))}

def _t_complexity_(self) -> 'TComplexity':
return TComplexity()

def on_classical_vals(self, reg: int) -> Dict[str, 'ClassicalValT']:
return {'reg': ints_to_bits(np.array([reg]), self.dtype.num_qubits)[0]}

Expand All @@ -103,16 +124,6 @@ def add_my_tensors(
)
)

def get_ctrl_system(self, ctrl_spec=None) -> Tuple['Bloq', 'AddControlledT']:
def add_controlled(
bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT']
) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]:
# ignore `ctrl_soq` and pass it through for bookkeeping operation.
out_soqs = bb.add_t(self, **in_soqs)
return ctrl_soqs, out_soqs

return self, add_controlled

def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol':
if reg is None:
return Text(self.pretty_name())
Expand All @@ -123,7 +134,7 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -


@frozen
class Join(Bloq):
class Join(_BookkeepingBloq):
"""Join a length-`n` array-register into one register of bitsize `n`.

Attributes:
Expand All @@ -147,9 +158,6 @@ def adjoint(self) -> 'Bloq':
def as_cirq_op(self, qubit_manager, reg: 'CirqQuregT') -> Tuple[None, Dict[str, 'CirqQuregT']]:
return None, {'reg': reg.reshape(self.dtype.num_qubits)}

def _t_complexity_(self) -> 'TComplexity':
return TComplexity()

def add_my_tensors(
self,
tn: 'qtn.TensorNetwork',
Expand All @@ -175,18 +183,6 @@ def add_my_tensors(
def on_classical_vals(self, reg: 'NDArray[np.uint]') -> Dict[str, int]:
return {'reg': bits_to_ints(reg)[0]}

def get_ctrl_system(
self, ctrl_spec: Optional['CtrlSpec'] = None
) -> Tuple['Bloq', 'AddControlledT']:
def add_controlled(
bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT']
) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]:
# ignore `ctrl_soq` and pass it through for bookkeeping operation.
out_soqs = bb.add_t(self, **in_soqs)
return ctrl_soqs, out_soqs

return self, add_controlled

def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol':
if reg is None:
return Text('')
Expand All @@ -197,7 +193,7 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -


@frozen
class Partition(Bloq):
class Partition(_BookkeepingBloq):
"""Partition a generic index into multiple registers.

Args:
Expand Down Expand Up @@ -240,9 +236,6 @@ def as_cirq_op(self, qubit_manager, **cirq_quregs) -> Tuple[None, Dict[str, 'Cir
else:
return None, {'x': np.concatenate([v.ravel() for _, v in cirq_quregs.items()])}

def _t_complexity_(self) -> 'TComplexity':
return TComplexity()

def add_my_tensors(
self,
tn: 'qtn.TensorNetwork',
Expand Down Expand Up @@ -323,7 +316,7 @@ def wire_symbol(self, reg: Register, idx: Tuple[int, ...] = tuple()) -> 'WireSym


@frozen
class Allocate(Bloq):
class Allocate(_BookkeepingBloq):
"""Allocate an `n` bit register.

Attributes:
Expand All @@ -342,9 +335,6 @@ def adjoint(self) -> 'Bloq':
def on_classical_vals(self) -> Dict[str, int]:
return {'reg': 0}

def _t_complexity_(self) -> 'TComplexity':
return TComplexity()

def add_my_tensors(
self,
tn: 'qtn.TensorNetwork',
Expand All @@ -367,7 +357,7 @@ def wire_symbol(self, reg: Register, idx: Tuple[int, ...] = tuple()) -> 'WireSym


@frozen
class Free(Bloq):
class Free(_BookkeepingBloq):
"""Free (i.e. de-allocate) an `n` bit register.

The tensor decomposition assumes the `n` bit register is uncomputed and is in the $|0^{n}>$
Expand All @@ -392,9 +382,6 @@ def on_classical_vals(self, reg: int) -> Dict[str, 'ClassicalValT']:
raise ValueError(f"Tried to free a non-zero register: {reg}.")
return {}

def _t_complexity_(self) -> 'TComplexity':
return TComplexity()

def add_my_tensors(
self,
tn: 'qtn.TensorNetwork',
Expand Down Expand Up @@ -440,13 +427,14 @@ def _t_complexity_(self) -> 'TComplexity':


@frozen
class Cast(Bloq):
class Cast(_BookkeepingBloq):
"""Cast a register from one n-bit QDType to another QDType.


Args:
in_qdtype: Input QDType to cast from.
out_qdtype: Output QDType to cast to.
inp_dtype: Input QDType to cast from.
out_dtype: Output QDType to cast to.
shape: shape of the register to cast.
anurudhp marked this conversation as resolved.
Show resolved Hide resolved

Registers:
in: input register to cast from.
Expand Down Expand Up @@ -501,9 +489,6 @@ def on_classical_vals(self, reg: int) -> Dict[str, 'ClassicalValT']:
def as_cirq_op(self, qubit_manager, reg: 'CirqQuregT') -> Tuple[None, Dict[str, 'CirqQuregT']]:
return None, {'reg': reg}

def _t_complexity_(self) -> 'TComplexity':
return TComplexity()


@frozen
class Power(GateWithRegisters):
Expand Down
6 changes: 6 additions & 0 deletions qualtran/symbolics/math_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ def sabs(x: SymbolicFloat) -> SymbolicFloat:
return cast(SymbolicFloat, abs(x))


def ssqrt(x: SymbolicFloat) -> SymbolicFloat:
if isinstance(x, sympy.Basic):
return sympy.sqrt(x)
return np.sqrt(x)


def ceil(x: SymbolicFloat) -> SymbolicInt:
if not isinstance(x, sympy.Basic):
return int(np.ceil(x))
Expand Down
Loading