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}