Skip to content

Commit

Permalink
Merge pull request #32 from a-barlow/release-0.3.0
Browse files Browse the repository at this point in the history
Release 0.3.0
  • Loading branch information
a-barlow committed Nov 17, 2023
2 parents 990e300 + 5c6ba49 commit 349c649
Show file tree
Hide file tree
Showing 16 changed files with 1,206 additions and 969 deletions.
76 changes: 76 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,82 @@

This file logs the versions of quantr.

## 0.3.0 - Interface refresh

This major update overhauls the structure of quantr, and the naming of
many methods. The aim is to increase simplicity in using the library,
in turn producing more readable and efficient code. The re-naming of
methods is meant to be more inkeeping with the Rust standard library,
that is `to` represents a pass by reference, while `into` moves the value
into the method.

Moreover, some examples have been added showcasing custom functions and
printing the circuits in a variety of ways.

Breaking Changes:

- The function `ProductState::new` now returns `Result<ProductState,
QuantrError>`. An error is returned if an empty slice is given as an
argument.
- Renamed the fields of `Complex` from `real` and `imaginary` to `re`
and `im` respectively.
- Removed `Circuit::simulate_with_register`. This is replaced with
`Circuit::change_register` which can be called before simulation, to
change the default register of |00..0> that is applied during
simulating.
- Removed `Printer::flush` as it cannot be used due to borrowing rules.
- Renamed the enum `StandardGate` to `Gate`.
- The `complex_zero!` macro has been replaced with a `Complex<f64>`
constant `quantr::COMPLEX_ZERO`.
- Changed method names:
- `Qubit::join` -> `Qubit::kronecker_prod`
- `Qubit::as_state` -> `Qubit::into_state`
- `ProductState::join` -> `ProductState::kronecker_prod`
- `ProductState::as_string` -> `ProductState::to_string`
- `SuperPosition::as_hash_map` -> `SuperPosition::to_hash_map`
- `ProductState::to_super_position` ->
`ProductState::into_super_position`
- The field of `ProductState` called `state` -> `qubits`.
- Re-structured access of structs and module paths. Now, every struct is
accessed through `quantr::...` except for those that control states,
which are accessed through the module `quantr::states::...`.
- Changed the input type of two methods in `Circuit`:
- `add_gates(Vec<Gate>)` -> `add_gates(&[Gate])`
- `add_repeating_gate(Gate, Vec<usize>)` ->
`add_repeating_gate(Gate, &[usize])`.
- `Circuit` methods that add gates now output a mutable reference to the
mutated circuit. This allows for a 'chain of method calls' to be made.

Features:

- The `QuantrError` struct has been made public for the user (this was
available in versions < 0.2.0). This allows for succinct error handling
with `?` when creating circuits when the main function is allowed to
return `Result<(), QuantrError>`.

Examples:

All examples print the circuit to the console, along with data of the
resulting superpositions from simulating the circuit.

- A custom function implementing a Quantum Fourier Transform in
`examples/qft.rs` which is designed to be used in other circuits. This
also showcases the idea of running a circuit within a custom function,
in a way sub-divding components of the larger circuit into smaller
ones.
- The custom function itself, implementing a CCC-not gate, is shown in
`examples/custom_gates.rs`. Within this function, the product states
are directly manipulated to produce a CCC-not gate. This example also
prints the progress of the simulation.

Tests:

- All tests and examples have been updated to reflect this major change.
Now answers had to be changed, only the interfaces with quantr.
- Boundary test to catch if a control node is greater than the size of
the circuit.
- The qft example has been added as an external test.

## 0.2.5 - Complex exponential, ASCII warnings and gates

Features:
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "quantr"
version = "0.2.5"
version = "0.3.0"
edition = "2021"
license = "EUPL-1.2"
readme = "README.md"
Expand All @@ -10,6 +10,7 @@ authors = ["Andrew Barlow <a.barlow.dev@gmail.com>"]
repository = "https://github.com/a-barlow/quantr"
homepage = "https://a-barlow.github.io/quantr-book"
description = "Readily create, simulate and print quantum circuits."
publish = false

[dependencies]
rand = "0.8.5"
114 changes: 69 additions & 45 deletions QUICK_START.md
Expand Up @@ -18,7 +18,8 @@ from the root directory with `cargo run --example grovers`.

