// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! This crate contains the noisy simulator backend for the Q# language.
//!
//! It includes two simulators:
//! - A density matrix simulator.
//! - A state vector simulator.
//!
//! # Density matrix simulator
//! The density matrix simulator is faster, since it evolves the entire probability space
//! of the system as a whole, so you only need to run the simulation once, and take all
//! the samples you need from the final density matrix. However, it is more memory intensive.
//!
//! A density matrix has 2 ^ (2 * `number_of_qubits`) complex numbers entries.
//! If each complex number is represented as two 64-bits floating point numbers,
//! the density matrix will be 2 ^ (2 * `number_of_qubits`) * 16 bytes. E.g., a density
//! matrix representing a 20 qubits system will be 17592186044416 bytes, or 16.4 TB.
//!
//! # State vector simulator
//! The state vector simulator allocates less memory, however if you want 1,000,000 shots
//! of the circuit, you need to run the simulation 1,000,000 times.
//! A state vector has 2 ^ (`number_of_qubits`) complex entries. So, a state vector of a 20
//! qubits system will be 16777216 bytes, or 16 MB.
//!
//! # Which one should I use?
//! If you are interested in running many shots of the circuit it is better to use the
//! density matrix simulator, as long as you have enough memory in your system (13 qubits or less).
//!
//! However if you are interested in a single or very few shots, you should use the state
//! vector simulator.
#![deny(missing_docs)]
pub(crate) mod density_matrix_simulator;
pub(crate) mod instrument;
pub(crate) mod kernel;
pub(crate) mod operation;
pub(crate) mod state_vector_simulator;
#[cfg(test)]
pub(crate) mod tests;
use nalgebra::{DMatrix, DVector};
use num_complex::Complex;
use thiserror::Error;
// Re-exports.
pub use {
density_matrix_simulator::{DensityMatrix, DensityMatrixSimulator},
instrument::Instrument,
operation::Operation,
state_vector_simulator::{StateVector, StateVectorSimulator},
};
/// A square matrix of `Complex<f64>`.
pub type SquareMatrix = DMatrix<Complex<f64>>;
/// A complex vector.
pub type ComplexVector = DVector<Complex<f64>>;
/// Error tolerance used in the simulators.
pub(crate) const TOLERANCE: f64 = 1e-12;
/// A trait representing a noisy quantum circuit simulator.
pub trait NoisySimulator {
/// State of the noisy simulator. Depending on the simulation method the state will be
/// a `DensityMatrix` or a `StateVector`.
type State;
/// Creates a new `NoisySimulator`.
fn new(number_of_qubits: usize) -> Self;
/// Creates a new `NoisySimulator` with a given seed for its random number generator.
fn new_with_seed(number_of_qubits: usize, seed: u64) -> Self;
/// Apply an operation to the given qubit ids.
fn apply_operation(&mut self, operation: &Operation, qubits: &[usize]) -> Result<(), Error>;
/// Performs selective evolution under the given instrument.
/// Returns the index of the observed outcome.
///
/// Use this method to perform measurements on the quantum system.
fn sample_instrument(
&mut self,
instrument: &Instrument,
qubits: &[usize],
) -> Result<usize, Error>;
/// Performs selective evolution under the given instrument using a custom random distribution.
/// Returns the index of the observed outcome.
///
/// This method is used for testing and debugging purposes.
fn sample_instrument_with_distribution(
&mut self,
instrument: &Instrument,
qubits: &[usize],
random_sample: f64,
) -> Result<usize, Error>;
/// Returns the `State` if the simulator is in a valid state.
fn state(&self) -> Result<&Self::State, &Error>;
/// Set state of the quantum system.
fn set_state(&mut self, new_state: Self::State) -> Result<(), Error>;
/// Return theoretical change in trace due to operations that have been applied so far
/// In reality, the density matrix is always renormalized after instruments/operations
/// have been applied.
fn trace_change(&self) -> Result<f64, Error>;
/// Set the trace of the quantum system.
fn set_trace(&mut self, trace: f64) -> Result<(), Error>;
}
/// A noisy simulation error.
#[derive(Clone, Debug, Error, PartialEq)]
pub enum Error {
/// Failure when building a `DensityMatrix` from raw data.
#[error("error when building `DensityMatrix` from raw_data: {0}")]
DensityMatrixTryFromError(String),
/// Failure when building an instrument.
#[error("error when building instrument: {0}")]
FailedToConstructInstrument(String),
/// Failure when building an operation.
#[error("error when building operation: {0}")]
FailedToConstructOperation(String),
/// Failure when sampling instrument outcome.
#[error("numerical error: no outcome found when sampling instrument")]
FailedToSampleInstrumentOutcome,
/// Failure when sampling Kraus operators.
#[error("numerical error: no outcome found when sampling Kraus operators")]
FailedToSampleKrausOperators,
/// Provided an invalid state when creating or setting the state of the simulator.
#[error("provided an invalid state when creating or setting the state of the simulator: {0}")]
InvalidState(String),
/// `Matrix` ⋅ `Vector` multiplication mismatch.
#[error(
"matrix ⋅ vector multiplication mismatch; matrix is of dimension ({nrows}, {ncols}) but vector has {vec_dim} entries"
)]
MatrixVecDimensionMismatch {
/// Number of rows in the matrix.
nrows: usize,
/// Number of columns in the matrix.
ncols: usize,
/// Number of elements in the vector.
vec_dim: usize,
},
/// State is not normalized.
#[error("numerical error: trace should be between 0 and 1, but it is {0}")]
NotNormalized(f64),
/// A numerical error, such as a probability-0 event.
#[error("numerical error: probability-0 event")]
ProbabilityZeroEvent,
/// A qubit-id is greater than the number of qubits the simulation supports.
#[error("qubit id out of bounds: {0}")]
QubitIdOutOfBounds(usize),
/// Failure when building a `StateVector` from raw data.
#[error("error when building `StateVector` from raw_data: {0}")]
StateVectorTryFromError(String),
/// Trace is not real
#[error(
"state trace should be real since it represents a probability, but its imaginary part is: {0}"
)]
TraceIsNotReal(f64),
}
impl Error {
const fn is_unrecoverable(&self) -> bool {
matches!(
self,
Error::ProbabilityZeroEvent
| Error::FailedToSampleInstrumentOutcome
| Error::FailedToSampleKrausOperators
)
}
}
impl From<&Error> for Error {
fn from(value: &Error) -> Self {
value.clone()
}
}
impl From<&mut Error> for Error {
fn from(value: &mut Error) -> Self {
value.clone()
}
}
/// Handles errors in the simulator.
/// If an error is unrecoverable, it will set the state of the simulator to that error,
/// invalidating any further evolution of the quantum system.
macro_rules! handle_error {
($self:expr, $err:expr) => {{
if $err.is_unrecoverable() {
$self.state = Err($err.clone());
}
return Err($err);
}};
}
pub(crate) use handle_error;
pub(crate) fn eq_with_tolerance(left: f64, right: f64, tolerance: f64) -> bool {
(left - right).abs() <= tolerance
}microsoft/qdk
Publicmirrored from https://github.com/microsoft/qdkAvailable
source/noisy_simulator/src/lib.rs
204lines · modepreview