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

QubitizationWalkOperator add attribute: sum of LCU coefficients #890

Merged
merged 8 commits into from
Apr 29, 2024
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
3 changes: 1 addition & 2 deletions qualtran/bloqs/chemistry/ising/hamiltonian.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,4 @@ def get_1d_ising_lcu_coeffs(
spins = cirq.LineQubit.range(n_spins)
ham = get_1d_ising_hamiltonian(spins, j_zz_strength, gamma_x_strength)
coeffs = np.array([term.coefficient.real for term in ham])
lcu_coeffs = coeffs / np.sum(abs(coeffs))
return lcu_coeffs
return coeffs
12 changes: 9 additions & 3 deletions qualtran/bloqs/for_testing/qubitization_walk_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from functools import cached_property
from typing import Tuple
from typing import Optional, Tuple

import attrs
import cirq
import scipy
from numpy._typing import NDArray
from numpy.typing import NDArray

from qualtran import Signature
from qualtran.bloqs.multiplexers.select_pauli_lcu import SelectPauliLCU
from qualtran.bloqs.qubitization_walk_operator import QubitizationWalkOperator
from qualtran.bloqs.select_and_prepare import PrepareOracle
from qualtran.bloqs.state_preparation import PrepareUniformSuperposition
from qualtran.resource_counting.symbolic_counting_utils import SymbolicFloat


@attrs.frozen
Expand All @@ -32,6 +33,7 @@ class PrepareUniformSuperpositionTest(PrepareOracle):
cvs: Tuple[int, ...] = attrs.field(
converter=lambda v: (v,) if isinstance(v, int) else tuple(v), default=()
)
qlambda: Optional[float] = None

@cached_property
def selection_registers(self) -> Signature:
Expand All @@ -41,6 +43,10 @@ def selection_registers(self) -> Signature:
def junk_registers(self) -> Signature:
return Signature.build()

@cached_property
def l1_norm_of_coeffs(self) -> Optional['SymbolicFloat']:
return self.qlambda

def decompose_from_registers(
self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] # type: ignore[type-var]
) -> cirq.OP_TREE:
Expand All @@ -57,7 +63,7 @@ def get_uniform_pauli_qubitized_walk(target_bitsize: int):
ham_dps = [ps.dense(q) for ps in ham]

assert scipy.linalg.ishermitian(ham.matrix())
prepare = PrepareUniformSuperpositionTest(len(ham_coeff))
prepare = PrepareUniformSuperpositionTest(len(ham_coeff), qlambda=sum(ham_coeff))
select = SelectPauliLCU(
(len(ham_coeff) - 1).bit_length(), select_unitaries=ham_dps, target_bitsize=target_bitsize
)
Expand Down
4 changes: 4 additions & 0 deletions qualtran/bloqs/for_testing/random_select_and_prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ def __attrs_post_init__(self):
def selection_registers(self) -> tuple[Register, ...]:
return (Register('selection', BoundedQUInt(bitsize=self.U.bitsize)),)

@property
def l1_norm_of_coeffs(self) -> float:
return 1.0

