Syntax and grammar¶
Note
The Blackbird used directly inside a Strawberry Fields Engine context is a superset of the Blackbird quantum assembly language described here, as it is embedded in a Python environment.
You can think of it as ‘Python-enhanced Blackbird’, as Python functions and constructs, can be used, even if they are not ‘officially’ part of the Blackbird spec.
Introduction¶
In this section, we define the structure, syntax, and grammar of Blackbird code.
Philosophy¶
Blackbird was designed from the ground up, adhering to the following philosophies:
Encapsulate any universal photonic quantum computation.
Be clear, concise, and simple to read and follow. This simplicity should allow for both
Human readability - operations and expressions should correspond to existing conventions, and allude to common notation in quantum computing
Hardware execution - code should be unambiguous, with one quantum operation per line
Be easy to learn, using constructs and operators familiar in scientific computation.
A Blackbird script should contain only one quantum algorithm or simulation, making it an ideal format for saving and loading photonic quantum computing algorithms.
Similarity to Python¶
To satisfy points 2 and 3, Blackbird is deliberately Python-like, inheriting the following:
Case sensitivity.
#
for line comments.
Newlines indicate the end of a statement.
Operators and literals are similar to their Python equivalents.
The
|
operator is used to apply intrinsic quantum operations to quantum registers.
After measurement, quantum modes are automatically and implicitly converted into classical registers.
The resulting output is implicitly determined by the presence of measurement statements.
Differences to Python¶
Contrary to Python, however, we also introduce the following restrictions, to enable Blackbird to function as a quantum assembly language across a wide array of quantum hardware:
Statically typed - you must declare the variable type, and variables and arguments of conflicting types are not automatically cast to the correct type.
Array variables may be declared, and individual elements accessed through indexing, but Blackbird does not support array manipulation.
Metadata¶
All Blackbird programs must begin with a required metadata block as follows:
name progam_name
version 1.0
The name
and the version
keywords must be given in the order above and
are mandatory. The name simply specifies the name of your Blackbird program,
while the version number is the version number of the Blackbird spec it is written
for.
In addition, you may specify an optional target
keyword:
target chip0
This indicates the device or simulator the Blackbird program targets — that is, you are specifying that the contained Blackbird code is compiled for the targeted device/simulator.
Furthermore, the program type may be specified via the optional type
keyword:
type tdm (temporal_modes=42, copies=1000)
where TDM would correspond to running a time-domain multiplexing experiment. If the program type metadata is omitted, a default Gaussian boson sampling program is assumed.
Both the target and the type keywords also accepts keyword options, using the syntax
(option1=0.32, option2=40)
. For example copies=1000
above or:
target chip0 (shots=100)
Variable declarations¶
Variable may be optionally defined on any new line in the blackbird script.
The syntax for defining variables is as follows:
type name = expression
with the following types supported:
int
:0
,1
,5
float
:8.0
,0.43
,-0.123
,89.23e-10
complex
:0+5j
,8.1-1j
,0.54+0.21j
bool
:True
,False
str
: any ASCII string surrounded by double quotation marks,"hello world"
Note
When using a float, you must provide the full decimal. I.e.,
8
and8.
are not valid floats, but8.0
is.When using a complex, you must provide both real and imaginary parts. I.e.,
8
and2j
are not valid complex literals, but8+0j
is.
Examples:
int n = +5
int k = n
float m = -0.5432
float alpha = 0.5432
float x = 0.5+0.1
float Delta = 0.543
complex beta = 5.21
complex y = -0.43e-4+0.912j
complex z = +0.43e-4-0.912j
bool flag = True
str name = "program1"
Warning
All variable names starting with a letter are allowed, except those consisting of a single ‘q’
followed by an integer, for example q0
, q1
, q2
, etc. These are reserved for quantum
register references.
Operators¶
Blackbird allows expressions using the following operators:
+
: addition, unary positive-
: subtraction, unary negation*
: multiplication/
: division**
: right-associative exponentiation.
Functions¶
Blackbird also supports the intrinsic functions
sqrt()
exp()
,log
sin()
,cos()
,tan()
arcsin()
,arccos()
,arctan()
sinh()
,cosh()
,tanh()
arcsinh()
,arccosh()
,arctanh()
and the intrinsic constant
pi
You can also use previously defined variable names in your expressions:
float gamma = 2.0*cos(alpha*pi)
float test = n**2.0
Arrays¶
To define arrays, specify 'array'
after the variable type.
Each row of the array is then defined on an indented line, with
columns separated by commas.
float array A =
-1.0, 2.0
-0.1, 0.2
complex array U[3, 3] =
-0.23191638+0.17828953j, 0.58457815+0.41415933j, -0.05795454-0.46965132j
+0.42259383+0.56368926j, -0.42219920+0.04735544j, -0.18902308-0.01590913j
-0.02396850+0.64301446j, 0.09918161+0.36797446j, 0.26993055+0.30341975j
Arrays support retrieving values through linear indexing. For example, U[4]
would correspond to
the fourth value in the above array if flattened, thus returning +0.42259383+0.56368926j
.
Note
For additional array validation, you can specify the shape of the array using square brackets
directly after the variable name (i.e., U[3, 3]
) but this is optional.
Quantum program¶
The |
operator is used to apply intrinsic quantum operations to quantum registers.
For example:
# Statements have the following form:
Operation(parameters) | modes
# Depending on the operation, parameters may be optional
# Parameters can be variables of literals or expressions
complex alpha = 0.5+0.2
float delta = 0.5423
Coherent(alpha**2, Delta*sqrt(pi)) | 0
# Multiple modes are specified by comma separated integers
Interferometer(U) | [0, 1, 2, 3]
# Finish with measurements
MeasureFock(dark_counts=[0.1, 0.2]) | [0, 1]
Currently, the device always accepts keyword arguments, and operations accept positional arguments and keyword arguments.
To pass measured mode values to successive gate arguments, you may use the reserved
variables qX
, where X
is an integer representing mode X
, as parameters:
S2gate(0.43, 0.12) | [0, 1]
MeasureX | 0
MeasureP | 1
Xgate(sqrt(2)*q0+q1) | 2
After running a Blackbird program, the user should expect to receive the results as an array:
each column is a measurement result, corresponding to the measurements in the order they appear in the blackbird program
each row represents a shot/run
For-loops¶
Similar to Python, for-loops can be declared using the for ... in ...
syntax, followed by lines
of indented statements. Notice that there is no colon (:
) at the end of the for-statement. The for-loop
variable type must be declared followed by either a list of values, of the specified type, or a
range using the syntax from:to:step
.
For example:
for int i in [0, 2, 1, 0, 2, 1]
MZgate(phases[i], phases[i+1]) | [i, i+1]
where phases
could be an array declared above, or:
for int m in 2:10:2
MeasureX | m
measuring over modes 2, 4, 6 and 8.
Note
Currently, the following are not supported:
Nested for-loops; only single for-loops are allowed,
Looping through arrays, e.g.
for int i in phases
.
Templates¶
A Blackbird template is simply a Blackbird script that contains template parameters.
Template parameters use the syntax {parameter_name}
, and can be placed within any numeric expression.
For example, consider the following state teleportation template:
name StateTeleportation
version 1.0
# state to be teleported:
Coherent({alpha}) | 0
# teleportation algorithm
Squeezed(-{sq}) | 1
Squeezed({sq}) | 2
BSgate(pi/4, 0) | (1, 2)
BSgate(pi/4, 0) | (0, 1)
MeasureX | 0
MeasureP | 1
Xgate(sqrt(2)*q0) | 2
Zgate(sqrt(2)*q1) | 2
Here, the initial state preparation uses a template parameter {alpha}
,
while the squeezed resource states have magnitude given by parameter {sq}
.
The advantage of Blackbird templates is that a Blackbird script can encapsulate a photonic quantum circuit with free parameters. A library that makes use of the Blackbird quantum assembly language (such as Strawberry Fields) can dynamically update template parameters without needing to recompile the program.
Including subroutines¶
There may be the case where you have a Blackbird program or template representing a circuit primitive that you may want to re-use across multiple Blackbird programs.
This is possible using the include
statement. This has the following syntax:
include "path/to/filename.xbb"
where the file path is relative to the location of the current Blackbird script. A Blackbird script may have multiple includes, and they must all be placed after the metadata block, and before the quantum program/variables are defined.
The include
statement allows the external Blackbird program to be used as
a subroutine within the existing script. This quantum subroutine is called
via the name
of the included Blackbird script. For example, consider
a state teleportation template, state_teleportation.xbb
:
name StateTeleportation
version 1.0
# maximally entangled states
Squeezed(-{sq}) | 1
Squeezed({sq}) | 2
BSgate(pi/4, 0) | (1, 2)
# Alice performs the joint measurement
# in the maximally entangled basis
BSgate(pi/4, 0) | (0, 1)
MeasureX | 0
MeasureP | 1
# Bob conditionally displaces his mode
# based on Alice's measurement result
Xgate(sqrt(2)*q0) | 2
Zgate(sqrt(2)*q1) | 2
This template accepts the parameter sq
(the squeezing magnitude of the
resource states), and acts on three modes, teleporting the state in mode 0
to mode 2.
Now, consider another file, example_include.xbb
, which includes the
above StateTeleportation
operation imported from the state_teleportation.xbb
template:
name ExampleInclude
version 1.0
target gaussian (shots=10)
include "state_teleportation.xbb"
float alpha = 0.3423
Coherent(a=alpha) | 0
Coherent(a=alpha) | 1
StateTeleportation(sq=1) | [0, 2, 3]
MeasureHeterodyne() | 3
We can now call the StateTeleportation
subroutine, with sq=1
,
and apply it to modes 0, 2, and 3.
Note
Make sure to avoid circular includes when using the include
statement.
Program types¶
A program type can be define with the type
keyword in the metadata. The type
includes support for a specific set of experiments and might differ in the way
that they are defined inside a Blackbird script. Currently, the only supported
type is tdm
, which stands for time-domain multiplexing, and runs a photonic
quantum circuit in the time domain encoding.
Time-domain multiplexing (TDM)¶
To define a TDM program you can declare the tdm
type in the metadata
together with two different keyword arguments: temporal_modes
, corresponding
to the number of time-bins used in the experiment, and copies
determining
how many times the full circuit is run, using the same parameter arrays each
time.
type tdm (temporal_modes=2, copies=1000)
The TDM program requires a set of gates that is looped over a number of times equal to the number of temporal modes, which is defined in the type options. The set of gates only needs to be defined one time, accompanied by arrays containing the parameters that are to be used in each loop, also with a length equal to the number of temporal modes.
TDM programs has reserved keywords starting with a p
followed by a number;
e.g., p0
, p1
, or p42
. These are placeholders for the parameters in
their corresponding arrays (see script example below). Using this notation, each
value in the array is assumed to be the gate parameter value for the temporal
mode with the same index number.
name tdm
version 1.0
type tdm (temporal_modes=2, copies=1000)
int array p0 =
1, 2
int array p1 =
3, 4
Sgate(0.7, 0) | 1
BSgate(p0, 0.0) | [0, 1]
MeasureHomodyne(phi=p1) | 0
In the above case, this would mean that BSgate
would use the first value in
p0
for the first temporal mode, and the second value in p0
for the
second temporal mode. Arrays not following this naming convention would simply
be passed as they are directly to the gate, i.e. the parameter would be the same
array for each temporal mode.