def get_relevant_facts(proposition, assumptions=None, context=None, exprs=None, relevant_facts=None): newexprs = set() if not assumptions: assumptions = CNF({S.true}) if not relevant_facts: relevant_facts = set() def find_symbols(pred): if isinstance(pred, CNF): symbols = set() for a in pred.all_predicates(): symbols |= find_symbols(a) return symbols if isinstance(pred.args, AppliedPredicate): return {pred.args[0]} return pred.atoms(Symbol) if not exprs: req_keys = find_symbols(proposition) keys = proposition.all_predicates() # XXX: We need this since True/False are not Basic lkeys = set() lkeys |= assumptions.all_predicates() if context: lkeys |= context.all_predicates() lkeys = lkeys - {S.true, S.false} tmp_keys = None while tmp_keys != set(): tmp = set() for l in lkeys: syms = find_symbols(l) if (syms & req_keys) != set(): tmp |= syms tmp_keys = tmp - req_keys req_keys |= tmp_keys keys |= {l for l in lkeys if find_symbols(l) & req_keys != set()} exprs = { key.args[0] if isinstance(key, AppliedPredicate) else key for key in keys } return exprs, relevant_facts for expr in exprs: for fact in fact_registry[expr.func]: newfact = fact.rcall(expr) relevant_facts.add(newfact) newexprs |= set( [key.args[0] for key in newfact.atoms(AppliedPredicate)]) return newexprs - exprs, relevant_facts
def get_all_relevant_facts( proposition, assumptions=True, context=global_assumptions, use_known_facts=True, iterations=oo, ): # The relevant facts might introduce new keys, e.g., Q.zero(x*y) will # introduce the keys Q.zero(x) and Q.zero(y), so we need to run it until # we stop getting new things. Hopefully this strategy won't lead to an # infinite loop in the future. i = 0 relevant_facts = CNF() exprs = None all_exprs = set() while exprs != set(): exprs, relevant_facts = get_relevant_facts( proposition, assumptions, context, exprs=exprs, relevant_facts=relevant_facts, ) all_exprs |= exprs i += 1 if i >= iterations: break if use_known_facts: known_facts_CNF = CNF() known_facts_CNF.add_clauses(get_all_known_facts()) kf_encoded = EncodedCNF() kf_encoded.from_cnf(known_facts_CNF) def translate_literal(lit, delta): if lit > 0: return lit + delta else: return lit - delta def translate_data(data, delta): return [{translate_literal(i, delta) for i in clause} for clause in data] data = [] symbols = [] n_lit = len(kf_encoded.symbols) for i, expr in enumerate(all_exprs): symbols += [pred(expr) for pred in kf_encoded.symbols] data += translate_data(kf_encoded.data, i * n_lit) encoding = dict(list(zip(symbols, range(1, len(symbols) + 1)))) ctx = EncodedCNF(data, encoding) else: ctx = EncodedCNF() ctx.add_from_cnf(relevant_facts) return ctx
def test_extract_predargs(): props = CNF.from_prop(Q.zero(Abs(x * y)) & Q.zero(x * y)) assump = CNF.from_prop(Q.zero(x)) context = CNF.from_prop(Q.zero(y)) assert extract_predargs(props) == {Abs(x * y), x * y} assert extract_predargs(props, assump) == {Abs(x * y), x * y, x} assert extract_predargs(props, assump, context) == {Abs(x * y), x * y, x, y}
def satask( proposition, assumptions=True, context=global_assumptions, use_known_facts=True, iterations=oo, ): props = CNF.from_prop(proposition) _props = CNF.from_prop(~proposition) if context: tmp = CNF() context = tmp.extend(context) assumptions = CNF.from_prop(assumptions) sat = get_all_relevant_facts( props, assumptions, context, use_known_facts=use_known_facts, iterations=iterations, ) if context: sat.add_from_cnf(context) sat.add_from_cnf(assumptions) return check_satisfiability(props, _props, sat)
def _extract_all_facts(assump, exprs): """ Extract all relevant assumptions from *assump* with respect to given *exprs*. Parameters ========== assump : sympy.assumptions.cnf.CNF exprs : tuple of expressions Returns ======= sympy.assumptions.cnf.CNF Examples ======== >>> from sympy import Q >>> from sympy.assumptions.cnf import CNF >>> from sympy.assumptions.ask import _extract_all_facts >>> from sympy.abc import x, y >>> assump = CNF.from_prop(Q.positive(x) & Q.integer(y)) >>> exprs = (x,) >>> cnf = _extract_all_facts(assump, exprs) >>> cnf.clauses {frozenset({Literal(Q.positive, False)})} """ facts = set() if len(exprs) == 1 and isinstance(exprs[0], Relational): rel = exprs[0] exprs = (rel, rel.reversed) for clause in assump.clauses: args = [] for literal in clause: if isinstance(literal.lit, AppliedPredicate) and len( literal.lit.arguments) == 1: if literal.lit.arg in exprs: # Add literal if it has matching in it args.append(Literal(literal.lit.function, literal.is_Not)) else: # If any of the literals doesn't have matching expr don't add the whole clause. break else: if args: facts.add(frozenset(args)) return CNF(facts)
def _extract_all_facts(expr, symbol): facts = set() if isinstance(symbol, Relational): symbols = (symbol, symbol.reversed) else: symbols = (symbol,) for clause in expr.clauses: args = [] for literal in clause: if isinstance(literal.lit, AppliedPredicate): if literal.lit.arg in symbols: # Add literal if it has 'symbol' in it args.append(Literal(literal.lit.func, literal.is_Not)) else: # If any of the literals doesn't have 'symbol' don't add the whole clause. break else: if args: facts.add(frozenset(args)) return CNF(facts)
def ask(proposition, assumptions=True, context=global_assumptions): """ Function to evaluate the proposition with assumptions. **Syntax** * ask(proposition) Evaluate the *proposition* in global assumption context. * ask(proposition, assumptions) Evaluate the *proposition* with respect to *assumptions* in global assumption context. This function evaluates the proposition to ``True`` or ``False`` if the truth value can be determined. If not, it returns ``None``. It should be discerned from :func:`~.refine()` which, when applied to a proposition, simplifies the argument to symbolic ``Boolean`` instead of Python built-in ``True``, ``False`` or ``None``. Parameters ========== proposition : any boolean expression Proposition which will be evaluated to boolean value. If this is not ``AppliedPredicate``, it will be wrapped by ``Q.is_true``. assumptions : any boolean expression, optional Local assumptions to evaluate the *proposition*. context : AssumptionsContext, optional Default assumptions to evaluate the *proposition*. By default, this is ``sympy.assumptions.global_assumptions`` variable. 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(4*x), Q.integer(x)) False If the truth value cannot be determined, ``None`` will be returned. >>> print(ask(Q.odd(3*x))) # cannot determine unless we know x None **Remarks** Relations in assumptions are not implemented (yet), so the following will not give a meaningful result. >>> ask(Q.positive(x), x > 0) It is however a work in progress. See Also ======== sympy.assumptions.refine.refine : Simplification using assumptions. Proposition is not reduced to ``None`` if the truth value cannot be determined. """ from sympy.assumptions.satask import satask proposition = sympify(proposition) assumptions = sympify(assumptions) if isinstance(proposition, Predicate) or proposition.kind is not BooleanKind: raise TypeError("proposition must be a valid logical expression") if isinstance(assumptions, Predicate) or assumptions.kind is not BooleanKind: raise TypeError("assumptions must be a valid logical expression") binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le} if isinstance(proposition, AppliedPredicate): key, args = proposition.function, proposition.arguments elif proposition.func in binrelpreds: key, args = binrelpreds[proposition.func], proposition.args else: key, args = Q.is_true, (proposition, ) # convert local and global assumptions to CNF assump = CNF.from_prop(assumptions) assump.extend(context) # extract the relevant facts from assumptions with respect to args local_facts = _extract_all_facts(assump, args) known_facts_cnf = get_all_known_facts() known_facts_dict = get_known_facts_dict() # convert default facts and assumed facts to encoded CNF enc_cnf = EncodedCNF() enc_cnf.from_cnf(CNF(known_facts_cnf)) enc_cnf.add_from_cnf(local_facts) # check the satisfiability of given assumptions if local_facts.clauses and satisfiable(enc_cnf) is False: raise ValueError("inconsistent assumptions %s" % assumptions) if local_facts.clauses: # quick exit if the prerequisite of proposition is not true # e.g. proposition = Q.odd(x), assumptions = ~Q.integer(x) if len(local_facts.clauses) == 1: cl, = local_facts.clauses if len(cl) == 1: f, = cl if f.is_Not and f.arg in known_facts_dict.get(key, []): return False for clause in local_facts.clauses: if len(clause) == 1: f, = clause fdict = known_facts_dict.get(f.arg, None) if not f.is_Not else None if fdict is None: pass elif key in fdict: # quick exit if proposition is directly satisfied by assumption # e.g. proposition = Q.integer(x), assumptions = Q.odd(x) return True elif Not(key) in fdict: # quick exit if proposition is directly rejected by assumption # example might be proposition = Q.even(x), assumptions = Q.odd(x) # but known_facts_dict does not have such information yet and # such example is computed by satask. return False # direct resolution method, no logic res = key(*args)._eval_ask(assumptions) if res is not None: return bool(res) # using satask (still costly) res = satask(proposition, assumptions=assumptions, context=context) return res
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(4*x), Q.integer(x)) 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)) It is however a work in progress. """ from sympy.assumptions.satask import satask 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) assump = CNF.from_prop(assumptions) assump.extend(context) local_facts = _extract_all_facts(assump, expr) known_facts_cnf = get_all_known_facts() known_facts_dict = get_known_facts_dict() enc_cnf = EncodedCNF() enc_cnf.from_cnf(CNF(known_facts_cnf)) enc_cnf.add_from_cnf(local_facts) if local_facts.clauses and satisfiable(enc_cnf) is False: raise ValueError("inconsistent assumptions %s" % assumptions) if local_facts.clauses: local_facts_ = CNF.CNF_to_cnf(local_facts) # 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 (isinstance(local_facts_, 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 isinstance(assum, 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 isinstance(local_facts_, Not) and local_facts_.args[0].is_Atom): if local_facts_.args[0] in known_facts_dict[key]: return False # direct resolution method, no logic res = key(expr)._eval_ask(assumptions) if res is not None: return bool(res) # using satask (still costly) res = satask(proposition, assumptions=assumptions, context=context) return res
def ask(proposition, assumptions=True, context=global_assumptions): """ Function to evaluate the proposition with assumptions. **Syntax** * ask(proposition) Evaluate the *proposition* in global assumption context. * ask(proposition, assumptions) Evaluate the *proposition* with respect to *assumptions* in global assumption context. This function evaluates the proposition to ``True`` or ``False`` if the truth value can be determined. If not, it returns ``None``. It should be discerned from :func:`~.refine()` which does not reduce the expression to ``None``. Parameters ========== proposition : any boolean expression Proposition which will be evaluated to boolean value. If this is not ``AppliedPredicate``, it will be wrapped by ``Q.is_true``. assumptions : any boolean expression, optional Local assumptions to evaluate the *proposition*. context : AssumptionsContext, optional Default assumptions to evaluate the *proposition*. By default, this is ``sympy.assumptions.global_assumptions`` variable. 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(4*x), Q.integer(x)) False If the truth value cannot be determined, ``None`` will be returned. >>> print(ask(Q.odd(3*x))) # cannot determine unless we know x None **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)) It is however a work in progress. See Also ======== sympy.assumptions.refine.refine : Simplification using assumptions. Proposition is not reduced to ``None`` if the truth value cannot be determined. """ from sympy.assumptions.satask import satask proposition = sympify(proposition) assumptions = sympify(assumptions) if isinstance(proposition, Predicate) or proposition.kind is not BooleanKind: raise TypeError("proposition must be a valid logical expression") if isinstance(assumptions, Predicate) or assumptions.kind is not BooleanKind: raise TypeError("assumptions must be a valid logical expression") if isinstance(proposition, AppliedPredicate): key, args = proposition.function, proposition.arguments else: key, args = Q.is_true, (proposition, ) assump = CNF.from_prop(assumptions) assump.extend(context) local_facts = _extract_all_facts(assump, args) known_facts_cnf = get_all_known_facts() known_facts_dict = get_known_facts_dict() enc_cnf = EncodedCNF() enc_cnf.from_cnf(CNF(known_facts_cnf)) enc_cnf.add_from_cnf(local_facts) if local_facts.clauses and satisfiable(enc_cnf) is False: raise ValueError("inconsistent assumptions %s" % assumptions) if local_facts.clauses: if len(local_facts.clauses) == 1: cl, = local_facts.clauses f, = cl if len(cl) == 1 else [None] if f and f.is_Not and f.arg in known_facts_dict.get(key, []): return False for clause in local_facts.clauses: if len(clause) == 1: f, = clause fdict = known_facts_dict.get(f.arg, None) if not f.is_Not else None if fdict and key in fdict: return True if fdict and Not(key) in known_facts_dict[f.arg]: return False # direct resolution method, no logic res = key(*args)._eval_ask(assumptions) if res is not None: return bool(res) # using satask (still costly) res = satask(proposition, assumptions=assumptions, context=context) return res
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(4*x), Q.integer(x)) 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)) It is however a work in progress. """ from sympy.assumptions.satask import satask 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) assump = CNF.from_prop(assumptions) assump.extend(context) local_facts = _extract_all_facts(assump, expr) known_facts_cnf = get_all_known_facts() known_facts_dict = get_known_facts_dict() enc_cnf = EncodedCNF() enc_cnf.from_cnf(CNF(known_facts_cnf)) enc_cnf.add_from_cnf(local_facts) if local_facts.clauses and satisfiable(enc_cnf) is False: raise ValueError("inconsistent assumptions %s" % assumptions) if local_facts.clauses: if len(local_facts.clauses) == 1: cl, = local_facts.clauses f, = cl if len(cl) == 1 else [None] if f and f.is_Not and f.arg in known_facts_dict.get(key, []): return False for clause in local_facts.clauses: if len(clause) == 1: f, = clause fdict = known_facts_dict.get(f.arg, None) if not f.is_Not else None if fdict and key in fdict: return True if fdict and Not(key) in known_facts_dict[f.arg]: return False # direct resolution method, no logic res = key(expr)._eval_ask(assumptions) if res is not None: return bool(res) # using satask (still costly) res = satask(proposition, assumptions=assumptions, context=context) return res
def generate_code(): from textwrap import dedent, wrap LINE = ",\n " HANG = ' ' * 8 code_string = dedent('''\ """ Do NOT manually edit this file. Instead, run ./bin/ask_update.py. """ from sympy.assumptions.ask import Q from sympy.assumptions.cnf import Literal from sympy.core.cache import cacheit @cacheit def get_all_known_facts(): """ Known facts between unary predicates as CNF clauses. """ return { %s } @cacheit def get_known_facts_dict(): """ Logical relations between unary predicates as dictionary. Each key is a predicate, and item is two groups of predicates. First group contains the predicates which are implied by the key, and second group contains the predicates which are rejected by the key. """ return { %s } ''') x = Symbol('x') fact = get_known_facts(x) # Generate CNF of facts between known unary predicates cnf = CNF.to_CNF(fact) p = LINE.join( sorted([ 'frozenset((' + ', '.join( str(Literal(lit.arg.function, lit.is_Not)) for lit in sorted(clause, key=str)) + '))' for clause in cnf.clauses ])) # Generate dictionary of facts between known unary predicates keys = [pred(x) for pred in get_known_facts_keys()] mapping = generate_known_facts_dict(keys, fact) items = sorted(mapping.items(), key=str) keys = [str(i[0]) for i in items] values = [ '(set(%s), set(%s))' % (sorted(i[1][0], key=str), sorted(i[1][1], key=str)) for i in items ] m = LINE.join([ '\n'.join( wrap("{}: {}".format(k, v), subsequent_indent=HANG, break_long_words=False)) for k, v in zip(keys, values) ]) + ',' return code_string % (p, m)
def ask(proposition, assumptions=True, context=global_assumptions): """ Function to evaluate the proposition with assumptions. Explanation =========== This function evaluates the proposition to ``True`` or ``False`` if the truth value can be determined. If not, it returns ``None``. It should be discerned from :func:`~.refine()` which, when applied to a proposition, simplifies the argument to symbolic ``Boolean`` instead of Python built-in ``True``, ``False`` or ``None``. **Syntax** * ask(proposition) Evaluate the *proposition* in global assumption context. * ask(proposition, assumptions) Evaluate the *proposition* with respect to *assumptions* in global assumption context. Parameters ========== proposition : Any boolean expression. Proposition which will be evaluated to boolean value. If this is not ``AppliedPredicate``, it will be wrapped by ``Q.is_true``. assumptions : Any boolean expression, optional. Local assumptions to evaluate the *proposition*. context : AssumptionsContext, optional. Default assumptions to evaluate the *proposition*. By default, this is ``sympy.assumptions.global_assumptions`` variable. Returns ======= ``True``, ``False``, or ``None`` Raises ====== TypeError : *proposition* or *assumptions* is not valid logical expression. ValueError : assumptions are inconsistent. 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(4*x), Q.integer(x)) False If the truth value cannot be determined, ``None`` will be returned. >>> print(ask(Q.odd(3*x))) # cannot determine unless we know x None ``ValueError`` is raised if assumptions are inconsistent. >>> ask(Q.integer(x), Q.even(x) & Q.odd(x)) Traceback (most recent call last): ... ValueError: inconsistent assumptions Q.even(x) & Q.odd(x) Notes ===== Relations in assumptions are not implemented (yet), so the following will not give a meaningful result. >>> ask(Q.positive(x), x > 0) It is however a work in progress. See Also ======== sympy.assumptions.refine.refine : Simplification using assumptions. Proposition is not reduced to ``None`` if the truth value cannot be determined. """ from sympy.assumptions.satask import satask proposition = sympify(proposition) assumptions = sympify(assumptions) if isinstance(proposition, Predicate) or proposition.kind is not BooleanKind: raise TypeError("proposition must be a valid logical expression") if isinstance(assumptions, Predicate) or assumptions.kind is not BooleanKind: raise TypeError("assumptions must be a valid logical expression") binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le} if isinstance(proposition, AppliedPredicate): key, args = proposition.function, proposition.arguments elif proposition.func in binrelpreds: key, args = binrelpreds[proposition.func], proposition.args else: key, args = Q.is_true, (proposition, ) # convert local and global assumptions to CNF assump_cnf = CNF.from_prop(assumptions) assump_cnf.extend(context) # extract the relevant facts from assumptions with respect to args local_facts = _extract_all_facts(assump_cnf, args) # convert default facts and assumed facts to encoded CNF known_facts_cnf = get_all_known_facts() enc_cnf = EncodedCNF() enc_cnf.from_cnf(CNF(known_facts_cnf)) enc_cnf.add_from_cnf(local_facts) # check the satisfiability of given assumptions if local_facts.clauses and satisfiable(enc_cnf) is False: raise ValueError("inconsistent assumptions %s" % assumptions) # quick computation for single fact res = _ask_single_fact(key, local_facts) if res is not None: return res # direct resolution method, no logic res = key(*args)._eval_ask(assumptions) if res is not None: return bool(res) # using satask (still costly) res = satask(proposition, assumptions=assumptions, context=context) return res
def get_relevant_facts(exprs, relevant_facts=None): """ Extract relevant facts from the items in *exprs*. Facts are defined in ``assumptions.sathandlers`` module. This function is recursively called by ``get_all_relevant_facts()``. Parameters ========== exprs : set Expressions whose relevant facts are searched relevant_facts : sympy.assumptions.cnf.CNF, optional Pre-discovered relevant facts Returns ======= exprs : set Candidates for next relevant fact searching. relevant_facts : sympy.assumptions.cnf.CNF Updated relevant facts. Examples ======== Here, we will see how facts relevant to ``Abs(x*y)`` are recursively extracted. On the first run, set containing the expression is passed without pre-discovered relevant facts. The result is a set containig candidates for next run, and ``CNF()`` instance containing facts which are relevant to ``Abs`` and its argument. >>> from sympy import Abs >>> from sympy.assumptions.satask import get_relevant_facts >>> from sympy.abc import x, y >>> exprs = {Abs(x*y)} >>> exprs, facts = get_relevant_facts(exprs) >>> exprs {x*y} >>> facts.clauses #doctest: +SKIP {frozenset({Literal(Q.odd(Abs(x*y)), False), Literal(Q.odd(x*y), True)}), frozenset({Literal(Q.zero(Abs(x*y)), False), Literal(Q.zero(x*y), True)}), frozenset({Literal(Q.even(Abs(x*y)), False), Literal(Q.even(x*y), True)}), frozenset({Literal(Q.zero(Abs(x*y)), True), Literal(Q.zero(x*y), False)}), frozenset({Literal(Q.even(Abs(x*y)), False), Literal(Q.odd(Abs(x*y)), False), Literal(Q.odd(x*y), True)}), frozenset({Literal(Q.even(Abs(x*y)), False), Literal(Q.even(x*y), True), Literal(Q.odd(Abs(x*y)), False)}), frozenset({Literal(Q.positive(Abs(x*y)), False), Literal(Q.zero(Abs(x*y)), False)})} We pass the first run's results to the second run, and get the expressions for next run and updated facts. >>> exprs, facts = get_relevant_facts(exprs, relevant_facts=facts) >>> exprs {x, y} On final run, no more candidate is returned thus we know that all relevant facts are successfully retrieved. >>> exprs, facts = get_relevant_facts(exprs, relevant_facts=facts) >>> exprs set() """ if not relevant_facts: relevant_facts = CNF() newexprs = set() for expr in exprs: for fact in fact_registry[expr.func]: cnf_fact = CNF.to_CNF(fact) newfact = cnf_fact.rcall(expr) relevant_facts = relevant_facts._and(newfact) for key in newfact.all_predicates(): if isinstance(key, AppliedPredicate): newexprs |= set(key.arguments) return newexprs - exprs, relevant_facts
def satask(proposition, assumptions=True, context=global_assumptions, use_known_facts=True, iterations=oo): """ Function to evaluate the proposition with assumptions using SAT algorithm. This function extracts every fact relevant to the expressions composing proposition and assumptions. For example, if a predicate containing ``Abs(x)`` is proposed, then ``Q.zero(Abs(x)) | Q.positive(Abs(x))`` will be found and passed to SAT solver because ``Q.nonnegative`` is registered as a fact for ``Abs``. Proposition is evaluated to ``True`` or ``False`` if the truth value can be determined. If not, ``None`` is returned. Parameters ========== proposition : Any boolean expression. Proposition which will be evaluated to boolean value. assumptions : Any boolean expression, optional. Local assumptions to evaluate the *proposition*. context : AssumptionsContext, optional. Default assumptions to evaluate the *proposition*. By default, this is ``sympy.assumptions.global_assumptions`` variable. use_known_facts : bool, optional. If ``True``, facts from ``sympy.assumptions.ask_generated`` module are passed to SAT solver as well. iterations : int, optional. Number of times that relevant facts are recursively extracted. Default is infinite times until no new fact is found. Returns ======= ``True``, ``False``, or ``None`` Examples ======== >>> from sympy import Abs, Q >>> from sympy.assumptions.satask import satask >>> from sympy.abc import x >>> satask(Q.zero(Abs(x)), Q.zero(x)) True """ props = CNF.from_prop(proposition) _props = CNF.from_prop(~proposition) assumptions = CNF.from_prop(assumptions) context_cnf = CNF() if context: context_cnf = context_cnf.extend(context) sat = get_all_relevant_facts(props, assumptions, context_cnf, use_known_facts=use_known_facts, iterations=iterations) sat.add_from_cnf(assumptions) if context: sat.add_from_cnf(context_cnf) return check_satisfiability(props, _props, sat)
def compute_known_facts(known_facts, known_facts_keys): """Compute the various forms of knowledge compilation used by the assumptions system. Explanation =========== This function is typically applied to the results of the ``get_known_facts`` and ``get_known_facts_keys`` functions 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.core.cache import cacheit from sympy.logic.boolalg import And from sympy.assumptions.cnf import Literal from sympy.assumptions.ask import Q # -{ Known facts as a set }- @cacheit def get_all_known_facts(): return { %s } # -{ Known facts in Conjunctive Normal Form }- @cacheit def get_known_facts_cnf(): return And( %s ) # -{ Known facts in compressed sets }- @cacheit def get_known_facts_dict(): return { %s } ''') # Compute the known facts in CNF form for logical inference LINE = ",\n " HANG = ' ' * 8 cnf = to_cnf(known_facts) cnf_ = CNF.to_CNF(known_facts) c = LINE.join([str(a) for a in cnf.args]) p = LINE.join( sorted([ 'frozenset((' + ', '.join(str(lit) for lit in sorted(clause, key=str)) + '))' for clause in cnf_.clauses ])) 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("{}: {}".format(k, v), subsequent_indent=HANG, break_long_words=False)) for k, v in zip(keys, values) ]) + ',' return fact_string % (p, c, m)
def test_to_CNF(): assert CNF.CNF_to_cnf(CNF.to_CNF(~(B | C))) == to_cnf(~(B | C)) assert CNF.CNF_to_cnf(CNF.to_CNF((A & B) | C)) == to_cnf((A & B) | C) assert CNF.CNF_to_cnf(CNF.to_CNF(A >> B)) == to_cnf(A >> B) assert CNF.CNF_to_cnf(CNF.to_CNF(A >> (B & C))) == to_cnf(A >> (B & C)) assert CNF.CNF_to_cnf(CNF.to_CNF(A & (B | C) | ~A & (B | C))) == to_cnf(A & (B | C) | ~A & (B | C)) assert CNF.CNF_to_cnf(CNF.to_CNF(A & B)) == to_cnf(A & B)
def get_all_relevant_facts(proposition, assumptions, context, use_known_facts=True, iterations=oo): """ Extract all relevant facts from *proposition* and *assumptions*. This function extracts the facts by recursively calling ``get_relevant_facts()``. Extracted facts are converted to ``EncodedCNF`` and returned. Parameters ========== proposition : sympy.assumptions.cnf.CNF CNF generated from proposition expression assumptions : sympy.assumptions.cnf.CNF CNF generated from assumption expression context : sympy.assumptions.cnf.CNF CNF generated from assumptions context use_known_facts : bool, optional If ``True``, facts from ``sympy.assumptions.ask_generated`` module are encoded as well. iterations : int, optional Number of times that relevant facts are recursively extracted. Default is infinite times until no new fact is found. Returns ======= sympy.assumptions.cnf.EncodedCNF Examples ======== >>> from sympy import Q >>> from sympy.assumptions.cnf import CNF >>> from sympy.assumptions.satask import get_all_relevant_facts >>> from sympy.abc import x, y >>> props = CNF.from_prop(Q.nonzero(x*y)) >>> assump = CNF.from_prop(Q.nonzero(x)) >>> context = CNF.from_prop(Q.nonzero(y)) >>> get_all_relevant_facts(props, assump, context) #doctest: +SKIP <sympy.assumptions.cnf.EncodedCNF at 0x7f09faa6ccd0> """ # The relevant facts might introduce new keys, e.g., Q.zero(x*y) will # introduce the keys Q.zero(x) and Q.zero(y), so we need to run it until # we stop getting new things. Hopefully this strategy won't lead to an # infinite loop in the future. i = 0 relevant_facts = CNF() exprs = None all_exprs = set() while True: if i == 0: exprs = extract_predargs(proposition, assumptions, context) all_exprs |= exprs exprs, relevant_facts = get_relevant_facts(exprs, relevant_facts) i += 1 if i >= iterations: break if not exprs: break if use_known_facts: known_facts_CNF = CNF() known_facts_CNF.add_clauses(get_all_known_facts()) kf_encoded = EncodedCNF() kf_encoded.from_cnf(known_facts_CNF) def translate_literal(lit, delta): if lit > 0: return lit + delta else: return lit - delta def translate_data(data, delta): return [{translate_literal(i, delta) for i in clause} for clause in data] data = [] symbols = [] n_lit = len(kf_encoded.symbols) for i, expr in enumerate(all_exprs): symbols += [pred(expr) for pred in kf_encoded.symbols] data += translate_data(kf_encoded.data, i * n_lit) encoding = dict(list(zip(symbols, range(1, len(symbols) + 1)))) ctx = EncodedCNF(data, encoding) else: ctx = EncodedCNF() ctx.add_from_cnf(relevant_facts) return ctx