def __prepare_for_inference(self):

        # the following attributes are updated in decouple.py, which replicates
        # functional cpts and handles nodes with hard evidence, creating clones
        # of nodes in the process (clones are added to another 'decoupled' network)
        self._original = None  # tbn node cloned by this one
        self._master = None  # exactly one clone is declared as master
        self._clamped = False  # whether tbn node has hard evidence

        # the following attributes with _cpt, _cpt1, _cpt2 are updated in cpt.y
        self._values_org = self.values  # original node values before pruning
        self._card_org = self.card  # original node cardinality before pruning
        self._values_idx = None  # indices of unpruned values, if pruning happens

        # -process node and its cpts
        # -prune node values & parents and expand/prune cpts into tabular form
        tbn.cpt.set_cpts(self)

        # the following attributes will be updated next
        self._all01_cpt = None  # whether cpt is 0/1 (not applicable for testing nodes)
        self._cpt_label = None  # for saving to file (updated when processing cpts)

        # identify 0/1 cpts
        if self.testing:
            # selected cpt is not necessarily all zero-one even if cpt1 and cpt2 are
            self._all01_cpt = False
        else:
            self._all01_cpt = np.all(
                np.logical_or(self.cpt == 0, self.cpt == 1))
            u.check(
                not (self.fixed_cpt and self._functional) or self._all01_cpt,
                f'node {self.name} is declared functional but its fixed cpt is not functional',
                f'specifying TBN node')

        # -pruning node values or parents changes the shape of cpt for node
        # -a set of tied cpts may end up having different shapes due to pruning
        # -we create refined ties between groups that continue to have the same shape
        """ this is not really proper and needs to be updated """
        if self.cpt_tie is not None:
            #            s = '.'.join([str(hash(n.values)) for n in self.family])
            self._cpt_tie = f'{self.cpt_tie}__{self.shape()}'

        self.__set_cpt_labels()

        # we need to sort parents & family and also adjust the cpt accordingly
        # this must be done after processing cpts which may prune parents
        self.__sort()
        assert u.sorted(u.map('id', self.parents))
        assert u.sorted(u.map('id', self.family))
 def __shrink(self):
     fvars_   = lambda i: set([i.var]) if i.var.func else set()
     sep_size = lambda i: reduce(lambda x,y: x*y, u.map('card',i.sep),1)
     # heuristic for deciding which branch to sum from
     for i in self.nodes: # bottom-up
         if i.leaf(): 
             i.size = 0
         else: 
             c1, c2 = i.left, i.right
             i.size = c1.size + c2.size + sep_size(c1) + sep_size(c2)
     # MUST process siblings simultaneously before processing their children
     # this is important to enforce the running intersection property
     def down_sum(i,p):
         if i.leaf(): return
         c1, c2 = i.left, i.right
         sum = c1.fvars & c2.fvars
         if sum:
             if c1.size < c2.size or (c1.size == c2.size and c1.id < c2.id):
                 c1.sep -= sum
             else:
                 c2.sep -= sum
         # propagate separator shrinking
         c2.sep &= c1.sep | i.sep 
         c1.sep &= c2.sep | i.sep
         down_sum(c1,i)
         down_sum(c2,i)
     r, h = self.root, self.host
     r.sep -= r.fvars & fvars_(h) 
     down_sum(r,h)
     # update clusters
     self.__set_cls()
Esempio n. 3
0
 def elm_order(self):
     pq = PQ(self.nodes)
     pi = []
     width_l = 0
     width_s = 0
     size = 0
     cliques = []
     while True:
         n = pq.pop()
         if n is None: break  # all nodes have been eliminated
         pi.append(n)
         clique = set(i.tbn_node for i in n.neighbors)
         clique.add(n.tbn_node)
         cliques.append(clique)
         l = 1 + len(n.neighbors)
         s = n.clique_size()
         width_l = max(width_l, l)  # before eliminating node
         width_s = max(width_s, s)  # before eliminating node
         size += s  # before eliminating node
         affected_nodes = self.eliminate(n)  # set
         affected_nodes = list(affected_nodes)  # list
         affected_nodes.sort()  # for deterministic behavior
         for n in affected_nodes:  # n has new fillin count
             pq.add(n, replace=True)
     assert pq.empty()
     assert (self.count == len(pi))
     pi = u.map('tbn_node', pi)
     return pi, cliques, width_l, width_s, size
