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.