Skip to main content
IBM Quantum Platform

QkCustomOp

typedef struct QkCustomOp QkCustomOp

A representation of an operation that has been defined in C.

This operation object contains the minimal functionality an object should adhere to in order operate on a QkCircuit.

Any object that can be implemented using QkCustomOp will be dynamically dispatched to be added to the circuit. In other words, the circuit is unaware of the type of object it is accepting, but it will work with it as long as it has the functionality expected from any operation.

To achieve this, an operation is defined by two parts: - The original pointer to the operation struct. - The pointer to a vtable with the function slots that define the functionality of this operation. See qk_custom_op_vtable_new() for more details.

Here’s a quick example of what that looks like:

// Define an operation with a single attribute.
struct foo_gate {
   uint32_t num_qubits;
};
// Represents the name of the operation.
const char *FOO_NAME = "foo";
// Design the required methods for the vtable.
const char *foo_name(const void *gate) {
   // Cast void to original pointer.
   struct foo_gate *_self = (struct foo_gate *)gate;
   // Cast once more to consume it
   (void)_self;
   return FOO_NAME;
}
uint32_t foo_num_qubits(const void *gate) {
   struct foo_gate *self = (struct foo_gate *)gate;
   // Used stored attirbute as return value.
   return self->num_qubits;
}
// Use same logic below for required methods that have
// fixed values.
uint32_t foo_num_clbits(const void *gate) {
   struct foo_gate *_self = (struct foo_gate *)gate;
   (void)_self;
   return 0;
}
// Implement all required methods.
// Build list of entries for the vtable (at least 7 required entries)
QkCustomOpVtableEntry entries[7] = {
   {.slot = 0, .func = foo_name},
   {.slot = 1, .func = foo_num_qubits},
   {.slot = 2, .func = foo_num_clbits},
   // ...
   // End with sentinel value
   {.slot = -1, .func = NULL},
};
// Create a vtable
QkCustomOpVtable *foo_vtable = qk_custom_op_vtable_new(entries);
// Declare a sample instance
struct foo_gate foo_3q = {
   .num_qubits = 3,
};
// Create the custom operation
QkCustomOp *foo_3q_custom = qk_custom_op_new(&foo_3q, foo_vtable);
// Add to a circuit
QkCircuit *circuit = qk_circuit_new(3, 0);
uint32_t qubits[3] = {0, 1, 2};
qk_circuit_custom_operation(circuit, foo_3q_custom, qubits, NULL, NULL);

Data Types

QkCustomOpSlot

enum QkCustomOpSlot

Represents the Vtable index of a QkCustomOp coming from the C domain.

Each named index refers to a required/optional method of the Operation and CustomOperation traits in Rust.

Values:

enumerator QkCustomOpSlot_Name

enumerator QkCustomOpSlot_NumQubits

enumerator QkCustomOpSlot_NumClbits

enumerator QkCustomOpSlot_NumParams

enumerator QkCustomOpSlot_Directive

enumerator QkCustomOpSlot_IsUnitary

enumerator QkCustomOpSlot_NumCtrlQubits

enumerator QkCustomOpSlot_Label

enumerator QkCustomOpSlot_Definition

enumerator QkCustomOpSlot_Eq

enumerator QkCustomOpSlot_Clone

enumerator QkCustomOpSlot_Delete


Functions

qk_custom_operation_new

QkCustomOp *qk_custom_operation_new(void *operation, const QkCustomOpVtable *v_table)

Builds a QkCustomOp based on a quantum operation fully defined in C.

Here’s a quick example of what that looks like:

// Define an operation with a single attribute.
struct foo_gate {
    uint32_t num_qubits;
}

// Implement all required methods
uint32_t foo_num_qubits(const void *gate) {
struct foo_gate *self = (struct foo_gate *)gate;
// Used stored attirbute as return value.
return self->num_qubits;
}

// Build list of entries for the vtable (at least 7 required entries)
QkVtableEntry entries[7] = {
    {.slot = QkCustomOpSlot_NumQubits, .func = foo_num_qubits},
// ...
// End with sentinel value
    {.slot = -1, .func = NULL},
};

// Create a vtable
QkCustomOpVtable *foo_vtable = qk_custom_operation_vtable_new(entries);

