Skip to main contentIBM Quantum Documentation
You are viewing the API reference for an old version of Qiskit SDK. Switch to latest version

QPY serialization

qiskit.qpy


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(file_obj[, metadata_deserializer])Load a QPY binary file
dump(circuits, file_obj[, metadata_serializer])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.

Version 4

Version 4 is identical to Version 3 except that it adds 2 new type strings to the INSTRUCTION_PARAM struct, z to represent None (which is encoded as no data), q to represent a QuantumCircuit (which is encoded as a QPY circuit), r to represent a range of integers (which is encoded as a RANGE), and t to represent a sequence (which is encoded as defined by SEQUENCE). Additionally, version 4 changes the type of register index mapping array from uint32_t to int64_t. If the values of any of the array elements are negative they represent a register bit that is not present in the circuit.

The REGISTERS header format has also been updated to

struct {
    char type;
    _Bool standalone;
    uint32_t size;
    uint16_t name_size;
    _bool in_circuit;
}

which just adds the in_circuit field which represents whether the register is part of the circuit or not.

RANGE

A RANGE is a representation of a range object. It is defined as:

struct {
    int64_t start;
    int64_t stop;
    int64_t step;
}

SEQUENCE

A SEQUENCE is a reprentation of a arbitrary sequence object. As sequence are just fixed length containers of arbitrary python objects their QPY can’t fully represent any sequence, but as long as the contents in a sequence are other QPY serializable types for the INSTRUCTION_PARAM payload the sequence object can be serialized.

A sequence instruction parameter starts with a header defined as:

struct {
    uint64_t size;
}

followed by size elements that are INSTRUCTION_PARAM payloads, where each of these define an element in the sequence. The sequence object will be typecasted into proper type, e.g. tuple, afterwards.

Version 3

Version 3 of the QPY format is identical to Version 2 except that it defines a struct format to represent a PauliEvolutionGate natively in QPY. To accomplish this the CUSTOM_DEFINITIONS struct now supports a new type value 'p' to represent a PauliEvolutionGate. Enties in the custom instructions tables have unique name generated that start with the string "###PauliEvolutionGate_" followed by a uuid string. This gate name is reservered in QPY and if you have a custom Instruction object with a definition set and that name prefix it will error. If it’s of type 'p' the data payload is defined as follows:

PAULI_EVOLUTION

This represents the high level PauliEvolutionGate

struct {
    uint64_t operator_count;
    _Bool standalone_op;
    char time_type;
    uint64_t time_size;
    uint64_t synthesis_size;
}

This is immediately followed by operator_count elements defined by the SPARSE_PAULI_OP_LIST_ELEM payload. Following that we have time_size bytes representing the time attribute. If standalone_op is True then there must only be a single operator. The encoding of these bytes is determined by the value of time_type. Possible values of time_type are 'f', 'p', and 'e'. If time_type is 'f' it’s a double, 'p' defines a Parameter object which is represented by a PARAMETER, e defines a ParameterExpression object (that’s not a Parameter) which is represented by a PARAMETER_EXPR. Following that is synthesis_size bytes which is a utf8 encoded json payload representing the EvolutionSynthesis class used by the gate.

SPARSE_PAULI_OP_LIST_ELEM

This represents an instance of PauliSumOp.

struct {
    uint32_t pauli_op_size;
}

which is immediately followed by pauli_op_size bytes which are .npy format 2 data which represents the SparsePauliOp.

Version 3 of the QPY format also defines a struct format to represent a ParameterVectorElement as a distinct subclass from a Parameter. This adds a new parameter type char 'v' to represent a ParameterVectorElement which is now supported as a type string value for an INSTRUCTION_PARAM. The payload for these parameters are defined below as PARAMETER_VECTOR_ELEMENT.

PARAMETER_VECTOR_ELEMENT

A PARAMETER_VECTOR_ELEMENT represents a ParameterVectorElement object the data for a INSTRUCTION_PARAM. The contents of the PARAMETER_VECTOR_ELEMENT are defined as:

struct {
    uint16_t vector_name_size;
    uint64_t vector_size;
    char uuid[16];
    uint64_t index;
}

which is immediately followed by vector_name_size utf8 bytes representing the parameter’s vector name.

PARAMETER_EXPR

Additionally, since QPY format version v3 distinguishes between a Parameter and ParameterVectorElement the payload for a ParameterExpression needs to be updated to distinguish between the types. The following is the modified payload format which is mostly identical to the format in Version 1 and Version 2 but just modifies the map_elements struct to include a symbol type field.

A PARAMETER_EXPR represents a ParameterExpression object that the data for an INSTRUCTION_PARAM. The contents of a PARAMETER_EXPR are defined as:

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. Following that is a symbol map which contains map_elements elements with the format

struct {
    char symbol_type;
    char type;
    uint64_t size;
}

The symbol_type key determines the payload type of the symbol representation for the element. If it’s p it represents a Parameter and if it’s v it represents a ParameterVectorElement. The map element struct is immediately followed by the symbol map key payload, if symbol_type is p then it is followed immediately by a PARAMETER object (both the struct and utf8 name bytes) and if symbol_type is v then the struct is imediately followed by PARAMETER_VECTOR_ELEMENT (both the struct and utf8 name bytes). 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. Similarly if the type is v then it represents a ParameterVectorElement and size will be 0 as 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 the COMPLEX. Finally, if type is i it represents an integer which is an int64_t.

Version 2

Version 2 of the QPY format is identical to version 1 except for the HEADER section is slightly different. You can refer to the Version 1 section for the details on the rest of the payload format.

The contents of HEADER are defined as a C struct are:

struct {
    uint16_t name_size;
    char global_phase_type;
    uint16_t global_phase_size;
    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. Following this is immediately global_phase_size bytes representing the global phase. The content of that data is dictated by the value of global_phase_type. If it’s 'f' the data is a float and is the size of a double. If it’s '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).

Version 1

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 int64_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.

Note

Prior to QPY Version 4 the type of array elements was uint32_t. This was changed to enable negative values which represent bits in the array not present in the circuit

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). The type field determines what type of object will get created with the custom definition. If it’s 'g' it will be a Gate object, 'i' it will be a Instruction object.

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 PARAMETER struct, e defines a ParameterExpression object (that’s not a Parameter) which is represented by a PARAMETER_EXPR struct (on QPY format Version 3 the format is tweak slightly see: PARAMETER_EXPR), 'n' represents an object from numpy (either an ndarray or a numpy type) which means the data is .npy format 2 data, and in QPY Version 3 'v' represents a ParameterVectorElement which is represented by a PARAMETER_VECTOR_ELEMENT struct.

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 COMPLEX. Finally, if type is i it represents an integer which is an int64_t.

COMPLEX

When representing a double precision complex value in QPY the following struct is used:

struct {
    double real;
    double imag;
}

this matches the internal C representation of Python’s complex type. 3

1

https://tools.ietf.org/html/rfc1700(opens in a new tab)

2(1,2)

https://numpy.org/doc/stable/reference/generated/numpy.lib.format.html(opens in a new tab)

3

https://docs.python.org/3/c-api/complex.html#c.Py_complex(opens in a new tab)

Was this page helpful?