## qip

A library for efficient quantum computing simulations

### 48 releases

 0.15.3 Sep 2, 2021 Aug 4, 2021 Jun 14, 2021 Feb 18, 2021 Jul 18, 2019

#1 in Emulators

370KB
9K SLoC

# RustQIP

Quantum Computing library leveraging graph building to build efficient quantum circuit simulations.

See all the examples in the examples directory.

PRs welcome

# Example (CSWAP)

Here's an example of a small circuit where two groups of Registers are swapped conditioned on a third. This circuit is very small, only three operations plus a measurement, so the boilerplate can look quite large in comparison, but that setup provides the ability to construct circuits easily and safely when they do get larger.

``````use qip::*;

// Make a new circuit builder.
let mut b = OpBuilder::new();

// Make three registers of sizes 1, 3, 3 (7 qubits total).
let q = b.qubit();  // Same as b.register(1)?;
let ra = b.register(3)?;
let rb = b.register(3)?;

// We will want to feed in some inputs later, hang on to the handles
// so we don't need to actually remember any indices.
let a_handle = ra.handle();
let b_handle = rb.handle();

// Define circuit
// First apply an H to q
// Then swap ra and rb, conditioned on q.
let (q, _, _) = b.cswap(q, ra, rb)?;
// Finally apply H to q again.
// Add a measurement to the first qubit, save a reference so we can get the result later.
let (q, m_handle) = b.measure(q);

// Now q is the end result of the above circuit, and we can run the circuit by referencing it.
// Make an initial state: |0,000,001> (default value for registers not mentioned is 0).
let initial_state = [a_handle.make_init_from_index(0b000)?,
b_handle.make_init_from_index(0b001)?];

// Run circuit with a given precision.
let (_, measured) = run_local_with_init::<f64>(&q, &initial_state)?;

// Lookup the result of the measurement we performed using the handle, and the probability
// of getting that measurement.
let (result, p) = measured.get_measurement(&m_handle).unwrap();
println!("Measured: {:?} (with chance {:?})", result, p);
``````

# The Program Macro

While the borrow checker included in rust is a wonderful tool for checking that our registers are behaving, it can be cumbersome. For that reason qip also includes a macro which provides an API similar to that which you would see in quantum computing textbooks. Notice that due to a design choice in rust's `macro_rules!` we use vertical bars to group qubits and a comma must appear before the closing bar. This may be fixed in the future using procedural macros.

``````use qip::*;

let n = 3;
let mut b = OpBuilder::new();
let ra = b.register(n)?;
let rb = b.register(n)?;

fn gamma(b: &mut dyn UnitaryBuilder, mut rs: Vec<Register>) -> Result<Vec<Register>, CircuitError> {
let rb = rs.pop().unwrap();
let ra = rs.pop().unwrap();
let (ra, rb) = b.cnot(ra, rb);
let (rb, ra) = b.cnot(rb, ra);
Ok(vec![ra, rb])
}

let (ra, rb) = program!(&mut b, ra, rb;
// Applies gamma to |ra ra>|ra>
gamma ra[0..2], ra;
// Applies gamma to |ra rb>|ra>
gamma |ra, rb,| ra;
// Applies gamma to |ra>|rb ra>
gamma ra, |rb, ra,|;
// Applies gamma to |ra ra>|ra> if rb == |111>
control gamma rb, ra[0..2], ra;
// Applies gamma to |ra ra>|ra> if rb == |110> (rb == |0>, rb == 1, ...)
control(0b110) gamma rb, ra[0..2], ra;
)?;
let r = b.merge(vec![ra, rb])?;
``````

To clean up gamma we can use the `wrap_fn` macro:

``````use qip::*;

let n = 3;
let mut b = OpBuilder::new();
let ra = b.register(n)?;
let rb = b.register(n)?;

fn gamma(b: &mut dyn UnitaryBuilder, ra: Register, rb: Register) -> (Register, Register) {
let (ra, rb) = b.cnot(ra, rb);
let (rb, ra) = b.cnot(rb, ra);
(ra, rb)
}

// Make a function gamma_op from gamma which matches the spec required by program!(...).
// Here we tell wrap_fn! that gamma takes two registers, which we will internally call ra, rb.
// if gamma returns a Result<(Register, Register), CircuitError>, write (gamma) instead.
// wrap_fn!(gamma_op, (gamma), ra, rb)
wrap_fn!(gamma_op, gamma, ra, rb);

let (ra, rb) = program!(&mut b, ra, rb;
gamma_op ra[0..2], ra;
)?;
let r = b.merge(vec![ra, rb])?;
``````

And with these wrapped functions, automatically produce their conjugates / inverses:

``````use qip::*;

let n = 3;
let mut b = OpBuilder::new();
let ra = b.register(n)?;
let rb = b.register(n)?;

fn gamma(b: &mut dyn UnitaryBuilder, ra: Register, rb: Register) -> (Register, Register) {
let (ra, rb) = b.cnot(ra, rb);
let (rb, ra) = b.cnot(rb, ra);
(ra, rb)
}

wrap_fn!(gamma_op, gamma, ra, rb);
invert_fn!(inv_gamma_op, gamma_op);

// This program is equivalent to the identity (U^-1 U = I).
let (ra, rb) = program!(&mut b, ra, rb;
gamma_op ra, rb;
inv_gamma_op ra, rb;
)?;
``````

Functions in the `program!` macro may have a single argument, which is passed after the registers. This argument must be included in the `wrap_fn!` call as well as the `invert_fn!` call.

``````use qip::*;

let mut b = OpBuilder::new();
let r = b.qubit();

fn rz(b: &mut dyn UnitaryBuilder, r: Register, theta: f64) -> Register {
b.rz(r, theta)
}

wrap_fn!(rz_op(theta: f64), rz, r);
invert_fn!(inv_rz_op(theta: f64), rz_op);

let r = program!(&mut b, r;
rz_op(3.141) r;
inv_rz_op(3.141) r;
)?;
``````

Generics can be used by substituting the usual angle brackets for square.

``````use qip::*;

let mut b = OpBuilder::new();
let r = b.qubit();

fn rz<T: Into<f64>>(b: &mut dyn UnitaryBuilder, r: Register, theta: T) -> Register {
b.rz(r, theta.into())
}

wrap_fn!(rz_op[T: Into<f64>](theta: T), rz, r);
invert_fn!(inv_rz_op[T: Into<f64>](theta: T), rz_op);

let r = program!(&mut b, r;
rz_op(3.141_f32) r;
inv_rz_op(3.141_f32) r;
)?;
``````

~0.6–1MB
~19K SLoC