Skip to main content

ket/c_api/
process.rs

1// SPDX-FileCopyrightText: 2026 Evandro Chagas Ribeiro da Rosa <evandro@quantuloop.com>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5#![allow(clippy::missing_safety_doc, clippy::not_unsafe_ptr_arg_deref)]
6
7//! C API for interacting with `Process` operations.
8
9use std::ffi::{c_char, CStr, CString};
10
11use crate::{
12    c_api::{c_error, c_ok},
13    error::KetError,
14    ir::{block::BasicBlock, gate::Param, hamiltonian::Hamiltonian},
15    process::{Process, ProcessState, QPUConfig},
16};
17
18/// Allocates a new [`Process`] using the provided QPU configuration.
19///
20/// `qpu_config` is consumed by this call: ownership is transferred to the
21/// newly created `Process` and the pointer must not be used afterwards.
22///
23/// # Parameters
24/// - `qpu_config`: Pointer to a [`QPUConfig`].
25/// - `process`: Output: receives the pointer to the newly allocated
26///   [`Process`]. Must eventually be freed with [`ket_process_delete`].
27///
28/// # Returns
29/// `0` on success, non-zero error code on failure.
30#[no_mangle]
31pub extern "C" fn ket_process_new(qpu_config: *mut QPUConfig, process: &mut *mut Process) -> i32 {
32    let qpu_config = unsafe { *Box::from_raw(qpu_config) };
33    *process = Box::into_raw(Box::new(Process::new(qpu_config)));
34    c_ok()
35}
36
37/// Allocates a new [`QPUConfig`] for a QPU with the specified number of physical qubits.
38///
39/// The newly created configuration has **no execution backend** attached. A
40/// `QuantumExecution` backend must be set (via `ket_quantum_execution_live`
41/// or `ket_quantum_execution_batch`) before passing the config to
42/// [`ket_process_new`].
43///
44/// # Parameters
45/// - `num_qubits`: The number of physical qubits available on the QPU.
46/// - `qpu_config`: Output: receives the pointer to the newly allocated
47///   [`QPUConfig`]. Ownership is transferred to the caller until it is
48///   consumed by [`ket_process_new`].
49///
50/// # Returns
51/// `0` on success, non-zero error code on failure.
52#[no_mangle]
53pub extern "C" fn ket_config_new(num_qubits: usize, qpu_config: &mut *mut QPUConfig) -> i32 {
54    *qpu_config = Box::into_raw(Box::new(QPUConfig {
55        num_qubits,
56        quantum_execution: None,
57    }));
58    c_ok()
59}
60
61/// Deallocates the memory associated with a [`Process`].
62///
63/// # Parameters
64/// - `process`: Pointer to the [`Process`] to free.
65///
66/// # Returns
67/// `0` on success, non-zero error code on failure.
68#[no_mangle]
69pub extern "C" fn ket_process_delete(process: *mut Process) -> i32 {
70    unsafe {
71        let _ = Box::from_raw(process);
72    }
73    c_ok()
74}
75
76/// Allocates a new logical qubit in the process.
77///
78/// Returns an error if the process is in a state that does not accept new
79/// qubits (e.g., `ProcessTerminated`) or if the QPU qubit limit has been
80/// reached (`QubitLimitExceeded`).
81///
82/// # Parameters
83/// - `process`: The process in which the qubit is allocated.
84/// - `qubit`: Output: receives the index of the newly allocated logical qubit.
85///
86/// # Returns
87/// `0` on success, non-zero error code on failure (including
88/// `ProcessTerminated` and `QubitLimitExceeded`).
89#[no_mangle]
90pub extern "C" fn ket_process_alloc(process: &mut Process, qubit: &mut usize) -> i32 {
91    *qubit = c_error!(process.alloc());
92    c_ok()
93}
94
95/// Appends a [`BasicBlock`] of gates to the process.
96///
97/// The `block` is **consumed** by this call and must not be used afterwards.
98/// Returns an error if the process state does not allow gate appending
99/// (`GateAppendForbidden`) or if a qubit index referenced in the block is
100/// out of range (`QubitIndexOutOfRange`).
101///
102/// # Parameters
103/// - `process`: The process to which the block is appended.
104/// - `block`: The block to consume and append. Ownership is transferred.
105///
106/// # Returns
107/// `0` on success, non-zero error code on failure (including
108/// `GateAppendForbidden` and `QubitIndexOutOfRange`).
109#[no_mangle]
110pub extern "C" fn ket_process_append_block(process: &mut Process, block: *mut BasicBlock) -> i32 {
111    let block = unsafe { *Box::from_raw(block) };
112    c_error(process.append_block(block))
113}
114
115/// Returns the accumulated gate instructions of the process as a JSON string.
116///
117/// The returned string is a JSON array of gate instruction objects representing
118/// the gates in the process's main block. The string is heap-allocated and the
119/// caller **must** free it with [`super::ket_string_delete`].
120///
121/// # Parameters
122/// - `process`: The process whose gate list is serialized.
123/// - `block`: Output: set to a pointer to a heap-allocated null-terminated
124///   JSON string.
125///
126/// # Returns
127/// `0` on success, non-zero error code on failure.
128#[no_mangle]
129pub extern "C" fn ket_process_gates_json(process: &Process, block: &mut *mut c_char) -> i32 {
130    *block = to_json!(&process.main_block().gates).into_raw();
131    c_ok()
132}
133
134/// Performs a single-shot mid-circuit measurement of `qubits`.
135///
136/// This function is only valid in **live mode**. Any gates accumulated since
137/// the last measurement are dispatched to the backend before the measurement
138/// is performed.
139///
140/// # Parameters
141/// - `process`: The process on which the measurement is performed.
142/// - `qubits`: Pointer to an array of qubit indices to measure.
143/// - `qubits_len`: Number of elements in `qubits`.
144/// - `result`: Output: the measurement result encoded as a bitmask
145///   (bit `i` corresponds to `qubits[i]`).
146///
147/// # Returns
148/// `0` on success, non-zero error code on failure.
149#[no_mangle]
150pub extern "C" fn ket_process_measure(
151    process: &mut Process,
152    qubits: *const usize,
153    qubits_len: usize,
154    result: &mut u64,
155) -> i32 {
156    let qubits = unsafe { std::slice::from_raw_parts(qubits, qubits_len) };
157    *result = c_error!(process.measure(qubits));
158    c_ok()
159}
160
161/// Dumps the quantum state of `qubits` (live mode only).
162///
163/// Returns the full probability amplitude vector for the specified qubits as a
164/// heap-allocated JSON string. The caller **must** free it with
165/// [`super::ket_string_delete`].
166///
167/// # Parameters
168/// - `process`: The process whose state is dumped.
169/// - `qubits`: Pointer to an array of qubit indices to dump.
170/// - `qubits_len`: Number of elements in `qubits`.
171/// - `dump_json`: Output: set to a pointer to a heap-allocated
172///   null-terminated JSON string encoding the quantum state.
173///
174/// # Returns
175/// `0` on success, non-zero error code on failure.
176#[no_mangle]
177pub extern "C" fn ket_process_dump(
178    process: &mut Process,
179    qubits: *const usize,
180    qubits_len: usize,
181    dump_json: &mut *const c_char,
182) -> i32 {
183    let qubits = unsafe { std::slice::from_raw_parts(qubits, qubits_len) };
184    *dump_json = to_json!(&c_error!(process.dump(qubits))).into_raw();
185
186    c_ok()
187}
188
189/// Requests a measurement sample of `qubits` over `shots` repetitions.
190///
191/// In **live mode**, the circuit is executed immediately and the result is
192/// returned as a heap-allocated JSON string (caller must free with
193/// [`super::ket_string_delete`]). In **batch mode**, the request is recorded for the
194/// next [`ket_process_execute`] call and `*sample_json` is set to a JSON
195/// `null` string; the actual data becomes available after execution and can be
196/// retrieved with [`ket_process_read_sample`].
197///
198/// # Parameters
199/// - `process`: The process on which the sample is requested.
200/// - `qubits`: Pointer to an array of qubit indices to sample.
201/// - `qubits_len`: Number of elements in `qubits`.
202/// - `shots`: Number of repetitions.
203/// - `sample_json`: Output: set to a pointer to a heap-allocated
204///   null-terminated JSON string (caller must free with [`super::ket_string_delete`]).
205///
206/// # Returns
207/// `0` on success, non-zero error code on failure.
208#[no_mangle]
209pub extern "C" fn ket_process_sample(
210    process: &mut Process,
211    qubits: *const usize,
212    qubits_len: usize,
213    shots: usize,
214    sample_json: &mut *const c_char,
215) -> i32 {
216    let qubits = unsafe { std::slice::from_raw_parts(qubits, qubits_len) };
217    *sample_json = to_json!(&c_error!(process.sample(qubits, shots))).into_raw();
218    c_ok()
219}
220
221/// Returns a serialized JSON array of expectation values from the most recent execution.
222///
223/// If no execution has been performed yet, the returned JSON string contains
224/// `null`. The string is heap-allocated; the caller **must** free it with
225/// [`super::ket_string_delete`].
226///
227/// # Parameters
228/// - `process`: The process from which expectation values are read.
229/// - `result_json`: Output: set to a pointer to a heap-allocated
230///   null-terminated JSON string.
231///
232/// # Returns
233/// `0` on success, non-zero error code on failure.
234#[no_mangle]
235pub extern "C" fn ket_process_read_exp_value(
236    process: &Process,
237    result_json: &mut *const c_char,
238) -> i32 {
239    *result_json = to_json!(&process.read_exp_value()).into_raw();
240    c_ok()
241}
242
243/// Returns a serialized JSON array of parameter-shift gradients from the most recent execution.
244///
245/// If no execution has been performed yet, the returned JSON string contains
246/// `null`. The string is heap-allocated; the caller **must** free it with
247/// [`super::ket_string_delete`].
248///
249/// # Parameters
250/// - `process`: The process from which gradients are read.
251/// - `result_json`: Output: set to a pointer to a heap-allocated
252///   null-terminated JSON string.
253///
254/// # Returns
255/// `0` on success, non-zero error code on failure.
256#[no_mangle]
257pub extern "C" fn ket_process_read_gradient(
258    process: &Process,
259    result_json: &mut *const c_char,
260) -> i32 {
261    *result_json = to_json!(&process.read_gradient()).into_raw();
262    c_ok()
263}
264
265/// Registers a new differentiable parameter with the given initial value.
266///
267/// The returned `param_index` uniquely identifies this parameter within the
268/// process. Use it to construct `Param::Ref` values when building
269/// parameterised gate instructions. When the parameter value changes between
270/// executions, gates that reference the same index are automatically updated
271/// during the next compilation pass.
272///
273/// # Parameters
274/// - `process`: The process in which the parameter is registered.
275/// - `param`: The initial numeric value of the parameter.
276/// - `param_index`: Output: receives the index that identifies this parameter
277///   within the process.
278///
279/// # Returns
280/// `0` on success, non-zero error code on failure.
281#[no_mangle]
282pub extern "C" fn ket_process_param(
283    process: &mut Process,
284    param: f64,
285    param_index: &mut usize,
286) -> i32 {
287    let param = process.param(param);
288    if let Param::Ref { index, .. } = param {
289        *param_index = index;
290    }
291    c_ok()
292}
293
294/// Compiles and executes the accumulated circuit.
295///
296/// This is the primary entry point for **batch mode** execution. Calling it in
297/// live mode returns an `ExplicitExecuteInLiveMode` error. If no pending
298/// measurement or expectation-value requests have been recorded,
299/// `NoPendingMeasurement` is returned. If the process is already in the
300/// `Terminated` state, this call is idempotent and returns success.
301///
302/// # Parameters
303/// - `process`: The process to compile and execute.
304///
305/// # Returns
306/// `0` on success, non-zero error code on failure (including
307/// `NoPendingMeasurement` and `ExplicitExecuteInLiveMode`).
308#[no_mangle]
309pub extern "C" fn ket_process_execute(process: &mut Process) -> i32 {
310    c_error(process.execute())
311}
312
313/// Reads the cached sample data from a [`Process`].
314///
315/// Returns the sample counts recorded during the most recent execution as a
316/// heap-allocated JSON string. If no execution has been performed yet, the
317/// returned JSON string contains `null`. The caller **must** free the string
318/// with [`super::ket_string_delete`].
319///
320/// # Parameters
321/// - `process`: The process from which sample data is read.
322/// - `sample_json`: Output: set to a pointer to a heap-allocated
323///   null-terminated JSON string.
324///
325/// # Returns
326/// `0` on success, non-zero error code on failure.
327#[no_mangle]
328pub extern "C" fn ket_process_read_sample(
329    process: &mut Process,
330    sample_json: &mut *const c_char,
331) -> i32 {
332    *sample_json = to_json!(&process.read_sample()).into_raw();
333
334    c_ok()
335}
336
337/// Requests the expectation value computation for a given [`Hamiltonian`] (passed as JSON) on a [`Process`].
338///
339/// In **live mode**, the expectation value is computed immediately:
340/// `*some_result` is set to `true` and the value is written to `*result`.
341/// In **batch mode**, the request is recorded for the next
342/// [`ket_process_execute`] call: `*some_result` is set to `false` and
343/// `*result` is left unmodified until execution completes and
344/// [`ket_process_read_exp_value`] is called.
345///
346/// # Parameters
347/// - `process`: The process on which the expectation value is computed.
348/// - `hamiltonian_json`: A null-terminated JSON string encoding the
349///   Hamiltonian.
350/// - `result`: Output: the computed expectation value (valid only when
351///   `*some_result` is `true`).
352/// - `some_result`: Output: set to `true` if the expectation value was
353///   computed immediately (live mode), or `false` if the request was
354///   deferred (batch mode).
355///
356/// # Returns
357/// `0` on success, non-zero error code on failure.
358#[no_mangle]
359pub extern "C" fn ket_process_exp_value(
360    process: &mut Process,
361    hamiltonian_json: *const c_char,
362    result: &mut f64,
363    some_result: &mut bool,
364) -> i32 {
365    let hamiltonian = from_json!(hamiltonian_json, Hamiltonian);
366    match c_error!(process.exp_value(hamiltonian)) {
367        Some(value) => {
368            *result = value;
369            *some_result = true;
370        }
371        None => {
372            *some_result = false;
373        }
374    }
375    c_ok()
376}
377
378/// Converts a [`ProcessState`] into the corresponding static C string.
379///
380/// Maps each variant to a null-terminated `&'static CStr`:
381/// - [`ProcessState::AcceptingGate`] → `"AcceptingGate"`
382/// - [`ProcessState::AcceptingHamiltonian`] → `"AcceptingHamiltonian"`
383/// - [`ProcessState::ReadyToExecute`] → `"ReadyToExecute"`
384/// - [`ProcessState::Terminated`] → `"Terminated"`
385///
386/// The returned pointer is valid for the lifetime of the program and must
387/// **not** be freed.
388impl From<ProcessState> for &'static CStr {
389    fn from(value: ProcessState) -> Self {
390        match value {
391            ProcessState::AcceptingGate => c"AcceptingGate",
392            ProcessState::AcceptingHamiltonian => c"AcceptingHamiltonian",
393            ProcessState::ReadyToExecute => c"ReadyToExecute",
394            ProcessState::Terminated => c"Terminated",
395        }
396    }
397}
398
399/// Returns the current status of the process as a static C string.
400///
401/// The string is one of:
402/// - `"AcceptingGate"`: the process is ready to accept gate blocks.
403/// - `"AcceptingHamiltonian"`: all gates have been appended; expectation-value
404///   requests can now be submitted.
405/// - `"ReadyToExecute"`: execution has been requested and the circuit is ready.
406/// - `"Terminated"`: execution has completed and results are available.
407///
408/// The returned pointer points to a static string and must **not** be freed.
409///
410/// # Parameters
411/// - `process`: The process whose status is queried.
412/// - `status`: Output: set to a pointer to a static null-terminated string.
413///
414/// # Returns
415/// `0` on success, non-zero error code on failure.
416#[no_mangle]
417pub extern "C" fn ket_process_status(process: &Process, status: &mut *const c_char) -> i32 {
418    let process_status: &CStr = process.status().into();
419    *status = process_status.as_ptr();
420    c_ok()
421}
422
423/// Registers a new parameter with the given initial value in the process.
424///
425/// The parameter's index within the process parameter list is written to `index`.
426///
427/// # Parameters
428/// - `process`: The process to register the parameter in.
429/// - `value`: The initial value of the parameter.
430/// - `index`: Output: set to the registered parameter's index.
431///
432/// # Returns
433/// `0` on success, non-zero error code on failure.
434#[no_mangle]
435pub extern "C" fn ket_process_set_parameter(
436    process: &mut Process,
437    value: f64,
438    index: &mut usize,
439) -> i32 {
440    let Param::Ref {
441        index: param_index, ..
442    } = process.param(value)
443    else {
444        unreachable!()
445    };
446    *index = param_index;
447
448    c_ok()
449}