Repository: Qiskit/qiskit
Pull request #14347: Add C API function to add a delay 
Author: login: kt474
Labels: name: Changelog: Added
description: Add an "Added" entry in the GitHub Release changelog.
Created: 2025-05-11T22:45:55Z | Merged: 2025-05-13T08:01:13Z

<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [ ] I have added the tests to cover my changes.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the CONTRIBUTING document.
-->

### Summary

fixes #14237 

### Details and comments




Linked issue #14237: Add C API function to add a delay to a circuit
### What should we add?

Since #14006 merged we now have a representation of a circuit in the C API. However in that PR one component we didn't include was the function to add a delay to the circuit. This should be fairly straightforward but it wasn't included in that PR because there was a question about how we deal with units. At the time I was thinking we should discuss whether we want to support all the various SI prefixes or just take seconds and dt and save it for a follow-up. But also now since #14006 was originally opened we also now have support for stretches in Rust too, so we can potentially expose that via the C API too (but that can also be a separate task to not overly complicate this).
Linked issue comments: user: login: kt474
created_at: 2025-04-25T16:52:40Z
body: @kt474 
author_association: CONTRIBUTOR

Diff:
diff --git a/crates/cext/src/circuit.rs b/crates/cext/src/circuit.rs
index 9a13f01d3917..a4200ddece37 100644
--- a/crates/cext/src/circuit.rs
+++ b/crates/cext/src/circuit.rs
@@ -17,7 +17,7 @@ use crate::pointers::{const_ptr_as_ref, mut_ptr_as_ref};
 
 use qiskit_circuit::bit::{ShareableClbit, ShareableQubit};
 use qiskit_circuit::circuit_data::CircuitData;
-use qiskit_circuit::operations::{Operation, Param, StandardGate, StandardInstruction};
+use qiskit_circuit::operations::{DelayUnit, Operation, Param, StandardGate, StandardInstruction};
 use qiskit_circuit::packed_instruction::PackedOperation;
 use qiskit_circuit::{Clbit, Qubit};
 
@@ -612,3 +612,73 @@ pub unsafe extern "C" fn qk_circuit_to_python(circuit: *mut CircuitData) -> *mut
             .into_ptr()
     }
 }
+
+/// @ingroup QkCircuit
+///
+/// Units for circuit delays.
+#[repr(u8)]
+pub enum QkDelayUnit {
+    /// Seconds.
+    S = 0,
+    /// Milliseconds.
+    MS = 1,
+    /// Microseconds.
+    US = 2,
+    /// Nanoseconds.
+    NS = 3,
+    /// Picoseconds.
+    PS = 4,
+    /// Device-native time unit ``dt``.
+    DT = 5,
+}
+
+/// @ingroup QkCircuit
+/// Append a delay instruction to the circuit.
+///
+/// @param circuit A pointer to the circuit to add the delay to.
+/// @param qubit The ``uint32_t`` index of the qubit to apply the delay to.
+/// @param duration The duration of the delay.
+/// @param unit An enum representing the unit of the duration.
+///
+/// @return An exit code.
+///
+/// # Example
+///
+///     QkCircuit *qc = qk_circuit_new(1, 0);
+///     qk_circuit_delay(qc, 0, 100.0, QkDelayUnit_NS);
+///     
+/// # Safety
+///
+/// Behavior is undefined if ``circuit`` is not a valid, non-null pointer to a ``QkCircuit``.
+#[no_mangle]
+#[cfg(feature = "cbinding")]
+pub unsafe extern "C" fn qk_circuit_delay(
+    circuit: *mut CircuitData,
+    qubit: u32,
+    duration: f64,
+    unit: QkDelayUnit,
+) -> ExitCode {
+    // SAFETY: Per documentation, the pointer is non-null and aligned.
+    let circuit = unsafe { mut_ptr_as_ref(circuit) };
+
+    let delay_unit_variant = match unit {
+        QkDelayUnit::S => DelayUnit::S,
+        QkDelayUnit::MS => DelayUnit::MS,
+        QkDelayUnit::US => DelayUnit::US,
+        QkDelayUnit::NS => DelayUnit::NS,
+        QkDelayUnit::PS => DelayUnit::PS,
+        QkDelayUnit::DT => DelayUnit::DT,
+    };
+
+    let duration_param: Param = duration.into();
+    let delay_instruction = StandardInstruction::Delay(delay_unit_variant);
+
+    circuit.push_packed_operation(
+        PackedOperation::from_standard_instruction(delay_instruction),
+        &[duration_param],
+        &[Qubit(qubit)],
+        &[],
+    );
+
+    ExitCode::Success
+}
diff --git a/releasenotes/notes/add-delay-c-api-d040c3f8b60b295b.yaml b/releasenotes/notes/add-delay-c-api-d040c3f8b60b295b.yaml
new file mode 100644
index 000000000000..b230ab75df1c
--- /dev/null
+++ b/releasenotes/notes/add-delay-c-api-d040c3f8b60b295b.yaml
@@ -0,0 +1,9 @@
+---
+features_circuits:
+  - |
+    There is a now a C API function, ``qk_circuit_delay`` for adding a delay to a circuit.
+
+    .. code-block:: c
+
+      QkCircuit *qc = qk_circuit_new(1, 0);
+      qk_circuit_delay(qc, 0, 100.0, QkDelayUnit_NS);
diff --git a/test/c/test_circuit.c b/test/c/test_circuit.c
index 6fbd79bfb53b..78f88c0a82f8 100644
--- a/test/c/test_circuit.c
+++ b/test/c/test_circuit.c
@@ -558,6 +558,30 @@ int test_get_gate_counts_bv_resets_barrier_and_measures(void) {
     return result;
 }
 
