"""\********************************************************************************* 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********************************************************************************/"""importnumpyasnpimportsympyimportqrisp.circuit.standard_operationsasopsfromqrisp.circuitimportClbit,Instruction,Operation,Qubit# Class to describe quantum circuits# The naming of the attributes is rather similar to the qiskit equivalent# in order to allow compatibility of qiskit programs to qrisp# The key attributes are# The list of qubits (.qubits).# the list of classical bits (.clbits)# the list of instructions (.data)
[docs]classQuantumCircuit:""" This class describes quantum circuits. Many of the attribute and method names are oriented at the `Qiskit QuantumCircuit <https://qiskit.org/documentation/stubs/qiskit.circuit.QuantumCircuit.html>`_ class in order to provide a high degree of compatibility. QuantumCircuits can be visualized by calling ``print`` on them. Qrisp QuantumCircuits can be quickly generated out of existing Qiskit QuantumCircuits with the :meth:`from_qiskit <qrisp.QuantumCircuit.from_qiskit>` method. Parameters ---------- num_qubits : integer, optional The amount of qubits, this QuantumCircuit is initialized with. The default is 0. num_clbits : integer, optional The amount of classical bits. The default is 0. name : string, optional A name for the QuantumCircuit. The default will generated a generic name. Examples -------- We create a QuantumCircuit containing a so-called fan-out gate: >>> from qrisp import QuantumCircuit >>> qc_0 = QuantumCircuit(4, name = "fan out") >>> qc_0.cx(0, range(1,4)) >>> print(qc_0) :: qb_0: ──■────■────■── ┌─┴─┐ │ │ qb_1: ┤ X ├──┼────┼── └───┘┌─┴─┐ │ qb_2: ─────┤ X ├──┼── └───┘┌─┴─┐ qb_3: ──────────┤ X ├ └───┘ Note that the :meth:`cx gate appending method <qrisp.QuantumCircuit.cx>` (like all other gate appending methods) can be called with integers, Qubit objects, lists of integers or lists of Qubit objects. We now turn this QuantumCircuit into a gate and append to another QuantumCircuit to generate a GHZ state: >>> qc_1 = QuantumCircuit(4) >>> qc_1.h(0) >>> qc_1.append(qc_0.to_gate(), qc_1.qubits) >>> print(qc_1) :: ┌───┐┌──────────┐ qb_4: ┤ H ├┤0 ├ └───┘│ │ qb_5: ─────┤1 ├ │ fan out │ qb_6: ─────┤2 ├ │ │ qb_7: ─────┤3 ├ └──────────┘ Finally, we add a measurement and evaluate the circuit: >>> qc_1.measure(qc_1.qubits) >>> print(qc_1) :: ┌───┐┌──────────┐┌─┐ qb_4: ┤ H ├┤0 ├┤M├───────── └───┘│ │└╥┘┌─┐ qb_5: ─────┤1 ├─╫─┤M├────── │ fan out │ ║ └╥┘┌─┐ qb_6: ─────┤2 ├─╫──╫─┤M├─── │ │ ║ ║ └╥┘┌─┐ qb_7: ─────┤3 ├─╫──╫──╫─┤M├ └──────────┘ ║ ║ ║ └╥┘ cb_0: ══════════════════╩══╬══╬══╬═ ║ ║ ║ cb_1: ═════════════════════╩══╬══╬═ ║ ║ cb_2: ════════════════════════╩══╬═ ║ cb_3: ═══════════════════════════╩═ >>> qc_1.run(shots = 1000) {'0000': 500, '1111': 500} **Converting from Qiskit** We construct the very same fan out QuantumCircuit in Qiskit: >>> from qiskit import QuantumCircuit as QiskitQuantumCircuit >>> qc_2 = QiskitQuantumCircuit(4) >>> qc_2.cx(0, range(1,4)) >>> print(qc_2) :: q_0: ──■────■────■── ┌─┴─┐ │ │ q_1: ┤ X ├──┼────┼── └───┘┌─┴─┐ │ q_2: ─────┤ X ├──┼── └───┘┌─┴─┐ q_3: ──────────┤ X ├ └───┘ To acquire the Qrisp QuantumCircuit we call the :meth:`from_qiskit <qrisp.QuantumCircuit.from_qiskit>` method. Note that we don't need to create a QuantumCircuit object first as this is a class method. >>> qrisp_qc_2 = QuantumCircuit.from_qiskit(qc_2) >>> print(qrisp_qc_2) :: qb_8: ──■────■────■── ┌─┴─┐ │ │ qb_9: ┤ X ├──┼────┼── └───┘┌─┴─┐ │ qb_10: ─────┤ X ├──┼── └───┘┌─┴─┐ qb_11: ──────────┤ X ├ └───┘ **Abstract Parameters** Abstract parameters are represented by `Sympy symbols <https://docs.sympy.org/latest/modules/core.html#module-sympy.core.symbol>`_ in Qrisp. We create a QuantumCircuit with some abstract parameters and bind them subsequently. >>> from qrisp import QuantumCircuit >>> from sympy import symbols >>> qc = QuantumCircuit(3) Create some Sympy symbols and use them as abstract parameters for phase gates: >>> abstract_parameters = symbols("a b c") >>> for i in range(3): qc.p(abstract_parameters[i], i) Create the substitution dictionary and bind the parameters: >>> subs_dic = {abstract_parameters[i] : i for i in range(3)} >>> bound_qc = qc.bind_parameters(subs_dic) >>> print(bound_qc) :: ┌──────┐ qb_0: ┤ P(0) ├ ├──────┤ qb_1: ┤ P(1) ├ ├──────┤ qb_2: ┤ P(2) ├ └──────┘ """qubit_index_counter=np.zeros(1,dtype=int)clbit_index_counter=np.zeros(1,dtype=int)xla_mode=0def__init__(self,num_qubits=0,num_clbits=0,name=None):object.__setattr__(self,"data",[])object.__setattr__(self,"qubits",[])object.__setattr__(self,"clbits",[])self.abstract_params=set()ifisinstance(num_qubits,int):foriinrange(num_qubits):self.qubits.append(Qubit("qb_"+str(self.qubit_index_counter[0]+i)))self.qubit_index_counter[0]+=num_qubitselse:raiseException(f"Tried to initialize QuantumCircuit with type {type(num_qubits)}")ifisinstance(num_clbits,int):foriinrange(num_clbits):self.clbits.append(Clbit("cb_"+str(self.clbit_index_counter[0]+i)))self.clbit_index_counter[0]+=num_clbitselse:raiseException(f"Tried to initialize QuantumCircuit with type {type(num_clbits)}")# Method to add qubit objects to the circuit
[docs]defadd_qubit(self,qubit=None):""" Adds a Qubit to the QuantumCircuit. Parameters ---------- qubit : Qubit, optional The Qubit to be added. If given none, a new Qubit will be generated. Returns ------- Qubit The added Qubit. Examples -------- >>> from qrisp import QuantumCircuit >>> qc = QuantumCircuit() >>> qc.add_qubit() >>> qc.qubits [qb_0] """self.qubit_index_counter+=1ifqubitisNone:qubit=Qubit("qb_"+str(self.qubit_index_counter[0]))ifself.xla_mode<2:forqbinself.qubits:ifqb.identifier==qubit.identifier:raiseException(f"Qubit name {qubit.identifier} already exists")ifnotisinstance(qubit,Qubit):raiseException(f"Tried to add type {type(qubit)} as a qubit")self.qubits.append(qubit)returnself.qubits[-1]
# Method to add classical bit objects to the circuit
[docs]defadd_clbit(self,clbit=None):""" Adds a classical bit to the QuantumCircuit. Parameters ---------- clbit : Clbit, optional The classical bit to be added. If given none, a new Clbit will be generated. Returns ------- Clbit The added Clbit. """ifclbitisNone:clbit=Clbit("cb_"+str(len(self.clbits)))ifnotisinstance(clbit,Clbit):raiseException(f"Tried to add type {type(clbit)} as a classical bit")forcbinself.clbits:ifcb.identifier==clbit.identifier:raiseException(f"Clbit name {clbit.identifier} already exists")self.clbits.append(clbit)returnself.clbits[-1]
# Method to transform the given circuit into an operation object
[docs]defto_op(self,name=None):""" Method to return an Operation object generated out of this QuantumCircuit. Operation objects can be appended to other QuantumCircuits. An alias for Qiskit compatibility is the :meth:`to_gate<qrisp.QuantumCircuit.to_gate>` method. Parameters ---------- name : string, optional The name of the gate. By default, the QuantumCircuit's name will be used. Returns ------- Operation The Operation defined by this QuantumCircuit. Examples -------- >>> from qrisp import QuantumCircuit >>> qc_0 = QuantumCircuit(4) >>> qc_0.x(qc.qubits) >>> operation = qc_0.to_gate() >>> qc_1 = QuantumCircuit(4) >>> qc_1.append(operation, qc_1.qubits) """ifnameisNone:name="circuit"+str(id(self))[:5]definition=self.copy()i=0whilei<len(definition.data):ifdefinition.data[i].op.namein["qb_alloc","qb_dealloc"]:definition.data.pop(i)continuei+=1returnOperation(name=name,num_qubits=len(self.qubits),num_clbits=len(self.clbits),definition=definition,params=[],)
# Wrapper to increase Qiskit compatibility
[docs]defto_gate(self,name=None):""" Similar to :meth:`to_op <qrisp.QuantumCircuit.to_op>` but raises an exception if self contains classical bits (like the `Qiskit equivalent <https://qiskit.org/documentation/stubs/qiskit.circuit.QuantumCircuit.to_gate.html>`_). # noqa Parameters ---------- name : str, optional A name for the resulting gate. The default is None. Raises ------ Exception Tried to turn a circuit including classical bits into unitary gate Returns ------- Operation The QuantumCircuit turned into an :ref:`Operation` instance. """iflen(self.clbits)!=0:raiseException("Tried to turn a circuit including classical bits into unitary gate")returnself.to_op(name)
# Method to extend the given circuit with another circuit# The dic translation dic encodes how the qubits should be plugged into each other
[docs]defextend(self,other,translation_dic="id"):""" Extends self in-place by another QuantumCircuit. Parameters ---------- other : QuantumCircuit The QuantumCircuit to extend by. translation_dic : dict, optional The dictionary containing the information about which Qubits and Clbits should be plugged into each other. This dictionary should contain qubits of other as keys and qubits of self as values. If given none, it is assumed that both QuantumCircuits have matching Qubits. Examples -------- We create two QuantumCircuits and extend the first with reversed qubit order by the other: >>> from qrisp import QuantumCircuit >>> extension_qc = QuantumCircuit(4) >>> qc_to_extend = QuantumCircuit(4) >>> extension_qc.cx(0, 1) >>> extension_qc.cy(0, 2) >>> extension_qc.cz(0, 3) >>> print(extension_qc) :: qb_0: ──■────■────■── ┌─┴─┐ │ │ qb_1: ┤ X ├──┼────┼── └───┘┌─┴─┐ │ qb_2: ─────┤ Y ├──┼── └───┘┌─┴─┐ qb_3: ──────────┤ Z ├ └───┘ >>> translation_dic = {extension_qc.qubits[i] : qc_to_extend.qubits[-1-i] >>> for i in range(4)} >>> qc_to_extend.extend(extension_qc, translation_dic) >>> print(qc_to_extend) :: ┌───┐ qb_4: ──────────┤ Z ├ ┌───┐└─┬─┘ qb_5: ─────┤ Y ├──┼── ┌───┐└─┬─┘ │ qb_6: ┤ X ├──┼────┼── └─┬─┘ │ │ qb_7: ──■────■────■── """iftranslation_dic=="id":translation_dic={}forqbinother.qubits:translation_dic[qb]=qbforcbinother.clbits:translation_dic[cb]=cb# Copy in order to prevent modificationtranslation_dic=dict(translation_dic)forkeyinlist(translation_dic.keys()):ifisinstance(key,(Qubit,Clbit)):translation_dic[key.identifier]=translation_dic[key]foriinrange(len(other.data)):instruction_other=other.data[i]qubits=[]forqbininstruction_other.qubits:qubits.append(translation_dic[qb.identifier])clbits=[]forcbininstruction_other.clbits:clbits.append(translation_dic[cb.identifier])self.append(instruction_other.op,qubits,clbits)
# Returns a copy of self
[docs]defcopy(self):""" Returns a copy of the given QuantumCircuit. Returns ------- QuantumCircuit The copied QuantumCircuit. """# If an inital circuit is given we construct a new instanceres=QuantumCircuit()object.__setattr__(res,"data",list(self.data))object.__setattr__(res,"qubits",list(self.qubits))object.__setattr__(res,"clbits",list(self.clbits))try:res.abstract_params=set(self.abstract_params)exceptAttributeError:passreturnres
# Returns a copy of self but with no instructions
[docs]defclearcopy(self):""" Returns a copy of the given QuantumCircuit but without any data (i.e. just the Qubits and Clbits). Returns ------- QuantumCircuit The empty, copied QuantumCircuit. """temp_data=list(self.data)self.data=[]res=self.copy()self.data=temp_datareturnres
# TO-DO write qiskit independent printer# Printing methoddef__str__(self):fromqiskit.visualization.circuit_visualizationimportcircuit_drawerfromqrisp.interfaceimportconvert_to_qiskittry:res_str=str(circuit_drawer(convert_to_qiskit(self,transpile=False),output="text",cregbundle=False,))exceptAttributeError:raiseException("Tried to print QuantumSession with uncompiled QuantumEnvironments")returnres_str# Method which compares the unitary of two given circuits and returns# True if they are equivalent
[docs]defcompare_unitary(self,other,precision=4,ignore_gphase=False):""" Compares the unitaries of two QuantumCircuits. This can be used to check if a QuantumCircuit transformation is valid. Parameters ---------- other : QuantumCircuit The QuantumCircuit to compare to. precision : int, optional The precision of the comparison. This function will return True, if the norm of the difference of the unitaries is below the precision. The default is 4. ignore_gphase: bool, optional If set to True, this method returns True if the unitaries only differ in a global phase. Returns ------- Bool The comparison outcome. Examples -------- We create two QuantumCircuit with equivalent unitaries but differing by a non-trivial commutation: >>> from qrisp import QuantumCircuit >>> qc_0 = QuantumCircuit(2) >>> qc_1 = QuantumCircuit(2) >>> qc_0.z(0) >>> qc_0.cx(0,1) >>> print(qc_0) :: ┌───┐ qb_0: ┤ Z ├──■── └───┘┌─┴─┐ qb_1: ─────┤ X ├ └───┘ >>> qc_1.cx(0,1) >>> qc_1.z(0) >>> print(qc_1) :: ┌───┐ qb_2: ──■──┤ Z ├ ┌─┴─┐└───┘ qb_3: ┤ X ├───── └───┘ >>> qc_0.compare_unitary(qc_1) True """iflen(self.qubits)!=len(other.qubits):returnFalseunitary_self=self.get_unitary()unitary_other=other.get_unitary()ifignore_gphase:arg_max=np.argmax(np.abs(unitary_self.flatten()))unitary_self=(unitary_self*unitary_other.flatten()[arg_max]/unitary_self.flatten()[arg_max])fromnumpy.linalgimportnormreturnbool(norm(unitary_self-unitary_other)<10**-precision)
# Converts several types of inputs to qubit lists.# Possible inputs are## A qubit object# An integer# A list of integers# A list of qubitsdefconvert_to_qubit_list(self,input,inner_recursion=False):ifisinstance(input,Qubit):ifinner_recursion:returninputelse:return[input]ifisinstance(input,int):try:returnself.convert_to_qubit_list(self.qubits[input],inner_recursion=inner_recursion)exceptIndexError:raiseException("Not enough qubits in circuit to access qubit "+str(input)+".")ifisinstance(input,list):return_list=[]forqbininput:return_list.append(self.convert_to_qubit_list(qb,inner_recursion=True))returnreturn_listraiseException("Could not convert input type "+type(input)+" to qubit list")# Similar function as above but with classical bitsdefconvert_to_clbit_list(self,input,inner_recursion=False):ifisinstance(input,Clbit):ifinner_recursion:returninputelse:return[input]ifisinstance(input,int):try:returnself.convert_to_clbit_list(self.clbits[input],inner_recursion=inner_recursion)exceptIndexError:raiseException("Not enough clbits in circuit to access clbit "+str(input)+".")ifisinstance(input,list):return_list=[]forcbininput:return_list.append(self.convert_to_clbit_list(cb,inner_recursion=True))returnreturn_listreturnself.convert_to_clbit_list(list(input))# Generates the inverse of self by applying the inverse gates in reversed order
[docs]definverse(self):""" Returns the inverse/daggered QuantumCircuit. Returns ------- inverted_circuit : QuantumCircuit The inverted QuantumCircuit. Examples -------- Daggering a QuantumCircuit reverses the order and daggers each operation. >>> from qrisp import QuantumCircuit >>> import numpy as np >>> qc = QuantumCircuit(1) >>> qc.x(0) >>> qc.p(np.pi/2, 0) >>> qc.y(0) >>> print(qc.inverse()) :: ┌───┐┌─────────┐┌───┐ qb_0: ┤ Y ├┤ P(-π/2) ├┤ X ├ └───┘└─────────┘└───┘ For the phase gate, a daggering implies the reversal of the phase - Pauli gates however are invariant under daggering. """inverted_circuit=self.clearcopy()forinstrinself.data[::-1]:inverted_circuit.append(instr.op.inverse(),instr.qubits,instr.clbits)returninverted_circuit
# Generate the circuits unitary matrix
[docs]defget_unitary(self,decimals=-1):""" Acquires the unitary matrix of the given QuantumCircuit as a Numpy array. This method also works with abstract parameters. In this case a Numpy array with Sympy entries is returned. Parameters ---------- decimals : integer, optional The amount of decimals to be rounded to. By default, the full precision is returned. Returns ------- numpy.ndarray The unitary matrix as a numpy array. Examples -------- We synthesize a controlled phase gate and inspect the unitary: >>> from qrisp import QuantumCircuit >>> import numpy as np >>> qc = QuantumCircuit(2) >>> phi = np.pi >>> qc.p(phi/2, 0) >>> qc.p(phi/2, 1) >>> qc.cx(0,1) >>> qc.p(-phi/2, 1) >>> qc.cx(0,1) >>> qc.get_unitary(decimals = 4) array([[ 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], [ 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j], [ 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j], [ 0.+0.j, 0.+0.j, 0.+0.j, -1.+0.j]], dtype=complex64) We now synthesize the exact same QuantumCircuit but this time ``phi`` is a Sympy symbol. >>> from sympy import Symbol >>> qc = QuantumCircuit(2) >>> phi = Symbol("phi") >>> qc.p(phi/2, 0) >>> qc.p(phi/2, 1) >>> qc.cx(0,1) >>> qc.p(-phi/2, 1) >>> qc.cx(0,1) >>> qc.get_unitary(decimals = 4) array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, exp(I*phi)]], dtype=object) """fromqrisp.simulatorimportcalc_circuit_unitaryres=calc_circuit_unitary(self)ifdecimals!=-1:ifres.dtype==np.dtype("O"):raveled_res=res.ravel()foriinrange(len(raveled_res)):expression=sympy.simplify(raveled_res[i])forainsympy.preorder_traversal(expression):ifisinstance(a,sympy.Float):rounded_float=round(a,decimals)ifabs(float(a)-1)<10**-(decimals):expression=expression.subs(a,1)else:expression=expression.subs(a,rounded_float)raveled_res[i]=expressionelse:res=np.round(res,decimals)returnres
defget_depth_dic(self):fromqrisp.miscimportget_depth_dicreturnget_depth_dic(self)defcnot_count(self):""" Method to determine the amount of CNOT gates used in this QuantumCircuit. Returns ------- int The amount of CNOT gates. """fromqrisp.miscimportcnot_countreturncnot_count(self)
[docs]deftranspile(self,transpilation_level=np.inf,**qiskit_kwargs):""" Transpiles the QuantumCircuit in the sense that there are no longer any synthesized gate objects. Furthermore, we can call the `Qiskit transpiler <https://qiskit.org/documentation/stubs/qiskit.compiler.transpile.html>`__ by supplying keyword arguments. The Qiskit transpiler is not called, if no keyword arguments are given. Parameters ---------- **qiskit_kwargs : Keyword arguments for the Qiskit transpiler. Returns ------- QuantumCircuit The transpiled QuantumCircuit. Examples -------- We create a QuantumCircuit and append a synthesized gate. Afterwards we transpile to a given set of basis gates using the Qiskit transpiler: >>> from qrisp import QuantumCircuit >>> qc = QuantumCircuit(3) >>> qc.mcx([0,1], 2) >>> print(qc) :: qb_0: ──■── │ qb_1: ──■── ┌─┴─┐ qb_2: ┤ X ├ └───┘ >>> print(qc.transpile(basis_gates = ["cx", "rz", "sx"])) :: global phase: 9π/8 ┌──────────┐ ┌───┐┌─────────┐ ┌───┐ ┌──────────┐┌───┐» qb_1: ┤ Rz(-π/4) ├─────────────────┤ X ├┤ Rz(π/4) ├───┤ X ├───┤ Rz(-π/4) ├┤ X ├» ├──────────┤ └─┬─┘└─────────┘ └─┬─┘ └──────────┘└─┬─┘» qb_2: ┤ Rz(-π/4) ├───────────────────┼───────■──────────■──────────■────────┼──» ├─────────┬┘┌────┐┌─────────┐ │ ┌─┴─┐ ┌─────────┐ ┌─┴─┐ │ » qb_3: ┤ Rz(π/2) ├─┤ √X ├┤ Rz(π/2) ├──■─────┤ X ├───┤ Rz(π/4) ├───┤ X ├──────■──» └─────────┘ └────┘└─────────┘ └───┘ └─────────┘ └───┘ » « ┌─────────┐┌───┐ «qb_1: ┤ Rz(π/4) ├┤ X ├──────────── « └─────────┘└─┬─┘ «qb_2: ─────────────■────────────── « ┌─────────┐┌────┐┌─────────┐ «qb_3: ┤ Rz(π/4) ├┤ √X ├┤ Rz(π/2) ├ « └─────────┘└────┘└─────────┘ """fromqrisp.circuitimporttranspilereturntranspile(self,transpilation_level,**qiskit_kwargs)
# Counts the amount of operations self contains and returns# a dict {"operatio_name" : operation_count, ...}
[docs]defcount_ops(self):""" Counts the amount of operations of each kind. Note that operations are identified by their name. Returns ------- count_dic : dict A dictionary containing the gate counts. Examples -------- We create a QuantumCircuit containing a number of gates and evaluates the gate-counts: >>> from qrisp import QuantumCircuit >>> qc = QuantumCircuit(5) >>> qc.x(qc.qubits) >>> qc.cx(0, range(1,5)) >>> qc.z(1) >>> qc.t(range(5)) >>> qc.count_ops() {'x': 5, 'cx': 4, 'z': 1, 't': 5} """count_dic={}forinsinself.data:ifins.op.namein["qb_alloc","qb_dealloc"]:continuetry:count_dic[ins.op.name]+=1exceptKeyError:count_dic[ins.op.name]=1returncount_dic
[docs]defbind_parameters(self,subs_dic):""" Returns a QuantumCircuit where the abstract parameters in ``subs_dic`` are bound to their specified values. Parameters ---------- subs_dic : dict A dictionary containing the abstract parameters of this QuantumCircuit as keys and the desired parameters as values. Raises ------ Exception ``subs_dic`` did not specify a value for all abstract parameters. Returns ------- QuantumCircuit The QuantumCircuit with substituted parameters. Examples -------- We create a QuantumCircuit with some abstract parameters and bind them subsequently: >>> from qrisp import QuantumCircuit >>> from sympy import symbols >>> qc = QuantumCircuit(3) Create some sympy symbols and use them as abstract parameters for phase gates: >>> abstract_parameters = symbols("a b c") >>> for i in range(3): qc.p(abstract_parameters[i], i) Create the substitution dictionary and bind the parameters: >>> subs_dic = {abstract_parameters[i] : i for i in range(3)} >>> bound_qc = qc.bind_parameters(subs_dic) >>> print(bound_qc) :: ┌──────┐ qb_0: ┤ P(0) ├ ├──────┤ qb_1: ┤ P(1) ├ ├──────┤ qb_2: ┤ P(2) ├ └──────┘ """subs_circ=self.clearcopy()missing_parameters=self.abstract_params-set(subs_dic.keys())ifmissing_parameters:raiseException("Need parameter specification for abstract parameters "+str(missing_parameters))forinsinself.data:iflen(ins.op.abstract_params):op=ins.op.bind_parameters(subs_dic)else:op=ins.op.copy()subs_circ.data.append(Instruction(op,ins.qubits,ins.clbits))subs_circ.abstract_params={}returnsubs_circ
[docs]defto_latex(self,**kwargs):""" Deploys the Qiskit circuit drawer to generate LaTeX output. Parameters ---------- **kwargs : dict Dictionary of keyword args for Qiskits `circuit_drawer <https://qiskit.org/documentation/stable/0.19/stubs/qiskit.visualization.circuit_drawer.html>`_ function. Returns ------- string A string containing the latex code. """fromqrisp.interfaceimportconvert_to_qiskitqiskit_qc=convert_to_qiskit(self,transpile=False)fromqiskit.visualizationimportcircuit_drawerreturncircuit_drawer(qiskit_qc,output="latex_source",**kwargs)
defto_qasm2(self,formatted=False,filename=None,encoding=None):""" Returns the `OpenQASM <https://en.wikipedia.org/wiki/OpenQASM>`_ string of self. Parameters ---------- formatted : bool, optional Return formatted Qasm string. The default is False. filename : string, optional Save Qasm to file with name ‘filename’. The default is None. encoding : TYPE, optional Optionally specify the encoding to use for the output file if filename is specified. By default, this is set to the system’s default encoding (i.e. whatever locale.getpreferredencoding() returns) and can be set to any valid codec or alias from stdlib’s codec module. Returns ------- string The OPENQASM string. """qiskit_qc=self.to_qiskit()try:returnqiskit_qc.qasm(formatted,filename,encoding)except:fromqiskit.qasm2importdumps,QASM2ExportErrortry:returndumps(qiskit_qc)except(QASM2ExportError,TypeError):fromqiskit.qasm3importdumpsfromqiskitimporttranspiletranspiled_qiskit_qc=transpile(qiskit_qc,basis_gates=["x","y","z","h","s","t","s_dg","t_dg","cx","cz","rz"])returndumps(qiskit_qc)defto_qasm3(self,formatted=False,filename=None,encoding=None):""" Returns the `OpenQASM <https://en.wikipedia.org/wiki/OpenQASM>`_ string of self. Parameters ---------- formatted : bool, optional Return formatted Qasm string. The default is False. filename : string, optional Save Qasm to file with name ‘filename’. The default is None. encoding : TYPE, optional Optionally specify the encoding to use for the output file if filename is specified. By default, this is set to the system’s default encoding (i.e. whatever locale.getpreferredencoding() returns) and can be set to any valid codec or alias from stdlib’s codec module. Returns ------- string The OPENQASM string. """qiskit_qc=self.to_qiskit()fromqiskit.qasm3importdumpsreturndumps(qiskit_qc)
[docs]defdepth(self,depth_indicator=lambdax:1,transpile=True):""" Returns the depth of the QuantumCircuit. Note that the depth on QuantumCircuit which are not transpiled, might have very little correlation with the runtime. Parameters ---------- depth_indicator : function, optional A function which receives an :ref:`Operation` instance and returns the time/logical depth this operation takes. By default every Operation takes logical depth 1. transpile : bool, optional Boolean to indicate wether the QuantumCircuit should be transpiled before the depth is calculated. The default is True. Returns ------- integer The depth of the QuantumCircuit. """fromqrisp.miscimportget_depth_diciflen(self.data)==0:return0depth_dic=get_depth_dic(self,transpile_qc=transpile,depth_indicator=depth_indicator)returnmax(depth_dic.values())
[docs]deft_depth(self,epsilon=None):r""" Estimates the T-depth of self. T-depth is an important metric for fault tolerant quantum computing, because T gates are expected to be the bottleneck in fault tolerant architectures. 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. Based on this formula, this method performs a conservative estimate of the T-depth of self. Parameters ---------- epsilon : float, optional The precision up to which parametrized gates should be approximated. If not given, Qrisp will determine the parameter with the highest precision. For more information on this feature see the examples Returns ------- float The estimated T-depth. Examples -------- We create a QuantumCircuit and evaluate the T-depth: >>> import numpy as np >>> from qrisp import QuantumCircuit >>> qc = QuantumCircuit(2) >>> qc.t(0) >>> qc.cx(0,1) >>> qc.rx(2*np.pi*3/2**4, 1) >>> qc.t_depth(epsilon = 2**-5) 16 In this example we execute a T-Gate on the 0-th qubit (T-depth : 1) followed by a CNOT gate (T-depth: 0) on qubits 0 and 1 and finally an RX gate. The RX-Gate can be executed by using the decomposition .. math:: RX(\phi) = \text{H } \text{RZ}(\phi) \text{ H} The T-depth of executing a parametrized RX gate is therefore the same as the parametrized RZ. To determine the T-depth of the RZ-gate, executed with precision $2^{-5}$ we use the above formula: .. math:: \begin{align} \text{TDEPTH}(\text{RZ}(\phi), \epsilon = 2^-5) = 3 \text{log}_2(2^5)\\ &= 15 \end{align} We therefore arrive at 16. **Automatic precision determination** In the case of unknown precision, Qrisp assumes that every parameter in the circuit is of the form. .. math:: \phi = 2\pi\frac{m}{2^k} Where $m$ is an integer. Qrisp will then determine the parameter with the maximum $k$ and set $\epsilon = 2^{k_{max} +3}$. >>> qc.t_depth() 22 In this circuit $k_{max} = 4$ therefore, $\epsilon = 2^{-7}$ implying the T-depth is 22. """ifepsilonisNone:transpiled_qc=self.transpile()max_circuit_prec=15forinstrintranspiled_qc.data:op=instr.opforparinop.params:par=int(np.round((par%(2*np.pi))/(2*np.pi)*2**15))foriinrange(max_circuit_prec):ifpar%(2**i):max_circuit_prec=ibreakmax_circuit_prec=16-max_circuit_precepsilon=2**(-max_circuit_prec-3)fromqrisp.misc.utilityimportt_depth_indicatorreturnself.depth(depth_indicator=lambdax:t_depth_indicator(x,epsilon))
[docs]defcnot_depth(self):""" This function returns the CNOT-depth of an self. 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. Returns ------- int The CNOT depth of self. Examples -------- We create a QuantumCircuit and evaluate it's CNOT depth. >>> from qrisp import QuantumCircuit >>> qc = QuantumCircuit(4) >>> qc.cx(0,1) >>> qc.x(1) >>> qc.cx(1,2) >>> qc.y(2) >>> qc.cx(2,3) >>> qc.cx(1,0) >>> print(qc) ┌───┐ qb_59: ──■────────────┤ X ├───── ┌─┴─┐┌───┐ └─┬─┘ qb_60: ┤ X ├┤ X ├──■────■─────── └───┘└───┘┌─┴─┐┌───┐ qb_61: ──────────┤ X ├┤ Y ├──■── └───┘└───┘┌─┴─┐ qb_62: ────────────────────┤ X ├ └───┘ >>> qc.cnot_depth() 3 """fromqrisp.misc.utilityimportcnot_depth_indicatorreturnself.depth(depth_indicator=cnot_depth_indicator)
[docs]defnum_qubits(self):""" Returns the amount of qubits. Returns ------- int Amount of Qubits. """returnlen(self.qubits)
# Interface for appending instructions# Can take either instruction or operations objects# Can apply multiple operations, if given the correct qubits# For instance if it is required to apply an x gate to qubits 4,5,6 execute# qc.append(XGate(), [qubit4, qubit5, qubit6])# If it is required to apply a cx gate to the qubit pairs (1,2), (3,4), (5,6)# execute qc.append(CXGate(), [[qubit1, qubit3, qubit5], [qubit2, qubit4, qubit6]])# If it is required to apply a cx gate to the qubit pairs (1,2), (1,3), (1,4)# execute qc.append(CXGate(), [qubit_1, [qubit2, qubit3, qubit4]])
[docs]defappend(self,operation_or_instruction,qubits=[],clbits=[]):r""" Method for appending Operation or Instruction objects to the QuantumCircuit. The parameter qubits can be an integer, a list of integers, a Qubit object or a list of Qubit objects. The same is valid for the clbit parameter. If given an Instruction object instead of an Operation, the given qubit and clbit parameters are ignored. Parameters ---------- operation_or_instruction : Operation or Instruction The operation or instruction to be appended to the QuantumCircuit. qubits : integer, list[integer], Qubit, list[Qubit], optional The qubits on which to apply the operation. The default is []. clbits : integer, list[integer], Clbit, list[Clbit], optional The classical bits on which to apply the operation. The default is []. Examples -------- We create a $H^{\otimes 4}$ gate and append it to every second qubit of another QuantumCircuit: >>> from qrisp import QuantumCircuit >>> multi_h_qc = QuantumCircuit(4, name = "multi h") >>> multi_h_qc.h(range(4)) >>> multi_h = multi_h_qc.to_gate() >>> qc = QuantumCircuit(8) >>> qc.append(multi_h, [2*i for i in range(4)]) >>> print(qc) :: ┌──────────┐ qb_4: ┤0 ├ │ │ qb_5: ┤ ├ │ │ qb_6: ┤1 ├ │ │ qb_7: ┤ multi h ├ │ │ qb_8: ┤2 ├ │ │ qb_9: ┤ ├ │ │ qb_10: ┤3 ├ └──────────┘ qb_11: ──────────── """# Check the type of the instruction/operation# from qrisp.circuit import Instruction, Operationifself.xla_mode>0:ifisinstance(operation_or_instruction,Instruction):self.data.append(operation_or_instruction)else:ifself.xla_mode<=1:ifnotisinstance(qubits,list):raiseException(f"Operation {operation_or_instruction.name} was appended with {qubits} in accelerated compilation mode (allowed is type List[Qubit]).")forqbinqubits:ifnotisinstance(qb,Qubit):raiseException(f"Operation {operation_or_instruction.name} was appended with {qubits} in accelerated compilation mode (allowed is type List[Qubit]).")self.data.append(Instruction(operation_or_instruction,qubits,clbits))returnifisinstance(operation_or_instruction,Instruction):instruction=operation_or_instructionself.append(instruction.op,instruction.qubits,instruction.clbits)returnelifisinstance(operation_or_instruction,Operation):operation=operation_or_instructionelse:raiseException("Tried to append object type "+str(type(operation_or_instruction))+" which is neither Instruction nor Operation")# Convert arguments (possibly integers) to list# The logic here is that the list structure gets preserved ie.# [[0, 1] ,2] ==> [[qubit_0, qubit_1], qubit_2]# unless the input is a single qubit/integer.# In this case we have# qubit_0 ==> [qubit_0]qubits=convert_to_qb_list(qubits,circuit=self)clbits=convert_to_cb_list(clbits,circuit=self)# Now we check which of the arguments is a list# For user convenience we allow to execute multiple gates at the same time# This comes with some restrictions where the operation to execute could be# ambigous.# When appending n gates with a single call of this function,# each qubit argument must either be a list of n qubits or a single qubit# First we check which arguments are listsqb_argument_is_list=[]foriinrange(len(qubits)):ifisinstance(qubits[i],list):qb_argument_is_list.append(i)# Same with classical bitscb_argument_is_list=[]foriinrange(len(clbits)):ifisinstance(clbits[i],list):cb_argument_is_list.append(i)ifqb_argument_is_list+cb_argument_is_list:# Determine the amount of gates to be appliedifqb_argument_is_list:arg_list_len=len(qubits[qb_argument_is_list[0]])else:arg_list_len=len(clbits[cb_argument_is_list[0]])# Check that indeed every list argument that has been given has# arg_list_len entriesforarg_list_indexinqb_argument_is_list:iflen(qubits[arg_list_index])!=arg_list_len:raiseException("Don't know how to combine appending arguments "+str((qubits+clbits)))forarg_list_indexincb_argument_is_list:iflen(clbits[arg_list_index])!=arg_list_len:raiseException("Don't know how to combine appending arguments "+str((qubits+clbits)))# Create argument constellationsforiinrange(arg_list_len):qubit_constellation=[]forjinrange(len(qubits)):ifjinqb_argument_is_list:qubit_constellation.append(qubits[j][i])else:qubit_constellation.append(qubits[j])clbit_constellation=[]forjinrange(len(clbits)):ifjincb_argument_is_list:clbit_constellation.append(clbits[j][i])else:clbit_constellation.append(clbits[j])# Append instruction (qubit_constellation and clbit_constellation) now# contains no lists but only qubit/clbit argumentsQuantumCircuit.append(self,operation,qubit_constellation,clbit_constellation)returniflen(qubits)!=operation.num_qubits:raiseException(f"Provided incorrect amount ({len(qubits)}) of qubits for operation "+str(operation.name)+f" (requires {operation.num_qubits})")iflen(clbits)!=operation.num_clbits:raiseException(f"Provided incorrect amount ({len(clbits)}) of clbits for operation "+str(operation.name)+f" (requires {operation.num_clbits})")iflen(set(qubits))!=len(qubits):raiseException(f"Duplicate qubit arguments in {qubits} for operation {operation.name}")# Building up the list of identifiers seems to slow down this function# We therefore check first if the qubit objects match and if this is not the# case we check if the identifiers matchifnotset(qubits).issubset(set(self.qubits)):op_identifiers=[qb.identifierforqbinqubits]qc_identifiers=[qb.identifierforqbinself.qubits]ifnotset(op_identifiers).issubset(qc_identifiers):raiseException("Instruction Qubits "+str(set(qubits)-set(self.qubits))+" not present in circuit")else:qubits=[self.qubits[qc_identifiers.index(op_id)]forop_idinop_identifiers]iflen(set([cb.identifierforcbinclbits]))!=len(clbits):raiseException("Duplicate clbit arguments")ifnotset([cb.identifierforcbinclbits]).issubset(set([cb.identifierforcbinself.clbits])):raiseException("Instruction Clbits not present in circuit")# Log which abstract parameters have been added to the circuittry:self.abstract_params.update(operation.abstract_params)exceptAttributeError:passcritical_qubits=[]perm_critical_qubits=[]forqbinqubits:ifqb.lock:critical_qubits.append(qb)ifqb.perm_lock:perm_critical_qubits.append(qb)critical_qubits=[qbforqbinqubitsifqb.lock]ifcritical_qubits:ifcritical_qubits[0].lock_message:raiseException(critical_qubits[0].lock_message)else:raiseException(f"Tried to perform operation {operation.name}""on locked qubit {critical_qubits[0]}")# Check if there are non-permeable operations on pt_locked qubitscritical_qubits=[qbforqbinqubitsifqb.perm_lock]ifcritical_qubits:fromqrisp.permeabilityimportis_permeablecritical_qubit_indices=[qubits.index(qb)forqbincritical_qubits]ifnotis_permeable(operation,critical_qubit_indices):ifcritical_qubits[0].perm_lock_message:raiseException(critical_qubits[0].perm_lock_message)else:raiseException(f"Tried to perform non-permeable operation {operation.name} on"f" perm_locked qubit {critical_qubits[0]}")self.data.append(Instruction(operation,qubits,clbits))
[docs]defrun(self,shots=None,backend=None):""" Runs a QuantumCircuit on a given backend. Parameters ---------- shots : int, optional The amount of shots to perform. The default is 10000. backend : BackendClient, optional The backend on which to evaluate the QuantumCircuit. The default is None. Returns ------- dict The resulting counts for the given QuantumCircuit. Examples -------- We create a GHZ QuantumCircuit and evaluate the results. >>> from qrisp import QuantumCircuit >>> qc = QuantumCircuit(5) >>> qc.h(0) >>> qc.cx(0, range(1,5)) >>> qc.measure(range(5)) >>> qc.run() {'0': 5000, '1': 5000} """ifbackendisNone:fromqrisp.default_backendimportdef_backendbackend=def_backendreturnbackend.run(self,shots)
[docs]defstatevector_array(self):""" Performs a simulation of the statevector of self and returns a numpy array of complex numbers. .. note:: Qrisps qubit ordering convention is reversed when compared to Qiskit, because of simulation efficiency reasons. As a rule of thumb you can remember: The statevector array of the following circuit has the amplitude 1 at the index ``0010 = 2`` :: qb.0: ───── qb.1: ───── ┌───┐ qb.2: ┤ X ├ └───┘ qb.3: ───── Returns ------- numpy.ndarray The statevector of this circuit. Examples -------- We create a QuantumCircuit, perform some operations and retrieve the statevector array. >>> from qrisp import QuantumCircuit >>> qc = QuantumCircuit(4) >>> qc.h(qc.qubits) >>> qc.z(-1) >>> qc.statevector_array() array([ 0.24999997+0.j, -0.24999997+0.j, 0.24999997+0.j, -0.24999997+0.j, 0.24999997+0.j, -0.24999997+0.j, 0.24999997+0.j, -0.24999997+0.j, 0.24999997+0.j, -0.24999997+0.j, 0.24999997+0.j, -0.24999997+0.j, 0.24999997+0.j, -0.24999997+0.j, 0.24999997+0.j, -0.24999997+0.j], dtype=complex64) """fromqrisp.simulatorimportstatevector_simreturnstatevector_sim(self)
[docs]@classmethoddeffrom_qasm_str(self,qasm_string):""" Loads a QuantumCircuit from a QASM String. Parameters ---------- qasm_string : string A string obeying the syntax of the OpenQASM specification. Returns ------- QuantumCircuit The corresponding QuantumCircuit. """fromqiskitimportQuantumCircuitqiskit_qc=QuantumCircuit().from_qasm_str(qasm_string)fromqrispimportQuantumCircuitreturnQuantumCircuit.from_qiskit(qiskit_qc)
[docs]@classmethoddeffrom_qasm_file(self,filename):""" Loads a QuantumCircuit from a QASM file. Parameters ---------- filename : string A string pointing to a file obeying the OpenQASM syntax. Returns ------- QuantumCircuit The corresponding QuantumCircuit. """fromqiskitimportQuantumCircuitqiskit_qc=QuantumCircuit().from_qasm_file(filename)fromqrispimportQuantumCircuitreturnQuantumCircuit.from_qiskit(qiskit_qc)
[docs]@classmethoddeffrom_qiskit(self,qiskit_qc):""" Class method to create QuantumCircuits from Qiskit QuantumCircuits. Parameters ---------- qiskit_qc : Qiskit QuantumCircuit The Qiskit QuantumCircuit to convert. Returns ------- QuantumCircuit The converted QuantumCircuit. Examples -------- We construct a fan-out QuantumCircuit in Qiskit: >>> from qiskit import QuantumCircuit as QiskitQuantumCircuit >>> qc_2 = QiskitQuantumCircuit(4) >>> qc_2.cx(0, range(1,4)) >>> print(qc_2) :: q_0: ──■────■────■── ┌─┴─┐ │ │ q_1: ┤ X ├──┼────┼── └───┘┌─┴─┐ │ q_2: ─────┤ X ├──┼── └───┘┌─┴─┐ q_3: ──────────┤ X ├ └───┘ Note that we don't need to create a QuantumCircuit object first as this is a class method. >>> qrisp_qc_2 = QuantumCircuit.from_qiskit(qc_2) >>> print(qrisp_qc_2) :: qb_8: ──■────■────■── ┌─┴─┐ │ │ qb_9: ┤ X ├──┼────┼── └───┘┌─┴─┐ │ qb_10: ─────┤ X ├──┼── └───┘┌─┴─┐ qb_11: ──────────┤ X ├ └───┘ """fromqrisp.interfaceimportconvert_from_qiskitreturnconvert_from_qiskit(qiskit_qc)
[docs]defto_qiskit(self):""" Method to convert the given QuantumCircuit to a Qiskit QuantumCircuit. Returns ------- Qiskit QuantumCircuit The converted circuit. """fromqrisp.interfaceimportconvert_to_qiskitreturnconvert_to_qiskit(self,transpile=False)
[docs]defto_pennylane(self):""" Method to convert the given QuantumCircuit to a `Pennylane <https://pennylane.ai/>`_ Circuit. Returns ------- function A function representing a pennylane QuantumCircuit. """fromqrisp.interfaceimportqml_converterreturnqml_converter(self)
[docs]defto_pytket(self):""" Method to convert the given QuantumCircuit to a `PyTket <https://cqcl.github.io/tket/pytket/api/#>`_ Circuit. Returns ------- function A function representing a pennylane QuantumCircuit. """fromqrisp.interfaceimportpytket_converterreturnpytket_converter(self)
# Several methods to apply the standard operation defined in standard_operations.py
[docs]defmeasure(self,qubits,clbits=None):""" Instructs a measurement. If given no classical bits, the proper amount will be created. Parameters ---------- qubits : Qubit The Qubit to be measured. clbits : ClBit, optional The Clbit to store the measurement result. The default is None. """ifclbitsisNone:fromqrispimportQuantumVariableifisinstance(qubits,(list,QuantumVariable)):clbits=[]foriinrange(len(qubits)):clbits.append(self.add_clbit())else:clbits=self.add_clbit()self.append(ops.Measurement(),[qubits],[clbits])
[docs]defcx(self,qubits_0,qubits_1):""" Instruct a CX-gate. Parameters ---------- qubits_0 : Qubit The Qubit to control on. qubits_1 : Qubit The target Qubit. """self.append(ops.CXGate(),[qubits_0,qubits_1])
[docs]defcy(self,qubits_0,qubits_1):""" Instruct a CY-gate. Parameters ---------- qubits_0 : Qubit The Qubit to control on. qubits_1 : Qubit The target Qubit. """self.append(ops.CYGate(),[qubits_0,qubits_1])
[docs]defcz(self,qubits_0,qubits_1):""" Instruct a CZ-gate. Parameters ---------- qubits_0 : Qubit The Qubit to control on. qubits_1 : Qubit The target Qubit. """self.append(ops.CZGate(),[qubits_0,qubits_1])
[docs]defh(self,qubits):""" Instruct a Hadamard-gate. Parameters ---------- qubits : Qubit The Qubit to apply the gate on. """self.append(ops.HGate(),[qubits])
[docs]defx(self,qubits):""" Instruct a Pauli-X-gate. Parameters ---------- qubits : Qubit The Qubit to apply the gate on. """self.append(ops.XGate(),[qubits])
[docs]defy(self,qubits):""" Instruct a Pauli-Y-gate. Parameters ---------- qubits : Qubit The Qubit to apply the gate on. """self.append(ops.YGate(),[qubits])
[docs]defz(self,qubits):""" Instruct a Pauli-Z-gate. Parameters ---------- qubits : Qubit The Qubit to apply the gate on. """self.append(ops.ZGate(),[qubits])
[docs]defrx(self,phi,qubits):""" Instruct a parametrized RX-gate. Parameters ---------- phi : float or sympy.Symbol The angle parameter. qubits : Qubit The Qubit to apply the gate on. """ifphi==0:returnself.append(ops.RXGate(phi),[qubits])
[docs]defry(self,phi,qubits):""" Instruct a parametrized RY-gate. Parameters ---------- phi : float or sympy.Symbol The angle parameter. qubits : Qubit The Qubit to apply the gate on. """ifphi==0:returnself.append(ops.RYGate(phi),[qubits])
[docs]defrz(self,phi,qubits):""" Instruct a parametrized RZ-gate. Parameters ---------- phi : float or sympy.Symbol The angle parameter. qubits : Qubit The Qubit to apply the gate on. """ifphi==0:returnself.append(ops.RZGate(phi),[qubits])
[docs]defcp(self,phi,qubits_0,qubits_1):""" Instruct a controlled phase-gate. Parameters ---------- phi : float or sympy.Symbol The angle parameter. qubits_0 : Qubit The Qubit to apply the gate on. qubits_1 : Qubit The other Qubit to apply the gate on. """ifphi==0:returnself.append(ops.CPGate(phi),[qubits_0,qubits_1])
[docs]defp(self,phi,qubits):""" Instruct a phase-gate. Parameters ---------- phi : float or sympy.Symbol The angle parameter. qubits : Qubit The Qubit to apply the gate on. """ifphi==0:returnself.append(ops.PGate(phi),[qubits])
[docs]defrxx(self,phi,qubits_0,qubits_1):""" Instruct an RXX-gate. Parameters ---------- phi : float or sympy.Symbol The angle parameter. qubits_0 : Qubit The Qubit to apply the gate on. qubits_1 : Qubit The other Qubit to apply the gate on. """ifphi==0:returnself.append(ops.RXXGate(phi),[qubits_0,qubits_1])
[docs]defrzz(self,phi,qubits_0,qubits_1):""" Instruct an RZZ-gate. Parameters ---------- phi : float or sympy.Symbol The angle parameter. qubits_0 : Qubit The Qubit to apply the gate on. qubits_1 : Qubit The other Qubit to apply the gate on. """ifphi==0:returnself.append(ops.RZZGate(phi),[qubits_0,qubits_1])
[docs]defxxyy(self,phi,beta,qubits_0,qubits_1):""" Instruct an XXYY-gate. Parameters ---------- phi : float or sympy.Symbol The angle parameter. beta : float or sympi.Symbol The other angle parameter qubits_0 : Qubit The Qubit to apply the gate on. qubits_1 : Qubit The other Qubit to apply the gate on. """ifphi==0:returnself.append(ops.XXYYGate(phi,beta),[qubits_0,qubits_1])
[docs]defswap(self,qubits_0,qubits_1):""" Instruct a SWAP-gate. Parameters ---------- qubits_0 : Qubit The qubit to swap. qubits_1 : Qubit The other qubit to swap. """self.append(ops.SwapGate(),[qubits_0,qubits_1])
[docs]defmcx(self,control_qubits,target_qubits,method="gray",ctrl_state=-1):""" Instruct a multi-controlled X-gate. Parameters ---------- control_qubits : list The list of Qubits to control on. target_qubits : Qubit The target Qubit. method : str, optional The algorithm to synthesize the mcx gate. The default is "gray". ctrl_state : str or int, optional The state on which the X gate is activated. Can be supplied as a string (i.e. "010110...") or an integer. The default is all ones ("11111..."). """self.append(ops.MCXGate(len(control_qubits),ctrl_state=ctrl_state,method=method),control_qubits+[target_qubits],)
[docs]defccx(self,ctrl_qubit_0,ctrl_qubit_1,target_qubit,method="gray"):""" Instruct a Toffoli-gate. Parameters ---------- ctrl_qubit_0 : list The first control Qubit. ctrl_qubit_1 : Qubit The second control Qubit. target_qubit : Qubit. The target Qubit. method : str, optional The algorithm to synthesize the mcx gate. The default is "gray". """self.mcx([ctrl_qubit_0,ctrl_qubit_1],target_qubit,method=method)
defcrx(self,phi,qubits_0,qubits_1):""" Instruct a controlled rx-gate. Parameters ---------- phi : float or sympy.Symbol The angle parameter. qubits_0 : Qubit The Qubit to apply the gate on. qubits_1 : Qubit The other Qubit to apply the gate on. """ifphi==0:returnself.append(ops.MCRXGate(phi,1),[qubits_0,qubits_1])
[docs]deft(self,qubits):""" Instruct a T-gate. Parameters ---------- qubits : Qubit The Qubit to apply the gate on. """self.append(ops.TGate(),[qubits])
[docs]deft_dg(self,qubits):""" Instruct a dagger T-gate. Parameters ---------- qubits : Qubit The Qubit to apply the gate on. """self.append(ops.TGate().inverse(),[qubits])
[docs]defs(self,qubits):""" Instruct an S-gate. Parameters ---------- qubits : Qubit The Qubit to apply the gate on. """self.append(ops.SGate(),[qubits])
[docs]defs_dg(self,qubits):""" Instruct a daggered S-gate. Parameters ---------- qubits : Qubit The Qubit to apply the gate on. """self.append(ops.SGate().inverse(),[qubits])
[docs]defsx(self,qubits):""" Instruct a SX-gate. Parameters ---------- qubits : Qubit The Qubit to apply the gate on. """self.append(ops.SXGate(),[qubits])
[docs]defsx_dg(self,qubits):""" Instruct a daggered SX-gate. Parameters ---------- qubits : Qubit The Qubit to apply the gate on. """self.append(ops.SXGate().inverse(),[qubits])
defbarrier(self,qubits=None,clbits=None):""" Instruct a Barrier onto the given Qubit. Barriers can be used as visual markers and compiler directives. Parameters ---------- qubits : Qubit The Qubit to apply the barrier on. clbits : Clbit The Clbits to apply the barrier on. """ifqubitsisNone:qubits=self.qubitsself.append(ops.Barrier(len(qubits)),qubits)
[docs]defreset(self,qubits):r""" Instruct a reset. This resets this Qubit into the $\ket{0}$ state regardless of its previous state. Parameters ---------- qubits : Qubit The Qubit to reset. """self.append(ops.Reset(),[qubits])
[docs]defu3(self,theta,phi,lam,qubits):r""" Instruct a U3-gate from given Euler angles. A U3 gate has the unitary: .. math:: U3(\theta, \phi, \lambda) = \begin{pmatrix} \cos{(\frac{\theta}{2})} & -\exp{(i\lambda)}\sin{(\frac{\theta}{2})} \\ \exp{(i\phi)} \sin{(\frac{\theta}{2})} & \exp{(i(\phi+\lambda))}\cos{(\frac{\theta}{2})} \end{pmatrix} Parameters ---------- theta : float or sympy.Symbol The theta parameter. phi : float or sympy.Symbol The phi parameter. lam : float or sympy.Symbol The lambda parameter. qubits : Qubit The Qubit to apply the u3 gate on. """self.append(ops.u3Gate(theta,phi,lam),[qubits])
[docs]defunitary(self,unitary_array,qubits):""" Instruct a U3-gate from a given U3 matrix. Parameters ---------- unitary_array : numpy.ndarray The U3 matrix to apply. qubits : Qubit The Qubit to apply the gate on. """mat=unitary_arraycoeff=1/np.sqrt(np.linalg.det(mat))gphase=-np.angle(coeff)%(2*np.pi)tmp_10=np.abs((coeff*mat[1][0]))tmp_00=np.abs((coeff*mat[0][0]))theta=2*np.arctan2(tmp_10,tmp_00)phiplambda2=np.angle(coeff*mat[1][1])%(2*np.pi)phimlambda2=np.angle(coeff*mat[1][0])%(2*np.pi)phi=phiplambda2+phimlambda2lam=phiplambda2-phimlambda2# gphase -= (phi + lam)arg_max=np.argmax(np.abs(mat).flatten())fromqrisp.simulator.unitary_managementimportu3matrixtemp_u3=u3matrix(theta,phi,lam,0).flatten()gphase=(-np.angle(temp_u3[arg_max]/mat.flatten()[arg_max]))%(2*np.pi)fromqrisp.circuitimportU3Gatefromqrisp.simulator.unitary_managementimportu3matrixself.append(U3Gate(theta,phi,lam,global_phase=gphase),qubits)
[docs]defgphase(self,phi,qubits):""" Instruct a global phase. Global phases do not directly influence the QuantumCircuits outcome however they can become physical if used as a base gate for a controlled operation. Parameters ---------- phi : float or sympy.Symbol The angle parameter. qubits : TYPE The Qubit to apply the gate on. """self.append(ops.GPhaseGate(phi),[qubits])
[docs]defid(self,qubits):""" Instruct an identity gate. Identity gates are simply placeholders and have no effect on the quantum state. Parameters ---------- qubits : TYPE The Qubit to apply the gate on. """self.append(ops.IDGate(),[qubits])
# Converts various inputs (eg. integers, qubits or quantum variables) to lists of qubit# used in the append method of QuantumCircuit and QuantumSessiondefconvert_to_qb_list(input,circuit=None,top_level=True):fromqrispimportQuantumArrayifissubclass(input.__class__,Qubit):iftop_level:result=[input]else:result=inputelifisinstance(input,QuantumArray):result=sum([qv.regforqvininput.flatten()],[])elifhasattr(input,"__iter__"):result=[]foriinrange(len(input)):result.append(convert_to_qb_list(input[i],circuit,top_level=False))elifhasattr(input,"reg"):result=list(input.reg)elifisinstance(input,int):ifisinstance(circuit,type(None)):raiseException("Tried to convert integer argument to qubit without given circuit")ifinput>=len(circuit.qubits):raiseException(f"Tried to adress qubit with index {input} in a circuit with {len(circuit.qubits)} qubits")result=convert_to_qb_list(circuit.qubits[input],top_level=top_level)else:raiseException("Couldn't convert type "+str(type(input))+" to qubit list")returnresultdefconvert_to_cb_list(input,circuit=None,top_level=True):fromqrisp.circuitimportClbitifhasattr(input,"__iter__"):result=[]foriinrange(len(input)):result.append(convert_to_cb_list(input[i],circuit,top_level=False))elifisinstance(input,int):ifisinstance(circuit,type(None)):raiseException("Tried to convert integer argument to qubit without given circuit")result=convert_to_cb_list(circuit.clbits[input],top_level=top_level)elifissubclass(input.__class__,Clbit):iftop_level:result=[input]else:result=inputreturnresult
Get in touch!
If you are interested in Qrisp or high-level quantum algorithm research in general connect with us on our
Slack workspace.