Notepad/enter/Machine Tips (Quantum)/Resources/Code & Circuit Operations/Languages/Python Libraries/Xanadu/Blackbird.md

5.6 KiB

The Blackbird Programming language is an assembly language invented by Canadian quantum computing company Xanadu for photonic computing. It is a separate language itself but can also exist as a separate python package. Here we will go directly into the main language.For the python implementation visit here.

  • Here is a helpful python notebook of all the PennyLane & Borealis files.

It is modeled after ProjectQ an open source quantum computing software. A really great tutorial can be found here.


Working with Blackbird Scripts

When submitting quantum programs to be executed remotely, they are communicated to the cloud platform using Blackbird—a quantum photonic assembly language. Strawberry Fields also supports exporting programs directly as Blackbird scripts (an xbb file); Blackbird scripts can then be submitted to be executed via the Xanadu Cloud Client.

For example, lets consider a Blackbird script program.xbb representing the same quantum program we constructed above:

name remote_job1
version 1.0
target X8_01 (shots = 20)

complex array U[4, 4] =
    -0.2075958002056761-0.1295303874004949j, 0.4168590195887626+0.585773678107707j, 0.2890475539846776-0.3529463027479843j, 0.213901659507021+0.411521709357663j
    -0.2618964139102731+0.4432947111670047j, -0.5184820552871022+0.1650915362584557j, -0.4128306651379415-0.4882838386727423j, -0.0079437590004708+0.172938838723708j
    0.1415402337953751+0.5501271526107689j, 0.3692746956219556+0.0108433797647406j, 0.1986531501150634-0.1359201690880894j, -0.6937372152789114-0.0404525424120204j
    -0.5917850330700981-0.0462912812620793j, 0.1868543708455093-0.1249918525715507j, -0.322811013686639+0.4699849324731709j, -0.2704622309566428+0.4459455876188795j

# Initial states are two-mode squeezed states
S2gate(1.0, 0.0) | [0, 4]
S2gate(1.0, 0.0) | [1, 5]
S2gate(1.0, 0.0) | [3, 7]

# Apply the unitary matrix above to
# the first pair of modes, as well
# as a beamsplitter
Interferometer(U) | [0, 1, 2, 3]
BSgate(0.543, 0.123) | [2, 0]
Rgate(0.453) | 1
MZgate(0.65, -0.54) | [2, 3]

# Duplicate the above unitary for
# the second pair of modes
Interferometer(U) | [4, 5, 6, 7]
BSgate(0.543, 0.123) | [6, 4]
Rgate(0.453) | 5
MZgate(0.65, -0.54) | [6, 7]

# Perform a PNR measurement in the Fock basis
MeasureFock() | [0, 1, 2, 3, 4, 5, 6, 7]

in:

$ xcc job submit --name "remote_job1" --target "X8_01" --circuit "$(cat program1.xbb)"

out:

{
    "id": "743bad0a-8a21-4a6b-86de-50f7ff35a9b3",
    "name": "remote_job1",
    "status": "open",
    "target": "X8_01",
    "language": "blackbird:1.0",
    "created_at": "2021-11-16 21:15:50.257162+00:00",
    "finished_at": null,
    "running_time": null,
    "metadata": {}
}

After executing the above command, the result will be accessible via its unique ID:

$ xcc job get 743bad0a-8a21-4a6b-86de-50f7ff35a9b3 --result

!Pasted image 20221218205543.png


Shots

The shots argument is an integer that defines how many times the circuit should be evaluated (or “sampled”) to estimate statistical quantities. On some supported simulator devices, shots=None computes measurement statistics exactly.

Note that this argument can be temporarily overwritten when a QNode is called. For example, my_qnode(shots=3) will temporarily evaluate my_qnode using three shots.

It is sometimes useful to retrieve the result of a computation for different shot numbers without evaluating a QNode several times (“shot batching”). Batches of shots can be specified by passing a list of integers, allowing measurement statistics to be course-grained with a single QNode evaluation.

Consider:

>>> shots_list = [5, 10, 1000]
>>> dev = qml.device("default.qubit", wires=2, shots=shots_list)

When QNodes are executed on this device, a single execution of 1015 shots will be submitted. However, three sets of measurement statistics will be returned; using the first 5 shots, second set of 10 shots, and final 1000 shots, separately.

For example:

@qml.qnode(dev)
def circuit(x):
  qml.RX(x, wires=0)
  qml.CNOT(wires=[0, 1])
  return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.expval(qml.PauliZ(0))

Executing this, we will get an output of size (3, 2):

>>> circuit(0.5)
tensor([[ 1.   ,  1.   ],
        [ 0.2  ,  1.   ],
        [-0.022,  0.876]], requires_grad=True)

Creating and drawing a quantum node

Together, a quantum function and a device are used to create a quantum node or QNode object, which wraps the quantum function and binds it to the device.

A QNode can be explicitly created as follows:

circuit = qml.QNode(my_quantum_function, dev_unique_wires)
circuit(np.pi/4, 0.7)
tensor(0.764, requires_grad=True)

To view the quantum circuit given specific parameter values, we can use the draw() transform,

print(qml.draw(circuit)(np.pi/4, 0.7))
aux: ───────────╭●─┤
 q1: ──RZ(0.79)─╰X─┤
 q2: ──RY(0.70)────┤  <Z>

or also :

import matplotlib.pyplot as plt
qml.drawer.use_style("black_white")
fig, ax = qml.draw_mpl(circuit)(np.pi/4, 0.7)
plt.show()

output: !Pasted image 20221218210829.png