-
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
Changes from 1 commit
c114618
ced62a7
50ca924
69d4cfa
32b4c1f
bcb67a1
d0daf81
9d8e4fe
06b0b0f
42d539d
43510e1
37da7b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| // 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::Normal; | ||
| 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] | ||
| fn random_unitaries(seed: u64, size: usize) -> impl Iterator<Item = Array2<Complex64>> { | ||
| let mut rng = Pcg64Mcg::seed_from_u64(seed); | ||
| let dist = Normal::new(0., 1.0).unwrap(); | ||
|
|
||
| (0..size).map(move |_| { | ||
| let mut z: Array2<Complex64> = Array2::from_shape_simple_fn((4, 4), || { | ||
| Complex64::new(dist.sample(&mut rng), dist.sample(&mut rng)) | ||
| }); | ||
| z.mapv_inplace(|x| x * std::f64::consts::FRAC_1_SQRT_2); | ||
| let qr = z.view().into_faer_complex().qr(); | ||
| let r = qr.compute_r().as_ref().into_ndarray_complex().to_owned(); | ||
| let mut d = r.into_diag(); | ||
| d.mapv_inplace(|d| d / d.norm()); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this re-compute the norm of
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does an in-place substitution on the diagonal
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am wondering if I should rewrite this using fixed size arrays or nalgebra's
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did this here: 32b4c1f the code is a lot more verbose now, but I think it's a bit more explicit and it'll be a bit more optimized (although the random unitary construction is not the bottleneck).
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! The bottleneck is the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, we are totally bottlenecked by the I'll do a quick test of serial mode dumping the unitaries to a vec first to see if that makes a difference performance wise (I don't expect it to), and will leave a comment here with the results.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| 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 *= d.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.), | ||
| ) | ||
| } | ||
| } | ||
| 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. |

Uh oh!
There was an error while loading. Please reload this page.