DQCsim

The Delft Quantum & Classical simulator

DQCsim is a framework that can be used to tie components of quantum computer simulators together in a standardized yet extensible, developer-friendly, and reproducible way.

Framework Components Developer-friendly
DQCsim only provides interfaces to tie simulator components together. That is, it does not contain any simulation code on its own. DQCsim is all the boilerplate code that you don't want to write when you're developing a new way to simulate qubits, a microarchitecture simulator, an error model, etc. DQCsim abstracts a quantum computer simulation into four components: hosts, frontends, operators, and backends. These components are separate operating system processes that each fulfill a well-defined function within the simulation, thus splitting the simulation up into more manageable parts. All the components can be written in Python, C, C++, or Rust. Just use whichever language you prefer – or any combination of those languages! DQCsim will automatically tie everything together for you at runtime, so you can focus on writing quantum algorithms instead of fighting CPython.
Standardized Extensible Reproducible
DQCsim fully specifies a set of core features that each component needs to support, as well as the interfaces used to drive them. Therefore, as long as the components don't rely on any user-defined extra features in other components, they can be swapped out without breaking anything. Besides standardizing the basic features of each component, DQCsim provides an interface for users to implement their own features, without needing to change anything in DQCsim's codebase. So don't panic about DQCsim being written in Rust: you shouldn't need to read or write a single line of code in here! While quantum mechanics are inherently stochastic, simulating it needs not be. DQCsim provides a random generator to the components that should be more than random enough for simulation purposes, while being reproducible when this is desirable, such as while debugging.

Interested?

Keep reading!

(or skip directly to the install notes)

The components of a simulation

DQCsim divides a mixed quantum-classical simulation up into up to four different types of components. Put briefly, these are:

  • the frontend, which deals with the microarchitecture-specific classical part of the simulation;
  • the backend, which deals with simulating the quantum mechanics behind the qubits, usually in a microarchitecture-agnostic way;
  • any number of operators, which sit between the frontend and backend to monitor or modify the gate and measurement streams flowing between them;
  • and an optional host program, which treats the simulated quantum computer as an accelerator.

The frontend, backend, and operators are collectively referred to as plugins. Frontend and operator plugins produce a stream of gates (gatestream source), while operator and backend plugins consume such a stream (gatestream sinks). The measurement stream flows in the opposite direction, in response to the execution of a measurement gate. We'll commonly use downstream and upstream to refer to stream directions between the plugins (or the next plugin over in said direction); in this case the gatestream is used for the direction reference. That is, in a simulation with just a frontend and a backend, the backend is the downstream plugin for the frontend.

Which plugins are used for a simulation is decided by the host program when it initializes the simulation. If you don't need any special host logic, you can also use DQCsim's command-line interface to start the simulation, which normally behaves like a host that just starts and stops a simulation without interacting with it.

Process boundaries

The plugins and host programs are usually all separate processes. The host side of DQCsim spawns these plugin processes and handles all communication between them for you, so you don't need to think about it. This requires you to fundamentally separate your host, frontend, and backend logic, which will help others understand and reuse parts of your code later on. It also prevents invalid memory accesses caused by bugs from propagating into other parts of the simulation, which helps you locate the source of the problem faster.

Perhaps most importantly, the process boundaries let you program the components in different languages. After all, different problems have different requirements and different programmers solving them; there is no one language that can solve everything efficiently! Normally such language boundaries are so cumbersome that you have to pick a single language for the whole, but DQCsim handles it all for you. There is absolutely zero difference between a plugin written in C, C++, Python, or Rust from the perspective of another plugin in the same simulation!

Null plugins

To simplify testing a little bit, particularly the regression tests of DQCsim itself, DQCsim also provides a so-called "null" plugin of each type. The frontend simply does nothing, the operator passes all gates and measurements through unchanged, and the backend returns 50/50 measurement results but otherwise does nothing.

Frontend use cases

Frontends can be subdivided into roughly three different classes: mixed quantum/classical algorithms, interpreters, and microarchitecture simulators.

The first is the easiest. Instead of writing any kind of simulator, the source code for the frontend is the algorithm itself. Python would usually be used for this, since it avoids a compilation step. Whenever your algorithm has to apply a gate, you simply call the appropriate DQCsim API to send a gate to the downstream plugin. All that you then have to do to simulate the algorithm is run dqcsim my-algorithm.py [backend]. That's it!

The second frontend plugin class is a plugin that loads a file written in some domain-specific language for describing quantum algorithms, such as cQASM, and interprets it into a DQCsim gate stream line by line. The command-line interface of DQCsim has some "sugaring" built in that allows it to automatically pick the appropriate interpreter plugin based on the algorithm's file extension, so simulation is still as easy as dqcsim my-algorithm.my-dsl [backend].

The third class represents frontends that simulate classical hardware, either functionally, cycle-accurately, or something in between. Such a plugin may for instance be largely written in SystemC, or interface with other simulators such as QuestaSim or GHDL. You can make this as easy or as complex as you need, as long as the final output remains a gatestream.

Note that unlike most qubit simulators, DQCsim's gatestreams have a concept of time built into them. This allows error models to model decoherence over time in an intuitive way, without the frontend needing to insert an identity gate for each qubit each cycle.

Backend use cases

The backend part of a DQCsim simulation is what people usually mean when they talk about a quantum computer simulator: it simulates the behavior of qubits in a quantum circuit.

There are various mathematical ways to go about doing this, with their own pros and cons in terms of accuracy, simulation speed, and memory footprint. This is primarily why being able to swap out the backend is very powerful.

Another big reason for wanting to swap out backends would be to get access to different error models. However, it is strongly recommended to describe such error models in the form of a operator plugins, if it's possible to describe them by modifying or inserting additional gates.

Operator use cases

Following up on the above, a prime example of an operator plugin is an error model. Any error model that can be described by modifying the unitary matrices of the gates requested by the frontend, by inserting gates to model decoherence over time, by modifying measurement results, or any combination thereof, can be described this way. Separating the error models from the (perfect) qubit simulation allows you to mix and match both error model and simulation accuracy by just changing DQCsim's command line.

Operators are more powerful than that, though. Imagine for instance that you're developing a new map-and-route algorithm. You have to test it at some point, but adding a pass to any kind of compiler is notoriously difficult, especially if the compiler is written in a language that you're not familiar with. You may be inclined to throw your own quick-and-dirty program together instead, to be able to iterate quickly while you debug your algorithm. Then you write your paper, get a new idea (or deadline), and never get around to turning the algorithm into something that someone else can use... at least not without trudging through undocumented code to figure out how to use it. DQCsim operators provide another alternative: writing an operator plugin should be easier than starting from scratch, and it allows other people to use your algorithm more easily since the interface is standardized! Operators have full control over the way gates are passed through to the next plugin, so modifying qubit indices or inserting swap gates is not a problem.

Operators are also useful for debugging. You might want to dump the gatestream produced by your mapping algorithm in addition to just simulating it, for instance. Easy – just write another operator that "tee's" the gatestream to a file.

An operator also has the power to allocate additional qubits. You can for instance model an upstream qubit as multiple downstream qubits to model some error correction code. You can then easily test the effect of the code easily by swapping out some error operators and the code operator itself.

Host use cases

There are two main use cases for host programs. The first – on the short term probably the most useful – is to provide an easy way to code batch simulations. Just write a couple nested loops around the simulation code that swap out plugins or modify parameters and collect the results in some file or database! DQCsim also lets you insert plugin threads into the simulation pipeline, which provide you with an easy way to collect statistics from various parts of the gatestream, without needing to compile and interface with a custom plugin executable.

The second use case for host programs is to model quantum accelerators. In this case, the host program is probably a larger software package, such as a genome sequencing tool, that could be accelerated using a quantum chip. This chip may not exist yet, but by loading DQCsim's host library you can still simulate it... as long as it doesn't use too many qubits of course. DQCsim's host interface is defined fairly generically, such that it should be possible to make a drop-in replacement for the control systems of a real quantum computer later on!

Jigsaw puzzle analogy

One way to represent the components of DQCsim is through jigsaw puzzle pieces. Each piece represents a component, while the shapes on the sides of the pieces represent the interfaces. Just like in a jigsaw puzzle, these interfaces must match exactly for the program to work.

Within this analogy, DQCsim provides the following jigsaw pieces.

But you can't make a finished (rectangular) puzzle with those pieces alone; you need to add some of your own. You can use the following shapes:

Here's an example of a finished puzzle:

You could decide not to use DQCsim's pieces at all, by just connecting a C frontend to a C backend, for instance. This actually only sort of works outside the analogy, since DQCsim's gatestream sink interface does not correspond exactly to its source interface, but you get the idea. However, you can't just connect a Python frontend to a C++ backend. It doesn't fit; you'd have to make an adapter piece first. You also can't add host logic easily, and don't get a command line parser for free. This is what DQCsim provides for you.

DQCsim's interfaces

The big thing for any framework from a user's perspective are the interfaces that the framework provides. This section introduces the most important ones conceptually. The exact interfaces differ for each programming language, so for more detailed information refer to the API documentation chapters instead.

Contents

ArbData and ArbCmds

Before we get into defining the host and gatestream interfaces, we need to define a generic objects through which data and user-defined commands can be communicated. We've named these objects ArbData and ArbCmds, short for arbitrary data and arbitrary commands.

These objects are used wherever something needs to be communicated that DQCsim doesn't necessarily need to know about. In some cases they'll be accompanied by additional data that is defined/standardized by DQCsim, to prevent everyone from rolling their own protocols for common stuff. After all, that would make swapping out different plugins a lot of trouble.

Arbitrary data

An ArbData object consists of a JSON-like object (specifically CBOR, which is a superset of JSON) and a list of binary strings. The JSON-like object can be used to represent structured data, whereas the binary strings are intended for unstructured data, or data that's structured in a way that doesn't mesh well with JSON.

The advantage of JSON data is that it's "annotated" through dictionary keys. It therefore lends itself better for backward- and forward-compatibility. JSON objects are also easy to print and make sense of. However, they're rather heavyweight. This introduces complexity into the plugins, and may therefore slow down the simulation. In cases where this is a concern, using the binary string list is probably a better option.

Arbitrary commands

ArbCmds expand on ArbData objects to not only communicate a bit of data, but also intent. Think of them as remote procedure calls. The desired procedure to be called is identified by two strings, called the interface ID and the operation ID. Both of these must match exactly (case-sensitive) for a command to be accepted. The difference between the two lies within how unsupported procedures are handled:

  • If the interface ID is known but the operation ID isn't, the target plugin must return an error.
  • If the interface ID is not known to the target plugin, it must acknowledge the request with an empty return value.

This allows ArbCmds to be used as hints (also known as pragmas) that get silently ignored if they're not available or applicable.

To test if a plugin supports a certain interface programmatically, you can send an ArbCmd with the respective interface ID and a dummy operation ID, such as a single underscore. If this returns an error, the interface is supported, per the default behavior described above.

When you need to make a new ArbCmd, it is recommended to set the interface ID to the name of your plugin. Alternatively, if you intend to add support for your interface in multiple plugins, come up with a sane name for the interface as a whole. Make sure to document the interfaces your plugin supports!

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.

The host interface

The host interface, also known as simulator or accelerator interface, connects the host (or DQCsim command-line interface) to the frontend. Especially from the perspective of the host, it intends to be as generic as possible – sufficiently generic, even, to allow for drop-in replacements with a real quantum computer control system once it becomes available. The following graphic shows the functions and callbacks used to form the interface on either side.

Algorithm execution

It is assumed that the quantum accelerator can only execute one algorithm at a time; that is, it is single-threaded. However, multiple algorithms can be run sequentially within the context of a simulation. It's also possible to control multiple parallel quantum accelerators from a single program by simply initializing DQCsim multiple times from different threads,

The host starts an algorithm by calling start(). This function takes an ArbData as an argument, which may for instance be used to select which algorithm to run for microarchitectures that allow multiple to be loaded in memory at the same time. This call is asynchronous; that is, it requests that the accelerator starts running, but does not wait for it to complete. Instead, this waiting has to be done explicitly through a call to wait wait(). This allows the quantum accelerator to run in parallel to the classical logic in the host program, even though the quantum accelerator itself is single-threaded.

Algorithm execution is modeled by means of a single callback on the frontend side. This callback takes the ArbData passed to start() as an argument. It also returns an ArbData; this response is returned to the host through the return value of wait().

Communication

While both the host and the quantum accelerator are running in parallel, they can communicate with each other through two queues, one in either direction. These queues carry ArbData objects as packets. The send() function asynchronously pushes a message into the respective queue, while the recv() returns a message from the queue. recv() will block until a message is available.

Host arbs

In addition to the above, the host can send ArbCmds to the accelerator. 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 model device memory access, or to query implementation-specific information.

Miscellaneous interfaces

Besides the interfaces described previously, DQCsim provides some miscellaneous services.

Plugin construction and destruction

Each type of plugin can define an initialize() and a drop() callback. DQCsim will ensure that these callbacks are called respectively before and after all other callbacks.

The initialize() callback takes a list of ArbCmds as an argument. These are configured by the host when the plugin is constructed. They can be regarded as a specialization of host arbs that deals with initialization specifically.

Host arbs to operators and backends

In addition to the host being able to send arbs to the frontend, it can also send arbs to the operator(s) and backend. The mechanism is the same. Synchronization with respect to the rest of the simulation is guaranteed at all times.

Logging

Each plugin has a log() function of some kind that allows it to send log messages through DQCsim's centralized logging framework. Using this system versus simply writing to stdout or stderr has the benefit of being synchronized (since I/O streams are not thread-safe), and allows users to filter out messages that they're not interested in.

Reproducibility

DQCsim provides various mechanisms that can be used to reproduce a previously performed simulation. This is intended primarily for debugging. Say, for instance, that the simulation returns a result that you were not expecting, while you did'nt have the logging verbosity set high enough to debug it. Without a way to precisely reproduce the simulation run, you may never get the same result again if it was due to random chance!

Random seed

DQCsim provides each plugin with a pseudorandom number generator that is guaranteed to return the same results every time for a specific random seed. By default, the random seed is randomly generated using entropy from the system clock, but you can also specify it manually to get deterministic simulations.

Reproduction files

More powerful than simply setting a random seed are reproduction files. A reproduction file is a YAML description of a previously performed simulation, containing all information needed to reproduce the simulation under normal circumstances. Sometimes DQCsim may need some help from the user, for instance when the simulation is to be reproduced on another computer, but in general it should work out of the box.

The command-line interface allows you to reproduce a simulation either exactly or as if you were reproducing a physical experiment. The seed specified in the reproduction file is ignored in the latter case.

When you run a simulation using the command-line interface, a reproduction file will be generated by default. In this case, the reproduction file is ultimately little more than the options you specified on the command. However, it is usually also possible to generate a reproduction file when a host program is involved. In this case, DQCsim will record the functions it calls on the host interface into the reproduction file, such that the command-line interface can reproduce the simulation later. This takes any non-deterministic behavior of the host program out of the equation.

Installation

Here's how to install DQCsim. If you're on Linux or macOS, it's easy: just install Python 3.5+ and follow one of the three installation methods listed below. If you're on Windows, you'll unfortunately have to wait, since DQCsim has a dependency that doesn't support Windows.

Recommended method (requires superuser access)

The recommended way to install DQCsim is through Python's package manager in the usual way:

$ sudo pip3 install dqcsim

Besides the Python module, this also installs the development headers and dynamic libraries needed to develop C/C++ plugins or host programs. On most distributions Python installs into /usr/local, which should be part of your compiler's search paths already.

Installation into your home directory

If you don't have superuser access, you can also install to your home directory as follows:

$ pip3 install dqcsim --user

This will normally install the package into ~/.local. You should probably check if ~/.local/bin is in your $PATH environment variable, otherwise the command-line interface and plugins may not work out of the box. If you're developing in C or C++, you'll also have to add the following to CFLAGS: -I ~/.local/include -L ~/.local/lib.

Installation into a venv

You can also install into a venv. This is particularly useful if you want to have multiple versions installed at the same time. To create a venv and install into it, run the following:

$ mkdir -p <your-install-directory>
$ cd <your-install-directory>
$ python3 -m venv <your-install-directory>
$ source <your-install-directory>/bin/activate
(venv) $ pip3 install dqcsim

To leave the venv, run

(venv) $ deactivate

If you're developing in C or C++, you'll also have to add the following to CFLAGS: -I <your-install-directory>/include -L <your-install-directory>/lib.

Plugin distribution

Since DQCsim is only a framework, installing just DQCsim doesn't let you do much besides development. To get started with it quickly, you'll also need to install some plugins. At the time of writing, the following plugins are available or planned.

QuantumSim backend

  • Install: sudo pip3 install dqcsim-quantumsim
  • Source/docs: GitHub

A very lightweight connection to the QuantumSim simulator. Very suitable as a more in-depth example for Python plugin development, as it's Python-only.

OpenQL mapper operator

  • Install: sudo pip3 install dqcsim-openql-mapper
  • Source/docs: GitHub

This plugin converts an incoming gatestream using virtual qubits to physical qubits in the outgoing gatestream, inserting swaps and moves along the way to satisfy configurable connectivity constraints, using the OpenQL mapper.

QX backend

  • Install: sudo pip3 install dqcsim-qx
  • Source/docs: GitHub

A connection to the QX simulator.

OpenQASM frontend

  • Install: cargo install dqcsim-openqasm
  • Source: Github

Allows execution of OpenQASM algorithm descriptions.

cQASM frontend

  • Install: sudo pip3 install dqcsim-cqasm
  • Source: Github

Allows execution of cQASM algorithm descriptions, giving DQCsim access to the full capability of the OpenQL compiler, as cQASM is its primary output format.

cQASM output operator (planned)

Interprets the gatestream passing through it to write a cQASM file.

Metrics operator (planned)

Interprets the gatestream passing through it to calculate statistics, like circuit depths, number of swap gates, and so on.

[Your plugin here]

Developing plugins for DQCsim is very easy once you get the hang of it! Most of the plugins listed above were written in about a day, initial debugging of DQCsim aside. Keep reading!

Command-line interface

TODO

For now, just run dqcsim --long-help to get the built-in documentation.

Python API

The Python API allows you to use DQCsim using Python 3, in order to both construct plugins and control simulations. It is built on top of the C API, but adds a thick abstraction layer to turn it into a Pythonic interface.

How to read this chapter

This chapter reads like a tutorial to DQCsim's Python API to get you coding quickly. However, it doesn't even get close to documenting every single feature. If you're looking for something more complete, check out the generated API documentation here.

The tutorial assumes that you already know what DQCsim is, and have a decent understanding of the basic concepts. If you don't, start here.

Contents

Hello, world!

Let's start with a basic frontend plugin, Hello, world! style. This is all you need:

from dqcsim.plugin import *

@plugin("My Plugin", "Me!", "0.1")
class MyPlugin(Frontend):
    def handle_run(self):
        self.info('Hello, world!')

MyPlugin().run()

This frontend just logs Hello, world! with the info loglevel when run. You can use the command line to test it as follows:

$ dqcsim my-plugin.py null
... Info dqcsim  Starting Simulation with seed: ...
... Info back    Running null backend initialization callback
... Info dqcsim  Executing 'start(...)' host call...
... Info dqcsim  Executing 'wait()' host call...
... Info front   Hello, world!
... Note dqcsim  'wait()' returned {}
... Info dqcsim  Reproduction file written to "my-plugin.py.repro".
... Info dqcsim  Simulation completed successfully.
... Info dqcsim  PluginProcess exited with status code: 0
... Info dqcsim  PluginProcess exited with status code: 0

Note the Hello, world! line in the middle.

While you're debugging a plugin, you might want to change the log verbosities around a little. For instance, the following will set the verbosity of your plugin to the debug level, and the verbosity of the other plugins and DQCsim itself to error.

$ dqcsim -ld --plugin-level e --dqcsim-level e my-plugin.py -ld null
... Info front   Hello, world!

Take a few minutes to look through dqcsim --long-help to see what those options mean and what else it can do for you, specifically on the subject of logging!

How it works

Let's dissect the hello world plugin line by line.

from dqcsim.plugin import *

This loads the DQCsim plugin library and pulls it into our module's scope. Specifically, we're using Frontend and plugin in this script.

@plugin("My Plugin", "Me!", "0.1")
class MyPlugin(Frontend):

These lines define a new plugin. Any plugin must derive from either Frontend, Operator, or Backend; we're deriving from Frontend here.

The @plugin annotation specifies some metadata for the plugin, namely the plugin's name, author, and version. The host can access this metadata to verify that it loaded the right plugin. While DQCsim requires you to specify these three strings, it doesn't actually do anything with it on its own.

def handle_run(self):

This function is called by DQCsim in response to the start() command from the host. There are a couple more callbacks that frontend plugins can define, but this is the most important one. It's also the only one that's required for a frontend.

self.info('Hello, world!')

This function sends a log message back to DQCsim. You can also use trace, debug, note, warn, error, or fatal to get a different loglevel. Any arguments specified in addition to the first are passed to str.format(), so you could for instance also call self.info('Hello, {}!', username) (if username would be a thing here).

MyPlugin().run()

This line actually turns the Python script into a DQCsim plugin. Without it, DQCsim would crash:

Fatal dqcsim  plugin did not connect within specified timeout

After all, just defining a class doesn't really make a Python script do anything!

The first part of the line, MyPlugin(), makes an instance of the plugin, but doesn't start it yet. This is because there are multiple ways to start a plugin, and it's also useful to pass instantiated but not-yet-started plugins around during initialization.

The second part, .run(), actually starts the plugin. It also waits for it to finish, and either returns None to indicate success or throws a RuntimeError to indicate failure.

Plugins need a simulator to connect to. DQCsim passes this endpoint as a string to the first command-line argument of a plugin process. run() let's you specify the endpoint manually if you like, but if you don't, it takes it from sys.argv[1] automatically, with appropriate error checking.

Debugging

Let's say we made a mistake in our hello world plugin. For instance,

from dqcsim.plugin import *

@plugin("My Plugin", "Me!", "0.1")
class MyPlugin(Frontend):
    def handle_run(self):
        self.ifo('Hello, world!')

MyPlugin().run()

Can you spot the typo? Running the plugin in DQCsim with its default settings will help a little bit already:

$ dqcsim my-plugin.py null
...  Info dqcsim  Starting Simulation with seed: 9198852889345625466
...  Info back    Running null backend initialization callback
...  Info dqcsim  Executing 'start(...)' host call...
...  Info dqcsim  Executing 'wait()' host call...
... Error front   'MyPlugin' object has no attribute 'ifo'
...  Info dqcsim  Reproduction file written to "my-plugin.py.repro".
... Fatal dqcsim  Simulation failed: 'MyPlugin' object has no attribute 'ifo'
...  Info dqcsim  PluginProcess exited with status code: 0
...  Info dqcsim  PluginProcess exited with status code: 0

Apparently we typed self.ifo somewhere. Easily fixed in this case... but there's no traceback, or even a line number in the log. This is because DQCsim's error handling system only propagates the error message itself, without any metadata. Propagating things like tracebacks properly would be pretty hard after all, with all the potential programming language boundaries.

The Python module does however log tracebacks of exceptions raised from within your callback functions. They're simply suppressed by default, as they're printed with the trace loglevel. To see them, you'll need to change DQCsim's command line:

$ dqcsim -lt my-plugin.py null
...
... Trace ... Traceback (most recent call last):
...
... Trace ...   File "my-plugin.py", line 6, in handle_run
... Trace ...     self.ifo('Hello, world!')
... Trace ... AttributeError: 'MyPlugin' object has no attribute 'ifo'
...

There are quite some messages to sift through with this verbosity, but the ones you're looking for would normally also be in there. If it's not, DQCsim's Python module is itself confused, either because you're doing something it didn't expect, or because there is a bug somewhere. In this case you'll have to narrow the problem down through trial and error.

Reproduction files

It's also possible that your exception only happens sometimes, because of some non-deterministic quantum behavior being modelled, or because you're running the plugin from a non-deterministic host program. In this case, you can try using reproduction files. The command-line interface will output one by default whenever you run a simulation, named after the frontend plugin with a .repro suffix. So, in this case, the following command should reproduce the previous run without any log message filtering.

$ dqcsim -lt --reproduce-exactly my-plugin.py.repro

Sending some gates

Let's change our plugin to make it do something more useful now. We'll implement the Deutsch–Jozsa algorithm because it's nice and simple. Put briefly, this algorithm determines whether a one-qubit to one-qubit oracle function is constant (x -> 0 or x -> 1) or balanced (x -> x or x -> !x) by only evaluating the function once.

Here's the plugin code:

from dqcsim.plugin import *

@plugin("Deutsch-Jozsa", "Tutorial", "0.1")
class MyPlugin(Frontend):

    def oracle_constant_0(self, qi, qo):
        """x -> 0 oracle function."""
        pass

    def oracle_constant_1(self, qi, qo):
        """x -> 1 oracle function."""
        self.x_gate(qo)

    def oracle_passthrough(self, qi, qo):
        """x -> x oracle function."""
        self.cnot_gate(qi, qo)

    def oracle_invert(self, qi, qo):
        """x -> !x oracle function."""
        self.cnot_gate(qi, qo)
        self.x_gate(qo)

    def deutsch_jozsa(self, qi, qo, oracle):
        """Runs the Deutsch-Jozsa algorithm on the given oracle. The oracle is
        called with the input and output qubits as positional arguments."""

        # Prepare the input qubit.
        self.prepare(qi)
        self.h_gate(qi)

        # Prepare the output qubit.
        self.prepare(qo)
        self.x_gate(qo)
        self.h_gate(qo)

        # Run the oracle function.
        oracle(qi, qo)

        # Measure the input.
        self.h_gate(qi)
        self.measure(qi)
        if self.get_measurement(qi).value:
            self.info('Oracle was balanced!')
        else:
            self.info('Oracle was constant!')

    def handle_run(self):
        qi, qo = self.allocate(2)

        self.info('Running Deutsch-Jozsa on x -> 0...')
        self.deutsch_jozsa(qi, qo, self.oracle_constant_0)

        self.info('Running Deutsch-Jozsa on x -> 1...')
        self.deutsch_jozsa(qi, qo, self.oracle_constant_1)

        self.info('Running Deutsch-Jozsa on x -> x...')
        self.deutsch_jozsa(qi, qo, self.oracle_passthrough)

        self.info('Running Deutsch-Jozsa on x -> !x...')
        self.deutsch_jozsa(qi, qo, self.oracle_invert)

        self.free(qi, qo)

MyPlugin().run()

The plugin runs the algorithm on all possible oracles in sequence. Observe that we hardly had to duplicate any code to do that, by making clever use of Python's expressivity! Also note that DQCsim's Python plugin provides you with a number of built-in gate types. You can find the full list here.

You can run this example with the null backend provided by DQCsim as follows.

$ dqcsim my-plugin.py null
... Info dqcsim  Starting Simulation with seed: 17518393962103239508
... Info back    Running null backend initialization callback
... Info dqcsim  Executing 'start(...)' host call...
... Info dqcsim  Executing 'wait()' host call...
... Info front   Running Deutsch-Jozsa on x -> 0...
... Info front   Oracle was constant!
... Info front   Running Deutsch-Jozsa on x -> 1...
... Info front   Oracle was balanced!
... Info front   Running Deutsch-Jozsa on x -> x...
... Info front   Oracle was balanced!
... Info front   Running Deutsch-Jozsa on x -> !x...
... Info front   Oracle was constant!
... Note dqcsim  'wait()' returned {}
... Info dqcsim  Reproduction file written to "my-plugin.py.repro".
... Info dqcsim  Simulation completed successfully.
... Info dqcsim  PluginProcess exited with status code: 0
... Info dqcsim  PluginProcess exited with status code: 0

Note that the null backend ignores all gates and just returns random measurements, so the algorithm also just returns random results. To make the algorithm work, you'll have to use a real backend.

Controlling simulations

Right now, our Deutsch-Jozsa algorithm always executes all oracles once and logs the results. This is fine when you just want to run it once, but what if you want to run it 1000 times, maybe with an error model in between, and apply some statistics to the results? You could of course make a script that just calls the DQCsim command line 1000 times and parses the stderr stream for every call... but that would be a very fragile solution, and it'd be annoying to make.

Instead, we can use DQCsim's host interface. Let's change the frontend plugin to make use of this feature.

from dqcsim.plugin import *

@plugin("Deutsch-Jozsa", "Tutorial", "0.2")
class MyPlugin(Frontend):

    def oracle_constant_0(self, qi, qo):
        """x -> 0 oracle function."""
        pass

    def oracle_constant_1(self, qi, qo):
        """x -> 1 oracle function."""
        self.x_gate(qo)

    def oracle_passthrough(self, qi, qo):
        """x -> x oracle function."""
        self.cnot_gate(qi, qo)

    def oracle_invert(self, qi, qo):
        """x -> !x oracle function."""
        self.cnot_gate(qi, qo)
        self.x_gate(qo)

    def deutsch_jozsa(self, qi, qo, oracle):
        """Runs the Deutsch-Jozsa algorithm on the given oracle. The oracle is
        called with the input and output qubits as positional arguments."""

        # Prepare the input qubit.
        self.prepare(qi)
        self.h_gate(qi)

        # Prepare the output qubit.
        self.prepare(qo)
        self.x_gate(qo)
        self.h_gate(qo)

        # Run the oracle function.
        oracle(qi, qo)

        # Measure the input.
        self.h_gate(qi)
        self.measure(qi)
        if self.get_measurement(qi).value:
            self.send(result='balanced')
        else:
            self.send(result='constant')

    def handle_run(self, oracle='', runs=1):

        oracle = {
            '0': self.oracle_constant_0,
            '1': self.oracle_constant_1,
            'x': self.oracle_passthrough,
            '!x': self.oracle_invert,
        }.get(oracle, None)

        if oracle is None:
            raise ValueError('Please specify an oracle!')

        qi, qo = self.allocate(2)

        for _ in range(runs):
            self.deutsch_jozsa(qi, qo, oracle)

        self.free(qi, qo)

MyPlugin().run()

When we try to run this frontend with DQCsim's command line, you'll get an error:

$ dqcsim my-plugin.py null
...  Info dqcsim  Starting Simulation with seed: 15043164643727486506
...  Info back    Running null backend initialization callback
...  Info dqcsim  Executing 'start(...)' host call...
...  Info dqcsim  Executing 'wait()' host call...
... Error front   Please specify an oracle!
...  Info dqcsim  Reproduction file written to "my-plugin.py.repro".
... Fatal dqcsim  Simulation failed: Please specify an oracle!
...  Info dqcsim  PluginProcess exited with status code: 0
...  Info dqcsim  PluginProcess exited with status code: 0

Indeed, we've changed the algorithm such that our handle_run() callback wants some arguments.

Remember how the start() call takes an ArbData as argument? The Python module passes the contents of this ArbData as arguments to handle_run(). Specifically, the binary strings are passed as positional arguments (*args), and the toplevel entries in the JSON/CBOR object are passed as keyword arguments (**kwargs). This abstraction is used all over the place in the Python layer, including when you have to send an ArbData with it, because it gives the callbacks a nice, Pythonic interface.

It's possible to tell the command-line interface to pass arguments to the start() call as follows, though the syntax isn't very friendly:

$ dqcsim -C 'start:{"oracle":"x"}' my-plugin.py null
... Info dqcsim  Starting Simulation with seed: 13451103132954817086
... Info back    Running null backend initialization callback
... Info dqcsim  Executing 'start(...)' host call...
... Info dqcsim  Executing 'wait()' host call...
... Note dqcsim  'wait()' returned {}
... Info dqcsim  Reproduction file written to "my-plugin.py.repro".
... Info dqcsim  Simulation completed successfully.
... Info dqcsim  PluginProcess exited with status code: 0
... Info dqcsim  PluginProcess exited with status code: 0

Now we don't get an error anymore... but we also don't get any output. Look again at the revised Python script: instead of logging the results, we send() them. send() takes an ArbData as argument, and thus, like the syntax we used for our handle_run() callback, we can construct it by passing positional and keyword arguments.

To see the result, we need to make DQCsim call recv() before it exits. We can do that through the command line as well:

$ dqcsim -C 'start:{"oracle":"x"}' -C recv my-plugin.py null
... Info dqcsim  Starting Simulation with seed: 8371252716093296353
... Info back    Running null backend initialization callback
... Info dqcsim  Executing 'start(...)' host call...
... Info dqcsim  Executing 'recv()' host call...
... Note dqcsim  'recv()' returned {"result":"balanced"}
... Info dqcsim  Executing 'wait()' host call...
... Note dqcsim  'wait()' returned {}
... Info dqcsim  Reproduction file written to "my-plugin.py.repro".
... Info dqcsim  Simulation completed successfully.
... Info dqcsim  PluginProcess exited with status code: 0
... Info dqcsim  PluginProcess exited with status code: 0

Now dqcsim notes that the recv() call returned {"result":"balanced"}, which coincidentally is the correct output of the algorithm. Since we're still using the null backend, the result is just 50/50.

Constructing an accompanying host program

It makes little sense to do all that work only to get a less convenient command-line interface. Indeed, this mechanism is not intended to be used this way. You should use it with a host program instead.

You can write this in any of the languages DQCsim supports, since it runs in a different process. But since this is a Python tutorial, we'll use Python.

Here's an example of a host program that makes use of our modified frontend.

from dqcsim.host import *

runs = 1000
oracle = 'x'

with Simulator('my-plugin.py', 'null') as sim:
    sim.start(oracle=oracle, runs=runs)
    results = [sim.recv()['result'] for _ in range(runs)]
    sim.wait()

print('Number of balanced outcomes:', results.count('balanced'))
print('Number of constant outcomes:', results.count('constant'))

Calling this script may yield the following:

$ python3 simulate.py
... Info dqcsim  Starting Simulation with seed: 14750381695807274720
... Info back    Running null backend initialization callback
... Info dqcsim  PluginProcess exited with status code: 0
... Info dqcsim  PluginProcess exited with status code: 0
Number of balanced outcomes: 493
Number of constant outcomes: 507

Let's dissect this script.

from dqcsim.host import *

This line loads DQCsim's host library and brings it into scope. Specifically, we're using the Simulator class.

runs = 1000
oracle = 'x'

These variables set the number of runs and the oracle under test.

with Simulator('my-plugin.py', 'null') as sim:

This line starts a simulation. You can call the Simulator() constructor in many different ways; the one we're using here is very similar to the command line we used before. However, instead of using strings to try to hack a list of host calls in there (which isn't even supported by the constructor), we'll interact with the simulation while it's running.

The with syntax used here is shorthand notation for the following:

sim = Simulator('my-plugin.py', 'null')
sim.simulate()
# block contents
sim.stop()

Calling the Simulator() constructor doesn't really do anything yet; it just configures a simulation. In fact, you're free to use it more than once, as long as you don't try to have multiple simulations running at the same time.

The simulate() function actually starts the simulation, in the sense that it spawns the plugins and calls their initialization callbacks (if applicable), but it doens't call our handle_run() callback yet. The stop() function performs the inverse operation of simulate(); it ensures that all the spawned processes get cleaned up.

sim.start(oracle=oracle, runs=runs)

This line actually starts the simulation. The keyword arguments specified are used to construct the ArbData argument, which the plugin library unpacks into keyword arguments again when it calls handle_run().

results = [sim.recv()['result'] for _ in range(runs)]

This list comprehension calls recv() just as many times as the algorithm runs for, and records the result key of the returned ArbData object into a list. These entries should be either 'balanced' or 'constant'.

Note that there is a possibility of a deadlock here: if the algorithm wouldn't call send() as often as we expect, everything would logically lock up. DQCsim detects all possible kinds of deadlocks on the host interface however, and immediately throws an exception instead. Try it out!

sim.wait()

This line waits for the handle_run() callback to return. wait() returns an ArbData object corresponding to what the handle_run() returned, but we're not using that feature, so we just discard it.

print('Number of balanced outcomes:', results.count('balanced'))
print('Number of constant outcomes:', results.count('constant'))

These two lines count the number of 'balanced' vs. 'constant' occurrences in the result list. As we can see when we run the script, the chance is 50/50. With a perfect qubit simulator backend you should get 1000 balanced and zero constant outcomes. When you add an error model to the simulation, you might get something in between.

Note that we're using regular print statements here, versus using DQCsim's log system. In fact, we have to do this: DQCsim's log system is intended for the plugins only. You can, however, instruct the Simulator() object to forward the messages it receives to Python's logging library to get uniform output. This is beyond the scope of this tutorial.

Inserting an operator

Now that we have a simulation to play around with, let's add an operator.

There are many types of operators conceivable. To keep things simple here, we'll use an operator that just affects measurements as an example. Here's the code:

from dqcsim.plugin import *

@plugin("Measurement-Error", "Tutorial", "0.1")
class MyOperator(Operator):

    def __init__(self):
        super().__init__()
        self.one_error = 0.0
        self.zero_error = 0.0

    def handle_host_measurementError_setOneError(self, value=0.0):
        self.one_error = value

    def handle_host_measurementError_setZeroError(self, value=0.0):
        self.zero_error = value

    def handle_measurement(self, measurement):
        if measurement.value == 1:
            measurement.value = self.random_float() < self.one_error
        elif measurement.value == 0:
            measurement.value = self.random_float() >= self.zero_error
        return measurement

MyOperator().run()

To use it within the simulation created in the previous section, replace the following line:

with Simulator('my-plugin.py', 'null') as sim:

With this:

with Simulator('my-plugin.py', 'my-operator.py', 'null') as sim:

What this does should be obvious – it sticks the operator we just made in between, assuming you used the same filename.

Running it doesn't change much though:

$ python3 simulate.py 
... Info dqcsim  Starting Simulation with seed: 14673804979996191647
... Info back    Running null backend initialization callback
... Info dqcsim  PluginProcess exited with status code: 0
... Info dqcsim  PluginProcess exited with status code: 0
... Info dqcsim  PluginProcess exited with status code: 0
Number of balanced outcomes: 498
Number of constant outcomes: 502

Still 50/50. Of course, that's because we never set any error rates.

Dissecting the new plugin

from dqcsim.plugin import *

@plugin("Measurement-Error", "Tutorial", "0.1")
class MyOperator(Operator):
    ...

MyOperator().run()