---

Open the console, and create a new Rust project by running
Open the console, and create a new Rust project (known as a cargo
package) by running

``` console
cargo new grovers_example
Expand All @@ -28,19 +29,18 @@ This will create a new directory called `grovers_example` containing
the necessary files for a Rust project. Enter this directory.

Add the latest version of quantr as a dependency by running `cargo add
quantr` on the console. This should add quantr below `[dependecies]` in
quantr` on the console. This should add quantr below `[d ependecies]` in
you `Cargo.toml` file. Then, run `cargo build`. This will download the
quantr crate from [crates.io](https://crates.io/) and make it
accessible to your IDE.
accessible for your project.

Once quantr has been installed, open `src/main.rs`. This is where the
subsequent code will be written to implement Grover's algorithm.

Add the following lines to the top of `main.rs`, and before `fn main()`:

```rust, ignore
use quantr::circuit::{Circuit, StandardGate, printer::Printer, state::ProductState, Measurement};
use std::collections::HashMap;
use quantr::{Circuit, Gate, Measurement, Printer};
```

These lines import the structs and enums that will be used throughout
Expand All @@ -56,16 +56,16 @@ let mut circuit: Circuit = Circuit::new(3).unwrap();

Grover's algorithm requires that the starting state is in a
superposition of all basis states with equal amplitudes. This can be
achieved by adding three Hadamard gates to each wire:
achieved by adding a Hadamard gate to each wire, that is to wires 0, 1
and 2:

```rust,ignore
circuit.add_repeating_gates(StandardGate::H, vec![0, 1, 2]).unwrap();
circuit.add_repeating_gate(Gate::H, &[0, 1, 2]).unwrap();
```

The `vec!` macro is a quick way to create a vector. The `.unwrap()`
forces the program to quit if there is an error in adding these gates,
such as adding a gate to a non-existent wire. There are no errors in
this example, so the method `.unwrap()` does nothing.
The `.unwrap()` forces the program to quit if there is an error in
adding these gates, such as adding a gate to a non-existent wire. There
are no errors in this example, so the method `.unwrap()` does nothing.

Let's visualise the circuit that has been built so far (or at any other
time throughout this guide) by adding the following:
Expand All @@ -76,7 +76,7 @@ printer.print_diagram();
```

This will print a circuit diagram to the console. This can be seen by
running the program, by entering `cargo run` while in the directory that
running the program by entering `cargo run` while in the directory that
cargo built.

The next step in the algorithm requires the oracle to be defined. This
Expand All @@ -90,29 +90,29 @@ wire, with its control node placed on the 1st wire; the first and second
wire from top to bottom in a circuit diagram respectively:

```rust,ignore
circuit.add_gate(StandardGate::CZ(1), 0).unwrap();
circuit.add_gate(Gate::CZ(1), 0).unwrap();
```

The second argument of `circuit.add_gate` specifies which wire is the
target, and the field of the variant `StandardGate::CZ` specifies the
target, and the field of the variant `Gate::CZ` specifies the
control wire.

With the solution states marked, these amplitudes are amplified so
that these states are more likely to be measured than other non-solution
that solution states are more likely to be measured than other non-solution
states. This can be achieved by adding:

```rust,ignore
circuit.add_repeating_gate(StandardGate::H, vec![0, 1, 2]).unwrap();
circuit.add_repeating_gate(StandardGate::X, vec![0, 1, 2]).unwrap();
circuit.add_repeating_gate(Gate::H, vec![0, 1, 2]).unwrap()
.add_repeating_gate(Gate::X, vec![0, 1, 2]).unwrap();
// CC-Z gate
circuit.add_gate(StandardGate::H, 2).unwrap();
circuit.add_gate(StandardGate::Toffoli(0, 1), 2).unwrap();
circuit.add_gate(StandardGate::H, 2).unwrap();
circuit.add_gate(Gate::H, 2).unwrap()
.add_gate(Gate::Toffoli(0, 1), 2).unwrap()
.add_gate(Gate::H, 2).unwrap();
circuit.add_repeating_gate(StandardGate::X, vec![0, 1, 2]).unwrap();
circuit.add_repeating_gate(StandardGate::H, vec![0, 1, 2]).unwrap();
```
circuit.add_repeating_gate(Gate::X, vec![0, 1, 2]).unwrap()
.add_repeating_gate(Gate::H, vec![0, 1, 2]).unwrap();
```

