diff --git a/src/lib.rs b/src/lib.rs index 5a31e34..79844ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,21 @@ uniffi::include_scaffolding!("qsharp-bridge"); -use crate::sim::qir; +use crate::noise::Noise; +use crate::noise::PauliNoiseDistribution; +use crate::qasm::QasmGenerationOptions; +use crate::qasm::QasmResetBehavior; +use crate::qasm::qasm2; +use crate::qasm::qasm2_expression; +use crate::sim::ExecutionOptions; +use crate::sim::ExecutionState; +use crate::sim::QsError; +use crate::sim::QubitState; use crate::sim::estimate; use crate::sim::estimate_expression; -use crate::sim::qasm2; -use crate::sim::qasm2_expression; +use crate::sim::qir; use crate::sim::run_qs; use crate::sim::run_qs_with_options; -use crate::sim::ExecutionState; -use crate::sim::QsError; -use crate::sim::QubitState; -use crate::sim::ExecutionOptions; -use crate::sim::PauliDistribution; -use crate::qasm::QasmGenerationOptions; -use crate::qasm::QasmResetBehavior; +pub mod noise; +pub mod qasm; pub mod sim; -pub mod qasm; \ No newline at end of file diff --git a/src/noise.rs b/src/noise.rs new file mode 100644 index 0000000..ec0c04f --- /dev/null +++ b/src/noise.rs @@ -0,0 +1,56 @@ +use crate::sim::QsError; + +#[derive(Debug, Clone)] +pub enum Noise { + Ideal, + Pauli { + noise: PauliNoiseDistribution, + }, + BitFlip { + p: f64, + }, + PhaseFlip { + p: f64, + }, + Depolarizing { + p: f64, + }, +} + +impl Noise { + pub fn to_distribution(&self) -> Result { + match self { + Noise::Ideal => PauliNoiseDistribution::new(0.0, 0.0, 0.0), + Noise::Pauli { noise } => Ok(noise.clone()), + Noise::BitFlip { p } => PauliNoiseDistribution::new(*p, 0.0, 0.0), + Noise::PhaseFlip { p } => PauliNoiseDistribution::new(0.0, 0.0, *p), + Noise::Depolarizing { p } => PauliNoiseDistribution::new(*p / 3.0, *p / 3.0, *p / 3.0), + } + } +} + +impl Default for Noise { + fn default() -> Self { + Noise::Ideal + } +} + +#[derive(Debug, Clone)] +pub struct PauliNoiseDistribution { + pub x: f64, + pub y: f64, + pub z: f64, +} + +impl PauliNoiseDistribution { + pub fn new(x: f64, y: f64, z: f64) -> Result { + if x < 0.0 || y < 0.0 || z < 0.0 || x + y + z > 1.0 { + return Err(QsError::ErrorMessage { + error_text: + "Invalid Pauli distribution: values must be non-negative and sum to <= 1.0" + .to_string(), + }); + } + Ok(Self { x, y, z }) + } +} diff --git a/src/qasm.rs b/src/qasm.rs index 90cac76..8101b99 100644 --- a/src/qasm.rs +++ b/src/qasm.rs @@ -1,7 +1,33 @@ use std::collections::HashSet; use num_bigint::BigUint; use num_complex::Complex; -use qsc::{interpret::Value, Backend}; +use qsc::{interpret::{GenericReceiver, Value}, Backend, PackageType, TargetCapabilityFlags}; + +use crate::sim::{create_interpreter, QsError}; + +pub fn qasm2(source: &str, generation_options: QasmGenerationOptions) -> Result { + let mut stdout = vec![]; + let mut out = GenericReceiver::new(&mut stdout); + let mut backend = Qasm2Backend::new(generation_options); + + let mut interpreter = create_interpreter(Some(source), PackageType::Exe, TargetCapabilityFlags::empty())?; + let _ = interpreter.eval_entry_with_sim(&mut backend, &mut out)?; + + let qasm = backend.get_qasm().map_err(|errors| QsError::ErrorMessage { error_text: errors.join(", ") })?; + Ok(qasm) +} + +pub fn qasm2_expression(expression: &str, generation_options: QasmGenerationOptions) -> Result { + let mut stdout = vec![]; + let mut out = GenericReceiver::new(&mut stdout); + let mut backend = Qasm2Backend::new(generation_options); + + let mut interpreter = create_interpreter(None, PackageType::Lib, TargetCapabilityFlags::empty())?; + let _ = interpreter.run_with_sim(&mut backend, &mut out, Some(expression))?; + + let qasm = backend.get_qasm().map_err(|errors| QsError::ErrorMessage { error_text: errors.join(", ") })?; + Ok(qasm) +} pub(crate) struct Qasm2Backend { code: Vec, diff --git a/src/qsharp-bridge.udl b/src/qsharp-bridge.udl index f25852d..deb0504 100644 --- a/src/qsharp-bridge.udl +++ b/src/qsharp-bridge.udl @@ -33,20 +33,32 @@ enum QasmResetBehavior { "Error" }; -dictionary PauliDistribution { +[Enum] +interface Noise { + Ideal(); + Pauli(PauliNoiseDistribution noise); + BitFlip(f64 p); + PhaseFlip(f64 p); + Depolarizing(f64 p); +}; + +dictionary PauliNoiseDistribution { f64 x; f64 y; f64 z; }; interface ExecutionOptions { - constructor(u32 shots, PauliDistribution noise); + constructor(u32 shots, Noise noise, f64? qubit_loss); [Name=from_shots] constructor(u32 shots); [Name=from_noise] - constructor(PauliDistribution noise); + constructor(Noise noise); + + [Name=from_qubit_loss] + constructor(f64 qubit_loss); }; [Error] diff --git a/src/sim.rs b/src/sim.rs index 5346b05..46ee9de 100644 --- a/src/sim.rs +++ b/src/sim.rs @@ -1,40 +1,32 @@ use std::sync::Arc; -use qsc::interpret::{self, GenericReceiver, Interpreter}; -use resource_estimator::{estimate_entry, estimate_expr}; -use thiserror::Error; use num_bigint::BigUint; use num_complex::Complex64; -use qsc::interpret::output::Receiver; use qsc::interpret::output; -use qsc::{format_state_id, LanguageFeatures, PackageType, PauliNoise, SourceMap, SparseSim, TargetCapabilityFlags}; +use qsc::interpret::output::Receiver; +use qsc::interpret::{self, Interpreter}; +use qsc::{ + LanguageFeatures, PackageType, PauliNoise, SourceMap, SparseSim, TargetCapabilityFlags, + format_state_id, +}; +use resource_estimator::{estimate_entry, estimate_expr}; +use thiserror::Error; -use crate::qasm::{Qasm2Backend, QasmGenerationOptions}; +use crate::noise::{Noise, PauliNoiseDistribution}; pub struct ExecutionOptions { pub shots: u32, - pub noise: PauliDistribution, -} - -pub struct PauliDistribution { - pub x: f64, - pub y: f64, - pub z: f64, + pub noise: Noise, + pub qubit_loss: Option, } -impl PauliDistribution { - pub fn new(x: f64, y: f64, z: f64) -> Result { - if x < 0.0 || y < 0.0 || z < 0.0 || x + y + z > 1.0 { - return Err(QsError::ErrorMessage { error_text: "Invalid Pauli distribution: values must be non-negative and sum to <= 1.0".to_string() }); - } - Ok(Self { x, y, z }) - } -} - - impl ExecutionOptions { - pub fn new(shots: u32, noise: PauliDistribution) -> Self { - Self { shots, noise } + pub fn new(shots: u32, noise: Noise, qubit_loss: Option) -> Self { + Self { + shots, + noise, + qubit_loss, + } } pub fn from_shots(shots: u32) -> Self { @@ -44,42 +36,70 @@ impl ExecutionOptions { } } - pub fn from_noise(noise: PauliDistribution) -> Self { + pub fn from_noise(noise: Noise) -> Self { Self { noise, ..Default::default() } } + + pub fn from_qubit_loss(qubit_loss: f64) -> Self { + Self { + qubit_loss: Some(qubit_loss), + ..Default::default() + } + } } impl Default for ExecutionOptions { fn default() -> Self { Self { shots: 1, - noise: PauliDistribution::new(0.0, 0.0, 0.0).unwrap(), + noise: Noise::Pauli { + noise: PauliNoiseDistribution::new(0.0, 0.0, 0.0).unwrap(), + }, + qubit_loss: None, } } } pub fn run_qs(source: &str) -> Result { - let mut interpreter = create_interpreter(Some(source), PackageType::Exe, TargetCapabilityFlags::all())?; + let mut interpreter = + create_interpreter(Some(source), PackageType::Exe, TargetCapabilityFlags::all())?; let mut rec = ExecutionState::default(); let result = interpreter.eval_entry(&mut rec)?; rec.set_result(result.to_string()); return Ok(rec); } -pub fn run_qs_with_options(source: &str, options: Arc) -> Result, QsError> { +pub fn run_qs_with_options( + source: &str, + options: Arc, +) -> Result, QsError> { let mut results: Vec = Vec::new(); - let mut interpreter = create_interpreter(Some(source), PackageType::Exe, TargetCapabilityFlags::all())?; - - let mut sim = if options.noise.x == 0.0 && options.noise.y == 0.0 && options.noise.z == 0.0 { + let mut interpreter = + create_interpreter(Some(source), PackageType::Exe, TargetCapabilityFlags::all())?; + + let noise_probabilities = options.noise.to_distribution()?; + let mut sim = if noise_probabilities.x == 0.0 + && noise_probabilities.y == 0.0 + && noise_probabilities.z == 0.0 + { SparseSim::new() //default } else { - let noise = PauliNoise::from_probabilities(options.noise.x, options.noise.y, options.noise.z).map_err(|error| QsError::ErrorMessage { error_text: error })?; + let noise = PauliNoise::from_probabilities( + noise_probabilities.x, + noise_probabilities.y, + noise_probabilities.z, + ) + .map_err(|error| QsError::ErrorMessage { error_text: error })?; SparseSim::new_with_noise(&noise) }; + if let Some(qubit_loss) = options.qubit_loss { + sim.set_loss(qubit_loss); + } + let shots = options.shots; for _ in 0..shots { let mut rec = ExecutionState::default(); @@ -92,50 +112,39 @@ pub fn run_qs_with_options(source: &str, options: Arc) -> Resu } pub fn qir(expression: &str) -> Result { - let mut interpreter = create_interpreter(None, PackageType::Lib, TargetCapabilityFlags::empty())?; + let mut interpreter = + create_interpreter(None, PackageType::Lib, TargetCapabilityFlags::empty())?; let result = interpreter.qirgen(expression)?; return Ok(result); } pub fn estimate(source: &str, job_params: Option) -> Result { - let mut interpreter = create_interpreter(Some(source), PackageType::Exe, TargetCapabilityFlags::empty())?; + let mut interpreter = create_interpreter( + Some(source), + PackageType::Exe, + TargetCapabilityFlags::empty(), + )?; let params = job_params.as_deref().unwrap_or("[{}]"); let result = estimate_entry(&mut interpreter, params)?; return Ok(result); } -pub fn estimate_expression(expression: &str, job_params: Option) -> Result { - let mut interpreter = create_interpreter(None, PackageType::Lib, TargetCapabilityFlags::empty())?; +pub fn estimate_expression( + expression: &str, + job_params: Option, +) -> Result { + let mut interpreter = + create_interpreter(None, PackageType::Lib, TargetCapabilityFlags::empty())?; let params = job_params.as_deref().unwrap_or("[{}]"); let result = estimate_expr(&mut interpreter, expression, params)?; return Ok(result); } -pub fn qasm2(source: &str, generation_options: QasmGenerationOptions) -> Result { - let mut stdout = vec![]; - let mut out = GenericReceiver::new(&mut stdout); - let mut backend = Qasm2Backend::new(generation_options); - - let mut interpreter = create_interpreter(Some(source), PackageType::Exe, TargetCapabilityFlags::empty())?; - let _ = interpreter.eval_entry_with_sim(&mut backend, &mut out)?; - - let qasm = backend.get_qasm().map_err(|errors| QsError::ErrorMessage { error_text: errors.join(", ") })?; - Ok(qasm) -} - -pub fn qasm2_expression(expression: &str, generation_options: QasmGenerationOptions) -> Result { - let mut stdout = vec![]; - let mut out = GenericReceiver::new(&mut stdout); - let mut backend = Qasm2Backend::new(generation_options); - - let mut interpreter = create_interpreter(None, PackageType::Lib, TargetCapabilityFlags::empty())?; - let _ = interpreter.run_with_sim(&mut backend, &mut out, Some(expression))?; - - let qasm = backend.get_qasm().map_err(|errors| QsError::ErrorMessage { error_text: errors.join(", ") })?; - Ok(qasm) -} - -fn create_interpreter(source: Option<&str>, package_type: PackageType, target_capability_flags: TargetCapabilityFlags) -> Result { +pub(crate) fn create_interpreter( + source: Option<&str>, + package_type: PackageType, + target_capability_flags: TargetCapabilityFlags, +) -> Result { let source_map = match source { Some(source) => SourceMap::new(vec![("temp.qs".into(), source.into())], None), None => SourceMap::default(), @@ -196,13 +205,14 @@ impl Receiver for ExecutionState { qubit_count: usize, ) -> Result<(), output::Error> { self.qubit_count = qubit_count as u64; - self.states = states.iter().map(|(qubit, amplitude)| { - QubitState { + self.states = states + .iter() + .map(|(qubit, amplitude)| QubitState { id: format_state_id(&qubit, qubit_count), amplitude_real: amplitude.re, amplitude_imaginary: amplitude.im, - } - }).collect(); + }) + .collect(); Ok(()) } @@ -221,7 +231,7 @@ impl Receiver for ExecutionState { #[derive(Error, Debug)] pub enum QsError { #[error("Error with message: `{error_text}`")] - ErrorMessage { error_text: String } + ErrorMessage { error_text: String }, } impl From> for QsError { @@ -236,7 +246,9 @@ impl From> for QsError { error_message.push_str(&format!(", error: {:?}", error)); } - QsError::ErrorMessage { error_text: error_message } + QsError::ErrorMessage { + error_text: error_message, + } } } @@ -250,7 +262,7 @@ impl From> for QsError { let qs_error: QsError = vec![interpret_error].into(); let QsError::ErrorMessage { error_text } = qs_error; error_message.push_str(&error_text); - }, + } resource_estimator::Error::Estimation(estimates_error) => { // Handle `estimates::Error` similarly, if applicable error_message.push_str(&format!("Estimation error: {:?}", estimates_error)); @@ -259,6 +271,8 @@ impl From> for QsError { } // ensure that the leading ", " is removed if it's the start of the error message - QsError::ErrorMessage { error_text: error_message.trim_start_matches(", ").to_string() } + QsError::ErrorMessage { + error_text: error_message.trim_start_matches(", ").to_string(), + } } -} \ No newline at end of file +} diff --git a/tests/assets/loss_single_qubit.qs b/tests/assets/loss_single_qubit.qs new file mode 100644 index 0000000..aa8d82e --- /dev/null +++ b/tests/assets/loss_single_qubit.qs @@ -0,0 +1,9 @@ +namespace MyQuantumApp { + + @EntryPoint() + operation Main() : Bool { + use q = Qubit(); + let m = M(q); + IsLossResult(m) + } +} \ No newline at end of file diff --git a/tests/tests_noise.rs b/tests/tests_noise.rs new file mode 100644 index 0000000..8b7713f --- /dev/null +++ b/tests/tests_noise.rs @@ -0,0 +1,67 @@ +use qsharp_bridge::noise::{Noise, PauliNoiseDistribution}; + +#[test] +fn test_ideal_noise_to_distribution() { + let noise = Noise::Ideal; + let dist = noise.to_distribution().unwrap(); + assert_eq!(dist.x, 0.0); + assert_eq!(dist.y, 0.0); + assert_eq!(dist.z, 0.0); +} + +#[test] +fn test_pauli_noise_to_distribution() { + let pauli_dist = PauliNoiseDistribution::new(0.1, 0.2, 0.3).unwrap(); + let noise = Noise::Pauli { + noise: pauli_dist.clone(), + }; + let dist = noise.to_distribution().unwrap(); + assert_eq!(dist.x, 0.1); + assert_eq!(dist.y, 0.2); + assert_eq!(dist.z, 0.3); +} + +#[test] +fn test_bit_flip_noise_to_distribution() { + let noise = Noise::BitFlip { p: 0.1 }; + let dist = noise.to_distribution().unwrap(); + assert_eq!(dist.x, 0.1); + assert_eq!(dist.y, 0.0); + assert_eq!(dist.z, 0.0); +} + +#[test] +fn test_phase_flip_noise_to_distribution() { + let noise = Noise::PhaseFlip { p: 0.2 }; + let dist = noise.to_distribution().unwrap(); + assert_eq!(dist.x, 0.0); + assert_eq!(dist.y, 0.0); + assert_eq!(dist.z, 0.2); +} + +#[test] +fn test_depolarizing_noise_to_distribution() { + let noise = Noise::Depolarizing { p: 0.3 }; + let dist = noise.to_distribution().unwrap(); + assert!((dist.x - 0.1).abs() < 1e-9); + assert!((dist.y - 0.1).abs() < 1e-9); + assert!((dist.z - 0.1).abs() < 1e-9); +} + +#[test] +fn test_pauli_distribution_new_valid() { + let dist = PauliNoiseDistribution::new(0.1, 0.2, 0.3); + assert!(dist.is_ok()); +} + +#[test] +fn test_pauli_distribution_new_invalid_negative() { + let dist = PauliNoiseDistribution::new(-0.1, 0.2, 0.3); + assert!(dist.is_err()); +} + +#[test] +fn test_pauli_distribution_new_invalid_sum() { + let dist = PauliNoiseDistribution::new(0.5, 0.6, 0.1); + assert!(dist.is_err()); +} diff --git a/tests/tests_qasm.rs b/tests/tests_qasm.rs index 6cfaf5c..cd7c9ee 100644 --- a/tests/tests_qasm.rs +++ b/tests/tests_qasm.rs @@ -1,4 +1,4 @@ -use qsharp_bridge::{qasm::{QasmGenerationOptions, QasmResetBehavior}, sim::{qasm2, qasm2_expression}}; +use qsharp_bridge::{qasm::{qasm2, qasm2_expression, QasmGenerationOptions, QasmResetBehavior}}; #[test] fn test_qasm_entanglement() { diff --git a/tests/tests_run.rs b/tests/tests_run.rs index 5aee01f..1f7afd0 100644 --- a/tests/tests_run.rs +++ b/tests/tests_run.rs @@ -99,4 +99,24 @@ fn test_teleportation_shots() { assert_eq!(inner_result.result, Some("true".into())); } +} + +#[test] +fn test_full_loss() { + let source = std::fs::read_to_string("tests/assets/loss_single_qubit.qs").unwrap(); + let options = ExecutionOptions::from_qubit_loss(1.0); + let result = run_qs_with_options(&source, Arc::new(options)).unwrap(); + + assert_eq!(result.len(), 1); + assert_eq!(result[0].result, Some("true".into())); +} + +#[test] +fn test_no_loss() { + let source = std::fs::read_to_string("tests/assets/loss_single_qubit.qs").unwrap(); + let options = ExecutionOptions::from_qubit_loss(0.0); + let result = run_qs_with_options(&source, Arc::new(options)).unwrap(); + + assert_eq!(result.len(), 1); + assert_eq!(result[0].result, Some("false".into())); } \ No newline at end of file