def bn1(): #CPTs aa = [0.6, 0.4] bb = [[0.2, 0.8], [0.75, 0.25]] cc = [[0.9, 0.1], [0.1, 0.9]] dd = [[[0.95, 0.05], [0.9, 0.1]], [[0.8, 0.2], [0.0, 1.0]]] ee = [[0.7, 0.3], [0.0, 1.0]] #nodes a = Node('a', values=('b', 'g'), cpt=aa) b = Node('b', parents=[a], cpt=bb) c = Node('c', parents=[a], cpt=cc) d = Node('d', parents=[b, c], cpt=dd) e = Node('e', parents=[c], cpt=ee) #TBN n = TBN('bn1') n.add(a) n.add(b) n.add(c) n.add(d) n.add(e) return n
def tbn1(random=False): #CPTs aa = [0.6, 0.4] bb = [[0.2, 0.8], [0.75, 0.25]] cc = [[0.9, 0.1], [0.1, 0.9]] dd = [[[0.95, 0.05], [0.9, 0.1]], [[0.8, 0.2], [0.0, 1.0]]] ee = [[0.7, 0.3], [0.0, 1.0]] if random: bb = dd = None #nodes a = Node('a', values=('b', 'g'), cpt=aa) b = Node('b', parents=[a], testing=True, cpt1=bb, cpt2=bb) c = Node('c', parents=[a], testing=True, cpt1=cc, cpt2=cc) d = Node('d', parents=[b, c], testing=True, cpt1=dd, cpt2=dd) e = Node('e', parents=[c], cpt=ee) #TBN n = TBN('tbn1') n.add(a) n.add( c) # intentionally c then b (selecting b can now depend on evidence e) n.add(b) n.add(d) n.add(e) return n
def fn2_chain(size, card=2): assert size >= 2 and card >= 2 net = TBN('fn2_chain') bvalues = ('v0', 'v1') values = tuple('v%d' % i for i in range(card)) x1 = Node('x', values=bvalues, parents=[]) y1 = Node('y', values=bvalues, parents=[]) z1 = Node('z1', values=values, parents=[x1, y1], testing=True) net.add(x1) net.add(y1) net.add(z1) for i in range(2, size + 1): last = (i == size) name = 'z' if last else 'z%d' % i values = bvalues if last else values p_name = 'z%d' % (i - 1) parent = net.node(p_name) z = Node(name, values=values, parents=[parent], testing=True) net.add(z) #net.dot(view=True) return (net, 'x', 'y', 'z')
def getHMM(size, card, param=False, transition=None, emission=None): # define an HMM model of size nodes with card hidden states assert size >= 2 and card >= 2 if param: assert transition is not None and emission is not None u.input_check(np.array(transition).shape == (card, card), f'wrong size for transition matrix') u.input_check(np.array(emission).shape == (card, card), f'wrong size for matrix') # check size for transition and emission matrix hmm = TBN(f'hmm_{size}') values = ['v' + str(i) for i in range(card)] hidden_nodes = [] # store list of created hidden nodes for i in range(size): # add hidden node if i == 0: uniform_cpt = [1./card] * card; hidden_i = Node('h_0', values=values, parents=[], cpt=uniform_cpt) hmm.add(hidden_i) # notice that H0 is uniform if parametrized hidden_nodes.append(hidden_i) else: hidden_i = Node('h_' + str(i), values=values, parents=[hidden_nodes[i - 1]], cpt_tie="transition", cpt=transition) hmm.add(hidden_i) hidden_nodes.append(hidden_i) # add evidence node evidence_i = Node('e_' + str(i), values=values, parents=[hidden_nodes[i]], cpt_tie="emission", cpt=emission) hmm.add(evidence_i) # finish creating the hmm #hmm.dot(view=True) print("Finish creating HMM_{} with cardinality {}".format(size, card)) return hmm
def kidney_tbn(): #nodes l = Node('L', values=['y', 'n']) t = Node('T', values=['A', 'B']) s = Node('S', values=['y', 'n'], parents=[l, t], testing=True) #TBN n = TBN('kidney tbn') n.add(l) n.add(t) n.add(s) return n
def bn0(): #CPTs aa = [0.6, 0.4] bb = [[0.9, 0.1], [0.2, 0.8]] #nodes a = Node('a', cpt=aa) b = Node('b', parents=[a], cpt=bb) #TBN n = TBN('bn0') n.add(a) n.add(b) return n
def getTestingHMM(size, card, N, testing=False, param=False, transition1=None, transition2=None, emission=None): # define an N order testing HMM model of length size with cardinality hidden states assert size >= 2 and card >= 2 and N >= 1 if param: u.input_check(np.array(transition1).shape == (card,) * (N + 1), "wrong size for transition matrix") u.input_check(np.array(emission).shape == (card, card), "wrong size for emission matrix") if testing: u.input_check(np.array(transition2).shape == (card,) * (N + 1), "wrong size for transition2 matrix") # check the size of transition and emission probabilities hmm = TBN(f'thmm_{N}_{size}') values = ['v' + str(i) for i in range(card)] hidden_nodes = [] # store list of hidden nodes # add first N hidden nodes for i in range(N): name = 'h_' + str(i) parents = [hidden_nodes[j] for j in range(i)] cpt = (1./card) * np.ones(shape=(card,)*(i+1)) # create a uniform conditional cpt hidden_i = Node(name, values=values, parents=parents, cpt=cpt) # the first N nodes cannot be testing hmm.add(hidden_i) hidden_nodes.append(hidden_i) # add hidden nodes # add the subsequent hidden nodes for i in range(N, size): name = 'h_' + str(i) parents = [hidden_nodes[j] for j in range(i-N, i)] if not testing: hidden_i = Node(name, values=values, parents=parents, cpt=transition1, cpt_tie="transition") else: hidden_i = Node(name, values=values, parents=parents, testing=True, cpt1=transition1, cpt2=transition2, cpt_tie="transition") hmm.add(hidden_i) hidden_nodes.append(hidden_i) # add evidence for i in range(size): name = 'e_' + str(i) parents = [hidden_nodes[i]] evidence_i = Node(name, values=values, parents=parents, cpt=emission, cpt_tie="emission") hmm.add(evidence_i) # finish defining the hmm # hmm.dot(view=True) print("Finish creating a {}-order testing hmm of length {} and cardinality {}".format(N, size, card)) return hmm
def kidney_full(): #CPTs ll = [0.49, 0.51] tt = [[0.77, .23], [0.24, 0.76]] ss = [[[0.73, 0.27], [0.69, 0.31]], [[0.93, 0.07], [0.87, 0.13]]] #nodes l = Node('L', values=['y', 'n'], cpt=ll) t = Node('T', values=['A', 'B'], parents=[l], cpt=tt) s = Node('S', values=['y', 'n'], parents=[l, t], cpt=ss) #TBN n = TBN('kidney true model') n.add(l) n.add(t) n.add(s) return n
def bn3(): #CPTs aa = [0.4, 0.6] bb = [[0.9, 0.1], [0.1, 0.9]] cc = [[0.3, 0.7], [0.8, 0.2]] #nodes a = Node('a', values=('t', 'f'), cpt=aa) b = Node('b', parents=[a], cpt=bb) c = Node('c', parents=[a], cpt=cc) #TBN n = TBN('bn3') n.add(a) n.add(b) n.add(c) return n
def bn2(): #CPTs aa = [0.6, 0.4] bb = [[0.9, 0.1], [0.2, 0.8]] cc = [[0.3, 0.7], [0.5, 0.5]] #nodes a = Node('a', cpt=aa) b = Node('b', parents=[a], cpt=bb) c = Node('c', parents=[b], cpt=cc) #TBN n = TBN('bn2') n.add(a) n.add(b) n.add(c) return n
def bn4(): #CPTs aa = [0.2, 0.8] bb = [0.7, 0.1, 0.2] cc = [[[0.3,0.7],[0.5,0.5]], \ [[0.8,0.2],[0.4,0.6]], \ [[0.1,0.9],[0.7,0.3]]] #nodes a = Node('a', cpt=aa) b = Node('b', values=('r', 'b', 'g'), cpt=bb) c = Node('c', parents=[b, a], cpt=cc) #TBN n = TBN('bn4') n.add(a) n.add(b) n.add(c) return n
def tbn3(random=False): #CPTs aa = [0.4, 0.6] bb = [[0.9, 0.1], [0.1, 0.9]] cc = [[0.3, 0.7], [0.8, 0.2]] if random: cc = None #nodes a = Node('a', values=('t', 'f'), cpt=aa) b = Node('b', parents=[a], cpt=bb) c = Node('c', parents=[a], testing=True, cpt1=cc, cpt2=cc) #TBN n = TBN('tbn3') n.add(a) n.add(b) n.add(c) return n
def tbn2(random=False): #CPTs aa = [0.6, 0.4] bb = [[0.9, 0.1], [0.2, 0.8]] cc = [[0.3, 0.7], [0.5, 0.5]] if random: bb = cc = None #nodes a = Node('a', cpt=aa) b = Node('b', parents=[a], testing=True, cpt1=bb, cpt2=bb) c = Node('c', parents=[b], testing=True, cpt1=cc, cpt2=cc) #TBN n = TBN('tbn2') n.add(a) n.add(b) n.add(c) return n
def chain(testing=False): n0 = Node('S', parents=[]) n1 = Node('n1', parents=[n0], testing=testing) n2 = Node('n2', parents=[n1], testing=testing) n3 = Node('M', parents=[n2], testing=testing) n4 = Node('n4', parents=[n3], testing=testing) n5 = Node('n5', parents=[n4], testing=testing) n6 = Node('E', parents=[n5], testing=testing) net = TBN('chain') net.add(n0) net.add(n1) net.add(n2) net.add(n3) net.add(n4) net.add(n5) net.add(n6) return net
def get(net1, hard_evd_nodes, trainable_tbn, elm_method, elm_wait): assert net1._for_inference #net1.dot(fname='tbn_pre_decouple.gv', view=True) u.show(f' Decoupling tbn:') elm_order, cliques1, max_binary_rank1, stats = net1.elm_order( elm_method, elm_wait) u.show(' ', stats) # cutting edges outgoing form hard evidence nodes cut_edges = lambda n: len(n.children) >= 1 and n in hard_evd_nodes and \ (n.parents or len(n.children) >= 2) cut_edges_set = set(n for n in net1.nodes if cut_edges(n)) # replicating functional cpts # if both duplicate and cut_edges trigger, use cut_edges as it is more effective duplicate = lambda n: n not in cut_edges_set and len(n.children) >= 2 \ and n.is_functional(trainable_tbn) duplicate_set = set(n for n in net1.nodes if duplicate(n)) # perhaps decoupling does nothing if not duplicate_set and not cut_edges_set: u.show(' nothing to decouple') return net1, elm_order, (max_binary_rank1, max_binary_rank1 ) # no decoupling possible # we will decouple net2 = TBN(f'{net1.name}__decoupled') net2._decoupling_of = net1 # -when creating a clone c(n) in net2 for node n in net1, we need to look up the # parents of c(n) in net2. # -this is done by calling get_image(p) on each parent p of node n # -the length of images[p] equals the number of times get_image(p) will be called # -members of images[p] may not be distinct depending on the replication strategey images = {} def get_image(n): return images[n].pop() # -when we have hard evidence on node n (net1), we create a replica r (net2) of n # for each child of n, which copies evidence on n into the cpts of its children. # -maps node r (net2) to node n (net1) that it is copying evidence from evidence_from = {} # maps node n (net1) to a tuple (c_1,...,c_k) where k is the number of clones that # node n will have in nets2, and c_i is the number of children for clone i in net2 ccounts = {} # fully replicated(i): one i-replica for each c-replica, where c is child of i in net1 # partial replicated(i): one i-replica for each child c of i in net1 fully_replicated = lambda i: all(ccount == 1 for ccount in ccounts[i]) replicas_count = lambda i: len(ccounts[i] ) # number of replicas node i has in net2 # compute the number of replicas in net2 for each node in net1 (fill ccounts) for n in reversed(net1.nodes): # bottom up ccounts[n] = [] cparents = set() for c in n.children: cparents |= set(c.parents) #replicate_node = any(cparents <= clique for clique in cliques1) #replicate_node = all(p in duplicate_set for p in n.parents) replicate_node = True if n in duplicate_set and replicate_node: # replicate node n for c in n.children: if True: #not fully_replicated(c): # replicate node n for each replica of child c ccounts[n].extend([1] * replicas_count(c)) else: # replicate node n for each child c ccounts[n].append(replicas_count(c)) else: # do not replicate node n # n could be in cut_edges_set, but ccounts will not be used in that case duplicate_set.discard(n) children_replicas_count = sum( replicas_count(c) for c in n.children) ccounts[n].append(children_replicas_count) # cutting edges takes priority over decoupling as it is more effective for n in net1.nodes: # visiting parents before children if n in cut_edges_set: # disconnect n from its children assert n not in duplicate_set n._clamped = True # flag set in original network (net1) parents = [get_image(p) for p in n.parents] master = clone_node(n, n.name, parents) net2.add(master) images[n] = [] # master not added to images as it will not be a parent of any node in net2 for i, c in enumerate(n.children): for j in range(replicas_count( c)): # j iterates over replicas of child c # these clones will be removed after elimination order is computed # clones are not testing even if master is testing clone = Node(f'{n.name}_evd{i}_{j}', values=master.values, parents=[]) net2.add(clone) evidence_from[clone] = master images[n].append( clone ) # children of n will reference clones, not master elif n in duplicate_set: # duplicate node n and its functional cpt images[n] = [] for i, ccount in enumerate(ccounts[n]): assert ccount > 0 # number of children each clone will have in net2 parents = [get_image(p) for p in n.parents] clone = clone_node(n, f'{n.name}_fcpt{i}', parents) if i > 0: clone._master = False # clone() sets this to True net2.add(clone) images[n].extend([clone] * ccount) else: # just copy node n from net1 to net2 (ccount, ) = ccounts[n] # number of children clone will have in net2 parents = [get_image(p) for p in n.parents] clone = clone_node(n, n.name, parents) net2.add(clone) images[n] = [clone] * ccount assert not net2._for_inference assert len(images) == len(net1.nodes) assert len(images) <= len(net2.nodes) assert all(v == [] for v in images.values()) #net2.dot(fname='tbn_post_decouple.gv', view=True) elm_order, _, max_binary_rank2, stats = net2.elm_order( elm_method, elm_wait) u.show(' ', stats) if not duplicate_set: elm_order = [n._original for n in elm_order if n not in evidence_from] # only clamping took place, so we only care about elimination order # return original network with _clamped flag set for some nodes return net1, elm_order, (max_binary_rank1, max_binary_rank2) if cut_edges_set: # some variables were clamped # get rid of auxiliary evidence nodes from elimination order elm_order = [n for n in elm_order if n not in evidence_from] # need to restore net2 by getting rid of auxiliary evidence nodes # and restoring children of clamped nodes net2.nodes = [n for n in net2.nodes if n not in evidence_from] replace = lambda n: evidence_from[n] if n in evidence_from else n for n in net2.nodes: n._parents = tuple(replace(p) for p in n.parents) n._family = tuple(replace(f) for f in n.family) u.show(f' node growth: {len(net2.nodes)/len(net1.nodes):.1f}') return net2, elm_order, (max_binary_rank1, max_binary_rank2)
def get(size, output, testing, use_bk, tie_parameters): assert output in ('label', 'height', 'width', 'row', 'col') net = TBN(f'rectangle_{size}_{size}') irange = range(size) # [0,..,size-1] srange = range(1, size + 1) # [1,..,size] ### 1. row and column origins (only roots) are _unconstrained_ # cpts: shape (size) uniform = lambda values: [1. / len(values)] * len(values) # nodes orn = Node('row', values=irange, parents=[], cpt=uniform(irange)) ocn = Node('col', values=irange, parents=[], cpt=uniform(irange)) net.add(orn) net.add(ocn) ### 2. height and width are _constrained_ by row and column origins # cpts: shape (size, size) constraint = lambda p, n, size=size: p + n <= size # nodes h = Node('height', values=srange, parents=[orn], cpt=constraint, fixed_zeros=use_bk) w = Node('width', values=srange, parents=[ocn], cpt=constraint, fixed_zeros=use_bk) net.add(h) net.add(w) ### 3. type is determined by height and width (except when height=weight) # cpt: shape (size,size,2) constraint = lambda h, w, t: h >= w if t == 'tall' else w >= h # nodes t = Node('label', values=('tall', 'wide'), parents=[h, w], cpt=constraint, fixed_cpt=use_bk) net.add(t) ### 4. row(i) is _determined_ by row origin and height: whether row i has an on-pixel ### col(i) is _determined_ by col origin and width: whether col i has an on-pixel row = {} # maps row to node col = {} # maps col to node for i in irange: fn = lambda o, s, i=i: (o <= i and i < o + s) row[i] = Node(f'r_{i}', parents=[orn, h], cpt=fn, fixed_cpt=use_bk, functional=True) col[i] = Node(f'c_{i}', parents=[ocn, w], cpt=fn, fixed_cpt=use_bk, functional=True) net.add(row[i]) net.add(col[i]) ### 5. pixels are _determined_ by row and column # cpt for pixel (i,j): shape (2,2,2) function = lambda r, c: r and c # nodes inputs = [] pname = lambda i, j: f'pixel_{i}_{j}' # evidence nodes must be generated row, then column to match data generation tie = 'pixel' if tie_parameters else None for i in irange: r = row[i] for j in irange: c = col[j] n = pname(i, j) tie = tie if not testing else None p = Node(n, parents=[r, c], cpt=function, testing=testing, cpt_tie=tie) net.add(p) inputs.append(n) #net.dot(view=True) return (net, inputs)
def get(vcount, scount, pcount, fcount, back, testing): assert vcount >= 2 and scount >= 2 assert fcount >= 0 and fcount <= vcount and pcount < vcount assert back < vcount and pcount <= back # decide values and parents first i2values = {} i2parents = {} parents_pool = [] for i in range(vcount): sc = np.random.randint(2, scount + 1) # number of values pc = np.random.randint(1 + min(i, pcount)) # number of parents assert sc >= 2 and sc <= scount assert pc <= pcount i2values[i] = range(sc) i2parents[i] = list(np.random.choice(parents_pool, pc, replace=False)) parents_pool.append(i) if len(parents_pool) > back: parents_pool.pop(0) # choose functional variables randomly candidates = [i for i, parents in i2parents.items() if parents] # exclude roots fcount = min(fcount, len(candidates)) functional_indices = set( np.random.choice(candidates, fcount, replace=False)) # construct bn bn = TBN(f'random-{vcount}-{scount}-{pcount}-{fcount}-{back}') if testing: # construct a tbn equivalent to bn bn2 = TBN(f'random-{vcount}-{scount}-{pcount}-{fcount}-{back}-Testing') i2node2 = {} i2node = {} # maps node number to node object for i in range(vcount): values = i2values[i] parents = [i2node[k] for k in i2parents[i]] functional = i in functional_indices assert not functional or parents # roots cannot be functional cpt_shape = [p.card for p in parents] cpt_shape.append(len(values)) cpt = __random_cpt(cpt_shape, functional) # tbn if testing: parents2 = [i2node2[k] for k in i2parents[i]] if parents2 and not functional and np.random.random( ) >= .5: # testing node = Node(f'v{i}', values=values, parents=parents2, cpt1=cpt, cpt2=cpt) else: # regular node = Node(f'v{i}', values=values, parents=parents2, cpt=cpt) bn2.add(node) i2node2[i] = node # bn node = Node(f'v{i}', values=values, parents=parents, cpt=cpt) bn.add(node) i2node[i] = node roots = [node.name for node in bn.nodes if not node.parents] leaves = [node.name for node in bn.nodes if not node.children] if testing: return bn, roots, leaves, bn2 return bn, roots, leaves
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)