qdk_sim/c_api.rs
1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4// The following two attributes include the README.md for this module when
5// building docs (requires +nightly).
6// See https://github.com/rust-lang/rust/issues/82768#issuecomment-803935643
7// for discussion.
8#![cfg_attr(doc, feature(extended_key_value_attributes))]
9#![cfg_attr(doc, cfg_attr(doc, doc = include_str!("../docs/c-api.md")))]
10
11use crate::{built_info, NoiseModel, Process, State};
12use lazy_static::lazy_static;
13use serde_json::json;
14use std::collections::HashMap;
15use std::ffi::CStr;
16use std::ffi::CString;
17use std::os::raw::c_char;
18use std::ptr;
19use std::sync::Mutex;
20
21struct CApiState {
22 register_state: State,
23 noise_model: NoiseModel,
24}
25
26lazy_static! {
27 static ref STATE: Mutex<HashMap<usize, CApiState>> = Mutex::new(HashMap::new());
28 static ref LAST_ERROR: Mutex<Option<String>> = Mutex::new(None);
29}
30
31// UTILITY FUNCTIONS //
32
33/// Exposes a result to C callers by setting LAST_ERROR in the Error
34/// case, and generating an appropriate error code.
35fn as_capi_err<F: FnOnce() -> Result<(), String>>(result_fn: F) -> i64 {
36 let result = result_fn();
37 match result {
38 Ok(_) => 0,
39 Err(msg) => {
40 *LAST_ERROR.lock().unwrap() = Some(msg);
41 -1
42 }
43 }
44}
45
46fn apply<F: Fn(&NoiseModel) -> &Process>(
47 sim_id: usize,
48 idxs: &[usize],
49 channel_fn: F,
50) -> Result<(), String> {
51 let state = &mut *STATE.lock().unwrap();
52 if let Some(sim_state) = state.get_mut(&sim_id) {
53 let channel = channel_fn(&sim_state.noise_model);
54 match channel.apply_to(idxs, &sim_state.register_state) {
55 Ok(new_state) => {
56 sim_state.register_state = new_state;
57 Ok(())
58 }
59 Err(err) => Err(err),
60 }
61 } else {
62 return Err(format!("No simulator with id {}.", sim_id));
63 }
64}
65
66// C API FUNCTIONS //
67
68/// Returns information about how this simulator was built, serialized as a
69/// JSON object.
70#[no_mangle]
71pub extern "C" fn get_simulator_info() -> *const c_char {
72 let build_info = json!({
73 "name": "Microsoft.Quantum.Experimental.Simulators",
74 "version": built_info::PKG_VERSION,
75 "opt_level": built_info::OPT_LEVEL,
76 "features": built_info::FEATURES,
77 "target": built_info::TARGET
78 });
79 CString::new(serde_json::to_string(&build_info).unwrap().as_str())
80 .unwrap()
81 .into_raw()
82}
83
84/// Returns the last error message raised by a call to a C function.
85/// If no error message has been raised, returns a null pointer.
86#[no_mangle]
87pub extern "C" fn lasterr() -> *const c_char {
88 match &*LAST_ERROR.lock().unwrap() {
89 None => ptr::null(),
90 Some(msg) => CString::new(msg.as_str()).unwrap().into_raw(),
91 }
92}
93
94/// Allocate a new simulator with a given capacity, measured in the number of
95/// qubits supported by that simulator. Returns an id that can be used to refer
96/// to the new simulator in future function calls.
97///
98/// The initial state of the new simulator is populated using the
99/// representation nominated by the `representation` argument:
100///
101/// - **`pure`**: Creates the simulator with an initial state represented by
102/// a state vector.
103/// - **`mixed`**: Creates the simulator with an initial state represented by
104/// a density operator.
105/// - **`stabilizer`**: Creates the simulator with an initial state represented by
106/// a stabilizer tableau.
107///
108/// # Safety
109/// The caller is responsible for:
110///
111/// - Ensuring that `sim_id_out` points to valid
112/// memory, and that the lifetime of this pointer extends at least for the
113/// duration of this call.
114/// - Ensuring that `representation` is a valid pointer to a null-terminated
115/// string of Unicode characters, encoded as UTF-8.
116#[no_mangle]
117pub unsafe extern "C" fn init(
118 initial_capacity: usize,
119 representation: *const c_char,
120 sim_id_out: *mut usize,
121) -> i64 {
122 as_capi_err(|| {
123 if representation.is_null() {
124 return Err("init called with null pointer for representation".to_string());
125 }
126 let representation = CStr::from_ptr(representation)
127 .to_str()
128 .map_err(|e| format!("UTF-8 error decoding representation argument: {}", e))?;
129
130 let state = &mut *STATE.lock().unwrap();
131 let id = 1 + state.keys().fold(std::usize::MIN, |a, b| a.max(*b));
132 state.insert(
133 id,
134 CApiState {
135 register_state: match representation {
136 "mixed" => State::new_mixed(initial_capacity),
137 "pure" => State::new_pure(initial_capacity),
138 "stabilizer" => State::new_stabilizer(initial_capacity),
139 _ => {
140 return Err(format!(
141 "Unknown initial state representation {}.",
142 representation
143 ))
144 }
145 },
146 noise_model: NoiseModel::ideal(),
147 },
148 );
149 *sim_id_out = id;
150 Ok(())
151 })
152}
153
154/// Deallocates the simulator with the given id, releasing any resources owned
155/// by that simulator.
156#[no_mangle]
157pub extern "C" fn destroy(sim_id: usize) -> i64 {
158 as_capi_err(|| {
159 let state = &mut *STATE.lock().unwrap();
160 if state.contains_key(&sim_id) {
161 state.remove(&sim_id);
162 Ok(())
163 } else {
164 Err(format!("No simulator with id {} exists.", sim_id))
165 }
166 })
167}
168
169// TODO[code quality]: refactor the following several functions into a macro.
170
171/// Applies the `X` operation acting on a given qubit to a given simulator,
172/// using the currently set noise model.
173#[no_mangle]
174pub extern "C" fn x(sim_id: usize, idx: usize) -> i64 {
175 as_capi_err(|| apply(sim_id, &[idx], |model| &model.x))
176}
177
178/// Applies the `Y` operation acting on a given qubit to a given simulator,
179/// using the currently set noise model.
180#[no_mangle]
181pub extern "C" fn y(sim_id: usize, idx: usize) -> i64 {
182 as_capi_err(|| apply(sim_id, &[idx], |model| &model.y))
183}
184
185/// Applies the `Z` operation acting on a given qubit to a given simulator,
186/// using the currently set noise model.
187#[no_mangle]
188pub extern "C" fn z(sim_id: usize, idx: usize) -> i64 {
189 as_capi_err(|| apply(sim_id, &[idx], |model| &model.z))
190}
191
192/// Applies the `H` operation acting on a given qubit to a given simulator,
193/// using the currently set noise model.
194#[no_mangle]
195pub extern "C" fn h(sim_id: usize, idx: usize) -> i64 {
196 as_capi_err(|| apply(sim_id, &[idx], |model| &model.h))
197}
198
199/// Applies the `S` operation acting on a given qubit to a given simulator,
200/// using the currently set noise model.
201#[no_mangle]
202pub fn s(sim_id: usize, idx: usize) -> i64 {
203 as_capi_err(|| apply(sim_id, &[idx], |model| &model.s))
204}
205
206/// Applies the `Adjoint S` operation acting on a given qubit to a given
207/// simulator, using the currently set noise model.
208#[no_mangle]
209pub fn s_adj(sim_id: usize, idx: usize) -> i64 {
210 as_capi_err(|| apply(sim_id, &[idx], |model| &model.s_adj))
211}
212
213/// Applies the `T` operation acting on a given qubit to a given simulator,
214/// using the currently set noise model.
215#[no_mangle]
216pub fn t(sim_id: usize, idx: usize) -> i64 {
217 as_capi_err(|| apply(sim_id, &[idx], |model| &model.t))
218}
219
220/// Applies the `Adjoint T` operation acting on a given qubit to a given simulator,
221/// using the currently set noise model.
222#[no_mangle]
223pub fn t_adj(sim_id: usize, idx: usize) -> i64 {
224 as_capi_err(|| apply(sim_id, &[idx], |model| &model.t_adj))
225}
226
227/// Applies the `CNOT` operation acting on two given qubits to a given simulator,
228/// using the currently set noise model.
229#[no_mangle]
230pub fn cnot(sim_id: usize, idx_control: usize, idx_target: usize) -> i64 {
231 as_capi_err(|| apply(sim_id, &[idx_control, idx_target], |model| &model.cnot))
232}
233
234/// Measures a single qubit in the $Z$-basis, returning the result by setting
235/// the value at a given pointer.
236///
237/// # Safety
238/// This function is marked as unsafe as it is the caller's responsibility to
239/// ensure that `result_out` is a valid pointer, and that the memory referenced
240/// by `result_out` can be safely set.
241#[no_mangle]
242pub unsafe extern "C" fn m(sim_id: usize, idx: usize, result_out: *mut usize) -> i64 {
243 as_capi_err(|| {
244 let state = &mut *STATE.lock().unwrap();
245 if let Some(sim_state) = state.get_mut(&sim_id) {
246 let instrument = &sim_state.noise_model.z_meas;
247 let (result, new_state) = instrument.sample(&[idx], &sim_state.register_state);
248 sim_state.register_state = new_state;
249 *result_out = result;
250 Ok(())
251 } else {
252 Err(format!("No simulator with id {} exists.", sim_id))
253 }
254 })
255}
256
257/// Gets the noise model corresponding to a particular name, serialized as a
258/// string representing a JSON object.
259///
260/// Currently recognized names:
261/// - `ideal`
262/// - `ideal_stabilizer`
263///
264/// # Safety
265/// As this is a C-language API, the Rust compiler cannot guarantee safety when
266/// calling into this function. The caller is responsible for ensuring that:
267///
268/// - `name` is a pointer to a null-terminated string of Unicode characters,
269/// encoded as UTF-8, and that the pointer remains valid for the lifetime of
270/// this call.
271/// - `noise_model_json` is a valid pointer whose lifetime extends for the
272/// duration of this function call.
273///
274/// After this call completes, this function guarantees that either of the two
275/// conditions below holds:
276///
277/// - The return value is negative, in which case calling `lasterr` will return
278/// an actionable error message, or
279/// - The return value is `0`, and `*noise_model_json` is a valid pointer to a
280/// null-terminated string of Unicode characters, encoded as UTF-8. In this
281/// case, the caller is considered to own the memory allocated for this
282/// string.
283#[no_mangle]
284pub extern "C" fn get_noise_model_by_name(
285 name: *const c_char,
286 noise_model_json: *mut *const c_char,
287) -> i64 {
288 as_capi_err(|| {
289 let name = unsafe { CStr::from_ptr(name) }
290 .to_str()
291 .map_err(|e| format!("UTF-8 error decoding representation argument: {}", e))?;
292 let noise_model = NoiseModel::get_by_name(name)?;
293 let noise_model = CString::new(noise_model.as_json()).unwrap();
294 unsafe {
295 *noise_model_json = noise_model.into_raw();
296 }
297 Ok(())
298 })
299}
300
301/// Returns the currently configured noise model for a given simulator,
302/// serialized as a string representing a JSON object.
303///
304/// # Safety
305/// As this is a C-language API, the Rust compiler cannot guarantee safety when
306/// calling into this function. The caller is responsible for ensuring that:
307///
308/// - `noise_model_json` is a valid pointer whose lifetime extends for the
309/// duration of this function call.
310///
311/// After this call completes, this function guarantees that either of the two
312/// conditions below holds:
313///
314/// - The return value is negative, in which case calling `lasterr` will return
315/// an actionable error message, or
316/// - The return value is `0`, and `*noise_model_json` is a valid pointer to a
317/// null-terminated string of Unicode characters, encoded as UTF-8. In this
318/// case, the caller is considered to own the memory allocated for this
319/// string.
320#[no_mangle]
321pub extern "C" fn get_noise_model(sim_id: usize, noise_model_json: *mut *const c_char) -> i64 {
322 as_capi_err(|| {
323 let state = &*STATE
324 .lock()
325 .map_err(|e| format!("Lock poisoning error: {}", e))?;
326 if let Some(sim_state) = state.get(&sim_id) {
327 let c_str = CString::new(sim_state.noise_model.as_json().as_str()).map_err(|e| {
328 format!("Null error while converting noise model to C string: {}", e)
329 })?;
330 unsafe {
331 *noise_model_json = c_str.into_raw();
332 }
333 } else {
334 return Err(format!("No simulator with id {} exists.", sim_id));
335 }
336 Ok(())
337 })
338}
339
340/// Sets the noise model used by a given simulator instance, given a string
341/// containing a JSON serialization of that noise model.
342///
343/// # Safety
344/// This function is marked as unsafe as the caller is responsible for ensuring
345/// that `new_model`:
346///
347/// - Is a valid pointer to a null-terminated array of C
348/// characters.
349/// - The pointer remains valid for at least the duration
350/// of the call.
351/// - No other thread may modify the memory referenced by `new_model` for at
352/// least the duration of the call.
353#[no_mangle]
354pub unsafe extern "C" fn set_noise_model(sim_id: usize, new_model: *const c_char) -> i64 {
355 as_capi_err(|| {
356 if new_model.is_null() {
357 return Err("set_noise_model called with null pointer".to_string());
358 }
359
360 let c_str = CStr::from_ptr(new_model);
361 match c_str.to_str() {
362 Ok(serialized_noise_model) => match serde_json::from_str(serialized_noise_model) {
363 Ok(noise_model) => {
364 let state = &mut *STATE.lock().unwrap();
365 if let Some(sim_state) = state.get_mut(&sim_id) {
366 sim_state.noise_model = noise_model;
367 Ok(())
368 } else {
369 Err(format!("No simulator with id {} exists.", sim_id))
370 }
371 }
372 Err(serialization_error) => Err(format!(
373 "{} error deserializing noise model at line {}, column {}.",
374 match serialization_error.classify() {
375 serde_json::error::Category::Data => "Data / schema",
376 serde_json::error::Category::Eof => "End-of-file",
377 serde_json::error::Category::Io => "I/O",
378 serde_json::error::Category::Syntax => "Syntax",
379 },
380 serialization_error.line(),
381 serialization_error.column()
382 )),
383 },
384 Err(msg) => Err(format!(
385 "UTF-8 error decoding serialized noise model; was valid until byte {}.",
386 msg.valid_up_to()
387 )),
388 }
389 })
390}
391
392/// Sets the noise model used by a given simulator instance, given a string
393/// containing the name of a built-in noise model.
394///
395/// # Safety
396/// This function is marked as unsafe as the caller is responsible for ensuring
397/// that `name`:
398///
399/// - Is a valid pointer to a null-terminated array of C
400/// characters.
401/// - The pointer remains valid for at least the duration
402/// of the call.
403/// - No other thread may modify the memory referenced by `new_model` for at
404/// least the duration of the call.
405#[no_mangle]
406pub unsafe extern "C" fn set_noise_model_by_name(sim_id: usize, name: *const c_char) -> i64 {
407 as_capi_err(|| {
408 if name.is_null() {
409 return Err("set_noise_model_by_name called with null pointer".to_string());
410 }
411
412 let name = CStr::from_ptr(name)
413 .to_str()
414 .map_err(|e| format!("UTF-8 error decoding name: {}", e))?;
415 let noise_model = NoiseModel::get_by_name(name)?;
416 let state = &mut *STATE.lock().unwrap();
417 if let Some(sim_state) = state.get_mut(&sim_id) {
418 sim_state.noise_model = noise_model;
419 Ok(())
420 } else {
421 Err(format!("No simulator with id {} exists.", sim_id))
422 }
423 })
424}
425
426/// Returns the state of a given simulator, serialized as a JSON object.
427#[no_mangle]
428pub extern "C" fn get_current_state(sim_id: usize) -> *const c_char {
429 let state = &mut *STATE.lock().unwrap();
430 if let Some(sim_state) = state.get_mut(&sim_id) {
431 CString::new(
432 serde_json::to_string(&sim_state.register_state)
433 .unwrap()
434 .as_str(),
435 )
436 .unwrap()
437 // NB: into_raw implies transferring ownership to the C caller,
438 // and hence moves its self.
439 .into_raw()
440 } else {
441 ptr::null()
442 }
443}