10 views
Skip to first unread message

Fabian Seoane

unread,
Mar 16, 2009, 11:13:26 AM3/16/09
to sympy-...@googlegroups.com
this introduces my work on the new assumption system. The old assumption is left
untouched until we migrate all the code the new assumption system.

you can pull from http://fseoane.net/git/sympy.git, branch proposed

Fabian Seoane

unread,
Mar 16, 2009, 11:13:28 AM3/16/09
to sympy-...@googlegroups.com, Fabian Seoane
Function get_class returns a class from a given string. It makes use
of get_mod_func
---
sympy/utilities/source.py | 26 ++++++++++++++++++++++++++
1 files changed, 26 insertions(+), 0 deletions(-)

diff --git a/sympy/utilities/source.py b/sympy/utilities/source.py
index 56ca7f7..1470759 100644
--- a/sympy/utilities/source.py
+++ b/sympy/utilities/source.py
@@ -10,3 +10,29 @@ def source(object):
"""
print 'In file: %s' % inspect.getsourcefile(object)
print inspect.getsource(object)
+
+def get_class(lookup_view):
+ """
+ Convert a string version of a class name to the object.
+
+ This is used by the query method
+ """
+ if isinstance(lookup_view, str):
+ # Bail early for non-ASCII strings (they can't be functions).
+ lookup_view = lookup_view.encode('ascii')
+ mod_name, func_name = get_mod_func(lookup_view)
+ if func_name != '':
+ lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
+ if not callable(lookup_view):
+ raise AttributeError("'%s.%s' is not a callable." % (mod_name, func_name))
+ return lookup_view
+
+
+def get_mod_func(callback):
+ # Converts 'sympy.core.query.QueryIntegerHandler' to
+ # ['sympy.core.query', 'QueryIntegerHandler']
+ try:
+ dot = callback.rindex('.')
+ except ValueError:
+ return callback, ''
+ return callback[:dot], callback[dot+1:]
\ No newline at end of file
--
1.6.1.2

Fabian Seoane

unread,
Mar 16, 2009, 11:13:29 AM3/16/09
to sympy-...@googlegroups.com, Fabian Seoane
This is a very naive implementation. It constructs EVERY possible query
with propositions from self.defined_facts and tries every one to see
if it succeeds.

It has unacceptable slow performance. This is implemented just as
workaround for the new assumption system, but eventually a complete
backward-chaining algorithm should be implemented.
---
sympy/core/facts.py | 42 ++++++++++++++++++++++++++++++++++
sympy/core/tests/test_assumptions.py | 28 +++++++++++++++++++++-
sympy/core/tests/test_facts.py | 10 ++++++++
3 files changed, 79 insertions(+), 1 deletions(-)

diff --git a/sympy/core/facts.py b/sympy/core/facts.py
index 2e9d666..27bf0c6 100644
--- a/sympy/core/facts.py
+++ b/sympy/core/facts.py
@@ -817,6 +817,48 @@ def __init__(self, rules):
# TODO: add proper support for U (None), i.e.
# integer=U -> rational=U ??? (XXX i'm not sure)

+ def bc_ask(self, goal, theta={}):
+ """Backward-chaining algorithm. Backward-chaining reasoners process
+ queries and return proofs for the answers they provide.
+
+ This is a very naive implementation. It constructs EVERY possible query
+ with propositions from self.defined_facts and tries every one to see
+ if it succeeds.
+
+ It has unacceptable slow performance. This is implemented just as
+ workaround for the new assumption system. Do *NOT* commit to the
+ main repo
+ """
+
+ if len(goal.keys()) > 1:
+ raise NotImplementedError
+
+ key = goal.keys()[0]
+ _facts = []
+ from sympy.utilities.iterables import subsets, variations
+ for k in range(len(self.defined_facts)):
+ k_subsets = list(subsets(self.defined_facts, k))
+ for k_sub in k_subsets:
+ if not key in k_sub:
+ # it won't be of any use if goal is in answers
+ for _pos in variations([True, False], len(k_sub), repetition=True):
+ _res = dict()
+ for k, i in enumerate(k_sub):
+ _res[i] = _pos[k]
+ _facts.append(_res)
+
+ answers = []
+ for _f in _facts:
+ try:
+ _deduced = self.deduce_all_facts(_f)
+
+ if _deduced.has_key(key) and _deduced[key] == goal[key]:
+ answers.append(_f)
+ except AssertionError:
+ pass
+
+ return answers
+

def deduce_all_facts(self, facts, base=None):
"""Deduce all facts from known facts ({} or [] of (k,v))
diff --git a/sympy/core/tests/test_assumptions.py b/sympy/core/tests/test_assumptions.py
index cb1c0d5..a4d7ca8 100644
--- a/sympy/core/tests/test_assumptions.py
+++ b/sympy/core/tests/test_assumptions.py
@@ -1,4 +1,30 @@
-from sympy.core import Symbol, S, Rational, Integer
+from sympy.core.assumptions import Assume
+from sympy.core import Symbol
+
+def test_assume():
+ x = Symbol('x')
+ assump = Assume(x, 'integer')
+ assert assump.expr == x
+ assert assump.key == 'integer'
+ assert assump.bool_key == True
+
+def test_assume_False():
+ x = Symbol('x')
+ assump = Assume(x, 'integer', False)
+ assert assump.expr == x
+ assert assump.key == 'integer'
+ assert assump.bool_key == False
+
+def test_assume_equal():
+ x = Symbol('x')
+ assert Assume(x, 'positive') == Assume(x, 'positive')
+ assert Assume(x, 'positive') != Assume(x, 'positive', False)
+ assert Assume(x, 'positive', False) == Assume(x, 'positive', False)
+
+### Also check the old assumption system until it is completely
+### removed
+
+from sympy.core import S, Rational, Integer
from sympy.utilities.pytest import XFAIL, raises

def test_symbol_unset():
diff --git a/sympy/core/tests/test_facts.py b/sympy/core/tests/test_facts.py
index b86346f..f7a129e 100644
--- a/sympy/core/tests/test_facts.py
+++ b/sympy/core/tests/test_facts.py
@@ -335,3 +335,13 @@ def X_test_FactRules_deduce_cow():

assert X == {'real': T, 'neg': F, 'zero': F, 'pos': T}
#assert set(new_knowledge) == set([ ('zero',F), ('pos',T) ]) # XXX disabled
+
+def test_bc_ask():
+ # test backward chaining
+ f = FactRules(['negative == real & !zero & !pos'])
+ f.bc_ask({'negative' : True}) == [{'pos': False, 'real': True, 'zero': False}]
+ f = FactRules(['Odd -> Integer'])
+ f.bc_ask({'Integer' : True}) == {'Odd':True, 'Integer':True}
+
+ f = FactRules(['positive == real & !negative & !zero'])
+ f.bc_ask({'positive' : True}) == {'negative' : False, 'real' : True, 'zero' : False}
--
1.6.1.2

Fabian Seoane

unread,
Mar 16, 2009, 11:13:27 AM3/16/09
to sympy-...@googlegroups.com, Fabian Seoane
variations is a routine that calculates all possible variations
of size n (with or without repetition) of a given set.
---
sympy/utilities/iterables.py | 36 +++++++++++++++++++++++++++++++
sympy/utilities/tests/test_iterables.py | 6 ++++-
2 files changed, 41 insertions(+), 1 deletions(-)

diff --git a/sympy/utilities/iterables.py b/sympy/utilities/iterables.py
index 6179622..61879a6 100644
--- a/sympy/utilities/iterables.py
+++ b/sympy/utilities/iterables.py
@@ -173,3 +173,39 @@ def recursion(result, M, k):
for elem in recursion([item], M[i + 1:], k - 1):
yield elem

+
+def cartes(seq0, seq1, modus='pair'):
+ """ return the Cartesian Product of two sequences """
+ if modus == 'pair':
+ return [[item0, item1] for item0 in seq0 for item1 in seq1]
+ elif modus == 'triple':
+ return [item0 + [item1] for item0 in seq0 for item1 in seq1]
+
+def variations(seq, n, repetition=False):
+ """Returns all the variations of the list of size n.
+
+ variations(seq, n, True) will return all the variations of the list of
+ size n with repetitions
+
+ variations(seq, n, False) will return all the variations of the list of
+ size n without repetitions
+ """
+ def setrep(seq): # remove sets with duplicates (repetition is relevant)
+ def delrep(seq): # remove duplicates while maintaining order
+ result = []
+ for item in seq:
+ if item not in result:
+ result.append(item)
+ return result
+ return [item for item in seq if item == delrep(item)]
+
+ if n == 1:
+ return [[item] for item in seq]
+ result = range(len(seq))
+ cartesmodus = 'pair'
+ for i in range(n-1):
+ result = cartes(result, range(len(seq)), cartesmodus)
+ if not repetition:
+ result = setrep(result)
+ cartesmodus = 'triple'
+ return [[seq[index] for index in indices] for indices in result]
diff --git a/sympy/utilities/tests/test_iterables.py b/sympy/utilities/tests/test_iterables.py
index 1eea497..959c900 100644
--- a/sympy/utilities/tests/test_iterables.py
+++ b/sympy/utilities/tests/test_iterables.py
@@ -1,6 +1,6 @@
from sympy import symbols
from sympy.utilities.iterables import postorder_traversal, \
- preorder_traversal, flatten, subsets
+ preorder_traversal, flatten, subsets, variations


