Skip to content

Commit

Permalink
Merge 656f134 into 20ddd13
Browse files Browse the repository at this point in the history
  • Loading branch information
swernli authored Sep 11, 2024
2 parents 20ddd13 + 656f134 commit b38a5b6
Show file tree
Hide file tree
Showing 15 changed files with 767 additions and 136 deletions.
364 changes: 364 additions & 0 deletions compiler/qsc/src/codegen/tests.rs

Large diffs are not rendered by default.

91 changes: 90 additions & 1 deletion compiler/qsc_circuit/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use crate::{
};
use num_bigint::BigUint;
use num_complex::Complex;
use qsc_codegen::remapper::{HardwareId, Remapper};
use qsc_data_structures::index_map::IndexMap;
use qsc_eval::{backend::Backend, val::Value};
use std::{fmt::Write, mem::take, rc::Rc};
Expand Down Expand Up @@ -181,6 +180,10 @@ impl Backend for Builder {
self.remapper.qubit_release(q);
}

fn qubit_swap_id(&mut self, q0: usize, q1: usize) {
self.remapper.swap(q0, q1);
}

fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
(Vec::new(), 0)
}
Expand Down Expand Up @@ -370,6 +373,92 @@ impl Builder {
}
}

/// Provides support for qubit id allocation, measurement and
/// reset operations for Base Profile targets.
///
/// Since qubit reuse is disallowed, a mapping is maintained
/// from allocated qubit ids to hardware qubit ids. Each time
/// a qubit is reset, it is remapped to a fresh hardware qubit.
///
/// Note that even though qubit reset & reuse is disallowed,
/// qubit ids are still reused for new allocations.
/// Measurements are tracked and deferred.
#[derive(Default)]
struct Remapper {
next_meas_id: usize,
next_qubit_id: usize,
next_qubit_hardware_id: HardwareId,
qubit_map: IndexMap<usize, HardwareId>,
measurements: Vec<(HardwareId, usize)>,
}

impl Remapper {
fn map(&mut self, qubit: usize) -> HardwareId {
if let Some(mapped) = self.qubit_map.get(qubit) {
*mapped
} else {
let mapped = self.next_qubit_hardware_id;
self.next_qubit_hardware_id.0 += 1;
self.qubit_map.insert(qubit, mapped);
mapped
}
}

fn m(&mut self, q: usize) -> usize {
let mapped_q = self.map(q);
let id = self.get_meas_id();
self.measurements.push((mapped_q, id));
id
}

fn mreset(&mut self, q: usize) -> usize {
let id = self.m(q);
self.reset(q);
id
}

fn reset(&mut self, q: usize) {
self.qubit_map.remove(q);
}

fn qubit_allocate(&mut self) -> usize {
let id = self.next_qubit_id;
self.next_qubit_id += 1;
let _ = self.map(id);
id
}

fn qubit_release(&mut self, _q: usize) {
self.next_qubit_id -= 1;
}

fn swap(&mut self, q0: usize, q1: usize) {
let q0_mapped = self.map(q0);
let q1_mapped = self.map(q1);
self.qubit_map.insert(q0, q1_mapped);
self.qubit_map.insert(q1, q0_mapped);
}

fn measurements(&self) -> impl Iterator<Item = &(HardwareId, usize)> {
self.measurements.iter()
}

#[must_use]
fn num_qubits(&self) -> usize {
self.next_qubit_hardware_id.0
}

#[must_use]
fn get_meas_id(&mut self) -> usize {
let id = self.next_meas_id;
self.next_meas_id += 1;
id
}
}

#[derive(Copy, Clone, Default)]
pub struct HardwareId(pub usize);

#[allow(clippy::unicode_not_nfc)]
static KET_ZERO: &str = "|0〉";

Expand Down
1 change: 0 additions & 1 deletion compiler/qsc_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@

pub mod qir;
pub mod qsharp;
pub mod remapper;
88 changes: 0 additions & 88 deletions compiler/qsc_codegen/src/remapper.rs

This file was deleted.

14 changes: 12 additions & 2 deletions compiler/qsc_eval/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,18 @@ pub trait Backend {
fn qubit_release(&mut self, _q: usize) {
unimplemented!("qubit_release operation");
}
fn qubit_swap_id(&mut self, _q0: usize, _q1: usize) {
unimplemented!("qubit_swap_id operation");
}
fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
unimplemented!("capture_quantum_state operation");
}
fn qubit_is_zero(&mut self, _q: usize) -> bool {
unimplemented!("qubit_is_zero operation");
}

fn custom_intrinsic(&mut self, _name: &str, _arg: Value) -> Option<Result<Value, String>> {
None
}

fn set_seed(&mut self, _seed: Option<u64>) {}
}

Expand Down Expand Up @@ -240,6 +241,10 @@ impl Backend for SparseSim {
self.sim.release(q);
}

fn qubit_swap_id(&mut self, q0: usize, q1: usize) {
self.sim.swap_qubit_ids(q0, q1);
}

fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
let (state, count) = self.sim.get_state();
// Because the simulator returns the state indices with opposite endianness from the
Expand Down Expand Up @@ -458,6 +463,11 @@ where
self.main.qubit_release(q);
}

fn qubit_swap_id(&mut self, q0: usize, q1: usize) {
self.chained.qubit_swap_id(q0, q1);
self.main.qubit_swap_id(q0, q1);
}

