Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.

Commit 0e35793

Browse files
committed
Improve error handling structure.
1 parent e6955f1 commit 0e35793

File tree

8 files changed

+200
-68
lines changed

8 files changed

+200
-68
lines changed

src/Simulation/qdk_sim_rs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ built = "0.5.0"
7777
cauchy = "0.4.0"
7878
thiserror = "1.0.30"
7979
miette = "4.3.0"
80+
anyhow = "1.0.56"
8081

8182
[build-dependencies]
8283
built = "0.5.0"

src/Simulation/qdk_sim_rs/src/c_api.rs

Lines changed: 65 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#![cfg_attr(doc, feature(extended_key_value_attributes))]
99
#![cfg_attr(doc, cfg_attr(doc, doc = include_str!("../docs/c-api.md")))]
1010

11+
use crate::error::{QdkSimError, QdkSimError::*};
1112
use crate::{built_info, NoiseModel, Process, State};
1213
use lazy_static::lazy_static;
1314
use serde_json::json;
@@ -32,12 +33,12 @@ lazy_static! {
3233

3334
/// Exposes a result to C callers by setting LAST_ERROR in the Error
3435
/// case, and generating an appropriate error code.
35-
fn as_capi_err<F: FnOnce() -> Result<(), String>>(result_fn: F) -> i64 {
36+
fn as_capi_err<F: FnOnce() -> Result<(), QdkSimError>>(result_fn: F) -> i64 {
3637
let result = result_fn();
3738
match result {
3839
Ok(_) => 0,
39-
Err(msg) => {
40-
*LAST_ERROR.lock().unwrap() = Some(msg);
40+
Err(err) => {
41+
*LAST_ERROR.lock().unwrap() = Some(err.to_string());
4142
-1
4243
}
4344
}
@@ -47,7 +48,7 @@ fn apply<F: Fn(&NoiseModel) -> &Process>(
4748
sim_id: usize,
4849
idxs: &[usize],
4950
channel_fn: F,
50-
) -> Result<(), String> {
51+
) -> Result<(), QdkSimError> {
5152
let state = &mut *STATE.lock().unwrap();
5253
if let Some(sim_state) = state.get_mut(&sim_id) {
5354
let channel = channel_fn(&sim_state.noise_model);
@@ -59,7 +60,10 @@ fn apply<F: Fn(&NoiseModel) -> &Process>(
5960
Err(err) => Err(err),
6061
}
6162
} else {
62-
return Err(format!("No simulator with id {}.", sim_id));
63+
Err(NoSuchSimulator {
64+
invalid_id: sim_id,
65+
expected: state.keys().into_iter().cloned().collect(),
66+
})
6367
}
6468
}
6569

@@ -121,11 +125,15 @@ pub unsafe extern "C" fn init(
121125
) -> i64 {
122126
as_capi_err(|| {
123127
if representation.is_null() {
124-
return Err("init called with null pointer for representation".to_string());
128+
return Err(NullPointer("representation".to_string()));
125129
}
126-
let representation = CStr::from_ptr(representation)
127-
.to_str()
128-
.map_err(|e| format!("UTF-8 error decoding representation argument: {}", e))?;
130+
let representation =
131+
CStr::from_ptr(representation)
132+
.to_str()
133+
.map_err(|e| InvalidUtf8InArgument {
134+
arg_name: "representation".to_string(),
135+
source: e,
136+
})?;
129137

130138
let state = &mut *STATE.lock().unwrap();
131139
let id = 1 + state.keys().fold(std::usize::MIN, |a, b| a.max(*b));
@@ -136,12 +144,7 @@ pub unsafe extern "C" fn init(
136144
"mixed" => State::new_mixed(initial_capacity),
137145
"pure" => State::new_pure(initial_capacity),
138146
"stabilizer" => State::new_stabilizer(initial_capacity),
139-
_ => {
140-
return Err(format!(
141-
"Unknown initial state representation {}.",
142-
representation
143-
))
144-
}
147+
_ => return Err(InvalidRepresentation(representation.to_string())),
145148
},
146149
noise_model: NoiseModel::ideal(),
147150
},
@@ -161,7 +164,10 @@ pub extern "C" fn destroy(sim_id: usize) -> i64 {
161164
state.remove(&sim_id);
162165
Ok(())
163166
} else {
164-
Err(format!("No simulator with id {} exists.", sim_id))
167+
Err(NoSuchSimulator {
168+
invalid_id: sim_id,
169+
expected: state.keys().into_iter().cloned().collect(),
170+
})
165171
}
166172
})
167173
}
@@ -249,7 +255,10 @@ pub unsafe extern "C" fn m(sim_id: usize, idx: usize, result_out: *mut usize) ->
249255
*result_out = result;
250256
Ok(())
251257
} else {
252-
Err(format!("No simulator with id {} exists.", sim_id))
258+
Err(NoSuchSimulator {
259+
invalid_id: sim_id,
260+
expected: state.keys().into_iter().cloned().collect(),
261+
})
253262
}
254263
})
255264
}
@@ -288,7 +297,10 @@ pub extern "C" fn get_noise_model_by_name(
288297
as_capi_err(|| {
289298
let name = unsafe { CStr::from_ptr(name) }
290299
.to_str()
291-
.map_err(|e| format!("UTF-8 error decoding representation argument: {}", e))?;
300+
.map_err(|e| InvalidUtf8InArgument {
301+
arg_name: "name".to_string(),
302+
source: e,
303+
})?;
292304
let noise_model = NoiseModel::get_by_name(name)?;
293305
let noise_model = CString::new(noise_model.as_json()).unwrap();
294306
unsafe {
@@ -320,20 +332,28 @@ pub extern "C" fn get_noise_model_by_name(
320332
#[no_mangle]
321333
pub extern "C" fn get_noise_model(sim_id: usize, noise_model_json: *mut *const c_char) -> i64 {
322334
as_capi_err(|| {
323-
let state = &*STATE
335+
let state = STATE
324336
.lock()
325-
.map_err(|e| format!("Lock poisoning error: {}", e))?;
337+
.map_err(|e| {
338+
// Note that as per https://github.com/dtolnay/anyhow/issues/81#issuecomment-609247231,
339+
// common practice is for poison errors to indicate that the containing thread
340+
// has been irrevocably corrupted and must panic.
341+
panic!("The lock on shared state for the C API has been poisoned.");
342+
})
343+
.unwrap();
326344
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-
})?;
345+
let c_str = CString::new(sim_state.noise_model.as_json().as_str())
346+
.map_err(|e| UnanticipatedCApiError(anyhow::Error::new(e)))?;
330347
unsafe {
331348
*noise_model_json = c_str.into_raw();
332-
}
349+
};
350+
Ok(())
333351
} else {
334-
return Err(format!("No simulator with id {} exists.", sim_id));
352+
Err(NoSuchSimulator {
353+
invalid_id: sim_id,
354+
expected: state.keys().into_iter().cloned().collect(),
355+
})
335356
}
336-
Ok(())
337357
})
338358
}
339359

@@ -354,7 +374,7 @@ pub extern "C" fn get_noise_model(sim_id: usize, noise_model_json: *mut *const c
354374
pub unsafe extern "C" fn set_noise_model(sim_id: usize, new_model: *const c_char) -> i64 {
355375
as_capi_err(|| {
356376
if new_model.is_null() {
357-
return Err("set_noise_model called with null pointer".to_string());
377+
return Err(NullPointer("new_model".to_string()));
358378
}
359379

360380
let c_str = CStr::from_ptr(new_model);
@@ -366,25 +386,18 @@ pub unsafe extern "C" fn set_noise_model(sim_id: usize, new_model: *const c_char
366386
sim_state.noise_model = noise_model;
367387
Ok(())
368388
} else {
369-
Err(format!("No simulator with id {} exists.", sim_id))
389+
Err(NoSuchSimulator {
390+
invalid_id: sim_id,
391+
expected: state.keys().into_iter().cloned().collect(),
392+
})
370393
}
371394
}
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-
)),
395+
Err(err) => Err(JsonDeserializationError(err)),
383396
},
384-
Err(msg) => Err(format!(
385-
"UTF-8 error decoding serialized noise model; was valid until byte {}.",
386-
msg.valid_up_to()
387-
)),
397+
Err(msg) => Err(InvalidUtf8InArgument {
398+
arg_name: "new_model".to_string(),
399+
source: msg,
400+
}),
388401
}
389402
})
390403
}
@@ -406,19 +419,25 @@ pub unsafe extern "C" fn set_noise_model(sim_id: usize, new_model: *const c_char
406419
pub unsafe extern "C" fn set_noise_model_by_name(sim_id: usize, name: *const c_char) -> i64 {
407420
as_capi_err(|| {
408421
if name.is_null() {
409-
return Err("set_noise_model_by_name called with null pointer".to_string());
422+
return Err(NullPointer("name".to_string()));
410423
}
411424

412425
let name = CStr::from_ptr(name)
413426
.to_str()
414-
.map_err(|e| format!("UTF-8 error decoding name: {}", e))?;
427+
.map_err(|e| InvalidUtf8InArgument {
428+
arg_name: "name".to_string(),
429+
source: e,
430+
})?;
415431
let noise_model = NoiseModel::get_by_name(name)?;
416432
let state = &mut *STATE.lock().unwrap();
417433
if let Some(sim_state) = state.get_mut(&sim_id) {
418434
sim_state.noise_model = noise_model;
419435
Ok(())
420436
} else {
421-
Err(format!("No simulator with id {} exists.", sim_id))
437+
Err(NoSuchSimulator {
438+
invalid_id: sim_id,
439+
expected: state.keys().into_iter().cloned().collect(),
440+
})
422441
}
423442
})
424443
}

src/Simulation/qdk_sim_rs/src/error.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,41 @@
33

44
//! Module defining common errors that can occur during quantum simulations.
55
6+
use std::str::Utf8Error;
7+
68
use miette::Diagnostic;
79
use thiserror::Error;
810

911
/// Represents errors that can occur during linear algebra operations.
1012
#[derive(Debug, Diagnostic, Error)]
1113
pub enum QdkSimError {
14+
// NB: As a design note, these should be eliminated before stabilizing the
15+
// API for this simulator crate.
16+
/// Raised on miscellaneous errors.
17+
#[error("{0}")]
18+
#[diagnostic(code(qdk_sim::other))]
19+
MiscError(String),
20+
21+
/// Raised when functionality that has not yet been implemented is called.
22+
#[error("Not yet implemented: {0}")]
23+
#[diagnostic(code(qdk_sim::not_yet_implemented))]
24+
NotYetImplemented(String),
25+
26+
/// Raised when the wrong number of qubits is provided for a quantum
27+
/// process.
28+
#[error("Channel acts on {expected} qubits, but was applied to an {actual}-qubit state.")]
29+
#[diagnostic(code(qdk_sim::process::wrong_n_qubits))]
30+
WrongNumberOfQubits { expected: usize, actual: usize },
31+
32+
/// Raised when a channel cannot be applied to a given state due to a
33+
/// mismatch between channel and state kinds.
34+
#[error("Unsupported quantum process variant {channel_variant} for applying to state variant {state_variant}.")]
35+
#[diagnostic(code(qdk_sim::process::unsupported_apply))]
36+
UnsupportedApply {
37+
channel_variant: &'static str,
38+
state_variant: &'static str,
39+
},
40+
1241
/// Raised when a matrix is singular, and thus does not have an inverse.
1342
#[error("expected invertible matrix, but got a singular or very poorly conditioned matrix (det = {det})")]
1443
#[diagnostic(code(qdk_sim::linalg::singular))]
@@ -28,4 +57,47 @@ pub enum QdkSimError {
2857
#[error("could not convert value of type `{0}` into element type `{1}`")]
2958
#[diagnostic(code(qdk_sim::linalg::cannot_convert_element))]
3059
CannotConvertElement(String, String),
60+
61+
/// Raised when no noise model exists for a given name.
62+
#[error("{0} is not the name of any valid noise model")]
63+
#[diagnostic(code(qdk_sim::noise_model::invalid_repr))]
64+
InvalidNoiseModel(String),
65+
66+
/// Raised when an initial state representation is invalid.
67+
#[error("C API error: {0} is not a valid initial state representation")]
68+
#[diagnostic(code(qdk_sim::c_api::invalid_repr))]
69+
InvalidRepresentation(String),
70+
71+
/// Raised when a null pointer is passed through the C API.
72+
#[error("C API error: {0} was null")]
73+
#[diagnostic(code(qdk_sim::c_api::nullptr))]
74+
NullPointer(String),
75+
76+
/// Raised when an invalid simulator ID is passed to the C API.
77+
#[error("C API error: No simulator with ID {invalid_id} exists. Expected: {expected:?}.")]
78+
#[diagnostic(code(qdk_sim::c_api::invalid_sim))]
79+
NoSuchSimulator {
80+
invalid_id: usize,
81+
expected: Vec<usize>,
82+
},
83+
84+
/// Raised when a string passed to the C API contains could not be decoded
85+
/// as a UTF-8 string.
86+
#[error("C API error: UTF-8 error decoding {arg_name} argument: {source}")]
87+
#[diagnostic(code(qdk_sim::c_api::utf8))]
88+
InvalidUtf8InArgument {
89+
arg_name: String,
90+
#[source]
91+
source: Utf8Error,
92+
},
93+
94+
/// Raised when a JSON serialization error occurs during a C API call.
95+
#[error(transparent)]
96+
#[diagnostic(code(qdk_sim::c_api::json_deser))]
97+
JsonDeserializationError(#[from] serde_json::Error),
98+
99+
/// Raised when an unanticipated error occurs during a C API call.
100+
#[error(transparent)]
101+
#[diagnostic(code(qdk_sim::c_api::unanticipated))]
102+
UnanticipatedCApiError(#[from] anyhow::Error),
31103
}

src/Simulation/qdk_sim_rs/src/noise_model.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::chp_decompositions::ChpOperation;
22
use crate::common_matrices;
3+
use crate::error::QdkSimError;
34
use crate::instrument::Instrument;
45
use crate::linalg::HasDagger;
56
use crate::processes::Process;
@@ -90,11 +91,11 @@ impl NoiseModel {
9091
/// # use qdk_sim::NoiseModel;
9192
/// let noise_model = NoiseModel::get_by_name("ideal");
9293
/// ```
93-
pub fn get_by_name(name: &str) -> Result<NoiseModel, String> {
94+
pub fn get_by_name(name: &str) -> Result<NoiseModel, QdkSimError> {
9495
match name {
9596
"ideal" => Ok(NoiseModel::ideal()),
9697
"ideal_stabilizer" => Ok(NoiseModel::ideal_stabilizer()),
97-
_ => Err(format!("Unrecognized noise model name {}.", name)),
98+
_ => Err(QdkSimError::InvalidNoiseModel(name.to_string())),
9899
}
99100
}
100101

0 commit comments

Comments
 (0)