This part is the same as what we've seen before, except we've renamed some things and are deriviing from Operator instead of Frontend. You'll see these lines in every DQCsim plugin; it's the boilerplate code that turns an otherwise regular Python script into a valid plugin.

def __init__(self):
    super().__init__()
    self.one_error = 0.0
    self.zero_error = 0.0

We've built the plugin such that we can set the measurement error rates. Specifically, we specify two probabilities:

  • one_error is the chance that a qubit observed to be in the one state is measured as zero;
  • zero_error is the chance that a qubit observed to be in the zero state is measured as one.

We can store these parameters within our operator class by defining them in its initializer, as done here.

Note specifically the super().__init__() line. It's often forgotten, but in fact, you have to call this any time you override any Python class' constructor, or your superclass' constructor will simply never be called. In this case, not doing it will break DQCsim, although it will tell you what you need to do to fix it in the error message.

def handle_host_measurementError_setOneError(self, value=0.0):
    self.one_error = value

def handle_host_measurementError_setZeroError(self, value=0.0):
    self.zero_error = value

These two functions provide entry points for any ArbCmds sent to us by the host or by the initialization callback (since we didn't override it). The interface and operation IDs are in the handler's name, as is the source of the command, which can be host or upstream.

The Python module detects which interfaces your plugin supports by looking for handlers of the form handle_host_<interface>_<operation>() inside the plugin constructor. Interface and operation IDs preferably don't contain any underscores, because this makes the split between the interface and operation ID ambiguous. In this case, the automatic detection algorithm will throw an error, and ask you to specify which interfaces your plugin supports through a keyword argument to the plugin's constructor.

In this case, the detection algorithm works just fine, and determines that the plugin supports the measurementError interface. Within the interface, two operations are defined, setOneError and setZeroError; any other operation will return an error.

def handle_measurement(self, measurement):
    if measurement.value == 1:
        measurement.value = self.random_float() < self.one_error
    elif measurement.value == 0:
        measurement.value = self.random_float() >= self.zero_error
    return measurement

This function is the core of the plugin. If it exists, it is called by DQCsim whenever a measurement passes through the operator, to allow the operator to return a modified measurement instead. It can also turn a single measurement into a list of measurements or block propagation of the measurement, but you wouldn't need to do this unless you're making some complex mapping algorithm.

The implementation provided here modifies the measurement value based on the perfect measurement received from downstream, DQCsim's random generator, and the configured probabilities.

Testing the error model

Let's change the Simulator() arguments again. It's getting a little complex now, so we'll fold it apart for clarity:

with Simulator(
    'my-plugin.py',
    (
        'my-operator.py',
        {
            'init': [
                ArbCmd('measurementError', 'setZeroError', value=0.5)
            ]
        }
    ),
    'null'
) as sim:

This adds an initialization command to our error model operator, that sets the measurement error probability for a zero observation to 0.5. When we run the program now, we get something like this:

$ python3 simulate.py
... Info dqcsim  Starting Simulation with seed: 8830780357769760084
... Info back    Running null backend initialization callback
... Info dqcsim  PluginProcess exited with status code: 0
... Info dqcsim  PluginProcess exited with status code: 0
... Info dqcsim  PluginProcess exited with status code: 0
Number of balanced outcomes: 266
Number of constant outcomes: 734

Success – this is clearly not 50/50 anymore! Half of the balanced outcomes are converted to constant by the error model, so the probability is 25/75 now.

To make the Simulator() syntax a bit more aesthetically pleasing, we can configure the simulation in multiple steps, like this:

sim_config = Simulator('my-plugin.py', 'null')
sim_config.with_operator(
    'my-operator.py',
    init=ArbCmd('measurementError', 'setZeroError', value=0.5))
with sim_config as sim:

Functionally, this is exactly the same.

Changing error model parameters at runtime

Because our operator allows the values to be set with run-time ArbCmds as well, we can also do the following:

with Simulator('my-plugin.py', 'my-operator.py', 'null') as sim:
    sim.start(oracle=oracle, runs=runs)
    results = [sim.recv()['result'] for _ in range(runs)]
    sim.wait()

    sim.arb('op1', 'measurementError', 'setZeroError', value=0.5)

    sim.start(oracle=oracle, runs=runs)
    results += [sim.recv()['result'] for _ in range(runs)]
    sim.wait()

Now half of our simulation runs at 50/50 probability, and half of it at 25/75. So we expect to get around 38/62. Indeed:

$ python3 simulate.py 
... Info dqcsim  Starting Simulation with seed: 6379995085809620608
... Info back    Running null backend initialization callback
... Info dqcsim  PluginProcess exited with status code: 0
... Info dqcsim  PluginProcess exited with status code: 0
... Info dqcsim  PluginProcess exited with status code: 0
Number of balanced outcomes: 722
Number of constant outcomes: 1278

Note however, that sending a new probability in the middle of our recv() calls will not work. This is because the frontend does not wait for recv() to be called; send() is asynchronous! To make that work, you'd have to have the frontend wait for the host through a recv() call, or have the frontend update the operator's parameters while it's running. In the latter case, you also have to define handle_upstream_*() equivalents for the handle_host_*() callbacks.

Reference

This is the end of the tutorial for now, but many more features are already supported by DQCsim. More exhaustive documentation is available through Python's builtin help()/docstring system, or on the pages generated from this by pdoc3 here.

C API

The C API allows any language that supports C bindings to use DQCsim for making plugins or host programs. It consists of a shared object and a header file, which are automatically installed along with the DQCsim Python package (more detailed notes here).

How to read this chapter

The sections form a somewhat coherent story that runs you through the entire API, starting with some conceptual things, and followed by a walkthrough of the objects encapsulated by the API using a bottom-up approach. The final section summarizes all the functions/types exposed by the API in alphabetical order, in case you're just looking for a searchable list.

The documentation assumes that you already know what DQCsim is, and have a decent understanding of the basic concepts. If you don't, start here.

Contents

Usage

The DQCsim C API consists of two files: the header file (dqcsim.h) and an associated dynamic library (libdqcsim.so on Linux, dqcsim.dylib on macOS). These will be installed automatically in the include and lib directories that Python is aware of when DQCsim is installed using sudo pip3 install dqcsim (more detailed notes here).

Once installed, you can use the API in your program by adding the following include to your sources:

#include <dqcsim.h>

and adding -ldqcsim to your compiler command line, specifically the linker.

Usage after install without root

If you don't have root access on your development machine (or didn't want to install DQCsim in your root directory), you'll also have to tell the compiler where you installed DQCsim. You need the following flags for that:

  • -I <path-to-dqcsim>/include: tells the compiler where to find the header file.
  • -L <path-to-dqcsim>/lib: tells the linker where to find the shared object file.

At runtime, you may need to add the library directory to your runtime linker's search path as well, using the LD_LIBRARY_PATH environment variable.

Usage after building from source

If you've built DQCsim from its source repository, you need to use the following paths:

  • -I <dqcsim-repo>/target/include for the header file;
  • -L <dqcsim-repo>/target/release or -L <dqcsim-repo>/target/debug for the shared object.

Again, you may need to add the latter to LD_LIBRARY_PATH as well.

Concepts

Now that you know how to add DQCsim to a C program, let's start with some basic concepts you should be aware of before you start actually using the API. The things in this chapter are either common to all API functions, or the more specific documentation assumes you're aware of them.

Contents

Handles

The API is based upon a handle system for referring to simulator data. Handles are like cross-language references or pointers: they point to a piece of data in the simulator, but don't contain it.

The usage of handles implies that the complex data structures contained within the simulator never actually leave the simulator. Instead, when the simulator needs to pass data to you, it returns a handle, which you can use to access the contents of the referred structure through a number of API calls. Similarly, when you need to pass something to the simulator, you construct an object through a number of API calls, and then pass the handle to the simulator.

Operating on handles

Handles can represent a number of different object types. Based on the type of object the handle represents, different interfaces are supported. For instance, ArbCmd objects support handle, arb, and cmd, while ArbData objects only support handle and arb. You can find an exhaustive list of all handle types and the interfaces they support in the documentation for dqcs_handle_type_t. Note that all normal handles support the handle interface.

The name of the API functions directly corresponds with the name of the interface it requires the primary handle it operates on to have: the functions have the form dqcs_<interface>_*.

Thread-safety

The global state that the API calls operate on is purely thread-local. This means that you can't exchange API objects/handles between threads. However, this also makes the API perfectly thread-safe.

Functions common to all handles

The following functions are available for all handle types.

dqcs_handle_delete()

Destroys the object associated with a handle.

dqcs_return_t dqcs_handle_delete(dqcs_handle_t handle)

Returns 0 when successful, -1 otherwise.

dqcs_handle_delete_all()

Deletes all handles for the current thread.

dqcs_return_t dqcs_handle_delete_all(void)

This can be used to clean stuff up at the end of main() or before an abort() of some kind. If you don't clean up properly, you might get undefined behavior or errors when DQCsim tries to do it for you.

dqcs_handle_dump()

Returns a debug dump of the object associated with the given handle.

char *dqcs_handle_dump(dqcs_handle_t handle)

On success, this returns a newly allocated string containing the description. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_handle_leak_check()

Succeeds only if there are no live handles in the current thread.

dqcs_return_t dqcs_handle_leak_check(void)

This is intended for testing and for finding handle leaks. The error message returned when handles remain contains dumps of the first 10 remaining handles.

dqcs_handle_type()

Returns the type of object associated with the given handle.

dqcs_handle_type_t dqcs_handle_type(dqcs_handle_t handle)

Memory management

For the most part, the API will handle memory allocation for you through the handle system. However, it is usually up to you to free memory back up. Failing to do this will usually not cause errors, but will adversely affect memory usage and performance.

To prevent such memory leaks, pay close attention to the documentation of the API calls you make. Most importantly, strings returned by DQCsim almost always have to be deallocated by you through free(). The only exception to that is dqcs_error_get(). You should also make sure that you delete handles that you no longer need through dqcs_handle_delete(), though most of the time DQCsim does this for you when you use a handle.

Error handling

Almost all API calls can fail, for instance because an invalid handle is supplied. Since C does not support any kind of exceptions, such failures are reported through the return value. Which value is used to indicate an error depends on the return type; refer to the data type section for more information. However, this value only indicates something went wrong, not what went wrong. The following function can be used for that.

dqcs_error_get()

Returns a pointer to the latest error message.

const char *dqcs_error_get(void)

Call this to get extra information when another function returns a failure code. The returned pointer is temporary and therefore should NOT be free()d. It will become invalid when a new error occurs.

The mechanism for reporting errors to DQCsim from within a callback is the same, but reversed: you first set the error string yourself, and then return the value that indicates that an error occurred. You can set the error as follows.

dqcs_error_set()

Sets the latest error message string.

void dqcs_error_set(const char *msg)

This must be called by callback functions when an error occurs within the callback, otherwise the upstream result for dqcs_error_get() will be undefined.

If msg is set to NULL, the error string is cleared instead.

Callbacks

In some places you can pass callbacks to the API. This is particularly important for defining plugins: the callbacks ultimately define all your plugin's functionality!

Depending on the callback, it may be called from a different thread than the one you configured it with. This is clearly documented along with the callback setter function and normally doesn't cause problems, but you should keep it in mind.

In order to support closures in higher-level languages, all callback setters take an optional cleanup callback and a void* to a piece of user data. The cleanup callback is intended for cleaning up this user data if necessary; it is called when DQCsim drops all references to the primary callback, so it is guaranteed that the primary callback is never called again when the cleanup. It is also guaranteed that the cleanup callback is executed exactly once (unless the process dies spectacularly, in which case it may not be called). However, very few guarantees are made about which thread the cleanup callback is called from! If you use it, make sure that it is thread-safe.

Type definitions

DQCsim defines some types and enumerations. These are documented below. Note that DQCsim does not define any structures; all types used on the interface are primitive. This should hopefully simplify using the bindings from languages other than C, which may not support such things.

Return codes

Almost all functions in DQCsim can fail. They indicate failure through their return value. For some types this return value is obvious; for instance, NULL is used for functions that return a string or another kind of pointer. For enumerations, the failure return value is usually 0 or -1. In other cases, the failure return value will be listed in the function documentation.

There are two special cases: functions that return a boolean and functions that don't otherwise return a value. These have the following two special enumerations defined for them:

dqcs_return_t

Default return type for functions that don't need to return anything.

typedef enum { ... } dqcs_return_t;

Variants:

DQCS_FAILURE = -1
The function has failed. More information may be obtained through `dqcsim_explain()`.
DQCS_SUCCESS = 0
The function did what it was supposed to.

dqcs_bool_return_t

Return type for functions that normally return a boolean but can also fail.

typedef enum { ... } dqcs_bool_return_t;

Variants:

DQCS_BOOL_FAILURE = -1
The function has failed. More information may be obtained through `dqcsim_explain()`.
DQCS_FALSE = 0
The function did what it was supposed to and returned false.
DQCS_TRUE = 1
The function did what it was supposed to and returned true.

Simulator object references

The following types are used to refer to simulator objects.

dqcs_handle_t

Type for a handle.

typedef unsigned long long dqcs_handle_t;

Handles are like pointers into DQCsim's internal structures: all API calls use these to refer to objects. Besides the object, they contain type information. This type can be retrieved using dqcs_handle_type().

Handles are always positive integers, counting upwards from 1 upon allocation, and they are not reused even after being deleted. Thus, every subsequent object allocation returns a handle one greater than the previous. Note however that DQCsim may allocate objects as well without the user specifically requesting this, so external code should generally not rely on this behavior unless otherwise noted. The value zero is reserved for invalid references or error propagation.

Note that the scope for handles is thread-local. That is, data referenced by a handle cannot be shared or moved between threads.

The value zero is reserved for invalid references or error propagation.

dqcs_qubit_t

Type for a qubit reference.

typedef unsigned long long dqcs_qubit_t;

Qubit references are exchanged between the frontend, operator, and backend plugins to indicate which qubits a gate operates on. Note that this makes them fundamentally different from handles, which are thread-local.

Qubit references are always positive integers, counting upwards from 1 upon allocation, and they are not reused even after the qubit is deallocated. Thus, every subsequent allocation returns a qubit reference one greater than the previous. This is guaranteed behavior that external code can rely upon. The value zero is reserved for invalid references or error propagation.

dqcs_plugin_state_t

Type for a plugin state.

typedef void *dqcs_plugin_state_t;

This is an opaque type that is passed along to plugin implementation callback functions, which those callbacks can then use to interact with the plugin instance. User code shall not create or modify values of this type, and shall only use the values when calling dqcs_plugin_* functions.

Timekeeping

DQCsim supports timed simulation using integral cycle numbers as a unit. The following typedef is used to refer to such timestamps.

dqcs_cycle_t

Type for a simulation cycle timestamp.

typedef long long dqcs_cycle_t;

Timestamps count upward from zero. The type is signed to allow usage of -1 for errors, and to allow numerical differences to be represented.

Misc. enumerations

The following enumerations are used for various purposes within the API.

dqcs_handle_type_t

Enumeration of types that can be associated with a handle.

typedef enum { ... } dqcs_handle_type_t;

Variants:

DQCS_HTYPE_INVALID = 0
Indicates that the given handle is invalid.

This indicates one of the following:

  • The handle value is invalid (zero or negative).
  • The handle has not been used yet.
  • The object associated with the handle was deleted.
DQCS_HTYPE_ARB_DATA = 100
Indicates that the given handle belongs to an `ArbData` object.

This means that the handle supports the handle and arb interfaces.

DQCS_HTYPE_ARB_CMD = 101
Indicates that the given handle belongs to an `ArbCmd` object.

This means that the handle supports the handle, arb, and cmd interfaces.

DQCS_HTYPE_ARB_CMD_QUEUE = 102
Indicates that the given handle belongs to a queue of `ArbCmd` object.

This means that the handle supports the handle, arb, cmd, and cq interfaces.

DQCS_HTYPE_QUBIT_SET = 103
Indicates that the given handle belongs to a set of qubit references.

This means that the handle supports the handle and qbset interfaces.

DQCS_HTYPE_GATE = 104
Indicates that the given handle belongs to a quantum gate description.

This means that the handle supports the handle, gate, and arb interfaces.

DQCS_HTYPE_MEAS = 105
Indicates that the given handle belongs to a qubit measurement result.

This means that the handle supports the handle, meas, and arb interfaces. It can also be used in place of a qubit measurement result set by functions that consume the object.

DQCS_HTYPE_MEAS_SET = 106
Indicates that the given handle belongs to a set of qubit measurement results.

This means that the handle supports the handle and mset interfaces.

DQCS_HTYPE_MATRIX = 107
Indicates that the given handle belongs to a matrix.

This means that the handle supports the handle and mat interfaces.

DQCS_HTYPE_GATE_MAP = 108
Indicates that the given handle belongs to a gate map.

This means that the handle supports the handle and gm interfaces.

DQCS_HTYPE_FRONT_PROCESS_CONFIG = 200
Indicates that the given handle belongs to a frontend plugin process configuration object.

This means that the handle supports the handle, pcfg, and xcfg interfaces.

DQCS_HTYPE_OPER_PROCESS_CONFIG = 201
Indicates that the given handle belongs to an operator plugin process configuration object.

This means that the handle supports the handle, pcfg, and xcfg interfaces.

DQCS_HTYPE_BACK_PROCESS_CONFIG = 203
Indicates that the given handle belongs to a backend plugin process configuration object.

This means that the handle supports the handle, pcfg, and xcfg interfaces.

DQCS_HTYPE_FRONT_THREAD_CONFIG = 204
Indicates that the given handle belongs to a frontend plugin thread configuration object.

This means that the handle supports the handle, tcfg, and xcfg interfaces.

DQCS_HTYPE_OPER_THREAD_CONFIG = 205
Indicates that the given handle belongs to an operator plugin thread configuration object.

This means that the handle supports the handle, tcfg, and xcfg interfaces.

DQCS_HTYPE_BACK_THREAD_CONFIG = 206
Indicates that the given handle belongs to a backend plugin thread configuration object.

This means that the handle supports the handle, tcfg, and xcfg interfaces.

DQCS_HTYPE_SIM_CONFIG = 207
Indicates that the given handle belongs to a simulator configuration object.

This means that the handle supports the handle and scfg interfaces.

DQCS_HTYPE_SIM = 208
Indicates that the given handle belongs to a simulator instance.

This means that the handle supports the handle and sim interfaces.

DQCS_HTYPE_FRONT_DEF = 300
Indicates that the given handle belongs to a frontend plugin definition object.

This means that the handle supports the handle and pdef interfaces.

DQCS_HTYPE_OPER_DEF = 301
Indicates that the given handle belongs to an operator plugin definition object.

This means that the handle supports the handle and pdef interfaces.

DQCS_HTYPE_BACK_DEF = 302
Indicates that the given handle belongs to a backend plugin definition object.

This means that the handle supports the handle and pdef interfaces.

DQCS_HTYPE_PLUGIN_JOIN = 303
Indicates that the given handle belongs to a plugin thread join handle.

This means that the handle supports the handle and pjoin interfaces.

dqcs_loglevel_t

Enumeration of loglevels and logging modes.

typedef enum { ... } dqcs_loglevel_t;

Variants:

DQCS_LOG_INVALID = -1
Invalid loglevel. Used to indicate failure of an API that returns a loglevel.
DQCS_LOG_OFF = 0
Turns logging off.
DQCS_LOG_FATAL = 1
This loglevel is to be used for reporting a fatal error, resulting from the owner of the logger getting into an illegal state from which it cannot recover. Such problems are also reported to the API caller via Result::Err if applicable.
DQCS_LOG_ERROR = 2
This loglevel is to be used for reporting or propagating a non-fatal error caused by the API caller doing something wrong. Such problems are also reported to the API caller via Result::Err if applicable.
DQCS_LOG_WARN = 3
This loglevel is to be used for reporting that a called API/function is telling us we did something wrong (that we weren't expecting), but we can recover. For instance, for a failed connection attempt to something that really should not be failing, we can still retry (and eventually report critical or error if a retry counter overflows). Since we're still trying to rectify things at this point, such problems are NOT reported to the API/function caller via Result::Err.
DQCS_LOG_NOTE = 4
This loglevel is to be used for reporting information specifically requested by the user/API caller, such as the result of an API function requested through the command line, or an explicitly captured stdout/stderr stream.
DQCS_LOG_INFO = 5
This loglevel is to be used for reporting information NOT specifically requested by the user/API caller, such as a plugin starting up or shutting down.
DQCS_LOG_DEBUG = 6
This loglevel is to be used for reporting debugging information useful for debugging the user of the API provided by the logged instance.
DQCS_LOG_TRACE = 7
This loglevel is to be used for reporting debugging information useful for debugging the internals of the logged instance. Such messages would normally only be generated by debug builds, to prevent them from impacting performance under normal circumstances.
DQCS_LOG_PASS = 8
This is intended to be used when configuring the stdout/stderr capture mode for a plugin process. Selecting it will prevent the stream from being captured; it will just be the same stream as DQCsim's own stdout/stderr. When used as the loglevel for a message, the message itself is sent to stderr instead of passing into DQCsim's log system. Using this for loglevel filters leads to undefined behavior.

dqcs_measurement_t

Qubit measurement value.

typedef enum { ... } dqcs_measurement_t;

Variants:

DQCS_MEAS_INVALID = -1
Error value used to indicate that something went wrong.
DQCS_MEAS_ZERO = 0
Indicates that the qubit was measured to be zero.
DQCS_MEAS_ONE = 1
Indicates that the qubit was measured to be one.
DQCS_MEAS_UNDEFINED = 2
Indicates that the measurement value is unknown for whatever reason.

dqcs_path_style_t

Reproduction file path style.

typedef enum { ... } dqcs_path_style_t;

Variants:

DQCS_PATH_STYLE_INVALID = -1
Error value used to indicate that something went wrong.
DQCS_PATH_STYLE_KEEP = 0
Specifies that paths should be saved the same way they were specified on the command line.
DQCS_PATH_STYLE_RELATIVE = 1
Specifies that all paths should be saved relative to DQCsim's working directory.
DQCS_PATH_STYLE_ABSOLUTE = 2
Specifies that all paths should be saved canonically, i.e. relative to the root directory.

dqcs_plugin_type_t

Enumeration of the three types of plugins.

typedef enum { ... } dqcs_plugin_type_t;

Variants:

DQCS_PTYPE_INVALID = -1
Invalid plugin type. Used to indicate failure of an API that returns a plugin type.
DQCS_PTYPE_FRONT = 0
Frontend plugin.
DQCS_PTYPE_OPER = 1
Operator plugin.
DQCS_PTYPE_BACK = 2
Backend plugin.

ArbData and ArbCmd objects

ArbData and ArbCmd objects are generic objects that plugin developers may use to expose custom functionality to the other plugins within the simulation. DQCsim never does anything with the data contained in either object, but it does define some basic structure to them.

Contents

ArbData objects

ArbData objects are used to communicate custom data between plugins. They are managed through the dqcs_arb_* functions. They are created using dqcs_arb_new():

dqcs_arb_new()

Creates a new ArbData object.

dqcs_handle_t dqcs_arb_new(void)

Returns the handle of the newly created ArbData. The ArbData is initialized with JSON object {} and an empty binary argument list.

ArbData objects support the handle and arb APIs.

Unlike most other objects, the data contained within one ArbData object can also be copied to another ArbData.

dqcs_arb_assign()

Copies the data from one object to another.

dqcs_return_t dqcs_arb_assign(
    dqcs_handle_t dest,
    dqcs_handle_t src
)

JSON-like data

To prevent the API from exploding, DQCsim does not provide any functions to manipulate the JSON data; you can only read and write the complete object in one go.

dqcs_arb_json_get()

Returns the JSON/CBOR object of an ArbData object in the form of a JSON string.

char *dqcs_arb_json_get(dqcs_handle_t arb)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

dqcs_arb_json_set()

Sets the JSON/CBOR object of an ArbData object by means of a JSON string.

dqcs_return_t dqcs_arb_json_set(
    dqcs_handle_t arb,
    const char *json
)

You can also read and write the object using CBOR. This is potentially much faster, because it's a binary format.

dqcs_arb_cbor_get()

Returns the JSON/CBOR object of an ArbData object in the form of a CBOR object.

ssize_t dqcs_arb_cbor_get(
    dqcs_handle_t arb,
    void *obj,
    size_t obj_size
)

If the actual size of the object differs from the specified object size, this function will copy the minimum of the actual and specified sizes number of bytes, and return what the actual size was.

If the specified object size is zero, obj is allowed to be NULL. You can use this to query the size before allocating an object.

This function returns -1 on failure.

dqcs_arb_cbor_set()

Sets the JSON/CBOR object of an ArbData object by means of a CBOR object.

dqcs_return_t dqcs_arb_cbor_set(
    dqcs_handle_t arb,
    const void *obj,
    size_t obj_size
)

Binary strings

Unlike the JSON object, the binary string list (a.k.a. unstructured data) is managed by DQCsim. Therefore, DQCsim provides all the list manipulation functions.

You can access the strings using both C-style strings and buffers. The former is easier, but is not binary safe: you cannot write binary strings with embedded nulls this way, and DQCsim will throw an error if you try to read a binary string with embedded nulls.

String-style access

dqcs_arb_get_str()

Returns the unstructured string argument at the specified index.

char *dqcs_arb_get_str(
    dqcs_handle_t arb,
    ssize_t index
)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

dqcs_arb_insert_str()

Inserts an unstructured string argument into the list at the specified index.

dqcs_return_t dqcs_arb_insert_str(
    dqcs_handle_t arb,
    ssize_t index,
    const char *s
)
dqcs_arb_pop_str()

Pops an unstructured string argument from the back of the list.

char *dqcs_arb_pop_str(dqcs_handle_t arb)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL. If the failure is due to the conversion from binary object to C string (i.e., embedded nulls), the data is still popped and is thus lost.

dqcs_arb_push_str()

Pushes an unstructured string argument to the back of the list.

dqcs_return_t dqcs_arb_push_str(
    dqcs_handle_t arb,
    const char *s
)
dqcs_arb_set_str()

Replaces the unstructured argument at the specified index with the specified string.

dqcs_return_t dqcs_arb_set_str(
    dqcs_handle_t arb,
    ssize_t index,
    const char *s
)

Buffer-style access

dqcs_arb_get_raw()

Returns the unstructured string argument at the specified index.

ssize_t dqcs_arb_get_raw(
    dqcs_handle_t arb,
    ssize_t index,
    void *obj,
    size_t obj_size
)

If the actual size of the object differs from the specified object size, this function will copy the minimum of the actual and specified sizes number of bytes, and return what the actual size was.

If the specified object size is zero, obj is allowed to be NULL. You can use this to determine the size of the argument prior to actually reading it, so you can allocate the right buffer size first.

This function returns -1 on failure.

dqcs_arb_insert_raw()

Inserts an unstructured raw argument into the list at the specified index.

dqcs_return_t dqcs_arb_insert_raw(
    dqcs_handle_t arb,
    ssize_t index,
    const void *obj,
    size_t obj_size
)
dqcs_arb_pop_raw()

Pops an unstructured raw argument from the back of the list.

ssize_t dqcs_arb_pop_raw(
    dqcs_handle_t arb,
    void *obj,
    size_t obj_size
)

If the actual size of the object differs from the specified object size, this function will copy the minimum of the actual and specified sizes number of bytes, and return what the actual size was.

If the specified object size is zero, obj is allowed to be NULL. You can use this if you don't need the contents of the argument and just want to delete it.

Since this function removes the returned element, data will be lost if the specified size is smaller than the actual size. To avoid this, first use dqcs_arb_get_size(handle, -1) to query the size.

This function returns -1 on failure. If this is due to a NULL buffer being passed, the data that was popped is lost.

dqcs_arb_push_raw()

Pushes an unstructured raw argument to the back of the list.

dqcs_return_t dqcs_arb_push_raw(
    dqcs_handle_t arb,
    const void *obj,
    size_t obj_size
)
dqcs_arb_set_raw()

Replaces the unstructured argument at the specified index with the specified raw object.

dqcs_return_t dqcs_arb_set_raw(
    dqcs_handle_t arb,
    ssize_t index,
    const void *obj,
    size_t obj_size
)

Miscellaneous list manipulation

dqcs_arb_clear()

Clears the unstructured argument list.

dqcs_return_t dqcs_arb_clear(dqcs_handle_t arb)
dqcs_arb_get_size()

Returns the size in bytes of the unstructured string argument at the specified index.

ssize_t dqcs_arb_get_size(
    dqcs_handle_t arb,
    ssize_t index
)

Returns -1 when the function fails.

dqcs_arb_len()

Returns the number of unstructured arguments, or -1 to indicate failure.

ssize_t dqcs_arb_len(dqcs_handle_t arb)
dqcs_arb_pop()

Pops an unstructured argument from the back of the list without returning it.

dqcs_return_t dqcs_arb_pop(dqcs_handle_t arb)
dqcs_arb_remove()

Removes the specified unstructured string argument from the list.

dqcs_return_t dqcs_arb_remove(
    dqcs_handle_t arb,
    ssize_t index
)

ArbCmd objects

ArbCmd objects are created using dqcs_cmd_new():

dqcs_cmd_new()

Creates a new ArbCmd object.

dqcs_handle_t dqcs_cmd_new(
    const char *iface,
    const char *oper
)

Returns the handle of the newly created ArbCmd. The ArbCmd is initialized with the given interface and operation IDs, JSON object {}, and an empty binary argument list. Upon failure, returns 0.

ArbCmd objects support the handle, arb, and cmd interfaces.

The interface and operation IDs are immutable after construction. They can be matched and read using the following functions.

dqcs_cmd_iface_cmp()

Compares the interface ID of an ArbCmd with the given string.

dqcs_bool_return_t dqcs_cmd_iface_cmp(
    dqcs_handle_t cmd,
    const char *iface
)

Returns -1 for failure, 0 for no match, or 1 for a match.

dqcs_cmd_iface_get()

Returns the interface ID of an ArbCmd.

char *dqcs_cmd_iface_get(dqcs_handle_t cmd)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

dqcs_cmd_oper_cmp()

Compares the operation ID of an ArbCmd with the given string.

dqcs_bool_return_t dqcs_cmd_oper_cmp(
    dqcs_handle_t cmd,
    const char *oper
)

Returns -1 for failure, 0 for no match, or 1 for a match.

dqcs_cmd_oper_get()

Returns the operation ID of an ArbCmd.

char *dqcs_cmd_oper_get(dqcs_handle_t cmd)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

In addition to the IDs, ArbCmd objects carry an ArbData object along with them as an argument. This object is accessed by applying dqcs_arb_*() functions directly to the ArbCmd handle.

ArbCmd queues

Some interfaces allow multiple commands to be specified. This is done through command queue objects.

Constructing a command queue

To construct a command queue, create a handle using dqcs_cq_new(), and then push ArbCmd objects into it one by one using dqcs_cq_push(). To keep the API simple, it is not possible to insert by index or override previously added commands

dqcs_cq_new()

Creates a new ArbCmd queue object.

dqcs_handle_t dqcs_cq_new(void)

Returns the handle of the newly created ArbCmd queue. The queue is initially empty. Queues implement a "first-in, first-out" model.

ArbCmd queue objects support the handle, arb, cmd, and cq APIs.

The arb and cmd APIs refer to the ArbCmd at the front of the queue. Use dqcs_cq_next() to remove the front entry, allowing access to the next command.

dqcs_cq_push()

Pushes an ArbCmd object into the given ArbCmd queue.

dqcs_return_t dqcs_cq_push(
    dqcs_handle_t cq,
    dqcs_handle_t cmd
)

This function returns -1 to indicate failure. The ArbCmd object specified by cmd is moved into the queue. That is, the handle is consumed if and only if the function succeeds.

Iterating over a command queue

Command queues can be iterated over as follows (note, only once!):

dqcs_handle_t queue = ...;
for (; dqcs_cq_len(queue) > 0; dqcs_cq_next(queue)) {
    ...
}

Within the loop body, the queue variable can be used as if it's an ArbCmd handle. The iteration happens in the same order in which dqcs_cq_push() was called.

dqcs_cq_len()

Returns the number of ArbCmd objects in the given ArbCmd queue.

ssize_t dqcs_cq_len(dqcs_handle_t cq)

This function returns -1 to indicate failure.

dqcs_cq_next()

Advances an ArbCmd queue to the next command.

dqcs_return_t dqcs_cq_next(dqcs_handle_t cq)

Use the dqcs_arb_* and dqcs_cmd_* interfaces to read out the command before calling this function.

To iterate over a queue in C, use the following snippit:

for (; dqcs_cq_len(queue) > 0; dqcs_cq_next(queue)) {
    dqcs_cmd_...(queue, ...)
    dqcs_arb_...(queue, ...)
}

Qubits

While DQCsim does not perform any quantum simulation on its own, and therefore does not maintain any kind of state space to refer to or measure, it is still very important to be able to refer to qubits. This is done using integers, specifically of the type dqcs_qubit_t, in a way that is not dissimilar from handles. The biggest difference compared to handles is that the entity that is referred to is not owned by DQCsim, but rather by the downstream plugin.

Allocating and freeing qubits

To be as flexible as possible, DQCsim allows qubits to be allocated and freed at any time. There is no physical analogue to this, but then again, a conventional computer cannot create and destroy physical bits out of thin air, either. This dynamic allocation intends to help solve two problems:

  • Operator plugins that perform a logical-to-physical mapping need to know which logical qubits are actually in use.
  • By doing allocation on-the-fly, a quantum algorithm doesn't need to specify how many qubits it's going to need before it's started. It can for instance interpret a file first, perform some classical computations, and determine the qubit requirements based on that.
  • Quantum simulations take up a lot of memory. Depending on the implementation, they may be able to use liveness information for optimizations.

The numbers assigned to the qubit references/handles are guaranteed to be sequential and unique within a simulation. Zero is reserved, so the first allocated qubit is always qubit 1. The second is qubit 2, and so on. Even if we free both of those, the next qubit will still be qubit 3. Implementation code can rely on this behavior if it wants.

Since qubits are owned by the downstream plugin, there is no function to ask DQCsim itself for a new qubit. Instead, this is done using dqcs_plugin_allocate(). Its inverse is dqcs_plugin_free(). These functions require a plugin state, which is only available from within plugin callback functions. Specifically, the functions can only be called by running frontend or operator plugins.

Sets of qubits

Many things within DQCsim operate not on a single qubit, but on a set of qubits. Such a set is represented through a qbset handle.

Constructing a qubit set

A qubit set can be constructed as follows. It is assumed that the qubits have already been allocated.

dqcs_handle_t qbset = dqcs_qbset_new();
for (qubit = ...; ...; ...) {
    dqcs_qbset_push(qbset, qubit);
}
dqcs_qbset_new()

Creates a new set of qubit references.

dqcs_handle_t dqcs_qbset_new(void)

Returns the handle of the newly created set. The set is initially empty. Qubit sets are ordered, meaning that the order in which qubits are popped from the set equals the order in which they were pushed. To iterate over a set, simply make a copy and drain the copy using pop.

dqcs_qbset_push()

Pushes a qubit reference into a qubit reference set.

dqcs_return_t dqcs_qbset_push(
    dqcs_handle_t qbset,
    dqcs_qubit_t qubit
)

This function will fail if the specified qubit was already part of the set.

Iterating over a qubit set

Iterating over a qubit set can be done as follows.

dqcs_handle_t qbset = ...;
dqcs_qubit_t qubit = 0;
while (qubit = dqcs_qbset_pop(qbset)) {
    ...
}
dqcs_handle_delete(qbset);
dqcs_qbset_pop()

Pops a qubit reference off of a qubit reference set.

dqcs_qubit_t dqcs_qbset_pop(dqcs_handle_t qbset)

Qubits are popped in the same order in which they were pushed. That is, they are FIFO-ordered.

Note that insertion order is maintained (in FIFO order). This means that qubit sets can be used for specifying the target qubits of a multi-qubit gate.

Note also that iteration is destructive: the set will be empty when iteration completes. If you need to iterate over a set multiple times, you can make a copy first.

dqcs_qbset_copy()

Returns a copy of the given qubit set, intended for non-destructive iteration.

dqcs_handle_t dqcs_qbset_copy(dqcs_handle_t qbset)

Querying qubit sets

You can also query qubit sets non-destructively using the following two functions.

dqcs_qbset_contains()

Returns whether the given qubit set contains the given qubit.

dqcs_bool_return_t dqcs_qbset_contains(
    dqcs_handle_t qbset,
    dqcs_qubit_t qubit
)
dqcs_qbset_len()

Returns the number of qubits in the given set.

ssize_t dqcs_qbset_len(dqcs_handle_t qbset)

This function returns -1 to indicate failure.

Matrices

The last component we need to describe a quantum gate is a unitary matrix. DQCsim internally represents all normal gates (that is, everything except measurements and custom gates) using such matrices as a universal format that all plugins must be able to deal with. Note that Gate maps can help you with converting between this format and the format your plugin uses, if they differ.

To prevent DQCsim from turning into a math library, its matrix API is very basic. Matrices are constructed from a C array of its elements and are subsequently immutable.

dqcs_mat_new()

Constructs a new gate matrix.

dqcs_handle_t dqcs_mat_new(
    size_t num_qubits,
    const double *matrix
)

num_qubits must be set to the number of qubits mutated by this matrix. It must be greater than or equal to zero. matrix must point to an appropriately sized array of doubles. The matrix is specified in row-major form, using pairs of doubles for the real vs. imaginary component of each entry. The size must be 4**num_qubits complex numbers = 2*4**num_qubits doubles = 16*4**num_qubits bytes, representing a 2**num_qubits by 2**num_qubits matrix. This function returns the constructed matrix handle, or 0 if an error occurs.

While not enforced at this level, the matrix is normally unitary, or approximately so within some floating-point error margin.

This function returns the handle to the matrix, or 0 to indicate failure.

The following functions can be used to query the size of a matrix.

dqcs_mat_len()

Returns the number of complex entries in the given matrix.

ssize_t dqcs_mat_len(dqcs_handle_t mat)

This function returns -1 when an error occurs.

dqcs_mat_dimension()

Returns the dimension (number of rows == number of columns) of the given matrix.

ssize_t dqcs_mat_dimension(dqcs_handle_t mat)

This function returns -1 when an error occurs.

dqcs_mat_num_qubits()

Returns the number of qubits targeted by the given matrix.

ssize_t dqcs_mat_num_qubits(dqcs_handle_t mat)

This function returns -1 when an error occurs.

The C array can of course also be recovered again.

dqcs_mat_get()

Returns a copy of the contained matrix as a C array.

double *dqcs_mat_get(dqcs_handle_t mat)

If this function succeeds, the matrix is returned in row-major form, using pairs of doubles for the real vs. imaginary component of each entry. The size will be 4**num_qubits complex numbers = 2*4**num_qubits doubles = 16*4**num_qubits bytes. A newly allocated matrix is returned; free it with free() when you're done with it to avoid memory leaks. On failure, this function returns NULL.

The primary use of this is to put all the complexity of converting between the C and internal DQCsim representation of such a matrix in a single place. This is particularly important for some of the gate map detector and constructor callbacks. However, DQCsim does provide some matrix operations that are common when dealing with gate detection and construction, but not so much anywhere else.

Matrix equality

A very common operation in DQCsim is matrix equality. An operator plugin may for instance want to detect whether a matrix is an X matrix. Getting this right is unfortunately difficult, due to floating point roundoff errors, numerical instability here and there, or (specifically to quantum gates) differences in global phase. For this reason, DQCsim provides an equality check function.

dqcs_mat_approx_eq()

Approximately compares two matrices.

dqcs_bool_return_t dqcs_mat_approx_eq(
    dqcs_handle_t a,
    dqcs_handle_t b,
    double epsilon,
    bool ignore_gphase
)

a and b are borrowed matrix handles. epsilon specifies the maximum element-wise root-mean-square error between the matrices that results in a positive match. ignore_gphase specifies whether the check should ignore global phase.

If ignore_gphase is set, this checks that the following holds for some x:

\[ A \cdot e^{ix} \approx B \]

This function returns DQCS_TRUE if the matrices match according to the aforementioned criteria, or DQCS_FALSE if not. DQCS_BOOL_ERROR is used when either handle is invalid or not a matrix. If the matrices differ in dimensionality, DQCS_FALSE is used.

Unitary check

DQCsim also exposes a unitary check, which it uses internally here and there anyway.

dqcs_mat_approx_unitary()

Returns whether the matrix is approximately unitary.

dqcs_bool_return_t dqcs_mat_approx_unitary(
    dqcs_handle_t matrix,
    double epsilon
)

matrix is a borrowed handle to the matrix to check. epsilon specifies the maximum element-wise root-mean-square error between the product of the matrix and its hermetian compared to the identity matrix.

This function returns DQCS_TRUE if the matrix is approximately unitary, or DQCS_FALSE if not. DQCS_BOOL_ERROR is used when either handle is invalid or not a matrix.

Predefined matrices

DQCsim provides a number of predefined gate matrices. These are identified by the dqcs_predefined_gate_t enumeration.

dqcs_predefined_gate_t

Enumeration of gates defined by DQCsim.

typedef enum { ... } dqcs_predefined_gate_t;

Variants:

DQCS_GATE_INVALID = 0
Invalid gate. Used as an error return value.
DQCS_GATE_PAULI_I = 100
The identity gate for a single qubit.

\[ I = \sigma_0 = \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \]

DQCS_GATE_PAULI_X = 101
The Pauli X matrix.

\[ X = \sigma_1 = \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix} \]

