Configuration

Configurability is a primary design goal of OpenQL: instead of hardcoding the way in which an algorithm is compiled for a particular platform, both the platform and the strategy for compiling to it are completely configurable. As such, OpenQL has quite a complex configuration system.

Most of the configuration is provided to OpenQL via JSON files. OpenQL uses a superset of the JSON file format for all input files, namely one that allows //-based single-line comments; therefore, a configuration file written for OpenQL is not strictly valid JSON, but OpenQL can parse any valid JSON file (as long as it complies with the expected structure).

The two most important configuration file types are the platform and compiler configuration files.

  • The platform configuration file includes everything OpenQL needs to know about the target platform (i.e., what the quantum chip and microarchitecture looks like), and optionally includes information about how to compile for it. This file is passed to OpenQL when you construct a ql.Platform. OpenQL also has a number of default platform configuration files built into it; one for each architecture variant. Furthermore, architecture variants may include preprocessing logic for the platform configuration file, such that repetitive things for a particular platform can automatically be expanded; in this case, the description below documents the resulting structure, not necessarily what you would write (although the preprocessing logic should be minimal, such as only providing additional default values). See also the section on supported architectures.

  • The compiler configuration file describes the steps that OpenQL should take to transform the incoming program to something that can run on the platform (or at least is no further away from being able to run on it). This is also referred to as the pass list or compilation strategy. Besides configuration via JSON, the strategy can also be configured using the Python/C++ API directly; this is particularly useful when you for example only want to insert a visualizer pass into the existing, default pass list, or when you’re doing design-space exploration to determine the optimal compilation strategy for a particular algorithm.

The structure of these files is documented below.

Platform configuration

The platform configuration JSON file (or JSON data, as it’s not necessarily always in file form) represents a complete description of what the target platform looks like, and optionally how to compile for it. At the top level, the structure is a JSON object, with the following keys recognized by OpenQL’s platform-agnostic logic, customarily written in the following order.

  • "eqasm_compiler": an optional description of how to compile for this platform.

  • "hardware_settings": contains basic descriptors for the hardware, such as qubit count and cycle time.

  • "topology": optionally provides a more in-depth description of how the qubits are organized.

  • "resources": optionally provides information about scheduling constraints, for example due to a number of qubits sharing a single waveform generator.

  • "instructions": lists the instruction set supported by the platform.

  • "gate_decomposition": optionally lists a set of decomposition rules that are immediately applied when a gate with a particular name is added.

Note

The plan is to move away from on-the-fly gate decomposition, and instead make a gate decomposition pass. The exact design for this has not been made yet, but it’s possible that the gate decomposition section will change in minor ways in the future, or will be deprecated in favor of an entirely new configuration file section.

Depending on the architecture being compiled for, as specified through the "eqasm_compiler" key or via the compiler configuration file override during platform construction, additional sections or keys may be optional or required, or entire sections may even be generated. Refer to the architecture documentation for details to this end. The "none" architecture by definition does not do any of this, and can thus always be used as an override of sorts for this behavior; the only thing that the architecture variant specification does is provide better defaults for the platform and compiler configuration, so everything can always be specified using the "none" architecture if need be. The remainder of this page thus describes the “universal” structure as used by "none", while the architecture documentation may make achitecture-specific addenda.

In addition, passes are allowed to make use of additional structures in the configuration file. This implies that the common OpenQL code will not check for or warn you about unrecognized keys: it assumes that these will be read by something it is not aware of.

"eqasm_compiler" section

The "eqasm_compiler" key can take assume of the following types of values.

  • No value/unspecified: the defaults for the “none” architecture will implicitly be used.

  • A string matching one of the available architectures or architecture variants, for example "cc" or "cc_light.s7": the defaults for that architecture (variant) will be used. Legacy values, such as "eqasm_compiler_cc" or "qx" may also be used. Refer to the architecture documentation for a full and up-to-date list of recognized values.

  • A filename: the specified file will be interpreted as a compiler configuration file, fully specifying what the compiler looks like.

  • A JSON object: the contents of the object will be interpreted as a compiler configuration file, again fully specifying what the compiler looks like, but without the extra file indirection.

