QPY serialization (qiskit.circuit.qpy_serialization
)¶
Using QPY¶
Using QPY is defined to be straightforward and mirror the user API of the
serializers in Python’s standard library, pickle
and json
. There are
2 user facing functions: qiskit.circuit.qpy_serialization.dump()
and
qiskit.circuit.qpy_serialization.load()
which are used to dump QPY data
to a file object and load circuits from QPY data in a file object respectively.
For example:
from qiskit.circuit import QuantumCircuit
from qiskit.circuit import qpy_serialization
qc = QuantumCircuit(2, name='Bell', metadata={'test': True})
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
with open('bell.qpy', 'wb') as fd:
qpy_serialization.dump(qc, fd)
with open('bell.qpy', 'rb') as fd:
new_qc = qpy_serialization.load(fd)[0]
API documentation¶
|
Load a QPY binary file |
|
Write QPY binary data to a file |
QPY Compatibility¶
The QPY format is designed to be backwards compatible moving forward. This means you should be able to load a QPY with any newer Qiskit version than the one that generated it. However, loading a QPY file with an older Qiskit version is not supported and may not work.
For example, if you generated a QPY file using qiskit-terra 0.18.1 you could load that QPY file with qiskit-terra 0.19.0 and a hypothetical qiskit-terra 0.29.0. However, loading that QPY file with 0.18.0 is not supported and may not work.
QPY Format¶
The QPY serialization format is a portable cross-platform binary
serialization format for QuantumCircuit
objects in Qiskit. The basic
file format is as follows:
A QPY file (or memory object) always starts with the following 7
byte UTF8 string: QISKIT
which is immediately followed by the overall
file header. The contents of the file header as defined as a C struct are:
struct {
uint8_t qpy_version;
uint8_t qiskit_major_version;
uint8_t qiskit_minor_version;
uint8_t qiskit_patch_version;
uint64_t num_circuits;
}
All values use network byte order 1 (big endian) for cross platform compatibility.
The file header is immediately followed by the circuit payloads. Each individual circuit is composed of the following parts:
HEADER | METADATA | REGISTERS | CUSTOM_DEFINITIONS | INSTRUCTIONS
There is a circuit payload for each circuit (where the total number is dictated
by num_circuits
in the file header). There is no padding between the
circuits in the data.
HEADER¶
The contents of HEADER as defined as a C struct are:
struct {
uint16_t name_size;
double global_phase;
uint32_t num_qubits;
uint32_t num_clbits;
uint64_t metadata_size;
uint32_t num_registers;
uint64_t num_instructions;
uint64_t num_custom_gates;
}
This is immediately followed by name_size
bytes of utf8 data for the name
of the circuit.
METADATA¶
The METADATA field is a UTF8 encoded JSON string. After reading the HEADER
(which is a fixed size at the start of the QPY file) and the name
string
you then read the`metadata_size`` number of bytes and parse the JSON to get
the metadata for the circuit.
REGISTERS¶
The contents of REGISTERS is a number of REGISTER object. If num_registers is > 0 then after reading METADATA you read that number of REGISTER structs defined as:
struct {
char type;
_Bool standalone;
uint32_t size;
uint16_t name_size;
}
type
can be 'q'
or 'c'
.
Immediately following the REGISTER struct is the utf8 encoded register name of
size name_size
. After the name
utf8 bytes there is then an array of
uint32_t values of size size
that contains a map of the register’s index to
the circuit’s qubit index. For example, array element 0’s value is the index
of the register[0]
’s position in the containing circuit’s qubits list.
The standalone boolean determines whether the register is constructed as a standalone register that was added to the circuit or was created from existing bits. A register is considered standalone if it has bits constructed solely as part of it, for example:
qr = QuantumRegister(2)
qc = QuantumCircuit(qr)
the register qr
would be a standalone register. While something like:
bits = [Qubit(), Qubit()]
qr = QuantumRegister(bits=bits)
qc = QuantumCircuit(bits=bits)
qr
would have standalone
set to False
.
CUSTOM_DEFINITIONS¶
This section specifies custom definitions for any of the instructions in the circuit.
CUSTOM_DEFINITION_HEADER contents are defined as:
struct {
uint64_t size;
}
If size is greater than 0 that means the circuit contains custom instruction(s). Each custom instruction is defined with a CUSTOM_INSTRUCTION block defined as:
struct {
uint16_t name_size;
char type;
_Bool custom_definition;
uint64_t size;
}
Immediately following the CUSTOM_INSTRUCTION struct is the utf8 encoded name
of size name_size
.
If custom_definition
is True
that means that the immediately following
size
bytes contains a QPY circuit data which can be used for the custom
definition of that gate. If custom_definition
is False
then the
instruction can be considered opaque (ie no definition).
INSTRUCTIONS¶
The contents of INSTRUCTIONS is a list of INSTRUCTION metadata objects
struct {
uint16_t name_size;
uint16_t label_size;
uint16_t num_parameters;
uint32_t num_qargs;
uint32_t num_cargs;
_Bool has_conditional;
uint16_t conditional_reg_name_size;
int64_t conditional_value;
}
This metadata object is immediately followed by name_size
bytes of utf8 bytes
for the name
. name
here is the Qiskit class name for the Instruction
class if it’s defined in Qiskit. Otherwise it falls back to the custom
instruction name. Following the name
bytes there are label_size
bytes of
utf8 data for the label if one was set on the instruction. Following the label
bytes if has_conditional
is True
then there are
conditional_reg_name_size
bytes of utf8 data for the name of the conditional
register name. In case of single classical bit conditions the register name
utf8 data will be prefixed with a null character “x00” and then a utf8 string
integer representing the classical bit index in the circuit that the condition
is on.
This is immediately followed by the INSTRUCTION_ARG structs for the list of arguments of that instruction. These are in the order of all quantum arguments (there are num_qargs of these) followed by all classical arguments (num_cargs of these).
The contents of each INSTRUCTION_ARG is:
struct {
char type;
uint32_t index;
}
type
can be 'q'
or 'c'
.
After all arguments for an instruction the parameters are specified with
num_parameters
INSTRUCTION_PARAM structs.
The contents of each INSTRUCTION_PARAM is:
struct {
char type;
uint64_t size;
}
After each INSTRUCTION_PARAM the next size
bytes are the parameter’s data.
The type
field can be 'i'
, 'f'
, 'p'
, 'e'
, 's'
, 'c'
or 'n'
which dictate the format. For 'i'
it’s an integer, 'f'
it’s
a double, 's'
if it’s a string (encoded as utf8), 'c'
is a complex and
the data is represented by the struct format in the PARAMETER_EXPR section.
'p'
defines a Parameter
object which is
represented by a PARAM struct (see below), e
defines a
ParameterExpression
object (that’s not a
Parameter
) which is represented by a PARAM_EXPR struct
(see below), and 'n'
represents an object from numpy (either an ndarray
or a numpy type) which means the data is .npy format 2 data.
PARAMETER¶
A PARAMETER represents a Parameter
object the data for
a INSTRUCTION_PARAM. The contents of the PARAMETER are defined as:
struct {
uint16_t name_size;
char uuid[16];
}
which is immediately followed by name_size
utf8 bytes representing the
parameter name.
PARAMETER_EXPR¶
A PARAMETER_EXPR represents a ParameterExpression
object that the data for an INSTRUCTION_PARAM. The contents of a PARAMETER_EXPR
are defined as:
The PARAMETER_EXPR data starts with a header:
struct {
uint64_t map_elements;
uint64_t expr_size;
}
Immediately following the header is expr_size
bytes of utf8 data containing
the expression string, which is the sympy srepr of the expression for the
parameter expression. Follwing that is a symbol map which contains
map_elements
elements with the format
struct {
char type;
uint64_t size;
}
Which is followed immediately by PARAMETER
object (both the struct and utf8
name bytes) for the symbol map key. That is followed by size
bytes for the
data of the symbol. The data format is dependent on the value of type
. If
type
is p
then it represents a Parameter
and
size will be 0, the value will just be the same as the key. If
type
is f
then it represents a double precision float. If type
is
c
it represents a double precision complex, which is represented by:
struct {
double real;
double imag;
}
this matches the internal C representation of Python’s complex type. 3
Finally, if type is i
it represents an integer which is an int64_t
.