w,x,y,z= symbols('wxyz')
@@ -31,3 +31,7 @@ def test_subsets():
assert list(subsets([1, 2, 3], 2)) == [[1, 2], [1,3], [2, 3]]
assert list(subsets([1, 2, 3], 3)) == [[1, 2, 3]]

+def test_variations():
+ assert variations([1,2,3], 2) == [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]
+ assert variations([1,2,3], 2, True) == [[1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], \
+ [3,1], [3,2], [3,3]]
--
1.6.1.2

Fabian Seoane

unread,
Mar 16, 2009, 11:13:30 AM3/16/09
to sympy-...@googlegroups.com, Fabian Seoane
The old assumption system had some limitations:

1. Can't assume relations. It is not possible to specify relations between different
objects. For example, you can assume that x>0 and y>0, but you can't
assume x>y

2. Verbose code splitted across multiple classes. Each class must override
query methods (.is_*).

3. Extensibility. To add new assumptions you have to add code to
the core

The assumption system implemented in this series of patches relies on these
principles:

1. Assumptions are separate from objects. Assumptions are instances of Assume, which is
a class that holds a reference to the expression and to the 'key' (what it assumes)

2. Queries out of the core. No more .is_* methods. Specific queries are implemented
as subclasses of QueryHandler, and the query function calls the appropriate
subclass of QueryHandler

query -> QueryHandler -> QueryNegativeHandler
-> QueryIntegerHandler
-> QueryBoundedHandler
...
That way, creating new queries is a matter of subclassing QueryHandler and override
the apropriate methods.

In this patch, two fundamental objects were created for this task:

- sympy.core.assumptions.Assume, which takes track of the
object's assumptions.

- a query method: sympy.query.query, which replaces the old
expr.is_* syntax. Also the relevat handlers were created to resolve
some queries, in sympy.queries.handlers

The old assumptions system was not modified, but should be removed
gradually
---
sympy/__init__.py | 1 +
sympy/core/__init__.py | 1 +
sympy/core/assumptions.py | 37 +++
sympy/query/__init__.py | 154 ++++++++++
sympy/query/handlers/__init__.py | 169 +++++++++++
sympy/query/handlers/calculus.py | 125 ++++++++
sympy/query/handlers/ntheory.py | 63 ++++
sympy/query/handlers/order.py | 168 +++++++++++
sympy/query/handlers/sets.py | 239 +++++++++++++++
sympy/query/tests/test_query.py | 594 ++++++++++++++++++++++++++++++++++++++
10 files changed, 1551 insertions(+), 0 deletions(-)
create mode 100644 sympy/query/__init__.py
create mode 100644 sympy/query/handlers/__init__.py
create mode 100644 sympy/query/handlers/calculus.py
create mode 100644 sympy/query/handlers/ntheory.py
create mode 100644 sympy/query/handlers/order.py
create mode 100644 sympy/query/handlers/sets.py
create mode 100644 sympy/query/tests/test_query.py

diff --git a/sympy/__init__.py b/sympy/__init__.py
index b0b637f..3cef873 100644
--- a/sympy/__init__.py
+++ b/sympy/__init__.py
@@ -33,6 +33,7 @@ def __sympy_debug():
from geometry import *
from utilities import *
from integrals import *
+from query import query
# This module is slow to import:
#from physics import units
from plotting import Plot, textplot
diff --git a/sympy/core/__init__.py b/sympy/core/__init__.py
index 6b9355e..0fd8976 100644
--- a/sympy/core/__init__.py
+++ b/sympy/core/__init__.py
@@ -15,6 +15,7 @@
Function, expand, PoleError
from interval import Interval
from evalf import PrecisionExhausted, N
+from assumptions import Assume

# expose singletons like exp, log, oo, I, etc.
for _n, _cls in Basic.singleton.items():
diff --git a/sympy/core/assumptions.py b/sympy/core/assumptions.py
index e8a9fd6..c528cfc 100644
--- a/sympy/core/assumptions.py
+++ b/sympy/core/assumptions.py
@@ -372,3 +372,40 @@ def getit(self):
#dis(getit)