+int test_delay_instruction(void) {
+    QkCircuit *qc = qk_circuit_new(2, 0);
+    int result = Ok;
+
+    QkExitCode delay_dt_code;
+    QkExitCode delay_s_code;
+
+    delay_dt_code = qk_circuit_delay(qc, 1, 10, QkDelayUnit_DT);
+    if (delay_dt_code != QkExitCode_Success) {
+        result = RuntimeError;
+        goto cleanup;
+    }
+
+    delay_s_code = qk_circuit_delay(qc, 0, 0.001, QkDelayUnit_S);
+    if (delay_s_code != QkExitCode_Success) {
+        result = RuntimeError;
+        goto cleanup;
+    }
+
+cleanup:
+    qk_circuit_free(qc);
+    return result;
+}
+
 int test_circuit(void) {
     int num_failed = 0;
     num_failed += RUN_TEST(test_empty);
@@ -568,6 +592,7 @@ int test_circuit(void) {
     num_failed += RUN_TEST(test_get_gate_counts_bv_resets_barrier_and_measures);
     num_failed += RUN_TEST(test_gate_num_qubits);
     num_failed += RUN_TEST(test_gate_num_params);
+    num_failed += RUN_TEST(test_delay_instruction);
 
     fflush(stderr);
     fprintf(stderr, "=== Number of failed subtests: %i\n", num_failed);


Reviews:
user: login: kt474
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T00:04:20Z
user: login: Cryoris
body: Thanks for tackling this! I left some comments below, mainly about making `unit` an enum 🙂 
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T07:09:46Z
user: login: kt474
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T13:22:15Z
user: login: Cryoris
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T14:26:54Z
user: login: Cryoris
body: LGTM thanks for the quick updates!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-13T06:33:33Z

Review comments:
diff_hunk: @@ -558,6 +558,27 @@ int test_get_gate_counts_bv_resets_barrier_and_measures(void) {
     return result;
 }
 
+int test_delay_instruction(void){
+    QkCircuit *qc = qk_circuit_new(2, 0)
+    int result = Ok
+
+    ExitCode delay_dt = qk_circuit_delay(qc, 1, 10, "dt");
+    if (delay_dt != Success) {
+        result = RuntimeError;
+        goto cleanup;
+    }
+
+    ExitCode delay_s = qk_circuit_delay(qc, 0, 0.001, "s");
+     if (delay_s != Success) {
+        result = RuntimeError;
+        goto cleanup;
+    }
+
+cleanup:
+    qk_circuit_free(qc);
+    return result;    
+}
+
path: test/c/test_circuit.c
user: login: kt474
body: I didn't have a chance to test this, couldn't get the c tests running locally 
created_at: 2025-05-12T00:04:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -605,3 +605,66 @@ pub unsafe extern "C" fn qk_circuit_to_python(circuit: *mut CircuitData) -> *mut
             .into_ptr()
     }
 }
+
+/// @ingroup QKCircuit
path: crates/cext/src/circuit.rs
user: login: Cryoris
body: ```suggestion
/// @ingroup QkCircuit
```
created_at: 2025-05-12T06:49:56Z
author_association: COLLABORATOR
diff_hunk: @@ -605,3 +605,66 @@ pub unsafe extern "C" fn qk_circuit_to_python(circuit: *mut CircuitData) -> *mut
             .into_ptr()
     }
 }