This completes the construction of Grover's algorithm. To make sure that
the gates are placed correctly, run the printing code as shown before.
Expand All @@ -131,15 +131,15 @@ observed states. This bin count can be found and printed with
if let Measurement::Observable(bin_count) = circuit.repeat_measurement(500).unwrap() {
// bin_count is a HashMap<ProductState, usize>
for (state, count) in bin_count {
println!("{} : {}", state.as_str(), count);
println!("{} : {}", state.to_string(), count);
}
}
```

The above prints the number of times each state was observed over 500
measurements. In this situation, the amplitude amplification results in
a superposition of two states: |110> and |111>.

Note that the above code is explicit in showing that the measurements
are *physically possible*. This is to distinguish from other data that
can be taken from circuit, such as the resulting superposition itself.
Expand All @@ -150,50 +150,74 @@ useful to view this "theoretical" superposition:
if let Measurement::NonObservable(output_super_position) = circuit.get_superposition().unwrap()
{
for (state, amplitude) in super_position.into_iter() {
println!("{} : {}", state.as_string(), amplitude);
println!("{} : {}", state.to_string(), amplitude);
}
}
```

This completes the construction and measurement of a three qubit
Grover's circuit. Other functions (which include examples in their
documentation) can add gates in other ways.
documentation) can add gates in other ways. Moreover, custom gates can
be built, which can be seen in examples `examples/qft.rs` and
`examples/custom_gate.rs`.

To improve the readability of this code, the numerous `unwrap()` calls
can be removed while the main function declaration can be edited like
so:

```rust,ignore
...
use quantr::QuantrError;
fn main() -> Result<(), QuantrError> {
...;
Ok(())
}
```

A `Ok(())` is returned on the last line; signalling that the program has
exited without errors. Then, effectively all unwrap methods called after
appending gates can be replaced with a `?`. However, an argument for
leaving the unwraps explicit is that if a function has appended a gate
resulting in an error, such as adding a gate out width the circuit's
size, then at runtime the program will panic and return a compiler
message explicitly directing the user to the line in question. Even
though the many unwraps may be unpleasant, it can be beneficial for
debugging while creating the circuit.

The following is the completed code. This can also be found in
`examples/grovers.rs`, and ran with `cargo run --example grovers` from
the root directory.

```rust,ignore
use quantr::circuit::{printer::Printer, Circuit, Measurement, StandardGate};
```rust
use quantr::{Circuit, Gate, Measurement, Printer};

