def __init__(self, table, vars, sort=False):

        if sort and not u.sorted(tuple(var.id for var in vars)):
            # used only for cpts as they may not be sorted
            table, vars = self.__sort(table, vars)

        assert type(table) is np.ndarray or (isinstance(table, Number)
                                             and not vars)
        assert u.sorted(tuple(var.id for var in vars))
        assert isinstance(table, Number) or table.shape == tuple(
            var.card for var in vars)

        self.table = table  # ndarray
        self.vars = vars  # sequence of ordered vars
        self.rank = len(vars)

        # tvars are variables without batch
        if vars and vars[0].batch:
            self.tvars = vars[1:]
            self.has_batch = True
        else:
            self.tvars = vars
            self.has_bacth = False

        self.is_scalar = not self.vars or not self.tvars  # no tbn nodes
    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))
示例#3
0
 def __init__(self, table, vars):
     assert type(table) == np.ndarray and type(vars) == tuple
     assert all(not var.is_batch for var in vars)
     assert u.sorted(tuple(v.id for v in vars))
     assert table.shape == tuple(var.card for var in vars)
     self.vars = vars
     self.table = table
     self.rank = len(self.vars)
示例#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 __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)
示例#6
0
def get(size,digits,testing,use_bk,tie_parameters,remove_common=False):
    assert size >= 7
    assert len(digits) >= 2
    assert all(d in (0,1,2,3,4,5,6,7,8,9) for d in digits)
    assert u.sorted(digits)

    # height is multiple of 7: 3 horizontal segments, 4 horizontal spaces
    # width  is multiple of 4: 2 vertical segments,   2 vertical spaces
    h_inc = 7
    w_inc = 4 
    
    net = TBN('digits')
    
    ###
    ### master START
    ###
    
    # nodes:
    # d digit
    # r upper-row (of bounding rectangle)
    # c left-col  (of bounding rectangle)
    # h height    (of bounding rectangle)
    # w width     (of bounding rectangle)
    # t thickness (of lines)
        
    # values
    dvals  = digits                # for digits (root)
    rvals  = range(0,size-h_inc+1) # for upper-row of digit (root)
    cvals  = range(0,size-w_inc+1) # for left-column of digit (root)
    srange = range(1,size+1)       # height, width, thickness (will be pruned)
    
    # constraints and functions
    # w is _constrained_ by c (length of segments)
    uniform = lambda values: [1./len(values)]*len(values)
    wct     = lambda c, w, w_inc=w_inc, size=size: (w % w_inc) == 0 and w <= size-c 
    
    # nodes
    dn  = Node('d', values=dvals,  parents=[],   cpt=uniform(dvals))
    rn  = Node('r', values=rvals,  parents=[],   cpt=uniform(rvals))
    cn  = Node('c', values=cvals,  parents=[],   cpt=uniform(cvals))
    wn  = Node('w', values=srange, parents=[cn], cpt=wct, fixed_zeros=use_bk)
    
    for n in [dn,rn,cn,wn]: net.add(n)
        
    ###
    ### segments
    ###
    
    # seven segments: 0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G
    # 1,2,4,5 are vertical
    # 0,3,6   are horizontal
    # https://en.wikipedia.org/wiki/Seven-segment_display
    
    # each digit corresponds to some segments (activated segments)
    segments  = (0,1,2,3,4,5,6)
    vsegments = (1,2,4,5) # vertical segments
    hsegments = (0,3,6)   # horizontal segments
    # map digits to segments
    dsegments = {0:'012345', 1:'12',     2:'01346', 3:'01236',   4:'1256', 
                 5:'02356',  6:'023456', 7:'012',   8:'0123456', 9:'012356'}
    
    # segments needed for the given digits
    segments  = tuple(s for s in segments if any(str(s) in dsegments[d] for d in digits))
    if remove_common:
        common   = tuple(s for s in segments if all(str(s) in dsegments[d] for d in digits))
        segments = tuple(s for s in segments if s not in common)
        u.show('Removing common segments',common)
    vsegments = tuple(s for s in vsegments if s in segments)
    hsegments = tuple(s for s in hsegments if s in segments)
    
    # nodes:
    # a segment is a rectangle
    # a segment s has upper-row srn[s], left-column scn[s], height shn[s], width swn[s],
    # whether segment is activated san[s], whether it will render in row i, sirn[s,i], 
    # whether it will render in column i, sicn[s,i], and whether it will render in 
    # pixel i,j, spn[s,i,j]
    
    # values
    irange = range(size)     # for segment row and column
    srange = range(1,size+1) # for segment height and width
                       
    # san[s] is whether segment s is activated given digit (True,False)
    # san[s] is a _function_ of node dn
    
    san = {} # maps segment to node
    for s in segments:
        fn   = lambda d, s=s, dsegments=dsegments: str(s) in dsegments[d]
        node = Node(f's{s}', parents=[dn], cpt=fn, fixed_cpt=use_bk, functional=True)
        net.add(node)
        san[s] = node
    
    # srn[s] is upper-row   for segment s
    # scn[s] is left-column for segment s
    # srn[s] and scn[s] are _functions_ of nodes rn and cn of master
    
    srn    = {} # maps segment to node
    scn    = {} # maps segment to node
    shifts = [(0,0), (0,3), (3,3), (6,0), (3,0), (0,0), (3,0)]
    # rshift: shift rn (digit) down  to get srn (segment)
    # cshift: shift cn (digit) right to get scn (segment)
        
    fn   = lambda r: r + 3
    sr3n = Node(f'sr3', values=irange, parents=[rn], cpt=fn, fixed_cpt=use_bk, functional=True)
    fn   = lambda r: r + 6
    sr6n = Node(f'sr6', values=irange, parents=[rn], cpt=fn, fixed_cpt=use_bk, functional=True)
    fn   = lambda c: c + 3
    sc3n = Node(f'sc3', values=irange, parents=[cn], cpt=fn, fixed_cpt=use_bk, functional=True)
    for n in (sr3n,sr6n,sc3n): net.add(n)
    
    rsn = {0:rn, 3:sr3n, 6:sr6n}
    csn = {0:cn, 3:sc3n}
    
    for s in segments: 
        rshift, cshift = shifts[s]
        srn[s] = rsn[rshift]
        scn[s] = csn[cshift]

    # sirn[s,i] is whether segment s will render in row i (True,False)
    # sicn[s,i] is whether segment s will render in col i (True,False)
    # sirn[s,i] is a _function_ of srn[s] and wn
    # sicn[s,i] is a _function_of  scn[s] and wn
    
    sirn = {} # maps (segment,row) to node
    sicn = {} # maps (segment,col) to node
    
    for s in segments:
        for i in irange: 
            name = f'in_r{s}_{i}'
            if s in vsegments: # vertical segment
                pa = [srn[s],wn]
                fn = lambda r, h, i=i: r <= i and i < r+h
            else:
                pa = [srn[s]]
                fn = lambda r, i=i: r == i
            node = Node(name, parents=pa, cpt=fn, fixed_cpt=use_bk, functional=True)
            net.add(node)
            sirn[(s,i)] = node
 
            name = f'in_c{s}_{i}'
            if s not in vsegments: # horizontal
                pa = [scn[s],wn]
                fn = lambda c, w, i=i: c <= i and i < c+w
            else:
                pa = [scn[s]]
                fn = lambda c, i=i: c == i
            node = Node(name, parents=pa, cpt=fn, fixed_cpt=use_bk, functional=True)
            net.add(node)
            sicn[s,i] = node
            
    # spn[s,i,j] is whether segment s will render in pixel i,j (True,False)
    # spn[s,i,j] is a _function_ of san[s] and sirn[s,i] and sicn[s,j]
    
    spn = {} # maps (segment,row,col) to node
    fn  = lambda a, r, c: a and r and c
    
    for s in segments:
        for i in irange:
            for j in irange:
                name = f'p{s}_{i}_{j}'
                pa   = [san[s],sirn[(s,i)],sicn[(s,j)]]
                node = Node(name, parents=pa, cpt=fn, fixed_cpt=use_bk, functional=True)
                net.add(node)
                spn[(s,i,j)] = node

    ###
    ### master END
    ###
    
    # image pixels: whether pixel i,j will render in image (iff some segment renders)
    
    output = dn.name
    inputs = []
    fn     = lambda *inputs: any(inputs) # or-gate
    
    tie = 'pixel' if tie_parameters else None
    for i in irange:
        for j in irange:
            name = f'p_{i}_{j}'
            pa   = [spn[(s,i,j)] for s in segments]
            tie  = tie if not testing else None
            pn   = Node(name, parents=pa, cpt=fn, testing=testing, cpt_tie=tie)
            net.add(pn)
            inputs.append(name)
    
    return (net, inputs, output)