fix Mul._eval_is_negative and add NOTE

The was moved to is_positive.

The note added inline is important.
Although this routine was tested with the code
below with 2924 expressions and never gave a wrong True or False,
there are cases where it
will give None when an answer should be returned, e.g.

real*real*imaginary will always be False for is_negative but None is
returned. There are many more such expressions which can be indentified
by running the code below with g(1,1) then g(0,0) and g(1,0).

============================
from sympy import *
def representative_numbers(n=None, rand=False):
    """Return a set of numbers build from numbers in n; 0, 1 and oo will always
    be included. If rand is True then 3 or more (depending on the length of n)
    random numbers between 0 and 1 will be used, too.

    Examples
    ========

    >>> from sympy.utilities.misc import filldedent
    >>> print filldedent(a)

    [0, 1, oo + I, -1 - oo*I, oo - I, -oo + oo*I, oo, oo*I, -oo - oo*I, -1
    + oo*I, oo - oo*I, -1 + I, oo + oo*I, 1 - oo*I, -1 - I, 1 + I, -oo*I,
    1 - I, I, -oo - I, -oo, -oo + I, 1 + oo*I, -1, -I]
    """
    from random import random
    from sympy.core.compatibility import iterable
    n = n or []
    n.extend([0, 1, oo])
    n = list(Tuple(*n))
    if rand and all(i.is_Integer for i in n):
        n.extend([Float(random()) for i in n])
    for s in subsets(n, 2, repetition=True):
        n.append(s[0] + I*s[1])
        n.append(s[1] + I*s[0])
        n.append(s[0] - I*s[1])
        n.append(s[1] - I*s[0])
    n.extend([-i for i in n])
    n = list(set(n))
    return n

def nmatches(x, n=None):
    """Return numbers in n that match the assumptions of x.
    If n=None representative numbers will be supplied.

    Examples
    ========

    >>> from sympy import Dummy
    >>> x = Dummy(imaginary=True)
    >>> nmatches(x)
    [oo*I, -oo*I, I, -I]
    """
    n = n or representative_numbers()
    ok = []
    d = x.assumptions0
    for i in n:
        if all(getattr(i, 'is_' + k) == d[k] for k in d):
            ok.append(i)
    return ok

def model(e, n=None, reps=False, filter=True):
    """Return the attributes used in the expression and the numerical
    expressions generated from numbers in n matching
    the symbol attributes. If `reps` is True then the values used will
    be returned in a tuple with the model. If `filter` is True then
    only models with unique attributes, relative to the attributes
    existing amongst the symbols will be returned.

    Examples
    ========

    >>> from sympy import Dummy
    >>> from sympy.utilities.misc import filldedent
    >>> x = Dummy(imaginary=True)
    >>> print filldedent(list(model(x**2)))

    [['antihermitian', 'commutative', 'complex', 'composite', 'even',
    'imaginary', 'integer', 'irrational', 'negative', 'noninteger',
    'nonnegative', 'nonpositive', 'nonzero', 'odd', 'positive', 'prime',
    'rational', 'real', 'zero'], -oo, -1]

    If you want to see how a result was obtained, set reps to True

    >>> print filldedent(list(model(x**2, reps=1)))

    [['antihermitian', 'commutative', 'complex', 'composite', 'even',
    'imaginary', 'integer', 'irrational', 'negative', 'noninteger',
    'nonnegative', 'nonpositive', 'nonzero', 'odd', 'positive', 'prime',
    'rational', 'real', 'zero'], (-oo, {x: oo*I}), (-1, {x: I})]
    """
    free = list(e.free_symbols)
    if filter:
        attr = set.union(*[set(f.assumptions0) for f in free])
        saw = set()
        yield sorted(attr)
        attr = ['is_'+k for k in attr]
    #print([nmatches(f, n) for f in free])
    for r in cartes(*[nmatches(f, n) for f in free]):
        rep = dict(zip(free, r))
        y = e.xreplace(rep)
        if filter:
            k = tuple([getattr(y, k) for k in attr])
            if k in saw:
                continue
            saw.add(k)
        if reps:
            yield y, rep
        else:
            yield y

def values(e, attr, n=None):
    """Return representative models having attributes of True, False or None
    wrt the given attribute, `attr` (which can be a string like 'is_real' or
    a function like `lambda x: x.is_positive and x.is_even`. The numbers in `n`
    will be used as candidates and will be generated if not given.

    Examples
    ========

    >>> from sympy import Dummy
    >>> x = Dummy(imaginary=True)
    >>> values(x**2, 'is_real')
    True: (-oo, {x: oo*I})}
    """
    d = {}
    for x, r in model(e, n, True, False):
      try:
          k = getattr(x, attr)
      except:
          k = attr(x)
      if k not in d:
        d[k] = x, r
      if len(d) == 3:
          break
    return d

atr='real,imaginary,positive,negative,nonnegative,nonpositive,zero'.split(',')
s=[]
ke = []
for i in (True, False, None):
  for a in atr:
    if i or a == 'real':  # eventually this should be 'if 1:'
      for j in range(3):
        s.append(Dummy(**{a:i, 'complex':True}))
        ke.append((a, i))

def attr(sy):
    return ke[s.index(sy)]

def go(checkNone, bounded):
    if bounded:
        n = Tuple(-1, 0, 1, -I, I, 1 + I, 1 - I)
    else:
        n = Tuple(-1, 0, 1, -I, I, 1 + I, 1 - I, oo, I*oo, oo+I*oo)
    for i, a in enumerate(subsets(s, 3)):
      if i%100 == 0:
          print(i)
      m = Mul(*a).expand()
      v = m.is_positive
      d = values(m,'is_positive',n)
      if v is None:
        if checkNone:
            try:
              assert len(d) > 1
            except:
               print m.is_positive, [attr(ai) for ai in a], d
               break
      else:
        try:
            assert len(d) == 1
            assert m.is_positive in d
        except:
           print m.is_positive, [attr(ai) for ai in a], d
           break
    else:
        print('completed %s tests' % i)

go(0,1)