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

Initial support for matrix inverse in qdk_sim_experimental crate #920

Merged
merged 14 commits into from
Apr 8, 2022
Merged
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
cgranade marked this conversation as resolved.
Show resolved Hide resolved
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
cgranade marked this conversation as resolved.
Show resolved Hide resolved
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