fn main() {
let mut circuit = Circuit::new(3).unwrap();

// Kick state into superposition of equal weights
circuit
.add_repeating_gate(StandardGate::H, vec![0, 1, 2])
.add_repeating_gate(Gate::H, &[0, 1, 2])
.unwrap();

// Oracle
circuit.add_gate(StandardGate::CZ(1), 0).unwrap();
circuit.add_gate(Gate::CZ(1), 0).unwrap();

// Amplitude amplification
circuit
.add_repeating_gate(StandardGate::H, vec![0, 1, 2])
.unwrap();
circuit
.add_repeating_gate(StandardGate::X, vec![0, 1, 2])
.add_repeating_gate(Gate::H, &[0, 1, 2])
.unwrap()
.add_repeating_gate(Gate::X, &[0, 1, 2])
.unwrap();

circuit.add_gate(StandardGate::H, 2).unwrap();
circuit.add_gate(StandardGate::Toffoli(0, 1), 2).unwrap();
circuit.add_gate(StandardGate::H, 2).unwrap();
circuit.add_gate(Gate::H, 2).unwrap()
.add_gate(Gate::Toffoli(0, 1), 2).unwrap()
.add_gate(Gate::H, 2).unwrap();

circuit
.add_repeating_gate(StandardGate::X, vec![0, 1, 2])
.unwrap();
circuit
.add_repeating_gate(StandardGate::H, vec![0, 1, 2])
.add_repeating_gate(Gate::X, &[0, 1, 2])
.unwrap()
.add_repeating_gate(Gate::H, &[0, 1, 2])
.unwrap();

// Prints the circuit in UTF-8
Expand All @@ -209,9 +233,9 @@ fn main() {
// Displays bin count of the resulting 500 repeat measurements of
// superpositions. bin_count is a HashMap<ProductState, usize>.
if let Measurement::Observable(bin_count) = circuit.repeat_measurement(500).unwrap() {
println!("[Observable] Bin count of observed states.");
println!("\n[Observable] Bin count of observed states.");
for (state, count) in bin_count {
println!("|{}> observed {} times", state.as_string(), count);
println!("|{}> observed {} times", state.to_string(), count);
}
}

Expand All @@ -220,7 +244,7 @@ fn main() {
{
println!("\n[Non-Observable] The amplitudes of each state in the final superposition.");
for (state, amplitude) in output_super_position.into_iter() {
println!("|{}> : {}", state.as_string(), amplitude);
println!("|{}> : {}", state.to_string(), amplitude);
}
}
}
Expand Down
31 changes: 14 additions & 17 deletions README.md
@@ -1,11 +1,12 @@
# 🚧 quantr 🚧
# quantr

[![Static
Badge](https://img.shields.io/badge/Version%20-%201.73.0%20-%20%20(185%2C71%2C0)?style=fat&logo=rust&color=%23B94700)](https://releases.rs/)
[![cargo
test](https://github.com/a-barlow/quantr/workflows/cargo%20test/badge.svg)](https://github.com/a-barlow/quantr/actions/workflows/rust.yml)
[![cargo test
(dev)](https://github.com/a-barlow/quantr/workflows/cargo%20test%20%28dev%29/badge.svg)](https://github.com/a-barlow/quantr/actions/workflows/rust_dev.yml)
[![Crates.io](https://img.shields.io/crates/v/quantr?style=flat-square&color=%23B94700)](https://crates.io/crates/quantr)
[![Static Badge](https://img.shields.io/badge/version%20-%201.74.0%20-%20white?style=flat-square&logo=rust&color=%23B94700)](https://releases.rs/)
[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/a-barlow/quantr/rust.yml?style=flat-square&label=tests&color=%2349881B)](https://github.com/a-barlow/quantr/actions/workflows/rust.yml)
[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/a-barlow/quantr/rust_dev.yml?style=flat-square&label=tests%20(dev)&color=%2349881B)](https://github.com/a-barlow/quantr/actions/workflows/rust_dev.yml)
![docs.rs](https://img.shields.io/docsrs/quantr?style=flat-square&color=%2349881B)
![Crates.io](https://img.shields.io/crates/d/quantr?style=flat-square&color=%23009250)
![Crates.io](https://img.shields.io/crates/l/quantr?style=flat-square&label=licence&color=%23009982)

> This crate is not production ready and so should **not** be considered
> stable, nor produce correct answers. It is still under heavy
Expand Down Expand Up @@ -50,19 +51,15 @@ implementation of Grover's algorithm.
An example of simulating and printing a two qubit circuit:

```rust
use quantr::circuit::{Circuit, StandardGate, printer::Printer,
Measurement::Observable};
use quantr::{Circuit, Gate, Printer, Measurement::Observable};

fn main() {

let mut quantum_circuit: Circuit = Circuit::new(2).unwrap();

quantum_circuit
.add_gates(vec![StandardGate::H, StandardGate::H])
.unwrap();
quantum_circuit
.add_gate(StandardGate::CNot(0), 1)
.unwrap();
.add_gates(&[Gate::H, Gate::H]).unwrap()
.add_gate(Gate::CNot(0), 1).unwrap();

let mut printer = Printer::new(&quantum_circuit);
printer.print_diagram();
Expand All @@ -83,7 +80,7 @@ fn main() {
if let Observable(bin_count) = quantum_circuit.repeat_measurement(500).unwrap() {
println!("[Observable] Bin count of observed states.");
for (state, count) in bin_count {
println!("|{}> observed {} times", state.as_string(), count);
println!("|{}> observed {} times", state.to_string(), count);
}
}

Expand Down Expand Up @@ -111,7 +108,7 @@ computational basis is defined as:
|b⟩ ──── ⟺ |a,b,c,⋯⟩ ≡ |a⟩⊗|b⟩⊗|c⟩⊗⋯
|c⟩ ────
⋮ ⋮
```
```

When defining a custom function that depends on the position of control
nodes to define gates (such as the CNot and Toffoli gates), it must be
Expand Down

0 comments on commit 349c649

Please sign in to comment.