return getit
+
+
+class Assume(object):
+ """New-style assumptions
+
+ >>> from sympy import Symbol, Assume


+ >>> x = Symbol('x')

+ >>> Assume(x, 'integer')
+ Assume( x, integer, True )
+ >>> Assume(x, 'integer', False)
+ Assume( x, integer, False )
+ """
+
+ def __init__(self, expr, key, bool_key=True):
+ if not expr.__class__.__name__ == "Symbol":
+ raise NotImplementedError
+ self._args = (expr, key, bool_key)
+
+ @property
+ def expr(self):
+ return self._args[0]
+
+ @property
+ def key(self):
+ return self._args[1]
+
+ @property
+ def bool_key(self):
+ return self._args[2]
+
+ def __repr__(self):
+ return u"Assume( %s, %s, %s )" % (self.expr, self.key, self.bool_key)
+
+ def __eq__(self, other):
+ if isinstance(other, type(self)):
+ return self._args == other._args
+ return False
diff --git a/sympy/query/__init__.py b/sympy/query/__init__.py
new file mode 100644
index 0000000..efcc891
--- /dev/null
+++ b/sympy/query/__init__.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+import copy
+import inspect
+from sympy.core import S
+from sympy.core.facts import FactRules
+from sympy.utilities.source import get_class
+
+# query_dict tells us what query handler we should use
+# for a particular key
+query_dict = {
+ 'bounded' : 'sympy.query.handlers.QueryBoundedHandler',
+ 'commutative' : 'sympy.query.handlers.QueryCommutativeHandler',
+ 'comparable' : 'sympy.query.handlers.QueryComparableHadler',
+ 'complex' : 'sympy.query.handlers.sets.QueryComplexHandler',
+ 'composite' : 'sympy.query.handlers.ntheory.QueryCompositeHandler',
+ 'even' : 'sympy.query.handlers.ntheory.QueryEvenHandler',
+ 'finite' : 'sympy.query.handlers.QueryFiniteHandler',
+ 'imaginary' : 'sympy.query.handlers.sets.QueryImaginaryHandler',
+ 'infinitesimal' : 'sympy.query.handlers.QueryInfinitesimalHandler',
+ 'integer' : 'sympy.query.handlers.sets.QueryIntegerHandler',
+ 'irrational' : 'sympy.query.handlers.sets.QueryIrrationalHandler',
+ 'rational' : 'sympy.query.handlers.sets.QueryRationalHandler',
+ 'nonnegative' : 'sympy.query.handlers.order.QueryNonNegativeHandler',
+ 'noninteger' : 'sympy.query.handlers.sets.QueryNonIntegerHandler',
+ 'nonpositive' : 'sympy.query.handlers.order.QueryNonPositiveHandler',
+ 'nonzero' : 'sympy.query.handlers.order.QueryNonZeroHandler',
+ 'negative' : 'sympy.query.handlers.order.QueryNegativeHandler',
+ 'positive' : 'sympy.query.handlers.order.QueryPositiveHandler',
+ 'prime' : 'sympy.query.handlers.ntheory.QueryPrimeHandler',
+ 'real' : 'sympy.query.handlers.sets.QueryRealHandler',
+ 'odd' : 'sympy.query.handlers.ntheory.QueryOddHandler',
+ 'unbounded' : 'sympy.query.handlers.QueryUnboundedHandler',
+ 'zero' : 'sympy.query.handlers.order.QueryZeroHandler'
+}
+
+
+def query(expr, key, assumptions=[], qdict={}, nesting=True):
+ """
+ Examples:
+ >>> query(2, 'negative')
+ False
+ >>> query(x + y, 'complex')
+ True
+ >>> query(x + y, 'real', Assume(x, 'real'))
+ True
+
+ Strategy:
+ - Phase 1: we call the handler for this key
+ - Phase 2: we try to deduce that the query is False by showing
+ that some prerequisite is not met
+ - Phase 3: try to deduce the query is True by showing that other requisite
+ that implies this queries does verify.
+ """
+ resolution = None
+ expr = S(expr)
+
+ if not isinstance(assumptions, (list, tuple)):
+ assumptions = [assumptions]
+
+ _assumptions = []
+ for assump in assumptions:
+ if expr.has(assump.expr):
+ # logic inference is expensive, so we only use the assumptions
+ # that are relevant
+ _assumptions.append(assump)
+
+ _query_dict = query_dict.copy()
+
+ if len(qdict) > 0:
+ _query_dict.update(qdict)
+
+ resolutor = get_class(_query_dict[key])(expr, key, _assumptions)
+
+ if resolutor.resolution is not None:
+ return resolutor.resolution
+
+ # Run this class handler
+ if hasattr(resolutor, expr.__class__.__name__):
+ resolution = getattr(resolutor, expr.__class__.__name__)()
+ else:
+ # we try for method defined for subclasses
+ mro = inspect.getmro(expr.__class__)
+ for subclass in mro:
+ if hasattr(resolutor, subclass.__name__):
+ resolution = getattr(resolutor, subclass.__name__)()
+ break
+ else:
+ # if no resolution maybe we could skip some steps
+ pass
+
+ if resolution is not None:
+ # if resolution is False or True there's no need to continue
+ return resolution
+
+ if not nesting:
+ return None
+
+ if resolutor.facts is None:
+ # initialize facts
+ facts = []
+ else:
+ facts = copy.copy(resolutor.facts)
+ # add to self.facts all the assumption's facts
+ # so we get a tree that connects the assumptions facts
+ # with the query's facts
+ for assump in _assumptions:
+ if not expr.has(assump.expr):
+ continue
+ if _query_dict.has_key(assump.key):
+ _resolutor = get_class(_query_dict[assump.key])
+ if _resolutor.facts is not None:
+ facts.extend(_resolutor.facts )
+ if len(facts) == 0:
+ # thre are no facts and we've finished
+ return None
+
+ compiled_rules = FactRules(facts)
+
+ #phase 2, deduce it is false by fc
+ fc_query = compiled_rules.deduce_all_facts({key : True})
+ fc_query.pop(key) # we do not want to fall into infinite recursion
+
+ for k in fc_query:
+ _qresult = query(expr, k, _assumptions, qdict, nesting=False)
+ if _qresult is None:
+ continue
+ elif not (_qresult == fc_query[k]):
+ return False
+
+ # Phase 3: deduce that the query is True backward-chaining
+ for bc_query in compiled_rules.bc_ask({key : True}):
+ for k in bc_query:
+ if query(expr, k, _assumptions, qdict, nesting=False) != bc_query[k]:
+ return
+ else:
+ return True
+
+class QueryHandler(object):
+ """Base class that all Query Handlers must inherit"""
+
+ facts = None
+ resolution = None
+
+ def __init__(self, expr, key, assumptions=None):
+ """
+ TODO: assumptions are not always expr
+
+ speed up putting facts locally to avoid extra attribute access
+
+ We **need** beta-rules in facts.FactorRules
+ """
+ self.expr = expr
+ self.key = key
+ self.assumptions = assumptions
diff --git a/sympy/query/handlers/__init__.py b/sympy/query/handlers/__init__.py
new file mode 100644
index 0000000..758a6e4
--- /dev/null
+++ b/sympy/query/handlers/__init__.py
@@ -0,0 +1,169 @@
+
+from sympy.core.assumptions import Assume
+from sympy.query import query, QueryHandler
+
+
+"""Facts must only have one level of indirection to avoid infinite recursion"""
+
+class CommonHandler(QueryHandler):
+ """Defines some useful methods common to most Handlers """
+ def Symbol(self):
+ if self.assumptions is None:
+ return None
+ for assump in self.assumptions:
+ if assump.expr == self.expr and assump.key == self.key:
+ return assump.bool_key
+ return None
+
+
+class QueryCommutativeHandler(CommonHandler):
+ def Basic(self):
+ for a in self.expr.args:
+ if query(a, self.key, self.assumptions) == False:
+ return False
+ else:
+ return True
+
+class QueryBoundedHandler(CommonHandler):
+
+ def Add(self):
+ """
+ Bounded + Bounded -> Bounded
+ Unbounded + Bounded -> Unbounded
+ Unbounded + Unbounded -> ?
+ """
+ _output = True
+ for arg in self.expr.args:
+ _result = query(arg, self.key, self.assumptions)
+ if _result is None:
+ return
+ elif _result is False:
+ if _output:
+ _output = False
+ else:
+ return
+ return _output
+
+ def Mul(self):
+ """
+ Bounded * Bounded -> Bounded
+ (NotInfinitesimal & Bounded) * Unbounded -> Unbounded
+ Bounded * Unbounded -> ?
+ """
+ _output = True
+ for arg in self.expr.args:
+ if not query(arg, self.key, self.assumptions):
+ if not query(arg, 'infinitesimal', self.assumptions):
+ if _output:
+ _output = False
+ else:
+ return
+ else:
+ return
+ return _output
+
+ def Number(self):
+ return True
+
+ def Infinity(self):
+ return False
+
+ def NegativeInfinity(self):
+ return False
+
+ def Pi(self):
+ return True
+
+ def Exp1(self):
+ return True
+
+ def ImaginaryUnit(self):
+ return True
+
+ def NaN(self):
+ return None
+
+class QueryUnboundedHandler(CommonHandler):
+ facts = ['unbounded == !bounded']
+
+class QueryFiniteHandler(CommonHandler):
+
+ def Mul(self):
+ for arg in self.expr.args:
+ _result = query(arg, self.key, self.assumptions)
+ if not _result:
+ return _result
+ return True
+
+ Add = Mul
+
+ def Number(self):
+ return True
+
+ def Infinity(self):
+ return False
+
+ def NegativeInfinity(self):
+ return False
+
+ def NumberSymbol(self):
+ return True
+
+ def ImaginaryUnit(self):
+ return True
+
+ def NaN(self):
+ return None
+
+class QueryInfinitesimalHandler(CommonHandler):
+
+ def Mul(self):
+ for arg in self.expr.args:
+ _result = query(arg, self.key, self.assumptions)
+ if not _result:
+ return _result
+ return True
+
+ Add = Mul
+
+ def Number(self):
+ return self.expr == 0
+
+ def Pi(self):
+ return False
+
+ def Exp1(self):
+ return False
+
+ def ImaginaryUnit(self):
+ return False
+
+ def NaN(self):
+ return None
+
+class QueryComparableHadler(CommonHandler):
+
+ def Mul(self):
+ for arg in self.expr.args:
+ _result = query(arg, self.key, self.assumptions)
+ if not _result:
+ return _result
+ return True
+
+ Add = Mul
+
+ def Number(self):
+ return True
+
+ def ImaginaryUnit(self):
+ return False
+
+ def Pi(self):
+ return True
+
+ def Exp1(self):
+ return True
+
+ def NaN(self):
+ return False
+
diff --git a/sympy/query/handlers/calculus.py b/sympy/query/handlers/calculus.py
new file mode 100644
index 0000000..5b10980
--- /dev/null
+++ b/sympy/query/handlers/calculus.py
@@ -0,0 +1,125 @@
+"""This module contains query handlers resposible for calculus queries:
+infinitesimal, bounded, etc.
+"""
+
+from sympy.query.handlers import CommonHandler
+
+class QueryInfinitesimalHandler(CommonHandler):
+
+ def Mul(self):
+ for arg in self.expr.args:
+ _result = query(arg, self.key, self.assumptions)
+ if not _result:
+ return _result
+ return True
+
+ Add = Mul
+
+ def Number(self):
+ return self.expr == 0
+
+ def Pi(self):
+ return False
+
+ def Exp1(self):
+ return False
+
+ def ImaginaryUnit(self):
+ return False
+
+ def NaN(self):
+ return None
+
+
+class QueryBoundedHandler(CommonHandler):
+
+ def Add(self):
+ """
+ Bounded + Bounded -> Bounded
+ Unbounded + Bounded -> Unbounded
+ Unbounded + Unbounded -> ?
+ """
+ _output = True
+ for arg in self.expr.args:
+ _result = query(arg, self.key, self.assumptions)
+ if _result is None:
+ return
+ elif _result is False:
+ if _output:
+ _output = False
+ else:
+ return
+ return _output
+
+ def Mul(self):
+ """
+ Bounded * Bounded -> Bounded
+ (NotInfinitesimal & Bounded) * Unbounded -> Unbounded
+ Bounded * Unbounded -> ?
+ """
+ _output = True
+ for arg in self.expr.args:
+ if not query(arg, self.key, self.assumptions):
+ if not query(arg, 'infinitesimal', self.assumptions):
+ if _output:
+ _output = False
+ else:
+ return
+ else:
+ return
+ return _output
+
+ def Number(self):
+ return True
+
+ def Infinity(self):
+ return False
+
+ def NegativeInfinity(self):
+ return False
+
+ def Pi(self):
+ return True
+
+ def Exp1(self):
+ return True
+
+ def ImaginaryUnit(self):
+ return True
+
+ def NaN(self):
+ return None
+
+
+class QueryUnboundedHandler(CommonHandler):
+ facts = ['unbounded == !bounded']
+
+
+class QueryFiniteHandler(CommonHandler):
+
+ def Mul(self):
+ for arg in self.expr.args:
+ _result = query(arg, self.key, self.assumptions)
+ if not _result:
+ return _result
+ return True
+
+ Add = Mul
+
+ def Number(self):
+ return True
+
+ def Infinity(self):
+ return False
+
+ def NegativeInfinity(self):
+ return False
+
+ def NumberSymbol(self):
+ return True
+
+ def ImaginaryUnit(self):
+ return True
+
+ def NaN(self):
+ return None