+
+/// @ingroup QKCircuit
+/// Append a delay instruction to the circuit.
+///
+/// @param circuit A pointer to the circuit to add the delay to.
+/// @param qubit The ``uint32_t`` index of the qubit to apply the delay to.
+/// @param duration The duration of the delay.
+/// @param unit A C string representing the unit of the duration.
path: crates/cext/src/circuit.rs
user: login: Cryoris
body: Could we make the unit be an enum? Similar to the `DelayUnit` in Rust (maybe without the `EXPR` part since classical expressions are not part of the C API yet). With an enum we don't have to do string comparisons and we can deal with one less pointer
created_at: 2025-05-12T07:06:07Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,4 @@
+---
+features_circuits:
+  - |
+    There is a now a C API function to add a delay to a circuit.
path: releasenotes/notes/add-delay-c-api-d040c3f8b60b295b.yaml
user: login: Cryoris
body: It would be nice to state out the function name here and add the small example from your docstring 🙂 
created_at: 2025-05-12T07:07:39Z
author_association: COLLABORATOR
diff_hunk: @@ -558,6 +558,27 @@ int test_get_gate_counts_bv_resets_barrier_and_measures(void) {
     return result;
 }
 
+int test_delay_instruction(void){
+    QkCircuit *qc = qk_circuit_new(2, 0)
+    int result = Ok
+
+    ExitCode delay_dt = qk_circuit_delay(qc, 1, 10, "dt");
+    if (delay_dt != Success) {
+        result = RuntimeError;
+        goto cleanup;
+    }
+
+    ExitCode delay_s = qk_circuit_delay(qc, 0, 0.001, "s");
+     if (delay_s != Success) {
+        result = RuntimeError;
+        goto cleanup;
+    }
+
+cleanup:
+    qk_circuit_free(qc);
+    return result;    
+}
+
path: test/c/test_circuit.c
user: login: Cryoris
body: What error are you seeing when trying to run the tests? Did you check the C API install guide here: https://docs.quantum.ibm.com/guides/install-c-api ?
created_at: 2025-05-12T07:09:21Z
author_association: COLLABORATOR
diff_hunk: @@ -558,6 +558,27 @@ int test_get_gate_counts_bv_resets_barrier_and_measures(void) {
     return result;
 }
 
+int test_delay_instruction(void){
+    QkCircuit *qc = qk_circuit_new(2, 0)
+    int result = Ok
+
+    ExitCode delay_dt = qk_circuit_delay(qc, 1, 10, "dt");
+    if (delay_dt != Success) {
+        result = RuntimeError;
+        goto cleanup;
+    }
+
+    ExitCode delay_s = qk_circuit_delay(qc, 0, 0.001, "s");
+     if (delay_s != Success) {
+        result = RuntimeError;
+        goto cleanup;
+    }
+
+cleanup:
+    qk_circuit_free(qc);
+    return result;    
+}
+
path: test/c/test_circuit.c
user: login: kt474
body: I didn't have `cmake` installed 😅 - tests run fine now
created_at: 2025-05-12T13:22:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -605,3 +605,65 @@ pub unsafe extern "C" fn qk_circuit_to_python(circuit: *mut CircuitData) -> *mut
             .into_ptr()
     }
 }
+
+/// @ingroup QkCircuit
+///
+/// C compatible enum for DelayUnit
+#[repr(C)]
+pub enum QkDelayUnit {
+    S,
+    MS,
+    US,
+    NS,
+    PS,
+    DT,
+}
path: crates/cext/src/circuit.rs
user: login: Cryoris
body: A few notes on this:
* I think we can make this `repr(u8)` since we don't have a lot of values
* it's good practice to fix the values here, otherwise the order of enum variants determines their values, which can be error-prone since these are just integers C-side
* I think we should write these docs for people that only use C as well. So e.g. "C compatible" is clear -- they will read this in the C API docs of Qiskit 🙂 
* It might be nice to explain the enum variants

```suggestion
/// @ingroup QkCircuit
///
/// Units for circuit delays.
#[repr(u8)]
pub enum QkDelayUnit {
    /// Seconds.
    S = 0,
    /// Milliseconds.
    MS = 1,
    /// Microseconds.
    US = 2,
    /// Nanoseconds.
    NS = 3,
    /// Picoseconds.
    PS = 4,
    /// Device-native time unit ``dt``.
    DT = 5,
}
```
created_at: 2025-05-12T14:24:14Z
author_association: COLLABORATOR
diff_hunk: @@ -605,3 +605,65 @@ pub unsafe extern "C" fn qk_circuit_to_python(circuit: *mut CircuitData) -> *mut
             .into_ptr()
     }
 }
+
+/// @ingroup QkCircuit
+///
+/// C compatible enum for DelayUnit
+#[repr(C)]
+pub enum QkDelayUnit {
+    S,
+    MS,
+    US,
+    NS,
+    PS,
+    DT,
+}
+
+/// @ingroup QkCircuit
+/// Append a delay instruction to the circuit.
+///
+/// @param circuit A pointer to the circuit to add the delay to.
+/// @param qubit The ``uint32_t`` index of the qubit to apply the delay to.
+/// @param duration The duration of the delay.
+/// @param unit A C enum representing the unit of the duration.
path: crates/cext/src/circuit.rs
user: login: Cryoris
body: ```suggestion
/// @param unit An enum representing the unit of the duration.
```
created_at: 2025-05-12T14:24:23Z
author_association: COLLABORATOR
diff_hunk: @@ -605,3 +605,65 @@ pub unsafe extern "C" fn qk_circuit_to_python(circuit: *mut CircuitData) -> *mut
             .into_ptr()
     }
 }
