# Using Experimental Simulators from C This module exposes a C API for this crate, useful for embedding into simulation runtimes. ## Safety As this is a foreign-function interface, many of the functions exposed here are **unsafe**, representing that the caller is required to ensure that safety conditions in the host language are upheld. Please pay attention to any listed safety notes when calling into this C API. ## Generating and Using C API Headers The [`qdk_sim`](..) crate has enabled the use of [`cbindgen`](https://crates.io/crates/cbindgen), such that C-language header files are generated automatically as part of the build for this crate. ```bash cargo install --force cbindgen cbindgen --language C --output include/qdk_sim.h cbindgen --language C++ --output include/qdk_sim.hpp ``` This will generate `include/qdk_sim.h` and `include/qdk_sim.hpp`, which can then be used from C and C++ callers, respectively. For example, to call from C: ```c #include #include "qdk_sim.h" int main() { uintptr_t sim_id; uintptr_t result0, result1; init(2, "mixed", &sim_id); h(sim_id, 0); h(sim_id, 1); m(sim_id, 0, &result0); m(sim_id, 1, &result1); printf("got %llu %llu", result0, result1); destroy(sim_id); } ``` To build and run the above example using Clang on Windows: ```bash $ clang example.c -Iinclude -Ltarget/debug -lqdk_sim -lws2_32 -lAdvapi32 -lUserenv $ ./a.exe got 1 1 ``` ## Error Handling and Return Values Most C API functions for this crate return an integer, with `0` indicating success and any other value indicating failure. In the case that a non-zero value is returned, API functions will also set the last error message, accessible by calling [`lasterr`]: ```c #include #include "qdk_sim.h" int main() { uintptr_t sim_id; uintptr_t result0, result1; if (init(2, "invalid", &sim_id) != 0) { printf("Got an error message: %s", lasterr()); } else { destroy(sim_id); } } ``` C API functions that need to return data to the caller, such as [`m`], do so by accepting pointers to memory where results should be stored. > **⚠ WARNING**: It is the caller's responsibility to ensure that pointers used to hold results are valid (that is, point to memory that can be safely written into). For example: ```c uintptr_t result; if (m(sim_id, 0, &result) != 0) { printf("Got an error message: %s", lasterr()); } else { printf("Got a measurement result: %llu", result); } ``` ## Initializing, Using, and Destroying Simulators To create a new simulator from C, use the [`init`] function. This function accepts a pointer to an unsigned integer that will be set to an ID for the new simulator: ```c uintptr_t sim_id; // Initialize a new simulator with two qubits and using a mixed-state // representation. if (init(2, "mixed", &sim_id) != 0) { printf("Error initializing simulator: %s", lasterr()); } ``` The ID for the newly created simulator can then be used to call into functions that apply different quantum operations, such as [`x`], [`h`], or [`cnot`]: ```c // Apply an 𝑋 operation to qubit #0. if (x(sim_id, 0) != 0) { printf("Error applying X: %s", lasterr()); } ``` To free the memory associated with a given simulator, use the [`destroy`] function: ```c if (destroy(sim_id) != 0) { printf("Error destroying simulator: %s", lasterr()); } ``` ## Getting and Setting Noise Models Noise models for each simulator can be accessed or set by using [`get_noise_model`], [`get_noise_model_by_name`], [`set_noise_model`], and [`set_noise_model_by_name`]. Each of these functions accepts either a name of a built-in noise model (see [`crate::NoiseModel::get_by_name`] for details). Noise models in the C API are represented by strings containing JSON serializations of the [`crate::NoiseModel`] data model. For example: ```c #include #include "qdk_sim.h" int main() { const char *noise_model; if (get_noise_model_by_name("ideal", &noise_model) != 0) { printf("Error getting noise model: %s", lasterr()); } printf("Noise model:\n%s", noise_model); } ``` Running the above results in the JSON representation of the ideal noise model being written to the console: ```text Noise model: {"initial_state":{"n_qubits":1,"data":{"Mixed":{"v":1,"dim":[2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0]]}}},"i":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0]]}}},"x":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0.0,0.0],[1.0,0.0],[1.0,0.0],[0.0,0.0]]}}},"y":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0.0,0.0],[0.0,1.0],[-0.0,-1.0],[0.0,0.0]]}}},"z":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[-1.0,-0.0]]}}},"h":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0.7071067811865476,0.0],[0.7071067811865476,0.0],[0.7071067811865476,0.0],[-0.7071067811865476,-0.0]]}}},"s":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,1.0]]}}},"s_adj":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,-0.0],[0.0,-0.0],[0.0,-0.0],[0.0,-1.0]]}}},"t":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.7071067811865476,0.7071067811865476]]}}},"t_adj":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,-0.0],[0.0,-0.0],[0.0,-0.0],[0.7071067811865476,-0.7071067811865476]]}}},"cnot":{"n_qubits":2,"data":{"Unitary":{"v":1,"dim":[4,4],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0],[0.0,0.0]]}}},"z_meas":{"Effects":[{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[1,2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0]]}}},{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[1,2,2],"data":[[0.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0]]}}}]}} ``` See [noise model serialization](crate#noise-model-serialization) for more details.