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 *;
// Make a new circuit builder.
let mut b = 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?;
let rb = b.register?;
// 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
let q = b.hadamard;
// Then swap ra and rb, conditioned on q.
let = b.cswap?;
// Finally apply H to q again.
let q = b.hadamard;
// Add a measurement to the first qubit, save a reference so we can get the result later.
let = b.measure;
// 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 = ;
// Run circuit with a given precision.
let = ?;
// Lookup the result of the measurement we performed using the handle, and the probability
// of getting that measurement.
let = measured.get_measurement.unwrap;
println!;
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 *;
let n = 3;
let mut b = new;
let ra = b.register?;
let rb = b.register?;
let = program!?;
let r = b.merge?;
To clean up gamma we can use the wrap_fn
macro:
use *;
let n = 3;
let mut b = new;
let ra = b.register?;
let rb = b.register?;
// 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!;
let = program!?;
let r = b.merge?;
And with these wrapped functions, automatically produce their conjugates / inverses:
use *;
let n = 3;
let mut b = new;
let ra = b.register?;
let rb = b.register?;
wrap_fn!;
invert_fn!;
// This program is equivalent to the identity (U^-1 U = I).
let = program!?;
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 *;
let mut b = new;
let r = b.qubit;
wrap_fn!;
invert_fn!;
let r = program!?;
Generics can be used by substituting the usual angle brackets for square.
use *;
let mut b = new;
let r = b.qubit;
wrap_fn!;
invert_fn!;
let r = program!?;