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), "abduce 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.tablename() 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 _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 _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 receive_data_update(self, msg): """Handler for when dataservice publishes a delta.""" LOG.info("%s:: received update data msg for %s: %s", self.name, msg.header['dataindex'], ";".join(str(x) for x in msg.body.data)) new_events = [] for event in msg.body.data: assert compile.is_atom(event.formula), ( "receive_data_update received non-atom: " + str(event.formula)) # prefix tablename with data source actual_table = compile.Tablename.build_service_table( msg.replyTo, event.formula.table.table) values = [term.name for term in event.formula.arguments] newevent = compile.Event(compile.Fact(actual_table, values), insert=event.insert) new_events.append(newevent) (permitted, changes) = self.policy.update(new_events) if not permitted: raise exception.CongressException( "Update not permitted." + '\n'.join(str(x) for x in changes)) else: tablename = msg.header['dataindex'] service = msg.replyTo LOG.debug("update data msg for %s from %s caused %d " "changes: %s", tablename, service, len(changes), ";".join(str(x) for x in changes))
def receive_data_update(self, msg): """Handler for when dataservice publishes a delta.""" LOG.info("%s:: received update data msg for %s: %s", self.name, msg.header['dataindex'], ";".join(str(x) for x in msg.body.data)) new_events = [] for event in msg.body.data: assert compile.is_atom( event.formula), ("receive_data_update received non-atom: " + str(event.formula)) # prefix tablename with data source actual_table = compile.Tablename.build_service_table( msg.replyTo, event.formula.table.table) values = [term.name for term in event.formula.arguments] newevent = compile.Event(compile.Fact(actual_table, values), insert=event.insert) new_events.append(newevent) (permitted, changes) = self.policy.update(new_events) if not permitted: raise exception.CongressException("Update not permitted." + '\n'.join( str(x) for x in changes)) else: tablename = msg.header['dataindex'] service = msg.replyTo LOG.debug( "update data msg for %s from %s caused %d " "changes: %s", tablename, service, len(changes), ";".join(str(x) for x in changes))
def __contains__(self, formula): if not compile.is_atom(formula): return False if formula.table.table not in self.data: return False event_data = self.data[formula.table.table] raw_tuple = tuple(formula.argument_names()) return any((dbtuple.tuple == raw_tuple for dbtuple in event_data))
def explain(self, query, tablenames, find_all): """Returns a list of proofs if QUERY is true or None if else.""" assert compile.is_atom(query), "Explain requires an atom" # ignoring TABLENAMES and FIND_ALL # except that we return the proper type. proof = self.explain_aux(query, 0) if proof is None: return None else: return [proof]
def delete_actual(self, atom, proofs=None): """Workhorse for deleting ATOM from the DB. Along with the proofs that are no longer true. """ assert compile.is_atom(atom), "Delete requires Atom" self.log(atom.table.table, "Delete: %s", atom) table, dbtuple = self.atom_to_internal(atom, proofs) if table not in self.data: return for i in range(0, len(self.data[table])): existingtuple = self.data[table][i] if existingtuple.tuple == dbtuple.tuple: existingtuple.proofs -= dbtuple.proofs if len(existingtuple.proofs) == 0: del self.data[table][i] return
def modify(self, event): """Insert/Delete atom. Inserts/deletes ATOM and returns a list of changes that were caused. That list contains either 0 or 1 Event. """ assert compile.is_atom(event.formula), "Modify requires Atom" atom = event.formula self.log(atom.table.table, "Modify: %s", atom) if self.is_noop(event): self.log(atom.table.table, "Event %s is a noop", event) return [] if event.insert: self.insert_actual(atom, proofs=event.proofs) else: self.delete_actual(atom, proofs=event.proofs) return [event]
def update_would_cause_errors(self, events): """Return a list of Policyxception. Return a list of PolicyException if we were to apply the events EVENTS to the current policy. """ self.log(None, "update_would_cause_errors %s", utility.iterstr(events)) errors = [] for event in events: if not compile.is_atom(event.formula): errors.append(exception.PolicyException( "Non-atomic formula is not permitted: {}".format( str(event.formula)))) else: errors.extend(compile.fact_errors( event.formula, self.theories, self.name)) return errors
def update_would_cause_errors(self, events): """Return a list of Policyxception. Return a list of PolicyException if we were to apply the events EVENTS to the current policy. """ self.log(None, "update_would_cause_errors %s", utility.iterstr(events)) errors = [] for event in events: if not compile.is_atom(event.formula): errors.append( exception.PolicyException( "Non-atomic formula is not permitted: {}".format( str(event.formula)))) else: errors.extend( compile.fact_errors(event.formula, self.theories, self.name)) return errors
def select(self, query, find_all=True): """Return list of instances of QUERY that are true. If FIND_ALL is False, the return list has at most 1 element. """ assert compile.is_datalog(query), "Query must be atom/rule" if compile.is_atom(query): literals = [query] else: literals = query.body # Because our output is instances of QUERY, need all the variables # in QUERY. bindings = self.top_down_evaluation(query.variables(), literals, find_all=find_all) # LOG.debug("Top_down_evaluation returned: %s", bindings) if len(bindings) > 0: self.log(query.tablename(), "Found answer %s", "[" + ",".join([str(query.plug(x)) for x in bindings]) + "]") return [query.plug(x) for x in bindings]
def insert_actual(self, atom, proofs=None): """Workhorse for inserting ATOM into the DB. Along with proofs explaining how ATOM was computed from other tables. """ assert compile.is_atom(atom), "Insert requires Atom" table, dbtuple = self.atom_to_internal(atom, proofs) self.log(table, "Insert: %s", atom) if table not in self.data: self.data[table] = [dbtuple] self.log(atom.table.table, "First tuple in table %s", table) return else: for existingtuple in self.data[table]: assert existingtuple.proofs is not None if existingtuple.tuple == dbtuple.tuple: assert existingtuple.proofs is not None existingtuple.proofs |= dbtuple.proofs assert existingtuple.proofs is not None return self.data[table].append(dbtuple)
def select(self, query, find_all=True): """Return list of instances of QUERY that are true. If FIND_ALL is False, the return list has at most 1 element. """ assert compile.is_datalog(query), "Query must be atom/rule" if compile.is_atom(query): literals = [query] else: literals = query.body # Because our output is instances of QUERY, need all the variables # in QUERY. bindings = self.top_down_evaluation(query.variables(), literals, find_all=find_all) # LOG.debug("Top_down_evaluation returned: %s", bindings) if len(bindings) > 0: self.log( query.tablename(), "Found answer %s", "[" + ",".join([str(query.plug(x)) for x in bindings]) + "]") return [query.plug(x) for x in bindings]
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 __contains__(self, formula): if compile.is_atom(formula): return self.rules.contains(formula.table.table, formula) else: return self.rules.contains(formula.head.table.table, formula)
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"))
def __contains__(self, formula): if compile.is_atom(formula): return self.rules.contains(formula.table, formula) else: return self.rules.contains(formula.head.table, formula)