@classmethod
def random(cls, bitsize: int, *, random_state: np.random.RandomState):
"""Generate a random unitary s.t. the first column has all real amplitudes"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@
"#### Parameters\n",
" - `walk_operator`: qubitization walk operator of $H$ constructed from SELECT and PREPARE oracles.\n",
" - `t`: time to simulate the Hamiltonian, i.e. $e^{-iHt}$\n",
" - `alpha`: the $1$-norm of the coefficients of the unitaries comprising the Hamiltonian $H$.\n",
" - `precision`: the precision $\\epsilon$ to approximate $e^{it\\cos\\theta}$ to a polynomial.\n"
]
},
Expand Down Expand Up @@ -123,9 +122,7 @@
"from qualtran.bloqs.hubbard_model import get_walk_operator_for_hubbard_model\n",
"\n",
"walk_op = get_walk_operator_for_hubbard_model(2, 2, 1, 1)\n",
"hubbard_time_evolution_by_gqsp = HamiltonianSimulationByGQSP(\n",
" walk_op, t=5, alpha=1, precision=1e-7\n",
")"
"hubbard_time_evolution_by_gqsp = HamiltonianSimulationByGQSP(walk_op, t=5, precision=1e-7)"
]
},
{
Expand All @@ -142,10 +139,9 @@
"from qualtran.bloqs.hubbard_model import get_walk_operator_for_hubbard_model\n",
"\n",
"walk_op = get_walk_operator_for_hubbard_model(2, 2, 1, 1)\n",
"t, alpha, inv_eps = sympy.symbols(\"t alpha N\")\n",
"symbolic_hamsim_by_gqsp = HamiltonianSimulationByGQSP(\n",
" walk_op, t=t, alpha=alpha, precision=1 / inv_eps\n",
")"
"\n",
"t, inv_eps = sympy.symbols(\"t N\")\n",
"symbolic_hamsim_by_gqsp = HamiltonianSimulationByGQSP(walk_op, t=t, precision=1 / inv_eps)"
]
},
{
Expand Down Expand Up @@ -191,8 +187,8 @@
},
"outputs": [],
"source": [
"from qualtran.resource_counting.generalizers import generalize_rotation_angle, ignore_split_join, ignore_alloc_free\n",
"hubbard_time_evolution_by_gqsp_g, hubbard_time_evolution_by_gqsp_sigma = hubbard_time_evolution_by_gqsp.call_graph(generalizer=[ignore_split_join, ignore_alloc_free, generalize_rotation_angle])\n",
"from qualtran.resource_counting.generalizers import ignore_split_join\n",
"hubbard_time_evolution_by_gqsp_g, hubbard_time_evolution_by_gqsp_sigma = hubbard_time_evolution_by_gqsp.call_graph(max_depth=1, generalizer=ignore_split_join)\n",
"show_call_graph(hubbard_time_evolution_by_gqsp_g)\n",
"show_counts_sigma(hubbard_time_evolution_by_gqsp_sigma)"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,18 +88,26 @@ class HamiltonianSimulationByGQSP(GateWithRegisters):
Args:
walk_operator: qubitization walk operator of $H$ constructed from SELECT and PREPARE oracles.
t: time to simulate the Hamiltonian, i.e. $e^{-iHt}$
alpha: the $1$-norm of the coefficients of the unitaries comprising the Hamiltonian $H$.
precision: the precision $\epsilon$ to approximate $e^{it\cos\theta}$ to a polynomial.
"""

walk_operator: QubitizationWalkOperator
t: SymbolicFloat = field(kw_only=True)
alpha: SymbolicFloat = field(kw_only=True)
precision: SymbolicFloat = field(kw_only=True)

def __attrs_post_init__(self):
if self.walk_operator.sum_of_lcu_coefficients is None:
raise ValueError(
f"Missing attribute `sum_of_ham_coeffs` for {self.walk_operator}, cannot implement Hamiltonian Simulation"
)

def is_symbolic(self):
return is_symbolic(self.t, self.alpha, self.precision)

@property
def alpha(self):
return self.walk_operator.sum_of_lcu_coefficients

@cached_property
def degree(self) -> SymbolicInt:
r"""degree of the polynomial to approximate the function e^{it\cos(\theta)}"""
Expand Down Expand Up @@ -179,9 +187,7 @@ def _hubbard_time_evolution_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)
hubbard_time_evolution_by_gqsp = HamiltonianSimulationByGQSP(
walk_op, t=5, alpha=1, precision=1e-7
)
hubbard_time_evolution_by_gqsp = HamiltonianSimulationByGQSP(walk_op, t=5, precision=1e-7)
return hubbard_time_evolution_by_gqsp


