Defining a plugin

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

Constructing a plugin definition

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

dqcs_pdef_new()

Creates a new PluginDefinition object.

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

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

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

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

dqcs_pdef_type()

Returns the plugin type for the given plugin definition object.

dqcs_plugin_type_t dqcs_pdef_type(dqcs_handle_t pdef)
dqcs_pdef_name()

Returns the plugin name for the given plugin definition object.

char *dqcs_pdef_name(dqcs_handle_t pdef)

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

dqcs_pdef_author()

Returns the plugin author for the given plugin definition object.

char *dqcs_pdef_author(dqcs_handle_t pdef)

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

dqcs_pdef_version()

Returns the plugin version for the given plugin definition object.

char *dqcs_pdef_version(dqcs_handle_t pdef)

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

Assigning callback functions

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

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

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

dqcs_pdef_set_initialize_cb()

Sets the user logic initialization callback.

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

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

The default behavior is no-op.

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

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

dqcs_pdef_set_drop_cb()

Sets the user logic drop/cleanup callback.

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

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

The default behavior is no-op.

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

dqcs_pdef_set_run_cb()

Sets the run callback for frontends.

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

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

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

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

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

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

dqcs_pdef_set_allocate_cb()

Sets the qubit allocation callback for operators and backends.

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

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

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

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

dqcs_pdef_set_free_cb()

Sets the qubit deallocation callback for operators and backends.

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

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

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

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

dqcs_pdef_set_gate_cb()

Sets the gate execution callback for operators and backends.

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

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

The callback must return one of the following things:

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

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

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

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

dqcs_pdef_set_modify_measurement_cb()

Sets the measurement modification callback for operators.

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

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

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

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

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

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

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

dqcs_pdef_set_advance_cb()

Sets the callback for advancing time for operators and backends.

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

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

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

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

dqcs_pdef_set_upstream_arb_cb()

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

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

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

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

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

dqcs_pdef_set_host_arb_cb()

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

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

The default behavior for this is no-op.

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

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