\ No newline at end of file

diff --git a/sympy/query/handlers/ntheory.py b/sympy/query/handlers/ntheory.py
new file mode 100644
index 0000000..ca8c6f7
--- /dev/null
+++ b/sympy/query/handlers/ntheory.py
@@ -0,0 +1,63 @@
+from sympy.query import query
+from sympy.query.handlers import CommonHandler
+
+
+class QueryPrimeHandler(CommonHandler):
+ facts = ['prime -> integer & positive',
+ 'prime -> nonnegative']
+
+ def Integer(self):
+ from sympy.ntheory import isprime
+ return isprime(self.expr)
+
+ def Rational(self):
+ return False
+
+ def Real(self):
+ if query(self.expr, 'integer'):
+ return query(int(self.expr), self.key)
+ else:
+ return False
+
+ def Infinity(self):
+ return None
+
+ def NegativeInfinity(self):
+ return None
+
+ def ImginaryUnit(self):
+ return False
+
+ def NaN(self):
+ return None
+
+class QueryCompositeHandler(CommonHandler):
+ facts = ['composite == positive & integer & !prime']
+
+
+class QueryEvenHandler(CommonHandler):
+ facts = ['even -> integer']
+
+ def Integer(self):
+ return self.expr % 2 == 0
+
+ def Rational(self):
+ return False
+
+ def Real(self):
+ if query(self.expr, 'integer'):
+ return int(self.expr) % 2 == 0
+ else:
+ return False
+
+ def Infinity(self):
+ return None
+
+ def NegativeInfinity(self):
+ return None
+
+ def NaN(self):
+ return None
+
+class QueryOddHandler(CommonHandler):
+ facts = ['odd == integer & !even']


\ No newline at end of file

diff --git a/sympy/query/handlers/order.py b/sympy/query/handlers/order.py
new file mode 100644
index 0000000..ff0f129
--- /dev/null
+++ b/sympy/query/handlers/order.py
@@ -0,0 +1,168 @@
+from sympy.core import Symbol
+from sympy.query import query
+from sympy.query.handlers import CommonHandler
+
+
+class QueryNegativeHandler(CommonHandler):
+ """http://en.wikipedia.org/wiki/Negative_number"""
+
+ facts = ['negative -> real',
+ 'negative -> !positive',
+ 'negative -> !zero']
+
+ def __init__(self, expr, key, assumptions=None):
+ CommonHandler.__init__(self, expr, key, assumptions)
+ if len(expr.atoms(Symbol)) == 0 and query(expr, 'comparable'):
+ # if there are no symbols just evalf
+ self.resolution = expr.evalf() < 0
+
+ def Mul(self):
+ _nonpositives = 0
+ for arg in self.expr.args:
+ _result = query(arg, 'nonpositive', self.assumptions)
+ if _result == True:
+ _nonpositives += 1
+ elif _result is None:
+ return
+ if _nonpositives % 2 == 0:
+ # we don't care about zero
+ return False
+ else:
+ if query(self.expr, 'zero', self.assumptions) == False:
+ return True
+ else:
+ return None
+
+ def Add(self):
+ """
+ Positive + Positive -> Positive,
+ Negative + Negative -> Negative
+ """
+ for arg in self.expr.args:
+ if query(arg, self.key, self.assumptions) != True:
+ break
+ else:
+ # if all argument's are positive
+ return True
+
+ def Power(self):
+ if query(self.expr.base, 'negative', self.assumptions):
+ if query(self.expr.exp, 'odd', self.assumptions):
+ return True
+ if query(self.expr.exp, 'even', self.assumptions):
+ return False
+ elif query(self.expr.base, 'positive'):
+ if query(self.expr.exp, 'real'):
+ return False
+ return None
+
+ def Nan(self):
+ return None
+
+class QueryNonNegativeHandler(CommonHandler):
+ facts = ['nonnegative == real & !negative']
+
+ def __init__(self, expr, key, assumptions=None):
+ CommonHandler.__init__(self, expr, key, assumptions)
+ if len(expr.atoms(Symbol)) == 0 and query(expr, 'comparable'):
+ # if there are no symbols just evalf
+ self.resolution = expr.evalf() >= 0
+
+class QueryZeroHandler(CommonHandler):
+ facts = ['zero -> real',
+ 'zero -> nonpositive',
+ 'zero -> nonnegative']
+
+ def __init__(self, expr, key, assumptions=None):
+ CommonHandler.__init__(self, expr, key, assumptions)
+ if len(expr.atoms(Symbol)) == 0 and query(expr, 'comparable'):
+ # if there are no symbols just evalf
+ self.resolution = expr.evalf() == 0
+
+ def Add(self):
+ return None
+
+ def Mul(self):
+ for arg in self.expr.args:
+ result = query(arg, self.key, self.assumptions)
+ if result == True:
+ return True
+ elif result is None:
+ return None
+ else:
+ return False
+
+ def Pow(self):
+ if query(self.expr.exp, self.key, self.assumptions) == False:
+ return query(self.expr.base, self.key, self.assumptions)
+
+ def NaN(self):
+ return None
+
+class QueryNonZeroHandler(CommonHandler):
+ facts = ['nonzero == real & !zero']
+
+class QueryPositiveHandler(CommonHandler):
+ facts = ['positive -> real',
+ 'positive -> !negative',
+ 'positive -> !zero']
+
+ def __init__(self, expr, key, assumptions=None):
+ CommonHandler.__init__(self, expr, key, assumptions)
+ if len(expr.atoms(Symbol)) == 0 and query(expr, 'comparable'):
+ # if there are no symbols just evalf
+ self.resolution = expr.evalf() > 0
+
+
+ def Mul(self):
+ _result = True
+ parity = 0
+ for arg in self.expr.args:
+ _result = query(arg, self.key, self.assumptions)
+ if _result == False:
+ _is_zero = query(arg, 'zero', self.assumptions)
+ if _is_zero or (_is_zero is None):
+ # if this is True or None
+ return None
+ parity += 1
+ elif _result is None:
+ return None
+ return parity % 2 == 0
+
+ def Add(self):
+ for arg in self.expr.args:
+ if query(arg, self.key, self.assumptions) != True:
+ break
+ else:
+ # if all argument's are positive
+ return True
+
+ def Power(self):
+ if query(self.expr.base, self.key, self.assumptions):
+ if query(self.expr.exp, 'odd', self.assumptions):
+ return False
+ if query(self.expr.exp, 'even', self.assumptions):
+ return False
+ elif query(self.expr.base, 'positive'):
+ if query(self.expr.exp, 'real'):
+ return True
+ return None
+
+ def abs(self):
+ return True
+
+ def exp(self):
+ if query(self.expr.args[0], 'real', self.assumptions):
+ return True
+
+ def NaN(self):
+ return None
+
+class QueryNonPositiveHandler(CommonHandler):
+ facts = ['nonpositive == real & !positive']
+
+ def __init__(self, expr, key, assumptions=None):
+ CommonHandler.__init__(self, expr, key, assumptions)
+ if len(expr.atoms(Symbol)) == 0 and query(expr, 'comparable'):
+ # if there are no symbols just evalf
+ self.resolution = expr.evalf() <= 0
diff --git a/sympy/query/handlers/sets.py b/sympy/query/handlers/sets.py
new file mode 100644
index 0000000..1cf6099
--- /dev/null
+++ b/sympy/query/handlers/sets.py
@@ -0,0 +1,239 @@
+from sympy.query import query
+from sympy.query.handlers import CommonHandler
+
+class QueryIntegerHandler(CommonHandler):
+ facts = ['integer -> rational',
+ 'integer -> real',
+ 'integer -> complex']
+
+ def Add(self):
+ """
+ Integer + Integer -> Integer
+ NonInteger + Integer -> Noninteger
+ NonInteger + NonInteger -> ?
+ """
+ _output = True
+ for arg in self.expr.args:
+ _result = query(arg, self.key, self.assumptions)
+ if _result is None:
+ return
+ elif _result is False:
+ if _output:
+ _output = False
+ else:
+ return
+ else:
+ return _output
+
+ def Mul(self):
+ """
+ Integer*Integer -> Integer
+ Integer*Irrational -> NonInteger
+ Integer*Rational -> ?
+ """
+ _output = True
+ for arg in self.expr.args:
+ if not query(arg, self.key, self.assumptions):
+ if query(arg, 'irrational', self.assumptions):
+ if _output:
+ _output = False
+ else:
+ return
+ else:
+ return
+ else:
+ return _output
+
+ def Pow(self):
+ for arg in self.expr.args:
+ if not query(arg, self.key, self.assumptions):
+ return
+ else:
+ return True
+
+ def int(self):
+ return True
+
+ def Integer(self):
+ return True
+
+ def Rational(self):
+ # rationals with denominator one get
+ # evaluated to Integers
+ return False
+
+ def Real(self):
+ return int(self.expr) == self.expr
+
+ def Pi(self):
+ return False
+
+ def Exp1(self):
+ return False
+
+ def Infinity(self):
+ return None
+
+ def NegativeInfinity(self):
+ return None
+
+ def ImaginaryUnit(self):
+ return False
+
+ def NaN(self):
+ return None
+
+
+class QueryNonIntegerHandler(CommonHandler):
+ facts = ['noninteger == real & !integer']
+
+ def Pi(self):
+ return True
+
+ def Exp1(self):
+ return True
+
+
+class QueryRationalHandler(CommonHandler):
+ facts = ['rational -> real']
+
+ def Add(self):
+ """
+ Rational + Rational -> Integer
+ Irrational + Rational -> Rational
+ Irrational + Irrational -> ?
+ """
+ _output = True
+ for arg in self.expr.args:
+ _result = query(arg, self.key, self.assumptions)
+ if _result is None:
+ return
+ elif _result is False:
+ if _output:
+ _output = False
+ else:
+ return
+ else:
+ return _output
+
+ def Mul(self):
+ """
+ Rational*Rational -> Rational
+ Rational*Irrational -> Irrational
+ Irrational*Irrational -> ?
+ """
+ _output = True
+ for arg in self.expr.args:
+ _result = query(arg, self.key, self.assumptions)
+ if _result is None:
+ return
+ elif _result is False:
+ if _output:
+ _output = False
+ else:
+ return
+ else:
+ return _output
+
+ def Rational(self):
+ return True
+
+ def Real(self):
+ # it's finite-precission
+ return True
+
+ def Infinity(self):
+ return None
+
+ def NegativeInfinity(self):
+ return None
+
+ def Pi(self):
+ return False
+
+ def Exp1(self):
+ return False
+
+ def NaN(self):
+ return None
+
+class QueryIrrationalHandler(CommonHandler):
+ facts = ['irrational == real & !rational']
+
+class QueryRealHandler(CommonHandler):
+ facts = ['real -> complex']
+
+ def Add(self):
+ for arg in self.expr.args:
+ if query(arg, self.key, self.assumptions) != True:
+ break
+ else:
+ return True
+
+ def Mul(self):
+ for arg in self.expr.args:
+ if query(arg, self.key, self.assumptions) != True:
+ break
+ else:
+ return True
+
+ def Rational(self):
+ return True
+
+ def Real(self):
+ return True
+
+ def Pi(self):
+ return True
+
+ def Exp1(self):
+ return True
+
+ def abs(self):
+ return True
+
+ def ImaginaryUnit(self):
+ return False
+
+ def NaN(self):
+ return None
+
+ def sin(self):
+ if query(self.expr.args[0], 'real', self.assumptions):
+ return True
+ cos = sin
+
+class QueryComplexHandler(CommonHandler):
+
+ def Add(self):
+ for arg in self.expr.args:
+ if not query(arg, self.key, self.assumptions):
+ return None
+ else:
+ return True
+
+ Mul = Add
+
+ def Number(self):
+ return True
+
+ def NumberSymbol(self):
+ return True
+
+ def abs(self):
+ return True
+
+ def ImaginaryUnit(self):
+ return True
+
+ def NaN(self):
+ return None
+
+class QueryImaginaryHandler(CommonHandler):
+ facts = ['imaginary == complex & !real']
+
+ def Number(self):
+ return not (self.expr.as_real_imag()[1] == 0)
+
+ def NaN(self):
+ return None
diff --git a/sympy/query/tests/test_query.py b/sympy/query/tests/test_query.py
new file mode 100644
index 0000000..6fcb210
--- /dev/null
+++ b/sympy/query/tests/test_query.py
@@ -0,0 +1,594 @@
+from sympy.core import Symbol, S, Rational, Integer, Assume
+from sympy.query import query
+
+def test_symbol_complex():