fn capture_quantum_state(
&mut self,
) -> (Vec<(num_bigint::BigUint, num_complex::Complex<f64>)>, usize) {
Expand Down
79 changes: 78 additions & 1 deletion compiler/qsc_eval/src/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{
};
use num_bigint::BigInt;
use rand::{rngs::StdRng, Rng};
use rustc_hash::FxHashSet;
use rustc_hash::{FxHashMap, FxHashSet};
use std::array;
use std::convert::TryFrom;

Expand Down Expand Up @@ -77,6 +77,7 @@ pub(crate) fn call(
Err(_) => Err(Error::OutputFail(name_span)),
}
}
"Relabel" => qubit_relabel(arg, arg_span, |q0, q1| sim.qubit_swap_id(q0, q1)),
"Message" => match out.message(&arg.unwrap_string()) {
Ok(()) => Ok(Value::unit()),
Err(_) => Err(Error::OutputFail(name_span)),
Expand Down Expand Up @@ -249,6 +250,82 @@ fn two_qubit_rotation(
}
}

/// Performs relabeling of qubits from the a given left array to the corresponding right array.
/// The function will swap qubits with the given function to match the new relabeling, returning an error
/// if the qubits are not unique or if the relabeling is not a valid permutation.
pub fn qubit_relabel(
arg: Value,
arg_span: PackageSpan,
mut swap: impl FnMut(usize, usize),
) -> Result<Value, Error> {
let [left, right] = unwrap_tuple(arg);
let left = left
.unwrap_array()
.iter()
.map(|q| q.clone().unwrap_qubit().0)
.collect::<Vec<_>>();
let right = right
.unwrap_array()
.iter()
.map(|q| q.clone().unwrap_qubit().0)
.collect::<Vec<_>>();
let left_set = left.iter().collect::<FxHashSet<_>>();
let right_set = right.iter().collect::<FxHashSet<_>>();
if left.len() != left_set.len() || right.len() != right_set.len() {
return Err(Error::QubitUniqueness(arg_span));
}
if left_set != right_set {
return Err(Error::RelabelingMismatch(arg_span));
}

let mut map = FxHashMap::default();
for (l, r) in left.into_iter().zip(right.into_iter()) {
if l == r {
continue;
}
match (map.contains_key(&l), map.contains_key(&r)) {
(false, false) => {
// Neither qubit has been relabeled yet.
swap(l, r);
map.insert(l, r);
map.insert(r, l);
}
(false, true) => {
// The right qubit has been relabeled, so we need to swap the left qubit with the
// qubit that the right qubit was relabeled to.
let mapped = *map
.keys()
.find(|k| map[*k] == r)
.expect("mapped qubit should be present as both key and value");
swap(l, mapped);
map.insert(l, r);
map.insert(mapped, l);
}
(true, false) => {
// The left qubit has been relabeled, so we swap the qubits as normal but
// remember the new mapping of the right qubit.
let mapped = *map.get(&l).expect("mapped qubit should be present");
swap(l, r);
map.insert(l, r);
map.insert(r, mapped);
}
(true, true) => {
// Both qubits have been relabeled, so we need to swap the mapped right qubit with
// the left qubit and remember the new mapping of both qubits.
let mapped_l = *map.get(&l).expect("mapped qubit should be present");
let mapped_r = *map.get(&r).expect("mapped qubit should be present");
if mapped_l != r && mapped_r != l {
swap(mapped_r, l);
map.insert(mapped_r, mapped_l);
map.insert(l, r);
}
}
}
}

Ok(Value::unit())
}

fn unwrap_tuple<const N: usize>(value: Value) -> [Value; N] {
let values = value.unwrap_tuple();
array::from_fn(|i| values[i].clone())
Expand Down
4 changes: 4 additions & 0 deletions compiler/qsc_eval/src/intrinsic/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ impl Backend for CustomSim {
self.sim.qubit_release(q);
}

fn qubit_swap_id(&mut self, q0: usize, q1: usize) {
self.sim.qubit_swap_id(q0, q1);
}

fn capture_quantum_state(
&mut self,
) -> (Vec<(num_bigint::BigUint, num_complex::Complex<f64>)>, usize) {
Expand Down
8 changes: 7 additions & 1 deletion compiler/qsc_eval/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mod tests;
pub mod backend;
pub mod debug;
mod error;
mod intrinsic;
pub mod intrinsic;
pub mod output;
pub mod state;
pub mod val;
Expand Down Expand Up @@ -136,6 +136,11 @@ pub enum Error {
#[diagnostic(code("Qsc.Eval.RangeStepZero"))]
RangeStepZero(#[label("invalid range")] PackageSpan),

#[error("qubit arrays used in relabeling must be a permutation of the same set of qubits")]
#[diagnostic(help("ensure that each qubit is present exactly once in both arrays"))]
#[diagnostic(code("Qsc.Eval.RelabelingMismatch"))]
RelabelingMismatch(#[label] PackageSpan),

#[error("Qubit{0} released while not in |0⟩ state")]
#[diagnostic(help("qubits should be returned to the |0⟩ state before being released to satisfy the assumption that allocated qubits start in the |0⟩ state"))]
#[diagnostic(code("Qsc.Eval.ReleasedQubitNotZero"))]
Expand Down Expand Up @@ -188,6 +193,7 @@ impl Error {
| Error::QubitsNotCounted(span)
| Error::QubitsNotSeparable(span)
| Error::RangeStepZero(span)
| Error::RelabelingMismatch(span)
| Error::ReleasedQubitNotZero(_, span)
| Error::ResultComparisonUnsupported(span)
| Error::UnboundName(span)
Expand Down
Loading

0 comments on commit b38a5b6

Please sign in to comment.