Repository: cupy/cupy

Issue:
Some `RandomState` methods cannot infer `size`
```
>>> numpy.random.RandomState(42).normal(loc=numpy.zeros(3))
array([ 0.49671415, -0.1382643 ,  0.64768854])
>>> cupy.random.RandomState(42).normal(loc=cupy.zeros(3))
TypeError: only size-1 arrays can be converted to Python scalars
```

while `cupy.random.normal(loc=cupy.zeros(3))` works.


Discussion:

Resolving patch:
diff --git a/cupy/random/_distributions.py b/cupy/random/_distributions.py
--- a/cupy/random/_distributions.py
+++ b/cupy/random/_distributions.py
@@ -1,4 +1,3 @@
-import cupy
 from cupy.random import _generator
 from cupy import _util
 
@@ -476,9 +475,8 @@ def multivariate_normal(mean, cov, size=None, check_valid='ignore',
     """
     _util.experimental('cupy.random.multivariate_normal')
     rs = _generator.get_random_state()
-    x = rs.multivariate_normal(mean, cov, size, check_valid, tol, method,
-                               dtype)
-    return x
+    return rs.multivariate_normal(
+        mean, cov, size, check_valid, tol, method, dtype)
 
 
 def normal(loc=0.0, scale=1.0, size=None, dtype=float):
@@ -500,13 +498,7 @@ def normal(loc=0.0, scale=1.0, size=None, dtype=float):
 
     """
     rs = _generator.get_random_state()
-    if size is None and any(isinstance(arg, cupy.ndarray)
-                            for arg in [scale, loc]):
-        size = cupy.broadcast_arrays(loc, scale)[0].shape
-    x = rs.normal(0, 1, size, dtype)
-    cupy.multiply(x, scale, out=x)
-    cupy.add(x, loc, out=x)
-    return x
+    return rs.normal(loc, scale, size, dtype)
 
 
 def pareto(a, size=None, dtype=float):
@@ -532,8 +524,7 @@ def pareto(a, size=None, dtype=float):
     .. seealso:: :func:`numpy.random.pareto`
     """
     rs = _generator.get_random_state()
-    x = rs.pareto(a, size, dtype)
-    return x
+    return rs.pareto(a, size, dtype)
 
 
 def noncentral_chisquare(df, nonc, size=None, dtype=float):
@@ -619,8 +610,7 @@ def poisson(lam=1.0, size=None, dtype=int):
     .. seealso:: :func:`numpy.random.poisson`
     """
     rs = _generator.get_random_state()
-    x = rs.poisson(lam, size, dtype)
-    return x
+    return rs.poisson(lam, size, dtype)
 
 
 def power(a, size=None, dtype=float):
@@ -671,8 +661,7 @@ def rayleigh(scale=1.0, size=None, dtype=float):
     .. seealso:: :func:`numpy.random.rayleigh`
     """
     rs = _generator.get_random_state()
-    x = rs.rayleigh(scale, size, dtype)
-    return x
+    return rs.rayleigh(scale, size, dtype)
 
 
 def standard_cauchy(size=None, dtype=float):
@@ -696,8 +685,7 @@ def standard_cauchy(size=None, dtype=float):
     .. seealso:: :func:`numpy.random.standard_cauchy`
     """
     rs = _generator.get_random_state()
-    x = rs.standard_cauchy(size, dtype)
-    return x
+    return rs.standard_cauchy(size, dtype)
 
 
 def standard_exponential(size=None, dtype=float):
@@ -766,7 +754,8 @@ def standard_normal(size=None, dtype=float):
     .. seealso:: :func:`numpy.random.standard_normal`
 
     """
-    return normal(size=size, dtype=dtype)
+    rs = _generator.get_random_state()
+    return rs.standard_normal(size, dtype)
 
 
 def standard_t(df, size=None, dtype=float):
diff --git a/cupy/random/_generator.py b/cupy/random/_generator.py
--- a/cupy/random/_generator.py
+++ b/cupy/random/_generator.py
@@ -75,7 +75,7 @@ def _generate_normal(self, func, size, dtype, *args):
         # * curand.generateLogNormal
         # * curand.generateLogNormalDouble
         size = core.get_size(size)
-        element_size = functools.reduce(operator.mul, size, 1)
+        element_size = core.internal.prod(size)
         if element_size % 2 == 0:
             out = cupy.empty(size, dtype=dtype)
             func(self._generator, out.data.ptr, out.size, *args)
@@ -279,6 +279,12 @@ def lognormal(self, mean=0.0, sigma=1.0, size=None, dtype=float):
             - :meth:`numpy.random.RandomState.lognormal`
 
         """
+        if any(isinstance(arg, cupy.ndarray) for arg in (mean, sigma)):
+            x = self.normal(mean, sigma, size, dtype)
+            cupy.exp(x, out=x)
+            return x
+        if size is None:
+            size = ()
         dtype = _check_and_get_dtype(dtype)
         if dtype.char == 'f':
             func = curand.generateLogNormal
@@ -437,11 +443,22 @@ def normal(self, loc=0.0, scale=1.0, size=None, dtype=float):
 
         """
         dtype = _check_and_get_dtype(dtype)
+        if size is None:
+            size = cupy.broadcast(loc, scale).shape
         if dtype.char == 'f':
             func = curand.generateNormal
         else:
             func = curand.generateNormalDouble
-        return self._generate_normal(func, size, dtype, loc, scale)
+        if isinstance(scale, cupy.ndarray):
+            x = self._generate_normal(func, size, dtype, 0.0, 1.0)
+            cupy.multiply(x, scale, out=x)
+            cupy.add(x, loc, out=x)
+        elif isinstance(loc, cupy.ndarray):
+            x = self._generate_normal(func, size, dtype, 0.0, scale)
+            cupy.add(x, loc, out=x)
+        else:
+            x = self._generate_normal(func, size, dtype, loc, scale)
+        return x
 
     def pareto(self, a, size=None, dtype=float):
         """Returns an array of samples drawn from the pareto II distribution.


Test patch:
diff --git a/tests/cupy_tests/random_tests/test_distributions.py b/tests/cupy_tests/random_tests/test_distributions.py
--- a/tests/cupy_tests/random_tests/test_distributions.py
+++ b/tests/cupy_tests/random_tests/test_distributions.py
@@ -282,9 +282,9 @@ def test_logistic(self, loc_dtype, scale_dtype, dtype):
 
 
 @testing.parameterize(*testing.product({
-    'shape': [(4, 3, 2), (3, 2)],
-    'mean_shape': [()],
-    'sigma_shape': [()],
+    'shape': [(4, 3, 2), (3, 2), None],
+    'mean_shape': [(), (3, 2)],
+    'sigma_shape': [(), (3, 2)],
 })
 )
 @testing.gpu
@@ -612,7 +612,7 @@ def test_standard_gamma(self, shape_dtype, dtype):
 
 
 @testing.parameterize(*testing.product({
-    'shape': [(4, 3, 2), (3, 2)],
+    'shape': [(4, 3, 2), (3, 2), None],
 })
 )
 @testing.gpu