Repository: sympy/sympy

Issue:
X.pdf(x) for Symbol x returns 0
```
>>> x = Symbol('x')
>>> from sympy.stats.frv_types import DieDistribution
>>> X = DieDistribution(6)
>>> X.pdf(x)
0
```
Either this should raise a `ValueError` or unevaluated. Returning unevaluated seems more favourable.

Discussion:
gxyd: I was hoping that something like
```
>>> x = Symbol('x')
>>> X = DieDistribution(6)
>>> X.pdf(x)
PDF(X, x)    # some PDF be returned though i guess no such PDF  class or function is there
```
I expected something similar to `Contains`
```
>>> A.contains(x)
Contains(A, x)
```
jksuom: I think it could be possible to return a sensible ``X.pdf(x)`` even for a symbol ``x``. For an ordinary die (with 6 faces) it would be ``AddWithLimits(KroneckerDelta(x, i)/6, (i, 1, 6))`` (though that looks a bit clumsy).
gxyd: It looks good to me. I did not know about the `KroneckerDelta` function till now though.
> AddWithLimits(KroneckerDelta(x, i)/6, (i, 1, 6)) (though that looks a bit clumsy).

I think i am ok, with that.
jksuom: I now think that we should have ``Sum`` instead of ``AddWithLimits``. I had forgotten its existence. It seems that ``X.pdf(x).subs(x, 1).doit()`` would compute correctly with ``Sum`` since it has its own version of ``doit``. There could be some tests for such substitutions. And maybe for some illegal substitution as well.
gxyd: I myself, even before seeing with `AddWithLimits` checked with `Sum.doit()` . But i could not though make that out how to do that. But Ok, let me try again. I may now be able to come to solution.
gxyd: I have made the changes. Perhaps previously i was also using `.doit()` itself in code. Now using ony `Sum` works fine.
jksuom: This looks good to me. The separation of arguments that do not belong to the probability space (i.e. integers between 1 and ``sides``) into two classes: numbers returning 0, and non-numbers returning an error, is somewhat arbitrary, but I cannot think of a better way now.

Patch:
diff --git a/sympy/stats/frv_types.py b/sympy/stats/frv_types.py
index be522333c8..fa6688dd1e 100644
--- a/sympy/stats/frv_types.py
+++ b/sympy/stats/frv_types.py
@@ -17,8 +17,9 @@
 from sympy.core.compatibility import as_int, range
 from sympy.core.logic import fuzzy_not, fuzzy_and
 from sympy.stats.frv import (SingleFinitePSpace, SingleFiniteDistribution)
+from sympy.concrete.summations import Sum
 from sympy import (S, sympify, Rational, binomial, cacheit, Integer,
-        Dict, Basic)
+        Dict, Basic, KroneckerDelta, Dummy)
 
 __all__ = ['FiniteRV', 'DiscreteUniform', 'Die', 'Bernoulli', 'Coin',
         'Binomial', 'Hypergeometric']
@@ -49,7 +50,7 @@ def FiniteRV(name, density):
 
     >>> E(X)
     2.00000000000000
-    >>> P(X>=2)
+    >>> P(X >= 2)
     0.700000000000000
     """
     return rv(name, FiniteDistributionHandmade, density)
@@ -118,14 +119,19 @@ def dict(self):
 
     @property
     def set(self):
-        return list(map(Integer, list(range(1, self.sides+1))))
+        return list(map(Integer, list(range(1, self.sides + 1))))
 
     def pdf(self, x):
         x = sympify(x)
-        if x.is_Integer and x >= 1 and x <= self.sides:
-            return Rational(1, self.sides)
-        else:
-            return 0
+        if x.is_number:
+            if x.is_Integer and x >= 1 and x <= self.sides:
+                return Rational(1, self.sides)
+            return S.Zero
+        if x.is_Symbol:
+            i = Dummy('i', integer=True, positive=True)
+            return Sum(KroneckerDelta(x, i)/self.sides, (i, 1, self.sides))
+        raise ValueError("'x' expected as an argument of type 'number' or 'symbol', "
+                        "not %s" % (type(x)))
 
 
 def Die(name, sides=6):


Test patch:
diff --git a/sympy/stats/tests/test_finite_rv.py b/sympy/stats/tests/test_finite_rv.py
index 5f3663623f..e8e3a9c2bb 100644
--- a/sympy/stats/tests/test_finite_rv.py
+++ b/sympy/stats/tests/test_finite_rv.py
@@ -1,13 +1,16 @@
 from sympy.core.compatibility import range
 from sympy import (FiniteSet, S, Symbol, sqrt,
         symbols, simplify, Eq, cos, And, Tuple, Or, Dict, sympify, binomial,
-        cancel)
+        cancel, KroneckerDelta)
+from sympy.concrete.expr_with_limits import AddWithLimits
+from sympy.matrices import Matrix
 from sympy.stats import (DiscreteUniform, Die, Bernoulli, Coin, Binomial,
     Hypergeometric, Rademacher, P, E, variance, covariance, skewness, sample,
     density, where, FiniteRV, pspace, cdf,
     correlation, moment, cmoment, smoment)
+from sympy.stats.frv_types import DieDistribution
 from sympy.utilities.pytest import raises, slow
-from sympy.abc import p
+from sympy.abc import p, x, i
 
 oo = S.Infinity
 
@@ -259,3 +262,14 @@ def test_density_call():
     assert 0 in d
     assert 5 not in d
     assert d(S(0)) == d[S(0)]
+
+
+def test_DieDistribution():
+    X = DieDistribution(6)
+    assert X.pdf(S(1)/2) == S.Zero
+    assert X.pdf(x).subs({x: 1}).doit() == S(1)/6
+    assert X.pdf(x).subs({x: 7}).doit() == 0
+    assert X.pdf(x).subs({x: -1}).doit() == 0
+    assert X.pdf(x).subs({x: S(1)/3}).doit() == 0
+    raises(TypeError, lambda: X.pdf(x).subs({x: Matrix([0, 0])}))
+    raises(ValueError, lambda: X.pdf(x**2 - 1))