Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 45 additions & 30 deletions src/Simulation/qdk_sim_rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
# Licensed under the MIT License.

[package]
name = "qdk_sim_experimental"
name = "qdk_sim_rs"
version = "0.0.1-alpha"
authors = ["Microsoft"]
edition = "2018"
license = "MIT"
description = "Experimental simulators for use with the Quantum Development Kit."
description = "Rust-based simulators for use with the Quantum Development Kit."
homepage = "https://github.com/microsoft/qsharp-runtime"
repository = "https://github.com/microsoft/qsharp-runtime"
readme = "README.md"

# Verified with cargo-msrv.
rust-version = "1.51.0"

exclude = [
# Exclude files specific to QDK build pipelines.
"*.template", "*.csx", "*.ps1", "NuGet.Config", "drop",
Expand All @@ -21,72 +24,84 @@ exclude = [
"*.egg-info", "qdk_sim_experimental", "setup.py", "*.whl", "pyproject.toml"
]

# Enable LaTeX on docs.rs.
# See https://stackoverflow.com/a/54573800/267841 and
# https://github.com/rust-num/num/pull/226/files for why this works.
[package.metadata.docs.rs]
rustdoc-args = [ "--html-in-header", "docs-includes/header.html", "--html-after-content", "docs-includes/after.html" ]
features = ["document-features"]

[lib]
crate-type = ["rlib", "staticlib", "cdylib"]
name = "qdk_sim"
path = "src/lib.rs"
crate-type = ["rlib", "staticlib", "cdylib"]

# Optional build-time features: we use this to create Python and WASM bindings.
[features]
default = []
wasm = ["web-sys"]

# When Python bindings are enabled, we also need the pyo3 dependency.
## Enables Python bindings for this crate.
python = ["pyo3", "numpy"]

# Enable LaTeX on docs.rs.
# See https://stackoverflow.com/a/54573800/267841 and
# https://github.com/rust-num/num/pull/226/files for why this works.
[package.metadata.docs.rs]
rustdoc-args = [ "--html-in-header", "docs-includes/header.html", "--html-after-content", "docs-includes/after.html" ]
## Ensures that the crate is compatible with usage from WebAssembly.
wasm = ["web-sys"]


[profile.release]
opt-level = 3
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
opt-level = 3
panic = 'unwind'

[dependencies]
ndarray = { version = "0.15.2", features = ["serde"] }
num-complex = { version = "0.4", features = ["serde"] }
num-traits = "0.2"
derive_more = "0.99.10"
itertools = "0.9.0"
rand = { version = "0.7.3", features = ["alloc"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
lazy_static = "1.4.0"
anyhow = "1.0.56"
# We use built to expose compile-time metadata about how this crate
# was built to C and Rust callers.
built = "0.5.0"
cauchy = "0.4.0"
cfg-if = "1.0.0"
num_enum = "0.5.1"
derive_more = "0.99.10"
# We use document-features to automatically generate feature documentation from
# Cargo.toml, following the example at
document-features = { version = "0.2", optional = true }
# See https://github.com/rust-random/rand/issues/990
# and https://docs.rs/getrandom/0.1.15/getrandom/index.html#support-for-webassembly-and-asmjs
# for why this is needed.
# NB: We depend on 0.1.15, since that's what gets brought in transitively
# by rand and rand_core.
getrandom = { version = "0.1.15", features = ["wasm-bindgen"] }

# We only need web-sys when compiling with the wasm feature.
web-sys = { version = "0.3.4", features = ['console'], optional = true }

itertools = "0.9.0"
lazy_static = "1.4.0"
miette = "4.3.0"
ndarray = { version = "0.15.2", features = ["serde"] }
num-complex = { version = "0.4", features = ["serde"] }
num-traits = "0.2"
num_enum = "0.5.1"
numpy = { version = "0.13.1", optional = true }
# We only need PyO3 when generating Python bindings.
pyo3 = { version = "0.13.2", features = ["extension-module"], optional = true }
numpy = {version = "0.13.1", optional = true}
rand = { version = "0.7.3", features = ["alloc"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0.30"

# We use built to expose compile-time metadata about how this crate
# was built to C and Rust callers.
built = "0.5.0"
# We only need web-sys when compiling with the wasm feature.
web-sys = { version = "0.3.4", features = ['console'], optional = true }

[build-dependencies]
built = "0.5.0"


[dev-dependencies]
approx = { version = "0.5.1", features = ["num-complex"] }
assert-json-diff = "2.0.1"
criterion = { version = "0.3", features = ['html_reports', 'csv_output'] }
ndarray = { version = "0.15.4", features = ["approx"] }

[[bench]]
name = "c_api_benchmark"
harness = false
name = "c_api_benchmark"

[[bench]]
name = "microbenchmark"
harness = false
name = "microbenchmark"
9 changes: 3 additions & 6 deletions src/Simulation/qdk_sim_rs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
To generate and view the documentation for this crate locally, please
run:

$ cargo +nightly doc --features python --open
$ cargo doc --features python,document-features --open
-->

# Quantum Development Kit Preview Simulators
Expand All @@ -28,11 +28,6 @@ The [`c_api`] module allows for using the simulation functionality in this crate

Similarly, the [`python`] module allows exposing data structures in this crate to Python programs.

## Cargo Features

- **`python`**: Enables Python bindings for this crate.
- **`wasm`**: Ensures that the crate is compatible with usage from WebAssembly.

## Representing quantum systems

This crate provides several different data structures for representing quantum systems in a variety of different conventions:
Expand Down Expand Up @@ -148,3 +143,5 @@ TODO
- Stabilizer simulation not yet exposed via C API.
- Test and microbenchmark coverage still incomplete.
- Too many APIs `panic!` or `unwrap`, and need replaced with `Result` returns instead.

# Crate features
118 changes: 66 additions & 52 deletions src/Simulation/qdk_sim_rs/src/c_api.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

// The following two attributes include the README.md for this module when
// building docs (requires +nightly).
// See https://github.com/rust-lang/rust/issues/82768#issuecomment-803935643
// for discussion.
#![cfg_attr(doc, feature(extended_key_value_attributes))]
#![cfg_attr(doc, cfg_attr(doc, doc = include_str!("../docs/c-api.md")))]
#![cfg_attr(all(), doc = include_str!("../docs/c-api.md"))]

use crate::error::{QdkSimError, QdkSimError::*};
use crate::{built_info, NoiseModel, Process, State};
use lazy_static::lazy_static;
use serde_json::json;
Expand All @@ -32,12 +28,12 @@ lazy_static! {

/// Exposes a result to C callers by setting LAST_ERROR in the Error
/// case, and generating an appropriate error code.
fn as_capi_err<F: FnOnce() -> Result<(), String>>(result_fn: F) -> i64 {
fn as_capi_err<F: FnOnce() -> Result<(), QdkSimError>>(result_fn: F) -> i64 {
let result = result_fn();
match result {
Ok(_) => 0,
Err(msg) => {
*LAST_ERROR.lock().unwrap() = Some(msg);
Err(err) => {
*LAST_ERROR.lock().unwrap() = Some(err.to_string());
-1
}
}
Expand All @@ -47,7 +43,7 @@ fn apply<F: Fn(&NoiseModel) -> &Process>(
sim_id: usize,
idxs: &[usize],
channel_fn: F,
) -> Result<(), String> {
) -> Result<(), QdkSimError> {
let state = &mut *STATE.lock().unwrap();
if let Some(sim_state) = state.get_mut(&sim_id) {
let channel = channel_fn(&sim_state.noise_model);
Expand All @@ -59,7 +55,10 @@ fn apply<F: Fn(&NoiseModel) -> &Process>(
Err(err) => Err(err),
}
} else {
return Err(format!("No simulator with id {}.", sim_id));
Err(NoSuchSimulator {
invalid_id: sim_id,
expected: state.keys().into_iter().cloned().collect(),
})
}
}

Expand Down Expand Up @@ -121,11 +120,15 @@ pub unsafe extern "C" fn init(
) -> i64 {
as_capi_err(|| {
if representation.is_null() {
return Err("init called with null pointer for representation".to_string());
return Err(NullPointer("representation".to_string()));
}
let representation = CStr::from_ptr(representation)
.to_str()
.map_err(|e| format!("UTF-8 error decoding representation argument: {}", e))?;
let representation =
CStr::from_ptr(representation)
.to_str()
.map_err(|e| InvalidUtf8InArgument {
arg_name: "representation".to_string(),
source: e,
})?;

let state = &mut *STATE.lock().unwrap();
let id = 1 + state.keys().fold(std::usize::MIN, |a, b| a.max(*b));
Expand All @@ -136,12 +139,7 @@ pub unsafe extern "C" fn init(
"mixed" => State::new_mixed(initial_capacity),
"pure" => State::new_pure(initial_capacity),
"stabilizer" => State::new_stabilizer(initial_capacity),
_ => {
return Err(format!(
"Unknown initial state representation {}.",
representation
))
}
_ => return Err(InvalidRepresentation(representation.to_string())),
},
noise_model: NoiseModel::ideal(),
},
Expand All @@ -161,7 +159,10 @@ pub extern "C" fn destroy(sim_id: usize) -> i64 {
state.remove(&sim_id);
Ok(())
} else {
Err(format!("No simulator with id {} exists.", sim_id))
Err(NoSuchSimulator {
invalid_id: sim_id,
expected: state.keys().into_iter().cloned().collect(),
})
}
})
}
Expand Down Expand Up @@ -249,7 +250,10 @@ pub unsafe extern "C" fn m(sim_id: usize, idx: usize, result_out: *mut usize) ->
*result_out = result;
Ok(())
} else {
Err(format!("No simulator with id {} exists.", sim_id))
Err(NoSuchSimulator {
invalid_id: sim_id,
expected: state.keys().into_iter().cloned().collect(),
})
}
})
}
Expand Down Expand Up @@ -288,7 +292,10 @@ pub extern "C" fn get_noise_model_by_name(
as_capi_err(|| {
let name = unsafe { CStr::from_ptr(name) }
.to_str()
.map_err(|e| format!("UTF-8 error decoding representation argument: {}", e))?;
.map_err(|e| InvalidUtf8InArgument {
arg_name: "name".to_string(),
source: e,
})?;
let noise_model = NoiseModel::get_by_name(name)?;
let noise_model = CString::new(noise_model.as_json()).unwrap();
unsafe {
Expand Down Expand Up @@ -320,20 +327,28 @@ pub extern "C" fn get_noise_model_by_name(
#[no_mangle]
pub extern "C" fn get_noise_model(sim_id: usize, noise_model_json: *mut *const c_char) -> i64 {
as_capi_err(|| {
let state = &*STATE
let state = STATE
.lock()
.map_err(|e| format!("Lock poisoning error: {}", e))?;
.map_err(|_| {
// Note that as per https://github.com/dtolnay/anyhow/issues/81#issuecomment-609247231,
// common practice is for poison errors to indicate that the containing thread
// has been irrevocably corrupted and must panic.
panic!("The lock on shared state for the C API has been poisoned.");
})
.unwrap();
if let Some(sim_state) = state.get(&sim_id) {
let c_str = CString::new(sim_state.noise_model.as_json().as_str()).map_err(|e| {
format!("Null error while converting noise model to C string: {}", e)
})?;
let c_str = CString::new(sim_state.noise_model.as_json().as_str())
.map_err(|e| UnanticipatedCApiError(anyhow::Error::new(e)))?;
unsafe {
*noise_model_json = c_str.into_raw();
}
};
Ok(())
} else {
return Err(format!("No simulator with id {} exists.", sim_id));
Err(NoSuchSimulator {
invalid_id: sim_id,
expected: state.keys().into_iter().cloned().collect(),
})
}
Ok(())
})
}

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

let c_str = CStr::from_ptr(new_model);
Expand All @@ -366,25 +381,18 @@ pub unsafe extern "C" fn set_noise_model(sim_id: usize, new_model: *const c_char
sim_state.noise_model = noise_model;
Ok(())
} else {
Err(format!("No simulator with id {} exists.", sim_id))
Err(NoSuchSimulator {
invalid_id: sim_id,
expected: state.keys().into_iter().cloned().collect(),
})
}
}
Err(serialization_error) => Err(format!(
"{} error deserializing noise model at line {}, column {}.",
match serialization_error.classify() {
serde_json::error::Category::Data => "Data / schema",
serde_json::error::Category::Eof => "End-of-file",
serde_json::error::Category::Io => "I/O",
serde_json::error::Category::Syntax => "Syntax",
},
serialization_error.line(),
serialization_error.column()
)),
Err(err) => Err(JsonDeserializationError(err)),
},
Err(msg) => Err(format!(
"UTF-8 error decoding serialized noise model; was valid until byte {}.",
msg.valid_up_to()
)),
Err(msg) => Err(InvalidUtf8InArgument {
arg_name: "new_model".to_string(),
source: msg,
}),
}
})
}
Expand All @@ -406,19 +414,25 @@ pub unsafe extern "C" fn set_noise_model(sim_id: usize, new_model: *const c_char
pub unsafe extern "C" fn set_noise_model_by_name(sim_id: usize, name: *const c_char) -> i64 {
as_capi_err(|| {
if name.is_null() {
return Err("set_noise_model_by_name called with null pointer".to_string());
return Err(NullPointer("name".to_string()));
}

let name = CStr::from_ptr(name)
.to_str()
.map_err(|e| format!("UTF-8 error decoding name: {}", e))?;
.map_err(|e| InvalidUtf8InArgument {
arg_name: "name".to_string(),
source: e,
})?;
let noise_model = NoiseModel::get_by_name(name)?;
let state = &mut *STATE.lock().unwrap();
if let Some(sim_state) = state.get_mut(&sim_id) {
sim_state.noise_model = noise_model;
Ok(())
} else {
Err(format!("No simulator with id {} exists.", sim_id))
Err(NoSuchSimulator {
invalid_id: sim_id,
expected: state.keys().into_iter().cloned().collect(),
})
}
})
}
Expand Down
Loading