def _delete_actual(self, rule): """Delete RULE and return True if there was a change.""" self.dirty = True if compile.is_atom(rule): rule = compile.Rule(rule, [], rule.location) self.log(rule.head.table.table, "Delete: %s", rule) return self.rules.discard_rule(rule.head.table.table, rule)
def _extract_lp_variable_equalities(self, rule, rewrite_theory): """Extract values for LP variables and slightly modify rule. :param rule: an instance of Rule :param rewrite_theory: reference to a theory that contains rules describing how tables correspond to LP variable inputs and outputs. Returns (i) dictionary mapping Datalog variable name (a string) to the set of LP variables to which it is equal and (ii) a rewriting of the rule that is the same as the original except some elements have been removed from the body. """ newbody = [] varnames = {} for lit in rule.body: result = self._extract_lp_variable_equality_lit( lit, rewrite_theory) if result is None: newbody.append(lit) else: datalogvar, lpvar = result if datalogvar not in varnames: varnames[datalogvar] = set([lpvar]) else: varnames[datalogvar].add(lpvar) return varnames, compile.Rule(rule.head, newbody)
def _insert_actual(self, rule): """Insert RULE and return True if there was a change.""" self.dirty = True if compile.is_atom(rule): rule = compile.Rule(rule, [], rule.location) self.log(rule.head.table.table, "Insert: %s", repr(rule)) return self.rules.add_rule(rule.head.table.table, rule)
def eliminate_self_joins(cls, formulas): """Remove self joins. Return new list of formulas that is equivalent to the list of formulas FORMULAS except that there are no self-joins. """ def new_table_name(name, arity, index): return "___{}_{}_{}".format(name, arity, index) def n_variables(n): vars = [] for i in xrange(0, n): vars.append("x" + str(i)) return vars # dict from (table name, arity) tuple to # max num of occurrences of self-joins in any rule global_self_joins = {} # remove self-joins from rules results = [] for rule in formulas: if rule.is_atom(): results.append(rule) continue LOG.debug("eliminating self joins from %s", rule) occurrences = {} # for just this rule for atom in rule.body: table = atom.tablename() arity = len(atom.arguments) tablearity = (table, arity) if tablearity not in occurrences: occurrences[tablearity] = 1 else: # change name of atom atom.table = new_table_name(table, arity, occurrences[tablearity]) # update our counters occurrences[tablearity] += 1 if tablearity not in global_self_joins: global_self_joins[tablearity] = 1 else: global_self_joins[tablearity] = (max( occurrences[tablearity] - 1, global_self_joins[tablearity])) results.append(rule) LOG.debug("final rule: %s", rule) # add definitions for new tables for tablearity in global_self_joins: table = tablearity[0] arity = tablearity[1] for i in xrange(1, global_self_joins[tablearity] + 1): newtable = new_table_name(table, arity, i) args = [compile.Variable(var) for var in n_variables(arity)] head = compile.Literal(newtable, args) body = [compile.Literal(table, args)] results.append(compile.Rule(head, body)) LOG.debug("Adding rule %s", results[-1]) return results
def test_add_fact(self): fact1 = Fact('p', (1, 2, 3)) equivalent_rule = compile.Rule(compile.parse1('p(1,2,3)'), ()) self.assertTrue(self.ruleset.add_rule('p', fact1)) self.assertTrue('p' in self.ruleset) self.assertEqual([equivalent_rule], self.ruleset.get_rules('p')) self.assertEqual(['p'], self.ruleset.keys())
def test_add_equivalent_rule(self): # equivalent_rule could be a fact because it has no body, and is # ground. equivalent_rule = compile.Rule(compile.parse1('p(1,2,3)'), ()) self.assertTrue(self.ruleset.add_rule('p', equivalent_rule)) self.assertTrue('p' in self.ruleset) self.assertEqual([equivalent_rule], self.ruleset.get_rules('p')) self.assertEqual(['p'], self.ruleset.keys())
def test_discard_equivalent_rule(self): fact = Fact('p', (1, 2, 3)) equivalent_rule = compile.Rule(compile.parse1('p(1,2,3)'), ()) self.assertTrue(self.ruleset.add_rule('p', fact)) self.assertTrue('p' in self.ruleset) self.assertEqual([equivalent_rule], self.ruleset.get_rules('p')) self.assertTrue(self.ruleset.discard_rule('p', equivalent_rule)) self.assertFalse('p' in self.ruleset) self.assertEqual([], self.ruleset.keys())
def get_rules(self, key, match_literal=None): facts = [] if (match_literal and not match_literal.is_negated() and key in self.facts): # If the caller supplies a literal to match against, then use an # index to find the matching rules. bound_arguments = tuple([ i for i, arg in enumerate(match_literal.arguments) if not arg.is_variable() ]) if (bound_arguments and not self.facts[key].has_index(bound_arguments)): # The index does not exist, so create it. self.facts[key].create_index(bound_arguments) partial_fact = tuple([ (i, arg.name) for i, arg in enumerate(match_literal.arguments) if not arg.is_variable() ]) facts = list(self.facts[key].find(partial_fact)) else: # There is no usable match_literal, so get all facts for the # table. facts = list(self.facts.get(key, ())) # Convert native tuples to Rule objects. # TODO(alex): This is inefficient because it creates Literal and Rule # objects. It would be more efficient to change the TopDownTheory and # unifier to handle Facts natively. fact_rules = [] for fact in facts: # Setting use_modules=False so we don't split up tablenames. # This allows us to choose at compile-time whether to split # the tablename up. literal = compile.Literal( key, [compile.Term.create_from_python(x) for x in fact], use_modules=False) fact_rules.append(compile.Rule(literal, ())) return fact_rules + list(self.rules.get(key, ()))
def check(self, rule, data, correct, possibilities=None): rule = compile.parse1(rule, use_modules=False) data = compile.parse(data, use_modules=False) possibilities = possibilities or '' possibilities = compile.parse(possibilities, use_modules=False) possibilities = [compile.Rule(x, []) for x in possibilities] poss = {} for rule_lit in possibilities: if rule_lit.head.tablename() not in poss: poss[rule_lit.head.tablename()] = set([rule_lit]) else: poss[rule_lit.head.tablename()].add(rule_lit) th = nonrecursive.MultiModuleNonrecursiveRuleTheory() th.debug_mode() for lit in data: th.insert(lit) result = th.instances(rule, poss) actual = " ".join(str(x) for x in result) e = helper.datalog_equal(actual, correct) self.assertTrue(e)
def test_contains(self): fact = Fact('p', (1, 2, 3)) rule = compile.parse1('p(x) :- q(x)') self.ruleset.add_rule('p', fact) self.ruleset.add_rule('p', rule) # positive tests equivalent_fact1 = Fact('p', (1, 2, 3)) equivalent_fact2 = compile.parse1('p(1,2,3)') equivalent_fact3 = compile.Rule(compile.parse1('p(1,2,3)'), ()) equivalent_rule = compile.parse1('p(x) :- q(x)') self.assertTrue(self.ruleset.contains('p', equivalent_fact1)) self.assertTrue(self.ruleset.contains('p', equivalent_fact2)) self.assertTrue(self.ruleset.contains('p', equivalent_fact3)) self.assertTrue(self.ruleset.contains('p', equivalent_rule)) # negative tests nonequiv_fact = compile.parse1('p(4, 5, 6)') nonequiv_rule = compile.parse1('p(x) :- r(x)') self.assertFalse(self.ruleset.contains('p', nonequiv_fact)) self.assertFalse(self.ruleset.contains('p', nonequiv_rule))
def abduce(self, query, tablenames, find_all=True): """Compute additional literals. Computes additional literals that if true would make (some instance of) QUERY true. Returns a list of rules where the head represents an instance of the QUERY and the body is the collection of literals that must be true in order to make that instance true. If QUERY is a rule, each result is an instance of the head of that rule, and the computed literals if true make the body of that rule (and hence the head) true. If FIND_ALL is true, the return list has at most one element. Limitation: every negative literal relevant to a proof of QUERY is unconditionally true, i.e. no literals are saved when proving a negative literal is true. """ assert compile.is_datalog(query), "Explain requires a formula" if compile.is_atom(query): literals = [query] output = query else: literals = query.body output = query.head # We need all the variables we will be using in the output, which # here is just the head of QUERY (or QUERY itself if it is an atom) abductions = self.top_down_abduction( output.variables(), literals, find_all=find_all, save=lambda lit, binding: lit.table in tablenames) results = [ compile.Rule(output.plug(abd.binding), abd.support) for abd in abductions ] self.log(query.tablename(), "abduction result:") self.log(query.tablename(), "\n".join([str(x) for x in results])) return results
def test_type_checkers(self): """Test the type checkers, e.g. is_atom, is_rule.""" atom = compile.Literal("p", []) atom2 = compile.Literal("q", []) atom3 = compile.Literal("r", []) lit = compile.Literal("r", [], negated=True) regular_rule = compile.Rule(atom, [atom2, atom3]) regular_rule2 = compile.Rule(atom, [lit, atom2]) multi_rule = compile.Rule([atom, atom2], [atom3]) fake_rule = compile.Rule([atom, 1], [atom2]) fake_rule2 = compile.Rule(atom, [atom2, 1]) # is_atom self.assertTrue(compile.is_atom(atom)) self.assertTrue(compile.is_atom(atom2)) self.assertTrue(compile.is_atom(atom3)) self.assertFalse(compile.is_atom(lit)) self.assertFalse(compile.is_atom(regular_rule)) self.assertFalse(compile.is_atom(regular_rule2)) self.assertFalse(compile.is_atom(multi_rule)) self.assertFalse(compile.is_atom(fake_rule)) self.assertFalse(compile.is_atom(fake_rule2)) self.assertFalse(compile.is_atom("a string")) # is_literal self.assertTrue(compile.is_literal(atom)) self.assertTrue(compile.is_literal(atom2)) self.assertTrue(compile.is_literal(atom3)) self.assertTrue(compile.is_literal(lit)) self.assertFalse(compile.is_literal(regular_rule)) self.assertFalse(compile.is_literal(regular_rule2)) self.assertFalse(compile.is_literal(multi_rule)) self.assertFalse(compile.is_literal(fake_rule)) self.assertFalse(compile.is_literal(fake_rule2)) self.assertFalse(compile.is_literal("a string")) # is_regular_rule self.assertFalse(compile.is_regular_rule(atom)) self.assertFalse(compile.is_regular_rule(atom2)) self.assertFalse(compile.is_regular_rule(atom3)) self.assertFalse(compile.is_regular_rule(lit)) self.assertTrue(compile.is_regular_rule(regular_rule)) self.assertTrue(compile.is_regular_rule(regular_rule2)) self.assertFalse(compile.is_regular_rule(multi_rule)) self.assertFalse(compile.is_regular_rule(fake_rule)) self.assertFalse(compile.is_regular_rule(fake_rule2)) self.assertFalse(compile.is_regular_rule("a string")) # is_multi_rule self.assertFalse(compile.is_multi_rule(atom)) self.assertFalse(compile.is_multi_rule(atom2)) self.assertFalse(compile.is_multi_rule(atom3)) self.assertFalse(compile.is_multi_rule(lit)) self.assertFalse(compile.is_multi_rule(regular_rule)) self.assertFalse(compile.is_multi_rule(regular_rule2)) self.assertTrue(compile.is_multi_rule(multi_rule)) self.assertFalse(compile.is_multi_rule(fake_rule)) self.assertFalse(compile.is_multi_rule(fake_rule2)) self.assertFalse(compile.is_multi_rule("a string")) # is_rule self.assertFalse(compile.is_rule(atom)) self.assertFalse(compile.is_rule(atom2)) self.assertFalse(compile.is_rule(atom3)) self.assertFalse(compile.is_rule(lit)) self.assertTrue(compile.is_rule(regular_rule)) self.assertTrue(compile.is_rule(regular_rule2)) self.assertTrue(compile.is_rule(multi_rule)) self.assertFalse(compile.is_rule(fake_rule)) self.assertFalse(compile.is_rule(fake_rule2)) self.assertFalse(compile.is_rule("a string")) # is_datalog self.assertTrue(compile.is_datalog(atom)) self.assertTrue(compile.is_datalog(atom2)) self.assertTrue(compile.is_datalog(atom3)) self.assertFalse(compile.is_datalog(lit)) self.assertTrue(compile.is_datalog(regular_rule)) self.assertTrue(compile.is_datalog(regular_rule2)) self.assertFalse(compile.is_datalog(multi_rule)) self.assertFalse(compile.is_datalog(fake_rule)) self.assertFalse(compile.is_datalog(fake_rule2)) self.assertFalse(compile.is_datalog("a string")) # is_extended_datalog self.assertTrue(compile.is_extended_datalog(atom)) self.assertTrue(compile.is_extended_datalog(atom2)) self.assertTrue(compile.is_extended_datalog(atom3)) self.assertFalse(compile.is_extended_datalog(lit)) self.assertTrue(compile.is_extended_datalog(regular_rule)) self.assertTrue(compile.is_extended_datalog(regular_rule2)) self.assertTrue(compile.is_extended_datalog(multi_rule)) self.assertFalse(compile.is_extended_datalog(fake_rule)) self.assertFalse(compile.is_extended_datalog(fake_rule2)) self.assertFalse(compile.is_extended_datalog("a string"))