Skip to content

Commit

Permalink
Introduce SwapLabels API
Browse files Browse the repository at this point in the history
This change introduces an API for swapping qubit labels without needing to use any quantum SWAP operations. This notion of relabeling is used in some quantum algorithms when all-to-all connectivity is assumed. The change includes support for label swapping in simulation, circuit generation, and QIR codegen.
  • Loading branch information
swernli committed Sep 8, 2024
1 parent 72c32aa commit d248b11
Show file tree
Hide file tree
Showing 17 changed files with 629 additions and 147 deletions.
364 changes: 364 additions & 0 deletions compiler/qsc/src/codegen/tests.rs

Large diffs are not rendered by default.

96 changes: 95 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,97 @@ 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)]
pub 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 {
pub 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
}
}

pub 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
}

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

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

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

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

pub 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);
}

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

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

#[must_use]
pub fn num_measurements(&self) -> usize {
self.next_meas_id
}

#[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
5 changes: 5 additions & 0 deletions compiler/qsc_eval/src/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ pub(crate) fn call(
Err(Error::ReleasedQubitNotZero(qubit, arg_span))
}
}
"__quantum__rt__qubit_swap_ids" => {
let [q0, q1] = unwrap_tuple(arg);
sim.qubit_swap_id(q0.unwrap_qubit().0, q1.unwrap_qubit().0);
Ok(Value::unit())
}
"__quantum__qis__ccx__body" => {
three_qubit_gate(|ctl0, ctl1, q| sim.ccx(ctl0, ctl1, q), arg, arg_span)
}
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
14 changes: 7 additions & 7 deletions compiler/qsc_eval/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,23 +418,23 @@ fn block_qubit_use_array_invalid_count_expr() {
0,
),
span: Span {
lo: 2064,
hi: 2121,
lo: 2172,
hi: 2229,
},
},
),
[
Frame {
span: Span {
lo: 2064,
hi: 2121,
lo: 2172,
hi: 2229,
},
id: StoreItemId {
package: PackageId(
0,
),
item: LocalItemId(
6,
7,
),
},
caller: PackageId(
Expand Down Expand Up @@ -3724,7 +3724,7 @@ fn controlled_operation_with_duplicate_controls_fails() {
1,
),
item: LocalItemId(
130,
131,
),
},
caller: PackageId(
Expand Down Expand Up @@ -3774,7 +3774,7 @@ fn controlled_operation_with_target_in_controls_fails() {
1,
),
item: LocalItemId(
130,
131,
),
},
caller: PackageId(
Expand Down
Loading

0 comments on commit d248b11

Please sign in to comment.