Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement some standard gates in rust #12507

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
fb70814
Add infrastructure for gates, instruction, and operations in Rust
mtreinish May 3, 2024
ad3e3c5
Merge branch 'main' into gates-in-rust
mtreinish May 25, 2024
37c0780
Fix Python->Rust Param conversion
mtreinish May 25, 2024
a6e69ba
Fix qasm3 exporter for std gates without stdgates.inc
mtreinish May 25, 2024
4e34642
Fix base scheduler analysis pass duration setting
mtreinish May 25, 2024
0edcfb0
Fix python lint
mtreinish May 25, 2024
f896512
Fix last failing qasm3 test for std gates without stdgates.inc
mtreinish May 25, 2024
046737f
Remove superfluous comment
mtreinish May 25, 2024
5c5b90f
Cache imported classes with GILOnceCell
mtreinish May 26, 2024
7329399
Remove unused python variables
mtreinish May 26, 2024
ae64fd7
Add missing file
mtreinish May 26, 2024
76599b2
Update QuantumCircuit gate methods to bypass Python object
mtreinish May 26, 2024
14b7133
Deduplicate gate matrix definitions
mtreinish May 26, 2024
0863830
Fix lint
mtreinish May 26, 2024
c4cda8d
Attempt to fix qasm3 test failure
mtreinish May 26, 2024
ffe04e5
Merge branch 'main' into gates-in-rust
mtreinish May 28, 2024
e9bb053
Add compile time option to cache py gate returns for rust std gates
mtreinish May 28, 2024
dfb02de
Merge branch 'main' into gates-in-rust
mtreinish May 28, 2024
0980d8d
Add num_nonlocal_gates implementation in rust
mtreinish May 29, 2024
dc9e8f0
Merge remote-tracking branch 'origin/main' into gates-in-rust
mtreinish May 29, 2024
b35bdbd
Performance tuning circuit construction
mtreinish May 30, 2024
3ea95de
Add back validation of parameters on gate methods
mtreinish May 31, 2024
2f81bde
Skip validation on gate creation from rust
mtreinish May 31, 2024
725f226
Offload operation copying to rust
mtreinish Jun 3, 2024
3ec3d3e
Merge remote-tracking branch 'origin/main' into gates-in-rust
mtreinish Jun 3, 2024
ed42276
Fix lint
mtreinish Jun 3, 2024
8017ca6
Perform deepcopy in rust
mtreinish Jun 3, 2024
9e21116
Fix QuantumCircuit.compose() performance regression
mtreinish Jun 3, 2024
29f278f
Fix map_ops test case with no caching case
mtreinish Jun 3, 2024
a7061d5
Fix typos in docs
mtreinish Jun 4, 2024
42d5a48
Shrink memory usage for extra mutable instruction state
mtreinish Jun 4, 2024
1ac5d4a
Remove Option<> from params field in CircuitInstruction
mtreinish Jun 4, 2024
951dec2
Merge branch 'main' into gates-in-rust
mtreinish Jun 4, 2024
0398e6a
Eagerly construct rust python wrappers in .append()
mtreinish Jun 4, 2024
8065184
Simplify code around handling python errors in rust
mtreinish Jun 4, 2024
39be17b
Revert "Skip validation on gate creation from rust"
mtreinish Jun 5, 2024
142d71b
Temporarily use git for qasm3 import
mtreinish Jun 5, 2024
39f1358
Fix lint
mtreinish Jun 5, 2024
5139411
Fix lint for real (we really need to use a py312 compatible version o…
mtreinish Jun 5, 2024
0d59bd4
Fix test failure caused by incorrect lint fix
mtreinish Jun 5, 2024
cfcc3d6
Relax trait-method typing requirements
jakelishman Jun 6, 2024
8cfa4d0
Encapsulate `GILOnceCell` initialisers to local logic
jakelishman Jun 6, 2024
1e3c064
Simplify Interface for building circuit of standard gates in rust
mtreinish Jun 6, 2024
faac655
Simplify complex64 creation in gate_matrix.rs
mtreinish Jun 6, 2024
c9ac618
Simplify initialization of array of elements that are not Copy (#28)
jlapeyre Jun 6, 2024
d7f3c66
This implements some gates in rust and give ergonomic improvements.
jlapeyre Jun 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ jobs:
python -m pip install -U -r requirements.txt -c constraints.txt
python -m pip install -U -r requirements-dev.txt -c constraints.txt
python -m pip install -c constraints.txt -e .
if: matrix.python-version == '3.10'
env:
QISKIT_NO_CACHE_GATES: 1
- name: 'Install dependencies'
run: |
python -m pip install -U -r requirements.txt -c constraints.txt
python -m pip install -U -r requirements-dev.txt -c constraints.txt
python -m pip install -c constraints.txt -e .
if: matrix.python-version == '3.12'
- name: 'Install optionals'
run: |
python -m pip install -r requirements-optional.txt -c constraints.txt
Expand Down
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,18 @@ Note that in order to run `python setup.py ...` commands you need have build
dependency packages installed in your environment, which are listed in the
`pyproject.toml` file under the `[build-system]` section.

### Compile time options

When building qiskit from source there are options available to control how
Qiskit is build. Right now the only option is if you set the environment
variable `QISKIT_NO_CACHE_GATES=1` this will disable runtime caching of
Python gate objects when accessing them from a `QuantumCircuit` or `DAGCircuit`.
This makes a tradeoff between runtime performance for Python access and memory
overhead. Caching gates will result in better runtime for users of Python at
the cost of increased memory consumption. If you're working with any custom
transpiler passes written in python or are otherwise using a workflow that
repeatedly accesses the `operation` attribute of a `CircuitInstruction` or `op`
attribute of `DAGOpNode` enabling caching is recommended.

## Issues and pull requests

Expand Down
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ license = "Apache-2.0"
[workspace.dependencies]
indexmap.version = "2.2.6"
hashbrown.version = "0.14.0"
num-complex = "0.4"
ndarray = "^0.15.6"
numpy = "0.21.0"
smallvec = "1.13"

# Most of the crates don't need the feature `extension-module`, since only `qiskit-pyext` builds an
# actual C extension (the feature disables linking in `libpython`, which is forbidden in Python
# distributions). We only activate that feature when building the C extension module; we still need
Expand Down
8 changes: 4 additions & 4 deletions crates/accelerate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,29 @@ doctest = false

[dependencies]
rayon = "1.10"
numpy = "0.21.0"
numpy.workspace = true
rand = "0.8"
rand_pcg = "0.3"
rand_distr = "0.4.3"
ahash = "0.8.11"
num-traits = "0.2"
num-complex = "0.4"
num-complex.workspace = true
num-bigint = "0.4"
rustworkx-core = "0.14"
faer = "0.19.0"
itertools = "0.13.0"
qiskit-circuit.workspace = true

[dependencies.smallvec]
version = "1.13"
workspace = true
features = ["union"]

[dependencies.pyo3]
workspace = true
features = ["hashbrown", "indexmap", "num-complex", "num-bigint", "smallvec"]

[dependencies.ndarray]
version = "^0.15.6"
workspace = true
features = ["rayon", "approx-0_5"]

[dependencies.approx]
Expand Down
5 changes: 1 addition & 4 deletions crates/accelerate/src/convert_2q_block_matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ use numpy::ndarray::{aview2, Array2, ArrayView2};
use numpy::{IntoPyArray, PyArray2, PyReadonlyArray2};
use smallvec::SmallVec;

static ONE_QUBIT_IDENTITY: [[Complex64; 2]; 2] = [
[Complex64::new(1., 0.), Complex64::new(0., 0.)],
[Complex64::new(0., 0.), Complex64::new(1., 0.)],
];
use qiskit_circuit::gate_matrix::ONE_QUBIT_IDENTITY;

/// Return the matrix Operator resulting from a block of Instructions.
#[pyfunction]
Expand Down
14 changes: 9 additions & 5 deletions crates/accelerate/src/euler_one_qubit_decomposer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ use ndarray::prelude::*;
use numpy::PyReadonlyArray2;
use pyo3::pybacked::PyBackedStr;

use qiskit_circuit::util::M_IM;
use qiskit_circuit::SliceOrInt;

// Macro c64!
use qiskit_circuit::c64;

pub const ANGLE_ZERO_EPSILON: f64 = 1e-12;

#[pyclass(module = "qiskit._accelerate.euler_one_qubit_decomposer")]
Expand Down Expand Up @@ -855,16 +859,16 @@ pub fn params_xyx(unitary: PyReadonlyArray2<Complex64>) -> [f64; 4] {

fn params_xzx_inner(umat: ArrayView2<Complex64>) -> [f64; 4] {
let det = det_one_qubit(umat);
let phase = (Complex64::new(0., -1.) * det.ln()).re / 2.;
let phase = (M_IM * det.ln()).re / 2.;
let sqrt_det = det.sqrt();
let mat_zyz = arr2(&[
[
Complex64::new((umat[[0, 0]] / sqrt_det).re, (umat[[1, 0]] / sqrt_det).im),
Complex64::new((umat[[1, 0]] / sqrt_det).re, (umat[[0, 0]] / sqrt_det).im),
c64!((umat[[0, 0]] / sqrt_det).re, (umat[[1, 0]] / sqrt_det).im),
c64!((umat[[1, 0]] / sqrt_det).re, (umat[[0, 0]] / sqrt_det).im),
],
[
Complex64::new(-(umat[[1, 0]] / sqrt_det).re, (umat[[0, 0]] / sqrt_det).im),
Complex64::new((umat[[0, 0]] / sqrt_det).re, -(umat[[1, 0]] / sqrt_det).im),
c64!(-(umat[[1, 0]] / sqrt_det).re, (umat[[0, 0]] / sqrt_det).im),
c64!((umat[[0, 0]] / sqrt_det).re, -(umat[[1, 0]] / sqrt_det).im),
],
]);
let [theta, phi, lam, phase_zxz] = params_zxz_inner(mat_zyz.view());
Expand Down
9 changes: 3 additions & 6 deletions crates/accelerate/src/isometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ use itertools::Itertools;
use ndarray::prelude::*;
use numpy::{IntoPyArray, PyReadonlyArray1, PyReadonlyArray2};

use crate::two_qubit_decompose::ONE_QUBIT_IDENTITY;
use qiskit_circuit::gate_matrix::ONE_QUBIT_IDENTITY;
use qiskit_circuit::util::ZERO;

/// Find special unitary matrix that maps [c0,c1] to [r,0] or [0,r] if basis_state=0 or
/// basis_state=1 respectively
Expand Down Expand Up @@ -315,11 +316,7 @@ pub fn merge_ucgate_and_diag(
.enumerate()
.map(|(i, raw_gate)| {
let gate = raw_gate.as_array();
let res = aview2(&[
[diag[2 * i], Complex64::new(0., 0.)],
[Complex64::new(0., 0.), diag[2 * i + 1]],
])
.dot(&gate);
let res = aview2(&[[diag[2 * i], ZERO], [ZERO, diag[2 * i + 1]]]).dot(&gate);
res.into_pyarray_bound(py).into()
})
.collect()
Expand Down
7 changes: 5 additions & 2 deletions crates/accelerate/src/pauli_exp_val.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
use rayon::prelude::*;

// Macro c64!
use qiskit_circuit::c64;

use crate::getenv_use_multiple_threads;

const PARALLEL_THRESHOLD: usize = 19;
Expand Down Expand Up @@ -88,15 +91,15 @@ pub fn expval_pauli_with_x(
let index_0 = ((i << 1) & mask_u) | (i & mask_l);
let index_1 = index_0 ^ x_mask;
let val_0 = (phase
* Complex64::new(
* c64!(
data_arr[index_1].re * data_arr[index_0].re
+ data_arr[index_1].im * data_arr[index_0].im,
data_arr[index_1].im * data_arr[index_0].re
- data_arr[index_1].re * data_arr[index_0].im,
))
.re;
let val_1 = (phase
* Complex64::new(
* c64!(
data_arr[index_0].re * data_arr[index_1].re
+ data_arr[index_0].im * data_arr[index_1].im,
data_arr[index_0].im * data_arr[index_1].re
Expand Down
5 changes: 4 additions & 1 deletion crates/accelerate/src/sampled_exp_val.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ use numpy::PyReadonlyArray1;
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;

// Macro c64!
use qiskit_circuit::c64;

use crate::pauli_exp_val::fast_sum;

const OPER_TABLE_SIZE: usize = (b'Z' as usize) + 1;
Expand Down Expand Up @@ -81,7 +84,7 @@ pub fn sampled_expval_complex(
let out: Complex64 = oper_strs
.into_iter()
.enumerate()
.map(|(idx, string)| coeff_arr[idx] * Complex64::new(bitstring_expval(&dist, string), 0.))
.map(|(idx, string)| coeff_arr[idx] * c64!(bitstring_expval(&dist, string), 0))
.sum();
Ok(out.re)
}
Expand Down
35 changes: 20 additions & 15 deletions crates/accelerate/src/sparse_pauli_op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,14 @@ use numpy::{PyArray1, PyArray2, PyReadonlyArray1, PyReadonlyArray2, PyUntypedArr
use hashbrown::HashMap;
use ndarray::{s, Array1, Array2, ArrayView1, ArrayView2, Axis};
use num_complex::Complex64;
// For method is_zero
use num_traits::Zero;
use rayon::prelude::*;

use qiskit_circuit::util::{ONE, ZERO};
// Macro c64!
use qiskit_circuit::c64;

use crate::rayon_ext::*;

/// Find the unique elements of an array.
Expand Down Expand Up @@ -257,9 +262,9 @@ impl<'py> ZXPaulisView<'py> {
let ys = (xs & zs).count_ones();
match (phase as u32 + ys) % 4 {
0 => coeff,
1 => Complex64::new(coeff.im, -coeff.re),
2 => Complex64::new(-coeff.re, -coeff.im),
3 => Complex64::new(-coeff.im, coeff.re),
1 => c64!(coeff.im, -coeff.re),
2 => c64!(-coeff.re, -coeff.im),
3 => c64!(-coeff.im, coeff.re),
_ => unreachable!(),
}
})
Expand Down Expand Up @@ -311,10 +316,10 @@ impl MatrixCompressedPaulis {
.zip(self.z_like.drain(..))
.zip(self.coeffs.drain(..))
{
*hash_table.entry(key).or_insert(Complex64::new(0.0, 0.0)) += coeff;
*hash_table.entry(key).or_insert(ZERO) += coeff;
}
for ((x, z), coeff) in hash_table {
if coeff == Complex64::new(0.0, 0.0) {
if coeff == ZERO {
continue;
}
self.x_like.push(x);
Expand Down Expand Up @@ -347,7 +352,7 @@ pub fn decompose_dense(
let mut coeffs = vec![];
if num_qubits > 0 {
decompose_dense_inner(
Complex64::new(1.0, 0.0),
ONE,
num_qubits,
&[],
operator.as_array(),
Expand Down Expand Up @@ -485,7 +490,7 @@ fn decompose_dense_inner(
);
recurse_if_nonzero(
Pauli::Y,
0.5 * Complex64::i() * factor,
c64!(0, 0.5) * factor,
&block.slice(s![..mid, mid..]) - &block.slice(s![mid.., ..mid]),
);
recurse_if_nonzero(
Expand Down Expand Up @@ -532,7 +537,7 @@ fn to_matrix_dense_inner(paulis: &MatrixCompressedPaulis, parallel: bool) -> Vec
// Doing the initialisation here means that when we're in parallel contexts, we do the
// zeroing across the whole threadpool. This also seems to give a speed-up in serial
// contexts, but I don't understand that. ---Jake
row.fill(Complex64::new(0.0, 0.0));
row.fill(ZERO);
for ((&x_like, &z_like), &coeff) in paulis
.x_like
.iter()
Expand Down Expand Up @@ -667,7 +672,7 @@ macro_rules! impl_to_matrix_sparse {
((i_row as $uint_ty) ^ (paulis.x_like[a] as $uint_ty))
.cmp(&((i_row as $uint_ty) ^ (paulis.x_like[b] as $uint_ty)))
});
let mut running = Complex64::new(0.0, 0.0);
let mut running = ZERO;
let mut prev_index = i_row ^ (paulis.x_like[order[0]] as usize);
for (x_like, z_like, coeff) in order
.iter()
Expand Down Expand Up @@ -748,7 +753,7 @@ macro_rules! impl_to_matrix_sparse {
(i_row as $uint_ty ^ paulis.x_like[a] as $uint_ty)
.cmp(&(i_row as $uint_ty ^ paulis.x_like[b] as $uint_ty))
});
let mut running = Complex64::new(0.0, 0.0);
let mut running = ZERO;
let mut prev_index = i_row ^ (paulis.x_like[order[0]] as usize);
for (x_like, z_like, coeff) in order
.iter()
Expand Down Expand Up @@ -844,11 +849,11 @@ mod tests {
// Deliberately using multiples of small powers of two so the floating-point addition
// of them is associative.
coeffs: vec![
Complex64::new(0.25, 0.5),
Complex64::new(0.125, 0.25),
Complex64::new(0.375, 0.125),
Complex64::new(-0.375, 0.0625),
Complex64::new(-0.5, -0.25),
c64!(0.25, 0.5),
c64!(0.125, 0.25),
c64!(0.375, 0.125),
c64!(-0.375, 0.0625),
c64!(-0.5, -0.25),
],
}
}
Expand Down
Loading