This key can also be completely overridden by explicitly specifying a compiler configuration file during platform construction, thus allowing the platform and compiler configuration files to be completely disjoint, if this is preferred for the intended application.

"hardware_settings" section

This must map to an object containing the basic parameters that describe the platform. OpenQL’s common code recognizes the following.

  • "qubit_number": must map to an integer specifying the total number of qubits in the platform. Qubit indices start at zero, so all indices must be in the range 0..N-1, where N is this value.

  • "creg_number": optionally specifies the number of 32-bit integer classical registers available in the platform. If not specified, the value will be inferred from the constructor of ql.Program.

  • "breg_number": optionally specifies the number of single-bit classical registers available in the platform, used for receiving measurement results and predicates. If not specified, the value will be inferred from the constructor of ql.Program.

  • "cycle_time": optionally specifies the cycle time used by the platform in nanoseconds. Currently this must be an integer value. If not specified, 1 will be used as a default, thus equating the nanosecond values to cycle values.

"topology" section

The topology JSON object must have the following structure.

{
    "form": <optional string, either "xy" or "irregular">,
    "x_size": <optional integer for form="xy">,
    "y_size": <optional integer for form="xy">,
    "qubits": <mandatory array of objects for form="xy", unused for "irregular">,
    "number_of_cores": <optional positive integer, default 1>,
    "comm_qubits_per_core": <optional positive integer, num_qubits / number_of_cores>,
    "connectivity": <optional string, either "specified" or "full">,
    "edges": <mandatory array of objects for connectivity="specified", unused for "full">
    ...
}

The "form" key specifies whether the qubits can be arranged in a 2D grid of integer coordinates ("xy") or not ("irregular"). If irregular, mapper heuristics that rely on sorting possible paths by angle are unavailable. If "xy", "x_size" and "y_size" specify the coordinate ranges (from zero to the limit minus one), and "qubits" specifies the coordinates. "qubits" must then be an array of objects of the following form:

{
    "id": <qubit index, mandatory>,
    "x": <X coordinate, mandatory>,
    "y": <Y coordinate, mandatory>,
    ...
}

Each qubit must be specified exactly once. Any additional keys in the object are silently ignored, as other parts of OpenQL may use the structure as well.

If the "form" key is missing, its value is derived from whether a "qubits" list is given. If "x_size" or "y_size" are missing, the values are inferred from the largest coordinate found in "qubits".

The "number_of_cores" key is used to specify multi-core architectures. It must be a positive integer. Each core is assumed to have the same number of qubits, so the total number of qubits must be divisible by this number. The first N qubits belong to core 0, the next N belong to core 1, etc, where N equals the total number of qubits divided by the number of cores.

Cores can communicate only via communication qubits. The amount of these qubits per cores may be set using the "comm_qubits_per_core" key. Its value must range between 1 and the number of qubits per core, and defaults to the latter. The first N qubits for each core are considered to be communication qubits, whereas the remainder are local qubits.

The "connectivity" key specifies whether there are qubit connectivity constraints ("specified") or all qubits (within a core) are connected ("full"). In the former case, the "edges" key must map to an array of objects of the following form:

{
    "id": <optional unique identifying integer>,
    "src": <source qubit index, mandatory>,
    "dst": <target qubit index, mandatory>,
    ...
}

Edges are directional; to allow qubits to interact “in both ways,” both directions must be specified. If any identifiers are specified, all edges should get one, and they should all be unique; otherwise, indices are generated using src*nq+dst. Any additional keys in the object are silently ignored, as other parts of OpenQL may use the structure as well (although they should preferably just extend this class).

When "connectivity" is set to "full" in a multi-core environment, inter-core edges are only generated when both the source and destination qubit is a communication qubit.

If the "connectivity" key is missing, its value is derived from whether an “edges” list is given.

Any additional keys in the topology root object are silently ignored, as other parts of OpenQL may use the structure as well.

"resources" section

Two JSON structures are supported: one for compatibility with older platform configuration files, and one extended structure. The extended structure has the following syntax:

