"""Module for querying SymPy objects about assumptions."""
from __future__ import print_function, division
from sympy.core import sympify
from sympy.logic.boolalg import (to_cnf, And, Not, Or, Implies, Equivalent,
BooleanFunction, true, false, BooleanAtom)
from sympy.logic.inference import satisfiable
from sympy.assumptions.assume import (global_assumptions, Predicate,
AppliedPredicate)
[docs]class Q:
"""Supported ask keys."""
antihermitian = Predicate('antihermitian')
bounded = Predicate('bounded')
commutative = Predicate('commutative')
complex = Predicate('complex')
composite = Predicate('composite')
even = Predicate('even')
extended_real = Predicate('extended_real')
hermitian = Predicate('hermitian')
imaginary = Predicate('imaginary')
infinitesimal = Predicate('infinitesimal')
infinity = Predicate('infinity')
integer = Predicate('integer')
irrational = Predicate('irrational')
rational = Predicate('rational')
negative = Predicate('negative')
nonzero = Predicate('nonzero')
positive = Predicate('positive')
prime = Predicate('prime')
real = Predicate('real')
odd = Predicate('odd')
is_true = Predicate('is_true')
nonpositive = Predicate('nonpositive')
nonnegative = Predicate('nonnegative')
zero = Predicate('zero')
symmetric = Predicate('symmetric')
invertible = Predicate('invertible')
singular = Predicate('singular')
orthogonal = Predicate('orthogonal')
unitary = Predicate('unitary')
normal = Predicate('normal')
positive_definite = Predicate('positive_definite')
upper_triangular = Predicate('upper_triangular')
lower_triangular = Predicate('lower_triangular')
diagonal = Predicate('diagonal')
triangular = Predicate('triangular')
unit_triangular = Predicate('unit_triangular')
fullrank = Predicate('fullrank')
square = Predicate('square')
real_elements = Predicate('real_elements')
complex_elements = Predicate('complex_elements')
integer_elements = Predicate('integer_elements')
def _extract_facts(expr, symbol):
"""
Helper for ask().
Extracts the facts relevant to the symbol from an assumption.
Returns None if there is nothing to extract.
"""
if isinstance(expr, bool):
return
if not expr.has(symbol):
return
if isinstance(expr, AppliedPredicate):
if expr.arg == symbol:
return expr.func
else:
return
args = [_extract_facts(arg, symbol) for arg in expr.args]
if isinstance(expr, And):
args = [x for x in args if x is not None]
if args:
return expr.func(*args)
if args and all(x != None for x in args):
return expr.func(*args)
[docs]def ask(proposition, assumptions=True, context=global_assumptions):
"""
Method for inferring properties about objects.
**Syntax**
* ask(proposition)
* ask(proposition, assumptions)
where ``proposition`` is any boolean expression
Examples
========
>>> from sympy import ask, Q, pi
>>> from sympy.abc import x, y
>>> ask(Q.rational(pi))
False
>>> ask(Q.even(x*y), Q.even(x) & Q.integer(y))
True
>>> ask(Q.prime(x*y), Q.integer(x) & Q.integer(y))
False
**Remarks**
Relations in assumptions are not implemented (yet), so the following
will not give a meaningful result.
>>> ask(Q.positive(x), Q.is_true(x > 0)) # doctest: +SKIP
It is however a work in progress.
"""
if not isinstance(proposition, (BooleanFunction, AppliedPredicate, bool, BooleanAtom)):
raise TypeError("proposition must be a valid logical expression")
if not isinstance(assumptions, (BooleanFunction, AppliedPredicate, bool, BooleanAtom)):
raise TypeError("assumptions must be a valid logical expression")
if isinstance(proposition, AppliedPredicate):
key, expr = proposition.func, sympify(proposition.arg)
else:
key, expr = Q.is_true, sympify(proposition)
assumptions = And(assumptions, And(*context))
assumptions = to_cnf(assumptions)
local_facts = _extract_facts(assumptions, expr)
if local_facts and satisfiable(And(local_facts, known_facts_cnf)) is False:
raise ValueError("inconsistent assumptions %s" % assumptions)
# direct resolution method, no logic
res = key(expr)._eval_ask(assumptions)
if res is not None:
return res
if assumptions == True:
return
if local_facts is None:
return
# See if there's a straight-forward conclusion we can make for the inference
if local_facts.is_Atom:
if key in known_facts_dict[local_facts]:
return True
if Not(key) in known_facts_dict[local_facts]:
return False
elif local_facts.func is And and all(k in known_facts_dict for k in local_facts.args):
for assum in local_facts.args:
if assum.is_Atom:
if key in known_facts_dict[assum]:
return True
if Not(key) in known_facts_dict[assum]:
return False
elif assum.func is Not and assum.args[0].is_Atom:
if key in known_facts_dict[assum]:
return False
if Not(key) in known_facts_dict[assum]:
return True
elif (isinstance(key, Predicate) and
local_facts.func is Not and local_facts.args[0].is_Atom):
if local_facts.args[0] in known_facts_dict[key]:
return False
# Failing all else, we do a full logical inference
return ask_full_inference(key, local_facts, known_facts_cnf)
[docs]def ask_full_inference(proposition, assumptions, known_facts_cnf):
"""
Method for inferring properties about objects.
"""
if not satisfiable(And(known_facts_cnf, assumptions, proposition)):
return False
if not satisfiable(And(known_facts_cnf, assumptions, Not(proposition))):
return True
return None
[docs]def register_handler(key, handler):
"""
Register a handler in the ask system. key must be a string and handler a
class inheriting from AskHandler::
>>> from sympy.assumptions import register_handler, ask, Q
>>> from sympy.assumptions.handlers import AskHandler
>>> class MersenneHandler(AskHandler):
... # Mersenne numbers are in the form 2**n + 1, n integer
... @staticmethod
... def Integer(expr, assumptions):
... import math
... return ask(Q.integer(math.log(expr + 1, 2)))
>>> register_handler('mersenne', MersenneHandler)
>>> ask(Q.mersenne(7))
True
"""
if type(key) is Predicate:
key = key.name
try:
getattr(Q, key).add_handler(handler)
except AttributeError:
setattr(Q, key, Predicate(key, handlers=[handler]))
[docs]def remove_handler(key, handler):
"""Removes a handler from the ask system. Same syntax as register_handler"""
if type(key) is Predicate:
key = key.name
getattr(Q, key).remove_handler(handler)
def single_fact_lookup(known_facts_keys, known_facts_cnf):
# Compute the quick lookup for single facts
mapping = {}
for key in known_facts_keys:
mapping[key] = set([key])
for other_key in known_facts_keys:
if other_key != key:
if ask_full_inference(other_key, key, known_facts_cnf):
mapping[key].add(other_key)
return mapping
[docs]def compute_known_facts(known_facts, known_facts_keys):
"""Compute the various forms of knowledge compilation used by the
assumptions system.
This function is typically applied to the variables
``known_facts`` and ``known_facts_keys`` defined at the bottom of
this file.
"""
from textwrap import dedent, wrap
fact_string = dedent('''\
"""
The contents of this file are the return value of
``sympy.assumptions.ask.compute_known_facts``. Do NOT manually
edit this file. Instead, run ./bin/ask_update.py.
"""
from sympy.logic.boolalg import And, Not, Or
from sympy.assumptions.ask import Q
# -{ Known facts in CNF }-
known_facts_cnf = And(
%s
)
# -{ Known facts in compressed sets }-
known_facts_dict = {
%s
}
''')
# Compute the known facts in CNF form for logical inference
LINE = ",\n "
HANG = ' '*8
cnf = to_cnf(known_facts)
c = LINE.join([str(a) for a in cnf.args])
mapping = single_fact_lookup(known_facts_keys, cnf)
items = sorted(mapping.items(), key=str)
keys = [str(i[0]) for i in items]
values = ['set(%s)' % sorted(i[1], key=str) for i in items]
m = LINE.join(['\n'.join(
wrap("%s: %s" % (k, v),
subsequent_indent=HANG,
break_long_words=False))
for k, v in zip(keys, values)]) + ','
return fact_string % (c, m)
# handlers tells us what ask handler we should use
# for a particular key
_val_template = 'sympy.assumptions.handlers.%s'
_handlers = [
("antihermitian", "sets.AskAntiHermitianHandler"),
("bounded", "calculus.AskBoundedHandler"),
("commutative", "AskCommutativeHandler"),
("complex", "sets.AskComplexHandler"),
("composite", "ntheory.AskCompositeHandler"),
("even", "ntheory.AskEvenHandler"),
("extended_real", "sets.AskExtendedRealHandler"),
("hermitian", "sets.AskHermitianHandler"),
("imaginary", "sets.AskImaginaryHandler"),
("infinitesimal", "calculus.AskInfinitesimalHandler"),
("integer", "sets.AskIntegerHandler"),
("irrational", "sets.AskIrrationalHandler"),
("rational", "sets.AskRationalHandler"),
("negative", "order.AskNegativeHandler"),
("nonzero", "order.AskNonZeroHandler"),
("nonpositive", "order.AskNonPositiveHandler"),
("nonnegative", "order.AskNonNegativeHandler"),
("zero", "order.AskZeroHandler"),
("positive", "order.AskPositiveHandler"),
("prime", "ntheory.AskPrimeHandler"),
("real", "sets.AskRealHandler"),
("odd", "ntheory.AskOddHandler"),
("algebraic", "sets.AskAlgebraicHandler"),
("is_true", "TautologicalHandler"),
("symmetric", "matrices.AskSymmetricHandler"),
("invertible", "matrices.AskInvertibleHandler"),
("orthogonal", "matrices.AskOrthogonalHandler"),
("unitary", "matrices.AskUnitaryHandler"),
("positive_definite", "matrices.AskPositiveDefiniteHandler"),
("upper_triangular", "matrices.AskUpperTriangularHandler"),
("lower_triangular", "matrices.AskLowerTriangularHandler"),
("diagonal", "matrices.AskDiagonalHandler"),
("fullrank", "matrices.AskFullRankHandler"),
("square", "matrices.AskSquareHandler"),
("integer_elements", "matrices.AskIntegerElementsHandler"),
("real_elements", "matrices.AskRealElementsHandler"),
("complex_elements", "matrices.AskComplexElementsHandler"),
]
for name, value in _handlers:
register_handler(name, _val_template % value)
known_facts_keys = [getattr(Q, attr) for attr in Q.__dict__
if not attr.startswith('__')]
known_facts = And(
Implies(Q.real, Q.complex),
Implies(Q.real, Q.hermitian),
Equivalent(Q.even, Q.integer & ~Q.odd),
Equivalent(Q.extended_real, Q.real | Q.infinity),
Equivalent(Q.odd, Q.integer & ~Q.even),
Equivalent(Q.prime, Q.integer & Q.positive & ~Q.composite),
Implies(Q.integer, Q.rational),
Implies(Q.rational, Q.algebraic),
Implies(Q.algebraic, Q.complex),
Implies(Q.imaginary, Q.complex & ~Q.real),
Implies(Q.imaginary, Q.antihermitian),
Implies(Q.antihermitian, ~Q.hermitian),
Equivalent(Q.negative, Q.nonzero & ~Q.positive),
Equivalent(Q.positive, Q.nonzero & ~Q.negative),
Equivalent(Q.rational, Q.real & ~Q.irrational),
Equivalent(Q.real, Q.rational | Q.irrational),
Implies(Q.nonzero, Q.real),
Equivalent(Q.nonzero, Q.positive | Q.negative),
Equivalent(Q.nonpositive, ~Q.positive & Q.real),
Equivalent(Q.nonnegative, ~Q.negative & Q.real),
Equivalent(Q.zero, Q.real & ~Q.nonzero),
Implies(Q.zero, Q.even),
Implies(Q.orthogonal, Q.positive_definite),
Implies(Q.orthogonal, Q.unitary),
Implies(Q.unitary & Q.real, Q.orthogonal),
Implies(Q.unitary, Q.normal),
Implies(Q.unitary, Q.invertible),
Implies(Q.normal, Q.square),
Implies(Q.diagonal, Q.normal),
Implies(Q.positive_definite, Q.invertible),
Implies(Q.diagonal, Q.upper_triangular),
Implies(Q.diagonal, Q.lower_triangular),
Implies(Q.lower_triangular, Q.triangular),
Implies(Q.upper_triangular, Q.triangular),
Implies(Q.triangular, Q.upper_triangular | Q.lower_triangular),
Implies(Q.upper_triangular & Q.lower_triangular, Q.diagonal),
Implies(Q.diagonal, Q.symmetric),
Implies(Q.unit_triangular, Q.triangular),
Implies(Q.invertible, Q.fullrank),
Implies(Q.invertible, Q.square),
Implies(Q.symmetric, Q.square),
Implies(Q.fullrank & Q.square, Q.invertible),
Equivalent(Q.invertible, ~Q.singular),
Implies(Q.integer_elements, Q.real_elements),
Implies(Q.real_elements, Q.complex_elements),
)
from sympy.assumptions.ask_generated import known_facts_dict, known_facts_cnf