Gate- and measurement streams

The gatestream interface connects frontends to backends, frontends to operators, and operators to backends. There is a slight difference between the three to do with the measurement path, but for the most part they are the same. The following graphic shows the functions and callbacks used to form the interface on either side.

The green datapaths only exist if the upstream plugin is an operator. The red datapaths only exist if the downstream plugin is an operator.

Allocating and freeing qubits

DQCsim allows upstream plugins to allocate qubits within the downstream plugins at all times. This was done to provide a means for qubit mapping operators to determine whether upstream qubits are in use or not, and because it seems obvious in general coming from the classical world. Similar to a classical computer, a backend with limited resources must make map the qubits that are actually in use appropriately.

The alloc() function takes the number of qubits that are to be allocated as an argument, as well as a list of ArbCmds. The ArbCmds can for instance be used for various things, such as assigning a name to qubit registers for debugging purposes or specifying error model information on a per-qubit basis. In response, DQCsim allocates unique indices for the requested qubits and returns them to the algorithm immediately. It also asynchronously sends an allocation request through the gatestream, which causes the alloc() callback to be called on the other end. This function takes the qubit indices that DQCsim allocated for the qubits and the list of ArbCmds as argument.

free() can be used to free previously allocated qubits. If these qubits are still entangled, the backend should collapse the state in some way; however, it is up to the backend to decide how to do this. free() only takes the list of qubit indices that are to be freed as arguments.

Qubit indices are assigned by DQCsim in a deterministic way. The first allocated qubit receives index 1. Subsequent allocations receive the next index. This means that freed qubit indices are never reused; if a backend wants to reuse indices internally, it must maintain its own mapping. Index 0 is reserved for marking invalid qubits and the end of a qubit list in the C API.

Sending and receiving gates

DQCsim supports four kinds of gates:

  • unitary gates, defined by a matrix, one or more target qubits, and zero or more control qubits;
  • measurement gates, defined by one or more measured qubits and an arbitrary measurement basis;
  • prep gates, defined by one or more target qubits and an arbitrary basis;
  • custom gates, defined by a name and any of the above. Downstream plugins should reject named gates that they don't recognize.

Note that all but the latter do not have any kind of name attached to them. In other words, detecting whether a gate is for instance an X gate is not very trivial: it requires matching the matrix with a known X matrix. Floating point roundoff errors could cause headaches, and global phase also becomes a thing then. After all, the following matrices are equivalent in quantum:

\f[ \begin{bmatrix} 0 & 1 \ 1 & 0 \end{bmatrix} __ \begin{bmatrix} 0 & i \ i & 0 \end{bmatrix} \f]

as they only differ in global phase. This also goes for the measurement and prep gates, where the basis is also represented with a matrix such that any basis and initial state can be described.

The good thing about this representation, however, is that there is no need for plugins to agree on some naming scheme for gates. After all, is Y the same as y? What about Pauli_Y? Or Y180? cnot versus cx? ccnot versus toffoli? And so on. Not to mention arbitrary rotations. The matrix representation of gates is pretty universally agreed upon, global phase aside in some cases, so it serves as a sort of universal language. Which is exactly what DQCsim is trying to do: provide a universal interface for components of quantum simulations to communicate with each other at runtime.

It's also worth noting that simulations don't necessarily have to care about the name of a gate; they just have to do the math. That's not always the case though. For instance, if an operator wants to count X gates, the X matrix equivalence problem has to be solved. For this purpose, DQCsim provides the gate map interface.

Gate maps deal with the translation between DQCsim's internal format and any enumeration-based format you may come up with as a plugin developer that looks like the following:

  • Some enumeration-like type (a name, if you like) determining the type of gate. Think X, CNOT, MEASURE_Z, and so on.
  • A number of qubit arguments. Which qubit does what is up to you, depending on the gate type.
  • A number of classical arguments, wrapped in an ArbData.

Gate maps are constructed using a detector and constructor function for each gate type. You can of course define these yourself, but unless you need some exotic parameterized gate or custom gates, you shouldn't have to: DQCsim provides predefined converters for a wide number of gate types. So, in practice, you usually only have to tell DQCsim what name or enum variant you want to use for each gate.

Measurement results

Measurement objects in DQCsim consist of the following:

  • (usually) the index of the measured qubit;
  • the measured value, which may be zero, one, or undefined (to model a failed measurement);
  • an ArbData object that may contain additional information about the measurement.

The upstream plugin will store the result of the latest measurement performed on a per-qubit basis. This storage can be queried using the get_measurement() function. Measuring the same qubit twice without calling get_measurement() in between is fine; in this case, the result of the first measurement is discarded.

DQCsim requires that every qubit in the measures list of a gate results in exactly one measurement being returned. Furthermore, it is illegal to return measurement data for a qubit that was not measured. This has to do with internal optimizations in the communication protocol. DQCsim will check whether you fulfill these requirements, and issue warnings if you don't. The stored measurement results become undefined in a potentionally non-deterministic way after violating the protocol in this way, so it is important to fix these warnings when you get them.

Note that operators do not need to return all measurement results immediately. Specifically, if they propagate the measurement gate further downstream in some way, the qubits measured by that gate must not be returned immediately. Instead, these measurement results pass through the modify_measurement() callback when they become available. modify_measurement() takes one measurement result as an argument and can return zero or more measurements, which will then be passed on to the upstream plugin. The only thing that matters, ultimately, is that the measurements received by the upstream plugin correspond exactly to the qubits it measured.

Passing time

Gates in DQCsim are modeled as being performed sequentially and instantaneously. Among other things, this allows operators to insert gates into the gatestream at any time, without having to worry about violating boundary conditions. However, DQCsim does have a basic concept of time.

Specifically, an integral cycle counter is maintained for every gatestream interface. This cycle counter can be advanced by the specified number of cycles using the advance() function, which results in the advance() callback being called for the downstream plugin. Other than that, DQCsim does nothing with this timing information.

This mechanism was introduced to provide a standardized way for upstream plugins to specify how much time is passing to downstream plugins. This is important specifically for error model operators, which may randomly insert gates in response to the advance() callback to decohere the quantum state.

Gatestream arbs

In addition to the above, the upstream plugin can send ArbCmds to the downstream plugin. These operate like synchronous remote procedure calls, taking an ArbCmd as argument and sending an ArbData or error message in response.

This mechanism can for instance be used to tell the downstream plugin to dump its quantum state for debug purposes.