-
Notifications
You must be signed in to change notification settings - Fork 9
Circuits and Gates
The classes representing quantum gates are found in circuits.py
, and are subclasses of sc.circuits.QuantumStep
. You will need to work with QuantumStep
objects to crate custom gatesets, and you will get a QuantumStep
object as a return value from compilation.
# Here are some examples of what you can do with QuantumStep objects
U3 = sc.QiskitU3QubitStep()
CNOT = sc.CNOTStep()
# get the matrix that a gate represents in a numpy matrix format
U3_unitary = U3.matrix([np.pi/2, np.pi/4, np./pi/6]) # the array of parameters must be provided
CNOT_unitary = CNOT.matrix([]) # cnot takes no parameters so an empty array is provided
# combine multiple gates to form a larger circuit
mycircuit = ProductStep(KroneckerStep(U3,U3), CNOT) # note that mycircuit is itself an instance of QuantumStep
# assemble a circuit to qiskit or qasm code (only if all components of the circuit implement the assemble function)
v = [np.pi/2, np.pi/4, np./pi/6, np.pi/2, np.pi/4, np./pi/6] # an array that contains the parameters for all parameterized gates in the circuit. Normally you get this array from the compiler.
sc.assembler.assemble(mycircuit, v, sc.assembler.ASSEMBLY_OPENQASM)
# produce a drawing of the circuit (this features is not fully fleshed out, and requires all components of the circuit to implement the _draw-assemble function
mycircuit.draw()
-
ZXZXZQubitStep
- represents a single qubit gate using the RZ-RX90-RZ-RX90-RZ decomposition -
XZXZPartialQubitStep
- is an incomplete representation of a single qubit gate. The first Z in a ZXZXZ decomposition can commute through the control qubit of a CNOT and combine with the single qubit gate on the other side, so this incomplete single qubit gate can be used instead of a full single qubit gate in certain placements with no lack of generality, yet it has one fewer parameter. Over the course of a long speedup, this leads to a significant speedup. -
QiskitU3QubitStep
- represents a single qubit gate using the same decomposition as IBM's Qiskit U3. It is more efficient to compute than theZXZXZQubitStep
and therefore is generally superior as a fully general single qubit gate.
-
CNOTStep
- represents the CNOT two-qubit gate -
NonadjacentCNOTStep
- represents the CNOT step, but is customizable for nonlinear topologies. It takes three parameters, the number of qubitsdits
, the index of the control qubitcontrol
, and the index of the target qubittarget
. The gate is the size ofdits
qubits, regardless of the location of the control and target qubits, anddits
must be large enough to hold both the control and target qubits. -
CNOTRootStep
- represents the sqrt(CNOT) gate. -
CRZStep
- represents an incomplete CRZ gate that consists of two sqrt(CNOT) gates and a Z gate. It is made into a complete CRZ gate when the surrounding single-qubit gates from the circuit tree construction are included.
-
IdentityStep
- implements the identity. It takes two parameters: the size of the unitaryn
, and the number of qu-dits requireddits
. -
KroneckerStep
- represents the Kronecker product between two or more gates. It is used to combine gates that are run on different qudits during the same time slot. -
ProductStep
- represents the matrix product between two or more gates. It is used to combine gates that are run on the same set of qudits during different time slots.
-
CSUMStep
- represents the CSUM two-qutrit gate. -
CPIStep
- represents the CPI two-qutrit gate without phases. -
CPIPhaseStep
- represents the CPI two-qutrit gate with phases. It is the default two-qutrit gate.
There is an existing gate that can be customized to your needs. However it will not show up when you assembly the circuit to OpenQASM or Qiskit.
-
UStep
- represents the gate described by the unitaryU
passed to__init__
, and takes updits
qudits.
You can also write your own QuantumStep
subclasses the required functions are:
-
__init__
- you must customize the initializer to setself.num_inputs
to the number of parameters for the gate (e.g. 3 for U3 or ZXZXZ, 0 for CNOT), andself.dits
to the number of qudits used by the gate (e.g. 1 for U3 or ZXZXZ, 2 for CNOT). -
matrix(v)
- here you generate and return the matrix represented by your gate when passed the parameters provided in the arrayv
.
If you want your code to output your custom gates when assembling, you must implement assemble
as well.
-
assemble(v, i)
- here you are givenv
, the list of parameters needed for your gate, andi
, the index of the first qubit in the set of qubits that your gate is assigned. You must return an array of the form[gate1, gate2]
wheregate1
andgate2
are tuples that represent gates that the assembler will be able to interpret. For example,ZXZXZQubitStep
returns an array for 5 tuples, one for each of the Z and X gates that it is based on, butQiskitU3QubitStep
only returns an array of 1 tuple because the assembler interprets it as a single gate. The tuples take the form("gate", gatename, parameters, indices)
, where the word "gate" is included to specify that this tuple represents a well defined gate as opposed to a Kronecker product of gates,gatename
is a string that will be used to look up the relevant format to print this gate when assembling, andparameters
is a list of the parameters formatted and organized the way they are needed to fill the format specified in the assembler, andindices
is a list of the indices of the involved qubits.
If you would like to take advantage of faster solvers that can take advantage of the Jacobian (marked with Jac
in their name), and your custom gate uses one or more parameters, you will need to implement mat_jac
as well.
-
mat_jac(v)
- here you generate and return a tuple(U, [J1, ... ,Jn])
whereU
is the same matrix you would return inmatrix
, and[J1, ... ,Jn]
is a list of matrix derivatives, where J1 is the matrix of derivatives with respect to the first parameter inv
, and so on for all the parameters in v. If your custom gate is constant (self.num_inputs == 0
), then you can take advantage of Jacobian solvers without implementingmat_jac
yourself.