Initial import
This commit is contained in:
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
[package]
|
||||
name = "spinoza"
|
||||
version = "0.5.1"
|
||||
edition = "2021"
|
||||
|
||||
authors = ["Saveliy Yusufov", "Charlee Stefanski", "Constantin Gonciulea"]
|
||||
license = "Apache-2.0"
|
||||
description = "A High Performance Quantum State Simulator"
|
||||
documentation = "https://qustate.github.io/spinoza/"
|
||||
repository = "A High Performance Quantum State Simulator implemented in Rust "
|
||||
keywords = ["quantum-computing", "simulation", "simulator", "quantum"]
|
||||
categories = ["science", "simulation"]
|
||||
|
||||
[features]
|
||||
default = ["double"]
|
||||
single = []
|
||||
double = []
|
||||
|
||||
[dependencies]
|
||||
arrayvec = "0.7.4"
|
||||
clap = { version = "4.4.13", features = ["derive"] }
|
||||
comfy-table = "7.1.0"
|
||||
env_logger = "0.10.1"
|
||||
evalexpr = "11.3.0"
|
||||
multiversion = "0.7.4"
|
||||
qasm = "1.0.0"
|
||||
rand = "0.8.5"
|
||||
rand_distr = "0.4.3"
|
||||
rayon = "1.8.0"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5.1"
|
||||
|
||||
[[bench]]
|
||||
name = "benchmark"
|
||||
harness = false
|
||||
Vendored
+188
@@ -0,0 +1,188 @@
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use rand::prelude::*;
|
||||
use spinoza::{
|
||||
circuit::{QuantumCircuit, QuantumRegister},
|
||||
core::{iqft, State},
|
||||
gates::{apply, c_apply, Gate},
|
||||
math::{pow2f, Float, PI},
|
||||
measurement::measure_qubit,
|
||||
utils::{gen_random_state, pretty_print_int},
|
||||
};
|
||||
|
||||
fn first_rotation(circuit: &mut QuantumCircuit, nqubits: usize, angles: &mut Vec<Float>) {
|
||||
for k in 0..nqubits {
|
||||
circuit.rx(angles.pop().unwrap(), k);
|
||||
circuit.rz(angles.pop().unwrap(), k);
|
||||
}
|
||||
}
|
||||
|
||||
fn mid_rotation(circuit: &mut QuantumCircuit, nqubits: usize, angles: &mut Vec<Float>) {
|
||||
for k in 0..nqubits {
|
||||
circuit.rz(angles.pop().unwrap(), k);
|
||||
circuit.rx(angles.pop().unwrap(), k);
|
||||
circuit.rz(angles.pop().unwrap(), k);
|
||||
}
|
||||
}
|
||||
|
||||
fn last_rotation(circuit: &mut QuantumCircuit, nqubits: usize, angles: &mut Vec<Float>) {
|
||||
for k in 0..nqubits {
|
||||
circuit.rz(angles.pop().unwrap(), k);
|
||||
circuit.rx(angles.pop().unwrap(), k);
|
||||
}
|
||||
}
|
||||
|
||||
fn entangler(circuit: &mut QuantumCircuit, pairs: &[(usize, usize)]) {
|
||||
for (a, b) in pairs.iter() {
|
||||
circuit.cx(*a, *b);
|
||||
}
|
||||
}
|
||||
|
||||
fn build_circuit(nqubits: usize, depth: usize, pairs: &[(usize, usize)]) -> QuantumCircuit {
|
||||
let mut rng = StdRng::seed_from_u64(42);
|
||||
let mut angles: Vec<_> = (0..(nqubits * 2) + (depth * nqubits * 3) + (nqubits * 2))
|
||||
.map(|_| rng.gen())
|
||||
.collect();
|
||||
|
||||
let mut q = QuantumRegister::new(nqubits);
|
||||
let mut circuit = QuantumCircuit::new(&mut [&mut q]);
|
||||
first_rotation(&mut circuit, nqubits, &mut angles);
|
||||
entangler(&mut circuit, pairs);
|
||||
for _ in 0..depth {
|
||||
mid_rotation(&mut circuit, nqubits, &mut angles);
|
||||
entangler(&mut circuit, pairs);
|
||||
}
|
||||
|
||||
last_rotation(&mut circuit, nqubits, &mut angles);
|
||||
circuit
|
||||
}
|
||||
|
||||
pub fn qcbm(circuit: &mut QuantumCircuit) {
|
||||
circuit.execute();
|
||||
}
|
||||
|
||||
fn value_encoding(n: usize, v: Float) {
|
||||
let mut state = State::new(n);
|
||||
|
||||
for i in 0..n {
|
||||
apply(Gate::H, &mut state, i);
|
||||
}
|
||||
for i in 0..n {
|
||||
apply(Gate::P(2.0 * PI / (pow2f(i + 1)) * v), &mut state, i);
|
||||
}
|
||||
|
||||
let targets: Vec<usize> = (0..n).rev().collect();
|
||||
iqft(&mut state, &targets);
|
||||
}
|
||||
|
||||
fn h_gate(n: usize) {
|
||||
let mut state = State::new(n);
|
||||
|
||||
for i in 0..n {
|
||||
apply(Gate::H, &mut state, i);
|
||||
}
|
||||
}
|
||||
|
||||
fn rx_gate(n: usize) {
|
||||
let mut state = State::new(n);
|
||||
|
||||
for i in 0..n {
|
||||
apply(Gate::RX(1.0), &mut state, i);
|
||||
}
|
||||
}
|
||||
|
||||
fn rz_gate(state: &mut State, n: usize) {
|
||||
for i in 0..n {
|
||||
apply(Gate::RZ(1.0), state, i);
|
||||
}
|
||||
}
|
||||
|
||||
fn x_gate(n: usize) {
|
||||
let mut state = State::new(n);
|
||||
|
||||
for i in 0..n {
|
||||
apply(Gate::X, &mut state, i);
|
||||
}
|
||||
}
|
||||
|
||||
fn p_gate(n: usize) {
|
||||
let mut state = State::new(n);
|
||||
|
||||
for i in 0..n {
|
||||
apply(Gate::P(1.0), &mut state, i);
|
||||
}
|
||||
}
|
||||
|
||||
fn z_gate(n: usize) {
|
||||
let mut state = State::new(n);
|
||||
|
||||
for i in 0..n {
|
||||
apply(Gate::Z, &mut state, i);
|
||||
}
|
||||
}
|
||||
|
||||
fn cx_gate(state: &mut State, n: usize, pairs: &[(usize, usize)]) {
|
||||
for i in 0..n {
|
||||
let (p0, p1) = pairs[i];
|
||||
c_apply(Gate::X, state, p0, p1);
|
||||
}
|
||||
}
|
||||
|
||||
fn u_gate(n: usize) {
|
||||
let mut state = State::new(n);
|
||||
|
||||
for t in 0..n {
|
||||
apply(Gate::U(1.0, 2.0, 3.0), &mut state, t);
|
||||
}
|
||||
}
|
||||
|
||||
fn pprint_int(i: u128) {
|
||||
let _res = pretty_print_int(i);
|
||||
}
|
||||
|
||||
fn measure(n: usize) {
|
||||
let mut state = gen_random_state(n);
|
||||
measure_qubit(&mut state, 0, true, None);
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let n = 25;
|
||||
|
||||
c.bench_function("h", |b| b.iter(|| h_gate(black_box(n))));
|
||||
|
||||
c.bench_function("x", |b| b.iter(|| x_gate(black_box(n))));
|
||||
|
||||
let mut state = State::new(n);
|
||||
let pairs: Vec<_> = (0..n).into_iter().map(|i| (i, (i + 1) % n)).collect();
|
||||
c.bench_function("cx", |b| {
|
||||
b.iter(|| cx_gate(black_box(&mut state), black_box(n), black_box(&pairs)))
|
||||
});
|
||||
|
||||
let mut state = State::new(n);
|
||||
c.bench_function("rz", |b| {
|
||||
b.iter(|| rz_gate(black_box(&mut state), black_box(n)))
|
||||
});
|
||||
|
||||
c.bench_function("rx", |b| b.iter(|| rx_gate(black_box(n))));
|
||||
|
||||
let mut circuit = build_circuit(n, 9, &pairs);
|
||||
c.bench_function("qcbm", |b| b.iter(|| qcbm(&mut circuit)));
|
||||
|
||||
c.bench_function("p", |b| b.iter(|| p_gate(black_box(n))));
|
||||
|
||||
c.bench_function("z", |b| b.iter(|| z_gate(black_box(n))));
|
||||
|
||||
c.bench_function("u", |b| b.iter(|| u_gate(black_box(n))));
|
||||
|
||||
c.bench_function("value_encoding", |b| {
|
||||
b.iter(|| value_encoding(black_box(n), black_box(2.4)))
|
||||
});
|
||||
|
||||
c.bench_function("pprint_int", |b| {
|
||||
b.iter(|| pprint_int(black_box(u128::MAX)))
|
||||
});
|
||||
|
||||
c.bench_function("measure", |b| b.iter(|| measure(black_box(n))));
|
||||
}
|
||||
|
||||
criterion_group! {name = benches; config = Criterion::default().sample_size(100); targets = criterion_benchmark}
|
||||
criterion_main!(benches);
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{State, CONFIG},
|
||||
gates::{apply, cc_apply, Gate},
|
||||
math::PI,
|
||||
utils::{pretty_print_int, to_table},
|
||||
};
|
||||
|
||||
fn x(n: usize, show_results: bool) {
|
||||
let now = std::time::Instant::now();
|
||||
let mut state = State::new(n);
|
||||
|
||||
for i in 0..n {
|
||||
apply(Gate::H, &mut state, i);
|
||||
apply(Gate::RZ(PI), &mut state, i);
|
||||
}
|
||||
|
||||
if show_results {
|
||||
to_table(&state);
|
||||
}
|
||||
|
||||
cc_apply(Gate::X, &mut state, 0, 2, 1);
|
||||
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{}", pretty_print_int(elapsed));
|
||||
if show_results {
|
||||
println!("{}", to_table(&state));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
x(config.qubits.into(), config.print);
|
||||
}
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
circuit::{QuantumCircuit, QuantumRegister},
|
||||
config::{Config, QSArgs},
|
||||
core::CONFIG,
|
||||
math::{pow2f, PI},
|
||||
utils::{pretty_print_int, to_table},
|
||||
};
|
||||
|
||||
fn benchmark_circuit_value_encoding(n: usize, show_results: bool) {
|
||||
let v = 2.4;
|
||||
let now = std::time::Instant::now();
|
||||
let mut q = QuantumRegister::new(n);
|
||||
let mut qc = QuantumCircuit::new(&mut [&mut q]);
|
||||
|
||||
for i in 0..n {
|
||||
qc.h(i)
|
||||
}
|
||||
for i in 0..n {
|
||||
qc.p(2.0 * PI / pow2f(i + 1) * v, i)
|
||||
}
|
||||
|
||||
let targets: Vec<usize> = (0..n).rev().collect();
|
||||
qc.iqft(&targets);
|
||||
qc.execute();
|
||||
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{}", pretty_print_int(elapsed));
|
||||
|
||||
if show_results {
|
||||
println!("{}", to_table(qc.get_statevector()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
benchmark_circuit_value_encoding(config.qubits.into(), config.print);
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
use clap::Parser;
|
||||
use spinoza::core::qubit_expectation_value;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{xyz_expectation_value, State, CONFIG},
|
||||
gates::{apply, Gate},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
|
||||
let n = config.qubits.into();
|
||||
let mut state = State::new(n);
|
||||
let target = 0;
|
||||
|
||||
apply(Gate::RX(0.54), &mut state, target);
|
||||
apply(Gate::RY(0.12), &mut state, target);
|
||||
|
||||
let targets = (0..n).collect::<Vec<usize>>();
|
||||
let exp_vals = xyz_expectation_value('z', &state, &targets);
|
||||
println!("expectation values: {:?}", exp_vals);
|
||||
|
||||
let exp_vals: Vec<_> = (0..n).map(|t| qubit_expectation_value(&state, t)).collect();
|
||||
println!(
|
||||
"expectation values using `qubit_expectation_value`: {:?}",
|
||||
exp_vals
|
||||
);
|
||||
}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{State, CONFIG},
|
||||
gates::{apply, Gate},
|
||||
utils::{pretty_print_int, to_table},
|
||||
};
|
||||
|
||||
fn h(n: usize, show_results: bool) {
|
||||
let now = std::time::Instant::now();
|
||||
let mut state = State::new(n);
|
||||
|
||||
for t in 0..n {
|
||||
apply(Gate::H, &mut state, t);
|
||||
}
|
||||
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{}", pretty_print_int(elapsed));
|
||||
if show_results {
|
||||
println!("{}", to_table(&state));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
h(config.qubits.into(), config.print);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::CONFIG,
|
||||
measurement::measure_qubit,
|
||||
utils::{gen_random_state, pretty_print_int},
|
||||
};
|
||||
|
||||
fn measure_qubits(n: usize) {
|
||||
let mut state = gen_random_state(n);
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
for t in 0..n {
|
||||
measure_qubit(&mut state, t, true, None);
|
||||
}
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("measured all qubits in {} us", pretty_print_int(elapsed));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
measure_qubits(config.qubits.into());
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{State, CONFIG},
|
||||
gates::{mc_apply, Gate},
|
||||
utils::to_table,
|
||||
};
|
||||
|
||||
fn mcx(_n: usize, show_results: bool) {
|
||||
let mut state = State::new(4);
|
||||
|
||||
mc_apply(Gate::X, &mut state, &[1, 2], None, 0);
|
||||
|
||||
if show_results {
|
||||
println!("{}", to_table(&state));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
mcx(config.qubits.into(), config.print);
|
||||
}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{State, CONFIG},
|
||||
gates::{apply, Gate},
|
||||
utils::{pretty_print_int, to_table},
|
||||
};
|
||||
|
||||
fn p(n: usize, show_results: bool) {
|
||||
let now = std::time::Instant::now();
|
||||
let mut state = State::new(n);
|
||||
|
||||
for i in 0..n {
|
||||
apply(Gate::P(1.0), &mut state, i);
|
||||
}
|
||||
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{}", pretty_print_int(elapsed));
|
||||
if show_results {
|
||||
println!("{}", to_table(&state));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
p(config.qubits.into(), config.print);
|
||||
}
|
||||
Vendored
+92
@@ -0,0 +1,92 @@
|
||||
use spinoza::utils::padded_bin;
|
||||
|
||||
fn print_pairs() {
|
||||
let timer = std::time::Instant::now();
|
||||
let n = 6;
|
||||
let p2n = 1 << n;
|
||||
|
||||
let t = 0;
|
||||
let p2t = 1 << t;
|
||||
|
||||
let c = 3;
|
||||
let p2c = 1 << (c + 1);
|
||||
|
||||
let p2m = 1 << (c - t - 1);
|
||||
|
||||
for i in 0..p2m {
|
||||
for j in (2 * i + 2 * p2m + 1) * p2t..(2 * i + 2 * p2m + 2) * p2t {
|
||||
// println!("{} = {}", j, padded_bin(j, n));
|
||||
for k in (j..j + p2n).step_by(p2c) {
|
||||
println!("{} = {}", k, padded_bin(k, n));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("time elapsed: {} us ... ", timer.elapsed().as_micros());
|
||||
}
|
||||
|
||||
fn print_target_pairs() {
|
||||
let n = 3;
|
||||
|
||||
let target = 1;
|
||||
let dist = 1 << target;
|
||||
|
||||
// 2 for loops
|
||||
println!("for loops");
|
||||
for i in 0..1 << (n - 1 - target) {
|
||||
for j in 2 * i * dist..(2 * i + 1) * dist {
|
||||
println!(
|
||||
"{} = {} -> {} = {}",
|
||||
j,
|
||||
padded_bin(j, n),
|
||||
dist + j,
|
||||
padded_bin(dist + j, n)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// divmod
|
||||
println!("\ndivmod");
|
||||
for i in 0..1 << (n - 1) {
|
||||
// let (p, s) = (i / dist, i % dist);
|
||||
// i = dist*p + s
|
||||
let j = i + ((i >> target) << target); //i + (p << target); // i + dist*p; // 2 * dist * p + s;
|
||||
println!(
|
||||
"{} = {} -> {} = {}",
|
||||
j,
|
||||
padded_bin(j, n),
|
||||
dist + j, // i + ((1 + (i >> target)) << target),
|
||||
padded_bin(dist + j, n)
|
||||
)
|
||||
}
|
||||
|
||||
// bit manipulation
|
||||
println!("\nbit manipulation");
|
||||
let neg_dist = !0 << target;
|
||||
for i in 0..1 << (n - 1) {
|
||||
let j = i + (i & neg_dist);
|
||||
println!(
|
||||
"{} = {} -> {} = {}",
|
||||
j,
|
||||
padded_bin(j, n),
|
||||
dist + j,
|
||||
padded_bin(dist + j, n)
|
||||
)
|
||||
}
|
||||
|
||||
for target in 0..n {
|
||||
for i in 0..1 << (n - 1) {
|
||||
assert_eq!(
|
||||
(i >> target) << target,
|
||||
i & !0 << target,
|
||||
"different for {}",
|
||||
i
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
print_target_pairs();
|
||||
print_pairs();
|
||||
}
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{State, CONFIG},
|
||||
gates::{apply, Gate},
|
||||
utils::{pretty_print_int, to_table},
|
||||
};
|
||||
|
||||
fn pauli_functional(n: usize, show_results: bool) {
|
||||
let now = std::time::Instant::now();
|
||||
let mut state = State::new(n);
|
||||
|
||||
apply(Gate::X, &mut state, 0);
|
||||
|
||||
for _ in 1..(1 << 30) {
|
||||
apply(Gate::H, &mut state, 0);
|
||||
apply(Gate::X, &mut state, 0);
|
||||
apply(Gate::Z, &mut state, 0);
|
||||
}
|
||||
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{}", pretty_print_int(elapsed));
|
||||
if show_results {
|
||||
println!("{}", to_table(&state));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
|
||||
pauli_functional(config.qubits.into(), config.print);
|
||||
}
|
||||
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{State, CONFIG},
|
||||
gates::{apply, c_apply, Gate},
|
||||
utils::{pretty_print_int, to_table},
|
||||
};
|
||||
|
||||
pub fn qcbm_functional(n: usize, show_results: bool) {
|
||||
let pairs: Vec<_> = (0..n).into_iter().map(|i| (i, (i + 1) % n)).collect();
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
let mut state: State = State::new(n);
|
||||
|
||||
for i in 0..n {
|
||||
apply(Gate::RX(1.0), &mut state, i);
|
||||
apply(Gate::RZ(1.0), &mut state, i);
|
||||
}
|
||||
|
||||
for i in 0..n {
|
||||
let (p0, p1) = pairs[i];
|
||||
c_apply(Gate::X, &mut state, p0, p1);
|
||||
}
|
||||
|
||||
for _ in 0..9 {
|
||||
for i in 0..n {
|
||||
apply(Gate::RZ(1.0), &mut state, i);
|
||||
apply(Gate::RX(1.0), &mut state, i);
|
||||
apply(Gate::RZ(1.0), &mut state, i);
|
||||
}
|
||||
|
||||
for i in 0..n {
|
||||
let (p0, p1) = pairs[i];
|
||||
c_apply(Gate::X, &mut state, p0, p1);
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..n {
|
||||
apply(Gate::RZ(1.0), &mut state, i);
|
||||
apply(Gate::RX(1.0), &mut state, i);
|
||||
}
|
||||
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{}", pretty_print_int(elapsed));
|
||||
if show_results {
|
||||
println!("{}", to_table(&state));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
qcbm_functional(config.qubits.into(), config.print);
|
||||
}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{State, CONFIG},
|
||||
gates::{apply, Gate},
|
||||
utils::{pretty_print_int, to_table},
|
||||
};
|
||||
|
||||
fn rx(n: usize, show_results: bool) {
|
||||
let now = std::time::Instant::now();
|
||||
let mut state = State::new(n);
|
||||
|
||||
for t in 0..n {
|
||||
apply(Gate::RX(1.0), &mut state, t);
|
||||
}
|
||||
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{}", pretty_print_int(elapsed));
|
||||
if show_results {
|
||||
println!("{}", to_table(&state));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
rx(config.qubits.into(), config.print);
|
||||
}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{State, CONFIG},
|
||||
gates::{apply, Gate},
|
||||
utils::{pretty_print_int, to_table},
|
||||
};
|
||||
|
||||
fn ry(n: usize, show_results: bool) {
|
||||
let now = std::time::Instant::now();
|
||||
let mut state = State::new(n);
|
||||
|
||||
for i in 0..n {
|
||||
apply(Gate::RY(1.0), &mut state, i);
|
||||
}
|
||||
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{}", pretty_print_int(elapsed));
|
||||
if show_results {
|
||||
println!("{}", to_table(&state));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
ry(config.qubits.into(), config.print);
|
||||
}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{State, CONFIG},
|
||||
gates::{apply, Gate},
|
||||
utils::{pretty_print_int, to_table},
|
||||
};
|
||||
|
||||
fn rz(n: usize, show_results: bool) {
|
||||
let now = std::time::Instant::now();
|
||||
let mut state = State::new(n);
|
||||
|
||||
for t in 0..n {
|
||||
apply(Gate::RZ(1.0), &mut state, t);
|
||||
}
|
||||
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{}", pretty_print_int(elapsed));
|
||||
if show_results {
|
||||
println!("{}", to_table(&state));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
rz(config.qubits.into(), config.print);
|
||||
}
|
||||
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
use clap::Parser;
|
||||
use spinoza::core::reservoir_sampling;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{iqft, State, CONFIG},
|
||||
gates::{apply, Gate},
|
||||
math::{pow2f, PI},
|
||||
utils::{pretty_print_int, to_table},
|
||||
};
|
||||
|
||||
fn run(n: usize, show_results: bool) {
|
||||
let now = std::time::Instant::now();
|
||||
let mut state = State::new(n);
|
||||
|
||||
let v = 2.4;
|
||||
for i in 0..n {
|
||||
apply(Gate::H, &mut state, i);
|
||||
}
|
||||
for i in 0..n {
|
||||
apply(Gate::P(2.0 * PI / (pow2f(i + 1)) * v), &mut state, i);
|
||||
}
|
||||
let targets: Vec<usize> = (0..n).rev().collect();
|
||||
|
||||
iqft(&mut state, &targets);
|
||||
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{}", pretty_print_int(elapsed));
|
||||
if show_results {
|
||||
println!("{}", to_table(&state));
|
||||
}
|
||||
|
||||
let reservoir = reservoir_sampling(&state, state.len(), state.len() * 100_000);
|
||||
let histogram = reservoir.get_outcome_count();
|
||||
println!("{:?}", histogram);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
run(config.qubits.into(), config.print);
|
||||
}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{State, CONFIG},
|
||||
gates::{apply, Gate},
|
||||
utils::{pretty_print_int, to_table},
|
||||
};
|
||||
|
||||
fn u(n: usize, show_results: bool) {
|
||||
let now = std::time::Instant::now();
|
||||
let mut state = State::new(n);
|
||||
|
||||
for i in 0..n {
|
||||
apply(Gate::U(1.0, 2.0, 3.0), &mut state, i);
|
||||
}
|
||||
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{}", pretty_print_int(elapsed));
|
||||
if show_results {
|
||||
println!("{}", to_table(&state));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
u(config.qubits.into(), config.print);
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
use spinoza::{
|
||||
core::State,
|
||||
gates::Gate,
|
||||
unitaries::{apply_unitary, Unitary},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let n = 16;
|
||||
let state = State::new(n);
|
||||
let u = Unitary::from_single_qubit_gate(&state, Gate::U(1.0, 2.0, 3.0), 0);
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
let _s = apply_unitary(&state, &u);
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{elapsed} us elapsed");
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{iqft, State, CONFIG},
|
||||
gates::{apply, Gate},
|
||||
math::{pow2f, PI},
|
||||
utils::{pretty_print_int, to_table},
|
||||
};
|
||||
|
||||
fn value_encoding(n: usize, show_results: bool) {
|
||||
let v = 2.4;
|
||||
let now = std::time::Instant::now();
|
||||
let mut state = State::new(n);
|
||||
|
||||
for i in 0..n {
|
||||
apply(Gate::H, &mut state, i);
|
||||
}
|
||||
for i in 0..n {
|
||||
apply(Gate::P(2.0 * PI / (pow2f(i + 1)) * v), &mut state, i);
|
||||
}
|
||||
let targets: Vec<usize> = (0..n).rev().collect();
|
||||
|
||||
iqft(&mut state, &targets);
|
||||
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{}", pretty_print_int(elapsed));
|
||||
if show_results {
|
||||
println!("{}", to_table(&state));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
value_encoding(config.qubits.into(), config.print);
|
||||
}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{State, CONFIG},
|
||||
gates::{apply, Gate},
|
||||
utils::{pretty_print_int, to_table},
|
||||
};
|
||||
|
||||
fn x(n: usize, show_results: bool) {
|
||||
let now = std::time::Instant::now();
|
||||
let mut state = State::new(n);
|
||||
|
||||
for t in 0..n {
|
||||
apply(Gate::X, &mut state, t);
|
||||
}
|
||||
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{}", pretty_print_int(elapsed));
|
||||
if show_results {
|
||||
println!("{}", to_table(&state));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
x(config.qubits.into(), config.print);
|
||||
}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{State, CONFIG},
|
||||
gates::{apply, Gate},
|
||||
utils::{pretty_print_int, to_table},
|
||||
};
|
||||
|
||||
fn y(n: usize, show_results: bool) {
|
||||
let now = std::time::Instant::now();
|
||||
let mut state = State::new(n);
|
||||
|
||||
for i in 0..n {
|
||||
apply(Gate::Y, &mut state, i);
|
||||
}
|
||||
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{}", pretty_print_int(elapsed));
|
||||
if show_results {
|
||||
println!("{}", to_table(&state));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
y(config.qubits.into(), config.print);
|
||||
}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
use clap::Parser;
|
||||
use spinoza::{
|
||||
config::{Config, QSArgs},
|
||||
core::{State, CONFIG},
|
||||
gates::{apply, Gate},
|
||||
utils::{pretty_print_int, to_table},
|
||||
};
|
||||
|
||||
fn z(n: usize, show_results: bool) {
|
||||
let now = std::time::Instant::now();
|
||||
let mut state = State::new(n);
|
||||
|
||||
for i in 0..n {
|
||||
apply(Gate::Z, &mut state, i);
|
||||
}
|
||||
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
println!("{}", pretty_print_int(elapsed));
|
||||
if show_results {
|
||||
println!("{}", to_table(&state));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = QSArgs::parse();
|
||||
let config = Config::from_cli(args);
|
||||
CONFIG.set(config).unwrap();
|
||||
z(config.qubits.into(), config.print);
|
||||
}
|
||||
Vendored
+1251
File diff suppressed because it is too large
Load Diff
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
//! Configuration options for running spinoza
|
||||
use clap::Parser;
|
||||
|
||||
use crate::core::CONFIG;
|
||||
|
||||
/// Config for simulations that are run using the CLI
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Config {
|
||||
/// The number of threads to distribute the workload.
|
||||
/// `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,
|
||||
/// 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 {
|
||||
threads: args.threads,
|
||||
qubits: args.qubits,
|
||||
print: args.print,
|
||||
}
|
||||
}
|
||||
|
||||
fn test() -> Config {
|
||||
Config {
|
||||
threads: std::thread::available_parallelism()
|
||||
.unwrap()
|
||||
.get()
|
||||
.try_into()
|
||||
.expect("Too much power"),
|
||||
// no input for tests, so this quantity should not matter
|
||||
qubits: 0,
|
||||
print: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of the CLI args
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about)]
|
||||
pub struct QSArgs {
|
||||
/// Number of threads to use
|
||||
#[clap(short, long)]
|
||||
threads: u32,
|
||||
/// Whether or not to print the state in tabular format
|
||||
#[clap(short, long)]
|
||||
print: bool,
|
||||
/// The number of qubits to use in the system
|
||||
#[clap(short, long)]
|
||||
qubits: u8,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn no_args() {
|
||||
let config = Config::global();
|
||||
assert_eq!(
|
||||
config.threads,
|
||||
u32::try_from(std::thread::available_parallelism().unwrap().get()).unwrap()
|
||||
);
|
||||
assert_eq!(config.qubits, 0);
|
||||
assert!(!config.print);
|
||||
}
|
||||
}
|
||||
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
//! Consts for Quantum State Simulation, such as the Pauli Gates and the Hadamard gate
|
||||
use crate::math::{Amplitude, SQRT_ONE_HALF};
|
||||
|
||||
/// The 2 x 2 matrix representation of the Hadamard gate
|
||||
pub const H: [Amplitude; 4] = [
|
||||
Amplitude {
|
||||
re: SQRT_ONE_HALF,
|
||||
im: 0.0,
|
||||
},
|
||||
Amplitude {
|
||||
re: SQRT_ONE_HALF,
|
||||
im: 0.0,
|
||||
},
|
||||
Amplitude {
|
||||
re: SQRT_ONE_HALF,
|
||||
im: 0.0,
|
||||
},
|
||||
Amplitude {
|
||||
re: -SQRT_ONE_HALF,
|
||||
im: 0.0,
|
||||
},
|
||||
];
|
||||
|
||||
/// The 2 x 2 matrix representation of the X gate
|
||||
pub const X: [Amplitude; 4] = [
|
||||
Amplitude { re: 0.0, im: 0.0 },
|
||||
Amplitude { re: 1.0, im: 0.0 },
|
||||
Amplitude { re: 1.0, im: 0.0 },
|
||||
Amplitude { re: 0.0, im: 0.0 },
|
||||
];
|
||||
|
||||
/// The 2 x 2 matrix representation of the Y gate
|
||||
pub const Y: [Amplitude; 4] = [
|
||||
Amplitude { re: 0.0, im: 0.0 },
|
||||
Amplitude { re: 0.0, im: -1.0 },
|
||||
Amplitude { re: 0.0, im: 1.0 },
|
||||
Amplitude { re: 0.0, im: 0.0 },
|
||||
];
|
||||
|
||||
/// The 2 x 2 matrix representation of the Z gate
|
||||
pub const Z: [Amplitude; 4] = [
|
||||
Amplitude { re: 1.0, im: 0.0 },
|
||||
Amplitude { re: 0.0, im: 0.0 },
|
||||
Amplitude { re: 0.0, im: 0.0 },
|
||||
Amplitude { re: -1.0, im: 0.0 },
|
||||
];
|
||||
Vendored
+340
@@ -0,0 +1,340 @@
|
||||
//! Abstractions for representing a Quantum State
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
use rand::distributions::Uniform;
|
||||
use rand::prelude::*;
|
||||
use rayon::prelude::*;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
gates::{apply, c_apply, x_apply, y_apply, z_apply, Gate},
|
||||
math::{modulus, pow2f, Float, PI},
|
||||
};
|
||||
|
||||
/// Reference to the Config for user passed config args
|
||||
pub static CONFIG: OnceLock<Config> = OnceLock::new();
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Representation of a Quantum State. Amplitudes are split between two vectors.
|
||||
pub struct State {
|
||||
/// The real components of the state.
|
||||
pub reals: Vec<Float>,
|
||||
/// The imaginary components of the state.
|
||||
pub imags: Vec<Float>,
|
||||
/// The number of qubits represented by the state.
|
||||
pub n: u8,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Create a new State. The state will always be of size 2^{n},
|
||||
/// where n is the number of qubits. Note that n cannot be 0.
|
||||
pub fn new(n: usize) -> Self {
|
||||
assert!(n > 0);
|
||||
let mut reals = vec![0.0; 1 << n];
|
||||
let imags = vec![0.0; 1 << n];
|
||||
reals[0] = 1.0;
|
||||
Self {
|
||||
n: n as u8,
|
||||
reals,
|
||||
imags,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the size of the state vector. Size of the state should always be
|
||||
/// 2^{n}, where n is the number of qubits.
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.imags.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for State {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.reals
|
||||
.iter()
|
||||
.zip(self.imags.iter())
|
||||
.for_each(|(re, im)| {
|
||||
writeln!(f, "{re} + i{im}").unwrap();
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Reservoir for sampling
|
||||
/// See <https://research.nvidia.com/sites/default/files/pubs/2020-07_Spatiotemporal-reservoir-resampling/ReSTIR.pdf>
|
||||
pub struct Reservoir {
|
||||
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],
|
||||
w_s: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, e_i: usize, w_i: Float) {
|
||||
self.w_s += w_i;
|
||||
let delta = w_i / self.w_s;
|
||||
|
||||
self.entries
|
||||
.par_iter_mut()
|
||||
.with_min_len(1 << 16)
|
||||
.for_each_init(thread_rng, |rng, e| {
|
||||
let epsilon_k: Float = rng.gen();
|
||||
if epsilon_k < delta {
|
||||
*e = e_i;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn weight(reals: &[Float], imags: &[Float], index: usize) -> Float {
|
||||
let (z_re, z_im) = (reals[index], imags[index]);
|
||||
modulus(z_re, z_im).powi(2)
|
||||
}
|
||||
|
||||
/// Run the sampling based on the given State
|
||||
pub fn sampling(&mut self, reals: &[Float], imags: &[Float], num_tests: usize) {
|
||||
debug_assert_eq!(reals.len(), imags.len());
|
||||
let uniform_dist = Uniform::from(0..reals.len());
|
||||
let mut rng = thread_rng();
|
||||
|
||||
self.w_s = 0.0;
|
||||
for _ in 0..num_tests {
|
||||
let outcome = uniform_dist.sample(&mut rng); // aka the index
|
||||
self.update(outcome, Self::weight(reals, imags, outcome));
|
||||
}
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
*samples.entry(*entry).or_insert(0) += 1;
|
||||
}
|
||||
samples
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience function for running reservoir sampling
|
||||
pub fn reservoir_sampling(state: &State, reservoir_size: usize, num_tests: usize) -> Reservoir {
|
||||
let mut reservoir = Reservoir::new(reservoir_size);
|
||||
reservoir.sampling(&state.reals, &state.imags, num_tests);
|
||||
reservoir
|
||||
}
|
||||
|
||||
// pub fn g_proc(
|
||||
// state: &mut State,
|
||||
// range: std::ops::Range<usize>,
|
||||
// gate: &Gate,
|
||||
// marks: &[usize],
|
||||
// zeros: &std::collections::HashSet<usize>,
|
||||
// dist: usize,
|
||||
// ) {
|
||||
// let n = marks.len();
|
||||
//
|
||||
// let mut offset = 0;
|
||||
// for j in 0..n {
|
||||
// if !zeros.contains(&j) {
|
||||
// offset = offset + (1 << marks[j]);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// for i in range {
|
||||
// let mut l1 = i;
|
||||
//
|
||||
// for j in (0..n).rev() {
|
||||
// let k = marks[j] - j;
|
||||
// l1 = l1 + ((l1 >> k) << k);
|
||||
// }
|
||||
// l1 = l1 + offset;
|
||||
// apply(gate, state, l1 - dist, l1);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// general transformation
|
||||
// pub fn g_transform(
|
||||
// state: &mut [Amplitude],
|
||||
// controls: &[usize],
|
||||
// zeros: &HashSet<usize>,
|
||||
// target: usize,
|
||||
// gate: &impl Gate,
|
||||
// ) {
|
||||
// let mut marks = [controls, &[target]].concat();
|
||||
// let num_pairs = state.len() >> marks.len();
|
||||
// marks.sort_unstable();
|
||||
//
|
||||
// let data = SendPtr(state.as_mut_ptr());
|
||||
// let dist = 1 << target;
|
||||
//
|
||||
// let range = Range {
|
||||
// start: 0,
|
||||
// end: num_pairs,
|
||||
// };
|
||||
//
|
||||
// g_proc(data, range, gate, &marks, zeros, dist);
|
||||
// }
|
||||
|
||||
/// 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]);
|
||||
for k in (0..j).rev() {
|
||||
c_apply(Gate::P(-PI / pow2f(j - k)), state, targets[j], targets[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fn apply_bit_flip(prob: Float, target: usize) {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
/// Compute the expectation value of a qubit measurement.
|
||||
pub fn qubit_expectation_value(state: &State, target: usize) -> Float {
|
||||
let chunk_size = 1 << (target + 1);
|
||||
let dist = 1 << target;
|
||||
|
||||
let prob0 = state
|
||||
.reals
|
||||
.par_chunks_exact(chunk_size)
|
||||
.zip_eq(state.imags.par_chunks_exact(chunk_size))
|
||||
.map(|(reals_chunk, imags_chunk)| {
|
||||
reals_chunk
|
||||
.par_iter()
|
||||
.take(dist)
|
||||
.zip_eq(imags_chunk.par_iter().take(dist))
|
||||
.with_min_len(1 << 16)
|
||||
.map(|(re_s0, im_s0)| re_s0.powi(2) + im_s0.powi(2))
|
||||
.sum::<Float>()
|
||||
})
|
||||
.sum::<Float>();
|
||||
|
||||
// p0 - p1 == p0 - (1 - p0) == p0 - 1 + p0 == 2p0 - 1
|
||||
2.0 * prob0 - 1.0
|
||||
}
|
||||
|
||||
/// Compute the expectation value of certain observables (either X, Y, or Z) in the given state.
|
||||
pub fn xyz_expectation_value(observable: char, state: &State, targets: &[usize]) -> Vec<Float> {
|
||||
if !"xyz".contains(observable) {
|
||||
panic!("observable {observable} not supported");
|
||||
}
|
||||
|
||||
let mut working_state = state.clone();
|
||||
let mut values = Vec::with_capacity(targets.len());
|
||||
|
||||
for target in targets.iter() {
|
||||
if observable == 'z' {
|
||||
z_apply(&mut working_state, *target);
|
||||
} else if observable == 'y' {
|
||||
y_apply(&mut working_state, *target);
|
||||
} else {
|
||||
x_apply(&mut working_state, *target);
|
||||
}
|
||||
|
||||
// v = O * psi
|
||||
// <psi | O | psi>
|
||||
|
||||
// <psi | v >
|
||||
// (a + ib) * (c + id) = a * c + ibc + iad - bd = ac - bd + i(bc + ad)
|
||||
let k_re = (
|
||||
&state.reals,
|
||||
&state.imags,
|
||||
&working_state.reals,
|
||||
&working_state.imags,
|
||||
)
|
||||
.into_par_iter()
|
||||
.map(|(s_re, s_im, v_re, v_im)| {
|
||||
let a = *s_re;
|
||||
let b = *s_im;
|
||||
let c = v_re;
|
||||
let d = v_im;
|
||||
a * c + b * d
|
||||
})
|
||||
.sum();
|
||||
|
||||
values.push(k_re);
|
||||
working_state = state.clone();
|
||||
}
|
||||
values
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::utils::assert_float_closeness;
|
||||
|
||||
#[test]
|
||||
fn encoded_integers() {
|
||||
const N: usize = 3;
|
||||
|
||||
let state = State::new(N);
|
||||
let reservoir = reservoir_sampling(&state, state.len(), state.len() * 10_000);
|
||||
let histogram = reservoir.get_outcome_count();
|
||||
let count = *histogram.get(&0).unwrap();
|
||||
assert_eq!(count, state.len());
|
||||
|
||||
for i in 1..(1 << N) {
|
||||
let mut state = State::new(N);
|
||||
state.reals[0] = 0.0;
|
||||
state.reals[i] = 1.0;
|
||||
|
||||
let reservoir = reservoir_sampling(&state, state.len(), state.len() * 10_000);
|
||||
let histogram = reservoir.get_outcome_count();
|
||||
let count = *histogram.get(&i).unwrap();
|
||||
assert_eq!(count, state.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xyz_exp_val() {
|
||||
let mut state = State::new(1);
|
||||
|
||||
apply(Gate::RX(0.54), &mut state, 0);
|
||||
apply(Gate::RY(0.12), &mut state, 0);
|
||||
let exp_vals = xyz_expectation_value('z', &state, &[0]);
|
||||
assert_float_closeness(exp_vals[0], 0.8515405859048367, 0.0001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn xyz_exp_val_bad_observable() {
|
||||
let mut state = State::new(1);
|
||||
apply(Gate::RX(0.54), &mut state, 0);
|
||||
apply(Gate::RY(0.12), &mut state, 0);
|
||||
xyz_expectation_value('a', &state, &[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xyz_exp_val_x_as_observable() {
|
||||
let mut state = State::new(1);
|
||||
apply(Gate::RX(0.54), &mut state, 0);
|
||||
apply(Gate::RY(0.12), &mut state, 0);
|
||||
xyz_expectation_value('x', &state, &[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xyz_exp_val_y_as_observable() {
|
||||
let mut state = State::new(1);
|
||||
apply(Gate::RX(0.54), &mut state, 0);
|
||||
apply(Gate::RY(0.12), &mut state, 0);
|
||||
xyz_expectation_value('y', &state, &[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qubit_exp_val() {
|
||||
let mut state = State::new(1);
|
||||
let target = 0;
|
||||
|
||||
apply(Gate::RX(0.54), &mut state, target);
|
||||
apply(Gate::RY(0.12), &mut state, target);
|
||||
let exp_vals = xyz_expectation_value('z', &state, &[target]);
|
||||
let qubit_exp_val = qubit_expectation_value(&state, target);
|
||||
|
||||
assert_float_closeness(qubit_exp_val, exp_vals[0], 0.0001);
|
||||
}
|
||||
}
|
||||
Vendored
+2237
File diff suppressed because it is too large
Load Diff
Vendored
+142
@@ -0,0 +1,142 @@
|
||||
//! A High Performance Quantum State Simulator
|
||||
//!
|
||||
//! Spinoza is a fast and flexible quantum simulator written exclusively in Rust, with bindings available for Python
|
||||
//! users. Spinoza simulates the evolution of a quantum system’s state by applying quantum gates, with the core design
|
||||
//! principle being that a single-qubit gate applied to a target qubit preserves the probability of pairs of amplitudes
|
||||
//! corresponding to measurement outcomes that differ only in the target qubit. Spinoza is intended to enable the
|
||||
//! development of quantum computing solutions by offering researchers and quantum developers a simple, flexible, and
|
||||
//! fast tool for classical simulation.
|
||||
//!
|
||||
//! # How to use Spinoza
|
||||
//!
|
||||
//! There are three ways to use Spinoza:
|
||||
//!
|
||||
//! - **Functional** the simplest way to mutate a quantum state, directly.
|
||||
//! - [apply][gates::apply] for quantum transformations that do not need a control (qubit).
|
||||
//! - [c_apply][gates::c_apply] for quantum transformations that have a single control.
|
||||
//! - [mc_apply][gates::mc_apply] for quantum transformation that require multiple controls.
|
||||
//! - **Object-Oriented** the [QuantumCircuit][circuit::QuantumCircuit] represents a [quantum circuit](https://en.wikipedia.org/wiki/Quantum_circuit).
|
||||
//! Using the [QuantumCircuit][circuit::QuantumCircuit] one can create, mutate, and simulate quantum circuits with
|
||||
//! various quantum [gates][gates::Gate], operators, etc.
|
||||
//! - **Python Bindings** Spinoza has python bindings named `Spynoza`
|
||||
//! - All functionality for [QuantumCircuit][circuit::QuantumCircuit] and other functions have corresponding bindings
|
||||
//! created using PyO3.
|
||||
//!
|
||||
//! # Examples
|
||||
//! Let's encode the value, 2.4 using the three aforementioned approaches:
|
||||
//!
|
||||
//! ### Functional
|
||||
//! ```
|
||||
//! use spinoza::{
|
||||
//! core::{iqft, State},
|
||||
//! gates::{apply, Gate},
|
||||
//! math::{pow2f, PI},
|
||||
//! utils::{to_table},
|
||||
//! };
|
||||
//!
|
||||
//! pub fn main() {
|
||||
//! let n = 3;
|
||||
//! let v = 2.4;
|
||||
//! let mut state = State::new(n);
|
||||
//!
|
||||
//! for i in 0..n {
|
||||
//! apply(Gate::H, &mut state, i);
|
||||
//! }
|
||||
//! for i in 0..n {
|
||||
//! apply(Gate::P(2.0 * PI / (pow2f(i + 1)) * v), &mut state, i);
|
||||
//! }
|
||||
//! let targets: Vec<usize> = (0..n).rev().collect();
|
||||
//!
|
||||
//! iqft(&mut state, &targets);
|
||||
//! println!("{}", to_table(&state));
|
||||
//! }
|
||||
//! ```
|
||||
//! ### Object Oriented (OO)
|
||||
//! ```
|
||||
//! use spinoza::{
|
||||
//! core::{iqft, State},
|
||||
//! circuit::{QuantumCircuit, QuantumRegister},
|
||||
//! math::{pow2f, PI},
|
||||
//! utils::{to_table},
|
||||
//! };
|
||||
//!
|
||||
//! pub fn main() {
|
||||
//! let n = 3;
|
||||
//! let v = 2.4;
|
||||
//! let now = std::time::Instant::now();
|
||||
//! let mut q = QuantumRegister::new(n);
|
||||
//! let mut qc = QuantumCircuit::new(&mut [&mut q]);
|
||||
//!
|
||||
//! for i in 0..n {
|
||||
//! qc.h(i)
|
||||
//! }
|
||||
//! for i in 0..n {
|
||||
//! qc.p(2.0 * PI / pow2f(i + 1) * v, i)
|
||||
//! }
|
||||
//!
|
||||
//! let targets: Vec<usize> = (0..n).rev().collect();
|
||||
//! qc.iqft(&targets);
|
||||
//! qc.execute();
|
||||
//! println!("{}", to_table(qc.get_statevector()));
|
||||
//! }
|
||||
//!```
|
||||
//! ### Spynoza
|
||||
//! ```python
|
||||
//! from math import pi
|
||||
//! from spynoza import QuantumCircuit, QuantumRegister, show_table
|
||||
//!
|
||||
//!
|
||||
//! def value_encoding(n, v):
|
||||
//! q = QuantumRegister(n)
|
||||
//! qc = QuantumCircuit(q)
|
||||
//!
|
||||
//! for i in range(n):
|
||||
//! qc.h(i)
|
||||
//!
|
||||
//! for i in range(n):
|
||||
//! qc.p(2 * pi / (2 ** (i + 1)) * v, i)
|
||||
//!
|
||||
//! qc.iqft(range(n)[::-1])
|
||||
//!
|
||||
//! qc.execute()
|
||||
//! return qc.get_statevector()
|
||||
//!
|
||||
//!
|
||||
//! if __name__ == "__main__":
|
||||
//! state = value_encoding(4, 2.4)
|
||||
//! print(show_table(state))
|
||||
//!```
|
||||
//!
|
||||
//! More complex examples can be found in the [Spinoza examples](https://github.com/QuState/spinoza/tree/main/spinoza/examples)
|
||||
//! and the [Spynoza exmaples](https://github.com/QuState/spinoza/tree/main/spynoza/examples).
|
||||
//!
|
||||
//! # References
|
||||
//! ```latex
|
||||
//! @misc{yusufov2023designing,
|
||||
//! title={Designing a Fast and Flexible Quantum State Simulator},
|
||||
//! author={Saveliy Yusufov and Charlee Stefanski and Constantin Gonciulea},
|
||||
//! year={2023},
|
||||
//! eprint={2303.01493},
|
||||
//! archivePrefix={arXiv},
|
||||
//! primaryClass={quant-ph}
|
||||
//! }
|
||||
//!```
|
||||
|
||||
#![warn(clippy::complexity)]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::style)]
|
||||
#![warn(clippy::correctness)]
|
||||
#![warn(clippy::suspicious)]
|
||||
#![warn(clippy::perf)]
|
||||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
pub mod circuit;
|
||||
pub mod config;
|
||||
pub mod consts;
|
||||
pub mod core;
|
||||
pub mod gates;
|
||||
pub mod math;
|
||||
pub mod measurement;
|
||||
pub mod openqasm;
|
||||
pub mod unitaries;
|
||||
pub mod utils;
|
||||
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
//! An assortment of mathematical structures, functions, and constants for quantum state
|
||||
//! simulation.
|
||||
|
||||
/// An alias for <https://doc.rust-lang.org/std/f64/consts/constant.FRAC_1_SQRT_2.html>
|
||||
pub const SQRT_ONE_HALF: Float = std::f64::consts::FRAC_1_SQRT_2 as Float;
|
||||
|
||||
/// An alias for <https://doc.rust-lang.org/std/f64/consts/constant.PI.html>
|
||||
pub const PI: Float = std::f64::consts::PI as Float;
|
||||
|
||||
/// The type of floating point number to use for amplitudes
|
||||
#[cfg(feature = "double")]
|
||||
pub type Float = f64;
|
||||
|
||||
#[cfg(feature = "single")]
|
||||
pub type Float = f32;
|
||||
|
||||
/// An amplitude that makes up a Quantum State
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Amplitude {
|
||||
/// imaginary component
|
||||
pub im: Float,
|
||||
/// real component
|
||||
pub re: Float,
|
||||
}
|
||||
|
||||
/// The absolute value of a complex number
|
||||
/// See <https://en.wikipedia.org/wiki/Absolute_value#Complex_numbers>
|
||||
#[inline]
|
||||
pub fn modulus(z_re: Float, z_im: Float) -> Float {
|
||||
(z_re.mul_add(z_re, z_im * z_im)).sqrt()
|
||||
}
|
||||
|
||||
/// Compute 2^n and convert it to a float
|
||||
pub fn pow2f(n: usize) -> Float {
|
||||
const BASE2: Float = 2.0;
|
||||
BASE2.powi(n.try_into().unwrap())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::utils::assert_float_closeness;
|
||||
|
||||
fn linspace(start: Float, end: Float, num: Option<usize>) -> Vec<Float> {
|
||||
let n = if let Some(num) = num { num } else { 50 };
|
||||
let step = (end - start) / n as Float;
|
||||
let mut x = 0.0;
|
||||
let mut res: Vec<Float> = Vec::with_capacity(n);
|
||||
|
||||
while x < end {
|
||||
res.push(x);
|
||||
x += step;
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modulus_unit_circle() {
|
||||
let angles = linspace(0.0, 2.0 * PI, Some(100));
|
||||
for angle in angles.into_iter() {
|
||||
let amplitude = Amplitude {
|
||||
re: angle.cos(),
|
||||
im: angle.sin(),
|
||||
};
|
||||
assert_float_closeness(modulus(amplitude.re, amplitude.im), 1.0, 0.001);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow() {
|
||||
for i in 0..52 {
|
||||
let mut res = 1.0;
|
||||
for _ in 0..i {
|
||||
res *= 2.0;
|
||||
}
|
||||
assert_float_closeness(pow2f(i), res, 0.001);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+247
@@ -0,0 +1,247 @@
|
||||
//! Functionality for measurement
|
||||
use rand_distr::{Binomial, Distribution};
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::{
|
||||
core::State,
|
||||
gates::{apply, Gate},
|
||||
math::Float,
|
||||
};
|
||||
|
||||
/// Single qubit measurement
|
||||
pub fn measure_qubit(state: &mut State, target: usize, reset: bool, v: Option<u8>) -> u8 {
|
||||
let chunk_size = 1 << (target + 1);
|
||||
let dist = 1 << target;
|
||||
|
||||
let prob0 = state
|
||||
.reals
|
||||
.par_chunks_exact(chunk_size)
|
||||
.zip_eq(state.imags.par_chunks_exact(chunk_size))
|
||||
.map(|(reals_chunk, imags_chunk)| {
|
||||
reals_chunk
|
||||
.par_iter()
|
||||
.take(dist)
|
||||
.zip_eq(imags_chunk.par_iter().take(dist))
|
||||
.with_min_len(1 << 16)
|
||||
.map(|(re_s0, im_s0)| re_s0.powi(2) + im_s0.powi(2))
|
||||
.sum::<Float>()
|
||||
})
|
||||
.sum::<Float>();
|
||||
|
||||
let val = if let Some(_v) = v {
|
||||
assert!(_v == 0 || _v == 1);
|
||||
_v
|
||||
} else {
|
||||
let bin = Binomial::new(1, 1.0 - prob0).unwrap();
|
||||
bin.sample(&mut rand::thread_rng()) as u8
|
||||
};
|
||||
|
||||
if val == 0 {
|
||||
let prob0_sqrt_recip = prob0.sqrt().recip();
|
||||
state
|
||||
.reals
|
||||
.par_chunks_exact_mut(chunk_size)
|
||||
.zip_eq(state.imags.par_chunks_exact_mut(chunk_size))
|
||||
.for_each(|(reals_chunk, imags_chunk)| {
|
||||
let (reals_s0, reals_s1) = reals_chunk.split_at_mut(dist);
|
||||
let (imags_s0, imags_s1) = imags_chunk.split_at_mut(dist);
|
||||
|
||||
reals_s0
|
||||
.par_iter_mut()
|
||||
.zip_eq(reals_s1.par_iter_mut())
|
||||
.zip_eq(imags_s0.par_iter_mut())
|
||||
.zip_eq(imags_s1.par_iter_mut())
|
||||
.with_min_len(1 << 16)
|
||||
.for_each(|(((re_s0, re_s1), im_s0), im_s1)| {
|
||||
*re_s0 *= prob0_sqrt_recip;
|
||||
*im_s0 *= prob0_sqrt_recip;
|
||||
*re_s1 = 0.0;
|
||||
*im_s1 = 0.0;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
let prob1 = 1.0 - prob0;
|
||||
let prob1_sqrt_recip = prob1.sqrt().recip();
|
||||
|
||||
state
|
||||
.reals
|
||||
.par_chunks_exact_mut(chunk_size)
|
||||
.zip_eq(state.imags.par_chunks_exact_mut(chunk_size))
|
||||
.for_each(|(reals_chunk, imags_chunk)| {
|
||||
let (reals_s0, reals_s1) = reals_chunk.split_at_mut(dist);
|
||||
let (imags_s0, imags_s1) = imags_chunk.split_at_mut(dist);
|
||||
|
||||
reals_s0
|
||||
.par_iter_mut()
|
||||
.zip_eq(reals_s1.par_iter_mut())
|
||||
.zip_eq(imags_s0.par_iter_mut())
|
||||
.zip_eq(imags_s1.par_iter_mut())
|
||||
.with_min_len(1 << 16)
|
||||
.for_each(|(((re_s0, re_s1), im_s0), im_s1)| {
|
||||
*re_s1 *= prob1_sqrt_recip;
|
||||
*im_s1 *= prob1_sqrt_recip;
|
||||
*re_s0 = 0.0;
|
||||
*im_s0 = 0.0;
|
||||
});
|
||||
});
|
||||
if reset {
|
||||
apply(Gate::X, state, target);
|
||||
}
|
||||
}
|
||||
val
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::utils::{assert_float_closeness, gen_random_state};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_measure_qubit() {
|
||||
let mut state = gen_random_state(3);
|
||||
println!("{state}");
|
||||
let sum = state
|
||||
.reals
|
||||
.iter()
|
||||
.zip(state.imags.iter())
|
||||
.map(|(re, im)| re.powi(2) + im.powi(2))
|
||||
.sum();
|
||||
assert_float_closeness(sum, 1.0, 0.001);
|
||||
|
||||
measure_qubit(&mut state, 0, true, Some(0));
|
||||
println!("{state}");
|
||||
let sum = state
|
||||
.reals
|
||||
.iter()
|
||||
.zip(state.imags.iter())
|
||||
.map(|(re, im)| re.powi(2) + im.powi(2))
|
||||
.sum();
|
||||
assert_float_closeness(sum, 1.0, 0.001);
|
||||
|
||||
measure_qubit(&mut state, 1, true, Some(0));
|
||||
println!("{state}");
|
||||
let sum = state
|
||||
.reals
|
||||
.iter()
|
||||
.zip(state.imags.iter())
|
||||
.map(|(re, im)| re.powi(2) + im.powi(2))
|
||||
.sum();
|
||||
assert_float_closeness(sum, 1.0, 0.001);
|
||||
|
||||
measure_qubit(&mut state, 2, true, Some(1));
|
||||
println!("{state}");
|
||||
let sum = state
|
||||
.reals
|
||||
.iter()
|
||||
.zip(state.imags.iter())
|
||||
.map(|(re, im)| re.powi(2) + im.powi(2))
|
||||
.sum();
|
||||
|
||||
assert_float_closeness(sum, 1.0, 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_measure_qubit_known_state() {
|
||||
let n = 3;
|
||||
let mut reals = Vec::with_capacity(1 << n);
|
||||
let mut imags = Vec::with_capacity(1 << n);
|
||||
|
||||
let vals = [
|
||||
0.034172256444052966,
|
||||
0.29007027387615136,
|
||||
-0.1300556493088507,
|
||||
0.47222164829858637,
|
||||
-0.032338373524095645,
|
||||
0.26511510737291843,
|
||||
0.1259630181898572,
|
||||
-0.09645897805840803,
|
||||
-0.31931099330088214,
|
||||
-0.24644972468157703,
|
||||
-0.15963222942036193,
|
||||
-0.14329373536970438,
|
||||
-0.1564141838467382,
|
||||
-0.4751067410290973,
|
||||
0.1034273381193853,
|
||||
-0.32966556091031934,
|
||||
];
|
||||
|
||||
let mut i = 0;
|
||||
while i < vals.len() - 1 {
|
||||
reals.push(vals[i]);
|
||||
imags.push(vals[i + 1]);
|
||||
i += 2;
|
||||
}
|
||||
|
||||
let mut state = State { reals, imags, n };
|
||||
|
||||
let epsilon = 0.001;
|
||||
|
||||
measure_qubit(&mut state, 0, true, Some(0));
|
||||
|
||||
assert_float_closeness(state.reals[0], 0.04528096797370981, epsilon);
|
||||
assert_float_closeness(state.imags[0], 0.38436627101331156, epsilon);
|
||||
assert_float_closeness(state.reals[1], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[1], 0.0, epsilon);
|
||||
|
||||
assert_float_closeness(state.reals[2], -0.042850926694402595, epsilon);
|
||||
assert_float_closeness(state.imags[2], 0.3512986830692283, epsilon);
|
||||
assert_float_closeness(state.reals[3], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[3], 0.0, epsilon);
|
||||
|
||||
assert_float_closeness(state.reals[4], -0.42311255872092046, epsilon);
|
||||
assert_float_closeness(state.imags[4], -0.32656556082875193, epsilon);
|
||||
assert_float_closeness(state.reals[5], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[5], 0.0, epsilon);
|
||||
|
||||
assert_float_closeness(state.reals[6], -0.2072612811212442, epsilon);
|
||||
assert_float_closeness(state.imags[6], -0.6295543626114914, epsilon);
|
||||
assert_float_closeness(state.reals[7], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[7], 0.0, epsilon);
|
||||
println!("{state}");
|
||||
|
||||
measure_qubit(&mut state, 1, true, Some(0));
|
||||
assert_float_closeness(state.reals[0], 0.06861878352538178, epsilon);
|
||||
assert_float_closeness(state.imags[0], 0.5824686866330654, epsilon);
|
||||
assert_float_closeness(state.reals[1], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[1], 0.0, epsilon);
|
||||
|
||||
assert_float_closeness(state.reals[2], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[2], 0.0, epsilon);
|
||||
assert_float_closeness(state.reals[3], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[3], 0.0, epsilon);
|
||||
|
||||
assert_float_closeness(state.reals[4], -0.6411848150109799, epsilon);
|
||||
assert_float_closeness(state.imags[4], -0.49487748447346463, epsilon);
|
||||
assert_float_closeness(state.reals[5], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[5], 0.0, epsilon);
|
||||
|
||||
assert_float_closeness(state.reals[6], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[6], 0.0, epsilon);
|
||||
assert_float_closeness(state.reals[7], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[7], 0.0, epsilon);
|
||||
println!("{state}");
|
||||
|
||||
measure_qubit(&mut state, 2, true, Some(1));
|
||||
assert_float_closeness(state.reals[0], -0.7916334352111761, epsilon);
|
||||
assert_float_closeness(state.imags[0], -0.6109963209838112, epsilon);
|
||||
assert_float_closeness(state.reals[1], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[1], 0.0, epsilon);
|
||||
|
||||
assert_float_closeness(state.reals[2], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[2], 0.0, epsilon);
|
||||
assert_float_closeness(state.reals[3], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[3], 0.0, epsilon);
|
||||
|
||||
assert_float_closeness(state.reals[4], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[4], 0.0, epsilon);
|
||||
assert_float_closeness(state.reals[5], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[5], 0.0, epsilon);
|
||||
|
||||
assert_float_closeness(state.reals[6], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[6], 0.0, epsilon);
|
||||
assert_float_closeness(state.reals[7], 0.0, epsilon);
|
||||
assert_float_closeness(state.imags[7], 0.0, epsilon);
|
||||
println!("{state}");
|
||||
}
|
||||
}
|
||||
Vendored
+305
@@ -0,0 +1,305 @@
|
||||
//! Functionality for creating a QuantumCircuit from an OpenQASM 2.0 program
|
||||
use std::{collections::HashMap, env, fs::File, io::prelude::*, path::Path};
|
||||
|
||||
use evalexpr::*;
|
||||
use qasm::{lex, parse, process, Argument, AstNode};
|
||||
|
||||
use crate::{
|
||||
circuit::{QuantumCircuit, QuantumRegister},
|
||||
math::{Float, PI},
|
||||
};
|
||||
|
||||
/// Parse an OpenQASM 2.0 program file, and convert it into a `QuantumCircuit`.
|
||||
pub fn load(filename: &Path) -> QuantumCircuit {
|
||||
let cwd = env::current_dir().unwrap();
|
||||
let mut source = String::new();
|
||||
let mut f = File::open(filename).expect("cannot find source file");
|
||||
f.read_to_string(&mut source).expect("couldn't read file");
|
||||
|
||||
let processed_source = process(&source, &cwd);
|
||||
let mut tokens = lex(&processed_source);
|
||||
let ast = parse(&mut tokens).unwrap();
|
||||
build_circuit(&ast)
|
||||
}
|
||||
|
||||
/// Parse an OpenQASM 2.0 program in `str` format, and convert it into a `QuantumCircuit`.
|
||||
pub fn loads(qasm_as_str: &str) -> QuantumCircuit {
|
||||
let cwd = env::current_dir().unwrap();
|
||||
let processed_source = process(qasm_as_str, &cwd);
|
||||
let mut tokens = lex(&processed_source);
|
||||
let ast = parse(&mut tokens).unwrap();
|
||||
build_circuit(&ast)
|
||||
}
|
||||
|
||||
/// Build a circuit from an AST
|
||||
fn build_circuit(ast: &[AstNode]) -> QuantumCircuit {
|
||||
let context = context_map! {
|
||||
"pi" => PI,
|
||||
"-pi" => -PI,
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
let mut registers = HashMap::new();
|
||||
|
||||
// Find all registers
|
||||
for node in ast.iter() {
|
||||
if let AstNode::QReg(identifier, num_qubits) = node {
|
||||
let n = (*num_qubits).try_into().unwrap();
|
||||
registers.insert(identifier, QuantumRegister::new(n));
|
||||
}
|
||||
}
|
||||
|
||||
let mut qrs: Vec<&mut QuantumRegister> = registers.iter_mut().map(|(_, r)| r).collect();
|
||||
let mut qc = QuantumCircuit::new(&mut qrs);
|
||||
|
||||
for node in ast.iter() {
|
||||
match node {
|
||||
AstNode::ApplyGate(gate, args0, args1) => match gate.as_str() {
|
||||
"h" => {
|
||||
for arg in args0.iter() {
|
||||
if let Argument::Qubit(identifier, qubit_num) = arg {
|
||||
let register = registers.get(identifier).unwrap();
|
||||
qc.h(register[(*qubit_num).try_into().unwrap()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
"x" => {
|
||||
for arg in args0.iter() {
|
||||
if let Argument::Qubit(identifier, qubit_num) = arg {
|
||||
let register = registers.get(identifier).unwrap();
|
||||
qc.x(register[(*qubit_num).try_into().unwrap()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
"y" => {
|
||||
for arg in args0.iter() {
|
||||
if let Argument::Qubit(identifier, qubit_num) = arg {
|
||||
let register = registers.get(identifier).unwrap();
|
||||
qc.y(register[(*qubit_num).try_into().unwrap()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
"z" => {
|
||||
for arg in args0.iter() {
|
||||
if let Argument::Qubit(identifier, qubit_num) = arg {
|
||||
let register = registers.get(identifier).unwrap();
|
||||
qc.z(register[(*qubit_num).try_into().unwrap()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
"rx" => {
|
||||
for arg in args0.iter() {
|
||||
if let Argument::Qubit(identifier, qubit_num) = arg {
|
||||
let angle = args1[0].replace(' ', "").parse::<Float>().unwrap();
|
||||
let register = registers.get(identifier).unwrap();
|
||||
qc.rx(angle, register[(*qubit_num).try_into().unwrap()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
"ry" => {
|
||||
for arg in args0.iter() {
|
||||
if let Argument::Qubit(identifier, qubit_num) = arg {
|
||||
let angle = args1[0].replace(' ', "").parse::<Float>().unwrap();
|
||||
let register = registers.get(identifier).unwrap();
|
||||
qc.ry(angle, register[(*qubit_num).try_into().unwrap()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
"rz" => {
|
||||
for arg in args0.iter() {
|
||||
if let Argument::Qubit(identifier, qubit_num) = arg {
|
||||
let angle = args1[0].replace(' ', "").parse::<Float>().unwrap();
|
||||
let register = registers.get(identifier).unwrap();
|
||||
qc.rz(angle, register[(*qubit_num).try_into().unwrap()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
"u" => {
|
||||
for arg in args0.iter() {
|
||||
if let Argument::Qubit(identifier, qubit_num) = arg {
|
||||
let theta = args1[0].replace(' ', "").parse::<Float>().unwrap();
|
||||
let phi = args1[1].replace(' ', "").parse::<Float>().unwrap();
|
||||
let lambda = args1[2].replace(' ', "").parse::<Float>().unwrap();
|
||||
let register = registers.get(identifier).unwrap();
|
||||
qc.u(
|
||||
theta,
|
||||
phi,
|
||||
lambda,
|
||||
register[(*qubit_num).try_into().unwrap()],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
"cp" => {
|
||||
let control = if let Argument::Qubit(identifier, qubit_num) = &args0[0] {
|
||||
let register = registers.get(identifier).unwrap();
|
||||
register[(*qubit_num).try_into().unwrap()]
|
||||
} else {
|
||||
panic!("there's no argument 0 for the CP gate")
|
||||
};
|
||||
let target = if let Argument::Qubit(identifier, qubit_num) = &args0[1] {
|
||||
let register = registers.get(identifier).unwrap();
|
||||
register[(*qubit_num).try_into().unwrap()]
|
||||
} else {
|
||||
panic!("there's no argument 1 for the CP gate")
|
||||
};
|
||||
let theta = eval_with_context(&args1[0], &context).unwrap();
|
||||
qc.cp(theta.as_float().unwrap(), control, target);
|
||||
}
|
||||
"cx" => {
|
||||
let control = if let Argument::Qubit(identifier, qubit_num) = &args0[0] {
|
||||
let register = registers.get(&identifier).unwrap();
|
||||
register[(*qubit_num).try_into().unwrap()]
|
||||
} else {
|
||||
panic!("there's no argument 0 for the CX gate")
|
||||
};
|
||||
let target = if let Argument::Qubit(identifier, qubit_num) = &args0[1] {
|
||||
let register = registers.get(&identifier).unwrap();
|
||||
register[(*qubit_num).try_into().unwrap()]
|
||||
} else {
|
||||
panic!("there's no argument 1 for the CX gate")
|
||||
};
|
||||
qc.cx(control, target);
|
||||
}
|
||||
_ => todo!(),
|
||||
},
|
||||
AstNode::QReg(_, _) => (),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
qc
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{circuit::QuantumRegister, utils::assert_float_closeness};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn quantum_lstm_load() {
|
||||
let filename: &Path = Path::new("../qasm/quantum_lstm.qasm");
|
||||
let mut qc1 = load(filename);
|
||||
qc1.execute();
|
||||
|
||||
qc1.state
|
||||
.reals
|
||||
.iter()
|
||||
.zip(qc1.state.imags.iter())
|
||||
.for_each(|(qc1_re, qc1_im)| {
|
||||
println!("{} + i{}", *qc1_re, *qc1_im);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iqft_load() {
|
||||
let filename: &Path = Path::new("../qasm/iqft.qasm");
|
||||
let mut qc2 = load(filename);
|
||||
qc2.execute();
|
||||
|
||||
let n: usize = qc2.state.n.into();
|
||||
let targets: Vec<_> = (0..n).rev().collect();
|
||||
let mut qr = QuantumRegister::new(n);
|
||||
let mut qc1 = QuantumCircuit::new(&mut [&mut qr]);
|
||||
qc1.iqft(&targets);
|
||||
qc1.execute();
|
||||
|
||||
qc1.state
|
||||
.reals
|
||||
.iter()
|
||||
.zip(qc1.state.imags.iter())
|
||||
.zip(qc2.state.reals.iter())
|
||||
.zip(qc2.state.imags.iter())
|
||||
.for_each(|(((qc1_re, qc1_im), qc2_re), qc2_im)| {
|
||||
assert_float_closeness(*qc1_re, *qc2_re, 0.001);
|
||||
assert_float_closeness(*qc1_im, *qc2_im, 0.001);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iqft_loads() {
|
||||
let qasm_as_str = include_str!("../../qasm/iqft.qasm");
|
||||
let mut qc2 = loads(qasm_as_str);
|
||||
qc2.execute();
|
||||
|
||||
let n: usize = qc2.state.n.into();
|
||||
let targets: Vec<_> = (0..n).rev().collect();
|
||||
let mut qr = QuantumRegister::new(n);
|
||||
let mut qc1 = QuantumCircuit::new(&mut [&mut qr]);
|
||||
qc1.iqft(&targets);
|
||||
qc1.execute();
|
||||
|
||||
qc1.state
|
||||
.reals
|
||||
.iter()
|
||||
.zip(qc1.state.imags.iter())
|
||||
.zip(qc2.state.reals.iter())
|
||||
.zip(qc2.state.imags.iter())
|
||||
.for_each(|(((qc1_re, qc1_im), qc2_re), qc2_im)| {
|
||||
assert_float_closeness(*qc1_re, *qc2_re, 0.001);
|
||||
assert_float_closeness(*qc1_im, *qc2_im, 0.001);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_controlled_gates_load() {
|
||||
let filename: &Path = Path::new("../qasm/test0.qasm");
|
||||
let mut qc2 = load(filename);
|
||||
qc2.execute();
|
||||
|
||||
let n: usize = qc2.state.n.into();
|
||||
let mut qr = QuantumRegister::new(n);
|
||||
let mut qc1 = QuantumCircuit::new(&mut [&mut qr]);
|
||||
qc1.h(0);
|
||||
qc1.x(1);
|
||||
qc1.y(2);
|
||||
qc1.z(3);
|
||||
qc1.rx(1.0, 4);
|
||||
qc1.ry(2.0, 5);
|
||||
qc1.rz(3.0, 6);
|
||||
qc1.u(1.0, 2.0, 3.0, 7);
|
||||
qc1.execute();
|
||||
|
||||
qc1.state
|
||||
.reals
|
||||
.iter()
|
||||
.zip(qc1.state.imags.iter())
|
||||
.zip(qc2.state.reals.iter())
|
||||
.zip(qc2.state.imags.iter())
|
||||
.for_each(|(((qc1_re, qc1_im), qc2_re), qc2_im)| {
|
||||
assert_float_closeness(*qc1_re, *qc2_re, 0.001);
|
||||
assert_float_closeness(*qc1_im, *qc2_im, 0.001);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_controlled_gates_loads() {
|
||||
let qasm_as_str = include_str!("../../qasm/test0.qasm");
|
||||
let mut qc2 = loads(qasm_as_str);
|
||||
qc2.execute();
|
||||
|
||||
let n: usize = qc2.state.n.into();
|
||||
let mut qr = QuantumRegister::new(n);
|
||||
let mut qc1 = QuantumCircuit::new(&mut [&mut qr]);
|
||||
qc1.h(0);
|
||||
qc1.x(1);
|
||||
qc1.y(2);
|
||||
qc1.z(3);
|
||||
qc1.rx(1.0, 4);
|
||||
qc1.ry(2.0, 5);
|
||||
qc1.rz(3.0, 6);
|
||||
qc1.u(1.0, 2.0, 3.0, 7);
|
||||
qc1.execute();
|
||||
|
||||
qc1.state
|
||||
.reals
|
||||
.iter()
|
||||
.zip(qc1.state.imags.iter())
|
||||
.zip(qc2.state.reals.iter())
|
||||
.zip(qc2.state.imags.iter())
|
||||
.for_each(|(((qc1_re, qc1_im), qc2_re), qc2_im)| {
|
||||
assert_float_closeness(*qc1_re, *qc2_re, 0.001);
|
||||
assert_float_closeness(*qc1_im, *qc2_im, 0.001);
|
||||
});
|
||||
}
|
||||
}
|
||||
Vendored
+374
@@ -0,0 +1,374 @@
|
||||
//! Functionality for applying large 2^n * 2^n matrices to the state
|
||||
//! Ideally, this should be a last resort
|
||||
use crate::{core::State, gates::Gate, math::Float};
|
||||
use rayon::prelude::*;
|
||||
use std::fmt;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
/// A representation of a Unitary Matrix
|
||||
#[derive(Clone)]
|
||||
pub struct Unitary {
|
||||
pub(crate) reals: Vec<Float>,
|
||||
pub(crate) imags: Vec<Float>,
|
||||
/// The number of rows in the matrix
|
||||
pub height: usize,
|
||||
/// The number of columns in the matrix
|
||||
pub width: usize,
|
||||
}
|
||||
|
||||
impl fmt::Display for Unitary {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.reals
|
||||
.chunks_exact(self.width)
|
||||
.zip(self.imags.chunks_exact(self.width))
|
||||
.for_each(|(re, im)| {
|
||||
re.iter().zip(im.iter()).for_each(|(z_re, z_im)| {
|
||||
write!(f, "{z_re}+i{z_im} ").unwrap();
|
||||
});
|
||||
writeln!(f).unwrap();
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Unitary {
|
||||
// TODO(saveliy): look into using strategy 2 here
|
||||
/// Construct a Unitary from a single qubit gate
|
||||
pub fn from_single_qubit_gate(state: &State, gate: Gate, target: usize) -> Self {
|
||||
let g = gate.to_matrix();
|
||||
let num_pairs = state.len() >> 1;
|
||||
let distance = 1 << target;
|
||||
|
||||
let width = state.len();
|
||||
let height = state.len();
|
||||
let mut reals = vec![0.0; height * width];
|
||||
let mut imags = vec![0.0; height * width];
|
||||
|
||||
for i in 0..num_pairs {
|
||||
let s0 = i + ((i >> target) << target);
|
||||
let s1 = s0 + distance;
|
||||
|
||||
reals[width * s0 + s0] = g[0].re;
|
||||
reals[width * s0 + s1] = g[1].re;
|
||||
reals[width * s1 + s0] = g[2].re;
|
||||
reals[width * s1 + s1] = g[3].re;
|
||||
|
||||
imags[width * s0 + s0] = g[0].im;
|
||||
imags[width * s0 + s1] = g[1].im;
|
||||
imags[width * s1 + s0] = g[2].im;
|
||||
imags[width * s1 + s1] = g[3].im;
|
||||
}
|
||||
|
||||
Self {
|
||||
reals,
|
||||
imags,
|
||||
height,
|
||||
width,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the conjugate transpose of this `Unitary`, in-place
|
||||
pub fn conj_t(&mut self) {
|
||||
self.imags.par_iter_mut().for_each(|z_im| {
|
||||
*z_im = -(*z_im);
|
||||
});
|
||||
|
||||
for i in 0..self.height {
|
||||
for j in i + 1..self.width {
|
||||
self.reals.swap(i * self.width + j, j * self.width + i);
|
||||
self.imags.swap(i * self.width + j, j * self.width + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Multiply this unitary matrix by another unitary matrix
|
||||
pub fn multiply(&self, other: &Unitary) -> Self {
|
||||
assert_eq!(self.width, other.height);
|
||||
|
||||
let mut result_reals = vec![0.0; self.height * other.width];
|
||||
let mut result_imags = vec![0.0; self.height * other.width];
|
||||
|
||||
// self.reals
|
||||
// .chunks_exact(self.width)
|
||||
// .zip(self.imags.chunks_exact(self.width))
|
||||
// .enumerate()
|
||||
// .map(|(i, (z_re, z_im))| {
|
||||
// for k in 0..other.height {
|
||||
//
|
||||
// }
|
||||
// });
|
||||
|
||||
for i in 0..self.height {
|
||||
for j in 0..other.width {
|
||||
let mut sum_real = 0.0;
|
||||
let mut sum_imag = 0.0;
|
||||
|
||||
for k in 0..self.width {
|
||||
let a_real = self.reals[i * self.width + k];
|
||||
let a_imag = self.imags[i * self.width + k];
|
||||
let b_real = other.reals[k * other.width + j];
|
||||
let b_imag = other.imags[k * other.width + j];
|
||||
|
||||
sum_real += a_real * b_real - a_imag * b_imag;
|
||||
sum_imag += a_real * b_imag + a_imag * b_real;
|
||||
}
|
||||
|
||||
result_reals[i * other.width + j] = sum_real;
|
||||
result_imags[i * other.width + j] = sum_imag;
|
||||
}
|
||||
}
|
||||
|
||||
Unitary {
|
||||
reals: result_reals,
|
||||
imags: result_imags,
|
||||
height: self.height,
|
||||
width: other.width,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a unitary matrix to the Quantum State Vector
|
||||
pub fn apply_unitary(state: &State, unitary: &Unitary) -> State {
|
||||
assert!(state.len() == unitary.width && state.len() == unitary.height);
|
||||
let chunk_size = unitary.width;
|
||||
|
||||
let mut reals = Vec::with_capacity(state.len());
|
||||
let mut imags = Vec::with_capacity(state.len());
|
||||
|
||||
unitary
|
||||
.reals
|
||||
.chunks_exact(chunk_size)
|
||||
.zip(unitary.imags.chunks_exact(chunk_size))
|
||||
.for_each(|(row_reals, row_imags)| {
|
||||
let mut dot_prod_re = 0.0;
|
||||
let mut dot_prod_im = 0.0;
|
||||
row_reals
|
||||
.iter()
|
||||
.zip(row_imags.iter())
|
||||
.zip(state.reals.iter())
|
||||
.zip(state.imags.iter())
|
||||
.for_each(|(((a, b), s_re), s_im)| {
|
||||
dot_prod_re += *a * s_re - *b * s_im;
|
||||
dot_prod_im += *a * s_im + *b * s_re;
|
||||
});
|
||||
reals.push(dot_prod_re);
|
||||
imags.push(dot_prod_im);
|
||||
});
|
||||
State {
|
||||
reals,
|
||||
imags,
|
||||
n: state.n,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::utils::{assert_float_closeness, gen_random_state};
|
||||
use crate::{
|
||||
gates::{apply, Gate},
|
||||
math::SQRT_ONE_HALF,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_hxi_from_single_qubit_gate() {
|
||||
let mut reals = vec![
|
||||
1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, -1.0, 0.0, 0.0, 1.0, 0.0, -1.0,
|
||||
];
|
||||
reals.iter_mut().for_each(|a| *a *= SQRT_ONE_HALF);
|
||||
|
||||
let imags = vec![0.0; reals.len()];
|
||||
|
||||
let u1 = Unitary {
|
||||
reals,
|
||||
imags,
|
||||
height: 4,
|
||||
width: 4,
|
||||
};
|
||||
|
||||
let state = State::new(2);
|
||||
let u2 = Unitary::from_single_qubit_gate(&state, Gate::H, 1);
|
||||
assert_eq!(u1.height, u2.height);
|
||||
assert_eq!(u1.width, u2.width);
|
||||
assert_eq!(u1.reals, u2.reals);
|
||||
assert_eq!(u1.imags, u2.imags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hxi() {
|
||||
let n = 2;
|
||||
let state = State::new(n);
|
||||
let u = Unitary::from_single_qubit_gate(&state, Gate::H, 0);
|
||||
|
||||
let s = apply_unitary(&state, &u);
|
||||
|
||||
let mut s1 = State::new(n);
|
||||
apply(Gate::H, &mut s1, 0);
|
||||
|
||||
assert_eq!(s.n, s1.n);
|
||||
assert_eq!(s.reals, s1.reals);
|
||||
assert_eq!(s.imags, s1.imags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_x() {
|
||||
let n = 2;
|
||||
let state = State::new(n);
|
||||
let u = Unitary::from_single_qubit_gate(&state, Gate::X, 0);
|
||||
|
||||
let s = apply_unitary(&state, &u);
|
||||
|
||||
let mut s1 = State::new(n);
|
||||
apply(Gate::X, &mut s1, 0);
|
||||
|
||||
assert_eq!(s.n, s1.n);
|
||||
assert_eq!(s.reals, s1.reals);
|
||||
assert_eq!(s.imags, s1.imags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_y() {
|
||||
let n = 2;
|
||||
let state = State::new(n);
|
||||
let u = Unitary::from_single_qubit_gate(&state, Gate::Y, 0);
|
||||
|
||||
let s = apply_unitary(&state, &u);
|
||||
|
||||
let mut s1 = State::new(n);
|
||||
apply(Gate::Y, &mut s1, 0);
|
||||
|
||||
assert_eq!(s.n, s1.n);
|
||||
assert_eq!(s.reals, s1.reals);
|
||||
assert_eq!(s.imags, s1.imags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_z() {
|
||||
let n = 2;
|
||||
let state = State::new(n);
|
||||
let u = Unitary::from_single_qubit_gate(&state, Gate::Z, 0);
|
||||
|
||||
let s = apply_unitary(&state, &u);
|
||||
|
||||
let mut s1 = State::new(n);
|
||||
apply(Gate::Z, &mut s1, 0);
|
||||
|
||||
assert_eq!(s.n, s1.n);
|
||||
assert_eq!(s.reals, s1.reals);
|
||||
assert_eq!(s.imags, s1.imags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_p() {
|
||||
let n = 2;
|
||||
let state = State::new(n);
|
||||
let u = Unitary::from_single_qubit_gate(&state, Gate::P(3.0), 0);
|
||||
|
||||
let s = apply_unitary(&state, &u);
|
||||
|
||||
let mut s1 = State::new(n);
|
||||
apply(Gate::P(3.0), &mut s1, 0);
|
||||
|
||||
assert_eq!(s.n, s1.n);
|
||||
assert_eq!(s.reals, s1.reals);
|
||||
assert_eq!(s.imags, s1.imags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rx() {
|
||||
let n = 2;
|
||||
let state = State::new(n);
|
||||
let u = Unitary::from_single_qubit_gate(&state, Gate::RX(3.0), 0);
|
||||
|
||||
let s = apply_unitary(&state, &u);
|
||||
|
||||
let mut s1 = State::new(n);
|
||||
apply(Gate::RX(3.0), &mut s1, 0);
|
||||
|
||||
assert_eq!(s.n, s1.n);
|
||||
assert_eq!(s.reals, s1.reals);
|
||||
assert_eq!(s.imags, s1.imags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ry() {
|
||||
let n = 2;
|
||||
let state = State::new(n);
|
||||
let u = Unitary::from_single_qubit_gate(&state, Gate::RY(3.0), 0);
|
||||
|
||||
let s = apply_unitary(&state, &u);
|
||||
|
||||
let mut s1 = State::new(n);
|
||||
apply(Gate::RY(3.0), &mut s1, 0);
|
||||
|
||||
assert_eq!(s.n, s1.n);
|
||||
assert_eq!(s.reals, s1.reals);
|
||||
assert_eq!(s.imags, s1.imags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rz() {
|
||||
let n = 2;
|
||||
let state = State::new(n);
|
||||
let u = Unitary::from_single_qubit_gate(&state, Gate::RZ(3.0), 0);
|
||||
|
||||
let s = apply_unitary(&state, &u);
|
||||
|
||||
let mut s1 = State::new(n);
|
||||
apply(Gate::RZ(3.0), &mut s1, 0);
|
||||
|
||||
assert_eq!(s.n, s1.n);
|
||||
assert_eq!(s.reals, s1.reals);
|
||||
assert_eq!(s.imags, s1.imags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_u() {
|
||||
let n = 2;
|
||||
let state = State::new(n);
|
||||
let u = Unitary::from_single_qubit_gate(&state, Gate::U(1.0, 2.0, 3.0), 0);
|
||||
|
||||
let s = apply_unitary(&state, &u);
|
||||
|
||||
let mut s1 = State::new(n);
|
||||
apply(Gate::U(1.0, 2.0, 3.0), &mut s1, 0);
|
||||
|
||||
assert_eq!(s.n, s1.n);
|
||||
assert_eq!(s.reals, s1.reals);
|
||||
assert_eq!(s.imags, s1.imags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display() {
|
||||
const N: usize = 1;
|
||||
let state = gen_random_state(N);
|
||||
let u = Unitary::from_single_qubit_gate(&state, Gate::X, 0);
|
||||
|
||||
let x_gate_as_str = "0+i0 1+i0 \n1+i0 0+i0 \n".to_string();
|
||||
let u_as_str = u.to_string();
|
||||
assert_eq!(u_as_str, x_gate_as_str);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conjugate_transpose() {
|
||||
const N: usize = 2;
|
||||
let state = State::new(N);
|
||||
let u = Unitary::from_single_qubit_gate(&state, Gate::H, 0);
|
||||
let mut u_ct = Unitary::from_single_qubit_gate(&state, Gate::H, 0);
|
||||
u_ct.conj_t();
|
||||
|
||||
let identity = u_ct.multiply(&u);
|
||||
|
||||
for i in 0..u.height {
|
||||
for j in 0..u.width {
|
||||
if i == j {
|
||||
assert_float_closeness(identity.reals[i * identity.width + j], 1.0, 0.00001);
|
||||
assert_float_closeness(identity.imags[i * identity.width + j], 0.0, 0.00001);
|
||||
} else {
|
||||
assert_float_closeness(identity.reals[i * identity.width + j], 0.0, 0.00001);
|
||||
assert_float_closeness(identity.imags[i * identity.width + j], 0.0, 0.00001);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+378
@@ -0,0 +1,378 @@
|
||||
//! An assortment of utility functions for visualizing, benchmarking, and testing.
|
||||
use comfy_table::{
|
||||
presets::UTF8_FULL,
|
||||
Color::Rgb,
|
||||
{Cell, Color, Table},
|
||||
};
|
||||
use rand::distributions::Uniform;
|
||||
use rand::prelude::*;
|
||||
|
||||
use crate::{
|
||||
core::State,
|
||||
gates::{c_apply, Gate},
|
||||
math::{modulus, Amplitude, Float, PI},
|
||||
};
|
||||
|
||||
/// Formats an unsigned, 128 bit integer with commas, as a string. Used for readability
|
||||
pub fn pretty_print_int(i: u128) -> String {
|
||||
if i == 0 {
|
||||
return "0".into();
|
||||
}
|
||||
|
||||
// u128::MAX == 340_282_366_920_938_463_463_374_607_431_768_211_455
|
||||
// len("340_282_366_920_938_463_463_374_607_431_768_211_455") == 51
|
||||
let mut q = arrayvec::ArrayVec::<u8, 51>::new();
|
||||
|
||||
let mut x = i;
|
||||
let mut comma = 0;
|
||||
|
||||
while x > 0 {
|
||||
let r = x % 10;
|
||||
x /= 10;
|
||||
|
||||
if comma == 3 {
|
||||
q.push(44); // 44 is ',' in ASCII
|
||||
comma = 0;
|
||||
}
|
||||
q.push((0x30 + r) as u8); // ascii digits 0, 1, 2, ... start at value 0x30
|
||||
comma += 1;
|
||||
}
|
||||
|
||||
q.into_iter().map(|d| d as char).rev().collect()
|
||||
}
|
||||
|
||||
/// Convert a `usize` to its binary expansion, but padded with 0's. Padding is of size, width.
|
||||
pub fn padded_bin(i: usize, width: usize) -> String {
|
||||
format!("{:01$b}", i, width + 2)
|
||||
}
|
||||
|
||||
/// Display the `State` as a table
|
||||
pub fn to_table(state: &State) -> String {
|
||||
let n: usize = state.n.into();
|
||||
let mut table = Table::new();
|
||||
table
|
||||
.load_preset(UTF8_FULL)
|
||||
// .set_content_arrangement(ContentArrangement::Dynamic)
|
||||
.set_width(100)
|
||||
.set_header(vec![
|
||||
Cell::new("Outcome"),
|
||||
Cell::new("Amplitude"),
|
||||
Cell::new("Magnitude"),
|
||||
Cell::new("Amplitude Bar"),
|
||||
Cell::new("Probability"),
|
||||
Cell::new("Probability Bar"),
|
||||
]);
|
||||
|
||||
(0..16.min(state.len())).for_each(|idx| {
|
||||
let z_re = state.reals[idx];
|
||||
let z_im = state.imags[idx];
|
||||
table.add_row(vec![
|
||||
Cell::new(format!("{} = {}", idx, padded_bin(idx, n))),
|
||||
Cell::new(format!("{:.5} + i{:.5}", z_re, z_im)),
|
||||
Cell::new(format!("{:.5}", modulus(z_re, z_im))),
|
||||
Cell::new(str::repeat(
|
||||
" ",
|
||||
(modulus(z_re, z_im) * 50.0).round() as usize,
|
||||
))
|
||||
.bg(complex_to_rgb(z_re, z_im, false)),
|
||||
Cell::new(format!("{:.5}", modulus(z_re, z_im).powi(2))),
|
||||
Cell::new(str::repeat(
|
||||
" ",
|
||||
(modulus(z_re, z_im).powi(2) * 50.0).round() as usize,
|
||||
))
|
||||
.bg(complex_to_rgb(1.0, 0.0, false)),
|
||||
]);
|
||||
});
|
||||
table.force_no_tty().enforce_styling().style_text_only();
|
||||
table.to_string()
|
||||
}
|
||||
|
||||
fn complex_to_rgb(z_re: Float, z_im: Float, scaled_saturation: bool) -> Color {
|
||||
let val = 100.0;
|
||||
|
||||
let mut hue: f32 = (z_im.atan2(z_re) * 180.0 / PI) as f32;
|
||||
if hue < 0.0 {
|
||||
hue += 360.0;
|
||||
}
|
||||
|
||||
let sat: f32 = if scaled_saturation {
|
||||
modulus(z_re, z_im) as f32 * 100.0
|
||||
} else {
|
||||
100.0
|
||||
};
|
||||
|
||||
let [r, g, b] = hsv_to_rgb(hue, sat, val);
|
||||
Rgb { r, g, b }
|
||||
}
|
||||
|
||||
// https://gist.github.com/eyecatchup/9536706 Colors
|
||||
fn hsv_to_rgb(hue: f32, sat: f32, val: f32) -> [u8; 3] {
|
||||
// Make sure our arguments stay in-range
|
||||
let (mut h, mut s, mut v) = (
|
||||
0.0_f32.max(360.0_f32.min(hue)),
|
||||
0.0_f32.max(100.0_f32.min(sat)),
|
||||
0.0_f32.max(100.0_f32.min(val)),
|
||||
);
|
||||
|
||||
// We accept saturation and value arguments from 0 to 100 because that's
|
||||
// how Photoshop represents those values. Internally, however, the
|
||||
// saturation and value are calculated from a range of 0 to 1.
|
||||
// We make that conversion here.
|
||||
s /= 100.0;
|
||||
v /= 100.0;
|
||||
|
||||
let (r, g, b) = (
|
||||
(v * 255.0).round(),
|
||||
(v * 255.0).round(),
|
||||
(v * 255.0).round(),
|
||||
);
|
||||
|
||||
if s == 0.0 {
|
||||
// Achromatic(grey)
|
||||
return [r as u8, g as u8, b as u8];
|
||||
}
|
||||
|
||||
h /= 60.0; // sector 0 to 5
|
||||
let i = h.floor() as i16;
|
||||
let f = h - f32::from(i); // factorial part of h
|
||||
let p = v * (1.0 - s);
|
||||
let q = v * (1.0 - s * f);
|
||||
let t = v * (1.0 - s * (1.0 - f));
|
||||
|
||||
let (r, g, b) = if i == 0 {
|
||||
(v, t, p)
|
||||
} else if i == 1 {
|
||||
(q, v, p)
|
||||
} else if i == 2 {
|
||||
(p, v, t)
|
||||
} else if i == 3 {
|
||||
(p, q, v)
|
||||
} else if i == 4 {
|
||||
(t, p, v)
|
||||
} else {
|
||||
(v, p, q)
|
||||
};
|
||||
[
|
||||
(r * 255.0).round() as u8,
|
||||
(g * 255.0).round() as u8,
|
||||
(b * 255.0).round() as u8,
|
||||
]
|
||||
}
|
||||
|
||||
/// Asserts that two floating point numbers are approximately equal.
|
||||
pub fn assert_float_closeness(actual: Float, expected: Float, epsilon: Float) {
|
||||
assert!((actual - expected).abs() < epsilon);
|
||||
}
|
||||
|
||||
/// Generates a random quantum state
|
||||
pub fn gen_random_state(n: usize) -> State {
|
||||
assert!(n > 0);
|
||||
let mut rng = thread_rng();
|
||||
let between = Uniform::from(0.0..1.0);
|
||||
let angle_dist = Uniform::from(0.0..2.0 * PI);
|
||||
let num_amps = 1 << n;
|
||||
|
||||
let mut probs: Vec<_> = (0..num_amps).map(|_| between.sample(&mut rng)).collect();
|
||||
|
||||
let total: Float = probs.iter().sum();
|
||||
let total_recip = total.recip();
|
||||
|
||||
probs.iter_mut().for_each(|p| *p *= total_recip);
|
||||
|
||||
let angles = (0..num_amps).map(|_| angle_dist.sample(&mut rng));
|
||||
|
||||
let mut reals = Vec::with_capacity(num_amps);
|
||||
let mut imags = Vec::with_capacity(num_amps);
|
||||
|
||||
probs.iter().zip(angles).for_each(|(p, a)| {
|
||||
let p_sqrt = p.sqrt();
|
||||
let (sin_a, cos_a) = a.sin_cos();
|
||||
let re = p_sqrt * cos_a;
|
||||
let im = p_sqrt * sin_a;
|
||||
reals.push(re);
|
||||
imags.push(im);
|
||||
});
|
||||
|
||||
State {
|
||||
reals,
|
||||
imags,
|
||||
n: n.try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// Utility function for multiplying two 2 x 2 gates
|
||||
pub fn mat_mul_2x2(m0: [Amplitude; 4], m1: [Amplitude; 4]) -> [Amplitude; 4] {
|
||||
let mut res: [Amplitude; 4] = [Amplitude { re: 0.0, im: 0.0 }; 4];
|
||||
|
||||
for i in 0..2 {
|
||||
for j in 0..2 {
|
||||
for k in 0..2 {
|
||||
res[i * 2 + j].re +=
|
||||
m0[i * 2 + k].re * m1[k * 2 + j].re - m0[i * 2 + k].im * m1[k * 2 + j].im;
|
||||
res[i * 2 + j].im +=
|
||||
m0[i * 2 + k].re * m1[k * 2 + j].im + m0[i * 2 + k].im * m1[k * 2 + j].re;
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::*;
|
||||
|
||||
// color values taken from: https://www.rapidtables.com/convert/color/hsv-to-rgb.html
|
||||
#[test]
|
||||
fn hsv_rgb_conv() {
|
||||
let hsv_colors = HashMap::from([
|
||||
("Black", (0.0, 0.0, 0.0)),
|
||||
("White", (0.0, 0.0, 100.0)),
|
||||
("Red", (0.0, 100.0, 100.0)),
|
||||
("Lime", (120.0, 100.0, 100.0)),
|
||||
("Blue", (240.0, 100.0, 100.0)),
|
||||
("Yellow", (60.0, 100.0, 100.0)),
|
||||
("Cyan", (180.0, 100.0, 100.0)),
|
||||
("Magenta", (300.0, 100.0, 100.0)),
|
||||
("Silver", (0.0, 0.0, 75.0)),
|
||||
("Gray", (0.0, 0.0, 50.0)),
|
||||
("Maroon", (0.0, 100.0, 50.0)),
|
||||
("Olive", (60.0, 100.0, 50.0)),
|
||||
("Green", (120.0, 100.0, 50.0)),
|
||||
("Purple", (300.0, 100.0, 50.0)),
|
||||
("Teal", (180.0, 100.0, 50.0)),
|
||||
("Navy", (240.0, 100.0, 50.0)),
|
||||
]);
|
||||
let rgb_colors = HashMap::from([
|
||||
("Black", (0, 0, 0)),
|
||||
("White", (255, 255, 255)),
|
||||
("Red", (255, 0, 0)),
|
||||
("Lime", (0, 255, 0)),
|
||||
("Blue", (0, 0, 255)),
|
||||
("Yellow", (255, 255, 0)),
|
||||
("Cyan", (0, 255, 255)),
|
||||
("Magenta", (255, 0, 255)),
|
||||
("Silver", (191, 191, 191)),
|
||||
("Gray", (128, 128, 128)),
|
||||
("Maroon", (128, 0, 0)),
|
||||
("Olive", (128, 128, 0)),
|
||||
("Green", (0, 128, 0)),
|
||||
("Purple", (128, 0, 128)),
|
||||
("Teal", (0, 128, 128)),
|
||||
("Navy", (0, 0, 128)),
|
||||
]);
|
||||
|
||||
for (color, value) in hsv_colors.into_iter() {
|
||||
let expected_rgb_color = *rgb_colors.get(color).unwrap();
|
||||
let actual_rgb_color = hsv_to_rgb(value.0, value.1, value.2);
|
||||
assert_eq!(actual_rgb_color[0], expected_rgb_color.0);
|
||||
assert_eq!(actual_rgb_color[1], expected_rgb_color.1);
|
||||
assert_eq!(actual_rgb_color[2], expected_rgb_color.2);
|
||||
}
|
||||
}
|
||||
|
||||
// Compare the output to a complex number color map
|
||||
// Positive real numbers always appear red.
|
||||
// The primary colors appear at phase angles 2 pi/3 (green) and 4 pi/3 (blue).
|
||||
// The subtractive colors yellow, cyan, and magenta have the phases pi/3, pi, and 5 pi/3.
|
||||
//https://vqm.uni-graz.at/pages/colormap.html
|
||||
#[test]
|
||||
fn complex_num_color_map() {
|
||||
let rgb_colors = HashMap::from([
|
||||
("Red", Rgb { r: 255, g: 0, b: 0 }),
|
||||
("Blue", Rgb { r: 0, g: 0, b: 255 }),
|
||||
(
|
||||
"Yellow",
|
||||
Rgb {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 0,
|
||||
},
|
||||
),
|
||||
(
|
||||
"Cyan",
|
||||
Rgb {
|
||||
r: 0,
|
||||
g: 255,
|
||||
b: 255,
|
||||
},
|
||||
),
|
||||
(
|
||||
"Magenta",
|
||||
Rgb {
|
||||
r: 255,
|
||||
g: 0,
|
||||
b: 255,
|
||||
},
|
||||
),
|
||||
("Green", Rgb { r: 0, g: 255, b: 0 }),
|
||||
]);
|
||||
|
||||
let z = Amplitude { re: 1.0, im: 0.0 };
|
||||
let rgb_val = complex_to_rgb(z.re, z.im, false);
|
||||
assert_eq!(rgb_val, *rgb_colors.get("Red").unwrap());
|
||||
|
||||
let z = Amplitude {
|
||||
re: (2.0 * PI / 3.0).cos(),
|
||||
im: (2.0 * PI / 3.0).sin(),
|
||||
};
|
||||
let rgb_val = complex_to_rgb(z.re, z.im, false);
|
||||
assert_eq!(rgb_val, *rgb_colors.get("Green").unwrap());
|
||||
|
||||
let z = Amplitude {
|
||||
re: (4.0 * PI / 3.0).cos(),
|
||||
im: (4.0 * PI / 3.0).sin(),
|
||||
};
|
||||
let rgb_val = complex_to_rgb(z.re, z.im, false);
|
||||
assert_eq!(rgb_val, *rgb_colors.get("Blue").unwrap());
|
||||
|
||||
let z = Amplitude {
|
||||
re: (PI / 3.0).cos(),
|
||||
im: (PI / 3.0).sin(),
|
||||
};
|
||||
let rgb_val = complex_to_rgb(z.re, z.im, false);
|
||||
println!("{:?}", rgb_val);
|
||||
assert_eq!(rgb_val, *rgb_colors.get("Yellow").unwrap());
|
||||
|
||||
let z = Amplitude {
|
||||
re: PI.cos(),
|
||||
im: PI.sin(),
|
||||
};
|
||||
let rgb_val = complex_to_rgb(z.re, z.im, false);
|
||||
assert_eq!(rgb_val, *rgb_colors.get("Cyan").unwrap());
|
||||
|
||||
let z = Amplitude {
|
||||
re: (5.0 * PI / 3.0).cos(),
|
||||
im: (5.0 * PI / 3.0).sin(),
|
||||
};
|
||||
let rgb_val = complex_to_rgb(z.re, z.im, false);
|
||||
assert_eq!(rgb_val, *rgb_colors.get("Magenta").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_int() {
|
||||
for i in 0..1000 {
|
||||
assert_eq!(pretty_print_int(i), i.to_string());
|
||||
}
|
||||
|
||||
assert_eq!(pretty_print_int(1_000), "1,000");
|
||||
assert_eq!(pretty_print_int(10_000), "10,000");
|
||||
assert_eq!(pretty_print_int(100_000), "100,000");
|
||||
assert_eq!(pretty_print_int(1_000_000), "1,000,000");
|
||||
assert_eq!(pretty_print_int(1_000_000_000), "1,000,000,000");
|
||||
assert_eq!(pretty_print_int(1_000_000_000_000), "1,000,000,000,000");
|
||||
assert_eq!(pretty_print_int(100_000_000_000_000), "100,000,000,000,000");
|
||||
assert_eq!(
|
||||
pretty_print_int(u128::MAX),
|
||||
"340,282,366,920,938,463,463,374,607,431,768,211,455"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user