+ x = Symbol('x')

+ assert query(x, 'complex', Assume(x, 'real')) == True
+
+
+def test_integer():
+ z = 1
+ assert query(z, 'commutative') == True
+ assert query(z, 'integer') == True
+ assert query(z, 'rational') == True
+ assert query(z, 'real') == True
+ assert query(z, 'complex') == True
+ assert query(z, 'noninteger') == False
+ assert query(z, 'irrational') == False
+ assert query(z, 'imaginary') == False
+ assert query(z, 'positive') == True
+ assert query(z, 'negative') == False
+ assert query(z, 'nonpositive') == False
+ assert query(z, 'nonnegative') == True
+ assert query(z, 'even') == False
+ assert query(z, 'odd') == True
+ assert query(z, 'bounded') == True
+ assert query(z, 'unbounded') == False
+ assert query(z, 'finite') == True
+ assert query(z, 'infinitesimal') == False
+ assert query(z, 'comparable') == True
+ assert query(z, 'prime') == False
+ assert query(z, 'composite') == True
+
+def test_float():
+ z = 1.0
+ assert query(z, 'commutative') == True
+ assert query(z, 'integer') == True
+ assert query(z, 'rational') == True
+ assert query(z, 'real') == True
+ assert query(z, 'complex') == True
+ assert query(z, 'noninteger') == False
+ assert query(z, 'irrational') == False
+ assert query(z, 'imaginary') == False
+ assert query(z, 'positive') == True
+ assert query(z, 'negative') == False
+ assert query(z, 'nonpositive') == False
+ assert query(z, 'nonnegative') == True
+ assert query(z, 'even') == False
+ assert query(z, 'odd') == True
+ assert query(z, 'bounded') == True
+ assert query(z, 'unbounded') == False
+ assert query(z, 'finite') == True
+ assert query(z, 'infinitesimal') == False
+ assert query(z, 'comparable') == True
+ assert query(z, 'prime') == False
+ assert query(z, 'composite') == True
+
+def test_zero():
+ z = Integer(0)
+ assert query(z, 'zero') == True
+ assert query(z, 'commutative') == True
+ assert query(z, 'integer') == True
+ assert query(z, 'rational') == True
+ assert query(z, 'real') == True
+ assert query(z, 'complex') == True
+ assert query(z, 'imaginary') == False
+ assert query(z, 'positive') == False
+ assert query(z, 'negative') == False
+ assert query(z, 'even') == True
+ assert query(z, 'odd') == False
+ assert query(z, 'bounded') == True
+ assert query(z, 'finite') == True
+ assert query(z, 'infinitesimal') == True
+ assert query(z, 'comparable') == True
+ assert query(z, 'prime') == False
+ assert query(z, 'composite') == False
+
+def test_one():
+ z = Integer(1)
+ assert query(z, 'commutative') == True
+ assert query(z, 'integer') == True
+ assert query(z, 'rational') == True
+ assert query(z, 'real') == True
+ assert query(z, 'complex') == True
+ assert query(z, 'noninteger') == False
+ assert query(z, 'irrational') == False
+ assert query(z, 'imaginary') == False
+ assert query(z, 'positive') == True
+ assert query(z, 'negative') == False
+ assert query(z, 'nonpositive') == False
+ assert query(z, 'nonnegative') == True
+ assert query(z, 'even') == False
+ assert query(z, 'odd') == True
+ assert query(z, 'bounded') == True
+ assert query(z, 'unbounded') == False
+ assert query(z, 'finite') == True
+ assert query(z, 'infinitesimal') == False
+ assert query(z, 'comparable') == True
+ assert query(z, 'prime') == False
+ assert query(z, 'composite') == True
+
+def test_negativeone():
+ z = Integer(-1)
+ assert query(z, 'commutative') == True
+ assert query(z, 'integer') == True
+ assert query(z, 'rational') == True
+ assert query(z, 'real') == True
+ assert query(z, 'complex') == True
+ assert query(z, 'noninteger') == False
+ assert query(z, 'irrational') == False
+ assert query(z, 'imaginary') == False
+ assert query(z, 'positive') == False
+ assert query(z, 'negative') == True
+ assert query(z, 'nonpositive') == True
+ assert query(z, 'nonnegative') == False
+ assert query(z, 'even') == False
+ assert query(z, 'odd') == True
+ assert query(z, 'bounded') == True
+ assert query(z, 'unbounded') == False
+ assert query(z, 'finite') == True
+ assert query(z, 'infinitesimal') == False
+ assert query(z, 'comparable') == True
+ assert query(z, 'prime') == False
+ assert query(z, 'composite') == False
+
+def test_infinity():
+ oo = S.Infinity
+
+ assert query(oo, 'commutative') == True
+ assert query(oo, 'integer') == None
+ assert query(oo, 'rational') == None
+ assert query(oo, 'real') == True
+ assert query(oo, 'complex') == True
+ assert query(oo, 'noninteger') == None
+ assert query(oo, 'irrational') == None
+ assert query(oo, 'imaginary') == False
+ assert query(oo, 'positive') == True
+ assert query(oo, 'negative') == False
+ assert query(oo, 'nonpositive') == False
+ assert query(oo, 'nonnegative') == True
+ assert query(oo, 'even') == None
+ assert query(oo, 'odd') == None
+ assert query(oo, 'bounded') == False
+ assert query(oo, 'unbounded') == True
+ assert query(oo, 'finite') == False
+ assert query(oo, 'infinitesimal') == False
+ assert query(oo, 'comparable') == True
+ assert query(oo, 'prime') == None
+ assert query(oo, 'composite') == None
+
+def test_neg_infinity():
+ mm = S.NegativeInfinity
+
+ assert query(mm, 'commutative') == True
+ assert query(mm, 'integer') == None
+ assert query(mm, 'rational') == None
+ assert query(mm, 'real') == True
+ assert query(mm, 'complex') == True
+ assert query(mm, 'noninteger') == None
+ assert query(mm, 'irrational') == None
+ assert query(mm, 'imaginary') == False
+ assert query(mm, 'positive') == False
+ assert query(mm, 'negative') == True
+ assert query(mm, 'nonpositive') == True
+ assert query(mm, 'nonnegative') == False
+ assert query(mm, 'even') == None
+ assert query(mm, 'odd') == None
+ assert query(mm, 'bounded') == False
+ assert query(mm, 'unbounded') == True
+ assert query(mm, 'finite') == False
+ assert query(mm, 'infinitesimal') == False
+ assert query(mm, 'comparable') == True
+ assert query(mm, 'prime') == False
+ assert query(mm, 'composite') == False
+
+def test_nan():
+ nan = S.NaN
+
+ assert query(nan, 'commutative') == True
+ assert query(nan, 'integer') == None
+ assert query(nan, 'rational') == None
+ assert query(nan, 'real') == None
+ assert query(nan, 'complex') == None
+ assert query(nan, 'noninteger') == None
+ assert query(nan, 'irrational') == None
+ assert query(nan, 'imaginary') == None
+ assert query(nan, 'positive') == None
+ assert query(nan, 'negative') == None
+ assert query(nan, 'nonpositive') == None
+ assert query(nan, 'zero') == None
+ assert query(nan, 'nonnegative') == None
+ assert query(nan, 'even') == None
+ assert query(nan, 'odd') == None
+ assert query(nan, 'bounded') == None
+ assert query(nan, 'unbounded') == None
+ assert query(nan, 'finite') == None
+ assert query(nan, 'infinitesimal') == None
+ assert query(nan, 'comparable') == False
+ assert query(nan, 'prime') == None
+ assert query(nan, 'composite') == None
+
+def test_pos_rational():
+ r = Rational(3,4)
+ assert query(r, 'commutative') == True
+ assert query(r, 'integer') == False
+ assert query(r, 'rational') == True
+ assert query(r, 'real') == True
+ assert query(r, 'complex') == True
+ assert query(r, 'noninteger') == True
+ assert query(r, 'irrational') == False
+ assert query(r, 'imaginary') == False
+ assert query(r, 'positive') == True
+ assert query(r, 'negative') == False
+ assert query(r, 'nonpositive') == False
+ assert query(r, 'nonnegative') == True
+ assert query(r, 'even') == False
+ assert query(r, 'odd') == False
+ assert query(r, 'bounded') == True
+ assert query(r, 'unbounded') == False
+ assert query(r, 'finite') == True
+ assert query(r, 'infinitesimal') == False
+ assert query(r, 'comparable') == True
+ assert query(r, 'prime') == False
+ assert query(r, 'composite') == False
+
+ r = Rational(1,4)
+ assert query(r, 'nonpositive') == False
+ assert query(r, 'positive') == True
+ assert query(r, 'negative') == False
+ assert query(r, 'nonnegative') == True
+
+ r = Rational(5,4)
+ assert query(r, 'negative') == False
+ assert query(r, 'positive') == True
+ assert query(r, 'nonpositive') == False
+ assert query(r, 'nonnegative') == True
+
+ r = Rational(5,3)
+ assert query(r, 'nonnegative') == True
+ assert query(r, 'positive') == True
+ assert query(r, 'negative') == False
+ assert query(r, 'nonpositive') == False
+
+def test_neg_rational():
+ r = Rational(-3,4)
+ assert query(r, 'positive') == False
+ assert query(r, 'nonpositive') == True
+ assert query(r, 'negative') == True
+ assert query(r, 'nonnegative') == False
+
+ r = Rational(-1,4)
+ assert query(r, 'nonpositive') == True
+ assert query(r, 'positive') == False
+ assert query(r, 'negative') == True
+ assert query(r, 'nonnegative') == False
+
+ r = Rational(-5,4)
+ assert query(r, 'negative') == True
+ assert query(r, 'positive') == False
+ assert query(r, 'nonpositive') == True
+ assert query(r, 'nonnegative') == False
+
+ r = Rational(-5,3)
+ assert query(r, 'nonnegative') == False
+ assert query(r, 'positive') == False
+ assert query(r, 'negative') == True
+ assert query(r, 'nonpositive') == True
+
+def test_pi():
+ z = S.Pi
+ assert query(z, 'commutative') == True
+ assert query(z, 'integer') == False
+ assert query(z, 'rational') == False
+ assert query(z, 'real') == True
+ assert query(z, 'complex') == True
+ assert query(z, 'noninteger') == True
+ assert query(z, 'irrational') == True
+ assert query(z, 'imaginary') == False
+ assert query(z, 'positive') == True
+ assert query(z, 'negative') == False
+ assert query(z, 'nonpositive') == False
+ assert query(z, 'nonnegative') == True
+ assert query(z, 'even') == False
+ assert query(z, 'odd') == False
+ assert query(z, 'bounded') == True
+ assert query(z, 'unbounded') == False
+ assert query(z, 'finite') == True
+ assert query(z, 'infinitesimal') == False
+ assert query(z, 'comparable') == True
+ assert query(z, 'prime') == False
+ assert query(z, 'composite') == False
+
+def test_pi_add():
+ z = S.Pi + 1
+ assert query(z, 'commutative') == True
+ assert query(z, 'integer') == False
+ assert query(z, 'rational') == False
+ assert query(z, 'real') == True
+ assert query(z, 'complex') == True
+ assert query(z, 'noninteger') == True
+ assert query(z, 'irrational') == True
+ assert query(z, 'imaginary') == False
+ assert query(z, 'positive') == True
+ assert query(z, 'negative') == False
+ assert query(z, 'nonpositive') == False
+ assert query(z, 'nonnegative') == True
+ assert query(z, 'even') == False
+ assert query(z, 'odd') == False
+ assert query(z, 'bounded') == True
+ assert query(z, 'unbounded') == False
+ assert query(z, 'finite') == True
+ assert query(z, 'infinitesimal') == False
+ assert query(z, 'comparable') == True
+ assert query(z, 'prime') == False
+ assert query(z, 'composite') == False
+
+def test_pi_mul():
+ z = 2*S.Pi
+ assert query(z, 'commutative') == True
+ assert query(z, 'integer') == False
+ assert query(z, 'rational') == False
+ assert query(z, 'real') == True
+ assert query(z, 'complex') == True
+ assert query(z, 'noninteger') == True
+ assert query(z, 'irrational') == True
+ assert query(z, 'imaginary') == False
+ assert query(z, 'positive') == True
+ assert query(z, 'negative') == False
+ assert query(z, 'nonpositive') == False
+ assert query(z, 'nonnegative') == True
+ assert query(z, 'even') == False
+ assert query(z, 'odd') == False
+ assert query(z, 'bounded') == True
+ assert query(z, 'unbounded') == False
+ assert query(z, 'finite') == True
+ assert query(z, 'infinitesimal') == False
+ assert query(z, 'comparable') == True
+ assert query(z, 'prime') == False
+ assert query(z, 'composite') == False
+
+def test_E():
+ z = S.Exp1
+ assert query(z, 'commutative') == True
+ assert query(z, 'integer') == False
+ assert query(z, 'rational') == False
+ assert query(z, 'real') == True
+ assert query(z, 'complex') == True
+ assert query(z, 'noninteger') == True
+ assert query(z, 'irrational') == True
+ assert query(z, 'imaginary') == False
+ assert query(z, 'positive') == True
+ assert query(z, 'negative') == False
+ assert query(z, 'nonpositive') == False
+ assert query(z, 'nonnegative') == True
+ assert query(z, 'even') == False
+ assert query(z, 'odd') == False
+ assert query(z, 'bounded') == True
+ assert query(z, 'unbounded') == False
+ assert query(z, 'finite') == True
+ assert query(z, 'infinitesimal') == False
+ assert query(z, 'comparable') == True
+ assert query(z, 'prime') == False
+ assert query(z, 'composite') == False
+
+def test_I():
+ z = S.ImaginaryUnit
+ assert query(z, 'comparable') == False
+ assert query(z, 'commutative') == True
+ assert query(z, 'integer') == False
+ assert query(z, 'rational') == False
+ assert query(z, 'real') == False
+ assert query(z, 'complex') == True
+ assert query(z, 'noninteger') == False
+ assert query(z, 'irrational') == False
+ assert query(z, 'imaginary') == True
+ assert query(z, 'positive') == False
+ assert query(z, 'negative') == False
+ assert query(z, 'nonpositive') == False
+ assert query(z, 'nonnegative') == False
+ assert query(z, 'even') == False
+ assert query(z, 'odd') == False
+ assert query(z, 'bounded') == True
+ assert query(z, 'unbounded') == False
+ assert query(z, 'finite') == True
+ assert query(z, 'infinitesimal') == False
+ assert query(z, 'prime') == False
+ assert query(z, 'composite') == False
+
+def test_symbol_zero():


