def decompose(self, alist: Alist, map_op): """ Apply a decomposition rule to create successors of an alist Args ---- alist : Alist to decompose map_op : str Name of the map operation to apply to the alist Return ------ alist: Alist Successor h-node alist that has z-node child alists Notes ----- z-nodes are alists that represent facts and contain variables that are to be instantiated with data from knowledge bases. h-nodes are alists that have operations to aggregate their child z-nodes. Decompositions create z-node and specify the `h` operations for aggregating the decomposed child nodes. (alist) | | (h-node) / ... \\ / ... \\ (z-node1) ... (z-nodeN) """ self.last_heartbeat = time.time() if alist.depth + 1 > config.config['max_depth']: print('max depth reached!\n') alist.state = states.IGNORE return alist self.write_trace( '{blue}{bold}T{thread}{reset} > {op}:{id}-{alist}{resetall}'. format(blue=pcol.BLUE, reset=pcol.RESET, bold=pcol.RESET, resetall=pcol.RESETALL, thread=threading.get_ident(), op=map_op[1], alist=alist, id=alist.id)) alist.branchType = br.OR child = map_op[0](alist, self.G) # check for query context context = alist.get(tt.CONTEXT) self.last_heartbeat = time.time() if child is not None: self.write_trace( f'{pcol.BLUE}>> {child.id}{pcol.RESET}-{str(child)}{pcol.RESETALL}' ) succ = self.G.successors(child.id) for node_id1 in succ: grandchild = self.G.alist(node_id1) self.write_trace( f' {pcol.BLUE}>>> {grandchild.id}{pcol.RESET}-{str(grandchild)}{pcol.RESETALL}' ) reducibleCtr = 0 succ2 = self.G.successors(grandchild.id) for node_id2 in succ2: ggchild = self.G.alist(node_id2) self.write_trace( f' {pcol.BLUE}>>> {ggchild.id}{pcol.RESET}-{str(ggchild)}{pcol.RESETALL}' ) if ggchild.state == states.REDUCIBLE: self.G.add_alist(ggchild) reducibleCtr += 1 # generate the WHY explanation self.explainer.why(self.G, alist, map_op[1]) return child else: return None
def reduce(alist: Alist, children: List[Alist], G: InferenceGraph): if not children: return None nodes_enqueue = [] nodes_enqueue_process = [] # get intersection of child values common_items = set() head, *tail = children has_head_children = False has_tail_children = False for c in G.child_alists(head.id): has_head_children = True if c.get(tt.OP) != 'comp': if c.get(tt.OPVAR).startswith(vx.NESTING): common_items.add(str(c.instantiation_value(c.get(tt.OPVAR)))) else: projVars = c.projection_variables() if projVars != None: for pvkey, pvval in projVars.items(): common_items.add(c.instantiation_value(pvkey)) for t in tail: c_items = set() for tc in G.child_alists(t.id): has_tail_children = True if tc.get(tt.OPVAR).startswith(vx.NESTING): c_items.add(str(c.instantiation_value(tc.get(tt.OPVAR)))) projVars = tc.projection_variables() if projVars != None: for pvkey, pvval in projVars.items(): c_items.add(tc.instantiation_value(pvkey)) common_items = common_items.intersection(c_items) if not common_items and not has_head_children and not has_tail_children: for c in children: if c.get(tt.OP) != 'comp': if c.get(tt.OPVAR).startswith(vx.NESTING): common_items.add( str(c.instantiation_value(c.get(tt.OPVAR)))) else: projVars = c.projection_variables() if projVars != None: for pvkey, pvval in projVars.items(): common_items.add(c.instantiation_value(pvkey)) if not common_items: return None else: # if common items not empty, ignore existing siblings before creating new siblings sibs = G.child_alists(G.parent_alists(alist.id)[0].id) for x in sibs: if x.id != alist.id: # x.prune() G.prune(x.id) print( f'{pcol.RED}sibling pruned {x.id}{pcol.RESET} {x}{pcol.RESETALL}' ) # setup new sibling branch(s) parent = G.parent_alists(alist.id)[0] op_alist = parent.copy() op_alist.set(alist.get(tt.OPVAR), '') op_alist.set(tt.OP, parent.get(tt.OP)) op_alist.set(tt.OPVAR, parent.get(tt.OPVAR)) op_alist.set(op_alist.get(tt.OPVAR), '') op_alist.state = states.EXPLORED # set as an aggregation node to help with display rendering op_alist.node_type = nt.HNODE G.link(parent, op_alist, 'comp') G.link(alist, op_alist, 'set-comp', create_new_id=False) nodes_enqueue.append((op_alist, parent, False, 'comp')) print( f'{pcol.BLUE}set-comp >> {op_alist.id}{pcol.RESET} {op_alist}{pcol.RESETALL}' ) if alist.children: nodes_enqueue.append((op_alist, alist, False, 'setcomp')) # create children of the new branch # copy to avoid using different version from another thread in loop op_alist_copy = op_alist.copy() for ff in common_items: new_sibling: Alist = op_alist_copy.copy() new_sibling.set(tt.OP, 'value') new_sibling.set(tt.OPVAR, op_alist_copy.get(tt.OPVAR)) new_sibling.set(alist.get(tt.OPVAR), ff) new_sibling.instantiate_variable(alist.get(tt.OPVAR), ff) for ref in new_sibling.variable_references(alist.get(tt.OPVAR)): if ref not in [tt.OPVAR]: new_sibling.set(ref, ff) new_sibling.node_type = nt.ZNODE G.link(op_alist, new_sibling, 'comp_lookup') nodes_enqueue_process.append( (new_sibling, op_alist, True, 'comp_lookup')) print( f'{pcol.BLUE} set-comp-child >>> {new_sibling.id}{pcol.RESET} {new_sibling}{pcol.RESETALL}' ) alist.state = states.IGNORE alist.nodes_to_enqueue_only = nodes_enqueue alist.nodes_to_enqueue_and_process = nodes_enqueue_process return alist
def run_frank(self, alist: Alist): """ Run the FRANK algorithm for an alist Args ---- alist : Alist Return ------ Return True if the instantiation of the project variable of an alist is propagated to the root node. Notes ----- For the given alist, attempt to instantiate projection variable. If instantiation is successful, attempt to reduce Else decompose and add new children to queue """ self.last_heartbeat = time.time() curr_propagated_alists = [] self.max_depth = alist.depth if alist.state is states.PRUNED: self.write_trace( f"{pcol.RED}ignore pruned {alist.id}{pcol.RESET}-{alist}{pcol.RESETALL}" ) return propagated_alists bool_result = False proj_vars = alist.projection_variables() proj_instantiated = True if proj_vars: for p, _ in proj_vars.items(): proj_instantiated = proj_instantiated and alist.is_instantiated( p) agg_vars = alist.get(tt.OPVAR).split(' ') agg_instantiated = True if agg_vars: for v in agg_vars: agg_instantiated = agg_instantiated and alist.is_instantiated( v) if agg_instantiated: bool_result = True if bool_result: alist.state = states.REDUCIBLE self.G.add_alist(alist) # if OPVAR not instantiated, search KB elif not bool_result: bool_result = self.search_kb(alist) # search kb for the variable instantiation if bool_result: is_propagated = False if alist.state != states.REDUCIBLE: # if in reducible state, don't change alist.state = states.EXPLORED self.G.add_alist(alist) if agg_instantiated and proj_instantiated: alist.state = states.REDUCIBLE self.G.add_alist(alist) if self.G.child_ids(alist.id): is_propagated = self.propagate(self.G.child_ids(alist.id)[0]) else: is_propagated = self.propagate( self.G.child_ids(self.G.parent_ids(alist.id)[0])[0]) if is_propagated: prop_alist = self.G.alist(self.root.id) self.write_trace( f"{pcol.CYAN}intermediate ans: " f"{pcol.RESET}-{prop_alist}{pcol.RESETALL}", loglevel=processLog.LogLevel.ANSWER) curr_propagated_alists.append(prop_alist.copy()) self.propagated_alists.append(prop_alist.copy()) else: alist.state = states.EXPLORED self.G.add_alist(alist) for mapOp in self.get_map_strategy(alist): self.decompose(alist, mapOp) return curr_propagated_alists