Skip to content

Commit

Permalink
Complete docs and update source code
Browse files Browse the repository at this point in the history
- Finish all the needed docs

- Update dependency, num_cpus, to 1.16.0

- Remove unused utility functions
  • Loading branch information
smu160 committed Jul 25, 2023
1 parent 16b0516 commit 0a28e5d
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 64 deletions.
2 changes: 1 addition & 1 deletion spinoza/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ clap = { version = "4.2.4", features = ["derive"] }
comfy-table = "6.1.4"
env_logger = "0.10.0"
lazy_static = "1.4.0"
num_cpus = "1.15.0"
num_cpus = "1.16.0"
once_cell = "1.17.1"
rand = "0.8.5"
rayon = "1.7.0"
Expand Down
41 changes: 41 additions & 0 deletions spinoza/src/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
};
use std::{collections::HashSet, ops::Index};

/// See https://en.wikipedia.org/wiki/Quantum_register
#[derive(Clone)]
pub struct QuantumRegister(pub Vec<usize>);

Expand All @@ -18,47 +19,66 @@ impl Index<usize> for QuantumRegister {
}

impl QuantumRegister {
/// Create a new QuantumRegister
pub fn new(size: usize) -> Self {
QuantumRegister((0..size).collect())
}

/// The length of the quantum register
#[allow(clippy::len_without_is_empty)]
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}

/// Update quantum register by shift
#[inline]
pub fn update_shift(&mut self, shift: usize) {
self.0 = (shift..shift + self.len()).collect();
}
}

/// Control qubits
#[derive(Clone)]
pub enum Controls {
/// No controls
None,
/// Single Control
Single(usize),
/// Multiple Controls
Ones(Vec<usize>),

/// Mixed Controls
#[allow(dead_code)]
Mixed {
/// Control qubits
controls: Vec<usize>,
/// Zeroes
zeros: HashSet<usize>,
},
}

/// QuantumTransformation to be applied to the State
#[derive(Clone)]
pub struct QuantumTransformation {
/// The quantum logic gate
pub gate: Gate,
/// The target qubits
pub target: usize,
/// The control qubits
pub controls: Controls,
}

/// A model of a Quantum circuit
/// See https://en.wikipedia.org/wiki/Quantum_circuit
pub struct QuantumCircuit {
transformations: Vec<QuantumTransformation>,
/// The Quantum State to which transformations are applied
pub state: State,
}