Esempio n. 4
0
    def restructure_for_mulpro(dims1, dims2, vars):
        assert u.sorted(u.map('id', vars))

        x, y, c, s = Dims.decompose_for_mulpro(dims1, dims2, vars)
        # dims1 has vard (c x s), dims2 has vars (c y s)
        # result of multiply-project will have vars (c x y)
        # s are variables that will be summed out (in dims1 and dims2 but not vars)
        # c are common to dims1, dims2 and vars
        # x are in dims1 but not in dims2 (must be im vars)
        # y are in dims2 but not in dims1 (must be in vars)
        # c, x, y, s are mutually disjoint; c, x, y can be empty
        # s cannot be empty otherwise dims1, dims2 and vars all have the
        # same variables and we would have used multiply instead of mulpro
        assert s

        # matmul requires c be first, s be last or before last (summed out)
        key = lambda vars: vars[0].id if vars else -1
        less = lambda a, b: key(a) < key(b)

        dvars1 = [x, s] if less(x, s) else [s, x]
        dvars2 = [y, s] if less(y, s) else [s, y]
        if less(x, y):
            dvars, invert = [x, y], False  # matmul(dims1,dims2)
        else:
            dvars, invert = [y, x], True  # matmul(dims2,dims1)
        squeeze = not x and not y  # result will two trivial dimensions

        if c:
            for dvars_ in (dvars1, dvars2, dvars):
                dvars_.insert(0, c)

        # matrix multiplication (matmul) requires that the dimensions of dvars1
        # and dvars2 be ordered as follows: dvars1=(c,*,s) and dvars2=(c,s,+)
        # to yield vars=(c,*,+). the current ordering of dvars1 and dvars2 only
        # ensures that c is first so it may violate this pattern, but we can
        # instruct matmul to transpose the last two dimensions on the fly if needed
        s_index1 = -1 if not invert else -2
        s_index2 = -2 if not invert else -1
        transpose1 = dvars1[
            s_index1] != s  # transpose last two dimensions of dims1_
        transpose2 = dvars2[
            s_index2] != s  # transpose last two dimensions of dims2_
        assert not transpose1 or not transpose2  # at most one tensor will be transposed

        # add batch/trivial dimension to dvars1, dvars2 and dvars if needed
        # (when either dims1 or dims2 has batch)
        Dims.insert_batch(dims1, dims2, dvars1, dvars2, dvars)

        dvars1, dvars2, dvars = tuple(dvars1), tuple(dvars2), tuple(dvars)

        # returned dims object will have at most 3 dimensions (plus batch if any)
        # dims may have up to two trivial dimensions (when x and y are empty)
        dims1_, dims2_, dims = get_dims(dvars1), get_dims(dvars2), get_dims(
            dvars)
        assert dims1_.rank <= 4 and dims2_.rank <= 4 and dims.rank <= 4

        return (((dims1_, transpose1), (dims2_, transpose2)), dims, invert,
                squeeze)
    def __compile(self, net, inputs, output, hard_inputs, trainable,
                  elm_method, elm_wait, profile):
        if profile: u.show('\n***PROFILER ON***')
        u.show(f'\nCompiling {self.network_type} into {self.circuit_type}')
        start_compile_time = time.time()

        # net1 and net2 have nodes corresponding to inputs and output (same names)
        net1 = net.copy_for_inference()
        self.tbn = net1
        self.input_nodes = u.map(net1.node, inputs)
        self.output_node = net1.node(output)
        self.hard_input_nodes = u.map(net1.node, hard_inputs)

        # decouple net1 for more efficient compilation
        # net2 is only used to build jointree (duplicate functional cpts)
        net2, elm_order, _ = decouple.get(net1, self.hard_input_nodes,
                                          trainable, elm_method, elm_wait)
        # net2 may be equal to net1 (no decoupling)
        # if net2 != net1 (decoupling happened), then net2._decoupling_of = net1

        # compile tbn into an ops_graph
        jt = jointree.Jointree(net2, elm_order, self.hard_input_nodes,
                               trainable)
        ops_graph = og.OpsGraph(trainable, net.testing)  # empty

        # inference will populate ops_graph with operations that construct tac_graph
        inference.trace(self.output_node, self.input_nodes, net1, jt,
                        ops_graph)
        if u.verbose: ops_graph.print_stats()

        # construct tac_graph by executing operations of ops_graph
        self.ops_graph = ops_graph
        self.tac_graph = tg.TacGraph(ops_graph, profile)
        self.size = self.tac_graph.size
        self.rank = self.tac_graph.rank
        self.binary_rank = self.tac_graph.binary_rank
        self.parameter_count = self.tac_graph.parameter_count

        compile_time = time.time() - start_compile_time
        u.show(f'Compile Time: {compile_time:.3f} sec')
    def simulate(self, size, evidence_type, *, hard_evidence=False):
        u.input_check(evidence_type is 'grid' or evidence_type is 'random',
                      f'evidence type {evidence_type} not supported')

        cards = u.map('card', self.input_nodes)

        if evidence_type is 'grid':
            assert len(cards) == 2 and all(card == 2 for card in cards)
            assert not hard_evidence
            evidence = data.evd_grid(size)
        else:
            evidence = data.evd_random(size, cards, hard_evidence)

        marginals = self.tac_graph.evaluate(evidence)

        return (evidence, marginals)
    def __sort(self):
        assert type(self.parents) is list and type(self.family) is list

        if u.sorted(u.map('id', self.family)):  # already sorted
            self._parents = tuple(self.parents)
            self._family = tuple(self.family)
            return

        self._parents.sort()
        self._parents = tuple(self.parents)

        # save original order of nodes in family (needed for transposing cpt)
        original_order = [(n.id, i) for i, n in enumerate(self.family)]
        self.family.sort()
        self._family = tuple(self.family)

        # sort cpt to match sorted family
        original_order.sort()  # by node id to match sorted family
        sorted_axes = [i for (_, i) in original_order]  # new order of axes
        if self.testing:
            self._cpt1 = np.transpose(self.cpt1, sorted_axes)
            self._cpt2 = np.transpose(self.cpt2, sorted_axes)
        else:
            self._cpt = np.transpose(self.cpt, sorted_axes)
 def sep_size(self, i):
     sep = self.sep(i)
     return reduce(lambda x, y: x * y, u.map('card', sep), 1)
 def cls_size(self, i):
     cls = self.cls(i)
     return reduce(lambda x, y: x * y, u.map('card', cls), 1)