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 ArbCmd
s, 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
ArbCmd
s 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 ArbCmd
s 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 ArbCmd
s. The ArbCmd
s 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 ArbCmd
s 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 ArbCmd
s 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 ArbCmd
s 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 ArbCmd
s 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 ArbCmd
s 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 ArbCmd
s 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
- Concepts
- Type definitions
- ArbData and ArbCmd objects
- Qubits
- Matrices
- Gates
- Gate maps
- Measurements
- Plugins
- Simulations
- Reference
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.
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.
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.
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.
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.
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.
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.
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.
Default return type for functions that don't need to return anything.
typedef enum { ... } dqcs_return_t;
Variants:
DQCS_FAILURE = -1
DQCS_SUCCESS = 0
dqcs_bool_return_t
Return type for functions that normally return a boolean but can also fail.
Return type for functions that normally return a boolean but can also fail.
typedef enum { ... } dqcs_bool_return_t;
Variants:
DQCS_BOOL_FAILURE = -1
DQCS_FALSE = 0
DQCS_TRUE = 1
Simulator object references
The following types are used to refer to simulator objects.
dqcs_handle_t
Type for a handle.
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.
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.
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.
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.
Enumeration of types that can be associated with a handle.
typedef enum { ... } dqcs_handle_type_t;
Variants:
This indicates one of the following: This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the DQCS_HTYPE_INVALID = 0
DQCS_HTYPE_ARB_DATA = 100
handle
and arb
interfaces.DQCS_HTYPE_ARB_CMD = 101
handle
, arb
, and cmd
interfaces.DQCS_HTYPE_ARB_CMD_QUEUE = 102
handle
, arb
, cmd
, and
cq
interfaces.DQCS_HTYPE_QUBIT_SET = 103
handle
and qbset
interfaces.DQCS_HTYPE_GATE = 104
handle
, gate
, and arb
interfaces.DQCS_HTYPE_MEAS = 105
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
handle
and mset
interfaces.DQCS_HTYPE_MATRIX = 107
handle
and mat
interfaces.DQCS_HTYPE_GATE_MAP = 108
handle
and gm
interfaces.DQCS_HTYPE_FRONT_PROCESS_CONFIG = 200
handle
, pcfg
, and xcfg
interfaces.DQCS_HTYPE_OPER_PROCESS_CONFIG = 201
handle
, pcfg
, and xcfg
interfaces.DQCS_HTYPE_BACK_PROCESS_CONFIG = 203
handle
, pcfg
, and xcfg
interfaces.DQCS_HTYPE_FRONT_THREAD_CONFIG = 204
handle
, tcfg
, and xcfg
interfaces.DQCS_HTYPE_OPER_THREAD_CONFIG = 205
handle
, tcfg
, and xcfg
interfaces.DQCS_HTYPE_BACK_THREAD_CONFIG = 206
handle
, tcfg
, and xcfg
interfaces.DQCS_HTYPE_SIM_CONFIG = 207
handle
and scfg
interfaces.DQCS_HTYPE_SIM = 208
handle
and sim
interfaces.DQCS_HTYPE_FRONT_DEF = 300
handle
and pdef
interfaces.DQCS_HTYPE_OPER_DEF = 301
handle
and pdef
interfaces.DQCS_HTYPE_BACK_DEF = 302
handle
and pdef
interfaces.DQCS_HTYPE_PLUGIN_JOIN = 303
handle
and pjoin
interfaces.
dqcs_loglevel_t
Enumeration of loglevels and logging modes.
Enumeration of loglevels and logging modes.
typedef enum { ... } dqcs_loglevel_t;
Variants:
DQCS_LOG_INVALID = -1
DQCS_LOG_OFF = 0
DQCS_LOG_FATAL = 1
DQCS_LOG_ERROR = 2
DQCS_LOG_WARN = 3
DQCS_LOG_NOTE = 4
DQCS_LOG_INFO = 5
DQCS_LOG_DEBUG = 6
DQCS_LOG_TRACE = 7
DQCS_LOG_PASS = 8
dqcs_measurement_t
Qubit measurement value.
Qubit measurement value.
typedef enum { ... } dqcs_measurement_t;
Variants:
DQCS_MEAS_INVALID = -1
DQCS_MEAS_ZERO = 0
DQCS_MEAS_ONE = 1
DQCS_MEAS_UNDEFINED = 2
dqcs_path_style_t
Reproduction file path style.
Reproduction file path style.
typedef enum { ... } dqcs_path_style_t;
Variants:
DQCS_PATH_STYLE_INVALID = -1
DQCS_PATH_STYLE_KEEP = 0
DQCS_PATH_STYLE_RELATIVE = 1
DQCS_PATH_STYLE_ABSOLUTE = 2
dqcs_plugin_type_t
Enumeration of the three types of plugins.
Enumeration of the three types of plugins.
typedef enum { ... } dqcs_plugin_type_t;
Variants:
DQCS_PTYPE_INVALID = -1
DQCS_PTYPE_FRONT = 0
DQCS_PTYPE_OPER = 1
DQCS_PTYPE_BACK = 2
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
.
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.
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
.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Enumeration of gates defined by DQCsim.
typedef enum { ... } dqcs_predefined_gate_t;
Variants:
\[
I = \sigma_0 = \begin{bmatrix}
1 & 0 \\
0 & 1
\end{bmatrix}
\] \[
X = \sigma_1 = \begin{bmatrix}
0 & 1 \\
1 & 0
\end{bmatrix}
\] \[
Y = \sigma_2 = \begin{bmatrix}
0 & -i \\
i & 0
\end{bmatrix}
\] \[
Z = \sigma_3 = \begin{bmatrix}
1 & 0 \\
0 & -1
\end{bmatrix}
\] \[
H = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & 1 \\
1 & -1
\end{bmatrix}
\] \[
S = \begin{bmatrix}
1 & 0 \\
0 & i
\end{bmatrix}
\] \[
S^\dagger = \begin{bmatrix}
1 & 0 \\
0 & -i
\end{bmatrix}
\] \[
T = \begin{bmatrix}
1 & 0 \\
0 & e^{i\frac{\pi}{4}}
\end{bmatrix}
\] \[
T^\dagger = \begin{bmatrix}
1 & 0 \\
0 & e^{-i\frac{\pi}{4}}
\end{bmatrix}
\] \[
R_x\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & -i \\
-i & 1
\end{bmatrix}
\] \[
R_x\left(-\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & i \\
i & 1
\end{bmatrix}
\] \[
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. \[
R_y\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & -1 \\
1 & 1
\end{bmatrix}
\] \[
R_y\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & 1 \\
-1 & 1
\end{bmatrix}
\] \[
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. \[
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. \[
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. \[
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. \[
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. \[
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. \[
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. \[
\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. \[
\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. 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. \[
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. \[
\textit{SWAP} = \begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
\] \[
\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}
\] 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. 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_INVALID = 0
DQCS_GATE_PAULI_I = 100
DQCS_GATE_PAULI_X = 101
DQCS_GATE_PAULI_Y = 102
DQCS_GATE_PAULI_Z = 103
DQCS_GATE_H = 104
DQCS_GATE_S = 105
DQCS_GATE_S_DAG = 106
DQCS_GATE_T = 107
DQCS_GATE_T_DAG = 108
DQCS_GATE_RX_90 = 109
DQCS_GATE_RX_M90 = 110
DQCS_GATE_RX_180 = 111
DQCS_GATE_RY_90 = 112
DQCS_GATE_RY_M90 = 113
DQCS_GATE_RY_180 = 114
DQCS_GATE_RZ_90 = 115
DQCS_GATE_RZ_M90 = 116
DQCS_GATE_RZ_180 = 117
DQCS_GATE_RX = 150
DQCS_GATE_RY = 151
DQCS_GATE_RZ = 152
DQCS_GATE_PHASE_K = 153
DQCS_GATE_PHASE = 154
DQCS_GATE_U1 = 190
DQCS_GATE_R = 191
DQCS_GATE_SWAP = 200
DQCS_GATE_SQRT_SWAP = 201
DQCS_GATE_U2 = 290
DQCS_GATE_U3 = 390
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.
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.
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.
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.
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.
Enumeration of Pauli bases.
typedef enum { ... } dqcs_basis_t;
Variants:
\[
\psi_X = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & -1 \\
1 & 1
\end{bmatrix}
\] \[
\psi_Y = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & i \\
i & 1
\end{bmatrix}
\] \[
\psi_Z = \begin{bmatrix}
1 & 0 \\
0 & 1
\end{bmatrix}
\]DQCS_BASIS_INVALID = 0
DQCS_BASIS_X = 1
DQCS_BASIS_Y = 2
DQCS_BASIS_Z = 3
... and constructed as follows:
dqcs_mat_basis()
Constructs a matrix with the eigenvectors of one of the Pauli matrices
as column vectors.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Types of DQCsim gates.
typedef enum { ... } dqcs_gate_type_t;
Variants:
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. The semantics are: 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. The semantics are: 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. The semantics are:DQCS_GATE_TYPE_INVALID = 0
DQCS_GATE_TYPE_UNITARY
DQCS_GATE_TYPE_MEASUREMENT
DQCS_GATE_TYPE_PREP
DQCS_GATE_TYPE_CUSTOM
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 thisArbData
. However, the cache only maps to the matching detection function, and calls it again for every cache hit. Thus, theArbData
returned by the detector can still depend on the gate'sArbData
. -
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 (-):
Callback | Frontend | Operator | Backend |
---|---|---|---|
initialize | o | o | o |
drop | o | o | o |
run | x | - | - |
allocate | - | o | o |
free | - | o | o |
gate | - | o | x |
modify_measurement | - | o | - |
advance | - | o | o |
upstream_arb | - | o | o |
host_arb | o | o | o |
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 bydqcs_plugin_start()
as well asdqcs_plugin_run()
.
dqcs_plugin_start()
Executes a plugin in a worker thread.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 ArbCmd
s 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 nameddqcsfepy
. 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.
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.
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.
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.
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.
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.
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 ArbCmd
s 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 ArbCmd
s 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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, orNULL
when no source is known.const char*
: string containing the filename of the source that generated the message, orNULL
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.
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.
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.
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.
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.
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.
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 ArbCmd
s 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.
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
)
ArbCmd
s 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.
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.
ArbCmd
s 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Enumeration of Pauli bases.
typedef enum { ... } dqcs_basis_t;
Variants:
\[
\psi_X = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & -1 \\
1 & 1
\end{bmatrix}
\] \[
\psi_Y = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & i \\
i & 1
\end{bmatrix}
\] \[
\psi_Z = \begin{bmatrix}
1 & 0 \\
0 & 1
\end{bmatrix}
\]DQCS_BASIS_INVALID = 0
DQCS_BASIS_X = 1
DQCS_BASIS_Y = 2
DQCS_BASIS_Z = 3
dqcs_bool_return_t
Return type for functions that normally return a boolean but can also fail.
Return type for functions that normally return a boolean but can also fail.
typedef enum { ... } dqcs_bool_return_t;
Variants:
DQCS_BOOL_FAILURE = -1
DQCS_FALSE = 0
DQCS_TRUE = 1
dqcs_cmd_iface_cmp()
Compares the interface ID of an ArbCmd
with the given string.
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
.
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.
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.
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
.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Types of DQCsim gates.
typedef enum { ... } dqcs_gate_type_t;
Variants:
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. The semantics are: 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. The semantics are: 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. The semantics are:DQCS_GATE_TYPE_INVALID = 0
DQCS_GATE_TYPE_UNITARY
DQCS_GATE_TYPE_MEASUREMENT
DQCS_GATE_TYPE_PREP
DQCS_GATE_TYPE_CUSTOM
dqcs_gm_add_custom()
Adds a fully customizable gate mapping to the given gate map.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Enumeration of types that can be associated with a handle.
typedef enum { ... } dqcs_handle_type_t;
Variants:
This indicates one of the following: This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the This means that the handle supports the DQCS_HTYPE_INVALID = 0
DQCS_HTYPE_ARB_DATA = 100
handle
and arb
interfaces.DQCS_HTYPE_ARB_CMD = 101
handle
, arb
, and cmd
interfaces.DQCS_HTYPE_ARB_CMD_QUEUE = 102
handle
, arb
, cmd
, and
cq
interfaces.DQCS_HTYPE_QUBIT_SET = 103
handle
and qbset
interfaces.DQCS_HTYPE_GATE = 104
handle
, gate
, and arb
interfaces.DQCS_HTYPE_MEAS = 105
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
handle
and mset
interfaces.DQCS_HTYPE_MATRIX = 107
handle
and mat
interfaces.DQCS_HTYPE_GATE_MAP = 108
handle
and gm
interfaces.DQCS_HTYPE_FRONT_PROCESS_CONFIG = 200
handle
, pcfg
, and xcfg
interfaces.DQCS_HTYPE_OPER_PROCESS_CONFIG = 201
handle
, pcfg
, and xcfg
interfaces.DQCS_HTYPE_BACK_PROCESS_CONFIG = 203
handle
, pcfg
, and xcfg
interfaces.DQCS_HTYPE_FRONT_THREAD_CONFIG = 204
handle
, tcfg
, and xcfg
interfaces.DQCS_HTYPE_OPER_THREAD_CONFIG = 205
handle
, tcfg
, and xcfg
interfaces.DQCS_HTYPE_BACK_THREAD_CONFIG = 206
handle
, tcfg
, and xcfg
interfaces.DQCS_HTYPE_SIM_CONFIG = 207
handle
and scfg
interfaces.DQCS_HTYPE_SIM = 208
handle
and sim
interfaces.DQCS_HTYPE_FRONT_DEF = 300
handle
and pdef
interfaces.DQCS_HTYPE_OPER_DEF = 301
handle
and pdef
interfaces.DQCS_HTYPE_BACK_DEF = 302
handle
and pdef
interfaces.DQCS_HTYPE_PLUGIN_JOIN = 303
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Enumeration of loglevels and logging modes.
typedef enum { ... } dqcs_loglevel_t;
Variants:
DQCS_LOG_INVALID = -1
DQCS_LOG_OFF = 0
DQCS_LOG_FATAL = 1
DQCS_LOG_ERROR = 2
DQCS_LOG_WARN = 3
DQCS_LOG_NOTE = 4
DQCS_LOG_INFO = 5
DQCS_LOG_DEBUG = 6
DQCS_LOG_TRACE = 7
DQCS_LOG_PASS = 8
dqcs_mat_add_controls()
Constructs a controlled matrix from the given matrix.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Qubit measurement value.
typedef enum { ... } dqcs_measurement_t;
Variants:
DQCS_MEAS_INVALID = -1
DQCS_MEAS_ZERO = 0
DQCS_MEAS_ONE = 1
DQCS_MEAS_UNDEFINED = 2
dqcs_mset_contains()
Returns whether the given qubit measurement set contains data for the given
qubit.
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.
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.
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.
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.
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.
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.
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.
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.
Reproduction file path style.
typedef enum { ... } dqcs_path_style_t;
Variants:
DQCS_PATH_STYLE_INVALID = -1
DQCS_PATH_STYLE_KEEP = 0
DQCS_PATH_STYLE_RELATIVE = 1
DQCS_PATH_STYLE_ABSOLUTE = 2
dqcs_pcfg_accept_timeout_get()
Returns the configured timeout for the plugin process to connect to DQCsim.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Enumeration of the three types of plugins.
typedef enum { ... } dqcs_plugin_type_t;
Variants:
DQCS_PTYPE_INVALID = -1
DQCS_PTYPE_FRONT = 0
DQCS_PTYPE_OPER = 1
DQCS_PTYPE_BACK = 2
dqcs_plugin_wait()
Waits for a plugin worker thread to finish executing.
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.
Enumeration of gates defined by DQCsim.
typedef enum { ... } dqcs_predefined_gate_t;
Variants:
\[
I = \sigma_0 = \begin{bmatrix}
1 & 0 \\
0 & 1
\end{bmatrix}
\] \[
X = \sigma_1 = \begin{bmatrix}
0 & 1 \\
1 & 0
\end{bmatrix}
\] \[
Y = \sigma_2 = \begin{bmatrix}
0 & -i \\
i & 0
\end{bmatrix}
\] \[
Z = \sigma_3 = \begin{bmatrix}
1 & 0 \\
0 & -1
\end{bmatrix}
\] \[
H = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & 1 \\
1 & -1
\end{bmatrix}
\] \[
S = \begin{bmatrix}
1 & 0 \\
0 & i
\end{bmatrix}
\] \[
S^\dagger = \begin{bmatrix}
1 & 0 \\
0 & -i
\end{bmatrix}
\] \[
T = \begin{bmatrix}
1 & 0 \\
0 & e^{i\frac{\pi}{4}}
\end{bmatrix}
\] \[
T^\dagger = \begin{bmatrix}
1 & 0 \\
0 & e^{-i\frac{\pi}{4}}
\end{bmatrix}
\] \[
R_x\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & -i \\
-i & 1
\end{bmatrix}
\] \[
R_x\left(-\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & i \\
i & 1
\end{bmatrix}
\] \[
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. \[
R_y\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & -1 \\
1 & 1
\end{bmatrix}
\] \[
R_y\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & 1 \\
-1 & 1
\end{bmatrix}
\] \[
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. \[
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. \[
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. \[
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. \[
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. \[
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. \[
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. \[
\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. \[
\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. 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. \[
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. \[
\textit{SWAP} = \begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
\] \[
\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}
\] 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. 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_INVALID = 0
DQCS_GATE_PAULI_I = 100
DQCS_GATE_PAULI_X = 101
DQCS_GATE_PAULI_Y = 102
DQCS_GATE_PAULI_Z = 103
DQCS_GATE_H = 104
DQCS_GATE_S = 105
DQCS_GATE_S_DAG = 106
DQCS_GATE_T = 107
DQCS_GATE_T_DAG = 108
DQCS_GATE_RX_90 = 109
DQCS_GATE_RX_M90 = 110
DQCS_GATE_RX_180 = 111
DQCS_GATE_RY_90 = 112
DQCS_GATE_RY_M90 = 113
DQCS_GATE_RY_180 = 114
DQCS_GATE_RZ_90 = 115
DQCS_GATE_RZ_M90 = 116
DQCS_GATE_RZ_180 = 117
DQCS_GATE_RX = 150
DQCS_GATE_RY = 151
DQCS_GATE_RZ = 152
DQCS_GATE_PHASE_K = 153
DQCS_GATE_PHASE = 154
DQCS_GATE_U1 = 190
DQCS_GATE_R = 191
DQCS_GATE_SWAP = 200
DQCS_GATE_SQRT_SWAP = 201
DQCS_GATE_U2 = 290
DQCS_GATE_U3 = 390
dqcs_qbset_contains()
Returns whether the given qubit set contains the given qubit.
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.
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.
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.
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.
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.
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.
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.
Default return type for functions that don't need to return anything.
typedef enum { ... } dqcs_return_t;
Variants:
DQCS_FAILURE = -1
DQCS_SUCCESS = 0
dqcs_scfg_dqcsim_verbosity_get()
Returns the configured verbosity for DQCsim's own messages.
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.
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.
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, orNULL
when no source is known.const char*
: string containing the filename of the source that generated the message, orNULL
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
)
ArbCmd
s 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.
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.
ArbCmd
s 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 thedqcsim::raw
namespace (similar to how C++ providescstdio
as a drop-in replacement for C'sstdio.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 withvoid*
casts. - Many function/method overloads are provided to help you make your code more succinct.
- Basic support for
nlohmann::json
for theArbData
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()
, orPlugin::Backend()
to start defining a plugin. - Assign callback functions at your leisure using the
with_*
functions. You can pass any combination of arguments supported bydqcsim::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.
- run the plugin in the current thread using
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
andgit checkout -b release-1.2.3
. - Update version number in
rust/Cargo.toml
. Runcargo check
in the root of the repository to reflect the version update in theCargo.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
andgit 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
andgit pull origin master
. - Create the tag object:
git tag -s 1.2.3
. Add theCHANGELOG.md
entry for this version as tag message. - Push the tag object:
git push origin 1.2.3
.