Expand All @@ -192,10 +198,9 @@ 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, alpha, inv_eps = sympy.symbols("t alpha N")
symbolic_hamsim_by_gqsp = HamiltonianSimulationByGQSP(
walk_op, t=t, alpha=alpha, precision=1 / inv_eps
)

t, inv_eps = sympy.symbols("t N")
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 @@ -25,7 +25,7 @@
check_polynomial_pair_on_random_points_on_unit_circle,
verify_generalized_qsp,
)
from qualtran.bloqs.qubitization_walk_operator import _walk_op, QubitizationWalkOperator
from qualtran.bloqs.qubitization_walk_operator import QubitizationWalkOperator

from .hamiltonian_simulation_by_gqsp import (
_hubbard_time_evolution_by_gqsp,
Expand All @@ -52,10 +52,11 @@ def test_generalized_qsp_with_exp_cos_approx_on_random_unitaries(
bitsize: int, t: float, precision: float
):
random_state = np.random.RandomState(42)
W, _ = random_qubitization_walk_operator(1, 1, random_state=random_state)

for _ in range(5):
U = MatrixGate.random(bitsize, random_state=random_state)
gqsp = HamiltonianSimulationByGQSP(_walk_op, t=t, alpha=1, precision=precision).gqsp
gqsp = HamiltonianSimulationByGQSP(W, t=t, precision=precision).gqsp
P, Q = gqsp.P, gqsp.Q

check_polynomial_pair_on_random_points_on_unit_circle(
Expand All @@ -65,16 +66,11 @@ def test_generalized_qsp_with_exp_cos_approx_on_random_unitaries(


def verify_hamiltonian_simulation_by_gqsp(
W: QubitizationWalkOperator,
H: NDArray[np.complex_],
*,
t: float,
alpha: float,
precision: float,
W: QubitizationWalkOperator, H: NDArray[np.complex_], *, t: float, precision: float
):
N = H.shape[0]

W_e_iHt = HamiltonianSimulationByGQSP(W, t=t, alpha=alpha, precision=precision)
W_e_iHt = HamiltonianSimulationByGQSP(W, t=t, precision=precision)
result_unitary = cirq.unitary(W_e_iHt)

expected_top_left = scipy.linalg.expm(-1j * H * t)
Expand All @@ -96,4 +92,4 @@ def test_hamiltonian_simulation_by_gqsp(
W, H = random_qubitization_walk_operator(
select_bitsize, target_bitsize, random_state=random_state
)
verify_hamiltonian_simulation_by_gqsp(W, H.matrix(), t=t, alpha=1, precision=precision)
verify_hamiltonian_simulation_by_gqsp(W, H.matrix(), t=t, precision=precision)
16 changes: 13 additions & 3 deletions qualtran/bloqs/hubbard_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
See the documentation for `PrepareHubbard` and `SelectHubbard` for details.
"""
from functools import cached_property
from typing import Collection, Optional, Sequence, Tuple, Union
from typing import Collection, Optional, Sequence, Tuple, TYPE_CHECKING, Union

import attrs
import cirq
Expand All @@ -67,6 +67,9 @@
PrepareUniformSuperposition,
)

if TYPE_CHECKING:
from qualtran.resource_counting.symbolic_counting_utils import SymbolicFloat


@attrs.frozen
class SelectHubbard(SelectOracle):
Expand Down Expand Up @@ -309,6 +312,13 @@ def selection_registers(self) -> Tuple[Register, ...]:
def junk_registers(self) -> Tuple[Register, ...]:
return (Register('temp', QAny(2)),)

@cached_property
def l1_norm_of_coeffs(self) -> 'SymbolicFloat':
# https://arxiv.org/abs/1805.03662v2 equation 60
N = self.x_dim * self.y_dim * 2
qlambda = 2 * N * self.t + (N * self.mu) // 2
return qlambda

@cached_property
def signature(self) -> Signature:
return Signature([*self.selection_registers, *self.junk_registers])
Expand All @@ -321,8 +331,7 @@ def decompose_from_registers(
temp = quregs['temp']

N = self.x_dim * self.y_dim * 2
qlambda = 2 * N * self.t + (N * self.mu) // 2
yield cirq.Ry(rads=2 * np.arccos(np.sqrt(self.t * N / qlambda))).on(*V)
yield cirq.Ry(rads=2 * np.arccos(np.sqrt(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 Expand Up @@ -352,4 +361,5 @@ def get_walk_operator_for_hubbard_model(
) -> 'QubitizationWalkOperator':
select = SelectHubbard(x_dim, y_dim)
prepare = PrepareHubbard(x_dim, y_dim, t, mu)

return QubitizationWalkOperator(select=select, prepare=prepare)
4 changes: 2 additions & 2 deletions qualtran/bloqs/multiplexers/select_pauli_lcu_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ def test_select_application_to_eigenstates():
# right now we only handle positive interaction term values
ham = get_1d_ising_hamiltonian(target, 1, 1)
dense_pauli_string_hamiltonian = [tt.dense(target) for tt in ham]
qubitization_lambda = sum(xx.coefficient.real for xx in dense_pauli_string_hamiltonian)
# built select with unary iteration gate
bloq = SelectPauliLCU(
selection_bitsize=selection_bitsize,
Expand All @@ -187,11 +188,10 @@ def test_select_application_to_eigenstates():
all_qubits = select_circuit.all_qubits()

coeffs = get_1d_ising_lcu_coeffs(num_sites, 1, 1)
prep_circuit = _fake_prepare(np.sqrt(coeffs), selection)
prep_circuit = _fake_prepare(np.sqrt(coeffs / qubitization_lambda), selection)
turn_on_control = cirq.Circuit(cirq.X.on(control))

ising_eigs, ising_wfns = np.linalg.eigh(ham.matrix())
qubitization_lambda = sum(xx.coefficient.real for xx in dense_pauli_string_hamiltonian)
for iw_idx, ie in enumerate(ising_eigs):
eigenstate_prep = cirq.Circuit()
eigenstate_prep.append(
Expand Down
4 changes: 3 additions & 1 deletion qualtran/bloqs/qubitization_walk_operator.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,16 @@
"## `QubitizationWalkOperator`\n",
"Constructs a Szegedy Quantum Walk operator using LCU oracles SELECT and PREPARE.\n",
"\n",
"For a Hamiltonian $H = \\sum_l w_l H_l$ (s.t. $w_l > 0$ and $H_l$ are unitaries),\n",
"Constructs a Szegedy quantum walk operator $W = R_{L} . SELECT$, which is a product of\n",
"two reflections $R_{L} = (2|L><L| - I)$ and $SELECT=\\sum_{l}|l><l|H_{l}$.\n",
"\n",
"The action of $W$ partitions the Hilbert space into a direct sum of two-dimensional irreducible\n",
"vector spaces. For an arbitrary eigenstate $|k>$ of $H$ with eigenvalue $E_k$, $|\\ell>|k>$ and\n",
"an orthogonal state $\\phi_{k}$ span the irreducible two-dimensional space that $|\\ell>|k>$ is\n",
"in under the action of $W$. In this space, $W$ implements a Pauli-Y rotation by an angle of\n",
"$-2arccos(E_{k} / \\lambda)$ s.t. $W = e^{i arccos(E_k / \\lambda) Y}$.\n",
"$-2arccos(E_{k} / \\lambda)$ s.t. $W = e^{i arccos(E_k / \\lambda) Y}$,\n",
"where $\\lambda = \\sum_l w_l$.\n",
"\n",
"Thus, the walk operator $W$ encodes the spectrum of $H$ as a function of eigenphases of $W$\n",
"s.t. $spectrum(H) = \\lambda cos(arg(spectrum(W)))$ where $arg(e^{i\\phi}) = \\phi$.\n",
Expand Down
17 changes: 10 additions & 7 deletions qualtran/bloqs/qubitization_walk_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@
class QubitizationWalkOperator(GateWithRegisters):
r"""Constructs a Szegedy Quantum Walk operator using LCU oracles SELECT and PREPARE.

For a Hamiltonian $H = \sum_l w_l H_l$ (s.t. $w_l > 0$ and $H_l$ are unitaries),
Constructs a Szegedy quantum walk operator $W = R_{L} . SELECT$, which is a product of
two reflections $R_{L} = (2|L><L| - I)$ and $SELECT=\sum_{l}|l><l|H_{l}$.

The action of $W$ partitions the Hilbert space into a direct sum of two-dimensional irreducible
vector spaces. For an arbitrary eigenstate $|k>$ of $H$ with eigenvalue $E_k$, $|\ell>|k>$ and
an orthogonal state $\phi_{k}$ span the irreducible two-dimensional space that $|\ell>|k>$ is
in under the action of $W$. In this space, $W$ implements a Pauli-Y rotation by an angle of
$-2arccos(E_{k} / \lambda)$ s.t. $W = e^{i arccos(E_k / \lambda) Y}$.
$-2arccos(E_{k} / \lambda)$ s.t. $W = e^{i arccos(E_k / \lambda) Y}$,
where $\lambda = \sum_l w_l$.

Thus, the walk operator $W$ encodes the spectrum of $H$ as a function of eigenphases of $W$
s.t. $spectrum(H) = \lambda cos(arg(spectrum(W)))$ where $arg(e^{i\phi}) = \phi$.
Expand Down Expand Up @@ -92,6 +94,11 @@ def signature(self) -> Signature:
def reflect(self) -> ReflectionUsingPrepare:
return ReflectionUsingPrepare(self.prepare, control_val=self.control_val, global_phase=-1)

@cached_property
def sum_of_lcu_coefficients(self) -> Optional[float]:
r"""value of $\lambda$, i.e. sum of absolute values of coefficients $w_l$."""
return self.prepare.l1_norm_of_coeffs

def decompose_from_registers(
self,
context: cirq.DecompositionContext,
Expand Down Expand Up @@ -132,17 +139,13 @@ def controlled(
):
c_select = self.select.controlled(control_values=control_values)
assert isinstance(c_select, SelectOracle)
return QubitizationWalkOperator(
c_select, self.prepare, control_val=control_values[0], power=self.power
)
return attrs.evolve(self, select=c_select, control_val=control_values[0])
raise NotImplementedError(
f'Cannot create a controlled version of {self} with control_values={control_values}.'
)

def with_power(self, new_power: int) -> 'QubitizationWalkOperator':
return QubitizationWalkOperator(
self.select, self.prepare, control_val=self.control_val, power=new_power
)
return attrs.evolve(self, power=new_power)

def __pow__(self, power: int):
return self.with_power(self.power * power)
Expand Down
5 changes: 3 additions & 2 deletions qualtran/bloqs/qubitization_walk_operator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,16 @@ def get_walk_operator_for_1d_ising_model(num_sites: int, eps: float) -> Qubitiza
def test_qubitization_walk_operator(num_sites: int, eps: float):
ham = get_1d_ising_hamiltonian(cirq.LineQubit.range(num_sites))
ham_coeff = [abs(ps.coefficient.real) for ps in ham]
qubitization_lambda = np.sum(ham_coeff)

walk = walk_operator_for_pauli_hamiltonian(ham, eps)
assert_valid_bloq_decomposition(walk)

qubitization_lambda = walk.sum_of_lcu_coefficients

g, qubit_order, walk_circuit = construct_gate_helper_and_qubit_order(walk)

L_state = np.zeros(2 ** len(g.quregs['selection']))
L_state[: len(ham_coeff)] = np.sqrt(ham_coeff / qubitization_lambda)
L_state[: len(ham_coeff)] = np.sqrt(np.array(ham_coeff) / qubitization_lambda)

assert len(walk_circuit.all_qubits()) < 23

Expand Down
Loading
Loading