<div dir="ltr">I would like to add a complex random normal generator to mtrand/RandomState. <div><br></div><div>A scalar complex normal is a (double) bivariate normal.  The main motivation is to simplify the construction of complex normals where are generally parameterized in terms of three values: location, covariance and relation.  location is the same as in a standard normal.  The covariance and the relation jointly determine the variance of the real and imaginary parts as well as the covariance between the two.</div><div><br></div><div>#1 The initial implementation in the PR has followed the standard template for scalar RV generators with three paths, scalar->scalar, scalar->array and array->array.  It is bulky since the existing array fillers that handle the scalar->array and array->array for double rvs cannot be used.  It supports broadcasting and has a similar API to other scalar RV generators (e.g. normal).</div><div><br></div><div>#2 The PR discussion has moved towards exploiting the relationship with the multivariate normal. Currently the MV normal doesn't broadcast, and so following this path would only allow the scalar->scalar and the scalar->array paths.  This could theoretically be extended to allow broadcasting if multivariate_normal was extended to allow broadcasting.  </div><div><br></div><div>#3 If broadcasting is off the table, then it might make more sense to skip a scalar complex normal and just move directly to a multivariate_complex_normal since this is also just a higher dimension (double) multivariate normal. This function could just wrap multivariate_normal and would be relatively straightforward forward.  The only down side of this path is that it would not easily support  a scalar->scalar path, although this could be added. </div><div><br></div><div>This probably isn't much of a performance hit for following #2 or #3.  I checked how normal and multivariate normal perform for large draws:</div><div><br></div><div><div><div>%timeit np.random.normal(2.0,4.0,size=1000000)</div><div>30.8 ms ± 125 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)</div></div><div><br></div><div>%timeit np.random.multivariate_normal([2.0],[[4.0]],size=1000000)</div><div>32.2 ms ± 308 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)</div><div><br></div><div>For smaller draws the performance difference is larger:</div><div><br></div><div><div>%timeit np.random.normal(2.0,4.0,size=10)</div><div>2.95 µs ± 16.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)</div><div><br></div><div>%timeit np.random.multivariate_normal([2.0],[[4.0]],size=10)</div><div>49.4 µs ± 249 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)</div></div><div><br></div><div>And for scalars the scalar path is about 30x faster than the multivariate path.  It is also worth noting that multivariate_normal will only return a vector even if the inputs only generate a single scalar. </div><div><br></div><div><div>%timeit np.random.normal(2.0,4.0)</div><div>1.42 µs ± 3.05 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)</div><div><br></div><div>%timeit np.random.multivariate_normal([2.0],[[4.0]])</div><div>47.9 µs ± 167 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)</div></div><div><br></div><div>It would be helpful do determine which path, </div></div><div><br></div><div>#1 Clone standard scalar generator with 3 paths including broadcasting</div><div>#2 Scalar generator using multivariate normal, excluding broadcasting</div><div>#3 Multivariate generator using multivariate normal, excluding broadcasting</div><div><br></div><div>is the preferred one. </div><div><br></div><div>Kevin</div></div>