impl QuantumCircuit {
/// Create a new QuantumCircuit from multiple QuantumRegisters
pub fn new_multi(registers: &mut [&mut QuantumRegister]) -> Self {
let mut bits = 0;

Expand All @@ -72,18 +92,22 @@ impl QuantumCircuit {
}
}

/// Create a new QuantumCircuit from a single QuantumRegister
pub fn new(r: &mut QuantumRegister) -> Self {
QuantumCircuit::new_multi(&mut [r])
}

/// Create a new QuantumCircuit from two QuantumRegisters
pub fn new2(r0: &mut QuantumRegister, r1: &mut QuantumRegister) -> Self {
QuantumCircuit::new_multi(&mut [r0, r1])
}

/// Get a reference to the Quantum State
pub fn get_statevector(&self) -> &State {
&self.state
}

/// Add the X gate for a given target to the list of QuantumTransformations
#[inline]
pub fn x(&mut self, target: usize) {
self.add(QuantumTransformation {
Expand All @@ -93,6 +117,7 @@ impl QuantumCircuit {
});
}

/// Add the Rx gate for a given target to the list of QuantumTransformations
#[inline]
pub fn rx(&mut self, angle: Float, target: usize) {
self.add(QuantumTransformation {
Expand All @@ -102,6 +127,8 @@ impl QuantumCircuit {
});
}

/// Add the CX gate for a given target qubit and control qubit to the list of
/// QuantumTransformations
#[inline]
pub fn cx(&mut self, control: usize, target: usize) {
self.add(QuantumTransformation {
Expand All @@ -111,6 +138,8 @@ impl QuantumCircuit {
});
}

/// Add the CCX gate for a given target qubit and two control qubits to the list of
/// QuantumTransformations
#[inline]
pub fn ccx(&mut self, control1: usize, control2: usize, target: usize) {
self.add(QuantumTransformation {
Expand All @@ -120,6 +149,7 @@ impl QuantumCircuit {
});
}

/// Add the Hadamard (H) gate for a given target qubit to the list of QuantumTransformations
#[inline]
pub fn h(&mut self, target: usize) {
self.add(QuantumTransformation {
Expand All @@ -129,6 +159,7 @@ impl QuantumCircuit {
});
}

/// Add Ry gate for a given target qubit to the list of QuantumTransformations
#[inline]
pub fn ry(&mut self, angle: Float, target: usize) {
self.add(QuantumTransformation {
Expand All @@ -138,6 +169,8 @@ impl QuantumCircuit {
});
}

/// Add CRy gate for a given target qubit and a given control qubit to the list of
/// QuantumTransformations
#[inline]
pub fn cry(&mut self, angle: Float, control: usize, target: usize) {
self.add(QuantumTransformation {
Expand All @@ -147,6 +180,7 @@ impl QuantumCircuit {
});
}

/// Add Phase (P) gate for a given target qubit to the list of QuantumTransformations
#[inline]
pub fn p(&mut self, angle: Float, target: usize) {
self.add(QuantumTransformation {
Expand All @@ -156,6 +190,8 @@ impl QuantumCircuit {
});
}

/// Add the Controlled Phase (CP) gate for a given target qubit and a given control qubit to
/// the list of QuantumTransformations
#[inline]
pub fn cp(&mut self, angle: Float, control: usize, target: usize) {
self.add(QuantumTransformation {
Expand All @@ -165,6 +201,7 @@ impl QuantumCircuit {
});
}

/// Add Z gate for a given target qubit to the list of QuantumTransformations
#[inline]
pub fn z(&mut self, target: usize) {
self.add(QuantumTransformation {
Expand All @@ -174,6 +211,7 @@ impl QuantumCircuit {
});
}

/// Add Rz gate for a given target qubit to the list of QuantumTransformations
#[inline]
pub fn rz(&mut self, angle: Float, target: usize) {
self.add(QuantumTransformation {
Expand All @@ -183,6 +221,7 @@ impl QuantumCircuit {
});
}

/// Add all transformations for an inverse Quantum Fourier Transform to the list of QuantumTransformations
#[inline]
pub fn iqft(&mut self, targets: &[usize]) {
for j in (0..targets.len()).rev() {
Expand All @@ -193,11 +232,13 @@ impl QuantumCircuit {
}
}

/// Add a given `QuantumTransformation` to the list of transformations
#[inline]
pub fn add(&mut self, transformation: QuantumTransformation) {
self.transformations.push(transformation)
}

/// Run the list of transformations against the State
pub fn execute(&mut self) {
for tr in self.transformations.drain(..) {
match (&tr.controls, tr.gate) {
Expand Down
30 changes: 16 additions & 14 deletions spinoza/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
use crate::core::CONFIG;
use clap::Parser;
use num_cpus;

/// Config for simulations that are run using the CLI
#[derive(Debug, Clone, Copy)]
pub struct Config {
pub threads: u32, // If a larger data type is needed, that's just wild
/// The number of threads to distribute the worload amongst.
/// `u32` is used to represent number of threads since 4,294,967,295 is a
/// reasonable upperbound. If you have access to a matrioshka brain, and you
/// need a larger data type, please reach out.
pub threads: u32,
/// Whether or not to print the State represented as a table.
pub print: bool,
pub qubits: u8, // State vector size is 2^{n}, where n is the # of qubits. Assuming single
// precision complex numbers, the upper bound with u8 is 2^255 * 64 bit ≈
// 4.632 * 10^{65} TB (terabytes). Thus, using u8 suffices.
/// The number of qubits that will make up the State. State vector size is 2^{n}, where n is
/// the # of qubits. Assuming single precision complex numbers, the upper bound with u8 is
/// 2^255 * 64 bit ≈ 4.632 * 10^{65} TB (terabytes). Thus, using u8 suffices.
pub qubits: u8,
}

impl Config {
/// Get or init the global Config. The default
pub fn global() -> &'static Config {
CONFIG.get_or_init(Config::test)
}

/// Convert the provided CLI args and turn it into a Config
pub const fn from_cli(args: QSArgs) -> Config {
assert!(args.threads > 0 && args.qubits > 0);
Config {
Expand All @@ -25,23 +34,16 @@ impl Config {
}
}

pub const fn benchmark() -> Config {
fn test() -> Config {
Config {
threads: 1,
qubits: 25,
print: false,
}
}

pub const fn test() -> Config {
Config {
threads: 14,
threads: num_cpus::get().try_into().unwrap(),
qubits: 25,
print: false,
}
}
}

/// Representation of the CLI args
#[derive(Parser, Debug)]
pub struct QSArgs {
#[clap(short, long)]
Expand Down
46 changes: 9 additions & 37 deletions spinoza/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,15 @@ impl State {
}
}

/// Reservoir for sampling
/// See https://research.nvidia.com/sites/default/files/pubs/2020-07_Spatiotemporal-reservoir-resampling/ReSTIR.pdf
pub struct Reservoir {
pub entries: Vec<usize>,
entries: Vec<usize>,
w_s: Float,
}

impl Reservoir {
/// Create a new reservoir for sampling
pub fn new(k: usize) -> Self {
Self {
entries: vec![0; k],
Expand All @@ -75,6 +78,7 @@ impl Reservoir {
modulus(z_re, z_im).powi(2)
}

/// Run the sampling based on the given State
pub fn sampling(&mut self, reals: &[Float], imags: &[Float], m: usize) {
let mut rng = thread_rng();
let mut outcomes = (0..reals.len()).cycle();
Expand All @@ -85,6 +89,7 @@ impl Reservoir {
}
}

/// Create a histogram of the counts for each outcome
pub fn get_outcome_count(&self) -> HashMap<usize, usize> {
let mut samples = HashMap::new();
for entry in self.entries.iter() {
Expand All @@ -94,49 +99,14 @@ impl Reservoir {
}
}

/// Convenience function for running reservoir sampling
pub fn reservoir_sampling(state: &State, k: usize) -> Reservoir {
let m = 1 << 10;
let mut reservoir = Reservoir::new(k);
reservoir.sampling(&state.reals, &state.imags, m);
reservoir
}

// TODO(Saveliy Yusufov): get multiple reservoirs working to parallelize measurement
// pub fn multi_reservoir_sampling(state: &State, k: usize) -> Reservoir {
// let cpus = num_cpus::get();
// let m = 1 << 10;
//
// let mut reservoirs: Vec<Reservoir> = (0..cpus).map(|_| Reservoir::new(k)).collect();
//
// let mut i = 0;
// for (chunk_reals, chunk_imags) in state.reals.chunks(cpus).zip(state.imags.chunks(cpus)) {
// reservoirs[i].sampling(chunk_reals, chunk_imags, m);
// i += 1;
// }
//
// reservoirs
// .into_iter()
// .fold(Reservoir::new(k), |r0, r1| merge_reservoirs(&r0, &r1, k))
// }
//
// fn merge_reservoirs(r0: &Reservoir, r1: &Reservoir, k: usize) -> Reservoir {
// let mut rng = thread_rng();
// let mut res = Reservoir::new(k);
//
// let (w_0, w_1) = (r0.w_s, r1.w_s);
// let prob = w_0 / (w_0 + w_1);
//
// for i in 0..k {
// let epsilon_i: Float = rng.gen();
// if epsilon_i <= prob {
// res.entries[i] = r0.entries[i];
// } else {
// res.entries[i] = r1.entries[i];
// }
// }
// res
// }

// pub fn g_proc(
// state: &mut State,
// range: std::ops::Range<usize>,
Expand Down Expand Up @@ -189,12 +159,14 @@ pub fn reservoir_sampling(state: &State, k: usize) -> Reservoir {
// g_proc(data, range, gate, &marks, zeros, dist);
// }

/// Swap using controlled X gates
pub fn swap(state: &mut State, first: usize, second: usize) {
c_apply(Gate::X, state, first, second);
c_apply(Gate::X, state, second, first);
c_apply(Gate::X, state, first, second);
}

/// Inverse Quantum Fourier transform
pub fn iqft(state: &mut State, targets: &[usize]) {
for j in (0..targets.len()).rev() {
apply(Gate::H, state, targets[j]);
Expand Down
Loading

0 comments on commit 0a28e5d

Please sign in to comment.