+ x = Symbol('x')

+ assumptions = Assume(x, 'zero')
+ assert query(x, 'zero', assumptions) == True
+ assert query(x, 'nonpositive', assumptions) == True
+ assert query(x, 'negative', assumptions) == False
+ assert query(x, 'nonnegative', assumptions) == True
+ assert query(x, 'zero', assumptions) == True
+ assert query(x, 'nonzero', assumptions) == False
+
+def test_symbol_positive():


+ x = Symbol('x')

+ assumptions = Assume(x, 'positive')
+ assert query( x, 'positive', assumptions) == True
+ assert query( x, 'nonpositive', assumptions) == False
+ assert query( x, 'negative', assumptions) == False
+ assert query( x, 'nonnegative', assumptions) == True
+ assert query( x, 'zero', assumptions) == False
+ assert query( x, 'nonzero', assumptions) == True
+
+ #test -x
+ assert query(-x, 'positive', assumptions) == False
+ assert query(-x, 'nonpositive', assumptions) == True
+ assert query(-x, 'negative', assumptions) == True
+ assert query(-x, 'nonnegative', assumptions) == False
+ assert query(-x, 'zero', assumptions) == False
+
+def test_symbol_positive_multiple():
+ #test with multiple symbols
+ x, y, z, w = Symbol('x'), Symbol('y'), Symbol('z'), Symbol('w')
+ assumptions = [ Assume(x, 'positive'),
+ Assume(y, 'negative'),
+ Assume(z, 'negative'),
+ Assume(w, 'positive')]
+ assert query( x*y*z, 'positive') == None
+ assert query( x*y*z, 'positive', assumptions) == True
+ assert query(-x*y*z, 'positive', assumptions) == False
+
+ assert query( y+z, 'negative', assumptions) == True
+ assert query( y+z, 'nonpositive', assumptions) == True
+ assert query( x+y, 'positive', assumptions) == None
+ assert query( x+y, 'negative', assumptions) == None
+
+def test_symbol_nonpositive():


