Source code for qrisp.algorithms.grover.grover_tools
"""\********************************************************************************* Copyright (c) 2025 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********************************************************************************/"""importnumpyasnpfromqrispimportQuantumArray,QuantumVariable,QuantumFloat,gate_wrap,gphase,h,mcx,mcp,mcz,p,x,z,merge,recursive_qs_search,conjugate,invert,controlfromqrisp.jaspimportcheck_for_tracing_mode,jrange# Applies the grover diffuser onto the (list of) quantum variable input_objectdefdiffuser(input_object,phase=np.pi,state_function=None,reflection_indices=None):r""" Applies the Grover diffuser onto (multiple) QuantumVariables. Parameters ---------- input_object : QuantumVariable or list[QuantumVariable] The (list of) QuantumVariables to apply the Grover diffuser on. phase : float or sympy.Symbol, optional Specifies the phase shift. The default is $\pi$, i.e. a multi-controlled Z gate. state_function : function, optional A Python function preparing the initial state. By default, the function prepares the uniform superposition state. refection_indices : list[int], optional A list indicating with respect to which variables the reflection is performed. By default, the reflection is performed with respect to all variables in ``input_object``. Examples -------- We apply the Grover diffuser onto several QuantumChars: >>> from qrisp import QuantumChar >>> from qrisp.grover import diffuser >>> q_ch_list = [QuantumChar(), QuantumChar(), QuantumChar()] >>> diffuser(q_ch_list) >>> print(q_ch_list[0].qs) :: ┌────────────┐ q_ch_0.0: ┤0 ├ │ │ q_ch_0.1: ┤1 ├ │ │ q_ch_0.2: ┤2 ├ │ │ q_ch_0.3: ┤3 ├ │ │ q_ch_0.4: ┤4 ├ │ │ q_ch_1.0: ┤5 ├ │ │ q_ch_1.1: ┤6 ├ │ │ q_ch_1.2: ┤7 diffuser ├ │ │ q_ch_1.3: ┤8 ├ │ │ q_ch_1.4: ┤9 ├ │ │ q_ch_2.0: ┤10 ├ │ │ q_ch_2.1: ┤11 ├ │ │ q_ch_2.2: ┤12 ├ │ │ q_ch_2.3: ┤13 ├ │ │ q_ch_2.4: ┤14 ├ └────────────┘ """ifisinstance(input_object,QuantumArray):input_object=[qvforqvininput_object.flatten()]ifisinstance(input_object,list)andreflection_indicesisNone:reflection_indices=[iforiinrange(len(input_object))]ifstate_functionisnotNone:definv_state_function(args):withinvert():state_function(*args)else:ifisinstance(input_object,list):definv_state_function(args):[h(qv)forqvinargs]else:definv_state_function(args):h(args)ifisinstance(input_object,list):withconjugate(inv_state_function)(input_object):ifcheck_for_tracing_mode():tag_state({input_object[i]:0foriinreflection_indices},phase=phase)else:tag_state({input_object[i]:"0"*input_object[i].sizeforiinreflection_indices},binary_values=True,phase=phase)gphase(np.pi,input_object[0][0])else:withconjugate(inv_state_function)(input_object):ifcheck_for_tracing_mode():tag_state({input_object:0},phase=phase)else:tag_state({input_object:input_object.size*"0"},binary_values=True,phase=phase)gphase(np.pi,input_object[0])returninput_objectdeftag_state(tag_specificator,binary_values=False,phase=np.pi):""" Applies a phase tag to (multiple) QuantumVariables. The tagged state is specified in the dictionary ``tag_specificator``. This dictionary should contain the QuantumVariables as keys and the labels of the states which should be tagged as values. Parameters ---------- tag_specificator : dict A dictionary specifying which state should be tagged. binary_values : bool, optional If set to True, the values in the tag_specificator dict have to be bitstrings instead of labels. The default is False. phase : float or sympy.Symbol, optional Specify the phase shift the tag should perform. The default is pi, i.e. a multi-controlled Z gate. Examples -------- We construct an oracle that tags the states -3 and 2 on two QuantumFloats :: from qrisp.grover import tag_state def test_oracle(qf_list): tag_dic = {qf_list[0] : -3, qf_list[1] : 2} tag_state(tag_dic) """qv_list=list(tag_specificator.keys())ifcheck_for_tracing_mode():states=[qv.encoder(tag_specificator[qv])forqvinqv_list]defconjugator(qv_list,temp_qf):foriinrange(len(qv_list)):mcx(qv_list[i],temp_qf[i],method="balauca",ctrl_state=states[i])m=len(qv_list)temp_qf=QuantumFloat(m)ifm==1:withconjugate(conjugator)(qv_list,temp_qf):withcontrol(phase==np.pi):z(temp_qf)withcontrol(phase!=np.pi):p(phase,temp_qf)else:withconjugate(conjugator)(qv_list,temp_qf):withcontrol(phase==np.pi):h(temp_qf[-1])mcx(temp_qf[:m-1],temp_qf[-1],method="balauca",ctrl_state=-1)h(temp_qf[-1])withcontrol(phase!=np.pi):mcp(phase,temp_qf,method="balauca",ctrl_state=-1)temp_qf.delete()else:states=[tag_specificator[qv]forqvinqv_list]ifnotlen(states):states=["1"*qv.sizeforqvinqv_list]bit_string=""fromqrisp.miscimportbin_repforiinrange(len(qv_list)):ifbinary_values:bit_string+=states[i][::-1]else:bit_string+=bin_rep(qv_list[i].encoder(states[i]),qv_list[i].size)[::-1]qubit_list=sum([list(qv.reg)forqvinqv_list],[])state=bit_stringifstate[-1]=="0":x(qubit_list[-1])# The control state is the state we are looking for without the base qubitctrl_state=state[:-1]ifphase==np.pi:mcz(qubit_list,ctrl_state=ctrl_state+"1")else:mcp(phase,qubit_list,ctrl_state=ctrl_state+"1")# Apply the final x gateifstate[-1]=="0":x(qubit_list[-1])
[docs]defgrovers_alg(qv_list,oracle_function,kwargs={},iterations=0,winner_state_amount=None,exact=False,):r""" Applies Grover's algorithm to a given oracle (in the form of a Python function). Parameters ---------- qv_list : QuantumVariable or list[QuantumVariable] A (list of) QuantumVariables on which to execute Grover's algorithm. oracle_function : function A Python function tagging the winner states. kwargs : dict, optional A dictionary containing keyword arguments for the oracle. The default is {}. iterations : int, optional The amount of Grover iterations to perfrom. winner_state_amount : int, optional If not given the amount of iterations, the established formula will be used based on the amount of winner states. The default is 1. exact : bool, optional If set to True, the `exact version <https://arxiv.org/pdf/quant-ph/0106071.pdf>` of Grover's algorithm will be evaluated. For this, the correct ``winner_state_amount`` has to be supplied and the oracle has to support the keyword argument ``phase`` which specifies how much the winner states are phaseshifted (in standard Grover this would be $\pi$). Raises ------ Exception Applied oracle introducing new QuantumVariables without uncomputing/deleting Examples -------- We construct an oracle that tags the states -3 and 2 on two QuantumFloats and apply Grover's algorithm to it. :: from qrisp import QuantumFloat #Create list of QuantumFloats qf_list = [QuantumFloat(2, signed = True), QuantumFloat(2, signed = True)] from qrisp.grover import tag_state, grovers_alg def test_oracle(qf_list): tag_dic = {qf_list[0] : -3, qf_list[1] : 2} tag_state(tag_dic) grovers_alg(qf_list, test_oracle) >>> from qrisp.misc import multi_measurement >>> print(multi_measurement(qf_list)) {(-3, 2): 0.9966, (0, 0): 0.0001, (0, 1): 0.0001, (0, 2): 0.0001, (0, 3): 0.0001, (0, -4): 0.0001, (0, -3): 0.0001, (0, -2): 0.0001, (0, -1): 0.0001, (1, 0): 0.0001, (1, 1): 0.0001, (1, 2): 0.0001, (1, 3): 0.0001, (1, -4): 0.0001, (1, -3): 0.0001, (1, -2): 0.0001, (1, -1): 0.0001, (2, 0): 0.0001, (2, 1): 0.0001, (2, 2): 0.0001, (2, 3): 0.0001, (2, -4): 0.0001, (2, -3): 0.0001, (2, -2): 0.0001, (2, -1): 0.0001, (3, 0): 0.0001, (3, 1): 0.0001, (3, 2): 0.0001, (3, 3): 0.0001, (3, -4): 0.0001, (3, -3): 0.0001, (3, -2): 0.0001, (3, -1): 0.0001, (-4, 0): 0.0001, (-4, 1): 0.0001, (-4, 2): 0.0001, (-4, 3): 0.0001, (-4, -4): 0.0001, (-4, -3): 0.0001, (-4, -2): 0.0001, (-4, -1): 0.0001, (-3, 0): 0.0001, (-3, 1): 0.0001, (-3, 3): 0.0001, (-3, -4): 0.0001, (-3, -3): 0.0001, (-3, -2): 0.0001, (-3, -1): 0.0001, (-2, 0): 0.0001, (-2, 1): 0.0001, (-2, 2): 0.0001, (-2, 3): 0.0001, (-2, -4): 0.0001, (-2, -3): 0.0001, (-2, -2): 0.0001, (-2, -1): 0.0001, (-1, 0): 0.0001, (-1, 1): 0.0001, (-1, 2): 0.0001, (-1, 3): 0.0001, (-1, -4): 0.0001, (-1, -3): 0.0001, (-1, -2): 0.0001, (-1, -1): 0.0001} **Exact Grovers Algorithm** In the next example, we will showcase the ``exact`` functionality. For this we create an oracle, which tags all the states of a QuantumVariable, that contain 3 ones and 2 zeros. To count the amount of ones we use quantum phase estimation on the operator .. math:: U = \text{exp}\left(\frac{i 2 \pi}{2^k} \sum_{i = 0}^{n-1} ( 1 - \sigma_{z}^i )\right) :: from qrisp import QPE, p, QuantumVariable, lifted from qrisp.grover import grovers_alg, tag_state import numpy as np def U(qv, prec = None, iter = 1): for i in range(qv.size): p(iter*2*np.pi/2**prec, qv[i]) @lifted def count_ones(qv): prec = int(np.ceil(np.log2(qv.size+1))) res = QPE(qv, U, precision = prec, iter_spec = True, kwargs = {"prec" : prec}) res <<= prec return res Quick test: >>> qv = QuantumVariable(5) >>> qv[:] = {"11000" : 1, "11010" : 1, "11110" : 1} >>> count_qf = count_ones(qv) >>> count_qf.qs.statevector() sqrt(3)*(|11000>*|2> + |11010>*|3> + |11110>*|4>)/3 We now define the oracle :: def counting_oracle(qv, phase = np.pi, k = 1): count_qf = count_ones(qv) tag_state({count_qf : k}, phase = phase) count_qf.uncompute() And evaluate Grover's algorithm :: n = 5 k = 3 qv = QuantumVariable(n) import math grovers_alg(qv, counting_oracle, exact = True, winner_state_amount = math.comb(n,k), kwargs = {"k" : k}) # noqa >>> print(qv) {'11100': 0.1, '11010': 0.1, '10110': 0.1, '01110': 0.1, '11001': 0.1, '10101': 0.1, '01101': 0.1, '10011': 0.1, '01011': 0.1, '00111': 0.1} We see that contrary to regular Grover's algorithm, the states which have not been tagged by the oracle have 0 percent measurement probability. """ifexactandwinner_state_amountisNone:raiseException("Tried to call exact Grover's algorithm without specifying ""winner_state_amount")elifwinner_state_amountisNone:winner_state_amount=1ifcheck_for_tracing_mode():importjax.numpyasjnpelse:importnumpyasjnpifisinstance(qv_list,list):N=2**jnp.sum(jnp.array([qv.sizeforqvinqv_list]))elifisinstance(qv_list,QuantumArray):N=2**sum(jnp.array([qv.sizeforqvinqv_list.flatten()]))elifisinstance(qv_list,QuantumVariable):N=2**qv_list.sizeifexact:# Implementation for phase calculation for exact grovers alg as in# https://arxiv.org/pdf/quant-ph/0106071.pdfiterations=1tmp=(jnp.sin(jnp.pi/(4*(iterations-1)+6))*(N/winner_state_amount)**0.5)ifcheck_for_tracing_mode():fromjax.laximportwhile_loopdefbody_fun(state):iterations,tmp,winner_state_amount=statereturniterations+1,jnp.sin(jnp.pi/(4*(iterations-1)+6))*(N/winner_state_amount)**0.5,winner_state_amountdefcond_fun(state):iterations,tmp,winner_state_amount=statereturntmp>1iterations,tmp,winner_state_amount=while_loop(cond_fun,body_fun,(iterations,tmp,winner_state_amount))else:whiletmp>1:iterations+=1tmp=(jnp.sin(jnp.pi/(4*(iterations-1)+6))*(N/winner_state_amount)**0.5)phi=2*jnp.arcsin(jnp.sin(jnp.pi/(4*(iterations-1)+6))*(N/winner_state_amount)**0.5)else:ifiterations==0:iterations=jnp.pi/4*jnp.sqrt(N/winner_state_amount)iterations=jnp.int64(jnp.round(iterations))ifisinstance(qv_list,(list,QuantumArray)):[h(qv)forqvinqv_list]else:h(qv_list)ifnotcheck_for_tracing_mode():merge(qv_list)qs=recursive_qs_search(qv_list)[0]qv_amount=len(qs.qv_list)foriinjrange(iterations):ifexact:oracle_function(qv_list,phase=phi,**kwargs)diffuser(qv_list,phase=phi)else:oracle_function(qv_list,**kwargs)diffuser(qv_list)ifnotcheck_for_tracing_mode():ifqv_amount!=len(qs.qv_list):raiseException("Applied oracle introducing new QuantumVariables without uncomputing/deleting")
# Workaround to keep the docstring but still gatewraptemp=diffuser.__doc__diffuser=gate_wrap(permeability=[],is_qfree=False)(diffuser)diffuser.__doc__=temptemp=tag_state.__doc__tag_state=gate_wrap(permeability="args",is_qfree=True)(tag_state)tag_state.__doc__=temp
Get in touch!
If you are interested in Qrisp or high-level quantum algorithm research in general connect with us on our
Slack workspace.