Skip to content

Commit 32b4c1f

Browse files
committed
Reduce allocations random_unitaries
The previous implementation had 4 heap allocations for each random unitary constructed, this commit uses some fixed sized stack allocated arrays and reduces that to two allocations one for q and r from the factorization. We'll always need at least one for the `Array2` that gets stored in each `UnitaryGate` as a numpy array. But to reduce to just this we'll need a method of computing the QR factorization without an allocation for the result space, nalgebtra might be a path for doing that. While this currently isn't a bottleneck as the `UnitaryGate` python object creation is the largest source of runtime, but assuming that's fixed in the future this might have a larger impact.
1 parent 69d4cfa commit 32b4c1f

1 file changed

Lines changed: 43 additions & 11 deletions

File tree

crates/accelerate/src/circuit_library/quantum_volume.rs

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use ndarray::prelude::*;
2222
use num_complex::Complex64;
2323
use numpy::IntoPyArray;
2424
use rand::prelude::*;
25-
use rand_distr::Normal;
25+
use rand_distr::StandardNormal;
2626
use rand_pcg::Pcg64Mcg;
2727
use rayon::prelude::*;
2828

@@ -33,29 +33,61 @@ use qiskit_circuit::packed_instruction::PackedOperation;
3333
use qiskit_circuit::Qubit;
3434
use smallvec::smallvec;
3535

36+
#[inline(always)]
37+
fn random_complex(rng: &mut Pcg64Mcg) -> Complex64 {
38+
Complex64::new(rng.sample(StandardNormal), rng.sample(StandardNormal))
39+
* std::f64::consts::FRAC_1_SQRT_2
40+
}
41+
3642
// This function's implementation was modeled off of the algorithm used in the
3743
// `scipy.stats.unitary_group.rvs()` function defined here:
3844
//
3945
// https://github.com/scipy/scipy/blob/v1.14.1/scipy/stats/_multivariate.py#L4224-L4256
4046
#[inline]
4147
fn random_unitaries(seed: u64, size: usize) -> impl Iterator<Item = Array2<Complex64>> {
4248
let mut rng = Pcg64Mcg::seed_from_u64(seed);
43-
let dist = Normal::new(0., 1.0).unwrap();
4449

4550
(0..size).map(move |_| {
46-
let mut z: Array2<Complex64> = Array2::from_shape_simple_fn((4, 4), || {
47-
Complex64::new(dist.sample(&mut rng), dist.sample(&mut rng))
48-
});
49-
z.mapv_inplace(|x| x * std::f64::consts::FRAC_1_SQRT_2);
50-
let qr = z.view().into_faer_complex().qr();
51-
let r = qr.compute_r().as_ref().into_ndarray_complex().to_owned();
52-
let mut d = r.into_diag();
53-
d.mapv_inplace(|d| d / d.norm());
51+
let raw_numbers: [[Complex64; 4]; 4] = [
52+
[
53+
random_complex(&mut rng),
54+
random_complex(&mut rng),
55+
random_complex(&mut rng),
56+
random_complex(&mut rng),
57+
],
58+
[
59+
random_complex(&mut rng),
60+
random_complex(&mut rng),
61+
random_complex(&mut rng),
62+
random_complex(&mut rng),
63+
],
64+
[
65+
random_complex(&mut rng),
66+
random_complex(&mut rng),
67+
random_complex(&mut rng),
68+
random_complex(&mut rng),
69+
],
70+
[
71+
random_complex(&mut rng),
72+
random_complex(&mut rng),
73+
random_complex(&mut rng),
74+
random_complex(&mut rng),
75+
],
76+
];
77+
78+
let qr = aview2(&raw_numbers).into_faer_complex().qr();
79+
let r = qr.compute_r();
80+
let diag: [Complex64; 4] = [
81+
r[(0, 0)].to_num_complex() / r[(0, 0)].abs(),
82+
r[(1, 1)].to_num_complex() / r[(1, 1)].abs(),
83+
r[(2, 2)].to_num_complex() / r[(2, 2)].abs(),
84+
r[(3, 3)].to_num_complex() / r[(3, 3)].abs(),
85+
];
5486
let mut q = qr.compute_q().as_ref().into_ndarray_complex().to_owned();
5587
q.axis_iter_mut(Axis(0)).for_each(|mut row| {
5688
row.iter_mut()
5789
.enumerate()
58-
.for_each(|(index, val)| *val *= d.diag()[index])
90+
.for_each(|(index, val)| *val *= diag[index])
5991
});
6092
q
6193
})

0 commit comments

Comments
 (0)