Quantum Computing library leveraging graph building to build efficient quantum circuit
simulations.
Rust is a great language for quantum computing with gate models because the borrow checker
is very similar to the No-cloning theorem.
See all the examples in the examples directory 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.
use qip::prelude::*;
use std::num::NonZeroUsize;
# fn main() -> CircuitResult<()> {
let mut b = LocalBuilder::<f64>::default();
let n = NonZeroUsize::new(3).unwrap();
let q = b.qubit(); let ra = b.register(n);
let rb = b.register(n);
let q = b.h(q);
let mut cb = b.condition_with(q);
let (ra, rb) = cb.swap(ra, rb)?;
let q = cb.dissolve();
let q = b.h(q);
let (q, m_handle) = b.measure(q);
let (_, measured) = b.calculate_state_with_init([(&ra, 0b000), (&rb, 0b001)]);
let (result, p) = measured.get_measurement(m_handle);
println!("Measured: {:?} (with chance {:?})", result, p);
# Ok(())
# }
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.
use qip::prelude::*;
use std::num::NonZeroUsize;
# #[cfg(feature = "macros")]
use qip_macros::program;
# #[cfg(feature = "macros")]
# fn main() -> CircuitResult<()> {
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 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 ra[0..2], ra[2];
gamma [ra[0], rb[0]], ra[2];
gamma ra[0], [rb[0], ra[2]];
control gamma rb, ra[0..2], ra[2];
control(0b110) gamma rb, ra[0..2], ra[2];
)?;
let r = b.merge_two_registers(ra, rb);
# Ok(())
# }
# #[cfg(not(feature = "macros"))]
# fn main() {}
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
use qip::prelude::*;
use std::num::NonZeroUsize;
# #[cfg(feature = "macros")]
use qip_macros::program;
# #[cfg(feature = "macros")]
# fn main() -> CircuitResult<()> {
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))
}
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_two_registers(ra, rb);
# Ok(())
# }
# #[cfg(not(feature = "macros"))]
# fn main() {}
The Invert Macro
It's often useful to define functions of registers as well as their inverses, the #[invert]
macro automates much of this process.
use qip::prelude::*;
use std::num::NonZeroUsize;
# #[cfg(feature = "macros")]
use qip_macros::*;
# #[cfg(feature = "macros")]
# fn main() -> CircuitResult<()> {
use qip::inverter::Invertable;
#[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))
}
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 ra[0..2], ra[2];
gamma_inv ra[0..2], ra[2];
)?;
let r = b.merge_two_registers(ra, rb);
# Ok(())
# }
# #[cfg(not(feature = "macros"))]
# fn main() {}
To invert functions with additional arguments, we must list the non-register arguments.
use qip::prelude::*;
use std::num::NonZeroUsize;
# #[cfg(feature = "macros")]
use qip_macros::*;
# #[cfg(feature = "macros")]
# fn main() -> CircuitResult<()> {
use qip::inverter::Invertable;
#[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))
}
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_inv(true) ra[0..2], ra[2];
)?;
let r = b.merge_two_registers(ra, rb);
# Ok(())
# }
# #[cfg(not(feature = "macros"))]
# fn main() {}