{
    "architecture": <optional string, default "">,
    "dnu": <optional list of strings, default []>,
    "resources": {
        "<name>": {
            "type": "<type>",
            "config": {
                <optional configuration>
            }
        }
        ...
    }
}

The optional "architecture" key may be used to make shorthands for architecture- specific resources, normally prefixed with "arch.<architecture>.". If it’s not specified or an empty string, the architecture is derived from the compiler configuration (either "eqasm_compiler" or as overridden by the compiler configuration file passed to the platform constructor).

The optional "dnu" key may be used to specify a list of do-not-use resource types (experimental, deprecated, or any other resource that’s considered unfit for “production” use) that you explicitly want to use, including the "dnu" namespace they are defined in. Once specified, you’ll be able to use the resource type without the "dnu" namespace element. For example, if you would include "dnu.whatever" in the list, the resource type "whatever" may be used to add the resource.

The "resources" key specifies the actual resource list. This consists of a map from unique resource names matching [a-zA-Z0-9_\-]+ to a resource configuration. The configuration object must have a “type” key, which must identify a resource type that OpenQL knows about. The “config” key is optional, and is used to pass type-specific configuration data to the resource. If not specified, an empty JSON object will be passed to the resource instead.

If the "resources" key is not present, the old structure is used instead. This has the following simplified form:

{
    "<type>": {
        <configuration>
    },
    ...
}

"instructions" section

This section specifies the instruction set of the architecture. It must be an object, where each key represents the name of a gate, and the value is again an object, containing any semantical information needed to describe the instruction.

Note

OpenQL currently derives much of the semantics from the gate name. For example, the cQASM writer determines whether it should emit a gate angle parameter based on whether the name of the gate equals a set of gate names that would logically have an angle. This is behavior is very much legacy, and is to be replaced with checking for keys in the instruction definition (though of course using appropriate defaults for backward compatibility).

OpenQL supports two classes of instructions: generalized gates and specialized gates. For generalized gates, the gate name (i.e. the JSON object key) must be a single identifier matching [a-zA-Z_][a-zA-Z0-9_]*. This means that the gate can be applied to any set of operands. Specialized gates, on the other hand, have a fixed set of qubit operands. Their JSON key must be of the form <name> <qubits>, where <name> is as above, and <qubits> is a comma-separated list (without spaces) of qubit indices, each of the form q<index>. For example, a correct name for a specialized two-qubit gate would be cz q0,q1. Specialized gates allow different semantical parameters to be specified for each possible set of qubit operands: for example, the duration for a particular gate on a particular architecture may depend on the operands.

Note

If you’re using the mapper, and thus the input to your program is unmapped, OpenQL will also use the gate specializations when the gates still operate on virtual qubits. Therefore, you must specify a specialization for all possible qubit operand combinations of a particular gate, even if the gate does not exist for a particular combination due to for example connectivity constraints. Alternatively, you may specify a fallback using a generalized gate, as OpenQL will favor specialized gates when they exist.

Within the instruction definition object, OpenQL’s architecture/pass-agnostic logic currently only recognizes the following keys.

  • "duration" or "duration_cycles": specifies the duration of the instruction in nanoseconds or cycles. OpenQL currently only supports durations that are an integer number of nanoseconds, so any fractions will be rounded up to the nearest nanosecond. Furthermore, in almost all contexts, the duration of an instruction will be rounded up to the nearest integer cycle count.

  • "qubits": this must map to a single qubit index or a list of qubit indices that corresponds to the qubits in the specialization. For generalized instructions, the list must either be empty or unspecified. This field will be removed in the future as it is redundant, from which point onward it will be ignored. The qubit indices themselves can be specified as either a string of the form "q<index>" or an integer with just the index.

Note

Older versions of OpenQL recognized and required the existence of many more keys, such as "matrix" and "latency". All passes relying on this information have since been cleaned out as they were no longer in use, and all requirements on the existence of these keys have likewise been lifted.

"gate_decomposition" section

This section specifies the decomposition rules for gates/instructions that are applied immediately when a gate is constructed. If specified, it must be an object, where each key represents the name of the gate, along with capture groups for the qubit operands. The keys must map to arrays of strings, wherein each string represents a gate in the decomposition.

