Browse Source

Error Handling

Adam Kelly 6 years ago
parent
commit
ae12dcf551
10 changed files with 177 additions and 157 deletions
  1. 2 5
      Cargo.toml
  2. 12 0
      docs/simulators.md
  3. 2 2
      examples/bell-state.rs
  4. 0 58
      examples/bernstein-vazirani.rs
  5. 11 9
      examples/super-dense.rs
  6. 49 38
      src/backends/opencl/mod.rs
  7. 49 0
      src/error.rs
  8. 0 1
      src/gate.rs
  9. 35 33
      src/lib.rs
  10. 17 11
      src/traits.rs

+ 2 - 5
Cargo.toml

@@ -7,6 +7,8 @@ authors = ["Adam Kelly <adamkelly2201@gmail.com>"]
 num-complex = "0.1.0"
 ocl = "0.18.0"
 rand = "0.5.1"
+failure = "0.1.1"
+failure_derive = "0.1.1"
 
 [[example]]
 name = "bell"
@@ -17,8 +19,3 @@ path = "examples/bell-state.rs"
 name = "super-dense"
 doc = false
 path = "examples/super-dense.rs"
-
-[[example]]
-name="bernstein-vazirani"
-doc = false
-path = "examples/bernstein-vazirani.rs"

+ 12 - 0
docs/simulators.md

@@ -0,0 +1,12 @@
+# Quantum Computer Simulators
+
+## Requirements
+
+At a bare minimum, a quantum computer simulator must have the following parts:
+
+* Ability to create a quantum register with a given numebr of qubits
+* Ability to apply controlled gates, or at minimum the controlled pauli-x gate (CNOT)
+* Ability to apply single qubit gates, which combined with the CNOT form a universal set of gates.
+* Ability to measure single qubits in the register and collapse their state into the measured state.
+
+Other functionality can be added which will make the simulator more useful, which is discussed later

+ 2 - 2
examples/bell-state.rs

@@ -11,11 +11,11 @@ use qcgpu::Simulator;
 fn main() {
     println!("Creating Bell State");
 
-    let mut sim = Simulator::new_opencl(2);
+    let mut sim = Simulator::new_opencl(2).unwrap();
 
     sim.h(0);
     sim.cx(0, 1);
 
     println!("Measurment Results:");
-    println!("{}", sim.measure());
+    println!("{}", sim.measure().unwrap());
 }

+ 0 - 58
examples/bernstein-vazirani.rs

@@ -1,58 +0,0 @@
-//! # Bernstein-Vazirani Algorithm
-//!
-//! This algorithm finds a hidden integer $a \in \{ 0, 1\}^n$ from
-//! an oracle $f_a$ which returns a bit $a \cdot x \equiv \sum_i a_i x_i \mod 2$
-//! for an input $x \in \{0,1\}^n$.
-//!
-//! A classical oracle returns $f_a(x) = a \dot x \mod 2$, while the quantum oracle
-//! must be queried with superpositions of input $x$'s.
-//!
-//! To solve this problem classically, the hidden integer can be found by checking the
-//! oracle with the inputs $x = 1,2,/dots,2^i,2^{n-1}$, where each
-//! query reveals the $i$th bit of $a$ ($a_i$).
-//! This is the optimal classical solution, and is O(n). Using a quantum oracle and the
-//! Bernstein-Vazirani algorithm, $a$ can be found with just one query to the oracle.
-//!
-//! ## The Algorithm
-//!
-//! 1. Initialize $n$ qubits in the state $\lvert 0, \dots, 0\rangle$.
-//! 2. Apply the Hadamard gate $H$ to each qubit.
-//! 3. Apply the inner product oracle.
-//! 4. Apply the Hadamard gate $H$ to each qubit.
-//! 5. Measure the register
-//!
-//! From this procedure, we find that the registers measured value is equal to that of
-//! the original hidden integer.
-
-extern crate qcgpu;
-
-use qcgpu::Simulator;
-use qcgpu::gate::h;
-
-fn main() {
-    let num_qubits = 16; // Number of qubits to use
-    let a = 101; // Hidden integer, bitstring is 1100101
-
-    // You should also make sure that a is representable with $n$ qubits,
-    // by settings a as $a mod 2^n$.
-
-    // Bernstein-Vazirani algorithm
-    let mut state = Simulator::new_opencl(num_qubits); // New quantum register, using the GPU.
-
-    // Apply a hadamard gate to each qubit
-    state.apply_all(h());
-
-    // Apply the inner products oracle
-    for i in 0..num_qubits {
-        if a & (1 << i) != 0 {
-            state.z(i);
-        }
-        // Otherwise should apply identity gate, but computationally this doens't change the state.
-    }
-
-    // Apply hadamard gates before measuring
-    state.apply_all(h());
-
-    println!("Measurement Results: {:?}", state.measure_many(1000));
-    // Measurement Results: {"0000000001100101": 1000}
-}