DQCS_GATE_PAULI_Y = 102
The Pauli Y matrix.

\[ Y = \sigma_2 = \begin{bmatrix} 0 & -i \\ i & 0 \end{bmatrix} \]

DQCS_GATE_PAULI_Z = 103
The Pauli Z matrix.

\[ Z = \sigma_3 = \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} \]

DQCS_GATE_H = 104
The hadamard gate matrix. That is, a 180-degree Y rotation, followed by a 90-degree X rotation.

\[ H = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & 1 \\ 1 & -1 \end{bmatrix} \]

DQCS_GATE_S = 105
The S matrix, also known as a 90 degree Z rotation.

\[ S = \begin{bmatrix} 1 & 0 \\ 0 & i \end{bmatrix} \]

DQCS_GATE_S_DAG = 106
The S-dagger matrix, also known as a negative 90 degree Z rotation.

\[ S^\dagger = \begin{bmatrix} 1 & 0 \\ 0 & -i \end{bmatrix} \]

DQCS_GATE_T = 107
The T matrix, also known as a 45 degree Z rotation.

\[ T = \begin{bmatrix} 1 & 0 \\ 0 & e^{i\frac{\pi}{4}} \end{bmatrix} \]

DQCS_GATE_T_DAG = 108
The T-dagger matrix, also known as a negative 45 degree Z rotation.

\[ T^\dagger = \begin{bmatrix} 1 & 0 \\ 0 & e^{-i\frac{\pi}{4}} \end{bmatrix} \]

DQCS_GATE_RX_90 = 109
Rx(90°) gate.

\[ R_x\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & -i \\ -i & 1 \end{bmatrix} \]

DQCS_GATE_RX_M90 = 110
Rx(-90°) gate.

