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

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.

"cqasm_name" key

Specifies an alternative name for the instruction when it is printed as cQASM or when read from cQASM. This must be a valid identifier. If not specified, it defaults to the normal instruction name.

"prototype" key

Specifies the amount and type of operands that the instruction expects. If specified, it must be an array of strings. Each of these strings represents an operand, and must be set to the name of its expected type, optionally prefixed with the access mode, separated by a colon. By default, the available types are:

  • qubit for qubits;

  • bit for classical bits;

  • int for 32-bit signed integers; and

  • real for floating-point numbers.

The available access modes are:

  • B for barriers (DDG write, liveness ignore);

  • W for write access or qubit state preparation (DDG write, liveness kill);

  • U for read+write/update access or regular non-commuting qubit usage (DDG write, liveness use);

  • R for read-only access (DDG read, liveness use);

  • L for operands that must be literals;

  • X for qubit access that behaves like an X rotation (DDG X, liveness use);

  • Y for qubit access that behaves like an Y rotation (DDG Y, liveness use);

  • Z for qubit access that behaves like an Z rotation (DDG Z, liveness use);

  • M for a measurement of the qubit for which the result is written to its implicitly associated bit register (DDG W for the qubit and its bit, liveness use for the qubit, and liveness kill for the bit); and

  • I for operands that should be ignored (DDG ignore, liveness ignore).

If the access mode is not specified for an operand, U is assumed, as it is the most pessimistic mode available. If no prototype is specified at all, it will be inferred based on the instruction name for backward compatibility, using the following rules (first regex-matching rule applies):

  • move_init|prep(_?[xyz])? -> ["W:qubit"]

  • h|i -> ["U:qubit"]

  • rx -> ["X:qubit", "L:real"]

  • (m|mr|r)?xm?[0-9]* -> ["X:qubit"]

  • ry -> ["Y:qubit", "L:real"]

  • (m|mr|r)?ym?[0-9]* -> ["Y:qubit"]

  • rz -> ["Z:qubit", "L:real"]

  • crz? -> ["Z:qubit", "Z:qubit", "L:real"]

  • crk -> ["Z:qubit", "Z:qubit", "L:int"]

    • [st](dag)?|(m|mr|r)?zm?[0-9]* -> ["Z:qubit"]

  • meas(ure)?(_?[xyz])?(_keep)? -> ["M:qubit"] and ["U:qubit", "W:bit"]

  • (teleport)?(move|swap) -> ["U:qubit", "U:qubit"]

  • cnot|cx -> ["Z:qubit", "X:qubit"]

  • cphase|cz -> ["Z:qubit", "Z:qubit"]

  • cz_park -> ["Z:qubit", "Z:qubit", "I:qubit"]

  • toffoli -> ["Z:qubit", "Z:qubit", "X:qubit"]

  • no operands otherwise.

Furthermore, when gates are added via the API or old IR that don’t match an existing instruction due to prototype mismatch, and the prototype was inferred per the above rules, a clone is made of the instruction type with the prototype inferred by means of the actual operands, using the U access mode for reference operands and R for anything else. When a default gate is encountered in the old IR and needs to be converted to the new IR, the entire instruction type is inferred from the default gate. From now on, however, it is strongly recommended to explicitly specify prototypes and not rely on this inference logic.

Note

It is possible to define multiple overloads for an instruction with the same name. Passes using the new IR will be able to distinguish between these overloads based on the types and writability of the operands, but be aware that any legacy pass will use one of the gate definitions at random (so differing duration or other attributes won’t work right). The JSON syntax for this is rather awkward, since object keys must be unique; the best thing to do is to just append spaces for the key, since these spaces are cleaned up when the instruction type is parsed.

"barrier" key

An optional boolean that specifies that an instruction is to behave as a complete barrier, preventing it from being commuted with any other instruction during scheduling, and preventing optimizations on it. If not specified, the flag defaults to false.

"duration" or "duration_cycles" key

These keys specify the duration of the instruction in nanoseconds or cycles. It is illegal to specify both of them for a single instruction. If neither is specified, the duration defaults to a single cycle.

Note

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.

"decomposition" key

May be used to specify one or more decomposition rules for the instruction type. Unlike the rules in the "gate_decomposition" section, these rules are normally only applied by an explicit decomposition pass, of which the predicate matches the name and/or additional JSON data for the rule. The value for the "decomposition" key can take a number of shapes:

  • a single string: treated as a single, anonymous decomposition rule of which the decomposition is defined by the string parsed as a single-line cQASM 1.2 block;

  • an array of strings: as above, but each string represents a new line in the cQASM block;

  • a single object: treated as a single decomposition specification; or

  • an array of objects: treated as multiple decomposition specifications.

A decomposition specification object must have an "into" key that specifies the decomposition, which must be a single string or an array of strings as above. In addition, it may be given a name via the "name" key, or any number of other keys for passes to use to determine whether to apply a decomposition rule, or which decomposition rule to apply if multiple options are defined.

The cQASM 1.2 block must satisfy the following rules:

  • the version header must not be specified (it is added automatically);

  • subcircuits and goto statements are not supported;

  • the operands of the to-be-decomposed gate can be accessed using the op(int) -> ... function, where the integer specifies the operand index (which must constant-propagate to an integer literal); and

  • the duration of the decomposed block may not be longer than the duration of the to-be-decomposed instruction (either make the instruction duration long enough, or define the decomposition as a single bundle and (re)schedule after applying the decomposition).

Other than that, the cQASM code is interpreted using the default cQASM 1.2 rules. Note that this also means that the cQASM name of an instruction must be used if said instructions has differing OpenQL and cQASM names. Refer to the documentation of the cQASM 1.2 reader pass (io.cqasm.Read) for more information.

As an example, a CNOT gate with its usual decomposition might be specified as follows.

{
    "prototype": ["Z:qubit", "X:qubit"],
    "duration_cycles": 4,
    "decomposition": {
        "name": "to_cz",
        "into": [
            "ym90 op(1)",
            "cz op(0), op(1)",
            "skip 1",
            "y90 op(1)"
        ]
    }
}

Note that application of this decomposition rule would retain program validity with respect to schedule and data dependencies (if it was valid before application) for platforms where single-qubit rotations are single-cycle and the CZ gate is two-cycle, because the CNOT gate is defined to take four cycles, and the schedule of the decomposition is valid.

Note

The "decomposition" key is only supported when the instruction prototype is explicitly specified using the "prototype" key. Without a fixed prototype, type checking the op() function would be impossible.

"qubits" key

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. The qubit indices can be specified as either a string of the form "q<index>" or an integer with just the index.

Note

This field is obviously redundant. As such, it may be removed in the future, from which point onward it will be ignored.

"gate_decomposition" section

This section specifies legacy decomposition rules for gates/instructions. They are applied in the following cases:

  • when a gate matching a decomposition rule is added to a kernel using the API;

  • immediately before a legacy pass (i.e. one that still operates on the old IR) is run; and

  • when a decomposition pass matching rules named “legacy” is run.

Rules in this section support only a subset of what the new decomposition system supports. For example, scheduling information cannot be represented, the to-be-decomposed instruction can only have qubit operands, and the to-be-decomposed instruction can’t exist in the old IR without being decomposed (preventing operations on it before decomposition). For these reasons, this decomposition system is deprecated, and only still exists for backward compatibility.

If the section is specified, it must be an object, where each key represents the name of the to-be-decomposed 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. Behavior for this is undefined; the nested rules may or may not end up being expanded, and if they’re not, internal compiler errors may result.

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.