"""
\********************************************************************************
* Copyright (c) 2024 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
********************************************************************************/
"""
from qrisp import z
from qrisp.alg_primitives.qae import amplitude_amplification
import numpy as np
[docs]
def IQAE(qargs, state_function, eps, alpha, mes_kwargs={}):
r"""
Accelerated Quantum Amplitude Estimation (IQAE). This function performs :ref:`QAE <QAE>` with a fraction of the quantum resources of the well-known `QAE algorithm <https://arxiv.org/abs/quant-ph/0005055>`_.
See `Accelerated Quantum Amplitude Estimation without QFT <https://arxiv.org/abs/2407.16795>`_.
The problem of iterative quantum amplitude estimation is described as follows:
* Given a unitary operator :math:`\mathcal{A}`, let :math:`\ket{\Psi}=\mathcal{A}\ket{0}\ket{\text{False}}`.
* Write :math:`\ket{\Psi}=\sqrt{a}\ket{\Psi_1}\ket{\text{True}}+\sqrt{1-a}\ket{\Psi_0}\ket{\text{False}}` as a superposition of the orthogonal good and bad components of :math:`\ket{\Psi}`.
* Find an estimate for $a$, the probability that a measurement of $\ket{\Psi}$ yields a good state.
Parameters
----------
qargs : list[QuantumVariable]
The list of QuantumVariables which represent the state,
the quantum amplitude estimation is performed on. The last variable in the list must be of type :ref:`QuantumBool`.
state_function : function
A Python function preparing the state :math:`\ket{\Psi}`.
This function will receive the variables in the list ``qargs`` as arguments in the
course of this algorithm.
eps : float
Accuracy $\epsilon>0$ of the algorithm.
alpha : float
Confidence level $\alpha\in (0,1)$ of the algorithm.
mes_kwargs : dict, optional
The keyword arguments for the measurement function. Default is an empty dictionary.
Returns
-------
a : float
An estimate $\hat{a}$ of $a$ such that
.. math::
\mathbb P\{|\hat{a}-a|<\epsilon\}\geq 1-\alpha
Examples
--------
We show the same **Numerical integration** example which can also be found in the :ref:`QAE documentation <QAE>`.
We wish to evaluate
.. math::
A=\int_0^1f(x)\mathrm dx.
For this, we set up the corresponding ``state_function`` acting on the variables in ``input_list``:
::
from qrisp import QuantumFloat, QuantumBool, control, z, h, ry, IQAE
import numpy as np
n = 6
inp = QuantumFloat(n,-n)
tar = QuantumBool()
input_list = [inp, tar]
For example, if $f(x)=\sin^2(x)$, the ``state_function`` can be implemented as follows:
::
def state_function(inp, tar):
h(inp)
N = 2**inp.size
for k in range(inp.size):
with control(inp[k]):
ry(2**(k+1)/N,tar)
Finally, we apply IQAE and obtain an estimate $a$ for the value of the integral $A=0.27268$.
::
a = IQAE(input_list, state_function, eps=0.01, alpha=0.01)
>>> a
0.26782038552705856
"""
# The oracle tagging the good states
def oracle_function(*args):
tar = args[-1]
z(tar)
E = 1/2 * pow(np.sin(np.pi * 3/14), 2) - 1/2 * pow(np.sin(np.pi * 1/6), 2)
F = 1/2 * np.arcsin(np.sqrt(2 * E))
C = 4/ (6*F + np.pi)
break_cond = 2 * eps + 1
#theta_b = 0
#theta_sh = 1
K_i = 1
m_i = 0
index_tot = 0
while break_cond > 2 * eps:
index_tot +=1
alp_i = C*alpha * eps * K_i
N_i = int(np.ceil(1/(2 * pow(E, 2) ) * np.log(2/alp_i) ) )
# Perform quantum step
qargs_dupl = [qarg.duplicate() for qarg in qargs]
A_i = quant_step( int((K_i -1 )/2) , N_i, qargs_dupl, state_function,
oracle_function, mes_kwargs )
for qarg in qargs_dupl:
qarg.delete()
# Compute new thetas
theta_b, theta_sh = compute_thetas(m_i, K_i, A_i, E)
# Compute new Li
L_new, m_new = compute_Li(m_i , K_i, theta_b, theta_sh)
m_i = m_new
K_i = L_new * K_i
break_cond = abs( theta_b - theta_sh )
final_res = np.sin((theta_b+theta_sh)/2)**2
return final_res
def quant_step(k, N, qargs, state_function, oracle_function, mes_kwargs):
"""
Performs the quantum step, i.e., Quantum Amplitude Amplification,
in accordance to `Accelerated Quantum Amplitude Estimation without QFT <https://arxiv.org/abs/2407.16795>`_
Parameters
----------
k : int
The amount of amplification steps, i.e., the power of :math:`\mathcal{Q}` in amplitude amplification.
N : int
The amount of shots, i.e., the amount of times the last qubit is measured after the amplitude amplification steps.
state_function : function
A Python function preparing the state :math:`\ket{\Psi}`.
This function will receive the variables in the list ``args`` as arguments in the
course of this algorithm.
oracle_function : function
A Python function tagging the good state :math:`\ket{\Psi_1}`.
This function will receive the variables in the list ``args`` as arguments in the
course of this algorithm.
mes_kwargs : dict, optional
The keyword arguments for the measurement function. Default is an empty dictionary.
"""
if k==0:
state_function(*qargs)
else:
state_function(*qargs)
amplitude_amplification(qargs, state_function, oracle_function, iter = k)
# store result of last qubit
# shot-based measurement
mes_kwargs["shots"] = N
res_dict = qargs[-1].get_measurement(**mes_kwargs)
# case of single dict return, this should not occur but to be safe
if True not in list(res_dict.keys()):
return 0
a_i = res_dict[True]
return a_i
def compute_thetas(m_i, K_i, A_i, E):
"""
Helper function to perform statistical evaluation and compute the angles for the next iteration.
See `the original paper <https://arxiv.org/abs/2407.16795>`_ , Algorithm 1.
Parameters
----------
m_i : Int
Used for the computation of the interval of allowed angles.
K_i : Int
Maximal amount of amplitude amplification steps for the next iteration.
A_i : Float
Share of ``1``-measurements in amplitude amplification steps
E :
:math:`\epsilon` limit
"""
b_max = max(A_i - E, 0)
sh_min = min(A_i + E, 1)
theta_b_intermed = update_angle(b_max, m_i)
theta_b = theta_b_intermed/K_i
sh_theta_intermed = update_angle(sh_min, m_i)
sh_theta = sh_theta_intermed/K_i
assert round( pow( np.sin(K_i * theta_b),2) , 8 ) == round(b_max, 8)
assert round( pow( np.sin(K_i * sh_theta),2), 8 ) == round(sh_min, 8)
return theta_b, sh_theta
def update_angle(old_angle, m_in):
"""
Subroutine to compute new angles.
Parameters
----------
old_angle : float
Old angle from last iteration.
m_in : int
Used for the computation of the interval of allowed angles.
"""
val_intermed1 = np.arcsin( np.sqrt(old_angle) ) - np.pi
val_intermed2 = np.arcsin( - np.sqrt(old_angle) )
cond_break = True
while cond_break :
if not (m_in*np.pi/2 <= val_intermed1 <= (m_in+1)*np.pi/2):
val_intermed1 += np.pi
else:
final_intermed = val_intermed1
cond_break = False
break
if not (m_in*np.pi/2 <= val_intermed2 <= (m_in+1)*np.pi/2):
val_intermed2 += np.pi
else:
final_intermed = val_intermed2
cond_break = False
break
return final_intermed
def compute_Li(m_i , K_i, theta_b, theta_sh):
"""
Helper function to perform statistical evaluation and compute further values for the next iteration.
See `the original paper <https://arxiv.org/abs/2407.16795>`_ , Algorithm 1.
Parameters
----------
m_i : int
Used for the computation of the interval of allowed angles.
K_i : int
Maximal amount of amplitude amplification steps for the next iteration.
theta_b : float
Lower bound for angle from last iteration.
theta_b : float
Upper bound for angle from last iteration.
"""
Li_list = (3,5,7)
# create the Li-values
for Li in Li_list:
# create the possible M_new vals
m_new_list = range(Li*m_i, Li*m_i +Li)
first_val = Li *K_i * theta_b
sec_val = Li *K_i * theta_sh
for m_new in m_new_list:
# check the conditions
if m_new*np.pi/2 <= first_val <= (m_new+1)*np.pi/2:
if m_new*np.pi/2 <= sec_val <= (m_new+1)*np.pi/2:
#assert m_new*np.pi/2 <= Li *K_i * theta_b <= (m_new+1)*np.pi/2
#assert m_new*np.pi/2 <= Li *K_i * theta_sh <= (m_new+1)*np.pi/2
elem1 = Li
elem2 = m_new
return elem1, elem2