+
+/// @ingroup QkCircuit
+///
+/// C compatible enum for DelayUnit
+#[repr(C)]
+pub enum QkDelayUnit {
+    S,
+    MS,
+    US,
+    NS,
+    PS,
+    DT,
+}
+
+/// @ingroup QkCircuit
+/// Append a delay instruction to the circuit.
+///
+/// @param circuit A pointer to the circuit to add the delay to.
+/// @param qubit The ``uint32_t`` index of the qubit to apply the delay to.
+/// @param duration The duration of the delay.
+/// @param unit A C enum representing the unit of the duration.
+///
+/// # Example
+///
+///     QkCircuit *qc = qk_circuit_new(1, 0);
+///     qk_circuit_delay(qc, 0, 100.0, QkDelayUnit_NS);
+///     
+/// # Safety
+///
+/// Behavior is undefined if ``circuit`` is not a valid, non-null pointer to a ``QkCircuit``.
+#[no_mangle]
+#[cfg(feature = "cbinding")]
+pub unsafe extern "C" fn qk_circuit_delay(
+    circuit: *mut CircuitData,
+    qubit: u32,
+    duration: f64,
+    unit: QkDelayUnit,
+) -> ExitCode {
+    // SAFETY: Per documentation, the pointer is non-null and aligned.
+    let circuit = unsafe { mut_ptr_as_ref(circuit) };
+
+    let delay_unit_variant = match unit {
+        QkDelayUnit::S => DelayUnit::S,
path: crates/cext/src/circuit.rs
user: login: Cryoris
body: We typically don't add the `Qk` prefix to the structs manually (see e.g. `SparseTerm` or `OpCounts` in the `cext` crate), and have `cbindgen` add this prefix. It would be more consistent to do the same here, but I'm not super set on this 🙂 
created_at: 2025-05-12T14:26:12Z
author_association: COLLABORATOR
diff_hunk: @@ -605,3 +605,65 @@ pub unsafe extern "C" fn qk_circuit_to_python(circuit: *mut CircuitData) -> *mut
             .into_ptr()
     }
 }
+
+/// @ingroup QkCircuit
+///
+/// C compatible enum for DelayUnit
+#[repr(C)]
+pub enum QkDelayUnit {
+    S,
+    MS,
+    US,
+    NS,
+    PS,
+    DT,
+}
+
+/// @ingroup QkCircuit
+/// Append a delay instruction to the circuit.
+///
+/// @param circuit A pointer to the circuit to add the delay to.
+/// @param qubit The ``uint32_t`` index of the qubit to apply the delay to.
+/// @param duration The duration of the delay.
+/// @param unit A C enum representing the unit of the duration.
+///
path: crates/cext/src/circuit.rs
user: login: Cryoris
body: This misses the `@return` doc
created_at: 2025-05-12T14:26:27Z
author_association: COLLABORATOR

PR comments:
user: login: qiskit-bot
created_at: 2025-05-11T22:45:58Z
body: 
One or more of the following people are relevant to this code:
- `@Qiskit/terra-core`

author_association: COLLABORATOR
user: login: coveralls
created_at: 2025-05-11T23:13:59Z
body: ## Pull Request Test Coverage Report for [Build 14981318567](https://coveralls.io/builds/73631814)


### Details

* **23** of **27**   **(85.19%)**  changed or added relevant lines in **1** file are covered.
* **7** unchanged lines in **2** files lost coverage.
* Overall coverage increased (+**0.001%**) to **88.736%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [crates/cext/src/circuit.rs](https://coveralls.io/builds/73631814/source?filename=crates%2Fcext%2Fsrc%2Fcircuit.rs#L666) | 23 | 27 | 85.19%
<!-- | **Total:** | **23** | **27** | **85.19%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [crates/qasm2/src/lex.rs](https://coveralls.io/builds/73631814/source?filename=crates%2Fqasm2%2Fsrc%2Flex.rs#L207) | 1 | 92.98% |
| [crates/qasm2/src/parse.rs](https://coveralls.io/builds/73631814/source?filename=crates%2Fqasm2%2Fsrc%2Fparse.rs#L607) | 6 | 97.61% |
<!-- | **Total:** | **7** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/73631814/badge)](https://coveralls.io/builds/73631814) |
| :-- | --: |
| Change from base [Build 14980695016](https://coveralls.io/builds/73631193): |  0.001% |
| Covered Lines: | 76094 |
| Relevant Lines: | 85753 |

---
##### 💛  - [Coveralls](https://coveralls.io)

author_association: NONE