+ x = Symbol('x')

+ assumptions = Assume(x, 'nonpositive')
+ assert query(x, 'nonpositive') == None
+ assert query(x, 'positive', assumptions) == False
+ assert query(x, 'nonpositive', assumptions) == True
+ assert query(x, 'negative', assumptions) == None
+ assert query(x, 'nonnegative', assumptions) == None
+ assert query(x, 'zero', assumptions) == None
+ assert query(x, 'nonzero', assumptions) == None
+
+def test_neg__symbol_nonpositive():


+ x = Symbol('x')

+ assumptions = Assume(x, 'nonpositive')
+ assert query(-x, 'positive', assumptions) == None
+ assert query(-x, 'nonpositive', assumptions) == None
+ assert query(-x, 'negative', assumptions) == False
+ assert query(-x, 'nonnegative', assumptions) == True
+ assert query(-x, 'zero', assumptions) == None
+ assert query(-x, 'nonzero', assumptions) == None
+
+def test_symbol_negative():


+ x = Symbol('x')

+ y = Symbol('y')
+ assert query(x, 'negative', Assume(x, 'negative')) == True
+ assert query(x, 'negative', Assume(x, 'positive')) == False
+ assert query(x+y, 'negative', [Assume(x, 'negative'), Assume(y, 'negative')]) == True
+
+def test_prime_symbol():


+ x = Symbol('x')

+ assumptions = Assume(x, 'prime')
+ assert query(x, 'prime', assumptions) == True
+ assert query(x, 'integer', assumptions) == True
+ assert query(x, 'positive', assumptions) == True
+ assert query(x, 'negative', assumptions) == False
+ assert query(x, 'nonpositive', assumptions) == False
+ assert query(x, 'nonnegative', assumptions) == True
+
+def test_not_prime_symbol():
+ # assumptions with False bool_key are not implemented


+ x = Symbol('x')

+ assumptions = Assume(x, 'prime', False)
+ assert query(x, 'prime', assumptions) == False
+ assert query(x, 'integer', assumptions) == None
+ assert query(x, 'positive', assumptions) == None
+ assert query(x, 'negative', assumptions) == None
+ assert query(x, 'nonpositive', assumptions) == None
+ assert query(x, 'nonnegative', assumptions) == None
+
+def test_symbol_integer():


+ x = Symbol('x')

+ assumptions = Assume(x, 'integer')
+ assert query(x, 'integer', assumptions) == True
+ assert query(x, 'real', assumptions) == True
+ assert query(x, 'complex', assumptions) == True
+ assert query(2*x, 'integer', assumptions) == True
+ assert query(x**2, 'integer', assumptions) == True
+ assert query(x**x, 'integer', assumptions) == True
+
+def test_symbol_integer_nonnegative():


+ x = Symbol('x')

+ assumptions = [Assume(x, 'integer'), Assume(x, 'nonnegative')]
+ assert query(x, 'integer', assumptions) == True
+ assert query(x, 'nonnegative', assumptions) == True
+ assert query(x, 'negative', assumptions) == False
+ assert query(x, 'positive', assumptions) == None
+
+def test_symbol_integer_nonpositive():


+ x = Symbol('x')

+ assumptions = [Assume(x, 'integer'), Assume(x, 'nonpositive')]
+ assert query(x, 'integer', assumptions) == True
+ assert query(x, 'nonpositive', assumptions) == True
+ assert query(x, 'positive', assumptions) == False
+ assert query(x, 'negative', assumptions) == None
+
+def test_symbol_odd():


+ x = Symbol('x')

+ assumptions = Assume(x, 'odd')
+ assert query(x, 'odd', assumptions) == True
+ assert query(x, 'even', assumptions) == False
+ assert query(x, 'integer', assumptions) == True
+
+def test_symbol_even():


+ x = Symbol('x')

+ assumptions = Assume(x, 'even')
+ assert query(x, 'even', assumptions) == True
+ assert query(x, 'odd', assumptions) == False
+ assert query(x, 'integer', assumptions) == True
+
+def test_symbol_integer_nonnegative():


+ x = Symbol('x')

+ assumptions = [Assume(x, 'integer'), Assume(x, 'nonnegative')]
+ assert query(x, 'integer', assumptions) == True
+ assert query(x, 'nonnegative', assumptions) == True
+
+def test_symbol_integer_nonpositive():


+ x = Symbol('x')

+ assumptions = [Assume(x, 'integer'), Assume(x, 'nonpositive')]
+ assert query(x, 'integer', assumptions) == True
+ assert query(x, 'nonpositive', assumptions) == True
+
+def test_symbol_odd_False():
+ # XXX x.is_even currently will be True


