Skip to content

Commit

Permalink
Update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Renmusxd committed Jun 4, 2022
1 parent f0f40ad commit f9940ca
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 119 deletions.
240 changes: 121 additions & 119 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,188 +10,190 @@ See all the examples in the [examples directory](https://github.com/Renmusxd/Rus

*PRs welcome*

_Note: Currently undergoing a large rewrite, the examples below are valid for the crates.io versions of the library but
are out of date for the bleeding edge_
Rust is a great language for quantum computing with gate models because the borrow checker
is very similar to the [No-cloning theorem](https://wikipedia.org/wiki/No-cloning_theorem).

See all the examples in the [examples directory](https://github.com/Renmusxd/RustQIP/tree/master/examples) of the Github
repository.

# 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.
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.

```rust
use qip::*;
use qip::prelude::*;
use std::num::NonZeroUsize;

// Make a new circuit builder.
let mut b = OpBuilder::new();
let mut b = LocalBuilder::<f64>::default();
let n = NonZeroUsize::new(3).unwrap();

// 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();
let ra = b.register(n);
let rb = b.register(n);

// Define circuit
// First apply an H to q
let q = b.hadamard(q);
let q = b.h(q);
// Then swap ra and rb, conditioned on q.
let (q, _, _) = b.cswap(q, ra, rb) ?;
let mut cb = b.condition_with(q);
let (ra, rb) = cb.swap(ra, rb) ?;
let q = cb.dissolve();
// Finally apply H to q again.
let q = b.hadamard(q);
let q = b.h(q);

// 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) ?;
let (_, measured) = b.calculate_state_with_init([( & ra, 0b000), ( & rb, 0b001)]);

// 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();
let (result, p) = measured.get_measurement(m_handle);

// Print the measured result
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.*
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.
This is guarded behind the `macros` feature.

```rust
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])
use qip::prelude::*;
use std::num::NonZeroUsize;
use qip_macros::program;

fn gamma<B>(b: &mut B, ra: B::Register, rb: B::Register) -> CircuitResult<(B::Register, B::Register)>
where B: AdvancedCircuitBuilder<f64>
{
let (ra, rb) = b.toffoli(ra, rb)?;
let (rb, ra) = b.toffoli(rb, ra)?;
Ok((ra, rb))
}

let (ra, rb) = program!(&mut b, ra, rb;
let n = NonZeroUsize::new(3).unwrap();
let mut b = LocalBuilder::default();
let ra = b.register(n);
let rb = b.register(n);

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

To clean up gamma we can use the `wrap_fn` macro:
We can also apply this to function which take other argument. Here `gamma` takes a boolean
argument `skip` which is passed in before the registers.
*The arguments to functions in the program macro may not reference the input registers*

```rust
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)
use qip::prelude::*;
use std::num::NonZeroUsize;
use qip_macros::program;

fn gamma<B>(b: &mut B, skip: bool, ra: B::Register, rb: B::Register) -> CircuitResult<(B::Register, B::Register)>
where B: AdvancedCircuitBuilder<f64>
{
let (ra, rb) = b.toffoli(ra, rb)?;
let (rb, ra) = if skip {
b.toffoli(rb, ra)?
} else {
(rb, ra)
};
Ok((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[2];
let n = NonZeroUsize::new(3).unwrap();
let mut b = LocalBuilder::default();
let ra = b.register(n);
let rb = b.register(n);

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

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

```rust
use qip::*;

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

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[2];
inv_gamma_op ra, rb[2];
)?;
```

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.
It's often useful to define functions of registers as well as their inverses, the `#[invert]`
macro automates much of this process.

```rust
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)
use qip::prelude::*;
use std::num::NonZeroUsize;
use qip_macros::*;
use qip::inverter::Invertable;

// Make gamma and its inverse: gamma_inv
#[invert(gamma_inv)]
fn gamma<B>(b: &mut B, ra: B::Register, rb: B::Register) -> CircuitResult<(B::Register, B::Register)>
where B: AdvancedCircuitBuilder<f64> + Invertable<SimilarBuilder=B>
{
let (ra, rb) = b.toffoli(ra, rb)?;
let (rb, ra) = b.toffoli(rb, ra)?;
Ok((ra, rb))
}

wrap_fn!(rz_op(theta: f64), rz, r);
invert_fn!(inv_rz_op(theta: f64), rz_op);
let n = NonZeroUsize::new(3).unwrap();
let mut b = LocalBuilder::default();
let ra = b.register(n);
let rb = b.register(n);

let r = program!(&mut b, r;
rz_op(3.141) r;
inv_rz_op(3.141) r;
let (ra, rb) = program!(&mut b; ra, rb;
gamma ra[0..2], ra[2];
gamma_inv ra[0..2], ra[2];
)?;
```

Generics can be used by substituting the usual angle brackets for square.
To invert functions with additional arguments, we must list the non-register arguments.

```rust
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())
use qip::prelude::*;
use std::num::NonZeroUsize;
use qip_macros::*;
use qip::inverter::Invertable;

// Make gamma and its inverse: gamma_inv
#[invert(gamma_inv, skip)]
fn gamma<B>(b: &mut B, skip: bool, ra: B::Register, rb: B::Register) -> CircuitResult<(B::Register, B::Register)>
where B: AdvancedCircuitBuilder<f64> + Invertable<SimilarBuilder=B>
{
let (ra, rb) = b.toffoli(ra, rb)?;
let (rb, ra) = if skip {
b.toffoli(rb, ra)?
} else {
(rb, ra)
};
Ok((ra, rb))
}

wrap_fn!(rz_op[T: Into<f64>](theta: T), rz, r);
invert_fn!(inv_rz_op[T: Into<f64>](theta: T), rz_op);
let n = NonZeroUsize::new(3).unwrap();
let mut b = LocalBuilder::default();
let ra = b.register(n);
let rb = b.register(n);

let r = program!(&mut b, r;
rz_op(3.141_f32) r;
inv_rz_op(3.141_f32) r;
let (ra, rb) = program!(&mut b; ra, rb;
gamma(true) ra[0..2], ra[2];
gamma_inv(true) ra[0..2], ra[2];
)?;
```

Loading

0 comments on commit f9940ca

Please sign in to comment.