Repository: scipy/scipy
Pull request: domain check for a < b in stats.truncnorm
I fix and a PR of the bug issued by @chrisb83.
I changed the condition formula for py_test from `a!=b and a<b` to 
`np.bitwise_and(np.not_equal(a, b), np.less(a, b))` in `_argcheck`.

Closes #9169 

Problem statement:
truncnorm does not work if b < a in scipy.stats
truncnorm does not check that `a < b`. If `b > a`, one can produce rvs but the pdf and cdf do not work.
Should be easy to fix using `_argcheck`. 

### Reproducing code example:
```
from scipy import stats
import numpy as np

x = np.arange(1, 2, 0.1)
stats.truncnorm(2, 1).pdf(x) # all 0
stats.truncnorm(2, 1).cdf(1) # 1.0
stats.truncnorm(2, 1).cdf(0.999) # 0.0
stats.truncnorm(2, 1).rvs(size=5, random_state=123) 
# array([1.1880652 , 1.54116815, 1.61108676, 1.29490424, 1.17234318])
```

This does not make sense.

### Scipy/Numpy/Python version information:
```
1.0.0 1.14.1 sys.version_info(major=3, minor=5, micro=4, releaselevel='final', serial=0)
```

Hints from issue discussion:

Commit message: argcheck condition updates

Changed the condition for py_test.

Patch to review:
diff --git a/scipy/stats/_continuous_distns.py b/scipy/stats/_continuous_distns.py
index 39d01c9c6275..65278fa6eda4 100644
--- a/scipy/stats/_continuous_distns.py
+++ b/scipy/stats/_continuous_distns.py
@@ -6254,7 +6254,7 @@ def _argcheck(self, a, b):
                                -(self._sb - self._sa),
                                self._nb - self._na)
         self._logdelta = np.log(self._delta)
-        return a != b
+        return np.bitwise_and(np.not_equal(a, b), np.less(a, b))
 
     def _pdf(self, x, a, b):
         return _norm_pdf(x) / self._delta


Code review comments:
diff_hunk: @@ -6254,7 +6254,7 @@ def _argcheck(self, a, b):
                                -(self._sb - self._sa),
                                self._nb - self._na)
         self._logdelta = np.log(self._delta)
-        return a != b
+        return np.bitwise_and(np.not_equal(a, b), np.less(a, b))
path: scipy/stats/_continuous_distns.py
text: @user1:
Less (<) implies not_equal

@author:
I used the numpy function as a one-to-one correspondence during the argument checking.
As you already fully knows, the `!=` symbol is `np.not_equal`, the `<` symbol is `np.less`.
If so, does it mean that you can use only one `np.less` function in this section?

@user2:
`return a < b` does the same thing.

@author:
OK, I will reflect that mention.
But,  I used the `np.less` function because it fails in py_test when using `a<b` condition.

```
bshape     = [5, 4, 3, 2]
dist       = 'truncnorm'
distfunc   = <scipy.stats._continuous_distns.truncnorm_gen object at 0x6ffef15b710>
k          = 1
loc        = array([0., 0.])
nargs      = 2
scale      = array([[1.],
       [1.],
       [1.]])
shape_args = (0.1, 2.0)
shape_only = False
shp        = (5, 1, 1, 1)

scipy/stats/tests/test_continuous_basic.py:258:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
scipy/stats/tests/common_tests.py:300: in check_rvs_broadcast
    sample = distfunc.rvs(*allargs)
scipy/stats/_distn_infrastructure.py:936: in rvs
    cond = logical_and(self._argcheck(*args), (scale >= 0))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <scipy.stats._continuous_distns.truncnorm_gen object at 0x6ffef15b710>
a = array([[[[0.1, 0.1],
         [0.1, 0.1],
         [0.1, 0.1]],

        [[0.1, 0.1],
         [0.1, 0.1],
         [0....1, 0.1],
         [0.1, 0.1],
         [0.1, 0.1]],

        [[0.1, 0.1],
         [0.1, 0.1],
         [0.1, 0.1]]]])
b = array([[[[2., 2.],
         [2., 2.],
         [2., 2.]],

        [[2., 2.],
         [2., 2.],
         [2., 2.]],

...
        [[2., 2.],
         [2., 2.],
         [2., 2.]],

        [[2., 2.],
         [2., 2.],
         [2., 2.]]]])

 def _argcheck(self, a, b):
        self.a = a
        self.b = b
        self._nb = _norm_cdf(b)
        self._na = _norm_cdf(a)
        self._sb = _norm_sf(b)
        self._sa = _norm_sf(a)
        self._delta = np.where(self.a > 0,
                               -(self._sb - self._sa),
                               self._nb - self._na)
        self._logdelta = np.log(self._delta)
>       return a != b and a < b
E       ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
```


@user2:
It should be only `return a < b`. Equality is ruled out by strict inequality `<` anyways. 

If you want to combine bool statements then it should read as `(a != b) & (a < b)`. But here not needed.

@author:
OK, I understood an expression of equality and comparison operator.
I will use the comparison operator `<` as your advised.
My worries were about the py_test as well as the comparison operator.
I used the numpy function(`np.less`, `np.not_equal`, `np.bitwise_and`) to solve the section 
where py_test failed in multiple condition.
As a result, I will commit the conditional statements used only by the comparison operator.

Thanks, your valuable advice @user2, @user1.