Examples of two decompositions are shown below. %0 and %1 refer to the first argument and the second argument. This means according to the decomposition on line 2, rx180 %0 will allow us to decompose rx180 q0 to x q0. Similarly, the decomposition on line 3 will allow us to decompose cnot q2, q0 to three instructions, namely: ry90 q0, cz q2, q0, and ry90 q0.

"gate_decomposition": {
    "rx180 %0" : ["x %0"],
    "cnot %0,%1" : ["ry90 %1","cz %0,%1","ry90 %1"]
}

These decompositions are simple macros (in-place substitutions) which allow programmer to manually specify a decomposition. These take place at the time of creation of a gate in a kernel. This means the scheduler will schedule decomposed instructions.

Note

Decomposition rules may only refer to custom gates that have already been defined in the instruction set.

Note

Recursive decomposition rules, i.e. decompositions that make use of other decomposed gate definitions, are not supported.

Note

These decomposition rules are intended to be replaced by a more powerful system in the future.

Compiler configuration

The compiler configuration JSON file (or JSON substructure) is expected to have the following structure:

{
    "architecture": <optional string, default "">,
    "dnu": <optional list of strings, default []>,
    "pass-options": <optional object, default {}>,
    "compatibility-mode": <optional boolean, default false>,
    "passes": [
        <pass description>
    ]
}

The optional "architecture" key may be used to make shorthands for architecture- specific passes, normally prefixed with "arch.<architecture>.". If it’s not specified or an empty string, no shorthand aliases are made.

The optional "dnu" key may be used to specify a list of do-not-use pass types (experimental passes, deprecated passes, or any other pass that’s considered unfit for “production” use) that you explicitly want to use, including the “dnu” namespace they are defined in. Once specified, you’ll be able to use the pass type without the "dnu" namespace element. For example, if you would include "dnu.whatever" in the list, the pass type "whatever" may be used to add the pass.

The optional "pass-options" key may be used to specify options common to all passes. The values may be booleans, integers, strings, or null, but nothing else. Null is used to reset an option to its hardcoded default value. An option need not exist for each pass affected by it; if it doesn’t, the default value is silently ignored for that pass. However, if it does exist, it must be a valid value for the option with that name. These option values propagate through the pass tree recursively, so setting a default option in the root using this record will affect all passes.

If "compatibility-mode" is enabled, some of OpenQL’s global options add implicit entries to the "pass-options" structure when set, for backward compatibility. However, entries in "pass-options" always take precedence. The logic for which options map to which is mostly documented in the global option docs now, since those options don’t do anything else anymore. Note that the global options by their original design have no way to specify what pass they refer to, so each option is attempted for each pass type! Which means we have to be a bit careful with picking option names for the passes that are included in compatibility mode.

Pass descriptions can either be strings (in which case the string is interpreted as a pass type alias and everything else is inferred/default), or an object with the following structure.

{
    "type": <optional string, default "">,
    "name": <optional string, default "">,
    "options": <optional object, default {}>
}

The "type" key, if specified, must identify a pass type that OpenQL knows about. You can call print_pass_types() on a ql.Compiler object to get the list of available pass types (and their documentation) for your particular configuration (just make an empty compiler object initially), or you can read the documentation section on supported passes. If the "type" key is not specified or empty, a group is made instead, and "group" must be specified for the group to do anything.

The "name" key, if specified, is a user-defined name for the pass, that must match [a-zA-Z0-9_\-]+ and be unique within the surrounding pass list. If not specified, a name that complies with these requirements is generated automatically, but the actual generated name should not be relied upon to be consistent between OpenQL versions. The name may be used to programmatically refer to passes after construction, and passes may use it for logging or unique output filenames. However, passes should not use the name for anything that affects the behavior of the pass.

The "options" key, if specified, may be an object that maps option names to option values. The values may be booleans, integers, strings, or null, but nothing else. Null is used to enforce usage of the OpenQL-default value for the option. The option names and values must be supported by the particular pass type.