Source code for qrisp.algorithms.gqsp.qet

"""
********************************************************************************
* Copyright (c) 2026 the Qrisp authors
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is
* available at https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************
"""

import numpy as np
import jax.numpy as jnp
from qrisp import QuantumBool
from qrisp.core.gate_application_functions import rx
from qrisp.environments import conjugate, control
from qrisp.alg_primitives.reflection import reflection
from qrisp.algorithms.gqsp.gqsp_angles import gqsp_angles
from qrisp.algorithms.gqsp.helper_functions import poly2cheb, _rescale_poly
from qrisp.block_encodings import BlockEncoding
from qrisp.jasp import jrange
from qrisp.operators import QubitOperator, FermionicOperator
from typing import Literal, TYPE_CHECKING

if TYPE_CHECKING:
    from jax.typing import ArrayLike


[docs] def QET( H: BlockEncoding | FermionicOperator | QubitOperator, p: "ArrayLike", kind: Literal["Polynomial", "Chebyshev"] = "Polynomial", rescale: bool = True, ) -> BlockEncoding: r""" Returns a BlockEncoding representing a polynomial transformation of the operator via `Quantum Eigenvalue Transform <https://arxiv.org/pdf/2312.00723>`_. For a block-encoded **Hermitian** operator $H$ and a **real, fixed parity** polynomial $p(x)$, this method returns a BlockEncoding of the operator $p(H)$. The Quantum Eigenvalue Transform is described as follows: * Given a Hermitian operator $H=\sum_i\lambda_i\ket{\lambda_i}\bra{\lambda_i}$ where $\lambda_i\in\mathbb R$ are the eigenvalues for the eigenstates $\ket{\lambda_i}$, * A quantum state $\ket{\psi}=\sum_i\alpha_i\ket{\lambda_i}$ where $\alpha_i\in\mathbb C$ are the amplitudes for the eigenstates $\ket{\lambda_i}$, * A (complex) polynomial $p(z)$, this transformation prepares a state proportional to .. math:: p(H)\ket{\psi}=\sum_i p(\lambda_i)\ket{\lambda_i}\bra{\lambda_i}\sum_j\alpha_j\ket{\lambda_j}=\sum_i p(\lambda_i)\alpha_i\ket{\lambda_i} Parameters ---------- H : BlockEncoding | FermionicOperator | QubitOperator The Hermitian operator to be transformed. p : ArrayLike 1-D array containing the polynomial coefficients, ordered from lowest order term to highest. kind : {"Polynomial", "Chebyshev"} The basis in which the coefficients are defined. - ``"Polynomial"``: $p(x) = \sum c_i x^i$ - ``"Chebyshev"``: $p(x) = \sum c_i T_i(x)$, where $T_i$ are Chebyshev polynomials of the first kind. Default is ``"Polynomial"``. rescale : bool If True (default), the method returns a block-encoding of $p(H)$. If False, the method returns a block-encoding of $p(H/\alpha)$ where $\alpha$ is the normalization factor for the block-encoding of the operator $H$. Returns ------- BlockEncoding A new BlockEncoding instance representing the transformed operator $p(H)$. Notes ----- - Improved efficiency compared to GQET for a real, fixed parity polynomial $p(x)$. Examples -------- Define a Hermitian matrix $A$ and a vector $\vec{b}$. :: import numpy as np A = np.array([[0.73255474, 0.14516978, -0.14510851, -0.0391581], [0.14516978, 0.68701415, -0.04929867, -0.00999921], [-0.14510851, -0.04929867, 0.76587818, -0.03420339], [-0.0391581, -0.00999921, -0.03420339, 0.58862043]]) b = np.array([0, 1, 1, 1]) Generate a BlockEncoding of $A$ and use QET to obtain a BlockEncoding of $p(H)$ for a real polynomial of even parity. :: from qrisp import * from qrisp.block_encodings import BlockEncoding from qrisp.gqsp import QET BA = BlockEncoding.from_array(A) BA_poly = QET(BA, np.array([1.,0.,1.])) # Prepares operand variable in state |b> def prep_b(): operand = QuantumVariable(2) prepare(operand, b) return operand @terminal_sampling def main(): operand = BA_poly.apply_rus(prep_b)() return operand res_dict = main() amps = np.sqrt([res_dict.get(i, 0) for i in range(len(b))]) Finally, compare the quantum simulation result with the classical solution: :: c = (np.eye(4) + A @ A) @ b c = c / np.linalg.norm(c) print("QUANTUM SIMULATION\n", amps, "\nCLASSICAL SOLUTION\n", c) #QUANTUM SIMULATION # [0.02405799 0.57657687 0.61493257 0.53743675] #CLASSICAL SOLUTION # [-0.024058 0.57657692 0.61493253 0.53743673] """ # is_real = jnp.allclose(p.imag, 0, atol=1e-6) # is_even = jnp.allclose(p[1::2], 0, atol=1e-6) is_odd = jnp.allclose(p[0::2], 0, atol=1e-6) ALLOWED_KINDS = {"Polynomial", "Chebyshev"} if kind not in ALLOWED_KINDS: raise ValueError( f"Invalid kind specified: '{kind}'. " f"Allowed kinds are: {', '.join(ALLOWED_KINDS)}" ) if isinstance(H, (QubitOperator, FermionicOperator)): H = BlockEncoding.from_operator(H) # Rescaling of the polynomial to account for scaling factor alpha of block-encoding if rescale: p = _rescale_poly(H.alpha, p, kind=kind) if kind == "Polynomial": p = poly2cheb(p) m = len(H._anc_templates) d = len(p) # Angles theta and lambda vanish for real polynomials https://arxiv.org/abs/2503.03026. # Implementation based on conjecture: phi has fixed parity iff p has fixed parity. # Combine two consecutive walk operators: (R U) (R U) = (R U R U ) = (R U_dg R U) = T_2 # This is qubitization step even if the block encoding unitary is not Hemitian. # See https://math.berkeley.edu/~linlin/qasc/qasc_notes.pdf (page 104). # If the parity is odd, there is a single (R U) at the end which is not followed by a rotation. # Since the QET is only successful if all ancillas are |0>, there is no need to control-(R U) # and the refelction acts as identity. angles, alpha = gqsp_angles(p) phi = angles[1][::-1] def T2(*args): with conjugate(H.unitary)(*args): reflection(args[:m]) reflection(args[:m]) def new_unitary(*args): rx(-2 * phi[0], args[0]) for i in jrange(1, (d + 1) // 2): with control(args[0], ctrl_state=0): T2(*args[1:]) rx(-2 * phi[2 * i], args[0]) with control(is_odd): H.unitary(*args[1:]) new_anc_templates = [QuantumBool().template()] + H._anc_templates return BlockEncoding( alpha, new_anc_templates, new_unitary, num_ops=H.num_ops, is_hermitian=False )