+ 11 - 9
examples/super-dense.rs

@@ -3,33 +3,35 @@
 //! If Alice and Bob share a pair of entangled qubits, then Alice can encode two classical bits into her one entangled qubit,
 //! send it to Bob, and Bob can decode it with the help of his entangled qubit.
 
+extern crate failure;
 extern crate qcgpu;
 
+use failure::Error;
 use qcgpu::Simulator;
 
-fn superdense(input: &str) -> u8 {
-    let mut state = Simulator::new_opencl(2);
+fn superdense(input: &str) -> Result<u8, Error> {
+    let mut state = Simulator::new_opencl(2)?;
     let input_str = String::from(input);
 
     // Prepare the bell state
-    state.h(0);
-    state.cx(0, 1);
+    state.h(0)?;
+    state.cx(0, 1)?;
 
     // Alice prepares her qubit
     let alice = 1;
     if input_str.get(0..1) == Some("1") {
-        state.z(alice);
+        state.z(alice)?;
     }
     if input_str.get(1..2) == Some("1") {
-        state.x(alice);
+        state.x(alice)?;
     }
 
     println!("\nState after Alice prepares her qubit: \n{}", state);
 
     // Alice sends her qubit to Bob
     let bob = 0;
-    state.cx(alice, bob);
-    state.h(alice);
+    state.cx(alice, bob)?;
+    state.h(alice)?;
 
     println!(
         "\nState after Bob receives Alice's qubit and 'decodes' it: \n{}",
@@ -46,7 +48,7 @@ fn main() {
     let mut input = String::new();
     match io::stdin().read_line(&mut input) {
         Ok(_n) => {
-            let result = superdense(input.as_str());
+            let result = superdense(input.as_str()).unwrap();
             println!("\nDecoded string is: {}", result);
         }
         Err(error) => println!("error: {}", error),

+ 49 - 38
src/backends/opencl/mod.rs

@@ -5,6 +5,7 @@ use ocl::{Buffer, MemFlags, ProQue};
 use num_complex::{Complex, Complex32};
 use rand::random;
 use std::fmt;
+use failure::Error;
 
 // OpenCL Kernel
 pub static KERNEL: &'static str = include_str!("kernel.cl");
@@ -14,69 +15,67 @@ pub struct OpenCL {
     /// OpenCL Buffer for the state vector
     pub buffer: Buffer<Complex<f32>>,
     pro_que: ProQue,
+    num_qubits: u8,
 }
 
 impl OpenCL {
-    pub fn new(num_qubits: u8) -> OpenCL {
+    pub fn new(num_qubits: u8) -> Result<OpenCL, Error> {
         // How many amplitudes needed?
         let num_amps = 2_usize.pow(u32::from(num_qubits)) as usize;
 
         let ocl_pq = ProQue::builder()
             .src(KERNEL)
-            .device(0)
+            .device(1)
             .dims(num_amps)
-            .build()
-            .expect("Error Building ProQue");
+            .build()?;
 
         let buffer: Buffer<Complex32> = Buffer::builder()
             .queue(ocl_pq.queue().clone())
             .flags(MemFlags::new().read_write())
             .len(num_amps)
-            .build()
-            .expect("Source Buffer");
+            .build()?;
 
         let apply = ocl_pq
             .kernel_builder("initialize_register")
             .arg(&buffer)
             .arg(0)
-            .build()
-            .unwrap();
+            .build()?;
 
         unsafe {
-            apply.enq().unwrap();
+            apply.enq()?;
         }
 
-        OpenCL {
+        Ok(OpenCL {
             pro_que: ocl_pq,
             buffer,
-        }
+            num_qubits,
+        })
     }
 
-    fn get_probabilities(&self) -> Vec<f32> {
-        let result_buffer: Buffer<f32> = self.pro_que.create_buffer().unwrap();
+    fn get_probabilities(&self) -> Result<Vec<f32>, Error> {
+        let result_buffer: Buffer<f32> = self.pro_que.create_buffer()?;
 
         let apply = self.pro_que
             .kernel_builder("calculate_probabilities")
             .arg(&self.buffer)
             .arg(&result_buffer)
-            .build()
-            .unwrap();
+            .build()?;
 
         unsafe {
-            apply.enq().unwrap();
+            apply.enq()?;
         }
 
         let mut vec_result = vec![0.0f32; self.buffer.len()];
-        result_buffer.read(&mut vec_result).enq().unwrap();
+        result_buffer.read(&mut vec_result).enq()?;
 
-        vec_result
+        Ok(vec_result)
     }
 }
 
 impl Backend for OpenCL {
-    fn apply_gate(&mut self, gate: Gate, target: u8) {
+    fn apply_gate(&mut self, gate: Gate, target: u8) -> Result<(), Error> {
         // create a temporary vector with the source buffer
-        let result_buffer: Buffer<Complex32> = self.pro_que.create_buffer().unwrap();
+        let result_buffer: Buffer<Complex32> = self.pro_que.create_buffer()?;
 
         let apply = self.pro_que
             .kernel_builder("apply_gate")
@@ -87,18 +86,18 @@ impl Backend for OpenCL {
             .arg(gate.b)
             .arg(gate.c)
             .arg(gate.d)
-            .build()
-            .unwrap();
+            .build()?;
 
         unsafe {
-            apply.enq().unwrap();
+            apply.enq()?;
         }
 
         self.buffer = result_buffer;
+        Ok(())
     }
 
-    fn apply_controlled_gate(&mut self, gate: Gate, control: u8, target: u8) {
-        let result_buffer: Buffer<Complex32> = self.pro_que.create_buffer().unwrap();
+    fn apply_controlled_gate(&mut self, gate: Gate, control: u8, target: u8) -> Result<(), Error> {
+        let result_buffer: Buffer<Complex32> = self.pro_que.create_buffer()?;
 
         let apply = self.pro_que
             .kernel_builder("apply_controlled_gate")
@@ -110,18 +109,19 @@ impl Backend for OpenCL {
             .arg(gate.b)
             .arg(gate.c)
             .arg(gate.d)
-            .build()
-            .unwrap();
+            .build()?;
 
         unsafe {
-            apply.enq().unwrap();
+            apply.enq()?;
         }
 
         self.buffer = result_buffer;
+
+        Ok(())
     }
 
-    fn measure(&self) -> u8 {
-        let probabilities = self.get_probabilities();
+    fn measure(&mut self) -> Result<u8, Error> {
+        let probabilities = self.get_probabilities()?;
 
         let mut key = random::<f32>();
         if key > 1.0 {
@@ -137,7 +137,15 @@ impl Backend for OpenCL {
             i += 1;
         }
 
-        i as u8
+        Ok(i as u8)
+    }
+
+    fn measure_qubit(&mut self, target: u8) -> Result<u8, Error> {
+        unimplemented!()
+    }
+
+    fn num_qubits(&self) -> u8 {
+        self.num_qubits
     }
 }
 
@@ -146,27 +154,30 @@ impl fmt::Display for OpenCL {
         let mut first = true;
 
         let mut vec_result = vec![Complex32::new(0.0, 0.0); self.buffer.len()];
-        self.buffer.read(&mut vec_result).enq().unwrap();
+        self.buffer
+            .read(&mut vec_result)
+            .enq()
+            .expect("Error Reading Memory From Device");
 
         for (idx, item) in vec_result.iter().enumerate() {
             if !first {
-                write!(f, ", ").unwrap();
+                write!(f, ", ")?;
             } else {
                 first = false;
             }
 
-            write!(f, "[{}]: ", idx).unwrap();
+            write!(f, "[{}]: ", idx)?;
 
             // Do we print the imaginary part?
             if item.im == 0.0 {
-                write!(f, "{}", item.re).unwrap();
+                write!(f, "{}", item.re)?;
             } else if item.re == 0.0 {
-                write!(f, "{}i", item.im).unwrap();
+                write!(f, "{}i", item.im)?;
             } else {
-                write!(f, "{}", item).unwrap();
+                write!(f, "{}", item)?;
             }
         }
 
         Ok(())
     }
-}
+}

+ 49 - 0
src/error.rs

@@ -0,0 +1,49 @@
+use failure::{Backtrace, Context, Fail};
+use std::fmt;
+
+#[derive(Debug)]
+struct BackendError {
+    inner: Context<BackendErrorKind>,
+}
+
+#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
+enum BackendErrorKind {
+    #[fail(display = "System doesn't have enough memory.")]
+    MemoryError,
+}
+
+impl Fail for BackendError {
+    fn cause(&self) -> Option<&Fail> {
+        self.inner.cause()
+    }
+
+    fn backtrace(&self) -> Option<&Backtrace> {
+        self.inner.backtrace()
+    }
+}
+
+impl fmt::Display for BackendError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::Display::fmt(&self.inner, f)
+    }
+}
+
+impl BackendError {
+    pub fn kind(&self) -> BackendErrorKind {
+        *self.inner.get_context()
+    }
+}
+
+impl From<BackendErrorKind> for BackendError {
+    fn from(kind: BackendErrorKind) -> BackendError {
+        BackendError {
+            inner: Context::new(kind),
+        }
+    }
+}
+
+impl From<Context<BackendErrorKind>> for BackendError {
+    fn from(inner: Context<BackendErrorKind>) -> BackendError {
+        BackendError { inner: inner }
+    }
+}

+ 0 - 1
src/gate.rs

@@ -59,7 +59,6 @@ pub fn x() -> Gate {
     }
 }
 
-
 /// Pauli Y Gate
 ///
 /// [0, -i]

+ 35 - 33
src/lib.rs

@@ -1,16 +1,21 @@
+extern crate failure;
 extern crate num_complex;
 extern crate ocl;
 extern crate rand;
 
+#[macro_use]
+extern crate failure_derive;
+
+pub mod error;
 pub mod gate;
 pub mod traits;
 pub mod backends;
 
 use backends::OpenCL;
-use gate::{Gate, h, x, y, z};
+use gate::{h, x, y, z, Gate};
 
 use std::fmt;
-use std::collections::HashMap;
+use failure::Error;
 
 #[derive(Debug)]
 pub struct Simulator {
@@ -19,50 +24,47 @@ pub struct Simulator {
 }
 
 impl Simulator {
-    pub fn new_opencl(num_qubits: u8) -> Simulator {
-        Simulator {
-            backend: Box::new(OpenCL::new(num_qubits)),
-            num_qubits
-        }
-    }
+    pub fn new_opencl(num_qubits: u8) -> Result<Simulator, Error> {
+        let backend = OpenCL::new(num_qubits)?;
 
-    pub fn apply_gate(&mut self, gate: Gate, target: u8) {
-        self.backend.apply_gate(gate, target);
+        Ok(Simulator {
+            backend: Box::new(backend),
+            num_qubits,
+        })
     }
 
-    pub fn apply_all(&mut self, gate: Gate) {
-        for i in 0..self.num_qubits { 
-            self.backend.apply_gate(gate, i);
-        }
+    pub fn apply_gate(&mut self, gate: Gate, target: u8) -> Result<(), Error> {
+        self.backend.apply_gate(gate, target)
     }
 
+    pub fn apply_all(&mut self, gate: Gate) -> Result<(), Error> {
+        for i in 0..self.num_qubits {
+            self.backend.apply_gate(gate, i)?
+        }
 
-    pub fn x(&mut self, target: u8) {
-        self.backend.apply_gate(x(), target);
+        Ok(())
     }
 
-    pub fn y(&mut self, target: u8) {
-        self.backend.apply_gate(y(), target);
+    pub fn x(&mut self, target: u8) -> Result<(), Error> {
+        self.backend.apply_gate(x(), target)
     }
-    
-    pub fn z(&mut self, target: u8) {
-        self.backend.apply_gate(z(), target);
+    pub fn y(&mut self, target: u8) -> Result<(), Error> {
+        self.backend.apply_gate(y(), target)
     }
-
-    pub fn h(&mut self, target: u8) {
-        self.backend.apply_gate(h(), target);
+    pub fn z(&mut self, target: u8) -> Result<(), Error> {
+        self.backend.apply_gate(z(), target)
     }
-
-    pub fn cx(&mut self, control: u8, target: u8) {
-        self.backend.apply_controlled_gate(x(), control, target);
+    pub fn h(&mut self, target: u8) -> Result<(), Error> {
+        self.backend.apply_gate(h(), target)
     }
-
-    pub fn measure(&self) -> u8 {
+    pub fn cx(&mut self, control: u8, target: u8) -> Result<(), Error> {
+        self.backend.apply_controlled_gate(x(), control, target)
+    }
+    pub fn measure(&mut self) -> Result<u8, Error> {
         self.backend.measure()
     }
-
-    pub fn measure_many(&self, iters: u32) -> HashMap<u8, u32> {
-        self.backend.measure_many(iters)
+    pub fn num_qubits(&mut self) -> u8 {
+        self.backend.num_qubits()
     }
 }
 
@@ -70,4 +72,4 @@ impl fmt::Display for Simulator {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(f, "{}", self.backend)
     }
-}
+}

+ 17 - 11
src/traits.rs

@@ -1,20 +1,26 @@
 use gate::Gate;
 use std::fmt::{Debug, Display};
-use std::collections::HashMap;
+use failure::Error;
 
 pub trait Backend: Debug + Display {
-    fn apply_gate(&mut self, gate: Gate, target: u8);
-    fn apply_controlled_gate(&mut self, gate: Gate, control: u8, target: u8);
-    fn measure(&self) -> u8;
-    fn measure_many(&self, iters: u32) -> HashMap<u8, u32> {
-        let mut results = HashMap::new();
+    fn num_qubits(&self) -> u8;
+    fn apply_gate(&mut self, gate: Gate, target: u8) -> Result<(), Error>;
+    fn apply_controlled_gate(&mut self, gate: Gate, control: u8, target: u8) -> Result<(), Error>;
+    fn measure_qubit(&mut self, target: u8) -> Result<u8, Error>;
 
-        for _ in 0..iters {
-            let state = self.measure();
-            let count = results.entry(state).or_insert(0);
-            *count += 1;
+    fn measure(&mut self) -> Result<u8, Error> {
+        let mut result = 0;
+        for i in 0..self.num_qubits() {
+            let bit_mask = 1 << i;
+            if self.measure_qubit(i)? == 1 {
+                // 1, set the bit in result
+                result = result | bit_mask
+            } else {
+                // 0, clear the bit in result
+                result = result & (!bit_mask)
+            }
         }
 
-        results
+        Ok(result)
     }
 }