\[ R_x\left(-\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & i \\ i & 1 \end{bmatrix} \]

DQCS_GATE_RX_180 = 111
Rx(180°) gate.

\[ R_x(\pi) = \begin{bmatrix} 0 & -i \\ -i & 0 \end{bmatrix} \]

This matrix is equivalent to the Pauli X gate, but differs in global phase. Note that this difference is significant when it is used as a submatrix for a controlled gate.

DQCS_GATE_RY_90 = 112
Ry(90°) gate.

\[ R_y\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & -1 \\ 1 & 1 \end{bmatrix} \]

DQCS_GATE_RY_M90 = 113
Ry(-90°) gate.

\[ R_y\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & 1 \\ -1 & 1 \end{bmatrix} \]

DQCS_GATE_RY_180 = 114
Ry(180°) gate.

\[ R_y(\pi) = \begin{bmatrix} 0 & -1 \\ 1 & 0 \end{bmatrix} \]

This matrix is equivalent to the Pauli Y gate, but differs in global phase. Note that this difference is significant when it is used as a submatrix for a controlled gate.

DQCS_GATE_RZ_90 = 115
Rz(90°) gate.

\[ R_z\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1-i & 0 \\ 0 & 1+i \end{bmatrix} \]

This matrix is equivalent to the S gate, but differs in global phase. Note that this difference is significant when it is used as a submatrix for a controlled gate.

DQCS_GATE_RZ_M90 = 116
Rz(-90°) gate.

\[ R_z\left(-\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1+i & 0 \\ 0 & 1-i \end{bmatrix} \]

This matrix is equivalent to the S-dagger gate, but differs in global phase. Note that this difference is significant when it is used as a submatrix for a controlled gate.

DQCS_GATE_RZ_180 = 117
Rz(180°) gate.

\[ R_z(\pi) = \begin{bmatrix} -i & 0 \\ 0 & i \end{bmatrix} \]

This matrix is equivalent to the Pauli Z gate, but differs in global phase. Note that this difference is significant when it is used as a submatrix for a controlled gate.

DQCS_GATE_RX = 150
The matrix for an arbitrary X rotation.

\[ R_x(\theta) = \begin{bmatrix} \cos{\frac{\theta}{2}} & -i\sin{\frac{\theta}{2}} \\ -i\sin{\frac{\theta}{2}} & \cos{\frac{\theta}{2}} \end{bmatrix} \]

θ is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as a little-endian double floating point value, specified in radians.

DQCS_GATE_RY = 151
The matrix for an arbitrary Y rotation.

\[ R_y(\theta) = \begin{bmatrix} \cos{\frac{\theta}{2}} & -\sin{\frac{\theta}{2}} \\ \sin{\frac{\theta}{2}} & \cos{\frac{\theta}{2}} \end{bmatrix} \]

θ is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as a little-endian double floating point value, specified in radians.

DQCS_GATE_RZ = 152
The matrix for an arbitrary Z rotation.

\[ R_z(\theta) = \begin{bmatrix} e^{-i\frac{\theta}{2}} & 0 \\ 0 & e^{i\frac{\theta}{2}} \end{bmatrix} \]

θ is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as a little-endian double floating point value, specified in radians.

DQCS_GATE_PHASE_K = 153
The matrix for a Z rotation with angle π/2^k.

\[ \textit{PhaseK}(k) = \textit{Phase}\left(\frac{\pi}{2^k}\right) = \begin{bmatrix} 1 & 0 \\ 0 & e^{i\pi / 2^k} \end{bmatrix} \]

k is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as a little-endian unsigned 64-bit integer.

DQCS_GATE_PHASE = 154
The matrix for an arbitrary Z rotation.

\[ \textit{Phase}(\theta) = \begin{bmatrix} 1 & 0 \\ 0 & e^{i\theta} \end{bmatrix} \]

θ is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as a little-endian double floating point value, specified in radians.

This matrix is equivalent to the Rz gate, but differs in global phase. Note that this difference is significant when it is used as a submatrix for a controlled gate. Specifically, controlled phase gates use the phase as specified by this gate, whereas Rz follows the usual algebraic notation.

DQCS_GATE_U1 = 190
Any single-qubit unitary gate, parameterized as a full unitary matrix.

The full matrix is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as an array of little-endian double floating point values, structured as real/imag pairs, with the pairs in row-major order.

DQCS_GATE_R = 191
Arbitrary rotation matrix.

\[ R(\theta, \phi, \lambda) = \begin{bmatrix} \cos{\frac{\theta}{2}} & -\sin{\frac{\theta}{2}} e^{i\lambda} \\ \sin{\frac{\theta}{2}} e^{i\phi} & \cos{\frac{\theta}{2}} e^{i\phi + i\lambda} \end{bmatrix} \]

This is equivalent to the following:

\[ R(\theta, \phi, \lambda) = \textit{Phase}(\phi) \cdot R_y(\theta) \cdot \textit{Phase}(\lambda) \]

The rotation order and phase is taken from Qiskit's U3 gate. Ignoring global phase, any unitary single-qubit gate can be represented with this notation.

θ, φ, and λ are specified or returned through the first three binary string arguments of the parameterization ArbData object. They are represented as little-endian double floating point values, specified in radians.

DQCS_GATE_SWAP = 200
The swap gate matrix.

\[ \textit{SWAP} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \]

DQCS_GATE_SQRT_SWAP = 201
The square-root of a swap gate matrix.

\[ \sqrt{\textit{SWAP}} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \frac{i+1}{2} & \frac{i-1}{2} & 0 \\ 0 & \frac{i-1}{2} & \frac{i+1}{2} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \]

DQCS_GATE_U2 = 290
Any two-qubit unitary gate, parameterized as a full unitary matrix.

The full matrix is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as an array of little-endian double floating point values, structured as real/imag pairs, with the pairs in row-major order.

DQCS_GATE_U3 = 390
Any three-qubit unitary gate, parameterized as a full unitary matrix.

The full matrix is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as an array of little-endian double floating point values, structured as real/imag pairs, with the pairs in row-major order.

Given such a variant and an ArbData object with the parameters described in the enum variant documentation, a matrix can be constructed.

dqcs_mat_predef()

Constructs a new gate matrix for one of DQCsim's predefined gates.

dqcs_handle_t dqcs_mat_predef(
    dqcs_predefined_gate_t gate_type,
    dqcs_handle_t param_data
)

gate_type specifies which kind of gate should be constructed.

param_data takes an optional ArbData object used to parameterize the matrix if necessary. If not specified, an empty object is used. The ArbData representation for each gate can be found in the docs for dqcs_predefined_gate_t. If nothing is specified, no ArbData is used.

This function returns the handle to the matrix, or 0 to indicate failure. The parameterization data (if specified) is consumed/deleted by this function if and only if it succeeds.

DQCsim also provides the reverse operation: going from a matrix matching a given gate type to its parameterization. This matrix detection uses the internal equivalent of dqcs_mat_approx_eq, so its parameters are also needed here.

dqcs_mat_is_predef()

Returns whether this matrix is of the given predefined form and, if it is, any parameters needed to describe it.

dqcs_bool_return_t dqcs_mat_is_predef(
    dqcs_handle_t mat,
    dqcs_predefined_gate_t gate_type,
    dqcs_handle_t *param_data,
    double epsilon,
    bool ignore_gphase
)

mat is a borrowed handle to the matrix to check. gate_type specifies which kind of gate should be detected. param_data, if non-null, receives a new ArbData handle with parameterization data, or an empty ArbData if the gate is not parameterized; the caller must delete this object when it is done with it. This function always writes the 0 handle to this return parameter if it fails. The ArbData representation can be found in the documentation for dqcs_predefined_gate_t.

epsilon specifies the maximum element-wise root-mean-square error between the matrices that results in a positive match. ignore_gphase specifies whether the check should ignore global phase.

This function returns DQCS_TRUE if the matrices match according to the aforementioned criteria, or DQCS_FALSE if not. DQCS_BOOL_ERROR is used when either handle is invalid or not a matrix. If the matrices differ in dimensionality, DQCS_FALSE is used.

Note that these two functions are only the most basic form for constructing and detecting gates using some higher abstraction level. If you feel like you're using these functions a lot, you should probably use a gate map instead.

Control normalization

DQCsim allows controlled quantum gates to be specified either with an explicit set of control qubits and the non-controlled submatrix, or the full controlled matrix. The canonical form within DQCsim is the former, as operating on only the submatrices may improve performance, and gives you controlled gates for free. In some cases however, the user may wish to convert between the two representations. DQCsim provides higher-level functions to do this as part of the gate API, but you can also call the low-level matrix conversion functions manually as follows.

dqcs_mat_add_controls()

Constructs a controlled matrix from the given matrix.

dqcs_handle_t dqcs_mat_add_controls(
    dqcs_handle_t mat,
    size_t number_of_controls
)

mat specifies the matrix to use as the non-controlled submatrix. This is a borrowed handle. number_of_controls specifies the number of control qubits to add. This function returns a new matrix handle with the constructed matrix, or 0 if it fails.

dqcs_mat_strip_control()

Splits a controlled matrix into its non-controlled submatrix and the indices of the control qubits.

dqcs_handle_t dqcs_mat_strip_control(
    dqcs_handle_t mat,
    double epsilon,
    bool ignore_global_phase,
    ssize_t **control_indices
)

mat specifies the matrix to modify. This is a borrowed handle. epsilon specifies the maximum magitude of the difference between the column vectors of the input matrix and the identity matrix (after dephasing if ignore_gphase is set) for the column vector to be considered to not affect the respective entry in the quantum state vector. Note that if this is greater than zero, the resulting gate may not be exactly equivalent. If ignore_global_phase is set, any global phase in the matrix is ignored, but note that if control qubits are stripped the "global" phase of the resulting submatrix is always significant. control_indices is a return argument through which DQCsim will pass the indices of the qubits that were removed in the process of constructing the submatrix. This is represented as an array of indices terminated by a -1 entry. The returned matrix must be freed using free() when you are done with it to avoid memory leaks. This function returns a new matrix handle with the submatrix, or 0 if it fails. In this case, control_indices is not mutated.

This function assumes that the incoming matrix is unitary (within epsilon) without verifying that this is the case. The results may thus be invalid if it was not.

Basis matrices

TODO: someone who knows what they're talking about should check/correct this section at some point. I'm mostly working off of hunches. - Jeroen

DQCsim uses 2x2 matrices to represent the basis for a measurement or prep gate to operate on. The intuitive nature of these matrices is as follows for measurements:

  • apply the Hermetian/conjugate transpose/inverse of the basis matrix as a gate to each measured qubit;
  • do a Z-basis measurement;
  • apply the basis matrix as a gate to each measured qubit.

Basically, the application of the inverse of the matrix rotates the state of the qubits from the desired basis to the Z basis, then a Z measurement is applied, then the application of the basis matrix rotates the state back to the desired basis.

The semantics for prep gates are basically the same:

  • initialize the state of each qubit to |0>;
  • apply the basis matrix as a gate to each targeted qubit.

With this definition, the basis matrices can be written as follows.

dqcs_basis_t

Enumeration of Pauli bases.

typedef enum { ... } dqcs_basis_t;

Variants:

DQCS_BASIS_INVALID = 0
Invalid basis. Used as an error return value.
DQCS_BASIS_X = 1
The X basis.

\[ \psi_X = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & -1 \\ 1 & 1 \end{bmatrix} \]

DQCS_BASIS_Y = 2
The Y basis.

\[ \psi_Y = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & i \\ i & 1 \end{bmatrix} \]

DQCS_BASIS_Z = 3
The Z basis.

\[ \psi_Z = \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \]

... and constructed as follows:

dqcs_mat_basis()

Constructs a matrix with the eigenvectors of one of the Pauli matrices as column vectors.

dqcs_handle_t dqcs_mat_basis(dqcs_basis_t basis)

This can be used for constructing measurement or prep gates with the given basis. Returns a new handle to the constructed matrix or returns 0 if an error occurs.

Basis matrices can be compared with the following function. This function ignores any of the phase differences that don't affect the basis in its approximate equality function.

dqcs_mat_basis_approx_eq()

Approximately compares two basis matrices.

dqcs_bool_return_t dqcs_mat_basis_approx_eq(
    dqcs_handle_t a,
    dqcs_handle_t b,
    double epsilon
)

a and b are borrowed matrix handles. epsilon specifies the maximum element-wise root-mean-square error between the matrices that results in a positive match.

This checks that the following holds for some x and y:

\[ A \cdot \begin{bmatrix} e^{ix} & 0 \\ 0 & e^{iy} \end{bmatrix} \approx B \]

This function returns DQCS_TRUE if the matrices match according to the aforementioned criteria, or DQCS_FALSE if not. DQCS_BOOL_ERROR is used when either handle is invalid or not a matrix. If either matrix is not 2x2, DQCS_FALSE is used.

Gates

The state of a quantum system is modified by means of quantum gates.

Constructing gates

DQCsim provides four types of gates.

  • Unitary gates: these apply a gate matrix on one or more qubits.
  • Measurement gates: these cause the state of a qubit to be collapsed along and measured in some basis.
  • Prep gates: these set the state of a qubit to some value.
  • Custom gates: anything else that the downstream plugin supports.

These are constructed using the following functions. The predefined gates are as described earlier.

dqcs_gate_new_predef()

Constructs a new predefined unitary gate.

dqcs_handle_t dqcs_gate_new_predef(
    dqcs_predefined_gate_t gate_type,
    dqcs_handle_t qubits,
    dqcs_handle_t param_data
)

gate_type specifies which kind of gate should be constructed.

targets must be a handle to a non-empty qubit set, containing at least as many qubits as needed for the specified gate type. If more qubits are specified, the rightmost qubits become the targets, and the remaining qubits become control qubits to make a controlled gate.

param_data takes an optional ArbData object used to parameterize the gate if necessary. If not specified, an empty object is used. Some of the gate types are parameterized, and use values from this ArbData as defined in the docs for dqcs_predefined_gate_t. Anything remaining in the ArbData afterwards is placed in the gate object.

This function returns the handle to the gate, or 0 to indicate failure. The qubit set and parameterization data (if specified) are consumed/deleted by this function if and only if it succeeds.

dqcs_gate_new_predef_one()

Constructs a new predefined unitary one-qubit gate.

dqcs_handle_t dqcs_gate_new_predef_one(
    dqcs_predefined_gate_t gate_type,
    dqcs_qubit_t qa,
    dqcs_handle_t param_data
)

This function is simply a shorthand for dqcs_gate_new_predef() with one qubit in the qubits set, to make constructing one-qubit gates more ergonomic. Refer to its documentation for more information.

dqcs_gate_new_predef_two()

Constructs a new predefined unitary two-qubit gate.

dqcs_handle_t dqcs_gate_new_predef_two(
    dqcs_predefined_gate_t gate_type,
    dqcs_qubit_t qa,
    dqcs_qubit_t qb,
    dqcs_handle_t param_data
)

This function is simply a shorthand for dqcs_gate_new_predef() with two qubit in the qubits set, to make constructing two-qubit gates more ergonomic. Refer to its documentation for more information.

dqcs_gate_new_predef_three()

Constructs a new predefined unitary three-qubit gate.

dqcs_handle_t dqcs_gate_new_predef_three(
    dqcs_predefined_gate_t gate_type,
    dqcs_qubit_t qa,
    dqcs_qubit_t qb,
    dqcs_qubit_t qc,
    dqcs_handle_t param_data
)

This function is simply a shorthand for dqcs_gate_new_predef() with three qubit in the qubits set, to make constructing three-qubit gates more ergonomic. Refer to its documentation for more information.

dqcs_gate_new_unitary()

Constructs a new unitary gate.

dqcs_handle_t dqcs_gate_new_unitary(
    dqcs_handle_t targets,
    dqcs_handle_t controls,
    dqcs_handle_t matrix
)

targets must be a handle to a non-empty qubit set. The qubits in this set correspond with the supplied unitary matrix.

controls optionally specifies a set of control qubits. You may pass 0 or an empty qubit set if you don't need control qubits.

matrix must be a handle to an appropriately sized matrix.

The supplied matrix is only applied to the target qubits if all the control qubits are or will be determined to be set. For instance, to encode a CCNOT/Toffoli gate, you can specify one target qubits, two control qubits, and [0, 1; 1, 0] (X) for the matrix. This is equivalent to extending the matrix to the full Toffoli matrix and specifying all three qubits in the targets set, or the midway solution using a CNOT matrix, but these solutions may be less efficient depending on whether the simulator can optimize its calculations for controlled gates.

Simulators are not required to apply the (hidden) global phase component of the gate matrix in the same way it is specified; that is, if the simulator can optimize its calculations by altering the global phase it is allowed to.

DQCsim checks whether the matrix is unitary using the equivalent of dqcs_mat_approx_unitary() with an epsilon value of 1e-6.

This function returns the handle to the gate, or 0 to indicate failure. The targets qubit set, (if specified) the controls qubit set, and the matrix are consumed/deleted by this function if and only if it succeeds.

dqcs_gate_new_measurement()

Constructs a new measurement gate.

dqcs_handle_t dqcs_gate_new_measurement(
    dqcs_handle_t measures,
    dqcs_handle_t matrix
)

measures must be a handle to a qubit set. matrix is an optional matrix handle signifying the measurement basis. If zero, the Z basis is used. Otherwise, it must be a handle to a unitary 2x2 matrix, and the semantics of the measurement are as follows:

  • apply the hermetian of the matrix to each qubit
  • measure each qubit in the Z basis
  • apply the matrix to each qubit

This function returns the handle to the gate, or 0 to indicate failure. The measures qubit set and matrix handle are consumed/deleted by this function if and only if it succeeds.

dqcs_gate_new_prep()

Constructs a new prep gate.

dqcs_handle_t dqcs_gate_new_prep(
    dqcs_handle_t targets,
    dqcs_handle_t matrix
)

targets must be a handle to a qubit set. matrix is an optional matrix handle signifying the state that the qubits are initialized to. If zero, the qubits are initialized to |0>. Otherwise, it must be a handle to a unitary 2x2 matrix, and the semantics are as follows:

  • initialize each qubit to |0>
  • apply the matrix to each qubit

This function returns the handle to the gate, or 0 to indicate failure. The targets qubit set and matrix handle are consumed/deleted by this function if and only if it succeeds.

dqcs_gate_new_custom()

Constructs a new custom gate.

dqcs_handle_t dqcs_gate_new_custom(
    const char *name,
    dqcs_handle_t targets,
    dqcs_handle_t controls,
    dqcs_handle_t measures,
    dqcs_handle_t matrix
)

The functionality of custom gates is not specified by DQCsim. Instead, this is left up to the plugins. Of course, for this to work, plugins that are connected to each other must agree on the format used.

name specifies the name of the gate. The name is used to indicate which custom operation is to be applied.

targets optionally specifies the set of target qubits. You may pass 0 or an empty qubit set if you don't need target qubits.

controls optionally specifies the set of control qubits. You may pass 0 or an empty qubit set if you don't need control qubits.

measures optionally specifies the set of measured qubits. You may pass 0 or an empty qubit set if no qubits are measured. Note that the upstream plugin expects exactly one measurement result for each qubit specified in this set; anything else results in a warning and the measurement result being set to undefined.

matrix optionally specifies a handle to an appropriately sized matrix for the targets qubit set.

In addition to the above data, gate objects implement the arb interface to allow user-specified classical information to be attached.

This function returns the handle to the gate, or 0 to indicate failure. The specified qubit sets are consumed/deleted by this function if and only if it succeeds.

Control qubit representation

A gatestream source is allowed to specify controlled gates either using DQCsim's separate list of control qubits (this is the recommended way), by using an explicitly controlled gate matrix and using only the target qubit list, or a even mix of the two. The following two functions, primarily intended for gatestream sinks, can be used to convert between these representations.

dqcs_gate_reduce_control()

Utility function that detects control qubits in the targets list of the gate by means of the gate matrix, and reduces them into controls qubits.

dqcs_handle_t dqcs_gate_reduce_control(
    dqcs_handle_t gate,
    double epsilon,
    bool ignore_gphase
)

This function borrows a handle to any gate with a matrix, and returns an equivalent copy of said gate with any control qubits in the targets set moved to the controls set. The associated gate matrix is accordingly reduced in size. The control qubits are added at the end of the controls set in the same order they appeared in the targets qubit set.

epsilon specifies the maximum element-wise deviation from the identity matrix for the relevant array elements for a qubit to be considered a control qubit. Note that if this is greater than zero, the resulting gate may not be exactly equivalent. If ignore_gphase is set, any global phase in the matrix is ignored, but the global phase of the non-control submatrix is not changed.

This function returns a new gate handle with the modified gate, or a copy of the input gate if the matrix could not be reduced. If the input gate does not have a matrix (measurement gate, or custom gate without matrix) an error is returned instead.

dqcs_gate_expand_control()

Utility function that expands a gate matrix to account for all control qubits.

dqcs_handle_t dqcs_gate_expand_control(dqcs_handle_t gate)

This function borrows a handle to any gate with a matrix, and returns an equivalent copy of said gate with any control qubits in the controls set moved to the targets set. The associated gate matrix is extended accordingly. The control qubits are added at the front of the targets set in the same order they appeared in the controls qubit set.

This function returns a new gate handle with the modified gate, or a copy of the input gate if the matrix could not be reduced. If the input gate does not have a matrix (measurement gate, or custom gate without matrix) an error is returned instead.

Attached classical data

Classical information can be attached to any gate using the ArbData protocol: gate handles support all the dqcs_arb_*() API calls. This is primarily intended for custom gates.

Interpreting gates

DQCsim provides two ways for interpreting incoming gates: manually querying the parameters and gate maps. The latter is quite advanced and deserves its own section (the next one), but let's deal with the manual method first.

The first step for any incoming gate is to query its type.

dqcs_gate_type()

Returns the gate type of the given gate.

dqcs_gate_type_t dqcs_gate_type(dqcs_handle_t gate)

Returns DQCS_GATE_TYPE_INVALID if the gate handle is invalid.

This results in the following enumeration. The exact semantics of each type of gate is listed in the documentation of each enum variant.

dqcs_gate_type_t

Types of DQCsim gates.

typedef enum { ... } dqcs_gate_type_t;

Variants:

DQCS_GATE_TYPE_INVALID = 0
Invalid gate type. Used as an error return value.
DQCS_GATE_TYPE_UNITARY
Unitary gates have one or more target qubits, zero or more control qubits, and a unitary matrix, sized for the number of target qubits.

The semantics are that the unitary matrix expanded by the number of control qubits is applied to the qubits.

The data field may add pragma-like hints to the gate, for instance to represent the line number in the source file that generated the gate, error modelling information, and so on. This data may be silently ignored.

DQCS_GATE_TYPE_MEASUREMENT
Measurement gates have one or more measured qubits and a 2x2 unitary matrix representing the basis.

The semantics are:

  • the hermetian of the matrix is applied to each individual qubit;
  • each individual qubit is measured in the Z basis;
  • the matrix is applied to each individual qubit;
  • the results of the measurement are propagated upstream.

This allows any measurement basis to be used.

The data field may add pragma-like hints to the gate, for instance to represent the line number in the source file that generated the gate, error modelling information, and so on. This data may be silently ignored.

DQCS_GATE_TYPE_PREP
Prep gates have one or more target qubits and a 2x2 unitary matrix representing the basis.

The semantics are:

  • each qubit is initialized to |0>;
  • the matrix is applied to each individual qubit.

This allows any initial state to be used.

The data field may add pragma-like hints to the gate, for instance to represent the line number in the source file that generated the gate, error modelling information, and so on. This data may be silently ignored.

DQCS_GATE_TYPE_CUSTOM
Custom gates perform a user-defined mixed quantum-classical operation, identified by a name. They can have zero or more target, control, and measured qubits, of which only the target and control sets must be mutually exclusive. They also have an optional matrix of arbitrary size.

The semantics are:

  • if the name is not recognized, an error is reported;
  • a user-defined operation is performed based on the name, qubits, matrix, and data arguments;
  • exactly one measurement result is reported upstream for exactly the qubits in the measures set.

The following functions can be used to read the remaining parameters associated with a gate.

dqcs_gate_has_targets()

Returns whether the specified gate has target qubits.

dqcs_bool_return_t dqcs_gate_has_targets(dqcs_handle_t gate)
dqcs_gate_targets()

Returns a handle to a new qubit reference set containing the qubits targeted by this gate.

dqcs_handle_t dqcs_gate_targets(dqcs_handle_t gate)
dqcs_gate_has_controls()

Returns whether the specified gate has control qubits.

dqcs_bool_return_t dqcs_gate_has_controls(dqcs_handle_t gate)
dqcs_gate_controls()

Returns a handle to a new qubit reference set containing the qubits that control this gate.

dqcs_handle_t dqcs_gate_controls(dqcs_handle_t gate)
dqcs_gate_has_measures()

Returns whether the specified gate measures any qubits.

dqcs_bool_return_t dqcs_gate_has_measures(dqcs_handle_t gate)
dqcs_gate_measures()

Returns a handle to a new qubit reference set containing the qubits measured by this gate.

dqcs_handle_t dqcs_gate_measures(dqcs_handle_t gate)
dqcs_gate_has_matrix()

Returns whether a unitary matrix is associated with this gate.

dqcs_bool_return_t dqcs_gate_has_matrix(dqcs_handle_t gate)
dqcs_gate_matrix()

Returns a copy of the unitary matrix associated with this gate, if one exists.

dqcs_handle_t dqcs_gate_matrix(dqcs_handle_t gate)

If this function succeeds, a new matrix handle is returned. If it fails, 0 is returned.

dqcs_gate_has_name()

Returns whether the specified gate has a name.

dqcs_bool_return_t dqcs_gate_has_name(dqcs_handle_t gate)
dqcs_gate_name()

Returns the name of a custom gate.

char *dqcs_gate_name(dqcs_handle_t gate)

This function fails if the gate is not a custom gate. Query dqcs_gate_has_name() to disambiguate between a non-custom gate and a different error.

On success, this returns a newly allocated string containing the gate name. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

Gate maps

Representing gates with matrices is a great way to ensure plugin compatibility; unlike using for example names for gates, the matrix unambiguously represents the mathematical operation that is to be performed. However, there are cases where you may want to distinguish whether a gate is for instance one of the Pauli gates or something else. You could do this with dqcs_mat_approx_eq(), but if you have a lot of gates your code will explode, be error-prone (especially if you want to do the reverse operation as well), and may not be very efficient. More generally, a plugin may want to use its own higher-level internal gate representation, and convert between that and DQCsim's matrix-based representation.

Gate maps intend to solve this problem. You can define any data structure to represent your gates as long as it can map to/from the following:

  • any kind of key (a void*) defining the type of gate.
  • a number of qubit arguments.
  • optionally, an ArbData representing parameters if your definition of a gate type is parameterized.

You can then use a gate map to convert between that representation and DQCsim's representation, typically in both directions. Going from DQCsim's matrix representation to your plugin's representation is called detection, while the opposite direction is called construction. Once you have a gate map, you can use the following functions to do this.

dqcs_gm_detect()

Uses a gate map object to convert an incoming DQCsim gate to the plugin's representation.

dqcs_bool_return_t dqcs_gm_detect(
    dqcs_handle_t gm,
    dqcs_handle_t gate,
    const void **key_data,
    dqcs_handle_t *qubits,
    dqcs_handle_t *param_data
)

gm must be a handle to a gate map object (dqcs_mm_new()). gate must be a handle to a gate. The handle is borrowed; it is not mutated or deleted. key_data serves as an optional return value; if non-NULL and a match is found, the key_data specified when the respective detector was added is returned here as a const void *. If no match is found, *key_data is not assigned. qubits serves as an optional return value; if non-NULL and a match is found, it is set to a handle to a new QubitSet object representing the gate's qubits. Ownership of this handle is passed to the user, so it is up to the user to eventually delete it. If no match is found, *qubits is set to 0. param_data serves as an optional return value; if non-NULL and a match is found, it is set to a handle to a new ArbData object representing the gate's parameters. Ownership of this handle is passed to the user, so it is up to the user to eventually delete it. If no match is found, *param_data is set to 0.

This function returns DQCS_TRUE if a match was found, DQCS_FALSE if no match was found, or DQCS_BOOL_FAILURE if an error occurs.

dqcs_gm_construct_one()

Uses a gate map object to construct a one-qubit DQCsim gate from the plugin's representation.

dqcs_handle_t dqcs_gm_construct_one(
    dqcs_handle_t gm,
    const void *key_data,
    dqcs_qubit_t qa,
    dqcs_handle_t param_data
)

This function is simply a shorthand for dqcs_gm_construct() with one qubit in the qubits set, to make constructing one-qubit gates more ergonomic. Refer to its documentation for more information.

dqcs_gm_construct_two()

Uses a gate map object to construct a two-qubit DQCsim gate from the plugin's representation.

dqcs_handle_t dqcs_gm_construct_two(
    dqcs_handle_t gm,
    const void *key_data,
    dqcs_qubit_t qa,
    dqcs_qubit_t qb,
    dqcs_handle_t param_data
)

This function is simply a shorthand for dqcs_gm_construct() with two qubits in the qubits set, to make constructing two-qubit gates more ergonomic. Refer to its documentation for more information.

dqcs_gm_construct_three()

Uses a gate map object to construct a three-qubit DQCsim gate from the plugin's representation.

dqcs_handle_t dqcs_gm_construct_three(
    dqcs_handle_t gm,
    const void *key_data,
    dqcs_qubit_t qa,
    dqcs_qubit_t qb,
    dqcs_qubit_t qc,
    dqcs_handle_t param_data
)

This function is simply a shorthand for dqcs_gm_construct() with three qubits in the qubits set, to make constructing three-qubit gates more ergonomic. Refer to its documentation for more information.

dqcs_gm_construct()

Uses a gate map object to construct a multi-qubit DQCsim gate from the plugin's representation.

dqcs_handle_t dqcs_gm_construct(
    dqcs_handle_t gm,
    const void *key_data,
    dqcs_handle_t qubits,
    dqcs_handle_t param_data
)

gm must be a handle to a gate map object (dqcs_mm_new()). gate must be a handle to a gate. The handle is borrowed; it is not mutated or deleted. key_data specifies the gate mapping key for the constructor to use. Note that the pointer must match exactly to what was specified when the mapping(s) was/were added. qubits specifies the qubits arguments for the constructed gate. It is up to the constructor function to determine how to interpret these. The parameter is optional; passing 0 is equivalent to passing an empty qubit set. The handle is deleted if the function succeeds. param_data specifies the ArbData object used to parameterize the gate. It is optional; if 0, an empty ArbData is automatically constructed by DQCsim. The handle is deleted if the function succeeds.

This function returns the handle to the gate, or 0 to indicate failure. The qubit set and parameterization data (if specified) are consumed/deleted by this function if and only if it succeeds.

Converters

Conceptually, a gate map consists of a number of converter objects, each typically consisting of a detector function and a constructor function. In the most generic case, the detector takes a DQCsim gate as its input, and converts it to a qubit set and an ArbData if it recognizes the gate, while the constructor performs the inverse operation. Detection is usually fuzzy to account for floating-point inaccuracies, while construction is as exact as possible.

These converter objects are stored in the gate map as the values of an ordered map, for which the key is the user-defined void* key defining the type of gate. Thus, each converter represents a single gate type. When a gate is to be detected, DQCsim will call each converter's detector function in insertion order until one of the detectors returns a match. When a gate is to be constructed, it simply maps the gate type key to the appropriate converter and calls only its constructor.

The most generic converter described above can be added to a map with the following function, but it is also the most complicated to implement.

dqcs_gm_add_custom()

Adds a fully customizable gate mapping to the given gate map.

dqcs_return_t dqcs_gm_add_custom(
    dqcs_handle_t gm,
    void (*key_free)(void *key_data),
    void *key_data,
    dqcs_bool_return_t (*detector)(
        const void *user_data,
        dqcs_handle_t gate,
        dqcs_handle_t *qubits,
        dqcs_handle_t *param_data
    ),
    void (*detector_user_free)(void *user_data),
    void *detector_user_data,
    dqcs_handle_t (*constructor)(
        const void *user_data,
        dqcs_handle_t qubits,
        dqcs_handle_t param_data
    ),
    void (*constructor_user_free)(void *user_data),
    void *constructor_user_data
)

Note that this is the only type of mapping that can handle custom/named gates.

detector is the detector function pointer. It is optional; if null, this mapping only supports construction. detector_user_free is an optional callback function used to free detector_user_data when the gate map is destroyed, when this function fails, or when detector was null. detector_user_data is a user-specified value that is passed to the detector callback function. It is not used by DQCsim. constructor is the constructor function pointer. It is optional; if null, this mapping only supports detection. constructor_user_free is an optional callback function used to free constructor_user_data when the gate map is destroyed, when this function fails, or when constructor was null. constructor_user_data is a user-specified value that is passed to the constructor callback function. It is not used by DQCsim.

If both constructor and detector are null for some reason, the function is no-op (besides possibly calling the *_free() callbacks.

The detector callback receives the complete gate passed to the gate map for it to match as it pleases. If the gate matches, the detector function must return DQCS_TRUE. It may assign qubits to a qbset object representing the qubit arguments (substituted with an empty set if it doesn't), and may assign param_data to an arb handle with the parameterization data (if it doesn't, the data from the gate is used; if this was modified by the callback, the modified data is used). If the gate doesn't match, it must return DQCS_FALSE. If an error occurs, it must call dqcs_error_set() with the error message and return DQCS_BOOL_FAILURE.

The constructor callback performs the reverse operation. It receives an ArbData handle containing the parameterization data and a qubit set, and must construct a gate based on this information. If construction succeeds, the constructor function must return the gate handle. If an error occurs, it must call dqcs_error_set() with the error message and return 0.

It is up to the user how to do the matching and constructing, but the converter functions must always return the same value for the same input. In other words, they must be pure functions. Otherwise, the caching behavior of the GateMap will make the results inconsistent.

There is also a specialized version that detects unitary gate matrices instead of complete gates. This version deals with distinguishing between unitary, measurement, and custom gates for you. It also converts between DQCsim's seperate target/control qubit set and the single gate-type-sensitive qubit set in the plugin representation for you.

dqcs_gm_add_custom_unitary()

Adds a custom unitary gate mapping to the given gate map.

dqcs_return_t dqcs_gm_add_custom_unitary(
    dqcs_handle_t gm,
    void (*key_free)(void *key_data),
    void *key_data,
    dqcs_bool_return_t (*detector)(
        const void *user_data,
        dqcs_handle_t matrix,
        size_t num_controls,
        dqcs_handle_t *param_data
    ),
    void (*detector_user_free)(void *user_data),
    void *detector_user_data,
    dqcs_handle_t (*constructor)(
        const void *user_data,
        dqcs_handle_t *param_data,
        intptr_t *num_controls
    ),
    void (*constructor_user_free)(void *user_data),
    void *constructor_user_data
)

gm must be a handle to a gate map object (dqcs_gm_new()). key_free is an optional callback function used to free key_data when the gate map is destroyed, or when this function fails. key_data is the user-specified value used to identify this mapping. detector is the detector function pointer. It is optional; if null, this mapping only supports construction. detector_user_free is an optional callback function used to free detector_user_data when the gate map is destroyed, when this function fails, or when detector was null. detector_user_data is a user-specified value that is passed to the detector callback function. It is not used by DQCsim. constructor is the constructor function pointer. It is optional; if null, this mapping only supports detection. constructor_user_free is an optional callback function used to free constructor_user_data when the gate map is destroyed, when this function fails, or when constructor was null. constructor_user_data is a user-specified value that is passed to the constructor callback function. It is not used by DQCsim.

If both constructor and detector are null for some reason, the function is no-op (besides possibly calling the *_free() callbacks.

The detector callback receives a matrix and control qubit information for the user to match. The matrix is passed through the matrix handle. num_controls is passed the number of explicit control qubits that exist besides the matrix (that is, if nonzero, the matrix is actually only the non-controlled submatrix of the controlled gate). param_data is given an ArbData handle initialized with the ArbData attached to the gate. If the gate matches, the detector function must return DQCS_TRUE. In this case, it can mutate the param_data to add the detected gate parameters. If it doesn't match, it must return DQCS_FALSE. If an error occurs, it must call dqcs_error_set() with the error message and return DQCS_BOOL_FAILURE.

The constructor callback performs the reverse operation. It receives an ArbData handle containing the parameterization data, and must construct the matrix, return the bound on the number of control qubits, and must return the ArbData associated with the gate by mutating the param_data handle. num_controls will point to a variable initialized to -1 representing a constraint on the number of control qubits. This works as follows: if negative, any number of qubits is allowed; if zero or positive, only that number is allowed. If construction succeeds, the constructor function must return a handle to the constructed matrix. If it fails, it must call dqcs_error_set() with an error message and return 0.

It is up to the user how to do the matching and constructing, but the converter functions must always return the same value for the same input. In other words, they must be pure functions. Otherwise, the caching behavior of the GateMap will make the results inconsistent.

More likely, though, you just want to detect the usual gates, like X, H, swap, and so on. To help you do this, DQCsim includes built-in converters for every dqcs_predefined_gate_t, which you can add to the map with the following, much simpler function.

dqcs_gm_add_predef_unitary()

Adds a unitary gate mapping for the given DQCsim-defined gate to the given gate map.

dqcs_return_t dqcs_gm_add_predef_unitary(
    dqcs_handle_t gm,
    void (*key_free)(void *user_data),
    void *key_data,
    dqcs_predefined_gate_t gate,
    intptr_t num_controls,
    double epsilon,
    bool ignore_gphase
)

gm must be a handle to a gate map object (dqcs_gm_new()). key_free is an optional callback function used to free key_data when the gate map is destroyed, or when this function fails. key_data is the user-specified value used to identify this mapping. gate defines which predefined gate to use. Some of the predefined gates are parameterized. num_controls specifies the number of control qubits associated with this gate type. If negative, the gate can have any number of control qubits. If zero or positive, the number of control qubits must be as specified. epsilon specifies the maximum element-wise root-mean-square error between the incoming matrix and the to be detected matrix that results in a positive match. ignore_phase specifies whether the aforementioned check should ignore global phase or not when there are no explicit control qubits.

For most gate types, the parameterization ArbData object returned by detection and consumed by construction is mapped one-to-one to the user data of the gate in the DQCsim-protocol. Some of the detectors however detect parameterized gate matrices. These detectors prefix a fixed number of binary string arguments to the ArbData upon detection, and pop these when constructing. The specs for this can be found in the docs for dqcs_predefined_gate_t.

You can also easily detect gates with a special, fixed matrix.

dqcs_gm_add_fixed_unitary()

Adds a unitary gate mapping for the given gate matrix to the given gate map.

dqcs_return_t dqcs_gm_add_fixed_unitary(
    dqcs_handle_t gm,
    void (*key_free)(void *key_data),
    void *key_data,
    dqcs_handle_t matrix,
    intptr_t num_controls,
    double epsilon,
    bool ignore_gphase
)

gm must be a handle to a gate map object (dqcs_gm_new()). key_free is an optional callback function used to free key_data when the gate map is destroyed, or when this function fails. key_data is the user-specified value used to identify this mapping. matrix must be passed a handle to the matrix to detect. It is consumed by this function. num_controls specifies the number of control qubits associated with this gate type. If negative, the gate can have any number of control qubits. If zero or positive, the number of control qubits must be as specified. epsilon specifies the maximum element-wise root-mean-square error between the incoming matrix and the to be detected matrix that results in a positive match. ignore_phase specifies whether the aforementioned check should ignore global phase or not when there are no explicit control qubits.

The parameterization ArbData object returned by detection and consumed by construction is mapped one-to-one to the user data of the gate in the DQCsim-protocol.

Finally, you can detect measurement and prep gates with the following built-in detectors.

dqcs_gm_add_measure()

Adds a measurement gate mapping to the given gate map.

dqcs_return_t dqcs_gm_add_measure(
    dqcs_handle_t gm,
    void (*key_free)(void *user_data),
    void *key_data,
    intptr_t num_measures,
    dqcs_handle_t basis,
    double epsilon
)

gm must be a handle to a gate map object (dqcs_gm_new()). key_free is an optional callback function used to free key_data when the gate map is destroyed, or when this function fails. key_data is the user-specified value used to identify this mapping. num_measures specifies the number of measured qubits for this gate type. If negative, the gate can have any number of measured qubits. If zero or positive, the number of measured qubits must be as specified. basis optionally specifies a handle to a 2x2 matrix specifying the measurement basis to be detected. If not specified, the Z basis is used. The matrix is deleted by the call iff the function succeeds. epsilon specifies the maximum RMS deviation between the specified basis (if any) and the incoming basis.

The parameterization ArbData object returned by detection and consumed by construction is mapped one-to-one to the user data of the gate in the DQCsim-protocol.

dqcs_gm_add_prep()

Adds a prep gate mapping to the given gate map.

dqcs_return_t dqcs_gm_add_prep(
    dqcs_handle_t gm,
    void (*key_free)(void *user_data),
    void *key_data,
    intptr_t num_targets,
    dqcs_handle_t basis,
    double epsilon
)

gm must be a handle to a gate map object (dqcs_gm_new()). key_free is an optional callback function used to free key_data when the gate map is destroyed, or when this function fails. key_data is the user-specified value used to identify this mapping. num_targets specifies the number of target qubits for this gate type. If negative, the gate can have any number of targets. If zero or positive, the number of target qubits must be as specified. basis optionally specifies a handle to a 2x2 matrix specifying the prep basis. If not specified, the Z basis is used. The matrix is deleted by the call iff the function succeeds. epsilon specifies the maximum RMS deviation between the specified basis (if any) and the incoming basis.

The parameterization ArbData object returned by detection and consumed by construction is mapped one-to-one to the user data of the gate in the DQCsim-protocol.

Caching

The detection logic in a gate map includes a cache to improve performance when the same gate is received a number of times. This is quite typical, as algorithms typically use only a small subset of gates frequently. There are four different caching strategies:

  • The cache key is an exact copy of the incoming gate, so the cache only hits when the exact gate is received twice. In this case, the cache maps directly to the result of the previous detection, so no detector function needs to be run. This is the safest option.

  • The cache ignores differing ArbData attachments to the incoming gates. This is valid only if whether a detector function matches or not is not sensitive to this ArbData. However, the cache only maps to the matching detection function, and calls it again for every cache hit. Thus, the ArbData returned by the detector can still depend on the gate's ArbData.

  • The cache key is a copy of the incoming gate, but without the qubit references. That is, for example an X gate applied to q1 is considered equal to an X gate applied to q2. This is valid only if whether a detector function matches or not is not sensitive to the qubit references. It can, however, be sensitive to the amount of qubits, and as with the ArbData insensitivity described above, the detector is still called for each cache hit and can thus still return a different qubit set.

  • A combination of the latter two, i.e., the cache is not sensitive to either the gate's ArbData or to the qubit references.

Preprocessing

To be as compatible with other plugins as possible, you may want to preprocess the incoming gates with either dqcs_gate_reduce_control() or dqcs_gate_expand_control(). We already went over these in the previous section, but their description is repeated here for convenience.

When you apply dqcs_gate_reduce_control() to each incoming gate before passing it to dqcs_gm_detect(), you ensure that if the upstream plugin is sending for instance a CNOT using a complete two-qubit gate matrix and two target qubits, it will still be detected as a controlled X gate with one control qubit, instead of some different gate.

You can also choose to do the opposite, converting from for instance DQCsim's controlled X representation to a full CNOT matrix using dqcs_gate_expand_control. However, in this case you'll have to detect controlled matrices with dqcs_gm_add_fixed_unitary() or a fully custom implementation, as DQCsim only provided predefined matrices for the non-controlled (sub)matrices.

Constructing a gate map

Having read all of the above, you should be ready to construct a new gate map. The construction function takes the caching strategy as its parameters, as well as two optional callback functions used to compare and hash your plugin's key type, needed for the internal hashmap mapping from key to converter object.

dqcs_gm_new()

Constructs a new gate map.

dqcs_handle_t dqcs_gm_new(
    bool strip_qubit_refs,
    bool strip_data,
    bool (*key_cmp)(
        const void,
        const void
    ),
    uint64_t (*key_hash)(const void)
)

Returns a handle to a gate map with no mappings attached to it yet. Use dqcs_gm_add_*() to do that. The mappings are queried in the order in which they are added, so be sure to add more specific gates first. Once added, use dqcs_gm_detect() to detect incoming DQCsim gates, and dqcs_gm_construct*() to (re)construct gates for transmission.

Gate maps objects retain a cache to speed up detection of similar DQCsim gates: if a gate is received for the second time, the cache will hit, avoiding recomputation of the detector functions. What constitutes "similar gates" is defined by the two booleans passed to this function. If strip_qubit_refs is set, all qubit references associated with the gate will be invalidated (i.e., set to 0), such that for instance an X gate applied to qubit 1 will be considered equal to an X gate applied to qubit 2. If strip_data is set, the ArbData associated with the incoming gate is removed.

Gates are identified through user-defined void* keys. To do the above, however, DQCsim needs to know the following things:

  • how to delete an owned copy of a key if your semantics are that DQCsim owns it,
  • how to compare two keys (equality);
  • how to hash a key.

The deletion function is passed when the key is passed. If the keys are objects of different classes, this allows different constructors to be passed here. There can only be a single comparison and hash function for each gate map, though. They are passed here.

key_cmp represents this comparison function. It takes two void* to keys and must returns whether they are equal or not. If not specified, the default is to compare the pointers themselves, instead of the values they refer to. key_cmp must be a pure function, i.e., depend only on its input values.

key_hash represents the hashing function. It takes a void* key and returns a 64-bit hash representative of the key. For any pair of keys for which key_cmp returns true, the hashes must be equal. The default behavior depends on whether key_cmp is defined: if it is, all keys will have the same hash; if it isn't, the pointer is itself hashed. key_hash must be a pure function, i.e., depend only on its input values.

It is recommended to first preprocess incoming gates with dqcs_gate_reduce_control(). In this case, controlled unitary gate matrices will be reduced to their non-controlled submatrix, such that the unitary gate detectors will operate on said submatrix. The predefined unitary gate detectors are more-or-less based on this assumption (as there are no predefined controlled matrices).

Alternatively, you can preprocess with dqcs_gate_expand_control(). In this case, you can use dqcs_gm_add_fixed_unitary() to detect the full matrix in all cases, by specifying the CNOT matrix instead of an X matrix with one control qubit.

If you don't preprocess, the upstream plugin determines the representation. That is, it may send a CNOT as a two-qubit gate with a CNOT matrix or as a controlled X gate with a single target and single control qubit. The gate map will then detect these as two different kinds of gates.

Measurements

Measurements are returned in response to measurement gates. While the measurement result for a qubit is normally just a 0 or a 1 (or some other boolean convention), DQCsim allows additional information to be attached, and also allows an "undefined" state to model measurement failure. Measurement objects/handles are used to encapsulate this. Furthermore, since a single function call may have to return multiple measurements, measurement sets are defined as well.

Contents

Singular measurements

Singular measurements, i.e. a single measurement for a single qubit, are represented through meas handles.

Constructing measurement objects

Measurement objects are constructed using dqcs_meas_new().

dqcs_meas_new()

Constructs a new measurement object.

dqcs_handle_t dqcs_meas_new(
    dqcs_qubit_t qubit,
    dqcs_measurement_t value
)

qubit must be set to the qubit that was measured, value must be set to its value. The return value is the handle to the measurement object, or 0 if something went wrong.

Note that measurement objects implement the arb interface, so additional data can be attached to the object.

They are also mutable after construction.

dqcs_meas_qubit_set()

Sets the qubit reference associated with a measurement object.

dqcs_return_t dqcs_meas_qubit_set(
    dqcs_handle_t meas,
    dqcs_qubit_t qubit
)
dqcs_meas_value_set()

Sets the measurement value associated with a measurement object.

dqcs_return_t dqcs_meas_value_set(
    dqcs_handle_t meas,
    dqcs_measurement_t value
)

Attaching custom data

Measurement objects support the ArbData protocol for attaching custom data. That is, all dqcs_arb_*() API calls can be applied to measurement handles.

Querying measurement objects

The measurement value and qubit reference can be queried from a measurement object as follows.

dqcs_meas_qubit_get()

Returns the qubit reference associated with a measurement object.

dqcs_qubit_t dqcs_meas_qubit_get(dqcs_handle_t meas)
dqcs_meas_value_get()

Returns the measurement value associated with a measurement object.

dqcs_measurement_t dqcs_meas_value_get(dqcs_handle_t meas)

Measurement sets

A measurement set encapsulates measurement results for zero or more qubits. It is therefore actually more like a map/dictionary than a set.

Constructing measurement sets

A measurement set can be constructed as follows.

dqcs_handle_t mset = dqcs_mset_new();
for (qubit, value = ...; ...; ...) {
    dqcs_handle_t meas = dqcs_meas_new(qubit, value);
    dqcs_mset_set(mset, meas);
    dqcs_handle_delete(meas);
}
dqcs_mset_new()

Creates a new set of qubit measurement results.

dqcs_handle_t dqcs_mset_new(void)

Returns the handle of the newly created set. The set is initially empty.

dqcs_mset_set()

Adds a measurement result to a measurement result set.

dqcs_return_t dqcs_mset_set(
    dqcs_handle_t mset,
    dqcs_handle_t meas
)

If there was already a measurement for the specified qubit, the previous measurement result is overwritten. The measurement result object is deleted if and only if the function succeeds.

Iterating over measurement sets

Destructive iteration can be performed as follows if needed.

dqcs_handle_t mset = ...;
while ((dqcs_handle_t meas = dqcs_mset_take_any(mset))) {
    dqcs_qubit_t qubit = dqcs_meas_qubit_get(meas);
    dqcs_measurement_t value = dqcs_meas_value_get(meas);
    dqcs_handle_delete(meas);
    ...
}

To iterate nondestructively, one would have to construct a new measurement set while iterating.

dqcs_mset_take_any()

Returns the measurement result for any of the qubits contained in a measurement result set and removes it from the set.

dqcs_handle_t dqcs_mset_take_any(dqcs_handle_t mset)

This is useful for iteration.

Note that insertion order is not preserved. Measurements can also be removed from a measurement set in a controlled order using the following functions.

dqcs_mset_take()

Returns the measurement result for the given qubit from a measurement result set and removes it from the set.

dqcs_handle_t dqcs_mset_take(
    dqcs_handle_t mset,
    dqcs_qubit_t qubit
)
dqcs_mset_remove()

Removes the measurement result for the given qubit from a measurement result set.

dqcs_return_t dqcs_mset_remove(
    dqcs_handle_t mset,
    dqcs_qubit_t qubit
)

Querying measurement sets

Measurement sets can be queried nondestructively using the following functions.

dqcs_mset_contains()

Returns whether the given qubit measurement set contains data for the given qubit.

dqcs_bool_return_t dqcs_mset_contains(
    dqcs_handle_t mset,
    dqcs_qubit_t qubit
)
dqcs_mset_get()

Returns a copy of the measurement result for the given qubit from a measurement result set.

dqcs_handle_t dqcs_mset_get(
    dqcs_handle_t mset,
    dqcs_qubit_t qubit
)
dqcs_mset_len()

Returns the number of qubits measurements in the given measurement set.

ssize_t dqcs_mset_len(dqcs_handle_t mset)

This function returns -1 to indicate failure.

Plugins

Now that we have all the generic data structures out of the way, let's focus on building DQCsim plugins.

Contents

Defining a plugin

Before a plugin can be started, it must be "defined". This process is largely concerned with installing callback functions and setting some metadata that is common to all plugins.

Constructing a plugin definition

Plugin definitions are constructed using dqcs_pdef_new(). This function sets the plugin type and the plugin metadata, which is immutable after the plugin definition object has been constructed.

dqcs_pdef_new()

Creates a new PluginDefinition object.

dqcs_handle_t dqcs_pdef_new(
    dqcs_plugin_type_t typ,
    const char *name,
    const char *author,
    const char *version
)

Plugin definitions contain the callback functions/closures that define the functionality of a plugin. They also contain some metadata to identify the implementation, in the form of a name, author, and version string, that must be specified when the definition is constructed. The callback functions/closures are initialized to sane defaults for the requested plugin type, but obviously one or more of these should be overridden to make the plugin do something.

Once a definition object has been built, it can be used to spawn a plugin thread or run a plugin in the main thread, given a DQCsim server URL for it to connect to.

It is, however, possible to query the metadata and plugin type as follows.

dqcs_pdef_type()

Returns the plugin type for the given plugin definition object.

dqcs_plugin_type_t dqcs_pdef_type(dqcs_handle_t pdef)
dqcs_pdef_name()

Returns the plugin name for the given plugin definition object.

char *dqcs_pdef_name(dqcs_handle_t pdef)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

dqcs_pdef_author()

Returns the plugin author for the given plugin definition object.

char *dqcs_pdef_author(dqcs_handle_t pdef)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

dqcs_pdef_version()

Returns the plugin version for the given plugin definition object.

char *dqcs_pdef_version(dqcs_handle_t pdef)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

Assigning callback functions

Plugins without callback functions not only don't do anything, they'll crash! The following matrix shows which functions are required (x), optional (o), and not applicable (-):

CallbackFrontendOperatorBackend
initializeooo
dropooo
runx--
allocate-oo
free-oo
gate-ox
modify_measurement-o-
advance-oo
upstream_arb-oo
host_arbooo

These callback functions can be set using the following functions. Don't forget to read the general callback information here as well.

dqcs_pdef_set_initialize_cb()

Sets the user logic initialization callback.

dqcs_return_t dqcs_pdef_set_initialize_cb(
    dqcs_handle_t pdef,
    dqcs_return_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t init_cmds
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

This is always called before any of the other callbacks are run. The downstream plugin has already been initialized at this stage, so it is legal to send it commands.

The default behavior is no-op.

Besides the common arguments, the callback receives a handle to an ArbCmd queue (dqcs_cq_*, dqcs_cmd_*, and dqcs_arb_* interfaces) containing user-defined initialization commands. This is a borrowed handle; the caller will delete it.

The callback can return an error by setting an error message using dqcs_error_set() and returning DQCS_FAILURE. Otherwise, it should return DQCS_SUCCESS.

dqcs_pdef_set_drop_cb()

Sets the user logic drop/cleanup callback.

dqcs_return_t dqcs_pdef_set_drop_cb(
    dqcs_handle_t pdef,
    dqcs_return_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

This is called when a plugin is gracefully terminated. It is not recommended to execute any downstream instructions at this time, but it is supported in case this is really necessary.

The default behavior is no-op.

The callback can return an error by setting an error message using dqcs_error_set() and returning DQCS_FAILURE. Otherwise, it should return DQCS_SUCCESS.

dqcs_pdef_set_run_cb()

Sets the run callback for frontends.

dqcs_return_t dqcs_pdef_set_run_cb(
    dqcs_handle_t pdef,
    dqcs_handle_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t args
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

This is called in response to a start() host API call. The return value is returned through the wait() host API call.

The default behavior is to fail with a "not implemented" error; frontends backends should always override this. This callback is never called for operator or backend plugins.

Besides the common arguments, the callback receives a handle to an ArbData object containing the data that the host passed to start(). This is a borrowed handle; the caller will delete it.

When the run callback is successful, it should return a valid ArbData handle. This can be the same as the argument, but it can also be a new object. This ArbData is returned to the host through wait(). This ArbData object is deleted after the callback completes.

The callback can return an error by setting an error message using dqcs_error_set() and returning 0. Otherwise, it should return a valid ArbData handle.

dqcs_pdef_set_allocate_cb()

Sets the qubit allocation callback for operators and backends.

dqcs_return_t dqcs_pdef_set_allocate_cb(
    dqcs_handle_t pdef,
    dqcs_return_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t qubits,
        dqcs_handle_t alloc_cmds
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

The default for operators is to pass through to dqcs_plugin_allocate(). The default for backends is no-op. This callback is never called for frontend plugins.

Besides the common arguments, the callback receives a handle to a qubit set containing the references that are to be used for the to-be-allocated qubits and an ArbCmd queue containing user-defined commands to optionally augment the behavior of the qubits. These are borrowed handles; the caller will delete them.

The callback can return an error by setting an error message using dqcs_error_set() and returning DQCS_FAILURE. Otherwise, it should return DQCS_SUCCESS.

dqcs_pdef_set_free_cb()

Sets the qubit deallocation callback for operators and backends.

dqcs_return_t dqcs_pdef_set_free_cb(
    dqcs_handle_t pdef,
    dqcs_return_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t qubits
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

The default for operators is to pass through to dqcs_plugin_free(). The default for backends is no-op. This callback is never called for frontend plugins.

Besides the common arguments, the callback receives a handle to a qubit set containing the qubits that are to be freed. This is a borrowed handle; the caller will delete it.

The callback can return an error by setting an error message using dqcs_error_set() and returning DQCS_FAILURE. Otherwise, it should return DQCS_SUCCESS.

dqcs_pdef_set_gate_cb()

Sets the gate execution callback for operators and backends.

dqcs_return_t dqcs_pdef_set_gate_cb(
    dqcs_handle_t pdef,
    dqcs_handle_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t gate
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

Besides the common arguments, the callback receives a handle to the to-be-executed gate. This is a borrowed handle; the caller will delete it.

The callback must return one of the following things:

  • a valid handle to a measurement set, created using dqcs_mset_new() (this object is automatically deleted after the callback returns);
  • a valid handle to a single qubit measurement, created using dqcs_meas_new() (this object is automatically deleted after the callback returns);
  • the handle to the supplied gate, a shortcut for not returning any measurements (this is less clear than returning an empty measurement set, but slightly faster); or
  • 0 to report an error, after calling the error string using dqcs_set_error().

Backend plugins must return a measurement result set containing exactly those qubits specified in the measurement set. For operators, however, the story is more complicated. Let's say we want to make a silly operator that inverts all measurements. The trivial way to do this would be to forward the gate, query all the measurement results using dqcs_plugin_get_measurement(), invert them, stick them in a measurement result set, and return that result set. However, this approach is not very efficient, because dqcs_plugin_get_measurement() has to wait for all downstream plugins to finish executing the gate, forcing the OS to switch threads, etc. Instead, operators are allowed to return only a subset (or none) of the measured qubits, as long as they return the measurements as they arrive through the modify_measurement() callback.

The default implementation for this callback for operators is to pass the gate through to the downstream plugin and return an empty set of measurements. Combined with the default implementation of modify_measurement(), this behavior is sane. Backends must override this callback; the default is to return a not-implemented error.

Note that for our silly example operator, the default behavior for this function is sufficient; you'd only have to override modify_measurement() to, well, modify the measurements.

dqcs_pdef_set_modify_measurement_cb()

Sets the measurement modification callback for operators.

dqcs_return_t dqcs_pdef_set_modify_measurement_cb(
    dqcs_handle_t pdef,
    dqcs_handle_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t meas
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

This callback is called for every measurement result received from the downstream plugin, and returns the measurements that should be reported to the upstream plugin. Note that the results from our plugin's dqcs_plugin_get_measurement() and friends are consistent with the results received from downstream; they are not affected by this function.

The callback takes a handle to a single qubit measurement object as an argument, and must return one of the following things:

  • a valid handle to a measurement set, created using dqcs_mset_new() (this object is automatically deleted after the callback returns);
  • a valid handle to a single qubit measurement object, which may or may not be the supplied one (this object is automatically deleted after the callback returns); or
  • 0 to report an error, after calling the error string using dqcs_set_error().

This callback is somewhat special in that it is not allowed to call any plugin command other than logging and the pseudorandom number generator functions. This is because this function is called asynchronously with respect to the downstream functions, making the timing of these calls non-deterministic based on operating system scheduling.

Note that while this function is called for only a single measurement at a time, it is allowed to produce a vector of measurements. This allows you to cancel propagation of the measurement by returning an empty vector, to just modify the measurement data itself, or to generate additional measurements from a single measurement. However, if you need to modify the qubit references for operators that remap qubits, take care to only send measurement data upstream when these were explicitly requested through the associated upstream gate function's measured list.

The default behavior for this callback is to return the measurement without modification.

dqcs_pdef_set_advance_cb()

Sets the callback for advancing time for operators and backends.

dqcs_return_t dqcs_pdef_set_advance_cb(
    dqcs_handle_t pdef,
    dqcs_return_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_cycle_t cycles
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

The default behavior for operators is to pass through to dqcs_plugin_advance(). The default for backends is no-op. This callback is never called for frontend plugins.

Besides the common arguments, the callback receives an unsigned integer specifying the number of cycles to advance by.

The callback can return an error by setting an error message using dqcs_error_set() and returning DQCS_FAILURE. Otherwise, it should return DQCS_SUCCESS.

dqcs_pdef_set_upstream_arb_cb()

Sets the callback function for handling an arb from upstream for operators and backends.

dqcs_return_t dqcs_pdef_set_upstream_arb_cb(
    dqcs_handle_t pdef,
    dqcs_handle_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t cmd
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

The default behavior for operators is to pass through to dqcs_plugin_arb(); operators that do not support the requested interface should always do this. The default for backends is no-op. This callback is never called for frontend plugins.

Besides the common arguments, the callback receives a handle to the ArbCmd object representing the request. It must return a valid ArbData handle containing the response. Both objects are deleted automatically after invocation.

The callback can return an error by setting an error message using dqcs_error_set() and returning 0. Otherwise, it should return a valid ArbData handle.

dqcs_pdef_set_host_arb_cb()

Sets the callback function function for handling an arb from the host.

dqcs_return_t dqcs_pdef_set_host_arb_cb(
    dqcs_handle_t pdef,
    dqcs_handle_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t cmd
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

The default behavior for this is no-op.

Besides the common arguments, the callback receives a handle to the ArbCmd object representing the request. It must return a valid ArbData handle containing the response. Both objects are deleted automatically after invocation.

The callback can return an error by setting an error message using dqcs_error_set() and returning 0. Otherwise, it should return a valid ArbData handle.

Running a plugin

Once a plugin has been defined, it can be started. Normally, DQCsim will do this in one way or another. There are three ways in which DQCsim can do this:

  • by spawning a process;
  • by spawning a thread within the simulation process;
  • by letting you spawn the plugin.

We'll go into this in greater depths when we get into simulation construction. For now, I'll assume that the first thing has already happened, and the process that was launched is the one that you're working on right now.

DQCsim will spawn a plugin process with a single command-line argument (in addition to the process name as per convention). This argument identifies a FIFO file or address of some kind that tells the DQCsim instance running in the plugin process how to connect to the DQCsim instance that controls the simulation. You don't need to know anything about the syntax of this argument; all you need to do is pass it to dqcs_plugin_run(), along with a plugin definition.

dqcs_plugin_run()

Executes a plugin in the current thread.

dqcs_return_t dqcs_plugin_run(
    dqcs_handle_t pdef,
    const char *simulator
)

pdef must be an appropriately populated plugin definition object. Its callback functions will be called from the current thread, from within the context of this function.

simulator must be set to the address of our endpoint of the simulator that's using the plugin; DQCsim normally passes this as the first command line argument of the plugin process.

If the plugin starts, the pdef handle is consumed by this function, regardless of whether the plugin eventually closes normally. The handle is only left alive if pdef is not a plugin definition object.

If the function fails, your process should return a nonzero exit code; if it succeeds, it should return 0. And that's it!

Some notes, though:

  • If you spawned the process manually, you're in control of how the connection endpoint string is passed. So you can do something else if you like, but keep in mind that the command-line interface won't be able to use your plugin in that case.
  • The connection endpoint is currently a FIFO file, so it cannot connect to another machine.
  • dqcs_plugin_run() is a blocking function call. If you're making some awesome plugin server that needs to be able to run multiple plugin threads at a time, you can also use the asynchronous equivalents below. This is particularly useful in case you want to construct the plugin definition in your main thread for some reason. However, you can currently only use a plugin definition once; it is deleted by dqcs_plugin_start() as well as dqcs_plugin_run().
dqcs_plugin_start()

Executes a plugin in a worker thread.

dqcs_handle_t dqcs_plugin_start(
    dqcs_handle_t pdef,
    const char *simulator
)

This function behaves the same as dqcs_plugin_log(), but is asynchronous; it always returns immediately. Of course, this means that the callbacks in pdef will be called from a different thread.

To wait for the thread to finish executing, call dqcs_plugin_wait() on the returned join handle. Alternatively you can delete the join handle object, which will detach the thread.

Note that dqcs_log_*() will only be available in the thread that the plugin actually runs in.

This function returns 0 to indicate failure to start the plugin. Otherwise, the join handle is returned.

dqcs_plugin_wait()

Waits for a plugin worker thread to finish executing.

dqcs_return_t dqcs_plugin_wait(dqcs_handle_t pjoin)

Unless the join handle is invalid, this function returns success/failure based on the result of the plugin execution. If the plugin thread is joined, the join handle is deleted.

Interacting with DQCsim

When you start a plugin with dqcs_plugin_run() or dqcs_plugin_start(), DQCsim will start calling the callbacks you provided. Each of these callbacks takes a dqcs_plugin_state_t handle, which can be used to interact with DQCsim and the downstream plugin(s) using the functions listed in this section.

Frontend to host communication

Within a frontend's run callback, the following two functions can be used to send and receive ArbData messages to and from the host.

dqcs_plugin_send()

Sends a message to the host.

dqcs_return_t dqcs_plugin_send(
    dqcs_plugin_state_t plugin,
    dqcs_handle_t arb
)

It is only legal to call this function from within the run() callback. Any other source will result in an error.

The cmd handle is consumed by this function if and only if it succeeds.

dqcs_plugin_recv()

Waits for a message from the host.

dqcs_handle_t dqcs_plugin_recv(dqcs_plugin_state_t plugin)

It is only legal to call this function from within the run() callback. Any other source will result in an error.

When successful, this function returns a new handle to the received ArbData object. 0 is used to indicate that an error occurred.

The send function always returns immediately, but the receive function may block to return control to the host if no messages were in the buffer. That means that the latter can call into a different callback, such as host_arb.

Upstream to downstream communication

The following functions can be used by upstream plugins (frontends and operators) to perform an operation on the downstream plugin. They correspond one-to-one with the downstream callbacks.

dqcs_plugin_allocate()

Allocate the given number of downstream qubits.

dqcs_handle_t dqcs_plugin_allocate(
    dqcs_plugin_state_t plugin,
    uintptr_t num_qubits,
    dqcs_handle_t cq
)

Backend plugins are not allowed to call this. Doing so will result in an error.

num_qubits specifies the number of qubits that are to be allocated.

commands must be 0 or a valid handle to an ArbCmd queue, containing a list of commands that may be used to modify the behavior of the qubit register; 0 is equivalent to zero commands. The queue is consumed by this function, i.e. the handle becomes invalid, if and only if it succeeds.

If the function is successful, a new handle to the set of qubit references representing the newly allocated register is returned. When the function fails, 0 is returned.

dqcs_plugin_free()

Free the given downstream qubits.

dqcs_return_t dqcs_plugin_free(
    dqcs_plugin_state_t plugin,
    dqcs_handle_t qbset
)

Backend plugins are not allowed to call this. Doing so will result in an error.

qubits must be a valid set of qubit references. The set is consumed by this function, i.e. the handle becomes invalid, if and only if it succeeds.

dqcs_plugin_gate()

Tells the downstream plugin to execute a gate.

dqcs_return_t dqcs_plugin_gate(
    dqcs_plugin_state_t plugin,
    dqcs_handle_t gate
)

Backend plugins are not allowed to call this. Doing so will result in an error.

gate must be a valid gate object. The object is consumed by this function, i.e. the handle becomes invalid, if and only if it succeeds.

dqcs_plugin_advance()

Tells the downstream plugin to run for the specified number of cycles.

dqcs_cycle_t dqcs_plugin_advance(
    dqcs_plugin_state_t plugin,
    dqcs_cycle_t cycles
)

Backend plugins are not allowed to call this. Doing so will result in an error.

The return value is the new cycle counter. This function uses -1 to signal an error.

dqcs_plugin_arb()

Sends an arbitrary command downstream.

dqcs_handle_t dqcs_plugin_arb(
    dqcs_plugin_state_t plugin,
    dqcs_handle_t cmd
)

Backend plugins are not allowed to call this. Doing so will result in an error.

This function returns a new handle to an ArbData object representing the return value of the ArbCmd when successful. Otherwise, it returns 0.

For performance reasons, all of the above functions except dqcs_plugin_arb() are asynchronous. They send the associated request immediately, but it is down to OS thread/process scheduling when the request is actually executed. This means the following:

  • The ordering of log messages sent by differing plugins depends on OS scheduling.
  • Errors caused by these asynchronous functions cannot be propagated upstream. Therefore, any error that does occur is necessarily fatal.

dqcs_plugin_arb() is exempt from this since it returns a value, so ArbCmd errors are not necessarily fatal.

Querying the state of the downstream plugin

Measurement results requested through measurement gates need to be explicitly fetched when they are needed through the following function. It always returns the result of the most recent measurement gate for a specific qubit.

dqcs_plugin_get_measurement()

Returns the latest measurement of the given downstream qubit.

dqcs_handle_t dqcs_plugin_get_measurement(
    dqcs_plugin_state_t plugin,
    dqcs_qubit_t qubit
)

Backend plugins are not allowed to call this. Doing so will result in an error.

If the function succeeds, it returns a new handle to a qubit measurement result object. Otherwise it returns 0.

DQCsim also records some timing information whenever a measurement is performed. This may be useful for calculating fidelity information within an algorithm running in the presence of errors.

dqcs_plugin_get_cycles_since_measure()

Returns the number of downstream cycles since the latest measurement of the given downstream qubit.

dqcs_cycle_t dqcs_plugin_get_cycles_since_measure(
    dqcs_plugin_state_t plugin,
    dqcs_qubit_t qubit
)

Backend plugins are not allowed to call this. Doing so will result in an error.

This function uses -1 to signal an error.

dqcs_plugin_get_cycles_between_measures()

Returns the number of downstream cycles between the last two measurements of the given downstream qubit.

dqcs_cycle_t dqcs_plugin_get_cycles_between_measures(
    dqcs_plugin_state_t plugin,
    dqcs_qubit_t qubit
)

Backend plugins are not allowed to call this. Doing so will result in an error.

This function uses -1 to signal an error.

Finally, a simulation cycle counter is maintained. This is just an accumulation of all the dqcs_plugin_advance() calls since the start of the simulation.

dqcs_plugin_get_cycle()

Returns the current value of the downstream cycle counter.

dqcs_cycle_t dqcs_plugin_get_cycle(dqcs_plugin_state_t plugin)

Backend plugins are not allowed to call this. Doing so will result in an error.

This function uses -1 to signal an error.

Random number generation

To ensure that a DQCsim simulation can be deterministically reproduced, it is strongly recommended to use the following random number generation functions.

dqcs_plugin_random_f64()

Generates a random floating point number using the simulator random seed.

double dqcs_plugin_random_f64(dqcs_plugin_state_t plugin)

The generated numbers are uniformly distributed in the range [0,1>.

This function only fails if the plugin handle is invalid, in which case it returns 0. Of course, 0 is also a valid (if rare) random return value.

dqcs_plugin_random_u64()

Generates a random unsigned 64-bit number using the simulator random seed.

dqcs_handle_t dqcs_plugin_random_u64(dqcs_plugin_state_t plugin)

This function only fails if the plugin handle is invalid, in which case it returns 0. Of course, 0 is also a valid (if rare) random return value.

Particularly, these generators use a separate PRNG stream depending on whether the callback they are executed from is synchronous to the upstream channel (modify_measurement) or the downstream channel (all other callbacks). This is important, because the ordering of upstream callbacks with respect to downstream callbacks is dependent on OS scheduling.

If you only use downstream callbacks, it's also fine to seed your own PRNG using the first number returned by dqcs_plugin_random_u64() in the init callback. However, using a randomly seeded PRNG is strongly discouraged, since it prevents a user from using a fixed random seed for reproduction.

Logging

DQCsim provides a centralized logging interface for plugins. This ensures that log messages are processed in a consistent way among different plugins. It also makes sure that messages don't print "through" each other, as they would when stdout/stderr were used directly (these are not thread-safe).

The logging interface is only available from within threads that are hosting a plugin (or the main thread of a plugin process) and the thread which started the simulation.

Log levels

Every message has a severity level associated with it. These severities should be interpreted as follows:

  • trace is intended for reporting debugging information useful for debugging the internals of the originating plugin, but not necessarily for debugging the plugins around it. Such messages would normally only be generated by debug builds, to prevent them from impacting performance under normal circumstances.
  • debug is intended for reporting debugging information which may also be useful when debugging the plugins around it, such as the arguments with which a public API function was called.
  • info is intended for reporting information that was not explicitly requested by the user, such as a plugin starting up or shutting down.
  • note is intended for reporting information that was explicitly requested by the user/API caller, such as the result of an API function requested through the command line, or an explicitly captured stdout/stderr stream. It may also be used for reporting events that the user may not be expecting, but are not exceptional enough to warrent issuing a warning.
  • warn is intended for reporting that a third party did something unexpected, but that an attempt will be made to work around the problem, such as a failed connection attempt that we're going to retry.
  • error is intended for reporting or propagating a non-fatal error, such as a third party requesting usage of an unimplemented function.
  • fatal is intended for reporting fatal errors, such as a third party reporting an error for something that we really needed.

Sending log messages

The preferred way for C programs to submit log messages is through the following macros. These automatically add information such as line number and source filename through the C preprocessor. They also fall back to writing to stderr if logging fails for some reason. Their interface is just like printf, but the final newline is implicit.

dqcs_log_trace()

Convenience macro for calling dqcs_log_format() with trace loglevel and automatically determined function name, filename, and line number.

#define dqcs_log_trace(fmt, ...)                \
  dqcs_log_format(                              \
    _DQCSIM_LOGLEVEL_PREFIX_ DQCS_LOG_TRACE,    \
    _DQCSIM_LANGUAGE_,                          \
    __FILE__,                                   \
    __LINE__,                                   \
    fmt,                                        \
    ##__VA_ARGS__                               \
  )
dqcs_log_debug()

Convenience macro for calling dqcs_log_format() with debug loglevel and automatically determined function name, filename, and line number.

#define dqcs_log_debug(fmt, ...)                \
  dqcs_log_format(                              \
    _DQCSIM_LOGLEVEL_PREFIX_ DQCS_LOG_DEBUG,    \
    _DQCSIM_LANGUAGE_,                          \
    __FILE__,                                   \
    __LINE__,                                   \
    fmt,                                        \
    ##__VA_ARGS__                               \
  )
dqcs_log_info()

Convenience macro for calling dqcs_log_format() with info loglevel and automatically determined function name, filename, and line number.

#define dqcs_log_info(fmt, ...)                 \
  dqcs_log_format(                              \
    _DQCSIM_LOGLEVEL_PREFIX_ DQCS_LOG_INFO,     \
    _DQCSIM_LANGUAGE_,                          \
    __FILE__,                                   \
    __LINE__,                                   \
    fmt,                                        \
    ##__VA_ARGS__                               \
  )
dqcs_log_note()

Convenience macro for calling dqcs_log_format() with note loglevel and automatically determined function name, filename, and line number.

#define dqcs_log_note(fmt, ...)                 \
  dqcs_log_format(                              \
    _DQCSIM_LOGLEVEL_PREFIX_ DQCS_LOG_NOTE,     \
    _DQCSIM_LANGUAGE_,                          \
    __FILE__,                                   \
    __LINE__,                                   \
    fmt,                                        \
    ##__VA_ARGS__                               \
  )
dqcs_log_warn()

Convenience macro for calling dqcs_log_format() with warn loglevel and automatically determined function name, filename, and line number.

#define dqcs_log_warn(fmt, ...)                 \
  dqcs_log_format(                              \
    _DQCSIM_LOGLEVEL_PREFIX_ DQCS_LOG_WARN,     \
    _DQCSIM_LANGUAGE_,                          \
    __FILE__,                                   \
    __LINE__,                                   \
    fmt,                                        \
    ##__VA_ARGS__                               \
  )
dqcs_log_error()

Convenience macro for calling dqcs_log_format() with error loglevel and automatically determined function name, filename, and line number.

#define dqcs_log_error(fmt, ...)                \
  dqcs_log_format(                              \
    _DQCSIM_LOGLEVEL_PREFIX_ DQCS_LOG_ERROR,    \
    _DQCSIM_LANGUAGE_,                          \
    __FILE__,                                   \
    __LINE__,                                   \
    fmt,                                        \
    ##__VA_ARGS__                               \
  )
dqcs_log_fatal()

Convenience macro for calling dqcs_log_format() with fatal loglevel and automatically determined function name, filename, and line number.

#define dqcs_log_fatal(fmt, ...)                \
  dqcs_log_format(                              \
    _DQCSIM_LOGLEVEL_PREFIX_ DQCS_LOG_FATAL,    \
    _DQCSIM_LANGUAGE_,                          \
    __FILE__,                                   \
    __LINE__,                                   \
    fmt,                                        \
    ##__VA_ARGS__                               \
  )

To get more control over the metadata or to set the loglevel programmatically, you can use the following function.

dqcs_log_format()

Sends a log message using the current logger using printf-like formatting.

static void dqcs_log_format(
    dqcs_loglevel_t level,
    const char *module,
    const char *file,
    uint32_t line,
    const char *fmt,
    ...
)

This function is identical to dqcs_log_raw(), except instead of a single string it takes a printf-like format string and varargs to compose the message.

The above macros and functions ultimately call dqcs_log_raw(). This function does not fall back to stderr if logging fails, and does not support printf-style format strings. The latter may be useful for interfacing with languages that don't support calling variadic functions.

dqcs_log_raw()

Primitive API for sending a log message using the current logger.

dqcs_return_t dqcs_log_raw(
    dqcs_loglevel_t level,
    const char *module,
    const char *file,
    uint32_t line_nr,
    const char *message
)

Returns DQCS_SUCCESS if logging was successful, or DQCS_FAILURE if no logger is available in the current thread or one of the arguments could not be converted. Loggers are available in the simulation host thread and in threads running plugins.

Formatting and fallback to stderr

As an alternative to this function, you can also use dqcs_log_format(). This function differs from dqcs_log_raw() in two ways:

  • Instead of the message string, a printf-style format string and associated varargs are passed to construct the message.
  • When logging fails, this function falls back to writing to stderr instead of returning the errors.

Macros

From C and C++, these functions are normally not called directly. Instead, the following macros are used:

dqcs_log_trace("trace message!");
dqcs_log_debug("debug message!");
dqcs_log_info("info message!");
dqcs_log_note("notice!");
dqcs_log_warn("warning!");
dqcs_log_error("error!");
dqcs_log_fatal("fatal error!");

These macros automatically set file to the C source filename and line to the line number. module is hardcoded to "C" or "CPP" depending on source file language. They use dqcs_log_format(), so they also support printf-style formatting. For instance:

dqcs_note("answer to %s: %d", "ultimate question", 42);

stdout and stderr

By default, DQCsim will capture the stdout and stderr streams of the plugin processes it launches. Each received line will simply be turned into a log message. This is particularly useful for logging problems related to connecting to DQCsim.

Simulations

The easiest way to start a simulation is to use the command line interface. However, it is also possible to launch simulations programmatically through DQCsim's host interface. Besides letting you start simulations without needing to fork a new process, this interface also allows you to interact with the frontend through a message queue, send ArbCmds to any plugin, and capture log messages.

Contents

Configuring plugins

Before we can build a simulation, we need to configure the individual plugins that make up the simulation. This is usually done using a plugin process configuration (pcfg).

Constructing a plugin process configuration

There are two ways to construct the configuration. dqcs_pcfg_new() is the easy one: it will look for the plugin executable in the same way that the command-line interface does it. It works using a single string, which can be:

  • a valid path to the plugin executable;
  • the basename of the plugin executable with implicit dqcsfe/dqcsop/dqcsbe prefix, searched for in A) the current working directory, B) the directory that the binary for the current process resides in, and C) the system $PATH (in that order);
  • a valid path to a script file with a file extension. In this case, the above rule is run for a plugin named by the file extension of the script file. For instance, if test.py is specified for a frontend plugin, DQCsim will look for an executable named dqcsfepy. The script filename is passed to the plugin through the first command-line argument, moving the simulator endpoint string to the second slot.

Alternatively, you can bypass this algorithm by specifying the full path to the plugin and (optionally) the script file directly using dqcs_pcfg_new_raw().

dqcs_pcfg_new()

Creates a new plugin process configuration object using sugared syntax.

dqcs_handle_t dqcs_pcfg_new(
    dqcs_plugin_type_t typ,
    const char *name,
    const char *spec
)

typ specifies the type of plugin. name specifies the name used to refer to the plugin later, which much be unique within a simulation; if it is empty or NULL, auto-naming will be performed: "front" for the frontend, "oper<i>" for the operators (indices starting at 1 from frontend to backend), and "back" for the backend. spec specifies which plugin to use, using the same syntax that the dqcsim command line interface uses.

dqcs_pcfg_new_raw()

Creates a new plugin process configuration object using raw paths.

dqcs_handle_t dqcs_pcfg_new_raw(
    dqcs_plugin_type_t typ,
    const char *name,
    const char *executable,
    const char *script
)

This works the same as dqcs_pcfg_new(), but instead of the sugared, command-line style specification you have to specify the path to the plugin executable and (if applicable) the script it must execute directly. This is useful when you have a specific executable in mind and you don't want the somewhat heuristic desugaring algorithm from doing something unexpected.

Pass NULL or an empty string to script to specify a native plugin executable that does not take a script argument.

After construction, the plugin type, name, executable path, and optional script path become immutable. However, their values can be queried using the following functions.

dqcs_pcfg_type()

Returns the type of the given plugin process configuration.

dqcs_plugin_type_t dqcs_pcfg_type(dqcs_handle_t pcfg)
dqcs_pcfg_name()

Returns the configured name for the given plugin process.

char *dqcs_pcfg_name(dqcs_handle_t pcfg)

On success, this returns a newly allocated string containing the name. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_pcfg_executable()

Returns the configured executable path for the given plugin process.

char *dqcs_pcfg_executable(dqcs_handle_t pcfg)

On success, this returns a newly allocated string containing the executable path. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_pcfg_script()

Returns the configured script path for the given plugin process.

char *dqcs_pcfg_script(dqcs_handle_t pcfg)

On success, this returns a newly allocated string containing the script path. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL. An empty string will be returned if no script is configured to distinguish it from failure.

Functional configuration

A plugin's behavior can be augmented using a list of ArbCmds passed to its initialization callback and through environment variables. These can be set using the following functions.

dqcs_pcfg_init_cmd()

Appends an ArbCmd to the list of initialization commands of a plugin process.

dqcs_return_t dqcs_pcfg_init_cmd(
    dqcs_handle_t pcfg,
    dqcs_handle_t cmd
)

The ArbCmd handle is consumed by this function, and is thus invalidated, if and only if it is successful.

dqcs_pcfg_env_set()

Overrides an environment variable for the plugin process.

dqcs_return_t dqcs_pcfg_env_set(
    dqcs_handle_t pcfg,
    const char *key,
    const char *value
)

The environment variable key is set to value regardless of whether it exists in the parent environment variable scope.

If value is NULL, the environment variable key is unset instead.

dqcs_pcfg_env_unset()

Removes/unsets an environment variable for the plugin process.

dqcs_return_t dqcs_pcfg_env_unset(
    dqcs_handle_t pcfg,
    const char *key
)

The environment variable key is unset regardless of whether it exists in the parent environment variable scope.

It's also possible to assign a different working directory to a plugin process using the following functions.

dqcs_pcfg_work_set()

Overrides the working directory for the plugin process.

dqcs_return_t dqcs_pcfg_work_set(
    dqcs_handle_t pcfg,
    const char *work
)
dqcs_pcfg_work_get()

Returns the configured working directory for the given plugin process.

char *dqcs_pcfg_work_get(dqcs_handle_t pcfg)

On success, this returns a newly allocated string containing the working directory. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

These configuration parameters are recorded in reproduction files, since they may modify the behavior of the plugin.

Logging configuration

DQCsim allows log message filtering to be performed independently for each plugin. The following functions can be used to configure this per-plugin filter. The verbosity defaults to trace to pass all messages through; the messages will also be filtered by DQCsim itself.

dqcs_pcfg_verbosity_set()

Configures the logging verbosity for the given plugin process.

dqcs_return_t dqcs_pcfg_verbosity_set(
    dqcs_handle_t pcfg,
    dqcs_loglevel_t level
)
dqcs_pcfg_verbosity_get()

Returns the configured verbosity for the given plugin process.

dqcs_loglevel_t dqcs_pcfg_verbosity_get(dqcs_handle_t pcfg)

You can also let DQCsim pipe only the messages of a specific plugin to a file. This behavior can be configured using dqcs_pcfg_tee().

dqcs_pcfg_tee()

Configures a plugin process to also output its log messages to a file.

dqcs_return_t dqcs_pcfg_tee(
    dqcs_handle_t pcfg,
    dqcs_loglevel_t verbosity,
    const char *filename
)

verbosity configures the verbosity level for the file only.

Finally, DQCsim will by default capture the stdout and stderr streams of the plugin process and convert each received line into a log message. The following functions can be used to configure the loglevels used for these messages, to disable capturing, or to void the streams altogether.

dqcs_pcfg_stdout_mode_set()

Configures the capture mode for the stdout stream of the specified plugin process.

dqcs_return_t dqcs_pcfg_stdout_mode_set(
    dqcs_handle_t pcfg,
    dqcs_loglevel_t level
)
dqcs_pcfg_stdout_mode_get()

Returns the configured stdout capture mode for the specified plugin process.

dqcs_loglevel_t dqcs_pcfg_stdout_mode_get(dqcs_handle_t pcfg)
dqcs_pcfg_stderr_mode_set()

Configures the capture mode for the stderr stream of the specified plugin process.

dqcs_return_t dqcs_pcfg_stderr_mode_set(
    dqcs_handle_t pcfg,
    dqcs_loglevel_t level
)
dqcs_pcfg_stderr_mode_get()

Returns the configured stderr capture mode for the specified plugin process.

dqcs_loglevel_t dqcs_pcfg_stderr_mode_get(dqcs_handle_t pcfg)

Timeouts

DQCsim uses a timeout mechanism when spawning a plugin and shutting it down to detect deadlocks due to misconfiguration. This timeout defaults to 5 seconds. If your plugin needs more time to start up or shut down gracefully for some reason, you can modify the timeouts using the following functions.

dqcs_pcfg_accept_timeout_set()

Configures the timeout for the plugin process to connect to DQCsim.

dqcs_return_t dqcs_pcfg_accept_timeout_set(
    dqcs_handle_t pcfg,
    double timeout
)

The default is 5 seconds, so you should normally be able to leave this alone.

The time unit is seconds. Use IEEE positive infinity to specify an infinite timeout.

dqcs_pcfg_accept_timeout_get()

Returns the configured timeout for the plugin process to connect to DQCsim.

double dqcs_pcfg_accept_timeout_get(dqcs_handle_t pcfg)

The time unit is in seconds. Returns positive inifinity for an infinite timeout. Returns -1 when the function fails.

dqcs_pcfg_shutdown_timeout_set()

Configures the timeout for the plugin process to shut down gracefully.

dqcs_return_t dqcs_pcfg_shutdown_timeout_set(
    dqcs_handle_t pcfg,
    double timeout
)

The default is 5 seconds, so you should normally be able to leave this alone.

The time unit is seconds. Use IEEE positive infinity to specify an infinite timeout.

dqcs_pcfg_shutdown_timeout_get()

Returns the configured timeout for the plugin process to shut down gracefully.

double dqcs_pcfg_shutdown_timeout_get(dqcs_handle_t pcfg)

The time unit is in seconds. Returns positive inifinity for an infinite timeout. Returns -1 when the function fails.

Running local plugins

Besides letting DQCsim spawn plugin processes for you, you can also let DQCsim run a plugin within a thread, or assume full control over spawning the process or thread. To do this, you need to use a plugin thread configuration object (tcfg) instead of a plugin process configuration.

Running a plugin within a thread

This method is similar to spawning a plugin in a process. However, because there's no process boundary, the plugin is defined within the host process, and can access memory of the host process. This can be useful particularly when you want to make a self-contained simulation, or want to insert a simple operator plugin somewhere in the pipeline that you don't want to make a separate executable for. The configuration object for this scenario is constructed using the following function.

dqcs_tcfg_new()

Creates a new plugin thread configuration object from a plugin definition.

dqcs_handle_t dqcs_tcfg_new(
    dqcs_handle_t pdef,
    const char *name
)

The plugin definition handle is consumed by this function.

Assuming full control over plugin spawning

This method gives you full control over spawning a plugin process or thread. This is useful for instance if you want to encapsulate the plugin process in a tool like gdb or valgrind. The configuration object for this method is constructed using the following function.

dqcs_tcfg_new_raw()

Creates a new plugin thread configuration object from a callback.

dqcs_handle_t dqcs_tcfg_new_raw(
    dqcs_plugin_type_t plugin_type,
    const char *name,
    void (*callback)(
        void *user_data,
        const char *simulator
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

The callback is called by DQCsim from a dedicated thread when DQCsim wants to start the plugin. The callback must then in some way spawn a plugin process that connects to the provided simulator string. The callback should return only when the process terminates.

Querying plugin thread configuration objects

Similar to plugin process configuration objects, the name and type of the plugin are immutable after construction, but can be queried.

dqcs_tcfg_type()

Returns the type of the given plugin thread configuration.

dqcs_plugin_type_t dqcs_tcfg_type(dqcs_handle_t tcfg)
dqcs_tcfg_name()

Returns the configured name for the given plugin thread.

char *dqcs_tcfg_name(dqcs_handle_t tcfg)

On success, this returns a newly allocated string containing the name. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

Functional configuration

Like plugin process configuration objects, it's possible to send ArbCmds to the plugin's initialization callback. However, environment variables and the working directory of the plugin cannot be set, since they're tied to the host process.

dqcs_tcfg_init_cmd()

Appends an ArbCmd to the list of initialization commands of a plugin thread.

dqcs_return_t dqcs_tcfg_init_cmd(
    dqcs_handle_t tcfg,
    dqcs_handle_t cmd
)

The ArbCmd handle is consumed by this function, and is thus invalidated, if and only if it is successful.

Logging configuration

The logging behavior for a plugin thread can be configured with the following functions, just like plugin processes. However, file descriptors are shared between threads, so the plugin thread does not have a separate stdout/stderr stream that can be captured.

dqcs_tcfg_verbosity_set()

Configures the logging verbosity for the given plugin thread.

dqcs_return_t dqcs_tcfg_verbosity_set(
    dqcs_handle_t tcfg,
    dqcs_loglevel_t level
)
dqcs_tcfg_verbosity_get()

Returns the configured verbosity for the given plugin thread.

dqcs_loglevel_t dqcs_tcfg_verbosity_get(dqcs_handle_t tcfg)
dqcs_tcfg_tee()

Configures a plugin thread to also output its log messages to a file.

dqcs_return_t dqcs_tcfg_tee(
    dqcs_handle_t tcfg,
    dqcs_loglevel_t verbosity,
    const char *filename
)

verbosity configures the verbosity level for the file only.

Configuring a simulation

Once you've constructed the plugin configuration objects for your simulation, you can start putting them together in a simulation configuration object (scfg).

Constructing a simulation configuration

A basic simulation with a frontend and a backend can be constructed as follows.

dqcs_handle_t frontend_pcfg = ...;
dqcs_handle_t backend_pcfg = ...;
dqcs_handle_t scfg = dqcs_scfg_new();
dqcs_scfg_push_plugin(scfg, frontend_pcfg);
dqcs_scfg_push_plugin(scfg, backend_pcfg);

The order in which you push plugins doesn't matter, except for operators, which must be pushed in downstream order.

dqcs_scfg_new()

Constructs an empty simulation configuration.

dqcs_handle_t dqcs_scfg_new(void)

Before the configuration can be used, at least a frontend and a backend plugin configuration must be pushed into it. This can be done with dqcs_scfg_push_plugin(). Failing to do this will result in an error when you try to start the simulation.

The default settings correspond to the defaults of the dqcsim command line interface. Refer to its help for more information.

dqcs_scfg_push_plugin()

Appends a plugin to a simulation configuration.

dqcs_return_t dqcs_scfg_push_plugin(
    dqcs_handle_t scfg,
    dqcs_handle_t xcfg
)

Both plugin process and plugin thread configuration objects may be used. The handle is consumed by this function, and is thus invalidated, if and only if it is successful.

Frontend and backend plugins will automatically be inserted at the front and back of the pipeline when the simulation is created. Operators are inserted in front to back order. This function does not provide safeguards against multiple frontends/backends; such errors will only be reported when the simulation is started.

Note that it is not possible to observe or mutate a plugin configuration once it has been added to a simulator configuration handle. If you want to do this for some reason, you should maintain your own data structures, and only build the DQCsim structures from them when you're done.

Random seed

The random seed can be set and queried using the functions below. The default seed is generated using a hash of the most accurate system timestamp available.

dqcs_scfg_seed_set()

Configures the random seed that the simulation should use.

dqcs_return_t dqcs_scfg_seed_set(
    dqcs_handle_t scfg,
    uint64_t seed
)

Note that the seed is randomized by default.

dqcs_scfg_seed_get()

Returns the configured random seed.

uint64_t dqcs_scfg_seed_get(dqcs_handle_t scfg)

This function will return 0 when it fails, but this can unfortunately not be reliably distinguished from a seed that was set to 0.

Reproduction

DQCsim can output a reproduction file for the simulation when the simulation is complete. The recording logic for this system is on by default, since it is recommended to always output such a file. After all, you never know when something unexpected that you might want to reproduce might happen!

By default, the generated reproduction file will specify the plugin executable and script paths as they were generated or specified. However, depending on how you intend to reproduce the simulation later, you may want purely relative or purely absolute paths instead. This can be configured using the following functions.

dqcs_scfg_repro_path_style_set()

Sets the path style used when writing reproduction files.

dqcs_return_t dqcs_scfg_repro_path_style_set(
    dqcs_handle_t scfg,
    dqcs_path_style_t path_style
)
dqcs_scfg_repro_path_style_get()

Returns the path style used when writing reproduction files.

dqcs_path_style_t dqcs_scfg_repro_path_style_get(dqcs_handle_t scfg)

It is also possible to disable the reproduction system. The only benefit this has is to supress the warning message that's generated when a simulation cannot be reproduced, which is the case when plugin threads are used.

dqcs_scfg_repro_disable()

Disables the reproduction logging system.

dqcs_return_t dqcs_scfg_repro_disable(dqcs_handle_t scfg)

Calling this will disable the warnings printed when a simulation that cannot be reproduced is constructed.

Logging configuration

DQCsim has two main log message verbosity filters. The first is configured using dqcs_scfg_dqcsim_verbosity_*(). It controls the filtering of messages generated by the simulation management code, as opposed to the messages received from the various plugins. Like the plugin-specific filters, this defaults to passing all messages through.

dqcs_scfg_dqcsim_verbosity_set()

Configures the logging verbosity for DQCsim's own messages.

dqcs_return_t dqcs_scfg_dqcsim_verbosity_set(
    dqcs_handle_t scfg,
    dqcs_loglevel_t level
)
dqcs_scfg_dqcsim_verbosity_get()

Returns the configured verbosity for DQCsim's own messages.

dqcs_loglevel_t dqcs_scfg_dqcsim_verbosity_get(dqcs_handle_t scfg)

The second filter controls the minimum verbosity that a message must have for it to be written to stderr. This filter defaults to info.

dqcs_scfg_stderr_verbosity_set()

Configures the stderr sink verbosity for a simulation.

dqcs_return_t dqcs_scfg_stderr_verbosity_set(
    dqcs_handle_t scfg,
    dqcs_loglevel_t level
)

That is, the minimum loglevel that a messages needs to have for it to be printed to stderr.

dqcs_scfg_stderr_verbosity_get()

Returns the configured stderr sink verbosity for a simulation.

dqcs_loglevel_t dqcs_scfg_stderr_verbosity_get(dqcs_handle_t scfg)

That is, the minimum loglevel that a messages needs to have for it to be printed to stderr.

It's also possible to have DQCsim pipe all the log messages it receives to a file, optionally with its own verbosity filter. This is configured using dqcs_scfg_tee().

dqcs_scfg_tee()

Configures DQCsim to also output its log messages to a file.

dqcs_return_t dqcs_scfg_tee(
    dqcs_handle_t scfg,
    dqcs_loglevel_t verbosity,
    const char *filename
)

verbosity configures the verbosity level for the file only.

Finally, you can have DQCsim call a callback function whenever it receives a log message. This can be used to tie DQCsim's logging system into whatever different logging system that the host process uses.

dqcs_scfg_log_callback()

Configures DQCsim to also output its log messages to callback function.

dqcs_return_t dqcs_scfg_log_callback(
    dqcs_handle_t scfg,
    dqcs_loglevel_t verbosity,
    void (*callback)(
        void *user_data,
        const char *message,
        const char *logger,
        dqcs_loglevel_t level,
        const char *module,
        const char *file,
        uint32_t line,
        uint64_t time_s,
        uint32_t time_ns,
        uint32_t pid,
        uint64_t tid
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

verbosity specifies the minimum importance of a message required for the callback to be called.

callback is the callback function to install. It is always called with the user_data pointer to make calling stuff like class member functions or closures possible. The user_free function, if non-null, will be called when the callback is uninstalled in any way. If callback is null, any current callback is uninstalled instead. For consistency, if user_free is non-null while callback is null, user_free is called immediately, under the assumption that the caller has allocated resources unbeknownst that the callback it's trying to install is null.

NOTE: both callback and user_free may be called from a thread spawned by the simulator. Calling any API calls from the callback is therefore undefined behavior!

The callback takes the following arguments:

  • void*: user defined data.
  • const char*: log message string, excluding metadata.
  • const char*: name assigned to the logger that was used to produce the message (= "dqcsim" or a plugin name).
  • dqcs_loglevel_t: the verbosity level that the message was logged with.
  • const char*: string representing the source of the log message, or NULL when no source is known.
  • const char*: string containing the filename of the source that generated the message, or NULL when no source is known.
  • uint32_t: line number within the aforementioned file, or 0 if not known.
  • uint64_t: Time in seconds since the Unix epoch.
  • uint32_t: Additional time in nanoseconds since the aforementioned.
  • uint32_t: PID of the generating process.
  • uint64_t: TID of the generating thread.

If an internal log record is particularly malformed and cannot be coerced into the above (nul bytes in the strings, invalid timestamp, whatever) the message is silently ignored.

The primary use of this callback is to pipe DQCsim's messages to an external logging framework. When you do this, you probably also want to call dqcs_scfg_stderr_verbosity_set(handle, DQCS_LOG_OFF) to prevent DQCsim from writing the messages to stderr itself.

Running a simulation

When you've finished building a simulation configuration object, you can turn it into a real simulation as described in this section.

Constructing a simulation

To run the simulation, all you have to do is pass the simulation configuration object to dqcs_sim_new(). This function will return when all the plugins have finished initializing as configured in the configuration object, and return a handle to the simulation.

dqcs_sim_new()

Constructs a DQCsim simulation.

dqcs_handle_t dqcs_sim_new(dqcs_handle_t scfg)

The provided handle is consumed if it is a simulation configuration, regardless of whether simulation construction succeeds.

Note that it is currently not possible to have more than one simulation handle within a single thread at the same time. This has to do with DQCsim's log system, which uses thread-local storage to determine where log messages should go. If you want to run multiple simulations in parallel, you'll have to run them from different threads.

Interacting with a simulation

After constructing the simulation, you have to explicitly tell the frontend plugin to start executing a quantum algorithm. This is done using dqcs_sim_start(). This function is asynchronous: the simulation request is only sent to the frontend when a blocking function is called. To get the result/return value of a previously started quantum algorithm, you can use dqcs_sim_stop(). In fact, you have to do this for every call to dqcs_sim_start(), and you can't have more than one quantum algorithm running at a time within the context of a single simulation.

dqcs_sim_start()

Starts a program on the simulated accelerator.

dqcs_return_t dqcs_sim_start(
    dqcs_handle_t sim,
    dqcs_handle_t data
)

This is an asynchronous call: nothing happens until yield(), recv(), or wait() is called.

The ArbData handle is optional; if 0 is passed, an empty data object is used. If a handle is passed, it is consumed if and only if the API call succeeds.

dqcs_sim_wait()

Waits for the simulated accelerator to finish its current program.

dqcs_handle_t dqcs_sim_wait(dqcs_handle_t sim)

When this succeeds, the return value of the accelerator's run() function is returned in the form of a new handle. When it fails, 0 is returned.

Deadlocks are detected and prevented by returning an error.

While a quantum algorithm is running, you can interact with it using ArbData message queues. You can send and receive data to and from these queues using the following functions. The send function is asynchronous, while the receive function will block if no messages are available.

dqcs_sim_send()

Sends a message to the simulated accelerator.

dqcs_return_t dqcs_sim_send(
    dqcs_handle_t sim,
    dqcs_handle_t data
)

This is an asynchronous call: nothing happens until yield(), recv(), or wait() is called.

The ArbData handle is optional; if 0 is passed, an empty data object is used. If a handle is passed, it is consumed if and only if the API call succeeds.

dqcs_sim_recv()

Waits for the simulated accelerator to send a message to us.

dqcs_handle_t dqcs_sim_recv(dqcs_handle_t sim)

When this succeeds, the received data is returned in the form of a new handle. When it fails, 0 is returned.

Deadlocks are detected and prevented by returning an error.

At any time, you can force DQCsim to pass control to the frontend plugin using the following function. This is primarily useful for debugging, when you for instance want to see the results of a single sent message in the log message stream without calling a blocking function that actually does something.

dqcs_sim_yield()

Yields to the simulator.

dqcs_return_t dqcs_sim_yield(dqcs_handle_t sim)

The simulation runs until it blocks again. This is useful if you want an immediate response to an otherwise asynchronous call through the logging system or some communication channel outside of DQCsim's control.

This function silently returns immediately if no asynchronous data was pending or if the simulator is waiting for something that has not been sent yet.

You can also send ArbCmds to plugins at any time. This corresponds to calling the host_arb callback within a plugin. This is always synchronous; any requests queued through dqcs_sim_start() and dqcs_sim_send() are processed before the ArbCmd, and the function waits for the ArbCmd to finish executing in order for it to return its result.

dqcs_sim_arb()

Sends an ArbCmd message to one of the plugins, referenced by name.

dqcs_handle_t dqcs_sim_arb(
    dqcs_handle_t sim,
    const char *name,
    dqcs_handle_t cmd
)

ArbCmds are executed immediately after yielding to the simulator, so all pending asynchronous calls are flushed and executed before the ArbCmd.

When this succeeds, the received data is returned in the form of a new handle. When it fails, 0 is returned.

The ArbCmd handle is consumed if and only if the API call succeeds.

dqcs_sim_arb_idx()

Sends an ArbCmd message to one of the plugins, referenced by index.

dqcs_handle_t dqcs_sim_arb_idx(
    dqcs_handle_t sim,
    ssize_t index,
    dqcs_handle_t cmd
)

The frontend always has index 0. 1 through N are used for the operators in front to back order (where N is the number of operators). The backend is at index N+1.

Python-style negative indices are supported. That is, -1 can be used to refer to the backend, -2 to the last operator, and so on.

ArbCmds are executed immediately after yielding to the simulator, so all pending asynchronous calls are flushed and executed before the ArbCmd.

When this succeeds, the received data is returned in the form of a new handle. When it fails, 0 is returned.

The ArbCmd handle is consumed if and only if the API call succeeds.

Querying plugin information

You can query the metadata associated with the plugins that make up a simulation using the following functions.

dqcs_sim_get_name()

Queries the implementation name of a plugin, referenced by instance name.

char *dqcs_sim_get_name(
    dqcs_handle_t sim,
    const char *name
)

On success, this returns a newly allocated string containing the name. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_sim_get_name_idx()

Queries the implementation name of a plugin, referenced by index.

char *dqcs_sim_get_name_idx(
    dqcs_handle_t sim,
    ssize_t index
)

On success, this returns a newly allocated string containing the name. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_sim_get_author()

Queries the author of a plugin, referenced by instance name.

char *dqcs_sim_get_author(
    dqcs_handle_t sim,
    const char *name
)

On success, this returns a newly allocated string containing the author. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_sim_get_author_idx()

Queries the author of a plugin, referenced by index.

char *dqcs_sim_get_author_idx(
    dqcs_handle_t sim,
    ssize_t index
)

On success, this returns a newly allocated string containing the author. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_sim_get_version()

Queries the version of a plugin, referenced by instance name.

char *dqcs_sim_get_version(
    dqcs_handle_t sim,
    const char *name
)

On success, this returns a newly allocated string containing the version. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_sim_get_version_idx()

Queries the version of a plugin, referenced by index.

char *dqcs_sim_get_version_idx(
    dqcs_handle_t sim,
    ssize_t index
)

On success, this returns a newly allocated string containing the version. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

Shutting a simulation down

When you're done with a simulation, you can just use dqcs_handle_delete() to shut it down. Before doing that, though, it is strongly recommended to output a reproduction file. This file lets you reproduce the simulation exactly without needing the host executable (or needing it to be deterministic) with just DQCsim's command-line interface. You never know when you might need this for debugging!

dqcs_sim_write_reproduction_file()

Writes a reproduction file for the simulation so far.

dqcs_return_t dqcs_sim_write_reproduction_file(
    dqcs_handle_t sim,
    const char *filename
)

Reference

This chapter simply lists all API functions and types ordered by name.

dqcs_arb_assign()

Copies the data from one object to another.

dqcs_return_t dqcs_arb_assign(
    dqcs_handle_t dest,
    dqcs_handle_t src
)
dqcs_arb_cbor_get()

Returns the JSON/CBOR object of an ArbData object in the form of a CBOR object.

ssize_t dqcs_arb_cbor_get(
    dqcs_handle_t arb,
    void *obj,
    size_t obj_size
)

If the actual size of the object differs from the specified object size, this function will copy the minimum of the actual and specified sizes number of bytes, and return what the actual size was.

If the specified object size is zero, obj is allowed to be NULL. You can use this to query the size before allocating an object.

This function returns -1 on failure.

dqcs_arb_cbor_set()

Sets the JSON/CBOR object of an ArbData object by means of a CBOR object.

dqcs_return_t dqcs_arb_cbor_set(
    dqcs_handle_t arb,
    const void *obj,
    size_t obj_size
)
dqcs_arb_clear()

Clears the unstructured argument list.

dqcs_return_t dqcs_arb_clear(dqcs_handle_t arb)
dqcs_arb_get_raw()

Returns the unstructured string argument at the specified index.

ssize_t dqcs_arb_get_raw(
    dqcs_handle_t arb,
    ssize_t index,
    void *obj,
    size_t obj_size
)

If the actual size of the object differs from the specified object size, this function will copy the minimum of the actual and specified sizes number of bytes, and return what the actual size was.

If the specified object size is zero, obj is allowed to be NULL. You can use this to determine the size of the argument prior to actually reading it, so you can allocate the right buffer size first.

This function returns -1 on failure.

dqcs_arb_get_size()

Returns the size in bytes of the unstructured string argument at the specified index.

ssize_t dqcs_arb_get_size(
    dqcs_handle_t arb,
    ssize_t index
)

Returns -1 when the function fails.

dqcs_arb_get_str()

Returns the unstructured string argument at the specified index.

char *dqcs_arb_get_str(
    dqcs_handle_t arb,
    ssize_t index
)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

dqcs_arb_insert_raw()

Inserts an unstructured raw argument into the list at the specified index.

dqcs_return_t dqcs_arb_insert_raw(
    dqcs_handle_t arb,
    ssize_t index,
    const void *obj,
    size_t obj_size
)
dqcs_arb_insert_str()

Inserts an unstructured string argument into the list at the specified index.

dqcs_return_t dqcs_arb_insert_str(
    dqcs_handle_t arb,
    ssize_t index,
    const char *s
)
dqcs_arb_json_get()

Returns the JSON/CBOR object of an ArbData object in the form of a JSON string.

char *dqcs_arb_json_get(dqcs_handle_t arb)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

dqcs_arb_json_set()

Sets the JSON/CBOR object of an ArbData object by means of a JSON string.

dqcs_return_t dqcs_arb_json_set(
    dqcs_handle_t arb,
    const char *json
)
dqcs_arb_len()

Returns the number of unstructured arguments, or -1 to indicate failure.

ssize_t dqcs_arb_len(dqcs_handle_t arb)
dqcs_arb_new()

Creates a new ArbData object.

dqcs_handle_t dqcs_arb_new(void)

Returns the handle of the newly created ArbData. The ArbData is initialized with JSON object {} and an empty binary argument list.

ArbData objects support the handle and arb APIs.

dqcs_arb_pop()

Pops an unstructured argument from the back of the list without returning it.

dqcs_return_t dqcs_arb_pop(dqcs_handle_t arb)
dqcs_arb_pop_raw()

Pops an unstructured raw argument from the back of the list.

ssize_t dqcs_arb_pop_raw(
    dqcs_handle_t arb,
    void *obj,
    size_t obj_size
)

If the actual size of the object differs from the specified object size, this function will copy the minimum of the actual and specified sizes number of bytes, and return what the actual size was.

If the specified object size is zero, obj is allowed to be NULL. You can use this if you don't need the contents of the argument and just want to delete it.

Since this function removes the returned element, data will be lost if the specified size is smaller than the actual size. To avoid this, first use dqcs_arb_get_size(handle, -1) to query the size.

This function returns -1 on failure. If this is due to a NULL buffer being passed, the data that was popped is lost.

dqcs_arb_pop_str()

Pops an unstructured string argument from the back of the list.

char *dqcs_arb_pop_str(dqcs_handle_t arb)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL. If the failure is due to the conversion from binary object to C string (i.e., embedded nulls), the data is still popped and is thus lost.

dqcs_arb_push_raw()

Pushes an unstructured raw argument to the back of the list.

dqcs_return_t dqcs_arb_push_raw(
    dqcs_handle_t arb,
    const void *obj,
    size_t obj_size
)
dqcs_arb_push_str()

Pushes an unstructured string argument to the back of the list.

dqcs_return_t dqcs_arb_push_str(
    dqcs_handle_t arb,
    const char *s
)
dqcs_arb_remove()

Removes the specified unstructured string argument from the list.

dqcs_return_t dqcs_arb_remove(
    dqcs_handle_t arb,
    ssize_t index
)
dqcs_arb_set_raw()

Replaces the unstructured argument at the specified index with the specified raw object.

dqcs_return_t dqcs_arb_set_raw(
    dqcs_handle_t arb,
    ssize_t index,
    const void *obj,
    size_t obj_size
)
dqcs_arb_set_str()

Replaces the unstructured argument at the specified index with the specified string.

dqcs_return_t dqcs_arb_set_str(
    dqcs_handle_t arb,
    ssize_t index,
    const char *s
)
dqcs_basis_t

Enumeration of Pauli bases.

typedef enum { ... } dqcs_basis_t;

Variants:

DQCS_BASIS_INVALID = 0
Invalid basis. Used as an error return value.
DQCS_BASIS_X = 1
The X basis.

\[ \psi_X = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & -1 \\ 1 & 1 \end{bmatrix} \]

DQCS_BASIS_Y = 2
The Y basis.

\[ \psi_Y = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & i \\ i & 1 \end{bmatrix} \]

DQCS_BASIS_Z = 3
The Z basis.

\[ \psi_Z = \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \]

dqcs_bool_return_t

Return type for functions that normally return a boolean but can also fail.

typedef enum { ... } dqcs_bool_return_t;

Variants:

DQCS_BOOL_FAILURE = -1
The function has failed. More information may be obtained through `dqcsim_explain()`.
DQCS_FALSE = 0
The function did what it was supposed to and returned false.
DQCS_TRUE = 1
The function did what it was supposed to and returned true.

dqcs_cmd_iface_cmp()

Compares the interface ID of an ArbCmd with the given string.

dqcs_bool_return_t dqcs_cmd_iface_cmp(
    dqcs_handle_t cmd,
    const char *iface
)

Returns -1 for failure, 0 for no match, or 1 for a match.

dqcs_cmd_iface_get()

Returns the interface ID of an ArbCmd.

char *dqcs_cmd_iface_get(dqcs_handle_t cmd)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

dqcs_cmd_new()

Creates a new ArbCmd object.

dqcs_handle_t dqcs_cmd_new(
    const char *iface,
    const char *oper
)

Returns the handle of the newly created ArbCmd. The ArbCmd is initialized with the given interface and operation IDs, JSON object {}, and an empty binary argument list. Upon failure, returns 0.

ArbCmd objects support the handle, arb, and cmd interfaces.

dqcs_cmd_oper_cmp()

Compares the operation ID of an ArbCmd with the given string.

dqcs_bool_return_t dqcs_cmd_oper_cmp(
    dqcs_handle_t cmd,
    const char *oper
)

Returns -1 for failure, 0 for no match, or 1 for a match.

dqcs_cmd_oper_get()

Returns the operation ID of an ArbCmd.

char *dqcs_cmd_oper_get(dqcs_handle_t cmd)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

dqcs_cq_len()

Returns the number of ArbCmd objects in the given ArbCmd queue.

ssize_t dqcs_cq_len(dqcs_handle_t cq)

This function returns -1 to indicate failure.

dqcs_cq_new()

Creates a new ArbCmd queue object.

dqcs_handle_t dqcs_cq_new(void)

Returns the handle of the newly created ArbCmd queue. The queue is initially empty. Queues implement a "first-in, first-out" model.

ArbCmd queue objects support the handle, arb, cmd, and cq APIs.

The arb and cmd APIs refer to the ArbCmd at the front of the queue. Use dqcs_cq_next() to remove the front entry, allowing access to the next command.

dqcs_cq_next()

Advances an ArbCmd queue to the next command.

dqcs_return_t dqcs_cq_next(dqcs_handle_t cq)

Use the dqcs_arb_* and dqcs_cmd_* interfaces to read out the command before calling this function.

To iterate over a queue in C, use the following snippit:

for (; dqcs_cq_len(queue) > 0; dqcs_cq_next(queue)) {
    dqcs_cmd_...(queue, ...)
    dqcs_arb_...(queue, ...)
}
dqcs_cq_push()

Pushes an ArbCmd object into the given ArbCmd queue.

dqcs_return_t dqcs_cq_push(
    dqcs_handle_t cq,
    dqcs_handle_t cmd
)

This function returns -1 to indicate failure. The ArbCmd object specified by cmd is moved into the queue. That is, the handle is consumed if and only if the function succeeds.

dqcs_cycle_t

Type for a simulation cycle timestamp.

typedef long long dqcs_cycle_t;

Timestamps count upward from zero. The type is signed to allow usage of -1 for errors, and to allow numerical differences to be represented.

dqcs_error_get()

Returns a pointer to the latest error message.

const char *dqcs_error_get(void)

Call this to get extra information when another function returns a failure code. The returned pointer is temporary and therefore should NOT be free()d. It will become invalid when a new error occurs.

dqcs_error_set()

Sets the latest error message string.

void dqcs_error_set(const char *msg)

This must be called by callback functions when an error occurs within the callback, otherwise the upstream result for dqcs_error_get() will be undefined.

If msg is set to NULL, the error string is cleared instead.

dqcs_gate_controls()

Returns a handle to a new qubit reference set containing the qubits that control this gate.

dqcs_handle_t dqcs_gate_controls(dqcs_handle_t gate)
dqcs_gate_expand_control()

Utility function that expands a gate matrix to account for all control qubits.

dqcs_handle_t dqcs_gate_expand_control(dqcs_handle_t gate)

This function borrows a handle to any gate with a matrix, and returns an equivalent copy of said gate with any control qubits in the controls set moved to the targets set. The associated gate matrix is extended accordingly. The control qubits are added at the front of the targets set in the same order they appeared in the controls qubit set.

This function returns a new gate handle with the modified gate, or a copy of the input gate if the matrix could not be reduced. If the input gate does not have a matrix (measurement gate, or custom gate without matrix) an error is returned instead.

dqcs_gate_has_controls()

Returns whether the specified gate has control qubits.

dqcs_bool_return_t dqcs_gate_has_controls(dqcs_handle_t gate)
dqcs_gate_has_matrix()

Returns whether a unitary matrix is associated with this gate.

dqcs_bool_return_t dqcs_gate_has_matrix(dqcs_handle_t gate)
dqcs_gate_has_measures()

Returns whether the specified gate measures any qubits.

dqcs_bool_return_t dqcs_gate_has_measures(dqcs_handle_t gate)
dqcs_gate_has_name()

Returns whether the specified gate has a name.

dqcs_bool_return_t dqcs_gate_has_name(dqcs_handle_t gate)
dqcs_gate_has_targets()

Returns whether the specified gate has target qubits.

dqcs_bool_return_t dqcs_gate_has_targets(dqcs_handle_t gate)
dqcs_gate_matrix()

Returns a copy of the unitary matrix associated with this gate, if one exists.

dqcs_handle_t dqcs_gate_matrix(dqcs_handle_t gate)

If this function succeeds, a new matrix handle is returned. If it fails, 0 is returned.

dqcs_gate_measures()

Returns a handle to a new qubit reference set containing the qubits measured by this gate.

dqcs_handle_t dqcs_gate_measures(dqcs_handle_t gate)
dqcs_gate_name()

Returns the name of a custom gate.

char *dqcs_gate_name(dqcs_handle_t gate)

This function fails if the gate is not a custom gate. Query dqcs_gate_has_name() to disambiguate between a non-custom gate and a different error.

On success, this returns a newly allocated string containing the gate name. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

dqcs_gate_new_custom()

Constructs a new custom gate.

dqcs_handle_t dqcs_gate_new_custom(
    const char *name,
    dqcs_handle_t targets,
    dqcs_handle_t controls,
    dqcs_handle_t measures,
    dqcs_handle_t matrix
)

The functionality of custom gates is not specified by DQCsim. Instead, this is left up to the plugins. Of course, for this to work, plugins that are connected to each other must agree on the format used.

name specifies the name of the gate. The name is used to indicate which custom operation is to be applied.

targets optionally specifies the set of target qubits. You may pass 0 or an empty qubit set if you don't need target qubits.

controls optionally specifies the set of control qubits. You may pass 0 or an empty qubit set if you don't need control qubits.

measures optionally specifies the set of measured qubits. You may pass 0 or an empty qubit set if no qubits are measured. Note that the upstream plugin expects exactly one measurement result for each qubit specified in this set; anything else results in a warning and the measurement result being set to undefined.

matrix optionally specifies a handle to an appropriately sized matrix for the targets qubit set.

In addition to the above data, gate objects implement the arb interface to allow user-specified classical information to be attached.

This function returns the handle to the gate, or 0 to indicate failure. The specified qubit sets are consumed/deleted by this function if and only if it succeeds.

dqcs_gate_new_measurement()

Constructs a new measurement gate.

dqcs_handle_t dqcs_gate_new_measurement(
    dqcs_handle_t measures,
    dqcs_handle_t matrix
)

measures must be a handle to a qubit set. matrix is an optional matrix handle signifying the measurement basis. If zero, the Z basis is used. Otherwise, it must be a handle to a unitary 2x2 matrix, and the semantics of the measurement are as follows:

  • apply the hermetian of the matrix to each qubit
  • measure each qubit in the Z basis
  • apply the matrix to each qubit

This function returns the handle to the gate, or 0 to indicate failure. The measures qubit set and matrix handle are consumed/deleted by this function if and only if it succeeds.

dqcs_gate_new_predef()

Constructs a new predefined unitary gate.

dqcs_handle_t dqcs_gate_new_predef(
    dqcs_predefined_gate_t gate_type,
    dqcs_handle_t qubits,
    dqcs_handle_t param_data
)

gate_type specifies which kind of gate should be constructed.

targets must be a handle to a non-empty qubit set, containing at least as many qubits as needed for the specified gate type. If more qubits are specified, the rightmost qubits become the targets, and the remaining qubits become control qubits to make a controlled gate.

param_data takes an optional ArbData object used to parameterize the gate if necessary. If not specified, an empty object is used. Some of the gate types are parameterized, and use values from this ArbData as defined in the docs for dqcs_predefined_gate_t. Anything remaining in the ArbData afterwards is placed in the gate object.

This function returns the handle to the gate, or 0 to indicate failure. The qubit set and parameterization data (if specified) are consumed/deleted by this function if and only if it succeeds.

dqcs_gate_new_predef_one()

Constructs a new predefined unitary one-qubit gate.

dqcs_handle_t dqcs_gate_new_predef_one(
    dqcs_predefined_gate_t gate_type,
    dqcs_qubit_t qa,
    dqcs_handle_t param_data
)

This function is simply a shorthand for dqcs_gate_new_predef() with one qubit in the qubits set, to make constructing one-qubit gates more ergonomic. Refer to its documentation for more information.

dqcs_gate_new_predef_three()

Constructs a new predefined unitary three-qubit gate.

dqcs_handle_t dqcs_gate_new_predef_three(
    dqcs_predefined_gate_t gate_type,
    dqcs_qubit_t qa,
    dqcs_qubit_t qb,
    dqcs_qubit_t qc,
    dqcs_handle_t param_data
)

This function is simply a shorthand for dqcs_gate_new_predef() with three qubit in the qubits set, to make constructing three-qubit gates more ergonomic. Refer to its documentation for more information.

dqcs_gate_new_predef_two()

Constructs a new predefined unitary two-qubit gate.

dqcs_handle_t dqcs_gate_new_predef_two(
    dqcs_predefined_gate_t gate_type,
    dqcs_qubit_t qa,
    dqcs_qubit_t qb,
    dqcs_handle_t param_data
)

This function is simply a shorthand for dqcs_gate_new_predef() with two qubit in the qubits set, to make constructing two-qubit gates more ergonomic. Refer to its documentation for more information.

dqcs_gate_new_prep()

Constructs a new prep gate.

dqcs_handle_t dqcs_gate_new_prep(
    dqcs_handle_t targets,
    dqcs_handle_t matrix
)

targets must be a handle to a qubit set. matrix is an optional matrix handle signifying the state that the qubits are initialized to. If zero, the qubits are initialized to |0>. Otherwise, it must be a handle to a unitary 2x2 matrix, and the semantics are as follows:

  • initialize each qubit to |0>
  • apply the matrix to each qubit

This function returns the handle to the gate, or 0 to indicate failure. The targets qubit set and matrix handle are consumed/deleted by this function if and only if it succeeds.

dqcs_gate_new_unitary()

Constructs a new unitary gate.

dqcs_handle_t dqcs_gate_new_unitary(
    dqcs_handle_t targets,
    dqcs_handle_t controls,
    dqcs_handle_t matrix
)

targets must be a handle to a non-empty qubit set. The qubits in this set correspond with the supplied unitary matrix.

controls optionally specifies a set of control qubits. You may pass 0 or an empty qubit set if you don't need control qubits.

matrix must be a handle to an appropriately sized matrix.

The supplied matrix is only applied to the target qubits if all the control qubits are or will be determined to be set. For instance, to encode a CCNOT/Toffoli gate, you can specify one target qubits, two control qubits, and [0, 1; 1, 0] (X) for the matrix. This is equivalent to extending the matrix to the full Toffoli matrix and specifying all three qubits in the targets set, or the midway solution using a CNOT matrix, but these solutions may be less efficient depending on whether the simulator can optimize its calculations for controlled gates.

Simulators are not required to apply the (hidden) global phase component of the gate matrix in the same way it is specified; that is, if the simulator can optimize its calculations by altering the global phase it is allowed to.

DQCsim checks whether the matrix is unitary using the equivalent of dqcs_mat_approx_unitary() with an epsilon value of 1e-6.

This function returns the handle to the gate, or 0 to indicate failure. The targets qubit set, (if specified) the controls qubit set, and the matrix are consumed/deleted by this function if and only if it succeeds.

dqcs_gate_reduce_control()

Utility function that detects control qubits in the targets list of the gate by means of the gate matrix, and reduces them into controls qubits.

dqcs_handle_t dqcs_gate_reduce_control(
    dqcs_handle_t gate,
    double epsilon,
    bool ignore_gphase
)

This function borrows a handle to any gate with a matrix, and returns an equivalent copy of said gate with any control qubits in the targets set moved to the controls set. The associated gate matrix is accordingly reduced in size. The control qubits are added at the end of the controls set in the same order they appeared in the targets qubit set.

epsilon specifies the maximum element-wise deviation from the identity matrix for the relevant array elements for a qubit to be considered a control qubit. Note that if this is greater than zero, the resulting gate may not be exactly equivalent. If ignore_gphase is set, any global phase in the matrix is ignored, but the global phase of the non-control submatrix is not changed.

This function returns a new gate handle with the modified gate, or a copy of the input gate if the matrix could not be reduced. If the input gate does not have a matrix (measurement gate, or custom gate without matrix) an error is returned instead.

dqcs_gate_targets()

Returns a handle to a new qubit reference set containing the qubits targeted by this gate.

dqcs_handle_t dqcs_gate_targets(dqcs_handle_t gate)
dqcs_gate_type()

Returns the gate type of the given gate.

dqcs_gate_type_t dqcs_gate_type(dqcs_handle_t gate)

Returns DQCS_GATE_TYPE_INVALID if the gate handle is invalid.

dqcs_gate_type_t

Types of DQCsim gates.

typedef enum { ... } dqcs_gate_type_t;

Variants:

DQCS_GATE_TYPE_INVALID = 0
Invalid gate type. Used as an error return value.
DQCS_GATE_TYPE_UNITARY
Unitary gates have one or more target qubits, zero or more control qubits, and a unitary matrix, sized for the number of target qubits.

The semantics are that the unitary matrix expanded by the number of control qubits is applied to the qubits.

The data field may add pragma-like hints to the gate, for instance to represent the line number in the source file that generated the gate, error modelling information, and so on. This data may be silently ignored.

DQCS_GATE_TYPE_MEASUREMENT
Measurement gates have one or more measured qubits and a 2x2 unitary matrix representing the basis.

The semantics are:

  • the hermetian of the matrix is applied to each individual qubit;
  • each individual qubit is measured in the Z basis;
  • the matrix is applied to each individual qubit;
  • the results of the measurement are propagated upstream.

This allows any measurement basis to be used.

The data field may add pragma-like hints to the gate, for instance to represent the line number in the source file that generated the gate, error modelling information, and so on. This data may be silently ignored.

DQCS_GATE_TYPE_PREP
Prep gates have one or more target qubits and a 2x2 unitary matrix representing the basis.

The semantics are:

  • each qubit is initialized to |0>;
  • the matrix is applied to each individual qubit.

This allows any initial state to be used.

The data field may add pragma-like hints to the gate, for instance to represent the line number in the source file that generated the gate, error modelling information, and so on. This data may be silently ignored.

DQCS_GATE_TYPE_CUSTOM
Custom gates perform a user-defined mixed quantum-classical operation, identified by a name. They can have zero or more target, control, and measured qubits, of which only the target and control sets must be mutually exclusive. They also have an optional matrix of arbitrary size.

The semantics are:

  • if the name is not recognized, an error is reported;
  • a user-defined operation is performed based on the name, qubits, matrix, and data arguments;
  • exactly one measurement result is reported upstream for exactly the qubits in the measures set.

dqcs_gm_add_custom()

Adds a fully customizable gate mapping to the given gate map.

dqcs_return_t dqcs_gm_add_custom(
    dqcs_handle_t gm,
    void (*key_free)(void *key_data),
    void *key_data,
    dqcs_bool_return_t (*detector)(
        const void *user_data,
        dqcs_handle_t gate,
        dqcs_handle_t *qubits,
        dqcs_handle_t *param_data
    ),
    void (*detector_user_free)(void *user_data),
    void *detector_user_data,
    dqcs_handle_t (*constructor)(
        const void *user_data,
        dqcs_handle_t qubits,
        dqcs_handle_t param_data
    ),
    void (*constructor_user_free)(void *user_data),
    void *constructor_user_data
)

Note that this is the only type of mapping that can handle custom/named gates.

detector is the detector function pointer. It is optional; if null, this mapping only supports construction. detector_user_free is an optional callback function used to free detector_user_data when the gate map is destroyed, when this function fails, or when detector was null. detector_user_data is a user-specified value that is passed to the detector callback function. It is not used by DQCsim. constructor is the constructor function pointer. It is optional; if null, this mapping only supports detection. constructor_user_free is an optional callback function used to free constructor_user_data when the gate map is destroyed, when this function fails, or when constructor was null. constructor_user_data is a user-specified value that is passed to the constructor callback function. It is not used by DQCsim.

If both constructor and detector are null for some reason, the function is no-op (besides possibly calling the *_free() callbacks.

The detector callback receives the complete gate passed to the gate map for it to match as it pleases. If the gate matches, the detector function must return DQCS_TRUE. It may assign qubits to a qbset object representing the qubit arguments (substituted with an empty set if it doesn't), and may assign param_data to an arb handle with the parameterization data (if it doesn't, the data from the gate is used; if this was modified by the callback, the modified data is used). If the gate doesn't match, it must return DQCS_FALSE. If an error occurs, it must call dqcs_error_set() with the error message and return DQCS_BOOL_FAILURE.

The constructor callback performs the reverse operation. It receives an ArbData handle containing the parameterization data and a qubit set, and must construct a gate based on this information. If construction succeeds, the constructor function must return the gate handle. If an error occurs, it must call dqcs_error_set() with the error message and return 0.

It is up to the user how to do the matching and constructing, but the converter functions must always return the same value for the same input. In other words, they must be pure functions. Otherwise, the caching behavior of the GateMap will make the results inconsistent.

dqcs_gm_add_custom_unitary()

Adds a custom unitary gate mapping to the given gate map.

dqcs_return_t dqcs_gm_add_custom_unitary(
    dqcs_handle_t gm,
    void (*key_free)(void *key_data),
    void *key_data,
    dqcs_bool_return_t (*detector)(
        const void *user_data,
        dqcs_handle_t matrix,
        size_t num_controls,
        dqcs_handle_t *param_data
    ),
    void (*detector_user_free)(void *user_data),
    void *detector_user_data,
    dqcs_handle_t (*constructor)(
        const void *user_data,
        dqcs_handle_t *param_data,
        intptr_t *num_controls
    ),
    void (*constructor_user_free)(void *user_data),
    void *constructor_user_data
)

gm must be a handle to a gate map object (dqcs_gm_new()). key_free is an optional callback function used to free key_data when the gate map is destroyed, or when this function fails. key_data is the user-specified value used to identify this mapping. detector is the detector function pointer. It is optional; if null, this mapping only supports construction. detector_user_free is an optional callback function used to free detector_user_data when the gate map is destroyed, when this function fails, or when detector was null. detector_user_data is a user-specified value that is passed to the detector callback function. It is not used by DQCsim. constructor is the constructor function pointer. It is optional; if null, this mapping only supports detection. constructor_user_free is an optional callback function used to free constructor_user_data when the gate map is destroyed, when this function fails, or when constructor was null. constructor_user_data is a user-specified value that is passed to the constructor callback function. It is not used by DQCsim.

If both constructor and detector are null for some reason, the function is no-op (besides possibly calling the *_free() callbacks.

The detector callback receives a matrix and control qubit information for the user to match. The matrix is passed through the matrix handle. num_controls is passed the number of explicit control qubits that exist besides the matrix (that is, if nonzero, the matrix is actually only the non-controlled submatrix of the controlled gate). param_data is given an ArbData handle initialized with the ArbData attached to the gate. If the gate matches, the detector function must return DQCS_TRUE. In this case, it can mutate the param_data to add the detected gate parameters. If it doesn't match, it must return DQCS_FALSE. If an error occurs, it must call dqcs_error_set() with the error message and return DQCS_BOOL_FAILURE.

The constructor callback performs the reverse operation. It receives an ArbData handle containing the parameterization data, and must construct the matrix, return the bound on the number of control qubits, and must return the ArbData associated with the gate by mutating the param_data handle. num_controls will point to a variable initialized to -1 representing a constraint on the number of control qubits. This works as follows: if negative, any number of qubits is allowed; if zero or positive, only that number is allowed. If construction succeeds, the constructor function must return a handle to the constructed matrix. If it fails, it must call dqcs_error_set() with an error message and return 0.

It is up to the user how to do the matching and constructing, but the converter functions must always return the same value for the same input. In other words, they must be pure functions. Otherwise, the caching behavior of the GateMap will make the results inconsistent.

dqcs_gm_add_fixed_unitary()

Adds a unitary gate mapping for the given gate matrix to the given gate map.

dqcs_return_t dqcs_gm_add_fixed_unitary(
    dqcs_handle_t gm,
    void (*key_free)(void *key_data),
    void *key_data,
    dqcs_handle_t matrix,
    intptr_t num_controls,
    double epsilon,
    bool ignore_gphase
)

gm must be a handle to a gate map object (dqcs_gm_new()). key_free is an optional callback function used to free key_data when the gate map is destroyed, or when this function fails. key_data is the user-specified value used to identify this mapping. matrix must be passed a handle to the matrix to detect. It is consumed by this function. num_controls specifies the number of control qubits associated with this gate type. If negative, the gate can have any number of control qubits. If zero or positive, the number of control qubits must be as specified. epsilon specifies the maximum element-wise root-mean-square error between the incoming matrix and the to be detected matrix that results in a positive match. ignore_phase specifies whether the aforementioned check should ignore global phase or not when there are no explicit control qubits.

The parameterization ArbData object returned by detection and consumed by construction is mapped one-to-one to the user data of the gate in the DQCsim-protocol.

dqcs_gm_add_measure()

Adds a measurement gate mapping to the given gate map.

dqcs_return_t dqcs_gm_add_measure(
    dqcs_handle_t gm,
    void (*key_free)(void *user_data),
    void *key_data,
    intptr_t num_measures,
    dqcs_handle_t basis,
    double epsilon
)

gm must be a handle to a gate map object (dqcs_gm_new()). key_free is an optional callback function used to free key_data when the gate map is destroyed, or when this function fails. key_data is the user-specified value used to identify this mapping. num_measures specifies the number of measured qubits for this gate type. If negative, the gate can have any number of measured qubits. If zero or positive, the number of measured qubits must be as specified. basis optionally specifies a handle to a 2x2 matrix specifying the measurement basis to be detected. If not specified, the Z basis is used. The matrix is deleted by the call iff the function succeeds. epsilon specifies the maximum RMS deviation between the specified basis (if any) and the incoming basis.

The parameterization ArbData object returned by detection and consumed by construction is mapped one-to-one to the user data of the gate in the DQCsim-protocol.

dqcs_gm_add_predef_unitary()

Adds a unitary gate mapping for the given DQCsim-defined gate to the given gate map.

dqcs_return_t dqcs_gm_add_predef_unitary(
    dqcs_handle_t gm,
    void (*key_free)(void *user_data),
    void *key_data,
    dqcs_predefined_gate_t gate,
    intptr_t num_controls,
    double epsilon,
    bool ignore_gphase
)

gm must be a handle to a gate map object (dqcs_gm_new()). key_free is an optional callback function used to free key_data when the gate map is destroyed, or when this function fails. key_data is the user-specified value used to identify this mapping. gate defines which predefined gate to use. Some of the predefined gates are parameterized. num_controls specifies the number of control qubits associated with this gate type. If negative, the gate can have any number of control qubits. If zero or positive, the number of control qubits must be as specified. epsilon specifies the maximum element-wise root-mean-square error between the incoming matrix and the to be detected matrix that results in a positive match. ignore_phase specifies whether the aforementioned check should ignore global phase or not when there are no explicit control qubits.

For most gate types, the parameterization ArbData object returned by detection and consumed by construction is mapped one-to-one to the user data of the gate in the DQCsim-protocol. Some of the detectors however detect parameterized gate matrices. These detectors prefix a fixed number of binary string arguments to the ArbData upon detection, and pop these when constructing. The specs for this can be found in the docs for dqcs_predefined_gate_t.

dqcs_gm_add_prep()

Adds a prep gate mapping to the given gate map.

dqcs_return_t dqcs_gm_add_prep(
    dqcs_handle_t gm,
    void (*key_free)(void *user_data),
    void *key_data,
    intptr_t num_targets,
    dqcs_handle_t basis,
    double epsilon
)

gm must be a handle to a gate map object (dqcs_gm_new()). key_free is an optional callback function used to free key_data when the gate map is destroyed, or when this function fails. key_data is the user-specified value used to identify this mapping. num_targets specifies the number of target qubits for this gate type. If negative, the gate can have any number of targets. If zero or positive, the number of target qubits must be as specified. basis optionally specifies a handle to a 2x2 matrix specifying the prep basis. If not specified, the Z basis is used. The matrix is deleted by the call iff the function succeeds. epsilon specifies the maximum RMS deviation between the specified basis (if any) and the incoming basis.

The parameterization ArbData object returned by detection and consumed by construction is mapped one-to-one to the user data of the gate in the DQCsim-protocol.

dqcs_gm_construct()

Uses a gate map object to construct a multi-qubit DQCsim gate from the plugin's representation.

dqcs_handle_t dqcs_gm_construct(
    dqcs_handle_t gm,
    const void *key_data,
    dqcs_handle_t qubits,
    dqcs_handle_t param_data
)

gm must be a handle to a gate map object (dqcs_mm_new()). gate must be a handle to a gate. The handle is borrowed; it is not mutated or deleted. key_data specifies the gate mapping key for the constructor to use. Note that the pointer must match exactly to what was specified when the mapping(s) was/were added. qubits specifies the qubits arguments for the constructed gate. It is up to the constructor function to determine how to interpret these. The parameter is optional; passing 0 is equivalent to passing an empty qubit set. The handle is deleted if the function succeeds. param_data specifies the ArbData object used to parameterize the gate. It is optional; if 0, an empty ArbData is automatically constructed by DQCsim. The handle is deleted if the function succeeds.

This function returns the handle to the gate, or 0 to indicate failure. The qubit set and parameterization data (if specified) are consumed/deleted by this function if and only if it succeeds.

dqcs_gm_construct_one()

Uses a gate map object to construct a one-qubit DQCsim gate from the plugin's representation.

dqcs_handle_t dqcs_gm_construct_one(
    dqcs_handle_t gm,
    const void *key_data,
    dqcs_qubit_t qa,
    dqcs_handle_t param_data
)

This function is simply a shorthand for dqcs_gm_construct() with one qubit in the qubits set, to make constructing one-qubit gates more ergonomic. Refer to its documentation for more information.

dqcs_gm_construct_three()

Uses a gate map object to construct a three-qubit DQCsim gate from the plugin's representation.

dqcs_handle_t dqcs_gm_construct_three(
    dqcs_handle_t gm,
    const void *key_data,
    dqcs_qubit_t qa,
    dqcs_qubit_t qb,
    dqcs_qubit_t qc,
    dqcs_handle_t param_data
)

This function is simply a shorthand for dqcs_gm_construct() with three qubits in the qubits set, to make constructing three-qubit gates more ergonomic. Refer to its documentation for more information.

dqcs_gm_construct_two()

Uses a gate map object to construct a two-qubit DQCsim gate from the plugin's representation.

dqcs_handle_t dqcs_gm_construct_two(
    dqcs_handle_t gm,
    const void *key_data,
    dqcs_qubit_t qa,
    dqcs_qubit_t qb,
    dqcs_handle_t param_data
)

This function is simply a shorthand for dqcs_gm_construct() with two qubits in the qubits set, to make constructing two-qubit gates more ergonomic. Refer to its documentation for more information.

dqcs_gm_detect()

Uses a gate map object to convert an incoming DQCsim gate to the plugin's representation.

dqcs_bool_return_t dqcs_gm_detect(
    dqcs_handle_t gm,
    dqcs_handle_t gate,
    const void **key_data,
    dqcs_handle_t *qubits,
    dqcs_handle_t *param_data
)

gm must be a handle to a gate map object (dqcs_mm_new()). gate must be a handle to a gate. The handle is borrowed; it is not mutated or deleted. key_data serves as an optional return value; if non-NULL and a match is found, the key_data specified when the respective detector was added is returned here as a const void *. If no match is found, *key_data is not assigned. qubits serves as an optional return value; if non-NULL and a match is found, it is set to a handle to a new QubitSet object representing the gate's qubits. Ownership of this handle is passed to the user, so it is up to the user to eventually delete it. If no match is found, *qubits is set to 0. param_data serves as an optional return value; if non-NULL and a match is found, it is set to a handle to a new ArbData object representing the gate's parameters. Ownership of this handle is passed to the user, so it is up to the user to eventually delete it. If no match is found, *param_data is set to 0.

This function returns DQCS_TRUE if a match was found, DQCS_FALSE if no match was found, or DQCS_BOOL_FAILURE if an error occurs.

dqcs_gm_new()

Constructs a new gate map.

dqcs_handle_t dqcs_gm_new(
    bool strip_qubit_refs,
    bool strip_data,
    bool (*key_cmp)(
        const void,
        const void
    ),
    uint64_t (*key_hash)(const void)
)

Returns a handle to a gate map with no mappings attached to it yet. Use dqcs_gm_add_*() to do that. The mappings are queried in the order in which they are added, so be sure to add more specific gates first. Once added, use dqcs_gm_detect() to detect incoming DQCsim gates, and dqcs_gm_construct*() to (re)construct gates for transmission.

Gate maps objects retain a cache to speed up detection of similar DQCsim gates: if a gate is received for the second time, the cache will hit, avoiding recomputation of the detector functions. What constitutes "similar gates" is defined by the two booleans passed to this function. If strip_qubit_refs is set, all qubit references associated with the gate will be invalidated (i.e., set to 0), such that for instance an X gate applied to qubit 1 will be considered equal to an X gate applied to qubit 2. If strip_data is set, the ArbData associated with the incoming gate is removed.

Gates are identified through user-defined void* keys. To do the above, however, DQCsim needs to know the following things:

  • how to delete an owned copy of a key if your semantics are that DQCsim owns it,
  • how to compare two keys (equality);
  • how to hash a key.

The deletion function is passed when the key is passed. If the keys are objects of different classes, this allows different constructors to be passed here. There can only be a single comparison and hash function for each gate map, though. They are passed here.

key_cmp represents this comparison function. It takes two void* to keys and must returns whether they are equal or not. If not specified, the default is to compare the pointers themselves, instead of the values they refer to. key_cmp must be a pure function, i.e., depend only on its input values.

key_hash represents the hashing function. It takes a void* key and returns a 64-bit hash representative of the key. For any pair of keys for which key_cmp returns true, the hashes must be equal. The default behavior depends on whether key_cmp is defined: if it is, all keys will have the same hash; if it isn't, the pointer is itself hashed. key_hash must be a pure function, i.e., depend only on its input values.

It is recommended to first preprocess incoming gates with dqcs_gate_reduce_control(). In this case, controlled unitary gate matrices will be reduced to their non-controlled submatrix, such that the unitary gate detectors will operate on said submatrix. The predefined unitary gate detectors are more-or-less based on this assumption (as there are no predefined controlled matrices).

Alternatively, you can preprocess with dqcs_gate_expand_control(). In this case, you can use dqcs_gm_add_fixed_unitary() to detect the full matrix in all cases, by specifying the CNOT matrix instead of an X matrix with one control qubit.

If you don't preprocess, the upstream plugin determines the representation. That is, it may send a CNOT as a two-qubit gate with a CNOT matrix or as a controlled X gate with a single target and single control qubit. The gate map will then detect these as two different kinds of gates.

dqcs_handle_delete()

Destroys the object associated with a handle.

dqcs_return_t dqcs_handle_delete(dqcs_handle_t handle)

Returns 0 when successful, -1 otherwise.

dqcs_handle_delete_all()

Deletes all handles for the current thread.

dqcs_return_t dqcs_handle_delete_all(void)

This can be used to clean stuff up at the end of main() or before an abort() of some kind. If you don't clean up properly, you might get undefined behavior or errors when DQCsim tries to do it for you.

dqcs_handle_dump()

Returns a debug dump of the object associated with the given handle.

char *dqcs_handle_dump(dqcs_handle_t handle)

On success, this returns a newly allocated string containing the description. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_handle_leak_check()

Succeeds only if there are no live handles in the current thread.

dqcs_return_t dqcs_handle_leak_check(void)

This is intended for testing and for finding handle leaks. The error message returned when handles remain contains dumps of the first 10 remaining handles.

dqcs_handle_t

Type for a handle.

typedef unsigned long long dqcs_handle_t;

Handles are like pointers into DQCsim's internal structures: all API calls use these to refer to objects. Besides the object, they contain type information. This type can be retrieved using dqcs_handle_type().

Handles are always positive integers, counting upwards from 1 upon allocation, and they are not reused even after being deleted. Thus, every subsequent object allocation returns a handle one greater than the previous. Note however that DQCsim may allocate objects as well without the user specifically requesting this, so external code should generally not rely on this behavior unless otherwise noted. The value zero is reserved for invalid references or error propagation.

Note that the scope for handles is thread-local. That is, data referenced by a handle cannot be shared or moved between threads.

The value zero is reserved for invalid references or error propagation.

dqcs_handle_type()

Returns the type of object associated with the given handle.

dqcs_handle_type_t dqcs_handle_type(dqcs_handle_t handle)
dqcs_handle_type_t

Enumeration of types that can be associated with a handle.

typedef enum { ... } dqcs_handle_type_t;

Variants:

DQCS_HTYPE_INVALID = 0
Indicates that the given handle is invalid.

This indicates one of the following:

  • The handle value is invalid (zero or negative).
  • The handle has not been used yet.
  • The object associated with the handle was deleted.
DQCS_HTYPE_ARB_DATA = 100
Indicates that the given handle belongs to an `ArbData` object.

This means that the handle supports the handle and arb interfaces.

DQCS_HTYPE_ARB_CMD = 101
Indicates that the given handle belongs to an `ArbCmd` object.

This means that the handle supports the handle, arb, and cmd interfaces.

DQCS_HTYPE_ARB_CMD_QUEUE = 102
Indicates that the given handle belongs to a queue of `ArbCmd` object.

This means that the handle supports the handle, arb, cmd, and cq interfaces.

DQCS_HTYPE_QUBIT_SET = 103
Indicates that the given handle belongs to a set of qubit references.

This means that the handle supports the handle and qbset interfaces.

DQCS_HTYPE_GATE = 104
Indicates that the given handle belongs to a quantum gate description.

This means that the handle supports the handle, gate, and arb interfaces.

DQCS_HTYPE_MEAS = 105
Indicates that the given handle belongs to a qubit measurement result.

This means that the handle supports the handle, meas, and arb interfaces. It can also be used in place of a qubit measurement result set by functions that consume the object.

DQCS_HTYPE_MEAS_SET = 106
Indicates that the given handle belongs to a set of qubit measurement results.

This means that the handle supports the handle and mset interfaces.

DQCS_HTYPE_MATRIX = 107
Indicates that the given handle belongs to a matrix.

This means that the handle supports the handle and mat interfaces.

DQCS_HTYPE_GATE_MAP = 108
Indicates that the given handle belongs to a gate map.

This means that the handle supports the handle and gm interfaces.

DQCS_HTYPE_FRONT_PROCESS_CONFIG = 200
Indicates that the given handle belongs to a frontend plugin process configuration object.

This means that the handle supports the handle, pcfg, and xcfg interfaces.

DQCS_HTYPE_OPER_PROCESS_CONFIG = 201
Indicates that the given handle belongs to an operator plugin process configuration object.

This means that the handle supports the handle, pcfg, and xcfg interfaces.

DQCS_HTYPE_BACK_PROCESS_CONFIG = 203
Indicates that the given handle belongs to a backend plugin process configuration object.

This means that the handle supports the handle, pcfg, and xcfg interfaces.

DQCS_HTYPE_FRONT_THREAD_CONFIG = 204
Indicates that the given handle belongs to a frontend plugin thread configuration object.

This means that the handle supports the handle, tcfg, and xcfg interfaces.

DQCS_HTYPE_OPER_THREAD_CONFIG = 205
Indicates that the given handle belongs to an operator plugin thread configuration object.

This means that the handle supports the handle, tcfg, and xcfg interfaces.

DQCS_HTYPE_BACK_THREAD_CONFIG = 206
Indicates that the given handle belongs to a backend plugin thread configuration object.

This means that the handle supports the handle, tcfg, and xcfg interfaces.

DQCS_HTYPE_SIM_CONFIG = 207
Indicates that the given handle belongs to a simulator configuration object.

This means that the handle supports the handle and scfg interfaces.

DQCS_HTYPE_SIM = 208
Indicates that the given handle belongs to a simulator instance.

This means that the handle supports the handle and sim interfaces.

DQCS_HTYPE_FRONT_DEF = 300
Indicates that the given handle belongs to a frontend plugin definition object.

This means that the handle supports the handle and pdef interfaces.

DQCS_HTYPE_OPER_DEF = 301
Indicates that the given handle belongs to an operator plugin definition object.

This means that the handle supports the handle and pdef interfaces.

DQCS_HTYPE_BACK_DEF = 302
Indicates that the given handle belongs to a backend plugin definition object.

This means that the handle supports the handle and pdef interfaces.

DQCS_HTYPE_PLUGIN_JOIN = 303
Indicates that the given handle belongs to a plugin thread join handle.

This means that the handle supports the handle and pjoin interfaces.

dqcs_log_debug()

Convenience macro for calling dqcs_log_format() with debug loglevel and automatically determined function name, filename, and line number.

#define dqcs_log_debug(fmt, ...)                \
  dqcs_log_format(                              \
    _DQCSIM_LOGLEVEL_PREFIX_ DQCS_LOG_DEBUG,    \
    _DQCSIM_LANGUAGE_,                          \
    __FILE__,                                   \
    __LINE__,                                   \
    fmt,                                        \
    ##__VA_ARGS__                               \
  )
dqcs_log_error()

Convenience macro for calling dqcs_log_format() with error loglevel and automatically determined function name, filename, and line number.

#define dqcs_log_error(fmt, ...)                \
  dqcs_log_format(                              \
    _DQCSIM_LOGLEVEL_PREFIX_ DQCS_LOG_ERROR,    \
    _DQCSIM_LANGUAGE_,                          \
    __FILE__,                                   \
    __LINE__,                                   \
    fmt,                                        \
    ##__VA_ARGS__                               \
  )
dqcs_log_fatal()

Convenience macro for calling dqcs_log_format() with fatal loglevel and automatically determined function name, filename, and line number.

#define dqcs_log_fatal(fmt, ...)                \
  dqcs_log_format(                              \
    _DQCSIM_LOGLEVEL_PREFIX_ DQCS_LOG_FATAL,    \
    _DQCSIM_LANGUAGE_,                          \
    __FILE__,                                   \
    __LINE__,                                   \
    fmt,                                        \
    ##__VA_ARGS__                               \
  )
dqcs_log_format()

Sends a log message using the current logger using printf-like formatting.

static void dqcs_log_format(
    dqcs_loglevel_t level,
    const char *module,
    const char *file,
    uint32_t line,
    const char *fmt,
    ...
)

This function is identical to dqcs_log_raw(), except instead of a single string it takes a printf-like format string and varargs to compose the message.

dqcs_log_info()

Convenience macro for calling dqcs_log_format() with info loglevel and automatically determined function name, filename, and line number.

#define dqcs_log_info(fmt, ...)                 \
  dqcs_log_format(                              \
    _DQCSIM_LOGLEVEL_PREFIX_ DQCS_LOG_INFO,     \
    _DQCSIM_LANGUAGE_,                          \
    __FILE__,                                   \
    __LINE__,                                   \
    fmt,                                        \
    ##__VA_ARGS__                               \
  )
dqcs_log_note()

Convenience macro for calling dqcs_log_format() with note loglevel and automatically determined function name, filename, and line number.

#define dqcs_log_note(fmt, ...)                 \
  dqcs_log_format(                              \
    _DQCSIM_LOGLEVEL_PREFIX_ DQCS_LOG_NOTE,     \
    _DQCSIM_LANGUAGE_,                          \
    __FILE__,                                   \
    __LINE__,                                   \
    fmt,                                        \
    ##__VA_ARGS__                               \
  )
dqcs_log_raw()

Primitive API for sending a log message using the current logger.

dqcs_return_t dqcs_log_raw(
    dqcs_loglevel_t level,
    const char *module,
    const char *file,
    uint32_t line_nr,
    const char *message
)

Returns DQCS_SUCCESS if logging was successful, or DQCS_FAILURE if no logger is available in the current thread or one of the arguments could not be converted. Loggers are available in the simulation host thread and in threads running plugins.

Formatting and fallback to stderr

As an alternative to this function, you can also use dqcs_log_format(). This function differs from dqcs_log_raw() in two ways:

  • Instead of the message string, a printf-style format string and associated varargs are passed to construct the message.
  • When logging fails, this function falls back to writing to stderr instead of returning the errors.

Macros

From C and C++, these functions are normally not called directly. Instead, the following macros are used:

dqcs_log_trace("trace message!");
dqcs_log_debug("debug message!");
dqcs_log_info("info message!");
dqcs_log_note("notice!");
dqcs_log_warn("warning!");
dqcs_log_error("error!");
dqcs_log_fatal("fatal error!");

These macros automatically set file to the C source filename and line to the line number. module is hardcoded to "C" or "CPP" depending on source file language. They use dqcs_log_format(), so they also support printf-style formatting. For instance:

dqcs_note("answer to %s: %d", "ultimate question", 42);
dqcs_log_trace()

Convenience macro for calling dqcs_log_format() with trace loglevel and automatically determined function name, filename, and line number.

#define dqcs_log_trace(fmt, ...)                \
  dqcs_log_format(                              \
    _DQCSIM_LOGLEVEL_PREFIX_ DQCS_LOG_TRACE,    \
    _DQCSIM_LANGUAGE_,                          \
    __FILE__,                                   \
    __LINE__,                                   \
    fmt,                                        \
    ##__VA_ARGS__                               \
  )
dqcs_log_warn()

Convenience macro for calling dqcs_log_format() with warn loglevel and automatically determined function name, filename, and line number.

#define dqcs_log_warn(fmt, ...)                 \
  dqcs_log_format(                              \
    _DQCSIM_LOGLEVEL_PREFIX_ DQCS_LOG_WARN,     \
    _DQCSIM_LANGUAGE_,                          \
    __FILE__,                                   \
    __LINE__,                                   \
    fmt,                                        \
    ##__VA_ARGS__                               \
  )
dqcs_loglevel_t

Enumeration of loglevels and logging modes.

typedef enum { ... } dqcs_loglevel_t;

Variants:

DQCS_LOG_INVALID = -1
Invalid loglevel. Used to indicate failure of an API that returns a loglevel.
DQCS_LOG_OFF = 0
Turns logging off.
DQCS_LOG_FATAL = 1
This loglevel is to be used for reporting a fatal error, resulting from the owner of the logger getting into an illegal state from which it cannot recover. Such problems are also reported to the API caller via Result::Err if applicable.
DQCS_LOG_ERROR = 2
This loglevel is to be used for reporting or propagating a non-fatal error caused by the API caller doing something wrong. Such problems are also reported to the API caller via Result::Err if applicable.
DQCS_LOG_WARN = 3
This loglevel is to be used for reporting that a called API/function is telling us we did something wrong (that we weren't expecting), but we can recover. For instance, for a failed connection attempt to something that really should not be failing, we can still retry (and eventually report critical or error if a retry counter overflows). Since we're still trying to rectify things at this point, such problems are NOT reported to the API/function caller via Result::Err.
DQCS_LOG_NOTE = 4
This loglevel is to be used for reporting information specifically requested by the user/API caller, such as the result of an API function requested through the command line, or an explicitly captured stdout/stderr stream.
DQCS_LOG_INFO = 5
This loglevel is to be used for reporting information NOT specifically requested by the user/API caller, such as a plugin starting up or shutting down.
DQCS_LOG_DEBUG = 6
This loglevel is to be used for reporting debugging information useful for debugging the user of the API provided by the logged instance.
DQCS_LOG_TRACE = 7
This loglevel is to be used for reporting debugging information useful for debugging the internals of the logged instance. Such messages would normally only be generated by debug builds, to prevent them from impacting performance under normal circumstances.
DQCS_LOG_PASS = 8
This is intended to be used when configuring the stdout/stderr capture mode for a plugin process. Selecting it will prevent the stream from being captured; it will just be the same stream as DQCsim's own stdout/stderr. When used as the loglevel for a message, the message itself is sent to stderr instead of passing into DQCsim's log system. Using this for loglevel filters leads to undefined behavior.

dqcs_mat_add_controls()

Constructs a controlled matrix from the given matrix.

dqcs_handle_t dqcs_mat_add_controls(
    dqcs_handle_t mat,
    size_t number_of_controls
)

mat specifies the matrix to use as the non-controlled submatrix. This is a borrowed handle. number_of_controls specifies the number of control qubits to add. This function returns a new matrix handle with the constructed matrix, or 0 if it fails.

dqcs_mat_approx_eq()

Approximately compares two matrices.

dqcs_bool_return_t dqcs_mat_approx_eq(
    dqcs_handle_t a,
    dqcs_handle_t b,
    double epsilon,
    bool ignore_gphase
)

a and b are borrowed matrix handles. epsilon specifies the maximum element-wise root-mean-square error between the matrices that results in a positive match. ignore_gphase specifies whether the check should ignore global phase.

If ignore_gphase is set, this checks that the following holds for some x:

\[ A \cdot e^{ix} \approx B \]

This function returns DQCS_TRUE if the matrices match according to the aforementioned criteria, or DQCS_FALSE if not. DQCS_BOOL_ERROR is used when either handle is invalid or not a matrix. If the matrices differ in dimensionality, DQCS_FALSE is used.

dqcs_mat_approx_unitary()

Returns whether the matrix is approximately unitary.

dqcs_bool_return_t dqcs_mat_approx_unitary(
    dqcs_handle_t matrix,
    double epsilon
)

matrix is a borrowed handle to the matrix to check. epsilon specifies the maximum element-wise root-mean-square error between the product of the matrix and its hermetian compared to the identity matrix.

This function returns DQCS_TRUE if the matrix is approximately unitary, or DQCS_FALSE if not. DQCS_BOOL_ERROR is used when either handle is invalid or not a matrix.

dqcs_mat_basis()

Constructs a matrix with the eigenvectors of one of the Pauli matrices as column vectors.

dqcs_handle_t dqcs_mat_basis(dqcs_basis_t basis)

This can be used for constructing measurement or prep gates with the given basis. Returns a new handle to the constructed matrix or returns 0 if an error occurs.

dqcs_mat_basis_approx_eq()

Approximately compares two basis matrices.

dqcs_bool_return_t dqcs_mat_basis_approx_eq(
    dqcs_handle_t a,
    dqcs_handle_t b,
    double epsilon
)

a and b are borrowed matrix handles. epsilon specifies the maximum element-wise root-mean-square error between the matrices that results in a positive match.

This checks that the following holds for some x and y:

\[ A \cdot \begin{bmatrix} e^{ix} & 0 \\ 0 & e^{iy} \end{bmatrix} \approx B \]

This function returns DQCS_TRUE if the matrices match according to the aforementioned criteria, or DQCS_FALSE if not. DQCS_BOOL_ERROR is used when either handle is invalid or not a matrix. If either matrix is not 2x2, DQCS_FALSE is used.

dqcs_mat_dimension()

Returns the dimension (number of rows == number of columns) of the given matrix.

ssize_t dqcs_mat_dimension(dqcs_handle_t mat)

This function returns -1 when an error occurs.

dqcs_mat_get()

Returns a copy of the contained matrix as a C array.

double *dqcs_mat_get(dqcs_handle_t mat)

If this function succeeds, the matrix is returned in row-major form, using pairs of doubles for the real vs. imaginary component of each entry. The size will be 4**num_qubits complex numbers = 2*4**num_qubits doubles = 16*4**num_qubits bytes. A newly allocated matrix is returned; free it with free() when you're done with it to avoid memory leaks. On failure, this function returns NULL.

dqcs_mat_is_predef()

Returns whether this matrix is of the given predefined form and, if it is, any parameters needed to describe it.

dqcs_bool_return_t dqcs_mat_is_predef(
    dqcs_handle_t mat,
    dqcs_predefined_gate_t gate_type,
    dqcs_handle_t *param_data,
    double epsilon,
    bool ignore_gphase
)

mat is a borrowed handle to the matrix to check. gate_type specifies which kind of gate should be detected. param_data, if non-null, receives a new ArbData handle with parameterization data, or an empty ArbData if the gate is not parameterized; the caller must delete this object when it is done with it. This function always writes the 0 handle to this return parameter if it fails. The ArbData representation can be found in the documentation for dqcs_predefined_gate_t.

epsilon specifies the maximum element-wise root-mean-square error between the matrices that results in a positive match. ignore_gphase specifies whether the check should ignore global phase.

This function returns DQCS_TRUE if the matrices match according to the aforementioned criteria, or DQCS_FALSE if not. DQCS_BOOL_ERROR is used when either handle is invalid or not a matrix. If the matrices differ in dimensionality, DQCS_FALSE is used.

dqcs_mat_len()

Returns the number of complex entries in the given matrix.

ssize_t dqcs_mat_len(dqcs_handle_t mat)

This function returns -1 when an error occurs.

dqcs_mat_new()

Constructs a new gate matrix.

dqcs_handle_t dqcs_mat_new(
    size_t num_qubits,
    const double *matrix
)

num_qubits must be set to the number of qubits mutated by this matrix. It must be greater than or equal to zero. matrix must point to an appropriately sized array of doubles. The matrix is specified in row-major form, using pairs of doubles for the real vs. imaginary component of each entry. The size must be 4**num_qubits complex numbers = 2*4**num_qubits doubles = 16*4**num_qubits bytes, representing a 2**num_qubits by 2**num_qubits matrix. This function returns the constructed matrix handle, or 0 if an error occurs.

While not enforced at this level, the matrix is normally unitary, or approximately so within some floating-point error margin.

This function returns the handle to the matrix, or 0 to indicate failure.

dqcs_mat_num_qubits()

Returns the number of qubits targeted by the given matrix.

ssize_t dqcs_mat_num_qubits(dqcs_handle_t mat)

This function returns -1 when an error occurs.

dqcs_mat_predef()

Constructs a new gate matrix for one of DQCsim's predefined gates.

dqcs_handle_t dqcs_mat_predef(
    dqcs_predefined_gate_t gate_type,
    dqcs_handle_t param_data
)

gate_type specifies which kind of gate should be constructed.

param_data takes an optional ArbData object used to parameterize the matrix if necessary. If not specified, an empty object is used. The ArbData representation for each gate can be found in the docs for dqcs_predefined_gate_t. If nothing is specified, no ArbData is used.

This function returns the handle to the matrix, or 0 to indicate failure. The parameterization data (if specified) is consumed/deleted by this function if and only if it succeeds.

dqcs_mat_strip_control()

Splits a controlled matrix into its non-controlled submatrix and the indices of the control qubits.

dqcs_handle_t dqcs_mat_strip_control(
    dqcs_handle_t mat,
    double epsilon,
    bool ignore_global_phase,
    ssize_t **control_indices
)

mat specifies the matrix to modify. This is a borrowed handle. epsilon specifies the maximum magitude of the difference between the column vectors of the input matrix and the identity matrix (after dephasing if ignore_gphase is set) for the column vector to be considered to not affect the respective entry in the quantum state vector. Note that if this is greater than zero, the resulting gate may not be exactly equivalent. If ignore_global_phase is set, any global phase in the matrix is ignored, but note that if control qubits are stripped the "global" phase of the resulting submatrix is always significant. control_indices is a return argument through which DQCsim will pass the indices of the qubits that were removed in the process of constructing the submatrix. This is represented as an array of indices terminated by a -1 entry. The returned matrix must be freed using free() when you are done with it to avoid memory leaks. This function returns a new matrix handle with the submatrix, or 0 if it fails. In this case, control_indices is not mutated.

This function assumes that the incoming matrix is unitary (within epsilon) without verifying that this is the case. The results may thus be invalid if it was not.

dqcs_meas_new()

Constructs a new measurement object.

dqcs_handle_t dqcs_meas_new(
    dqcs_qubit_t qubit,
    dqcs_measurement_t value
)

qubit must be set to the qubit that was measured, value must be set to its value. The return value is the handle to the measurement object, or 0 if something went wrong.

Note that measurement objects implement the arb interface, so additional data can be attached to the object.

dqcs_meas_qubit_get()

Returns the qubit reference associated with a measurement object.

dqcs_qubit_t dqcs_meas_qubit_get(dqcs_handle_t meas)
dqcs_meas_qubit_set()

Sets the qubit reference associated with a measurement object.

dqcs_return_t dqcs_meas_qubit_set(
    dqcs_handle_t meas,
    dqcs_qubit_t qubit
)
dqcs_meas_value_get()

Returns the measurement value associated with a measurement object.

dqcs_measurement_t dqcs_meas_value_get(dqcs_handle_t meas)
dqcs_meas_value_set()

Sets the measurement value associated with a measurement object.

dqcs_return_t dqcs_meas_value_set(
    dqcs_handle_t meas,
    dqcs_measurement_t value
)
dqcs_measurement_t

Qubit measurement value.

typedef enum { ... } dqcs_measurement_t;

Variants:

DQCS_MEAS_INVALID = -1
Error value used to indicate that something went wrong.
DQCS_MEAS_ZERO = 0
Indicates that the qubit was measured to be zero.
DQCS_MEAS_ONE = 1
Indicates that the qubit was measured to be one.
DQCS_MEAS_UNDEFINED = 2
Indicates that the measurement value is unknown for whatever reason.

dqcs_mset_contains()

Returns whether the given qubit measurement set contains data for the given qubit.

dqcs_bool_return_t dqcs_mset_contains(
    dqcs_handle_t mset,
    dqcs_qubit_t qubit
)
dqcs_mset_get()

Returns a copy of the measurement result for the given qubit from a measurement result set.

dqcs_handle_t dqcs_mset_get(
    dqcs_handle_t mset,
    dqcs_qubit_t qubit
)
dqcs_mset_len()

Returns the number of qubits measurements in the given measurement set.

ssize_t dqcs_mset_len(dqcs_handle_t mset)

This function returns -1 to indicate failure.

dqcs_mset_new()

Creates a new set of qubit measurement results.

dqcs_handle_t dqcs_mset_new(void)

Returns the handle of the newly created set. The set is initially empty.

dqcs_mset_remove()

Removes the measurement result for the given qubit from a measurement result set.

dqcs_return_t dqcs_mset_remove(
    dqcs_handle_t mset,
    dqcs_qubit_t qubit
)
dqcs_mset_set()

Adds a measurement result to a measurement result set.

dqcs_return_t dqcs_mset_set(
    dqcs_handle_t mset,
    dqcs_handle_t meas
)

If there was already a measurement for the specified qubit, the previous measurement result is overwritten. The measurement result object is deleted if and only if the function succeeds.

dqcs_mset_take()

Returns the measurement result for the given qubit from a measurement result set and removes it from the set.

dqcs_handle_t dqcs_mset_take(
    dqcs_handle_t mset,
    dqcs_qubit_t qubit
)
dqcs_mset_take_any()

Returns the measurement result for any of the qubits contained in a measurement result set and removes it from the set.

dqcs_handle_t dqcs_mset_take_any(dqcs_handle_t mset)

This is useful for iteration.

dqcs_path_style_t

Reproduction file path style.

typedef enum { ... } dqcs_path_style_t;

Variants:

DQCS_PATH_STYLE_INVALID = -1
Error value used to indicate that something went wrong.
DQCS_PATH_STYLE_KEEP = 0
Specifies that paths should be saved the same way they were specified on the command line.
DQCS_PATH_STYLE_RELATIVE = 1
Specifies that all paths should be saved relative to DQCsim's working directory.
DQCS_PATH_STYLE_ABSOLUTE = 2
Specifies that all paths should be saved canonically, i.e. relative to the root directory.

dqcs_pcfg_accept_timeout_get()

Returns the configured timeout for the plugin process to connect to DQCsim.

double dqcs_pcfg_accept_timeout_get(dqcs_handle_t pcfg)

The time unit is in seconds. Returns positive inifinity for an infinite timeout. Returns -1 when the function fails.

dqcs_pcfg_accept_timeout_set()

Configures the timeout for the plugin process to connect to DQCsim.

dqcs_return_t dqcs_pcfg_accept_timeout_set(
    dqcs_handle_t pcfg,
    double timeout
)

The default is 5 seconds, so you should normally be able to leave this alone.

The time unit is seconds. Use IEEE positive infinity to specify an infinite timeout.

dqcs_pcfg_env_set()

Overrides an environment variable for the plugin process.

dqcs_return_t dqcs_pcfg_env_set(
    dqcs_handle_t pcfg,
    const char *key,
    const char *value
)

The environment variable key is set to value regardless of whether it exists in the parent environment variable scope.

If value is NULL, the environment variable key is unset instead.

dqcs_pcfg_env_unset()

Removes/unsets an environment variable for the plugin process.

dqcs_return_t dqcs_pcfg_env_unset(
    dqcs_handle_t pcfg,
    const char *key
)

The environment variable key is unset regardless of whether it exists in the parent environment variable scope.

dqcs_pcfg_executable()

Returns the configured executable path for the given plugin process.

char *dqcs_pcfg_executable(dqcs_handle_t pcfg)

On success, this returns a newly allocated string containing the executable path. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_pcfg_init_cmd()

Appends an ArbCmd to the list of initialization commands of a plugin process.

dqcs_return_t dqcs_pcfg_init_cmd(
    dqcs_handle_t pcfg,
    dqcs_handle_t cmd
)

The ArbCmd handle is consumed by this function, and is thus invalidated, if and only if it is successful.

dqcs_pcfg_name()

Returns the configured name for the given plugin process.

char *dqcs_pcfg_name(dqcs_handle_t pcfg)

On success, this returns a newly allocated string containing the name. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_pcfg_new()

Creates a new plugin process configuration object using sugared syntax.

dqcs_handle_t dqcs_pcfg_new(
    dqcs_plugin_type_t typ,
    const char *name,
    const char *spec
)

typ specifies the type of plugin. name specifies the name used to refer to the plugin later, which much be unique within a simulation; if it is empty or NULL, auto-naming will be performed: "front" for the frontend, "oper<i>" for the operators (indices starting at 1 from frontend to backend), and "back" for the backend. spec specifies which plugin to use, using the same syntax that the dqcsim command line interface uses.

dqcs_pcfg_new_raw()

Creates a new plugin process configuration object using raw paths.

dqcs_handle_t dqcs_pcfg_new_raw(
    dqcs_plugin_type_t typ,
    const char *name,
    const char *executable,
    const char *script
)

This works the same as dqcs_pcfg_new(), but instead of the sugared, command-line style specification you have to specify the path to the plugin executable and (if applicable) the script it must execute directly. This is useful when you have a specific executable in mind and you don't want the somewhat heuristic desugaring algorithm from doing something unexpected.

Pass NULL or an empty string to script to specify a native plugin executable that does not take a script argument.

dqcs_pcfg_script()

Returns the configured script path for the given plugin process.

char *dqcs_pcfg_script(dqcs_handle_t pcfg)

On success, this returns a newly allocated string containing the script path. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL. An empty string will be returned if no script is configured to distinguish it from failure.

dqcs_pcfg_shutdown_timeout_get()

Returns the configured timeout for the plugin process to shut down gracefully.

double dqcs_pcfg_shutdown_timeout_get(dqcs_handle_t pcfg)

The time unit is in seconds. Returns positive inifinity for an infinite timeout. Returns -1 when the function fails.

dqcs_pcfg_shutdown_timeout_set()

Configures the timeout for the plugin process to shut down gracefully.

dqcs_return_t dqcs_pcfg_shutdown_timeout_set(
    dqcs_handle_t pcfg,
    double timeout
)

The default is 5 seconds, so you should normally be able to leave this alone.

The time unit is seconds. Use IEEE positive infinity to specify an infinite timeout.

dqcs_pcfg_stderr_mode_get()

Returns the configured stderr capture mode for the specified plugin process.

dqcs_loglevel_t dqcs_pcfg_stderr_mode_get(dqcs_handle_t pcfg)
dqcs_pcfg_stderr_mode_set()

Configures the capture mode for the stderr stream of the specified plugin process.

dqcs_return_t dqcs_pcfg_stderr_mode_set(
    dqcs_handle_t pcfg,
    dqcs_loglevel_t level
)
dqcs_pcfg_stdout_mode_get()

Returns the configured stdout capture mode for the specified plugin process.

dqcs_loglevel_t dqcs_pcfg_stdout_mode_get(dqcs_handle_t pcfg)
dqcs_pcfg_stdout_mode_set()

Configures the capture mode for the stdout stream of the specified plugin process.

dqcs_return_t dqcs_pcfg_stdout_mode_set(
    dqcs_handle_t pcfg,
    dqcs_loglevel_t level
)
dqcs_pcfg_tee()

Configures a plugin process to also output its log messages to a file.

dqcs_return_t dqcs_pcfg_tee(
    dqcs_handle_t pcfg,
    dqcs_loglevel_t verbosity,
    const char *filename
)

verbosity configures the verbosity level for the file only.

dqcs_pcfg_type()

Returns the type of the given plugin process configuration.

dqcs_plugin_type_t dqcs_pcfg_type(dqcs_handle_t pcfg)
dqcs_pcfg_verbosity_get()

Returns the configured verbosity for the given plugin process.

dqcs_loglevel_t dqcs_pcfg_verbosity_get(dqcs_handle_t pcfg)
dqcs_pcfg_verbosity_set()

Configures the logging verbosity for the given plugin process.

dqcs_return_t dqcs_pcfg_verbosity_set(
    dqcs_handle_t pcfg,
    dqcs_loglevel_t level
)
dqcs_pcfg_work_get()

Returns the configured working directory for the given plugin process.

char *dqcs_pcfg_work_get(dqcs_handle_t pcfg)

On success, this returns a newly allocated string containing the working directory. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_pcfg_work_set()

Overrides the working directory for the plugin process.

dqcs_return_t dqcs_pcfg_work_set(
    dqcs_handle_t pcfg,
    const char *work
)
dqcs_pdef_author()

Returns the plugin author for the given plugin definition object.

char *dqcs_pdef_author(dqcs_handle_t pdef)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

dqcs_pdef_name()

Returns the plugin name for the given plugin definition object.

char *dqcs_pdef_name(dqcs_handle_t pdef)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

dqcs_pdef_new()

Creates a new PluginDefinition object.

dqcs_handle_t dqcs_pdef_new(
    dqcs_plugin_type_t typ,
    const char *name,
    const char *author,
    const char *version
)

Plugin definitions contain the callback functions/closures that define the functionality of a plugin. They also contain some metadata to identify the implementation, in the form of a name, author, and version string, that must be specified when the definition is constructed. The callback functions/closures are initialized to sane defaults for the requested plugin type, but obviously one or more of these should be overridden to make the plugin do something.

Once a definition object has been built, it can be used to spawn a plugin thread or run a plugin in the main thread, given a DQCsim server URL for it to connect to.

dqcs_pdef_set_advance_cb()

Sets the callback for advancing time for operators and backends.

dqcs_return_t dqcs_pdef_set_advance_cb(
    dqcs_handle_t pdef,
    dqcs_return_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_cycle_t cycles
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

The default behavior for operators is to pass through to dqcs_plugin_advance(). The default for backends is no-op. This callback is never called for frontend plugins.

Besides the common arguments, the callback receives an unsigned integer specifying the number of cycles to advance by.

The callback can return an error by setting an error message using dqcs_error_set() and returning DQCS_FAILURE. Otherwise, it should return DQCS_SUCCESS.

dqcs_pdef_set_allocate_cb()

Sets the qubit allocation callback for operators and backends.

dqcs_return_t dqcs_pdef_set_allocate_cb(
    dqcs_handle_t pdef,
    dqcs_return_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t qubits,
        dqcs_handle_t alloc_cmds
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

The default for operators is to pass through to dqcs_plugin_allocate(). The default for backends is no-op. This callback is never called for frontend plugins.

Besides the common arguments, the callback receives a handle to a qubit set containing the references that are to be used for the to-be-allocated qubits and an ArbCmd queue containing user-defined commands to optionally augment the behavior of the qubits. These are borrowed handles; the caller will delete them.

The callback can return an error by setting an error message using dqcs_error_set() and returning DQCS_FAILURE. Otherwise, it should return DQCS_SUCCESS.

dqcs_pdef_set_drop_cb()

Sets the user logic drop/cleanup callback.

dqcs_return_t dqcs_pdef_set_drop_cb(
    dqcs_handle_t pdef,
    dqcs_return_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

This is called when a plugin is gracefully terminated. It is not recommended to execute any downstream instructions at this time, but it is supported in case this is really necessary.

The default behavior is no-op.

The callback can return an error by setting an error message using dqcs_error_set() and returning DQCS_FAILURE. Otherwise, it should return DQCS_SUCCESS.

dqcs_pdef_set_free_cb()

Sets the qubit deallocation callback for operators and backends.

dqcs_return_t dqcs_pdef_set_free_cb(
    dqcs_handle_t pdef,
    dqcs_return_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t qubits
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

The default for operators is to pass through to dqcs_plugin_free(). The default for backends is no-op. This callback is never called for frontend plugins.

Besides the common arguments, the callback receives a handle to a qubit set containing the qubits that are to be freed. This is a borrowed handle; the caller will delete it.

The callback can return an error by setting an error message using dqcs_error_set() and returning DQCS_FAILURE. Otherwise, it should return DQCS_SUCCESS.

dqcs_pdef_set_gate_cb()

Sets the gate execution callback for operators and backends.

dqcs_return_t dqcs_pdef_set_gate_cb(
    dqcs_handle_t pdef,
    dqcs_handle_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t gate
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

Besides the common arguments, the callback receives a handle to the to-be-executed gate. This is a borrowed handle; the caller will delete it.

The callback must return one of the following things:

  • a valid handle to a measurement set, created using dqcs_mset_new() (this object is automatically deleted after the callback returns);
  • a valid handle to a single qubit measurement, created using dqcs_meas_new() (this object is automatically deleted after the callback returns);
  • the handle to the supplied gate, a shortcut for not returning any measurements (this is less clear than returning an empty measurement set, but slightly faster); or
  • 0 to report an error, after calling the error string using dqcs_set_error().

Backend plugins must return a measurement result set containing exactly those qubits specified in the measurement set. For operators, however, the story is more complicated. Let's say we want to make a silly operator that inverts all measurements. The trivial way to do this would be to forward the gate, query all the measurement results using dqcs_plugin_get_measurement(), invert them, stick them in a measurement result set, and return that result set. However, this approach is not very efficient, because dqcs_plugin_get_measurement() has to wait for all downstream plugins to finish executing the gate, forcing the OS to switch threads, etc. Instead, operators are allowed to return only a subset (or none) of the measured qubits, as long as they return the measurements as they arrive through the modify_measurement() callback.

The default implementation for this callback for operators is to pass the gate through to the downstream plugin and return an empty set of measurements. Combined with the default implementation of modify_measurement(), this behavior is sane. Backends must override this callback; the default is to return a not-implemented error.

Note that for our silly example operator, the default behavior for this function is sufficient; you'd only have to override modify_measurement() to, well, modify the measurements.

dqcs_pdef_set_host_arb_cb()

Sets the callback function function for handling an arb from the host.

dqcs_return_t dqcs_pdef_set_host_arb_cb(
    dqcs_handle_t pdef,
    dqcs_handle_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t cmd
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

The default behavior for this is no-op.

Besides the common arguments, the callback receives a handle to the ArbCmd object representing the request. It must return a valid ArbData handle containing the response. Both objects are deleted automatically after invocation.

The callback can return an error by setting an error message using dqcs_error_set() and returning 0. Otherwise, it should return a valid ArbData handle.

dqcs_pdef_set_initialize_cb()

Sets the user logic initialization callback.

dqcs_return_t dqcs_pdef_set_initialize_cb(
    dqcs_handle_t pdef,
    dqcs_return_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t init_cmds
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

This is always called before any of the other callbacks are run. The downstream plugin has already been initialized at this stage, so it is legal to send it commands.

The default behavior is no-op.

Besides the common arguments, the callback receives a handle to an ArbCmd queue (dqcs_cq_*, dqcs_cmd_*, and dqcs_arb_* interfaces) containing user-defined initialization commands. This is a borrowed handle; the caller will delete it.

The callback can return an error by setting an error message using dqcs_error_set() and returning DQCS_FAILURE. Otherwise, it should return DQCS_SUCCESS.

dqcs_pdef_set_modify_measurement_cb()

Sets the measurement modification callback for operators.

dqcs_return_t dqcs_pdef_set_modify_measurement_cb(
    dqcs_handle_t pdef,
    dqcs_handle_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t meas
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

This callback is called for every measurement result received from the downstream plugin, and returns the measurements that should be reported to the upstream plugin. Note that the results from our plugin's dqcs_plugin_get_measurement() and friends are consistent with the results received from downstream; they are not affected by this function.

The callback takes a handle to a single qubit measurement object as an argument, and must return one of the following things:

  • a valid handle to a measurement set, created using dqcs_mset_new() (this object is automatically deleted after the callback returns);
  • a valid handle to a single qubit measurement object, which may or may not be the supplied one (this object is automatically deleted after the callback returns); or
  • 0 to report an error, after calling the error string using dqcs_set_error().

This callback is somewhat special in that it is not allowed to call any plugin command other than logging and the pseudorandom number generator functions. This is because this function is called asynchronously with respect to the downstream functions, making the timing of these calls non-deterministic based on operating system scheduling.

Note that while this function is called for only a single measurement at a time, it is allowed to produce a vector of measurements. This allows you to cancel propagation of the measurement by returning an empty vector, to just modify the measurement data itself, or to generate additional measurements from a single measurement. However, if you need to modify the qubit references for operators that remap qubits, take care to only send measurement data upstream when these were explicitly requested through the associated upstream gate function's measured list.

The default behavior for this callback is to return the measurement without modification.

dqcs_pdef_set_run_cb()

Sets the run callback for frontends.

dqcs_return_t dqcs_pdef_set_run_cb(
    dqcs_handle_t pdef,
    dqcs_handle_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t args
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

This is called in response to a start() host API call. The return value is returned through the wait() host API call.

The default behavior is to fail with a "not implemented" error; frontends backends should always override this. This callback is never called for operator or backend plugins.

Besides the common arguments, the callback receives a handle to an ArbData object containing the data that the host passed to start(). This is a borrowed handle; the caller will delete it.

When the run callback is successful, it should return a valid ArbData handle. This can be the same as the argument, but it can also be a new object. This ArbData is returned to the host through wait(). This ArbData object is deleted after the callback completes.

The callback can return an error by setting an error message using dqcs_error_set() and returning 0. Otherwise, it should return a valid ArbData handle.

dqcs_pdef_set_upstream_arb_cb()

Sets the callback function for handling an arb from upstream for operators and backends.

dqcs_return_t dqcs_pdef_set_upstream_arb_cb(
    dqcs_handle_t pdef,
    dqcs_handle_t (*callback)(
        void *user_data,
        dqcs_plugin_state_t state,
        dqcs_handle_t cmd
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

The default behavior for operators is to pass through to dqcs_plugin_arb(); operators that do not support the requested interface should always do this. The default for backends is no-op. This callback is never called for frontend plugins.

Besides the common arguments, the callback receives a handle to the ArbCmd object representing the request. It must return a valid ArbData handle containing the response. Both objects are deleted automatically after invocation.

The callback can return an error by setting an error message using dqcs_error_set() and returning 0. Otherwise, it should return a valid ArbData handle.

dqcs_pdef_type()

Returns the plugin type for the given plugin definition object.

dqcs_plugin_type_t dqcs_pdef_type(dqcs_handle_t pdef)
dqcs_pdef_version()

Returns the plugin version for the given plugin definition object.

char *dqcs_pdef_version(dqcs_handle_t pdef)

On success, this returns a newly allocated string containing the JSON string. Free it with free() when you're done with it to avoid memory leaks. On failure, this returns NULL.

dqcs_plugin_advance()

Tells the downstream plugin to run for the specified number of cycles.

dqcs_cycle_t dqcs_plugin_advance(
    dqcs_plugin_state_t plugin,
    dqcs_cycle_t cycles
)

Backend plugins are not allowed to call this. Doing so will result in an error.

The return value is the new cycle counter. This function uses -1 to signal an error.

dqcs_plugin_allocate()

Allocate the given number of downstream qubits.

dqcs_handle_t dqcs_plugin_allocate(
    dqcs_plugin_state_t plugin,
    uintptr_t num_qubits,
    dqcs_handle_t cq
)

Backend plugins are not allowed to call this. Doing so will result in an error.

num_qubits specifies the number of qubits that are to be allocated.

commands must be 0 or a valid handle to an ArbCmd queue, containing a list of commands that may be used to modify the behavior of the qubit register; 0 is equivalent to zero commands. The queue is consumed by this function, i.e. the handle becomes invalid, if and only if it succeeds.

If the function is successful, a new handle to the set of qubit references representing the newly allocated register is returned. When the function fails, 0 is returned.

dqcs_plugin_arb()

Sends an arbitrary command downstream.

dqcs_handle_t dqcs_plugin_arb(
    dqcs_plugin_state_t plugin,
    dqcs_handle_t cmd
)

Backend plugins are not allowed to call this. Doing so will result in an error.

This function returns a new handle to an ArbData object representing the return value of the ArbCmd when successful. Otherwise, it returns 0.

dqcs_plugin_free()

Free the given downstream qubits.

dqcs_return_t dqcs_plugin_free(
    dqcs_plugin_state_t plugin,
    dqcs_handle_t qbset
)

Backend plugins are not allowed to call this. Doing so will result in an error.

qubits must be a valid set of qubit references. The set is consumed by this function, i.e. the handle becomes invalid, if and only if it succeeds.

dqcs_plugin_gate()

Tells the downstream plugin to execute a gate.

dqcs_return_t dqcs_plugin_gate(
    dqcs_plugin_state_t plugin,
    dqcs_handle_t gate
)

Backend plugins are not allowed to call this. Doing so will result in an error.

gate must be a valid gate object. The object is consumed by this function, i.e. the handle becomes invalid, if and only if it succeeds.

dqcs_plugin_get_cycle()

Returns the current value of the downstream cycle counter.

dqcs_cycle_t dqcs_plugin_get_cycle(dqcs_plugin_state_t plugin)

Backend plugins are not allowed to call this. Doing so will result in an error.

This function uses -1 to signal an error.

dqcs_plugin_get_cycles_between_measures()

Returns the number of downstream cycles between the last two measurements of the given downstream qubit.

dqcs_cycle_t dqcs_plugin_get_cycles_between_measures(
    dqcs_plugin_state_t plugin,
    dqcs_qubit_t qubit
)

Backend plugins are not allowed to call this. Doing so will result in an error.

This function uses -1 to signal an error.

dqcs_plugin_get_cycles_since_measure()

Returns the number of downstream cycles since the latest measurement of the given downstream qubit.

dqcs_cycle_t dqcs_plugin_get_cycles_since_measure(
    dqcs_plugin_state_t plugin,
    dqcs_qubit_t qubit
)

Backend plugins are not allowed to call this. Doing so will result in an error.

This function uses -1 to signal an error.

dqcs_plugin_get_measurement()

Returns the latest measurement of the given downstream qubit.

dqcs_handle_t dqcs_plugin_get_measurement(
    dqcs_plugin_state_t plugin,
    dqcs_qubit_t qubit
)

Backend plugins are not allowed to call this. Doing so will result in an error.

If the function succeeds, it returns a new handle to a qubit measurement result object. Otherwise it returns 0.

dqcs_plugin_random_f64()

Generates a random floating point number using the simulator random seed.

double dqcs_plugin_random_f64(dqcs_plugin_state_t plugin)

The generated numbers are uniformly distributed in the range [0,1>.

This function only fails if the plugin handle is invalid, in which case it returns 0. Of course, 0 is also a valid (if rare) random return value.

dqcs_plugin_random_u64()

Generates a random unsigned 64-bit number using the simulator random seed.

dqcs_handle_t dqcs_plugin_random_u64(dqcs_plugin_state_t plugin)

This function only fails if the plugin handle is invalid, in which case it returns 0. Of course, 0 is also a valid (if rare) random return value.

dqcs_plugin_recv()

Waits for a message from the host.

dqcs_handle_t dqcs_plugin_recv(dqcs_plugin_state_t plugin)

It is only legal to call this function from within the run() callback. Any other source will result in an error.

When successful, this function returns a new handle to the received ArbData object. 0 is used to indicate that an error occurred.

dqcs_plugin_run()

Executes a plugin in the current thread.

dqcs_return_t dqcs_plugin_run(
    dqcs_handle_t pdef,
    const char *simulator
)

pdef must be an appropriately populated plugin definition object. Its callback functions will be called from the current thread, from within the context of this function.

simulator must be set to the address of our endpoint of the simulator that's using the plugin; DQCsim normally passes this as the first command line argument of the plugin process.

If the plugin starts, the pdef handle is consumed by this function, regardless of whether the plugin eventually closes normally. The handle is only left alive if pdef is not a plugin definition object.

dqcs_plugin_send()

Sends a message to the host.

dqcs_return_t dqcs_plugin_send(
    dqcs_plugin_state_t plugin,
    dqcs_handle_t arb
)

It is only legal to call this function from within the run() callback. Any other source will result in an error.

The cmd handle is consumed by this function if and only if it succeeds.

dqcs_plugin_start()

Executes a plugin in a worker thread.

dqcs_handle_t dqcs_plugin_start(
    dqcs_handle_t pdef,
    const char *simulator
)

This function behaves the same as dqcs_plugin_log(), but is asynchronous; it always returns immediately. Of course, this means that the callbacks in pdef will be called from a different thread.

To wait for the thread to finish executing, call dqcs_plugin_wait() on the returned join handle. Alternatively you can delete the join handle object, which will detach the thread.

Note that dqcs_log_*() will only be available in the thread that the plugin actually runs in.

This function returns 0 to indicate failure to start the plugin. Otherwise, the join handle is returned.

dqcs_plugin_state_t

Type for a plugin state.

typedef void *dqcs_plugin_state_t;

This is an opaque type that is passed along to plugin implementation callback functions, which those callbacks can then use to interact with the plugin instance. User code shall not create or modify values of this type, and shall only use the values when calling dqcs_plugin_* functions.

dqcs_plugin_type_t

Enumeration of the three types of plugins.

typedef enum { ... } dqcs_plugin_type_t;

Variants:

DQCS_PTYPE_INVALID = -1
Invalid plugin type. Used to indicate failure of an API that returns a plugin type.
DQCS_PTYPE_FRONT = 0
Frontend plugin.
DQCS_PTYPE_OPER = 1
Operator plugin.
DQCS_PTYPE_BACK = 2
Backend plugin.

dqcs_plugin_wait()

Waits for a plugin worker thread to finish executing.

dqcs_return_t dqcs_plugin_wait(dqcs_handle_t pjoin)

Unless the join handle is invalid, this function returns success/failure based on the result of the plugin execution. If the plugin thread is joined, the join handle is deleted.

dqcs_predefined_gate_t

Enumeration of gates defined by DQCsim.

typedef enum { ... } dqcs_predefined_gate_t;

Variants:

DQCS_GATE_INVALID = 0
Invalid gate. Used as an error return value.
DQCS_GATE_PAULI_I = 100
The identity gate for a single qubit.

\[ I = \sigma_0 = \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \]

DQCS_GATE_PAULI_X = 101
The Pauli X matrix.

\[ X = \sigma_1 = \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix} \]

DQCS_GATE_PAULI_Y = 102
The Pauli Y matrix.

\[ Y = \sigma_2 = \begin{bmatrix} 0 & -i \\ i & 0 \end{bmatrix} \]

DQCS_GATE_PAULI_Z = 103
The Pauli Z matrix.

\[ Z = \sigma_3 = \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} \]

DQCS_GATE_H = 104
The hadamard gate matrix. That is, a 180-degree Y rotation, followed by a 90-degree X rotation.

\[ H = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & 1 \\ 1 & -1 \end{bmatrix} \]

DQCS_GATE_S = 105
The S matrix, also known as a 90 degree Z rotation.

\[ S = \begin{bmatrix} 1 & 0 \\ 0 & i \end{bmatrix} \]

DQCS_GATE_S_DAG = 106
The S-dagger matrix, also known as a negative 90 degree Z rotation.

\[ S^\dagger = \begin{bmatrix} 1 & 0 \\ 0 & -i \end{bmatrix} \]

DQCS_GATE_T = 107
The T matrix, also known as a 45 degree Z rotation.

\[ T = \begin{bmatrix} 1 & 0 \\ 0 & e^{i\frac{\pi}{4}} \end{bmatrix} \]

DQCS_GATE_T_DAG = 108
The T-dagger matrix, also known as a negative 45 degree Z rotation.

\[ T^\dagger = \begin{bmatrix} 1 & 0 \\ 0 & e^{-i\frac{\pi}{4}} \end{bmatrix} \]

DQCS_GATE_RX_90 = 109
Rx(90°) gate.

\[ R_x\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & -i \\ -i & 1 \end{bmatrix} \]

DQCS_GATE_RX_M90 = 110
Rx(-90°) gate.

\[ R_x\left(-\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & i \\ i & 1 \end{bmatrix} \]

DQCS_GATE_RX_180 = 111
Rx(180°) gate.

\[ R_x(\pi) = \begin{bmatrix} 0 & -i \\ -i & 0 \end{bmatrix} \]

This matrix is equivalent to the Pauli X gate, but differs in global phase. Note that this difference is significant when it is used as a submatrix for a controlled gate.

DQCS_GATE_RY_90 = 112
Ry(90°) gate.

\[ R_y\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & -1 \\ 1 & 1 \end{bmatrix} \]

DQCS_GATE_RY_M90 = 113
Ry(-90°) gate.

\[ R_y\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & 1 \\ -1 & 1 \end{bmatrix} \]

DQCS_GATE_RY_180 = 114
Ry(180°) gate.

\[ R_y(\pi) = \begin{bmatrix} 0 & -1 \\ 1 & 0 \end{bmatrix} \]

This matrix is equivalent to the Pauli Y gate, but differs in global phase. Note that this difference is significant when it is used as a submatrix for a controlled gate.

DQCS_GATE_RZ_90 = 115
Rz(90°) gate.

\[ R_z\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1-i & 0 \\ 0 & 1+i \end{bmatrix} \]

This matrix is equivalent to the S gate, but differs in global phase. Note that this difference is significant when it is used as a submatrix for a controlled gate.

DQCS_GATE_RZ_M90 = 116
Rz(-90°) gate.

\[ R_z\left(-\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1+i & 0 \\ 0 & 1-i \end{bmatrix} \]

This matrix is equivalent to the S-dagger gate, but differs in global phase. Note that this difference is significant when it is used as a submatrix for a controlled gate.

DQCS_GATE_RZ_180 = 117
Rz(180°) gate.

\[ R_z(\pi) = \begin{bmatrix} -i & 0 \\ 0 & i \end{bmatrix} \]

This matrix is equivalent to the Pauli Z gate, but differs in global phase. Note that this difference is significant when it is used as a submatrix for a controlled gate.

DQCS_GATE_RX = 150
The matrix for an arbitrary X rotation.

\[ R_x(\theta) = \begin{bmatrix} \cos{\frac{\theta}{2}} & -i\sin{\frac{\theta}{2}} \\ -i\sin{\frac{\theta}{2}} & \cos{\frac{\theta}{2}} \end{bmatrix} \]

θ is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as a little-endian double floating point value, specified in radians.

DQCS_GATE_RY = 151
The matrix for an arbitrary Y rotation.

\[ R_y(\theta) = \begin{bmatrix} \cos{\frac{\theta}{2}} & -\sin{\frac{\theta}{2}} \\ \sin{\frac{\theta}{2}} & \cos{\frac{\theta}{2}} \end{bmatrix} \]

θ is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as a little-endian double floating point value, specified in radians.

DQCS_GATE_RZ = 152
The matrix for an arbitrary Z rotation.

\[ R_z(\theta) = \begin{bmatrix} e^{-i\frac{\theta}{2}} & 0 \\ 0 & e^{i\frac{\theta}{2}} \end{bmatrix} \]

θ is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as a little-endian double floating point value, specified in radians.

DQCS_GATE_PHASE_K = 153
The matrix for a Z rotation with angle π/2^k.

\[ \textit{PhaseK}(k) = \textit{Phase}\left(\frac{\pi}{2^k}\right) = \begin{bmatrix} 1 & 0 \\ 0 & e^{i\pi / 2^k} \end{bmatrix} \]

k is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as a little-endian unsigned 64-bit integer.

DQCS_GATE_PHASE = 154
The matrix for an arbitrary Z rotation.

\[ \textit{Phase}(\theta) = \begin{bmatrix} 1 & 0 \\ 0 & e^{i\theta} \end{bmatrix} \]

θ is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as a little-endian double floating point value, specified in radians.

This matrix is equivalent to the Rz gate, but differs in global phase. Note that this difference is significant when it is used as a submatrix for a controlled gate. Specifically, controlled phase gates use the phase as specified by this gate, whereas Rz follows the usual algebraic notation.

DQCS_GATE_U1 = 190
Any single-qubit unitary gate, parameterized as a full unitary matrix.

The full matrix is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as an array of little-endian double floating point values, structured as real/imag pairs, with the pairs in row-major order.

DQCS_GATE_R = 191
Arbitrary rotation matrix.

\[ R(\theta, \phi, \lambda) = \begin{bmatrix} \cos{\frac{\theta}{2}} & -\sin{\frac{\theta}{2}} e^{i\lambda} \\ \sin{\frac{\theta}{2}} e^{i\phi} & \cos{\frac{\theta}{2}} e^{i\phi + i\lambda} \end{bmatrix} \]

This is equivalent to the following:

\[ R(\theta, \phi, \lambda) = \textit{Phase}(\phi) \cdot R_y(\theta) \cdot \textit{Phase}(\lambda) \]

The rotation order and phase is taken from Qiskit's U3 gate. Ignoring global phase, any unitary single-qubit gate can be represented with this notation.

θ, φ, and λ are specified or returned through the first three binary string arguments of the parameterization ArbData object. They are represented as little-endian double floating point values, specified in radians.

DQCS_GATE_SWAP = 200
The swap gate matrix.

\[ \textit{SWAP} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \]

DQCS_GATE_SQRT_SWAP = 201
The square-root of a swap gate matrix.

\[ \sqrt{\textit{SWAP}} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \frac{i+1}{2} & \frac{i-1}{2} & 0 \\ 0 & \frac{i-1}{2} & \frac{i+1}{2} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \]

DQCS_GATE_U2 = 290
Any two-qubit unitary gate, parameterized as a full unitary matrix.

The full matrix is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as an array of little-endian double floating point values, structured as real/imag pairs, with the pairs in row-major order.

DQCS_GATE_U3 = 390
Any three-qubit unitary gate, parameterized as a full unitary matrix.

The full matrix is specified or returned through the first binary string argument of the parameterization ArbData object. It is represented as an array of little-endian double floating point values, structured as real/imag pairs, with the pairs in row-major order.

dqcs_qbset_contains()

Returns whether the given qubit set contains the given qubit.

dqcs_bool_return_t dqcs_qbset_contains(
    dqcs_handle_t qbset,
    dqcs_qubit_t qubit
)
dqcs_qbset_copy()

Returns a copy of the given qubit set, intended for non-destructive iteration.

dqcs_handle_t dqcs_qbset_copy(dqcs_handle_t qbset)
dqcs_qbset_len()

Returns the number of qubits in the given set.

ssize_t dqcs_qbset_len(dqcs_handle_t qbset)

This function returns -1 to indicate failure.

dqcs_qbset_new()

Creates a new set of qubit references.

dqcs_handle_t dqcs_qbset_new(void)

Returns the handle of the newly created set. The set is initially empty. Qubit sets are ordered, meaning that the order in which qubits are popped from the set equals the order in which they were pushed. To iterate over a set, simply make a copy and drain the copy using pop.

dqcs_qbset_pop()

Pops a qubit reference off of a qubit reference set.

dqcs_qubit_t dqcs_qbset_pop(dqcs_handle_t qbset)

Qubits are popped in the same order in which they were pushed. That is, they are FIFO-ordered.

dqcs_qbset_push()

Pushes a qubit reference into a qubit reference set.

dqcs_return_t dqcs_qbset_push(
    dqcs_handle_t qbset,
    dqcs_qubit_t qubit
)

This function will fail if the specified qubit was already part of the set.

dqcs_qubit_t

Type for a qubit reference.

typedef unsigned long long dqcs_qubit_t;

Qubit references are exchanged between the frontend, operator, and backend plugins to indicate which qubits a gate operates on. Note that this makes them fundamentally different from handles, which are thread-local.

Qubit references are always positive integers, counting upwards from 1 upon allocation, and they are not reused even after the qubit is deallocated. Thus, every subsequent allocation returns a qubit reference one greater than the previous. This is guaranteed behavior that external code can rely upon. The value zero is reserved for invalid references or error propagation.

dqcs_return_t

Default return type for functions that don't need to return anything.

typedef enum { ... } dqcs_return_t;

Variants:

DQCS_FAILURE = -1
The function has failed. More information may be obtained through `dqcsim_explain()`.
DQCS_SUCCESS = 0
The function did what it was supposed to.

dqcs_scfg_dqcsim_verbosity_get()

Returns the configured verbosity for DQCsim's own messages.

dqcs_loglevel_t dqcs_scfg_dqcsim_verbosity_get(dqcs_handle_t scfg)
dqcs_scfg_dqcsim_verbosity_set()

Configures the logging verbosity for DQCsim's own messages.

dqcs_return_t dqcs_scfg_dqcsim_verbosity_set(
    dqcs_handle_t scfg,
    dqcs_loglevel_t level
)
dqcs_scfg_log_callback()

Configures DQCsim to also output its log messages to callback function.

dqcs_return_t dqcs_scfg_log_callback(
    dqcs_handle_t scfg,
    dqcs_loglevel_t verbosity,
    void (*callback)(
        void *user_data,
        const char *message,
        const char *logger,
        dqcs_loglevel_t level,
        const char *module,
        const char *file,
        uint32_t line,
        uint64_t time_s,
        uint32_t time_ns,
        uint32_t pid,
        uint64_t tid
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

verbosity specifies the minimum importance of a message required for the callback to be called.

callback is the callback function to install. It is always called with the user_data pointer to make calling stuff like class member functions or closures possible. The user_free function, if non-null, will be called when the callback is uninstalled in any way. If callback is null, any current callback is uninstalled instead. For consistency, if user_free is non-null while callback is null, user_free is called immediately, under the assumption that the caller has allocated resources unbeknownst that the callback it's trying to install is null.

NOTE: both callback and user_free may be called from a thread spawned by the simulator. Calling any API calls from the callback is therefore undefined behavior!

The callback takes the following arguments:

  • void*: user defined data.
  • const char*: log message string, excluding metadata.
  • const char*: name assigned to the logger that was used to produce the message (= "dqcsim" or a plugin name).
  • dqcs_loglevel_t: the verbosity level that the message was logged with.
  • const char*: string representing the source of the log message, or NULL when no source is known.
  • const char*: string containing the filename of the source that generated the message, or NULL when no source is known.
  • uint32_t: line number within the aforementioned file, or 0 if not known.
  • uint64_t: Time in seconds since the Unix epoch.
  • uint32_t: Additional time in nanoseconds since the aforementioned.
  • uint32_t: PID of the generating process.
  • uint64_t: TID of the generating thread.

If an internal log record is particularly malformed and cannot be coerced into the above (nul bytes in the strings, invalid timestamp, whatever) the message is silently ignored.

The primary use of this callback is to pipe DQCsim's messages to an external logging framework. When you do this, you probably also want to call dqcs_scfg_stderr_verbosity_set(handle, DQCS_LOG_OFF) to prevent DQCsim from writing the messages to stderr itself.

dqcs_scfg_new()

Constructs an empty simulation configuration.

dqcs_handle_t dqcs_scfg_new(void)

Before the configuration can be used, at least a frontend and a backend plugin configuration must be pushed into it. This can be done with dqcs_scfg_push_plugin(). Failing to do this will result in an error when you try to start the simulation.

The default settings correspond to the defaults of the dqcsim command line interface. Refer to its help for more information.

dqcs_scfg_push_plugin()

Appends a plugin to a simulation configuration.

dqcs_return_t dqcs_scfg_push_plugin(
    dqcs_handle_t scfg,
    dqcs_handle_t xcfg
)

Both plugin process and plugin thread configuration objects may be used. The handle is consumed by this function, and is thus invalidated, if and only if it is successful.

Frontend and backend plugins will automatically be inserted at the front and back of the pipeline when the simulation is created. Operators are inserted in front to back order. This function does not provide safeguards against multiple frontends/backends; such errors will only be reported when the simulation is started.

Note that it is not possible to observe or mutate a plugin configuration once it has been added to a simulator configuration handle. If you want to do this for some reason, you should maintain your own data structures, and only build the DQCsim structures from them when you're done.

dqcs_scfg_repro_disable()

Disables the reproduction logging system.

dqcs_return_t dqcs_scfg_repro_disable(dqcs_handle_t scfg)

Calling this will disable the warnings printed when a simulation that cannot be reproduced is constructed.

dqcs_scfg_repro_path_style_get()

Returns the path style used when writing reproduction files.

dqcs_path_style_t dqcs_scfg_repro_path_style_get(dqcs_handle_t scfg)
dqcs_scfg_repro_path_style_set()

Sets the path style used when writing reproduction files.

dqcs_return_t dqcs_scfg_repro_path_style_set(
    dqcs_handle_t scfg,
    dqcs_path_style_t path_style
)
dqcs_scfg_seed_get()

Returns the configured random seed.

uint64_t dqcs_scfg_seed_get(dqcs_handle_t scfg)

This function will return 0 when it fails, but this can unfortunately not be reliably distinguished from a seed that was set to 0.

dqcs_scfg_seed_set()

Configures the random seed that the simulation should use.

dqcs_return_t dqcs_scfg_seed_set(
    dqcs_handle_t scfg,
    uint64_t seed
)

Note that the seed is randomized by default.

dqcs_scfg_stderr_verbosity_get()

Returns the configured stderr sink verbosity for a simulation.

dqcs_loglevel_t dqcs_scfg_stderr_verbosity_get(dqcs_handle_t scfg)

That is, the minimum loglevel that a messages needs to have for it to be printed to stderr.

dqcs_scfg_stderr_verbosity_set()

Configures the stderr sink verbosity for a simulation.

dqcs_return_t dqcs_scfg_stderr_verbosity_set(
    dqcs_handle_t scfg,
    dqcs_loglevel_t level
)

That is, the minimum loglevel that a messages needs to have for it to be printed to stderr.

dqcs_scfg_tee()

Configures DQCsim to also output its log messages to a file.

dqcs_return_t dqcs_scfg_tee(
    dqcs_handle_t scfg,
    dqcs_loglevel_t verbosity,
    const char *filename
)

verbosity configures the verbosity level for the file only.

dqcs_sim_arb()

Sends an ArbCmd message to one of the plugins, referenced by name.

dqcs_handle_t dqcs_sim_arb(
    dqcs_handle_t sim,
    const char *name,
    dqcs_handle_t cmd
)

ArbCmds are executed immediately after yielding to the simulator, so all pending asynchronous calls are flushed and executed before the ArbCmd.

When this succeeds, the received data is returned in the form of a new handle. When it fails, 0 is returned.

The ArbCmd handle is consumed if and only if the API call succeeds.

dqcs_sim_arb_idx()

Sends an ArbCmd message to one of the plugins, referenced by index.

dqcs_handle_t dqcs_sim_arb_idx(
    dqcs_handle_t sim,
    ssize_t index,
    dqcs_handle_t cmd
)

The frontend always has index 0. 1 through N are used for the operators in front to back order (where N is the number of operators). The backend is at index N+1.

Python-style negative indices are supported. That is, -1 can be used to refer to the backend, -2 to the last operator, and so on.

ArbCmds are executed immediately after yielding to the simulator, so all pending asynchronous calls are flushed and executed before the ArbCmd.

When this succeeds, the received data is returned in the form of a new handle. When it fails, 0 is returned.

The ArbCmd handle is consumed if and only if the API call succeeds.

dqcs_sim_get_author()

Queries the author of a plugin, referenced by instance name.

char *dqcs_sim_get_author(
    dqcs_handle_t sim,
    const char *name
)

On success, this returns a newly allocated string containing the author. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_sim_get_author_idx()

Queries the author of a plugin, referenced by index.

char *dqcs_sim_get_author_idx(
    dqcs_handle_t sim,
    ssize_t index
)

On success, this returns a newly allocated string containing the author. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_sim_get_name()

Queries the implementation name of a plugin, referenced by instance name.

char *dqcs_sim_get_name(
    dqcs_handle_t sim,
    const char *name
)

On success, this returns a newly allocated string containing the name. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_sim_get_name_idx()

Queries the implementation name of a plugin, referenced by index.

char *dqcs_sim_get_name_idx(
    dqcs_handle_t sim,
    ssize_t index
)

On success, this returns a newly allocated string containing the name. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_sim_get_version()

Queries the version of a plugin, referenced by instance name.

char *dqcs_sim_get_version(
    dqcs_handle_t sim,
    const char *name
)

On success, this returns a newly allocated string containing the version. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_sim_get_version_idx()

Queries the version of a plugin, referenced by index.

char *dqcs_sim_get_version_idx(
    dqcs_handle_t sim,
    ssize_t index
)

On success, this returns a newly allocated string containing the version. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_sim_new()

Constructs a DQCsim simulation.

dqcs_handle_t dqcs_sim_new(dqcs_handle_t scfg)

The provided handle is consumed if it is a simulation configuration, regardless of whether simulation construction succeeds.

dqcs_sim_recv()

Waits for the simulated accelerator to send a message to us.

dqcs_handle_t dqcs_sim_recv(dqcs_handle_t sim)

When this succeeds, the received data is returned in the form of a new handle. When it fails, 0 is returned.

Deadlocks are detected and prevented by returning an error.

dqcs_sim_send()

Sends a message to the simulated accelerator.

dqcs_return_t dqcs_sim_send(
    dqcs_handle_t sim,
    dqcs_handle_t data
)

This is an asynchronous call: nothing happens until yield(), recv(), or wait() is called.

The ArbData handle is optional; if 0 is passed, an empty data object is used. If a handle is passed, it is consumed if and only if the API call succeeds.

dqcs_sim_start()

Starts a program on the simulated accelerator.

dqcs_return_t dqcs_sim_start(
    dqcs_handle_t sim,
    dqcs_handle_t data
)

This is an asynchronous call: nothing happens until yield(), recv(), or wait() is called.

The ArbData handle is optional; if 0 is passed, an empty data object is used. If a handle is passed, it is consumed if and only if the API call succeeds.

dqcs_sim_wait()

Waits for the simulated accelerator to finish its current program.

dqcs_handle_t dqcs_sim_wait(dqcs_handle_t sim)

When this succeeds, the return value of the accelerator's run() function is returned in the form of a new handle. When it fails, 0 is returned.

Deadlocks are detected and prevented by returning an error.

dqcs_sim_write_reproduction_file()

Writes a reproduction file for the simulation so far.

dqcs_return_t dqcs_sim_write_reproduction_file(
    dqcs_handle_t sim,
    const char *filename
)
dqcs_sim_yield()

Yields to the simulator.

dqcs_return_t dqcs_sim_yield(dqcs_handle_t sim)

The simulation runs until it blocks again. This is useful if you want an immediate response to an otherwise asynchronous call through the logging system or some communication channel outside of DQCsim's control.

This function silently returns immediately if no asynchronous data was pending or if the simulator is waiting for something that has not been sent yet.

dqcs_tcfg_init_cmd()

Appends an ArbCmd to the list of initialization commands of a plugin thread.

dqcs_return_t dqcs_tcfg_init_cmd(
    dqcs_handle_t tcfg,
    dqcs_handle_t cmd
)

The ArbCmd handle is consumed by this function, and is thus invalidated, if and only if it is successful.

dqcs_tcfg_name()

Returns the configured name for the given plugin thread.

char *dqcs_tcfg_name(dqcs_handle_t tcfg)

On success, this returns a newly allocated string containing the name. Free it with free() when you're done with it to avoid memory leaks. On failure (i.e., the handle is invalid) this returns NULL.

dqcs_tcfg_new()

Creates a new plugin thread configuration object from a plugin definition.

dqcs_handle_t dqcs_tcfg_new(
    dqcs_handle_t pdef,
    const char *name
)

The plugin definition handle is consumed by this function.

dqcs_tcfg_new_raw()

Creates a new plugin thread configuration object from a callback.

dqcs_handle_t dqcs_tcfg_new_raw(
    dqcs_plugin_type_t plugin_type,
    const char *name,
    void (*callback)(
        void *user_data,
        const char *simulator
    ),
    void (*user_free)(void *user_data),
    void *user_data
)

The callback is called by DQCsim from a dedicated thread when DQCsim wants to start the plugin. The callback must then in some way spawn a plugin process that connects to the provided simulator string. The callback should return only when the process terminates.

dqcs_tcfg_tee()

Configures a plugin thread to also output its log messages to a file.

dqcs_return_t dqcs_tcfg_tee(
    dqcs_handle_t tcfg,
    dqcs_loglevel_t verbosity,
    const char *filename
)

verbosity configures the verbosity level for the file only.

dqcs_tcfg_type()

Returns the type of the given plugin thread configuration.

dqcs_plugin_type_t dqcs_tcfg_type(dqcs_handle_t tcfg)
dqcs_tcfg_verbosity_get()

Returns the configured verbosity for the given plugin thread.

dqcs_loglevel_t dqcs_tcfg_verbosity_get(dqcs_handle_t tcfg)
dqcs_tcfg_verbosity_set()

Configures the logging verbosity for the given plugin thread.

dqcs_return_t dqcs_tcfg_verbosity_set(
    dqcs_handle_t tcfg,
    dqcs_loglevel_t level
)

C++ API

The C++ API allows you to use DQCsim in a more abstract way than the C API can provide, by making use of C++11 features. The C++ API is a header-only wrapper around the C API, so the two APIs use the same shared object file. The C++ headers are automatically installed along with the DQCsim Python package (more detailed notes here).

How to read this chapter

This chapter provides basic information about the C++ API, as well as a few examples to get you coding quickly. It is assumed that you already know what DQCsim is, and have a decent understanding of the basic concepts. If you don't, start here.

However, this chapter doesn't even get close to documenting every single feature. If you're looking for something more complete, check out the generated API documentation here. It is also advised to skim through the C API documentation; the C++ API borrows heavily from it (as it is in fact merely a wrapper around it), and its documentation is much more complete.

Contents

Usage

The DQCsim C++ API consists of three files:

  • dqcsim: the primary include file for the C++ API.
  • cdqcsim: the C API wrapped in the dqcsim::raw namespace (similar to how C++ provides cstdio as a drop-in replacement for C's stdio.h).
  • the shared object file (libdqcsim.so on Linux, dqcsim.dylib on macOS).

These will be installed automatically in the include and lib directories that Python is aware of when DQCsim is installed using sudo pip3 install dqcsim (more detailed notes here).

Once installed, you can use the API in your program by adding the following include to your sources:

#include <dqcsim>

// Optionally:
using namespace dqcsim::wrap;

and adding -ldqcsim to your compiler command line, specifically the linker. You may also need to add -std=c++11 (or newer) if you haven't already, as DQCsim uses features from C++11.

Note that the dqcsim header includes cdqcsim, so the above will also give you access to the raw C API through dqcsim::raw. You can in fact mix the two, if you like.

Usage using CMake

TODO: Matthijs

Usage after install without root

If you don't have root access on your development machine (or didn't want to install DQCsim in your root directory), you'll also have to tell the compiler where you installed DQCsim. You need the following flags for that:

  • -I <path-to-dqcsim>/include: tells the compiler where to find the header file.
  • -L <path-to-dqcsim>/lib: tells the linker where to find the shared object file.

At runtime, you may need to add the library directory to your runtime linker's search path as well, using the LD_LIBRARY_PATH environment variable.

Usage after building from source

If you've built DQCsim from its source repository, you need to use the following paths:

  • -I <dqcsim-repo>/target/include for the header file;
  • -L <dqcsim-repo>/target/release or -L <dqcsim-repo>/target/debug for the shared object.

Again, you may need to add the latter to LD_LIBRARY_PATH as well.

Comparison to the C API

As stated earlier, the C++ API is basically just a wrapper around the C API: it makes use of C++11 features to hide some of the verbosity of the C interface, making it more ergonomic to use. The primary advantages of the C++ interface over the C interface are:

  • DQCsim's error handling is abstracted through exceptions.
  • Handles are wrapped by classes with appropriate inheritance.
  • Handle construction and deletion is more-or-less abstracted away by RAII, so you never have to worry about calling dqcs_handle_delete().
  • All strings are wrapped using std::string, so you don't need to worry about malloc/free when dealing with DQCsim's string functions.
  • All callbacks support C-style callbacks with a template for the user data argument type, class member functions, and std::function objects, so you don't have to deal with void* casts.
  • Many function/method overloads are provided to help you make your code more succinct.
  • Basic support for nlohmann::json for the ArbData JSON/CBOR object.

There shouldn't be any downsides to using the C++ interface over the C interface in C++ programs. If one should ever occur, you can just mix in the C API calls where needed.

Plugin anatomy

To define your own plugin, you can use the Plugin class. The workflow is as follows:

  • Use Plugin::Frontend(), Plugin::Operator(), or Plugin::Backend() to start defining a plugin.
  • Assign callback functions at your leisure using the with_* functions. You can pass any combination of arguments supported by dqcsim::wrap::Callback() to these builder functions, provided that the callback function signature is correct, of course.
  • Either:
    • run the plugin in the current thread using Plugin::run();
    • start the plugin in a DQCsim-managed worker thread using Plugin::start();
    • or pass the plugin definition object to PluginConfigurationBuilder::with_callbacks() to directly add it to a simulation.

Here's an example of a simple frontend plugin that just logs "Hello, World!":

#define DQCSIM_SHORT_LOGGING_MACROS
#include <dqcsim>
using namespace dqcsim::wrap;

ArbData run(RunningPluginState &state, ArbData &&arg) {
  INFO("Hello, World!");
  return ArbData();
}

int main(int argc, char *argv[]) {
  return Plugin::Frontend("hello", "JvS", "v1.0")
    .with_run(run)
    .run(argc, argv);
}

The Plugin class is equivalent to the pdef C API, but, as you can see, is much more succinct. Note for instance that the example includes all error handling implicitly thanks to exceptions, and that only a single statement is needed thanks to the builder pattern.

The C++ API supports a few different styles for the callback functions. The one used here is the most basic one; even more basic than the one in the C API as it does not take a user-defined parameter to store state or initialization parameters in. You can add that quite simply:

#include <string>

#define DQCSIM_SHORT_LOGGING_MACROS
#include <dqcsim>
using namespace dqcsim::wrap;

ArbData run(std::string *message, RunningPluginState &state, ArbData &&arg) {
  INFO("%s", message->c_str());
  *message = "I was run!";
  return ArbData();
}

int main(int argc, char *argv[]) {
  std::string message = "Hello!";
  int code = Plugin::Frontend("hello", "JvS", "v1.0")
    .with_run(run, &message)
    .run(argc, argv);
  INFO("%s", message.c_str());
  return code;
}

You can also abstract your plugin into a class. For instance, the following more complex example counts the number of gates passing through the operator.

#define DQCSIM_SHORT_LOGGING_MACROS
#include <dqcsim>
using namespace dqcsim::wrap;

class GateCounter {
public:

  int counter = 0;

  MeasurementSet gate(PluginState &state, Gate &&gate) {
    counter++;
    state.gate(std::move(gate));
    return MeasurementSet();
  }

  void drop(PluginState &state) {
    NOTE("%d gate(s) were transferred!", counter);
  }

};

int main(int argc, char *argv[]) {
  GateCounter gateCounter;
  return Plugin::Operator("GateCounter", "JvS", "v1.0")
    .with_gate(&gateCounter, &GateCounter::gate)
    .with_drop(&gateCounter, &GateCounter::drop)
    .run(argc, argv);
}

Note the std::move() in the gate() callback. Handles in DQCsim are generally not efficiently copyable, thus move()ing stuff around with rvalue references becomes important at times. If you don't know what that means, you should probably look it up at some point, but the C++ API provides (inefficient) copy constructors and overloads with regular references for most handle types, so usually things will just work without the std::move().

Host/simulation anatomy

To run a simulation (that is, make a host process) with the C++ interface, you start with a SimulationConfiguration object. The most important thing to do with this object is to configure which plugins you want to use, particularly the frontend and backend. You do this by adding plugin configurations using with_plugin() or add_plugin(). These plugin configurations are in turn constructed with the Frontend(), Operator(), and Backend() shorthands, followed by the appropriate builder functions for how you want to launch the plugins.

When you're done with your configuration, call build() or run(). The difference is that the former only initializes the simulation and then passes control over to you, while the latter is a shorthand for just calling run(), which is usually sufficient. After this, you may want to write a reproduction file with write_reproduction_file(); this file allows you to reproduce your simulation exactly using the DQCsim command line (as long as the plugins only use DQCsim's pseudorandom number generator or are deterministic) without even needing your host program anymore.

The simplest example for running a simulation is therefore as follows:

#include <dqcsim>
using namespace dqcsim::wrap;

int main() {

  SimulationConfiguration()
    .with_plugin(Frontend().with_spec("null"))
    .with_plugin(Backend().with_spec("null"))
    .run()
    .write_reproduction_file("null.repro");

  return 0;
}

Plugins can either run as separate processes (as above), or can run as threads within the host process. You can for instance insert the hello world frontend we'd defined in the previous section as follows:

#define DQCSIM_SHORT_LOGGING_MACROS
#include <dqcsim>
using namespace dqcsim::wrap;

ArbData run(RunningPluginState &state, ArbData &&arg) {
  INFO("Hello, World!");
  return ArbData();
}

int main() {

  SimulationConfiguration()
    .without_reproduction()
    .with_plugin(Frontend().with_callbacks(
      Plugin::Frontend("hello", "JvS", "v1.0")
        .with_run(run)
    ))
    .with_plugin(Backend().with_spec("null"))
    .run();

  return 0;
}

Note that simulations with plugins defined in-place in the host process cannot be reproduced through a reproduction file. Therefore, the reproduction system was turned off here.

Reference

The generated documentation can be found here.

Rust API

Of course, you can also develop plugins in Rust, since that's DQCsim's native programming language. Simply add the dqcsim crate to your Cargo dependencies, and use DQCsim's internal structures. You can find the crate documentation here.

Release

This chapter documents the release procedure of DQCsim. A release consists of a git tag object and an accompanying GitHub release. Build artifacts for the tag reference are generated and published by the Release workflow.

Prepare

  • Create a new branch from an up-to-date master branch: git checkout master, git pull origin master and git checkout -b release-1.2.3.
  • Update version number in rust/Cargo.toml. Run cargo check in the root of the repository to reflect the version update in the Cargo.lock file.
  • Update version number in the project command in CMakeLists.txt.
  • Add new entry to CHANGELOG.md. To list all commits since the previous tag reference use: git log $(git describe --tags --abbrev=0)..HEAD --oneline | xargs -L1 echo.
  • Stage the release commit by adding modified files and committing: git add rust/Cargo.toml Cargo.lock CMakeLists.txt CHANGELOG.md and git commit -m "Release 1.2.3".
  • Push the branch and create a pull request on GitHub.

Release

  • Merge the pull request with reviewers approval and all checks passed.
  • Update your local repository: git checkout master and git pull origin master.
  • Create the tag object: git tag -s 1.2.3. Add the CHANGELOG.md entry for this version as tag message.
  • Push the tag object: git push origin 1.2.3.

Validate

  • The release workflow is triggered by pushing the tag object. The workflow creates a GitHub release and uploads the generated artifacts to PyPi and Crates.io. Validate the workflow passes without errors.