// Declare a sample instance
struct foo_gate foo_3q = {
    .num_qubits = 3,
};

// Create the custom operation
QkCustomOp foo_3q_custom = qk_custom_operation_new(&foo_3q, foo_vtable);

Safety

It falls on the responsibility of the implementors to ensure that the data enclosed in the operation struct can:

  • Be accessed safely by multiple threads concurrently.
  • Be immutably borrowed by other threads without causing race conditions.
  • Be preserved throughout the lifetime of the operation.

Behavior is undefined if the provided v_table pointer is null or non-aligned.

Failure to comply with these conditions may result in undefined behavior.

Parameters

  • operation – An owned pointer to the operation struct.
  • v_table – A pointer to a correctly constructed v_table designed to work with the data of the struct operation points to.

Returns

A pointer to QkCustomOp.

qk_custom_operation_free

void qk_custom_operation_free(QkCustomOp *data)

Free an owned :c:type:QkCustomOp.

You typically do not need to call this, because :c:func:qk_circuit_custom_operation takes ownership of the object.

This function is a no-op if given NULL.

Safety

Behavior is undefined if data is not null or a valid owning pointer to a :c:type:QkCustomOp.

Parameters

  • data – An owning pointer to data to free.

qk_custom_operation_vtable_new

const QkCustomOpVtable *qk_custom_operation_vtable_new(const QkVtableEntry *slots)

Builds a QkCustomOpVtable based on a list of QkVtableEntry instances.

The vtable is built from a collection of slots that hold an index and a pointer to a function of the correct argument and return types.

Refer to the following table to identify the correct slots.

Slot
Arg(s) type
Return type
Index
Required
Default
nameconst void *char *QkCustomOpSlot_NameYesn/a
num_qubitsconst void *uint32_tQkCustomOpSlot_NumQubitsYesn/a
num_clbitsconst void *uint32_tQkCustomOpSlot_NumClbitsNo0
num_paramsconst void *uint32_tQkCustomOpSlot_NumParamsNo0
directiveconst void *boolQkCustomOpSlot_DirectiveNofalse
is_unitaryconst void *boolQkCustomOpSlot_IsUnitaryNotrue
num_ctrl_qubitsconst void *uint32_tQkCustomOpSlot_NumCtrlQubitsNo0
labelconst void *char *QkCustomOpSlot_LabelNoNULL
definitionconst void *, QkParam **QkCircuit *QkCustomOpSlot_DefinitionNoNULL
eqconst void *, const void *boolQkCustomOpSlot_EqNoPointer comparison
cloneconst void *void *QkCustomOpSlot_CloneNopointer alias
deletevoid *voidQkCustomOpSlot_DeleteNono-op

Each function will be seen as a void pointer to Rust and will be transmuted to a function pointer of the correct signature.

If a required slot is not received, the vtable will not be constructed and this function will return a NULL pointer. If an optional slot is not included, the vtable will still be built and its slots will point to default implementations of the said method(s).

If a slot does not have a valid index (other than the sentinel value), the provided function pointer will be ignored. This ensures that if any non-required methods are added or removed from the chart above, the program should still be able to run without issues.

Every list of slots should be delimited by a sentinel valued QkVtableEntry at the end. The sentinel should look as follows:

QkVtableEntry sentinel = {.slot = -1, .func = NULL};

This function will stop reading any slots located after the sentinel is found.

Safety

Behavior is undefined if a list of entries without delimiting sentinel value are provided.

Undefined behavior may also happen during transmutation if the provided function pointer does not have the correct signature.

Parameters

  • slots – A pointer to a list of entries delimited by an entry with a sentinel value.

Returns

A pointer to a constructed vtable or a null pointer if any required entries are absent.

qk_custom_operation_vtable_free

void qk_custom_operation_vtable_free(const QkCustomOpVtable *v_table)

Frees the QkCustomOpVtable pointer

Safety

Undefined behavior may occur if v_table is a NULL or unaligned invalid pointer to a QkCustomOpVtable.

Parameters

  • v_table – The pointer to a QkCustomOpVtable object.
Was this page helpful?
Report a bug, typo, or request content on GitHub.