-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Add Rust quantum volume function #13238
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
Merged
Merged
Changes from 6 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
c114618
Add Rust quantum volume function
mtreinish ced62a7
Adjust type hints on python function
mtreinish 50ca924
Add missing __future__ import
mtreinish 69d4cfa
Add comment on random unitary algorithm
mtreinish 32b4c1f
Reduce allocations random_unitaries
mtreinish bcb67a1
Merge branch 'main' into rust-qv
mtreinish d0daf81
Preallocate unitaries for serial path
mtreinish 9d8e4fe
Fix determinism and error handling of of qv function
mtreinish 06b0b0f
Mention the new function is multithreaded in docstring
mtreinish 42d539d
Merge branch 'main' into rust-qv
mtreinish 43510e1
Update qiskit/circuit/library/quantum_volume.py
mtreinish 37da7b2
Merge branch 'main' into rust-qv
Cryoris File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
185 changes: 185 additions & 0 deletions
185
crates/accelerate/src/circuit_library/quantum_volume.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| // This code is part of Qiskit. | ||
| // | ||
| // (C) Copyright IBM 2024 | ||
| // | ||
| // This code is licensed under the Apache License, Version 2.0. You may | ||
| // obtain a copy of this license in the LICENSE.txt file in the root directory | ||
| // of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. | ||
| // | ||
| // Any modifications or derivative works of this code must retain this | ||
| // copyright notice, and modified files need to carry a notice indicating | ||
| // that they have been altered from the originals. | ||
|
|
||
| use std::thread::available_parallelism; | ||
|
|
||
| use qiskit_circuit::imports::UNITARY_GATE; | ||
|
|
||
| use pyo3::prelude::*; | ||
|
|
||
| use crate::getenv_use_multiple_threads; | ||
| use faer_ext::{IntoFaerComplex, IntoNdarrayComplex}; | ||
| use ndarray::prelude::*; | ||
| use num_complex::Complex64; | ||
| use numpy::IntoPyArray; | ||
| use rand::prelude::*; | ||
| use rand_distr::StandardNormal; | ||
| use rand_pcg::Pcg64Mcg; | ||
| use rayon::prelude::*; | ||
|
|
||
| use qiskit_circuit::circuit_data::CircuitData; | ||
| use qiskit_circuit::operations::Param; | ||
| use qiskit_circuit::operations::PyInstruction; | ||
| use qiskit_circuit::packed_instruction::PackedOperation; | ||
| use qiskit_circuit::Qubit; | ||
| use smallvec::smallvec; | ||
|
|
||
| #[inline(always)] | ||
| fn random_complex(rng: &mut Pcg64Mcg) -> Complex64 { | ||
| Complex64::new(rng.sample(StandardNormal), rng.sample(StandardNormal)) | ||
| * std::f64::consts::FRAC_1_SQRT_2 | ||
| } | ||
|
|
||
| // This function's implementation was modeled off of the algorithm used in the | ||
| // `scipy.stats.unitary_group.rvs()` function defined here: | ||
| // | ||
| // https://github.com/scipy/scipy/blob/v1.14.1/scipy/stats/_multivariate.py#L4224-L4256 | ||
| #[inline] | ||
| fn random_unitaries(seed: u64, size: usize) -> impl Iterator<Item = Array2<Complex64>> { | ||
| let mut rng = Pcg64Mcg::seed_from_u64(seed); | ||
|
|
||
| (0..size).map(move |_| { | ||
| let raw_numbers: [[Complex64; 4]; 4] = [ | ||
| [ | ||
| random_complex(&mut rng), | ||
| random_complex(&mut rng), | ||
| random_complex(&mut rng), | ||
| random_complex(&mut rng), | ||
| ], | ||
| [ | ||
| random_complex(&mut rng), | ||
| random_complex(&mut rng), | ||
| random_complex(&mut rng), | ||
| random_complex(&mut rng), | ||
| ], | ||
| [ | ||
| random_complex(&mut rng), | ||
| random_complex(&mut rng), | ||
| random_complex(&mut rng), | ||
| random_complex(&mut rng), | ||
| ], | ||
| [ | ||
| random_complex(&mut rng), | ||
| random_complex(&mut rng), | ||
| random_complex(&mut rng), | ||
| random_complex(&mut rng), | ||
| ], | ||
| ]; | ||
|
|
||
| let qr = aview2(&raw_numbers).into_faer_complex().qr(); | ||
| let r = qr.compute_r(); | ||
| let diag: [Complex64; 4] = [ | ||
| r[(0, 0)].to_num_complex() / r[(0, 0)].abs(), | ||
| r[(1, 1)].to_num_complex() / r[(1, 1)].abs(), | ||
| r[(2, 2)].to_num_complex() / r[(2, 2)].abs(), | ||
| r[(3, 3)].to_num_complex() / r[(3, 3)].abs(), | ||
| ]; | ||
| let mut q = qr.compute_q().as_ref().into_ndarray_complex().to_owned(); | ||
| q.axis_iter_mut(Axis(0)).for_each(|mut row| { | ||
|
Cryoris marked this conversation as resolved.
|
||
| row.iter_mut() | ||
| .enumerate() | ||
| .for_each(|(index, val)| *val *= diag[index]) | ||
| }); | ||
| q | ||
| }) | ||
| } | ||
|
|
||
| #[pyfunction] | ||
| pub fn quantum_volume( | ||
| py: Python, | ||
| num_qubits: u32, | ||
| depth: usize, | ||
| seed: Option<u64>, | ||
| ) -> PyResult<CircuitData> { | ||
| let width = num_qubits as usize / 2; | ||
| let num_unitaries = width * depth; | ||
| let mut permutation: Vec<Qubit> = (0..num_qubits).map(Qubit).collect(); | ||
|
|
||
| let mut build_instruction = |(unitary_index, unitary_array): (usize, Array2<Complex64>), | ||
| rng: &mut Pcg64Mcg| { | ||
| let layer_index = unitary_index % width; | ||
| if layer_index == 0 { | ||
| permutation.shuffle(rng); | ||
| } | ||
| let unitary = unitary_array.into_pyarray_bound(py); | ||
| let unitary_gate = UNITARY_GATE | ||
| .get_bound(py) | ||
| .call1((unitary.clone(), py.None(), false)) | ||
| .unwrap(); | ||
| let instruction = PyInstruction { | ||
| qubits: 2, | ||
| clbits: 0, | ||
| params: 1, | ||
| op_name: "unitary".to_string(), | ||
| control_flow: false, | ||
| instruction: unitary_gate.unbind(), | ||
| }; | ||
| let qubit = layer_index * 2; | ||
| ( | ||
| PackedOperation::from_instruction(Box::new(instruction)), | ||
| smallvec![Param::Obj(unitary.unbind().into())], | ||
| vec![permutation[qubit], permutation[qubit + 1]], | ||
| vec![], | ||
| ) | ||
| }; | ||
|
|
||
| if getenv_use_multiple_threads() { | ||
| let mut per_thread = num_unitaries / available_parallelism().unwrap(); | ||
| if per_thread == 0 { | ||
| if num_unitaries > 10 { | ||
| per_thread = 10 | ||
| } else { | ||
| per_thread = num_unitaries | ||
| } | ||
| } | ||
|
|
||
| let mut outer_rng = match seed { | ||
| Some(seed) => Pcg64Mcg::seed_from_u64(seed), | ||
| None => Pcg64Mcg::from_entropy(), | ||
| }; | ||
| let seed_vec: Vec<u64> = outer_rng | ||
| .clone() | ||
| .sample_iter(&rand::distributions::Standard) | ||
| .take(num_unitaries) | ||
| .collect(); | ||
| let unitaries: Vec<Array2<Complex64>> = seed_vec | ||
| .into_par_iter() | ||
| .chunks(per_thread) | ||
| .flat_map_iter(|seeds| random_unitaries(seeds[0], seeds.len())) | ||
| .collect(); | ||
| CircuitData::from_packed_operations( | ||
| py, | ||
| num_qubits, | ||
| 0, | ||
| unitaries | ||
| .into_iter() | ||
| .enumerate() | ||
| .map(|x| build_instruction(x, &mut outer_rng)), | ||
| Param::Float(0.), | ||
| ) | ||
| } else { | ||
| let mut outer_rng = match seed { | ||
| Some(seed) => Pcg64Mcg::seed_from_u64(seed), | ||
| None => Pcg64Mcg::from_entropy(), | ||
| }; | ||
| let seed: u64 = outer_rng.sample(rand::distributions::Standard); | ||
| CircuitData::from_packed_operations( | ||
| py, | ||
| num_qubits, | ||
| 0, | ||
| random_unitaries(seed, num_unitaries) | ||
| .enumerate() | ||
| .map(|x| build_instruction(x, &mut outer_rng)), | ||
| Param::Float(0.), | ||
| ) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| --- | ||
| features_circuits: | ||
| - | | ||
| Added a new function :func:`.quantum_volume` for generating a quantum volume | ||
| :class:`.QuantumCircuit` object as defined in A. Cross et al. Validating quantum computers | ||
| using randomized model circuits, Phys. Rev. A 100, 032328 (2019) | ||
| `https://link.aps.org/doi/10.1103/PhysRevA.100.032328 <https://link.aps.org/doi/10.1103/PhysRevA.100.032328>`__. | ||
| This new function differs from the existing :class:`.QuantumVolume` class in that it returns | ||
| a :class:`.QuantumCircuit` object instead of building a subclass object. The second is | ||
| that this new function is multithreaded and implemented in rust so it generates the output | ||
| circuit ~10x faster than the :class:`.QuantumVolume` class. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.