"""\********************************************************************************* Copyright (c) 2023 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********************************************************************************/"""importtracebackimportnumpyasnpimportsympydefbin_rep(n,bits):ifn<0:raiseException("Only positive numbers are supported")ifn>=2**bits:raiseException(str(n)+" can't be represented as a "+str(bits)+" bit number")returnbin(n)[2:].zfill(bits)zero_string="".join(["0"forkinrange(bits)])return(zero_string+bin(n)[2:])[-bits:]defint_encoder(qv,encoding_number):ifencoding_number>2**len(qv)-1:raiseException("Not enough qubits to encode integer "+str(encoding_number))binary_rep=bin_rep(encoding_number,len(qv))[::-1]fromqrispimportxforiinrange(len(binary_rep)):ifint(binary_rep[i]):x(qv[i])# Calculates the binary expression of a given integer and returns it as an array of# length bitsdefint_as_array(k,bit):bin_str=bin_rep(k,bit)returnnp.array([int(c)forcinbin_str])defarray_as_int(array):result=0forkinrange(len(array)):ifarray[::-1][k]:result+=2**(k)returnresult# Decomposes the circuit qc until no more decompositions are possible and then counts# the cnot operationsdefcnot_count(qc):qc=qc.transpile()gate_count_dic=qc.count_ops()cnot_count=0forgate_namein["cx","cy","cz"]:cnot_count+=gate_count_dic.get(gate_name,0)returncnot_countdefis_inv(x,bit):# return (math.gcd(int(np.round(x, 3)),2**bit) == 1)# The only divisors 2**bit has is powers of 2# ie. if tha factorization of x doesn't contain any powers of 2 it is invertible# in other words: x is invertible if it is unevenreturnbool(int(x)%2)defget_depth_dic(qc,transpile_qc=True,depth_indicator=lambdax:1):iflen(qc.qubits)==0:return{}iftranspile_qc:qc=qc.transpile()# Assign each bit in the circuit a unique integer# to index into op_stack.bit_indices={bit:idxforidx,bitinenumerate(qc.qubits+qc.clbits)}# If no bits, return 0ifnotbit_indices:return0# A list that holds the height of each qubit# and classical bit.op_stack=[0]*len(bit_indices)# Here we are playing a modified version of# Tetris where we stack gates, but multi-qubit# gates, or measurements have a block for each# qubit or cbit that are connected by a virtual# line so that they all stacked at the same depth.# Conditional gates act on all cbits in the register# they are conditioned on.# We treat barriers or snapshots different as# They are transpiler and simulator directives.# The max stack height is the circuit depth.forinstrinqc.data:ifinstr.op.namein["qb_alloc","qb_dealloc","gphase"]:continueqargs=instr.qubitscargs=instr.clbitslevels=[]reg_ints=[]# If count then add one to stack heightsgate_depth=depth_indicator(instr.op)forind,reginenumerate(qargs+cargs):# Add to the stacks of the qubits and# cbits used in the gate.reg_ints.append(bit_indices[reg])levels.append(op_stack[reg_ints[ind]]+gate_depth)max_level=max(levels)forindinreg_ints:op_stack[ind]=max_levelreturn{qc.qubits[i]:op_stack[i]foriinrange(len(qc.qubits))}
[docs]defgate_wrap(*args,permeability=None,is_qfree=None,name=None,verify=False):""" Decorator to bundle up the quantum instructions of a function into a single gate object. Bundled gate objects can help debugging as it allows for a more clear QuantumCircuit visualisation. Furthermore, bundling up functions is relevant for Qrisps uncomputation algorithm. When bundling up for uncomputation, this decorator provides the means to annotate the gate objects with information about its permeability and qfree-ness. For further information about these concepts check the :ref:`uncomputation documentation<uncomputation>`. Specifying this information allows to skip the computationally costly automatic determination at runtime. Note that the specified information is not checked for correctness as this would defy the purpose. A shorthand for ``gate_wrap(permeability = "args", is_qfree = True)`` is the :meth:`lifted <qrisp.lifted>` decorator. .. warning:: Using ``gate_wrap`` without specifying permeability and ``qfree``-ness on functions processing a lot of qubits, can causes long compile times, since the unitaries of these gates have to be determined numerically. .. warning:: Incorrect information about permeability and ``qfree``-ness can yield incorrect compilation results. If you are unsure, use the ``verify`` keyword on a small scale first. Parameters ---------- permeability : string or list, optional Specify the permeability behavior of the function. When given "args", it is assumed that the gate is permeable only on the qubits of the arguments. When given "full", it is assumed that the gate is permeable on every qubit it acts on (i.e. also the result). When given a list of integers it is assumed, that the gate is permeable on the qubits of the arguments corresponding to the integers. The default is None. is_qfree : bool, optional Specify the qfree-ness of the function. The default is None. name : string, optional String which will be used for naming the gate object. The default is None. verify : bool, optional If set to ``True``, the specified information about permeability and ``qfree``-ness will be checked numerically. The default is ``False``. Examples -------- We create a simple function wrapping up multiple gates: :: from qrisp import QuantumVariable, cx, x, h, z, gate_wrap @gate_wrap def example_function(a, b): cx(a,b) x(a) cx(b,a) h(b) a = QuantumVariable(3) b = QuantumVariable(3) example_function(a, b) >>> print(a.qs) :: QuantumCircuit: -------------- ┌───────────────────┐ b.0: ┤0 ├ │ │ b.1: ┤1 ├ │ │ b.2: ┤2 ├ │ example_function │ a.0: ┤3 ├ │ │ a.1: ┤4 ├ │ │ a.2: ┤5 ├ └───────────────────┘ Live QuantumVariables: --------------------- QuantumVariable a QuantumVariable b >>> print(a.qs.transpile()) :: ┌───┐ ┌───┐ b.0: ┤ X ├─────────────────■──┤ H ├────────── └─┬─┘┌───┐ │ └───┘┌───┐ b.1: ──┼──┤ X ├────────────┼────■──┤ H ├───── │ └─┬─┘┌───┐ │ │ └───┘┌───┐ b.2: ──┼────┼──┤ X ├───────┼────┼────■──┤ H ├ │ │ └─┬─┘┌───┐┌─┴─┐ │ │ └───┘ a.0: ──■────┼────┼──┤ X ├┤ X ├──┼────┼─────── │ │ ├───┤└───┘┌─┴─┐ │ a.1: ───────■────┼──┤ X ├─────┤ X ├──┼─────── │ ├───┤ └───┘┌─┴─┐ a.2: ────────────■──┤ X ├──────────┤ X ├───── └───┘ └───┘ In the next example, we create a function that performs no quantum gates and specify that it is qfree and permeable on the second argument but not on the first. :: from qrisp import QuantumCircuit @gate_wrap(permeability = [1], is_qfree = True) def example_function(arg_0, arg_1): res = QuantumVariable(1) #Append an identity gate res.qs.append(QuantumCircuit(3).to_gate(), [arg_0, arg_1, res]) return res qv_0 = QuantumVariable(1) qv_1 = QuantumVariable(1) res = example_function(qv_0, qv_1) >>> print(qv_0.qs) :: QuantumCircuit: -------------- ┌───────────────────┐ qv_0.0: ┤0 ├ │ │ qv_1.0: ┤1 example_function ├ │ │ res.0: ┤2 ├ └───────────────────┘ Live QuantumVariables: --------------------- QuantumVariable qv_0 QuantumVariable qv_1 QuantumVariable res >>> qv_1.uncompute() >>> print(qv_0.qs) :: QuantumCircuit: -------------- ┌───────────────────┐ qv_0.0: ┤0 ├ │ │ qv_1.0: ┤1 example_function ├ │ │ res.0: ┤2 ├ └───────────────────┘ Live QuantumVariables: --------------------- QuantumVariable qv_0 QuantumVariable res Since ``arg_1`` is marked as permeable, there are no further gates required for uncomputation. The situation is different for the other two QuantumVariables, where #the qubits are not marked as permeable. >>> qv_0.uncompute(do_it = False) >>> res.uncompute() >>> print(qv_0.qs) :: QuantumCircuit: -------------- ┌───────────────────┐┌──────────────────────┐ qv_0.0: ┤0 ├┤0 ├ │ ││ │ qv_1.0: ┤1 example_function ├┤1 example_function_dg ├ │ ││ │ res.0: ┤2 ├┤2 ├ └───────────────────┘└──────────────────────┘ Live QuantumVariables: --------------------- """iflen(args):returngate_wrap_inner(args[0])else:defgate_wrap_helper(function):returngate_wrap_inner(function,permeability=permeability,is_qfree=is_qfree,name=name,verify=verify,)returngate_wrap_helper
defgate_wrap_inner(function,permeability=None,is_qfree=None,name=None,verify=False):defwrapped_function(*args,permeability=permeability,is_qfree=is_qfree,verify=verify,**kwargs):wrapped_function.__name__=function.__name__fromqrisp.circuitimportQubitfromqrisp.coreimportrecursive_qs_search,recursive_qv_searchfromqrisp.environmentsimportGateWrapEnvironmentfromqrispimportmerge,QuantumVariable,QuantumArraytry:qs=find_qs(args)except:qs_list=recursive_qs_search([args,kwargs])qs=qs_list[0]initial_qubits=set(qs.qubits)ifnameisNone:gwe=GateWrapEnvironment(name=function.__name__)else:gwe=GateWrapEnvironment(name=name)withgwe:result=function(*args,**kwargs)iflen(qs.env_stack):gwe.compile()ifgweinqs.data:qs.data.remove(gwe)ifgwe.instructionisNone:returnresultcreated_qubits=set(qs.qubits)-initial_qubitsancillas=[]forqbincreated_qubits:ifqb.allocatedisFalse:ancillas.append(qb)ifis_qfreeisnotNone:ifverifyandis_qfree:fromqrisp.permeabilityimportis_qfreeasis_qfree_functionifnotis_qfree_function(gwe.instruction.op):raiseException(f"Verification of qfree-ness for function {function.__name__} "f"failed")gwe.instruction.op.is_qfree=is_qfreeifpermeabilityisnotNone:permeability_dict={i:Noneforiinrange(gwe.instruction.op.num_qubits)}permeable_qubits=[]not_permeable_qubits=[]ifisinstance(permeability,list):foriinrange(len(args)):ifiinpermeability:extension_list=permeable_qubitselse:extension_list=not_permeable_qubitsarg=args[i]ifisinstance(arg,QuantumVariable):extension_list+=arg.regelifisinstance(arg,(tuple,list)):foriteminarg:ifisinstance(item,Qubit):extension_list.append(item)elifisinstance(item,QuantumVariable):extension_list+=item.regelifisinstance(arg,QuantumArray):forqvinarg.flatten():extension_list+=qv.regifisinstance(result,QuantumVariable):not_permeable_qubits+=result.regelifisinstance(result,(tuple,list)):foriteminresult:ifisinstance(item,Qubit):not_permeable_qubits.append(item)elifisinstance(item,QuantumVariable):not_permeable_qubits+=item.regelifisinstance(result,QuantumArray):forqvinresult.flatten():not_permeable_qubits+=qv.regelifisinstance(permeability,str):forarginargs:ifisinstance(arg,QuantumVariable):permeable_qubits+=arg.regelifisinstance(arg,(tuple,list)):foriteminarg:ifisinstance(item,Qubit):permeable_qubits.append(item)elifisinstance(item,QuantumVariable):permeable_qubits+=item.regelifisinstance(arg,QuantumArray):forqvinarg.flatten():permeable_qubits+=qv.regifpermeability=="full":extension_list=permeable_qubitselifpermeability=="args":extension_list=not_permeable_qubitselse:raiseException(f"Don't know permeability option {permeability}")ifisinstance(result,QuantumVariable):extension_list+=result.regelifisinstance(result,(tuple,list)):foriteminresult:ifisinstance(item,Qubit):extension_list.append(item)elifisinstance(item,QuantumVariable):extension_list+=item.regelifisinstance(result,QuantumArray):forqvinresult.flatten():extension_list+=qv.regforiinrange(len(gwe.instruction.qubits)):qb=gwe.instruction.qubits[i]ifqbinpermeable_qubits:permeability_dict[i]=Trueelifqbinnot_permeable_qubits:permeability_dict[i]=Falseelifqbinancillas:# Even though ancilla qubits are permeable, we want to be able to# use the gate_wrap decorator as an interface to perform# recomputation. If we mark them as permeable, Unqomp won't wrap# the uncomputed gate in alloc/dealloc gates but instead wrap both# the computation gate and the recomputation in alloc/dealloc gates# To undestand this behavior better consider the following example# from qrisp import QuantumFloat# a = QuantumFloat(2)# qf_res = a * a# qf_res.uncompute()# print(qf_res.qs)# If we mark the ancilla qubits as permeable, this gives# QuantumCircuit:# ---------------# ┌──────────┐┌──────────┐┌─────────────┐# a.0: ┤ qb_alloc ├┤0 ├┤0 ├──────────────# ├──────────┤│ ││ │# a.1: ┤ qb_alloc ├┤1 ├┤1 ├──────────────# ├──────────┤│ ││ │┌────────────┐# return.0: ┤ qb_alloc ├┤2 ├┤2 ├┤ qb_dealloc ├# ├──────────┤│ ││ │├────────────┤# return.1: ┤ qb_alloc ├┤3 __mul__ ├┤3 __mul___dg ├┤ qb_dealloc ├# ├──────────┤│ ││ │├────────────┤# return.2: ┤ qb_alloc ├┤4 ├┤4 ├┤ qb_dealloc ├# ├──────────┤│ ││ │├────────────┤# return.3: ┤ qb_alloc ├┤5 ├┤5 ├┤ qb_dealloc ├# ├──────────┤│ ││ │├────────────┤# sbp_anc_0.0: ┤ qb_alloc ├┤6 ├┤6 ├┤ qb_dealloc ├# └──────────┘└──────────┘└─────────────┘└────────────┘# Live QuantumVariables:# ----------------------# QuantumFloat a# If we set them to non-permeable, we get instead# QuantumCircuit:# ---------------# ┌──────────┐┌──────────┐ ┌─────────────┐»# a.0: ┤ qb_alloc ├┤0 ├──────────────────────────┤0 ├»# ├──────────┤│ │ │ │»# a.1: ┤ qb_alloc ├┤1 ├──────────────────────────┤1 ├»# ├──────────┤│ │ │ │»# return.0: ┤ qb_alloc ├┤2 ├──────────────────────────┤2 ├»# ├──────────┤│ │ │ │»# return.1: ┤ qb_alloc ├┤3 __mul__ ├──────────────────────────┤3 __mul___dg ├»# ├──────────┤│ │ │ │»# return.2: ┤ qb_alloc ├┤4 ├──────────────────────────┤4 ├»# ├──────────┤│ │ │ │»# return.3: ┤ qb_alloc ├┤5 ├──────────────────────────┤5 ├»# ├──────────┤│ │┌────────────┐┌──────────┐│ │»# sbp_anc_0.0: ┤ qb_alloc ├┤6 ├┤ qb_dealloc ├┤ qb_alloc ├┤6 ├»# └──────────┘└──────────┘└────────────┘└──────────┘└─────────────┘»# «# « a.0: ──────────────# «# « a.1: ──────────────# « ┌────────────┐# « return.0: ┤ qb_dealloc ├# « ├────────────┤# « return.1: ┤ qb_dealloc ├# « ├────────────┤# « return.2: ┤ qb_dealloc ├# « ├────────────┤# « return.3: ┤ qb_dealloc ├# « ├────────────┤# «sbp_anc_0.0: ┤ qb_dealloc ├# « └────────────┘# Live QuantumVariables:# ----------------------# QuantumFloat a# In both cases, sbp_anc is recomputed, but in the second case, the# reallocation allows the compiler to use the free qubit elsewhere# and afterwards pick a potentially different qubit for the# recomputation.permeability_dict[i]=False# for i in range(len(gwe.instruction.qubits)):# if permeability_dict[i] is None:# permeability_dict[i] = Falseifverify:fromqrisp.permeabilityimportis_permeablepermeable_qubit_indices=[]foriinrange(gwe.instruction.op.num_qubits):ifpermeability_dict[i]:permeable_qubit_indices.append(i)ifnotis_permeable(gwe.instruction.op,permeable_qubit_indices):raiseException(f"Verification of permeability for function "f"{function.__name__} failed")gwe.instruction.op.permeability=permeability_dictreturnresultreturnwrapped_functiondeffind_qs(args):ifhasattr(args,"qs"):returnargs.qs()fromqrispimportQuantumVariable,QuantumArray,Qubitforarginargs:ifisinstance(arg,(QuantumVariable,QuantumArray)):returnarg.qsifisinstance(arg,Qubit):returnarg.qs()else:forarginargs:ifisinstance(arg,(list,tuple)):try:returnfind_qs(arg)except:passifisinstance(arg,dict):try:returnfind_qs(arg.items())except:passraiseException("Couldn't find QuantumSession")# Function to measure multiple quantum variables at once to assess their entanglement
[docs]defmulti_measurement(qv_list,shots=None,backend=None):""" This functions facilitates the measurement of multiple QuantumVariables at the same time. This can be used if the entanglement structure between several QuantumVariables is of interest. Parameters ---------- qv_list : list[QuantumVariable] A list of QuantumVariables. shots : int, optional The amount of shots to perform. The default is given by the backend used. backend : BackendClient, optional The backend to evaluate the compiled QuantumCircuit on. By default, the backend from default_backend.py will be used. Raises ------ Exception Tried to perform measurement with open environments. Returns ------- counts_list : list A list of tuples. The first element of each tuple is a tuple again and contains the labels of the QuantumVariables. The second element is a float and indicates the probability of measurement. Examples -------- We entangle three QuantumFloats via addition and perform a multi-measurement: >>> from qrisp import QuantumFloat, h, multi_measurement >>> qf_0 = QuantumFloat(4) >>> qf_1 = QuantumFloat(4) >>> qf_0[:] = 3 >>> qf_1[:] = 2 >>> h(qf_1[0]) >>> qf_sum = qf_0 + qf_1 >>> multi_measurement([qf_0, qf_1, qf_sum]) {(3, 2, 5): 0.5, (3, 3, 6): 0.5} """ifbackendisNone:ifqv_list[0].qs.backendisNone:fromqrisp.default_backendimportdef_backendbackend=def_backendelse:backend=qv_list[0].qs.backendiflen(qv_list[0].qs.env_stack)!=0:raiseException("Tried to perform measurement with open environments")fromqrispimportmergemerge(qv_list)# Copy circuit in order to prevent modificationfromqrispimportQuantumArray,QuantumVariable,recursive_qv_searchfromqrisp.core.compilationimportqompilertemp=recursive_qv_search(qv_list)compiled_qc=qompiler(qv_list[0].qs,intended_measurements=sum([qv.regforqvintemp],[]))# compiled_qc = qv_list[0].qs.copy()# Add classical registers for the measurement results to be stored incl_reg_list=[]forvarinqv_list[::-1]:cl_reg=[]ifisinstance(var,QuantumArray):qubits=sum([qv.regforqvinvar.flatten()[::-1]],[])elifisinstance(var,QuantumVariable):qubits=var.regelse:raiseException(f"Found type {type(var)} in measurement list")foriinrange(len(qubits)):cl_reg.append(compiled_qc.add_clbit())cl_reg_list.append(cl_reg)# Add measurement instructioncompiled_qc.measure(qubits,cl_reg)# counts = execute(qs_temp, backend, basis_gates = basis_gates,# noise_model = noise_model, shots = shots).result().get_counts()counts=backend.run(compiled_qc,shots)counts={k:counts[k]forkinsorted(counts)}shots=sum(counts.values())# Convert the labeling bistrings of counts into list of labelsnew_counts={}foriinrange(len(counts)):# Retrieve the separated strings of each measurement variablecounts_strings=[]counts_bitstring=list(counts.keys())[i]bitstring_adress=0forjinrange(len(cl_reg_list)):cl_reg=cl_reg_list[::-1][j]counts_strings.append(counts_bitstring[bitstring_adress:bitstring_adress+len(cl_reg)][::-1])bitstring_adress+=len(cl_reg)# Convert to integers and insert outcome labelscounts_values=[]forjinrange(len(counts_strings)):outcome_int=int(counts_strings[j][::-1],2)try:label=qv_list[j].decoder(outcome_int)ifisinstance(label,np.ndarray):fromqrispimportOutcomeArraylabel=OutcomeArray(label)counts_values.append(label)exceptAttributeError:counts_values.append(outcome_int)# Create arrayarray_state=tuple(counts_values)try:no_of_shots_executed=sum(counts.values())new_counts[array_state]=counts[list(counts.keys())[i]]/no_of_shots_executedexceptTypeError:raiseException("Tried to create measurement outcome dic for QuantumVariable ""with unhashable labels")# Append to the counts list# counts_list.append((array_state, counts[list(counts.keys())[i]]/shots))# Sort counts_list such the most probable values come firstnew_counts=dict(sorted(new_counts.items(),key=lambdaitem:-item[1]))returnnew_counts
# Function to apply a phase function of signature phase_function(x,y,z..) -> float# which specifies the phase for each constellation of outcome labels of the quantum# variables in qv_list.defapp_phase_function(qv_list,phase_function,t=1,**kwargs):# Prepare the list of index tuples# For this we first create a list of outcome indices of each qv firstindex_lists=[list(range(2**qv.size))forqvinqv_list]# We now calculate the direct product in order to obtain every possible combinationfromitertoolsimportproductproduct_index_list=list(product(*index_lists))# The next step is to iterate over every combination in order to determine the# phases.phases=[]foriinrange(len(product_index_list)):# Calculate the outcome labels of the current constellation of indiceslabels=[qv_list[j].decoder(product_index_list[i][j])forjinrange(len(qv_list))]# Calculate the phasephases.append(phase_function(*labels,**kwargs)*t)# Synthesize phasefromqrispimportgray_phase_synth_qb_listgray_phase_synth_qb_list(qv_list[0].qs,sum([qv.reg[::-1]forqvinqv_list],[]),phases)
[docs]defas_hamiltonian(hamiltonian):r""" Decorator that recieves a regular Python function (returning a float) and returns a function of QuantumVariables, applying phases based on the function's output. Parameters ---------- hamiltonian : function A function of arbitrary (non-quantum) variables returning a float. Returns ------- hamiltonian_application : function A function of QuantumVariables, which applies the phase dictated by the hamiltonian to the corresponding states. Examples -------- In this example we will demonstrate how a phase function with multiple arguments can be synthesized. For this we will create a phase function which encodes the fourier transform of different integers on the QuantumFloat x conditioned on the value of a QuantumChar ch. We will then apply the inverse Fourier transform to x and measure the results. :: import numpy as np from qrisp import QuantumChar, QuantumFloat, QFT, h #Create Variables x_size = 3 x = QuantumFloat(x_size, 0, signed = False) ch = QuantumChar() # Bring x into uniform superposition so the phase function application yields # a fourier transformed computation basis state h(x) #Bring ch into partial superposition (here |a> + |b> + |c> + |d>) h(ch[0]) h(ch[1]) from qrisp import multi_measurement, as_hamiltonian #In order to define the hamiltonian, we use regular Python syntax #The decorator "as_hamiltonian" turns it into a function #that takes Quantum Variables as arguments. The decorator will add the #keyword argument t to the function which mimics the t in exp(i*H*t) @as_hamiltonian def apply_multi_var_hamiltonian(ch, x): if ch == "a": k = 2 elif ch == "b": k = 7 elif ch == "c": k = 3 else: k = 4 #Return phase value #This is the phase distribution of the Fourier-transform #of the computational basis state |k> return k*x * 2*np.pi/2**x_size #Apply Hamiltonian apply_multi_var_hamiltonian(ch,x, t = 1) #Apply inverse Fourier transform QFT(x, inv = True) Acquire measurement results >>> multi_measurement([ch, x]) {('a', 2): 0.25, ('b', 7): 0.25, ('c', 3): 0.25, ('d', 4): 0.25} We see that the measurement results correspond to what we specified in the Hamiltonian. In Bra-Ket notation, before applying the Hamiltonian, we are in the state .. math:: \ket{\psi} = \frac{1}{\sqrt{4}}(\ket{a} + \ket{b} + \ket{c} + \ket{d}) \left( \frac{1}{\sqrt{8}} \sum_{x = 0}^8 \ket{x} \right) We then apply the Hamiltonian: .. math:: \text{exp}(i\text{H(ch, x)})\ket{\psi} = \frac{1}{\sqrt{32}} ( &\ket{a} \sum_{x = 0}^{2^3-1} \text{exp}(2x \frac{2 \pi i}{2^3}) \ket{x} \\ +&\ket{b} \sum_{x = 0}^{2^3-1} \text{exp}(7x \frac{2 \pi i}{2^3}) \ket{x} \\ +&\ket{c} \sum_{x = 0}^{2^3-1} \text{exp}(3x \frac{2 \pi i}{2^3}) \ket{x} \\ +&\ket{d} \sum_{x = 0}^{2^3-1} \text{exp}(4x \frac{2 \pi i}{2^3}) \ket{x}) For each branch, the QuantumFloat tensor-factor is in a Fourier-transformed computational basis state. Thus, if we apply the inverse QFT, we receive: .. math:: &\text{QFT}^{-1}\text{exp}(i\text{H(ch, x)})\ket{\psi} \\ = & \frac{1}{\sqrt{4}} (\ket{a} \ket{2} + \ket{b} \ket{7} +\ket{c} \ket{3} + \ket{d} \ket{4}) """defhamiltonian_application(*args,t=1,**kwargs):gate_wrap(app_phase_function)(args,hamiltonian,t=t,**kwargs)# app_phase_function(args, hamiltonian, t = t, **kwargs)returnhamiltonian_application
[docs]defperm_lock(qubits):""" Locks a list of qubits such that only permeable gates can be executed on these qubits. This means that an error will be raised if the user attempts to perform any operation involving these qubits if the operation does not commute with the Z-operator of this qubit. For more information, what a permeable gate is, check the :ref:`uncomputation documentation <uncomputation>`. This can be helpfull as it forbids all operations that change that computational basis state of this qubit but still allow controling on this qubit or applying phase gates. The effect of this function can be reversed using perm_unlock. Parameters ---------- qubits : list[Qubit] or QuantumVariable The qubits to phase-tolerantly lock. Examples -------- We create a QuantumChar, perm-lock it's Qubits and attempt to initialize. >>> from qrisp import QuantumChar, perm_lock, cx, p >>> q_ch_0 = QuantumChar() >>> perm_lock(q_ch_0) >>> q_ch_0[:] = "g" Exception: Tried to perform non-permeable operations on perm_locked qubits We now create a second QuantumChar and perform a CNOT gate >>> q_ch_1 = QuantumChar() >>> cx(q_ch_0[3], q_ch_1[2]) Phase-gates are possible, too >>> p(0.1, q_ch_0) """fromqrisp.circuit.quantum_circuitimportconvert_to_qb_listforqbinconvert_to_qb_list(qubits):ifisinstance(qb,list):foriteminqb:perm_lock(item)continueqb.perm_lock=True
[docs]defperm_unlock(qubits):""" Reverses the effect of "perm_lock". Parameters ---------- qubits : list[Qubit] or QuantumVariable The qubits to phase-tolerantly unlock. Examples -------- We create a QuantumChar, perm-lock it's Qubits and attempt to initialize. >>> from qrisp import QuantumChar, perm_lock, perm_unlock >>> q_ch = QuantumChar() >>> perm_lock(q_ch) >>> q_ch[:] = "g" Exception: Tried to perform non-permeable operations on perm_locked qubits >>> perm_unlock(q_ch) >>> q_ch[:] = "g" >>> print(q_ch) {'g': 1.0} """fromqrisp.circuit.quantum_circuitimportconvert_to_qb_listforqbinconvert_to_qb_list(qubits):ifisinstance(qb,list):foriteminqb:perm_unlock(item)continueqb.perm_lock=False
[docs]deflock(qubits):""" Locks a list of qubits, implying an error will be raised if the user tries to perform any operation involving these qubits. This can be reversed by calling unlock. Parameters ---------- qubits : list[Qubit] or QuantumVariable The list of Qubits to lock. Examples -------- We create a QuantumChar, lock it's Qubits and attempt to initialize. >>> from qrisp import QuantumChar, lock >>> q_ch = QuantumChar() >>> lock(q_ch) >>> q_ch[:] = "g" Exception: Tried to operation on locked qubits """fromqrisp.circuit.quantum_circuitimportconvert_to_qb_listforqbinconvert_to_qb_list(qubits):ifisinstance(qb,list):foriteminqb:lock(item)continueqb.lock=True
[docs]defunlock(qubits):""" Reverses the effect of "lock". Parameters ---------- qubits : list[Qubit] or QuantumVariable The list of Qubits to lock. Examples -------- We create a QuantumChar, lock it's Qubits and attempt to initialize. >>> from qrisp import QuantumChar, lock, unlock >>> q_ch = QuantumChar() >>> lock(q_ch) >>> q_ch[:] = "g" Exception: Tried to perform operations on locked qubits We now unlock and try again >>> unlock(q_ch) >>> q_ch[:] = "g" >>> print(q_ch) {'g': 1.0} """fromqrisp.circuit.quantum_circuitimportconvert_to_qb_listforqbinconvert_to_qb_list(qubits):ifisinstance(qb,list):foriteminqb:unlock(item)continueqb.lock=False
defbenchmark_function(function):defbenchmarked_function(*args,sort_stats="tottime",stat_amount=20,**kwargs):defslow_function():function(*args,**kwargs)importcProfileimportpstatsprofile=cProfile.Profile()profile.runcall(slow_function)ps=pstats.Stats(profile)ps.strip_dirs()ps.sort_stats(sort_stats)ps.print_stats(stat_amount)returnbenchmarked_functiondefcustom_qv(labels,decoder=None,qs=None,name=None):ifnotisinstance(labels,list):raiseException("Tried to create custom QuantumVariable without providing a list type")iflen(labels)==0:raiseException("Tried to create custom QuantumVariable without providing labels")eliflen(labels)==1:n=1else:n=int(np.ceil(np.log2(len(labels))))fromqrispimportQuantumVariableclassCustomQuantumVariable(QuantumVariable):def__init__(self,qs=None,name=None):super().__init__(n,qs=qs,name=name)defdecoder(self,x):ifdecoderisNone:ifx<len(labels):returnlabels[x]else:return"undefined_label_"+str(x)returndecoder(x)returnCustomQuantumVariable(qs=qs,name=name)definit_state(qv,target_array):fromqiskit.circuit.library.data_preparation.state_preparationimport(StatePreparation,)qiskit_qc=StatePreparation(target_array).definitionfromqrispimportQuantumCircuitinit_qc=QuantumCircuit.from_qiskit(qiskit_qc)# Find global phase correctionfromqrisp.simulatorimportstatevector_siminit_qc.qubits.reverse()sim_array=statevector_sim(init_qc)init_qc.qubits.reverse()arg_max=np.argmax(np.abs(sim_array))gphase_dif=(np.angle(target_array[arg_max]/sim_array[arg_max]))%(2*np.pi)init_qc.gphase(gphase_dif,0)init_gate=init_qc.to_gate()init_gate.name="state_init"qv.qs.append(init_gate,qv)defget_statevector_function(qs,decimals=None):iflen(qs.qv_list)==0:returnlambdax:0else:fromqrisp.simulatorimportstatevector_simcompiled_qc=qs.compile()sv_array=statevector_sim(compiled_qc)ifdecimalsisnotNone:sv_array=np.round(sv_array,decimals)defstatevector(label_constellation,round=None):fromqrispimportbin_repqs=list(label_constellation.keys())[0].qsiflen(label_constellation)!=len(qs.qv_list):missing_variables=set([qv.nameforqvinqs.qv_list])-set([qv.nameforqvinlabel_constellation.keys()])raiseException("Tried to invoke statevector debugger without specifying an ""outcome label for each QuantumVariable registered in ""QuantumSession. Missing variables are: "+str(missing_variables))bitstring=len(compiled_qc.qubits)*["0"]forqfinlabel_constellation.keys():label_int=qf.encoder(label_constellation[qf])bin_label_int=bin_rep(label_int,qf.size)[::-1]foriinrange(qf.size):qubit_pos=compiled_qc.qubits.index(qf[i])bitstring[qubit_pos]=bin_label_int[i]bitstring="".join(bitstring)state_index=int(bitstring,base=2)ifroundisNone:returnsv_array[state_index]else:returnnp.around(sv_array[state_index],round)returnstatevectordefcheck_if_fresh(qubits,qs,ignore_q_envs=True):fromqrispimportQuantumEnvironmentifnotignore_q_envs:temp_data=list(qs.data)qs.data=[]foriinrange(len(temp_data)):ifisinstance(temp_data[i],QuantumEnvironment):env=temp_data[i]env.compile()else:qs.append(temp_data[i])forqbinqubits:reversed_data=qb.qs().data[::-1]forinstrinreversed_data:ifisinstance(instr,QuantumEnvironment)andignore_q_envs:continueifqbininstr.qubits:ifinstr.op.name=="qb_alloc":breakelse:returnFalseelse:returnFalsereturnTruedefget_measurement_from_qc(qc,qubits,backend,shots=None):# Add classical registers for the measurement results to be stored incl=[]foriinrange(len(qubits)):cl.append(qc.add_clbit())# Add measurement instructionforiinrange(len(qubits)):qc.measure(qubits[i],cl[i])# Execute circuitcounts=backend.run(qc,shots)# Remove other measurements outcomes from counts dicnew_counts_dic={}no_of_shots_executed=0forkeyincounts.keys():# Remove possible whitespacesnew_key=key.replace(" ","")# Remove other measurementsnew_key=new_key[:len(cl)]new_key=int(new_key,base=2)try:new_counts_dic[new_key]+=counts[key]exceptKeyError:new_counts_dic[new_key]=counts[key]no_of_shots_executed+=counts[key]counts=new_counts_dic# Plot result (if needed)# Normalize countsforkeyincounts.keys():counts[key]=counts[key]/abs(no_of_shots_executed)returncountsdeffind_calling_line(level=0):stack=traceback.extract_stack(limit=level+3)returnstr(traceback.format_list(stack)[1].split("\n")[1].strip())# prints "a = fct1()"defretarget_instructions(data,source_qubits,target_qubits):fromqrispimportQuantumEnvironment,recursive_qs_search,multi_session_mergeforiinrange(len(data)):instr=data[i]ifisinstance(instr,QuantumEnvironment):retarget_instructions(instr.original_data,source_qubits,target_qubits)retarget_instructions(instr.env_data,source_qubits,target_qubits)continueforjinrange(len(instr.qubits)):ifinstr.qubits[j]insource_qubits:instr.qubits[j]=target_qubits[source_qubits.index(instr.qubits[j])]
[docs]defredirect_qfunction(function_to_redirect):""" Decorator to turn a function returning a QuantumVariable into an in-place function. This can be helpful for manual uncomputation if we have a function returning some QuantumVariable, but we want the result to operate on some other variable, which is supposed to be uncomputed. Parameters ---------- function_to_redirect : function A function returning a QuantumVariable. Raises ------ Exception Given function did not return a QuantumVariable Exception Tried to redirect quantum function into QuantumVariable of differing size Returns ------- redirected_function : function A function which performs the same operation as the input but now has the keyword argument target. Every instruction that would have been executed on the input functions result is executed on the QuantumVariable specified by target instead. Examples -------- We create a function that determins the AND value of its inputs and redirect it onto another QuantumBool. :: from qrisp import QuantumBool, mcx, redirect_qfunction #This function has only two arguments and returns its result def AND(a, b): res = QuantumBool() mcx([a,b], res) return res a = QuantumBool(name = "a") b = QuantumBool(name = "b") c = QuantumBool(name = "c") #This function has two arguments and the keyword argument target redirected_AND = redirect_qfunction(AND) redirected_AND(a, b, target = c) >>> print(a.qs) :: QuantumCircuit: -------------- b.0: ──■── │ a.0: ──■── ┌─┴─┐ c.0: ┤ X ├ └───┘ Live QuantumVariables: --------------------- QuantumBool b QuantumBool a QuantumBool c """fromqrispimportQuantumEnvironment,QuantumVariable,merge,QuantumArrayimportweakrefdefredirected_qfunction(*args,target=None,**kwargs):merge([argforarginlist(args)+[target]ifisinstance(arg,(QuantumVariable,QuantumArray))])env=QuantumEnvironment()env.manual_allocation_management=Trueqs=target.qswithenv:res=function_to_redirect(*args,**kwargs)ifnotisinstance(res,QuantumVariable):raiseException("Given function did not return a QuantumVariable")target=list(target)iflen(res)!=len(target):raiseException("Tried to redirect quantum function into QuantumVariable of ""differing size")i=0res_is_new=Falsewhilei<len(env.env_qs.data):instr=env.env_qs.data[i]ifisinstance(instr,QuantumEnvironment):passelifinstr.op.name=="qb_alloc"andinstr.qubits[0]inlist(res):env.env_qs.data.pop(i)res_is_new=Truecontinueelse:forqbininstr.qubits:qb.qs=weakref.ref(qs)i+=1retarget_instructions(env.env_qs.data,list(res),target)ifres_is_new:# Remove all traces of resres.delete()foriinrange(res.size):res.qs.qubits.remove(res[i])res.qs.data.pop(-1)foriinrange(len(res.qs.deleted_qv_list)):qv=res.qs.deleted_qv_list[i]ifqv.name==res.name:res.qs.deleted_qv_list.pop(i)breakreturntargetredirected_qfunction.__name__=function_to_redirect.__name__returnredirected_qfunction
defget_sympy_state(qs,decimals):fromsympyimport(I,Rational,Symbol,cancel,cos,count_ops,exp,factor,nsimplify,pi,simplify,sin,)fromsympy.physics.quantumimportKet,OrthogonalKetfromqrisp.simulatorimportstatevector_simqv_list=list(qs.qv_list)labels=[]forqvinqv_list:labels.append([qv.decoder(i)foriinrange(2**qv.size)])compiled_qc=qs.compile()sv_array=statevector_sim(compiled_qc)ifnotsv_array.dtype==np.dtype("O"):angles=np.angle(sv_array)%(2*np.pi)/(np.pi)ifdecimalsisnotNone:sv_array=np.round(sv_array,decimals)angles=np.round(angles,decimals)else:sv_array=np.round(sv_array,5)angles=np.round(angles,5)nz_indices=np.nonzero(sv_array)[0]nnz=len(nz_indices)else:importsympyasspnz_indices=[]foriinrange(len(sv_array)):entry=simplify(sv_array[i])forainsp.preorder_traversal(entry):ifisinstance(a,sp.Float):entry=entry.subs(a,round(a,5))sv_array[i]=entryifnotsv_array[i]==0:nz_indices.append(i)nnz=len(nz_indices)res=0forindinlist(nz_indices):amplitude=sv_array[ind]ifnotsv_array.dtype==np.dtype("O"):ifdecimalsisNone:try:abs_amp=trigify_amp(amplitude,nnz)exceptTypeError:abs_amp=amplitude# For some reason there is a sympy error, when the angle is equal to 1ifangles[ind]==1:phase=1else:phase=nsimplify(float(angles[ind]),tolerance=10**-5)ifcount_ops(phase)>5:phase=angles[ind]ket_expr=exp(I*phase*pi)*abs_amp*nnz**0.5else:ket_expr=sympy.N(amplitude,decimals)else:process_stack=[amplitude]whileprocess_stack:a=process_stack.pop(0)if(isinstance(a,(sympy.core.add.Add,sympy.core.mul.Mul))andlen(a.free_symbols)!=0):process_stack.extend(a.args)eliflen(a.free_symbols)==0:sub_float=np.round(complex(a.evalf()),5)ifnp.abs(sub_float-1)<10**-5:abs_amp=1continueelifnp.abs(sub_float)<10**-5:entry=entry.subs(a,0)continueelifnp.abs(sub_float)>1:continueelse:abs_amp=trigify_amp(sub_float,nnz)ifnp.angle(complex(a.evalf()))/np.pi==1:phase=-1else:phase=sp.exp(sp.I*nsimplify(np.angle(complex(a.evalf()))/np.pi,tolerance=10**-5,)*Symbol("pi"))expr=abs_amp*phaseamplitude=amplitude.subs(a,expr)amplitude=amplitude.subs(1j,sp.I)ket_expr=sp.trigsimp(amplitude)*nnz**0.5int_string=bin_rep(ind,len(compiled_qc.qubits))labels=[]forqvinqv_list:bit_string=""forqbinqv.reg:bit_string+=int_string[compiled_qc.qubits.index(qb)]label=qv.decoder(int(bit_string[::-1],2))ket_expr*=OrthogonalKet((label))res+=ket_exprifdecimalsisNoneorsv_array.dtype==np.dtype("O"):res=cancel(nsimplify(1/nnz**0.5)*res)ifisinstance(res,sympy.core.mul.Mul):temp=1forarginres.args[:-1]:temp*=nsimplify(arg.subs({Symbol("pi"):pi}))res=temp*res.args[-1]res=res.subs({Symbol("pi"):pi})returnresdeftrigify_amp(amplitude,nnz):fromsympyimport(I,Rational,Symbol,cancel,cos,count_ops,exp,factor,latex,nsimplify,pi,simplify,sin,)cos_expr=nsimplify(float(np.arccos(np.abs(amplitude))/np.pi),tolerance=10**-5)sin_expr=nsimplify(float(np.arcsin(np.abs(amplitude))/np.pi),tolerance=10**-5)# if count_ops(sin_expr) > count_ops(cos_expr):iflen(latex(sin_expr))>len(latex(cos_expr)):expr="cos"temp=cos_expr# elif count_ops(sin_expr) < count_ops(cos_expr):eliflen(latex(sin_expr))<len(latex(cos_expr)):expr="sin"temp=sin_expreliflen(sin_expr.free_symbols)==0:ifsin_expr.evalf()>cos_expr.evalf():expr="cos"temp=cos_exprelse:expr="sin"temp=sin_exprelse:temp=(nsimplify(np.abs(amplitude)*nnz**0.5,tolerance=10**-5)/nnz**0.5)# if count_ops(temp) > 4:iflen(latex(temp))>20:temp=(nsimplify(float(np.abs(amplitude)*nnz**0.5),tolerance=10**-5)/nnz**0.5)iflen(latex(temp))>20:abs=np.abs(amplitude)else:abs=tempelse:ifexpr=="cos":abs=cos(cos_expr*Symbol("pi"))else:abs=sin(sin_expr*Symbol("pi"))returnabsdefrender_qc(qc):latex_str=qc.to_latex()importos.pathimportsubprocessimporttempfilefromIPython.displayimportImage,displaywithtempfile.TemporaryDirectory(prefix="texinpy_")astmpdir:path=os.path.join(tmpdir,"document.tex")withopen(path,"w")asfp:fp.write(latex_str)subprocess.run(["lualatex",path],cwd=tmpdir)subprocess.run(["pdftocairo","-singlefile","-transp","-r","100","-png","document.pdf","document",],cwd=tmpdir,)im=Image(filename=os.path.join(tmpdir,"document.png"))display(im)
[docs]deflifted(*args,verify=False):""" Shorthand for ``gate_wrap(permability = "args", is_qfree = True)``. A lifted function is ``qfree`` and permeable on its inputs. The results of lifted functions can be automatically uncomputed even if they contain functions that could not be uncomputed on their own. You can find more information about these concepts :ref:`here <Uncomputation>` or `here <https://silq.ethz.ch/overview#/overview/3_uncomputation>`_. Note that the concept of permeability in Qrisp is a more general version of Silq's ``const``. .. warning:: Incorrect information about permeability and ``qfree``-ness can yield incorrect compilation results. If you are unsure, use the ``verify`` keyword on a small scale first. Parameters ---------- verify : bool, optional If set to ``True``, the specified information about permeability and ``qfree``-ness will be checked numerically. The default is ``False``. Examples -------- We create a function performing the `Margolus gate <https://arxiv.org/abs/quant-ph/0312225>`_. As it contains ``ry`` rotations, there are non-``qfree`` steps involved. Putting on the ``lifted`` decorator however marks the function as ``qfree`` as a whole. :: from qrisp import QuantumVariable, cx, ry, lifted from numpy import pi @lifted(verify = True) def margolus(control): res = QuantumVariable(1) ry(pi/4, res) cx(control[1], res) ry(-pi/4, res) cx(control[0], res) ry(pi/4, res) cx(control[1], res) ry(-pi/4, res) return res control = QuantumVariable(2) res = margolus(control) >>> print(res.qs) :: QuantumCircuit: -------------- ┌───────────┐ control.0: ┤0 ├ │ │ control.1: ┤1 margolus ├ │ │ res.0: ┤2 ├ └───────────┘ Live QuantumVariables: --------------------- QuantumVariable control QuantumVariable res >>> res.uncompute() >>> print(res.qs) :: QuantumCircuit: -------------- ┌───────────┐┌──────────────┐ control.0: ┤0 ├┤0 ├ │ ││ │ control.1: ┤1 margolus ├┤1 margolus_dg ├ │ ││ │ res.0: ┤2 ├┤2 ├ └───────────┘└──────────────┘ Live QuantumVariables: --------------------- QuantumVariable control Note that we set the ``verify`` keyword to ``True`` in this example. In more complex functions, involving many qubits this feature should only be used for bug-fixing on a small scale, since the verification can be time-consuming. """iflen(args)==0:deflifted_helper(function):returngate_wrap(permeability="args",is_qfree=True,verify=verify)(function)returnlifted_helperelse:returngate_wrap(permeability="args",is_qfree=True)(args[0])
[docs]deft_depth_indicator(op,epsilon):r""" This function returns the T-depth of an :ref:`Operation` object. According to `this paper <https://arxiv.org/abs/1403.2975>`_, the synthesis of an $RZ(\phi)$ up to precision $\epsilon$ requires $3\text{log}_2(\frac{1}{\epsilon})$ T-gates. Parameters ---------- op : :ref:`Operation` The operation, whose T-depth should be estimated. epsilon : float The precision of the RZ gate simulation. Returns ------- float The estimated T-depth of the Operation. """fromqrispimportClControlledOperationifisinstance(op,ClControlledOperation):returnt_depth_indicator(op.base_op,epsilon)elifop.definitionisnotNone:returnop.definition.t_depth(epsilon)elifop.namein["cx","cx","cz","x","y","z","s","h","s_dg","sx","sx_dg","measure","reset","qb_alloc","qb_dealloc","barrier","gphase",]:return0elifop.namein["rx","ry","rz","p","u1"]:par=op.params[0]/(np.pi)%1ifparin[0,1/2]:return0elifparin[1/4,3/4]:return1else:return3*np.log2(1/epsilon)elifop.namein["t","t_dg"]:return1elifop.name=="u3":res=0foriinrange(3):par=op.params[0]/(np.pi)%1ifparin[0,1/2]:passelifparin[1/4,3/4]:res+=1else:res+=3*np.log2(1/epsilon)returnreselse:raiseException(f"Gate {op.name} not implemented")
[docs]defcnot_depth_indicator(op):r""" This function returns the CNOT-depth of an :ref:`Operation` object. In NISQ-era devices, CNOT gates are the restricting bottleneck for quantum circuit execution. This function can be used as a gate-speed specifier for the :meth:`compile <qrisp.QuantumSession.compile>` method. Parameters ---------- op : :ref:`Operation` The operation, whose CNOT-depth should be computed. Returns ------- float The CNOT-depth of the Operation. """fromqrispimportClControlledOperationifisinstance(op,ClControlledOperation):returncnot_depth_indicator(op.base_op)elifop.definitionisnotNone:returnop.definition.cnot_depth()ifop.num_qubits==1orop.name=="barrier":return0elifop.namein["cx","cx","cz"]:return1else:raiseException(f"Gate {op.name} not implemented")
[docs]definpl_adder_test(inpl_adder):""" This function runs tests on a desired inplace addition function. An inplace addition function is a function mapping (a, b) to (a, a+b), where a is a :ref:`QuantumVariable`, list[:ref:`Qubit`] or an integer and b is either a :ref:`QuantumVariable` or a list[:ref:`Qubit`]. Parameters ---------- inpl_adder : callable A quantum inplace addition function that can either act on single QuantumVariables or on lists of Qubits by adding the first one to the second. Returns ------- Bool: True if all tests are passed, else False/ Exceptions. Examples -------- We test the built-in Cuccaro adder: :: from qrisp import cuccaro_adder, inpl_adder_test inpl_adder_test(cuccaro_adder) print("The cuccaro adder passed the tests without errors.") And now a new user-defined qcla adder: :: from qrisp import inpl_adder_test, qcla qcla_2_0 = lambda x, y : qcla(x, y, radix_base = 2, radix_exponent = 0) inpl_adder_test(qcla_2_0) print("The qcla_2_0 adder passed the tests without errors.") """fromqrispimportQuantumFloat,multi_measurement,h,control,QuantumBoolforiinrange(1,7):forjinrange(1,i+1):a=QuantumFloat(j)b=QuantumFloat(i)c=QuantumFloat(i)h(a)h(b)c[:]=binpl_adder(a,c)statevector_arr=a.qs.compile().statevector_array()angles=np.angle(statevector_arr[np.abs(statevector_arr)>1/2**((a.size+b.size)/2+1)])# Test correct phase behaviorassert(np.sum(np.abs(angles))<0.1),f"Quantum-quantum adder produced a faulty phase shift on input sizes, {i},{j}."mes_res=multi_measurement([a,b,c])fora,b,cinmes_res.keys():assert(a+b)%(2**i)==c,f"Quantum-quantum addition result was incorrect for input values {a} += {c} on input sizes, {i},{j}."ifi<6:forjinrange(1,2**i):a=QuantumFloat(i)b=QuantumFloat(i)h(a)b[:]=ainpl_adder(j,a)statevector_arr=a.qs.compile().statevector_array()angles=np.angle(statevector_arr[np.abs(statevector_arr)>1/2**((a.size)/2+1)])assert(np.sum(np.abs(angles))<0.1),f"Classical-quantum adder produced a faulty phase shift on input size {i}."mes_res=multi_measurement([a,b])fora,binmes_res.keys():assert(b+j)%(2**i)==a,f"Classical-quantum addition result was incorrect for input values {a} += {c} on input size {i}."foriinrange(1,7):forjinrange(1,i+1):a=QuantumFloat(j)b=QuantumFloat(i)c=QuantumFloat(i)qbl=QuantumBool()h(qbl)h(a)h(b)c[:]=bwithcontrol(qbl):inpl_adder(a,c)statevector_arr=a.qs.compile().statevector_array()angles=np.angle(statevector_arr[np.abs(statevector_arr)>1/2**((a.size+b.size)/2+1)])assert(np.sum(np.abs(angles))<0.1),f"Controlled quantum-quantum adder produced a faulty phase shift on input sizes, {i},{j}."mes_res=multi_measurement([a,b,c,qbl])fora,b,c,qblinmes_res.keys():ifqbl:assert(a+b)%(2**i)==c,f"Controlled quantum-quantum addition result was incorrect for input values {a} += {c} on input sizes, {i},{j}."else:assert(c==b),f"Controlled quantum-quantum addition behaviour was incorrect; an operation was performed without the control qubit in |1> state.Faulty input sizes: {i},{j}"ifi<6:forjinrange(1,2**i):a=QuantumFloat(i)b=QuantumFloat(i)qbl=QuantumBool()h(qbl)h(a)b[:]=awithcontrol(qbl):inpl_adder(j,a)statevector_arr=a.qs.compile().statevector_array()angles=np.angle(statevector_arr[np.abs(statevector_arr)>1/2**((a.size)/2+1)])assert(np.sum(np.abs(angles))<0.1),f"Controlled classical-quantum adder produced a faulty phase shift on input size {i}."mes_res=multi_measurement([a,b,qbl])fora,b,qblinmes_res.keys():ifqbl:assert(b+j)%(2**i)==a,f"Controlled classical-quantum addition result was incorrect for input values {b} += {j} on input size, {i}."else:assert(b==a),f"Controlled classical-quantum addition behaviour was incorrect; an operation was performed without the control qubit in |1> state. Faulty input sizes: {i}"
Get in touch!
If you are interested in Qrisp or high-level quantum algorithm research in general connect with us on our
Slack workspace.