Skip to content

Examples

Renmusxd edited this page Aug 4, 2018 · 6 revisions

Basic Circuit and Operation

Constructing basic circuits is easy:

from qip.operators import Qubit, H
q = Qubit(n=1)
h = H(q)

All circuits are run the same way:

quantum_state, classic_map = h.run()

quantum_state is the vector of complex numbers for the wavefunction. classic_map is a map from measurement objects to their measured values, more on that later.

Qubits cannot be cloned, thus at this time you cannot reuse qubit variables

q = Qubit()
h = H(q)
n = Not(q)  # Raises exception to prevent cloning!

Multi qubit circuits are natural extensions:

q1 = Qubit(n=1)
q2 = Qubit(n=2)
h1, h2 = H(q1, q2)

o, c = h1.run()
# equivalently, since the same circuit is run for h2
o, c = h2.run()

Qubit bundles

Each Qubit object can represent multiple qubits

q = Qubit(n=10)
h = H(q)

The Qubit constructor and .split(...) function are used to manipulate these bundles.

q1 = Qubit(n=5)
q2 = Qubit(n=5)
h1 = H(q1)
q3 = Qubit(h1, q2)
# q3 represents n=10 qubits

q4, q5 = q3.split()
# q4 and q5 have the same sizes as the
# inputs to q3 (5 and 5)

Measurement

Measurements are easily performed like many other operation.

q1 = Qubit(n=5)
q2 = Qubit(n=5)
q = Qubit(H(q1), q2)
m = Measure(q)

_, classic_map = m.run()
# now we can work with classic_map[m] = (measured_value, probability_of_measurement)

Since the measurement operator worked on all 10 qubits, therefore the quantum state has no qubits left over, and is size 0. If it measured only a subset then the quantum state would represent the leftovers.

StochasticMeasure also exists and instead of consuming qubits it simply acts as a no-op and meanwhile populates the classic_map with a probability distribution.

Conditions

Conditional qubits are a main method of introducing logic to the circuit

q1 = Qubit(n=1)
q2 = Qubit()
cn1, cn2 = C(Not)(q1, q2)

Here C(Not) constructs a conditional-Not operation out of the existing Not operation. This can be done for any MatrixOp. The first qubit passed to the call is used for the condition while remaining are for the underlying op.

These can be nested since C produces a MatrixOp

c1, c2, c3 = C(C(Not))(q1, q2, q3)

If you want to have a series of operations all under the same condition, you can use an operator context:

from qip.qubit_util import QubitWrapperContext

q1 = Qubit(n=1)
q2 = Qubit()

with QubitWrapperContext(C, [q1]):
    h2 = H(q2)
    n2 = Not(h2)

o, c = n2.run()

This is equivalent to:

q1 = Qubit(n=1)
q2 = Qubit()

h1, h2 = C(H)(q1, q2)
n1, n2 = C(Not)(h1, h2)

o, c = n2.run()

But can be useful for more complicated circuits. Notice that we've lost a reference to q1 after the context ends, it can be retrieved by doing the following complicated code:

q1 = Qubit(n=1)
q2 = Qubit()

with QubitWrapperContext(C, [q1]) as context:
    h2 = H(q2)
    n2 = Not(h2)
    n1, n2 = context.put_qubits_in_local_context_order(n2)

but we can use a helpful wrapper to simplify this:

from qip.qubit_util import QubitFuncWrapper

@QubitFuncWrapper.wrap
def my_circuit(*qubits):
    hs = H(*qubits)
    ns = Not(*hs)
    return ns

q1 = Qubit(n=1)
q2 = Qubit()

n1, n2 = C(my_circuit)(q1, q2)
o, c = n2.run()

As usual the first qubit q1 goes to the control but the remaining are sent to the my_circuit function. The decorator will allow the function to work as an operator in many situations.

Clone this wiki locally