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

Commit 4e2e386

Browse files
committed
Code quality improvements.
1 parent 0e35793 commit 4e2e386

File tree

10 files changed

+217
-32
lines changed

10 files changed

+217
-32
lines changed

src/Simulation/qdk_sim_rs/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ homepage = "https://github.com/microsoft/qsharp-runtime"
1212
repository = "https://github.com/microsoft/qsharp-runtime"
1313
readme = "README.md"
1414

15+
# Verified with cargo-msrv.
16+
rust-version = "1.51.0"
17+
1518
exclude = [
1619
# Exclude files specific to QDK build pipelines.
1720
"*.template", "*.csx", "*.ps1", "NuGet.Config", "drop",
@@ -86,6 +89,8 @@ built = "0.5.0"
8689
[dev-dependencies]
8790
assert-json-diff = "2.0.1"
8891
criterion = { version = "0.3", features = ['html_reports', 'csv_output'] }
92+
approx = { version = "0.5.1", features = ["num-complex"] }
93+
ndarray = { version = "0.15.4", features = ["approx"] }
8994

9095
[[bench]]
9196
name = "c_api_benchmark"

src/Simulation/qdk_sim_rs/src/c_api.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

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")))]
4+
#![cfg_attr(all(), doc = include_str!("../docs/c-api.md"))]
105

116
use crate::error::{QdkSimError, QdkSimError::*};
127
use crate::{built_info, NoiseModel, Process, State};

src/Simulation/qdk_sim_rs/src/error.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,22 @@ pub enum QdkSimError {
2727
/// process.
2828
#[error("Channel acts on {expected} qubits, but was applied to an {actual}-qubit state.")]
2929
#[diagnostic(code(qdk_sim::process::wrong_n_qubits))]
30-
WrongNumberOfQubits { expected: usize, actual: usize },
30+
WrongNumberOfQubits {
31+
/// The number of qubits that was expected, as given by the size of the
32+
/// channel to be applied.
33+
expected: usize,
34+
/// The actual number of qubits for the given state.
35+
actual: usize,
36+
},
3137

3238
/// Raised when a channel cannot be applied to a given state due to a
3339
/// mismatch between channel and state kinds.
3440
#[error("Unsupported quantum process variant {channel_variant} for applying to state variant {state_variant}.")]
3541
#[diagnostic(code(qdk_sim::process::unsupported_apply))]
3642
UnsupportedApply {
43+
/// The enum variant of the channel to be applied.
3744
channel_variant: &'static str,
45+
/// The enum variant of the state that the channel is to be applied to.
3846
state_variant: &'static str,
3947
},
4048

@@ -77,7 +85,9 @@ pub enum QdkSimError {
7785
#[error("C API error: No simulator with ID {invalid_id} exists. Expected: {expected:?}.")]
7886
#[diagnostic(code(qdk_sim::c_api::invalid_sim))]
7987
NoSuchSimulator {
88+
/// The invalid simulator id which caused this error.
8089
invalid_id: usize,
90+
/// A list of valid simulator ids at the point when this error occured.
8191
expected: Vec<usize>,
8292
},
8393

@@ -86,7 +96,10 @@ pub enum QdkSimError {
8696
#[error("C API error: UTF-8 error decoding {arg_name} argument: {source}")]
8797
#[diagnostic(code(qdk_sim::c_api::utf8))]
8898
InvalidUtf8InArgument {
99+
/// The name of the argument containing invalid UTF-8 data.
89100
arg_name: String,
101+
102+
/// The underlying UTF-8 error that caused this error.
90103
#[source]
91104
source: Utf8Error,
92105
},

src/Simulation/qdk_sim_rs/src/lib.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
// The following two attributes include the README.md for this crate 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!("../README.md")))]
4+
#![cfg_attr(all(), doc = include_str!("../README.md"))]
105
// Set linting rules for documentation. We will stop the build on missing docs,
116
// or docs with known broken links. We only enable this when all relevant
127
// features are enabled, otherwise the docs build will fail on links to
@@ -17,7 +12,7 @@
1712
// that are missing an `# Example` section. Currently, that raises a lot of
1813
// warnings when building docs, but ideally we should make sure to address
1914
// warnings going forward by adding relevant examples.
20-
#![cfg_attr(doc, warn(missing_doc_code_examples))]
15+
#![cfg_attr(doc, warn(rustdoc::missing_doc_code_examples))]
2116

2217
#[macro_use(array, s)]
2318
extern crate ndarray;

src/Simulation/qdk_sim_rs/src/linalg/decompositions/lu.rs

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::{
1212
linalg::array_ext::{RemoveAxisExt, ShapeExt},
1313
};
1414

15+
/// Represents the output of an LU decomposition acting on a matrix.
1516
#[derive(Debug)]
1617
pub struct LU<A, S>
1718
where
@@ -22,13 +23,22 @@ where
2223
pub(self) pivots: Array1<usize>,
2324
}
2425

26+
/// Represents types that represent matrices that are decomposable into lower-
27+
/// and upper-triangular parts.
2528
pub trait LUDecomposable {
29+
/// The element type for the output matrices.
2630
type Elem: Scalar;
27-
type Repr: Data<Elem = Self::Elem>;
31+
32+
/// Type of owned data in the output matrices.
2833
type OwnedRepr: Data + RawData<Elem = Self::Elem>;
34+
35+
/// The output type for LU decompositions on this type.
2936
type Output: LUDecomposition<Self::Elem, Self::OwnedRepr>;
37+
38+
/// The type used to represent errors in the decomposition.
3039
type Error;
3140

41+
/// Performs an LU decomposition on the given type.
3242
fn lu(&self) -> Result<Self::Output, Self::Error>;
3343
}
3444

@@ -38,7 +48,6 @@ where
3848
A: Scalar,
3949
{
4050
type Elem = A;
41-
type Repr = S;
4251
type OwnedRepr = OwnedRepr<A>;
4352
type Error = QdkSimError;
4453
type Output = LU<A, OwnedRepr<A>>;
@@ -51,7 +60,7 @@ where
5160
QdkSimError::CannotConvertElement("f64".to_string(), type_name::<A::Real>().to_string())
5261
})?;
5362

54-
let mut factors = &mut (*self).to_owned();
63+
let factors = &mut (*self).to_owned();
5564
let mut pivots: Array1<_> = (0..n_rows).collect::<Vec<_>>().into();
5665

5766
for j in 0..n_rows {
@@ -178,16 +187,84 @@ where
178187

179188
#[cfg(test)]
180189
mod tests {
181-
use ndarray::array;
190+
use approx::assert_abs_diff_eq;
191+
use cauchy::c64;
192+
use ndarray::{array, Array2, OwnedRepr};
182193

183-
use crate::{error::QdkSimError, linalg::decompositions::LUDecomposable};
194+
use crate::{
195+
error::QdkSimError,
196+
linalg::decompositions::{LUDecomposable, LU},
197+
};
184198

185199
#[test]
186200
fn lu_decomposition_works_f64() -> Result<(), QdkSimError> {
187201
let mtx = array![[6.0, 18.0, 3.0], [2.0, 12.0, 1.0], [4.0, 15.0, 3.0]];
188-
// TODO: Actually write the test!
189-
let lu = mtx.lu()?;
190-
println!("{:?}", lu);
202+
let lu: LU<f64, OwnedRepr<f64>> = mtx.lu()?;
203+
204+
// NB: This tests the internal structure of the LU decomposition, and
205+
// may validly fail if the algorithm above is modified.
206+
let expected_factors = array![
207+
[6.0, 18.0, 3.0],
208+
[0.3333333333333333, 6.0, 0.0],
209+
[0.6666666666666666, 0.5, 1.0],
210+
];
211+
for (actual, expected) in lu.factors.iter().zip(expected_factors.iter()) {
212+
assert_abs_diff_eq!(actual, expected, epsilon = 1e-6);
213+
}
214+
215+
let expected_pivots = vec![0, 1, 2];
216+
assert_eq!(lu.pivots.to_vec(), expected_pivots);
217+
Ok(())
218+
}
219+
220+
#[test]
221+
fn lu_decomposition_works_c64() -> Result<(), QdkSimError> {
222+
// In [1]: import scipy.linalg as la
223+
// In [2]: la.lu([
224+
// ...: [-1, 1j, -2],
225+
// ...: [3, 0, -4j],
226+
// ...: [-1, 5, -1]
227+
// ...: ])
228+
// Out[2]: (array([[0., 0., 1.],
229+
// [1., 0., 0.],
230+
// [0., 1., 0.]]),
231+
// array([[ 1. +0.j , 0. +0.j , 0. +0.j ],
232+
// [-0.33333333+0.j , 1. +0.j , 0. +0.j ],
233+
// [-0.33333333+0.j , 0. +0.2j, 1. +0.j ]]),
234+
// array([[ 3. +0.j , 0. +0.j ,
235+
// -0. -4.j ],
236+
// [ 0. +0.j , 5. +0.j ,
237+
// -1. -1.33333333j],
238+
// [ 0. +0.j , 0. +0.j ,
239+
// -2.26666667-1.13333333j]]))
240+
let mtx: Array2<c64> = array![
241+
[c64::new(-1.0, 0.0), c64::new(0.0, 1.0), c64::new(-2.0, 0.0)],
242+
[c64::new(3.0, 0.0), c64::new(0.0, 0.0), c64::new(0.0, -4.0)],
243+
[c64::new(-1.0, 0.0), c64::new(5.0, 0.0), c64::new(-1.0, 0.0)]
244+
];
245+
let lu: LU<c64, OwnedRepr<c64>> = mtx.lu()?;
246+
247+
// NB: This tests the internal structure of the LU decomposition, and
248+
// may validly fail if the algorithm above is modified.
249+
let expected_factors = array![
250+
[c64::new(3.0, 0.0), c64::new(0.0, 0.0), c64::new(0.0, -4.0)],
251+
[
252+
c64::new(-0.3333333333333333, 0.0),
253+
c64::new(5.0, 0.0),
254+
c64::new(-1.0, -1.3333333333333333)
255+
],
256+
[
257+
c64::new(-0.3333333333333333, 0.0),
258+
c64::new(0.0, 0.2),
259+
c64::new(-2.26666667, -1.13333333)
260+
],
261+
];
262+
for (actual, expected) in lu.factors.iter().zip(expected_factors.iter()) {
263+
assert_abs_diff_eq!(actual, expected, epsilon = 1e-6);
264+
}
265+
266+
let expected_pivots = vec![1, 2, 2];
267+
assert_eq!(lu.pivots.to_vec(), expected_pivots);
191268
Ok(())
192269
}
193270
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
//! Support for various decompositions used in linear algebra.
2+
13
mod lu;
24
pub use lu::*;
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use cauchy::Scalar;
2+
use ndarray::{Array1, Array2, Data, OwnedRepr, RawData};
3+
4+
use crate::linalg::decompositions::{LUDecomposable, LUDecomposition};
5+
6+
/// Types that support the matrix inverse $A^{-1}$.
7+
pub trait Inv {
8+
/// Errors that can result from the [`inv`] method.
9+
///
10+
/// [`inv`]: Inv::inv
11+
type Error;
12+
/// Type used to represent $A^{-1}$ when calling [`inv`].
13+
///
14+
/// [`inv`]: Inv::inv
15+
type Output;
16+
17+
/// Computes the inverse of a given matrix.
18+
fn inv(&self) -> Result<Self::Output, Self::Error>;
19+
}
20+
21+
impl<M, A, E> Inv for M
22+
where
23+
M: LUDecomposable<Elem = A, OwnedRepr = OwnedRepr<A>, Error = E>,
24+
A: Scalar,
25+
// TODO: Allow decomposables and decompositions to have different errors.
26+
<M as LUDecomposable>::Output: LUDecomposition<A, OwnedRepr<A>, Error = E>,
27+
{
28+
type Error = M::Error;
29+
type Output =
30+
<<M as LUDecomposable>::Output as LUDecomposition<A, OwnedRepr<A>>>::MatrixSolution;
31+
32+
fn inv(&self) -> Result<Self::Output, Self::Error> {
33+
// TODO: Check determinant here.
34+
let lu = self.lu()?;
35+
// TODO: Change to use Identity trait.
36+
let id_mtx = Array2::from_diag(&Array1::ones(lu.order()));
37+
lu.solve_matrix(&id_mtx)
38+
}
39+
}
40+
41+
#[cfg(test)]
42+
mod tests {
43+
use approx::assert_abs_diff_eq;
44+
use cauchy::c64;
45+
use ndarray::array;
46+
use num_traits::Zero;
47+
48+
use crate::{error::QdkSimError, linalg::Inv};
49+
50+
#[test]
51+
fn inv_works_f64() -> Result<(), QdkSimError> {
52+
let mtx = array![[6.0, 18.0, 3.0], [2.0, 12.0, 1.0], [4.0, 15.0, 3.0]];
53+
// TODO: Actually write the test!
54+
let inv = mtx.inv()?;
55+
println!("{:?}", inv);
56+
Ok(())
57+
}
58+
59+
#[test]
60+
fn inv_works_c64() -> Result<(), QdkSimError> {
61+
let mtx = array![
62+
[c64::zero(), c64::new(0.0, 1.0), c64::new(2.0, 0.0)],
63+
[c64::new(0.0, 3.0), c64::new(4.0, 0.0), c64::new(0.0, 5.0)],
64+
[c64::new(6.0, 0.0), c64::new(0.0, 7.0), c64::new(8.0, 0.0)]
65+
];
66+
let inv = mtx.inv()?;
67+
let expected_inv = array![
68+
[
69+
c64::new(-0.69791667, 0.),
70+
c64::new(0., -0.0625),
71+
c64::new(0.13541667, 0.)
72+
],
73+
[
74+
c64::new(0., -0.0625),
75+
c64::new(0.125, 0.),
76+
c64::new(0., -0.0625)
77+
],
78+
[
79+
c64::new(0.46875, 0.),
80+
c64::new(0., -0.0625),
81+
c64::new(-0.03125, 0.)
82+
],
83+
];
84+
85+
for (actual, expected) in inv.iter().zip(expected_inv.iter()) {
86+
assert_abs_diff_eq!(actual, expected, epsilon = 1e-6);
87+
}
88+
Ok(())
89+
}
90+
}

src/Simulation/qdk_sim_rs/src/linalg/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ use crate::{common_matrices::nq_eye, C64};
1414
mod tensor;
1515
pub use tensor::*;
1616

17+
mod inv;
18+
pub use inv::*;
19+
1720
pub mod decompositions;
1821

1922
// Define private modules as well.

src/Simulation/qdk_sim_rs/src/tableau.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33

44
use std::fmt::Display;
55

6-
use crate::utils::{set_row_to_row_sum, set_vec_to_row_sum, swap_columns};
6+
use crate::{
7+
error::QdkSimError,
8+
utils::{set_row_to_row_sum, set_vec_to_row_sum, swap_columns},
9+
};
710
use ndarray::{s, Array, Array1, Array2};
811
use serde::{Deserialize, Serialize};
912

@@ -91,16 +94,18 @@ impl Tableau {
9194
///
9295
/// If the assertion would pass, `Ok(())` is returned, otherwise an [`Err`]
9396
/// describing the assertion failure is returned.
94-
pub fn assert_meas(&self, idx_target: usize, expected: bool) -> Result<(), String> {
95-
let actual = self.determinstic_result(idx_target).ok_or(format!(
96-
"Expected {}, but measurement result would be random.",
97-
expected
98-
))?;
97+
pub fn assert_meas(&self, idx_target: usize, expected: bool) -> Result<(), QdkSimError> {
98+
let actual = self
99+
.determinstic_result(idx_target)
100+
.ok_or(QdkSimError::MiscError(format!(
101+
"Expected {}, but measurement result would be random.",
102+
expected
103+
)))?;
99104
if actual != expected {
100-
Err(format!(
105+
Err(QdkSimError::MiscError(format!(
101106
"Expected {}, but measurement result would actually be {}.",
102107
expected, actual
103-
))
108+
)))
104109
} else {
105110
Ok(())
106111
}

src/Simulation/qdk_sim_rs/tests/chp_simulation_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
use qdk_sim::{Pauli, Process, State, Tableau};
4+
use qdk_sim::{error::QdkSimError, Pauli, Process, State, Tableau};
55

66
#[test]
7-
fn pauli_channel_applies_correctly() -> Result<(), String> {
7+
fn pauli_channel_applies_correctly() -> Result<(), QdkSimError> {
88
let x = Process::new_pauli_channel(Pauli::X);
99
let state = State::new_stabilizer(1);
1010
let output_state = x.apply(&state)?;

0 commit comments

Comments
 (0)