+ x = Symbol('x')

+ assumptions = Assume(x, 'odd', False)
+ assert query(x, 'odd', assumptions) == False
+ assert query(x, 'even', assumptions) == None
+ assert query(x, 'integer', assumptions) == None
+
+def test_symbol_symbol_fail2():
+ # XXX x.is_odd currently will be True


+ x = Symbol('x')

+ assumptions = Assume(x, 'even', False)
+ assert query(x, 'even', assumptions) == False
+ assert query(x, 'odd', assumptions) == None
+ assert query(x, 'integer', assumptions) == None
+
+def test_symbol_real_False():
+ a = Symbol('a')
+
+ assumptions = Assume(a, 'real', False)
+ assert query(a, 'real', assumptions) == False
+ assert query(a, 'integer', assumptions) == False
+ assert query(a, 'negative', assumptions) == False
+ assert query(a, 'positive', assumptions) == False
+ assert query(a, 'nonnegative', assumptions) == False
+ assert query(a, 'nonpositive', assumptions) == False
+ assert query(a, 'zero', assumptions) == False
+
+def test_abs():


+ x = Symbol('x')

+ assert query(abs(x), 'positive') == True
+ assert query(abs(x), 'positive', Assume(x, 'negative')) == True
+ assert query(abs(x), 'negative') == False
+ assert query(abs(x), 'real') == True
+ assert query(abs(x), 'complex') == True
+ assert query(abs(x), 'odd') == None
+
+def test_trigonometric():
+ from sympy.functions import sin, cos


+ x = Symbol('x')

+ assert query(sin(x), 'real') == None
+ assert query(cos(x), 'real') == None
+ assert query(sin(x), 'real', Assume(x, 'real')) == True
+ assert query(cos(x), 'real', Assume(x, 'real')) == True
+
+def test_x_exp():
+ from sympy.functions import exp


+ x = Symbol('x')

+ assert query(exp(x), 'positive', Assume(x, 'real')) == True
+ assert query(x + exp(x), 'positive', Assume(x, 'real')) == None
+
+def test_hash_vs_eq():
+ """catch: different hash for equal objects"""
+ a = 1 + S.Pi # important: do not fold it into a Number instance
+ ha= hash(a) # it should be Add/Mul/... to trigger the bug
+ b = a.expand(trig=True)
+ hb= hash(b)
+
+ assert query(a, 'positive') == True # this uses .evalf() and deduces it is positive
+ # be sure that hash stayed the same
+ assert a == b
+ assert ha == hash(a)
+ assert ha== hb
--
1.6.1.2

Fabian Seoane

unread,
Mar 16, 2009, 11:13:31 AM3/16/09
to sympy-...@googlegroups.com, Fabian Seoane
---
doc/src/modules.txt | 1 +
doc/src/modules/query.txt | 85 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 86 insertions(+), 0 deletions(-)
create mode 100644 doc/src/modules/query.txt

diff --git a/doc/src/modules.txt b/doc/src/modules.txt
index 0232b4d..d13a655 100644
--- a/doc/src/modules.txt
+++ b/doc/src/modules.txt
@@ -27,6 +27,7 @@ access any SymPy module, or use this contens:
modules/geometry.txt
modules/galgebra/GA/GAsympy.txt
modules/statistics.txt
+ modules/query.txt
modules/concrete.txt
modules/solvers.txt

diff --git a/doc/src/modules/query.txt b/doc/src/modules/query.txt
new file mode 100644
index 0000000..df9aaa1
--- /dev/null
+++ b/doc/src/modules/query.txt
@@ -0,0 +1,85 @@
+Query module
+============
+
+This module is responsible for inferring properties from objects
+
+Supported queries
+-----------------
+
+ - *bounded*
+ - *commutative*
+ - *comparable*
+ - *complex*
+ - *composite*
+ - *even*
+ - *finite*
+ - *imaginary*
+ - *infinitesimal*
+ - *integer*
+ - *irrational*
+ - *rational*
+ - *nonnegative*
+ - *noninteger*
+ - *nonpositive*
+ - *nonzero*
+ - *negative*
+ - *positive*
+ - *prime*
+ - *real*
+ - *odd*
+ - *unbounded*
+ - *zero*
+
+Examples
+--------
+ >>> from sympy import Symbol, Assume, query
+ >>> query(2, 'integer')
+ True
+ >>> query(2, 'rational')
+ True
+ >>> query(1.5, 'integer')
+ Flase


+ >>> x = Symbol('x')

+ >>> query(x, 'integer', Assume(x, 'even'))
+ True
+
+You can find more examples in the in the form of test under directory sympy/query/tests/
+
+
+
+Design
+------
+Each time query is called, the appropriate Handler for the current key is called. This is
+always a subclass of sympy.query.QueryHandler. It's classmethods have the name's of the classes
+it supports. For example,
+
+For example, a (simplified) QueryHandler for the query 'positive' would look like this::
+
+ class QueryPositiveHandler(CommonHandler):
+
+ def Mul(self):
+ # return True if all argument's in self.expr.args are positive
+ ...


+
+ def Add(self):
+ for arg in self.expr.args:
+ if query(arg, self.key, self.assumptions) != True:
+ break
+ else:
+ # if all argument's are positive
+ return True

+ ...
+
+So that the .Mul() method would be called when self.expr is an instance of Mul, the Add method
+would be called when self.expr is an instance of Add and so on.
+
+
+Extensibility
+-------------
+You can define your own queries by subclassing sympy.query.QueryHandler and passing
+to query a a qdict argument consting of the query's string and the appropriate
+handler. For example::
+
+ query(expr, 'my_query', qdict={'my_query': MyQueryHandler})
+
+would call MyQueryHandler to resolve the query 'my_query'


\ No newline at end of file

--
1.6.1.2

Ondrej Certik

unread,
Mar 16, 2009, 12:02:16 PM3/16/09
to sympy-...@googlegroups.com
Let's push this in after the release?

I am now going to review the patches.

Ondrej

Ondrej Certik

unread,
Mar 16, 2009, 12:04:40 PM3/16/09
to sympy-...@googlegroups.com, Fabian Seoane
On Mon, Mar 16, 2009 at 8:13 AM, Fabian Seoane <fab...@fseoane.net> wrote:
>

Maybe add an example:

>>> variations([1,2,3], 2, True)

[[1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], \

[3,1], [3,2], [3,3]]

> +


> +    variations(seq, n, False) will return all the variations of the list of
> +        size n without repetitions
> +    """

Maybe add an example

>>> variations([1,2,3], 2)


[[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]

Otherwise +1

Ondrej

Ondrej Certik

unread,
Mar 16, 2009, 12:07:42 PM3/16/09
to sympy-...@googlegroups.com, Fabian Seoane
On Mon, Mar 16, 2009 at 8:13 AM, Fabian Seoane <fab...@fseoane.net> wrote:
>

Could there be tests for both of the functions above? It's not exactly
clear what they are doing from the docstrings. Also code like this is
really hackish:

getattr(__import__(mod_name, {}, {}, ['']), func_name)

If this is needed, I think it deserves a thorough docstrings and
comments why this is needed and what it does.

Ondrej

Ondrej Certik

unread,
Mar 16, 2009, 12:12:37 PM3/16/09
to sympy-...@googlegroups.com, Fabian Seoane
On Mon, Mar 16, 2009 at 8:13 AM, Fabian Seoane <fab...@fseoane.net> wrote:
>
^^^^ I think those Assume tests should go to the next patch.

Otherwise +1.

Ondrej Certik

unread,
Mar 16, 2009, 12:14:09 PM3/16/09
to sympy-...@googlegroups.com
This looks good, +1

On Mon, Mar 16, 2009 at 8:13 AM, Fabian Seoane <fab...@fseoane.net> wrote:
>

Ondrej Certik

unread,
Mar 16, 2009, 12:29:40 PM3/16/09
to sympy-...@googlegroups.com
At first sight it looks pretty good, great job!

I want to play with this a bit more in the coming days in your branch.
Probably more docstrings could be written, if I find time, I'll write
some and send a patch.

Ondrej

On Mon, Mar 16, 2009 at 8:13 AM, Fabian Seoane <fab...@fseoane.net> wrote:
>

Fabian Seoane

unread,
Mar 16, 2009, 3:22:45 PM3/16/09
to sympy-...@googlegroups.com

yeah, I know it looks hackish, but it is copied from a similar utility
in the django codebase, so i'm
quite sure that there isn't a better way of doing it

It sure lacks docstrings and tests, so I'll send a reworked patch or
this and the other modules soon.

Thanks for the review,

Reply all
Reply to author
Forward
0 new messages