Beispiel #1
0
   2 : 00000010 
  12 : 00001100 
  13 : 00001101 
   6 : 00000110 
  14 : 00001110 
  15 : 00001111 
   7 : 00000111 
   3 : 00000011 
   1 : 00000001 

"""
from opticks.bin.ffs import clz_

height = 3

depth_ = lambda i:31 - clz_(i)    ## count leading zeros 

elevation_ = lambda i:height - depth_(i)

next_ = lambda i:i >> 1 if i & 1 else (i << elevation_(i)) + (1 << elevation_(i))

leftmost_ = lambda h:1 << h 

bin_ = lambda _:"{0:08b}".format(_)


if __name__ == '__main__':
   
    i = leftmost_(height)
    while i > 0:
        print "%4s : %s " % ( i, bin_(i) )
Beispiel #2
0
            begin2 = POSTORDER_BEGIN(tmp)
            end2 = POSTORDER_END(tmp)

            print "  %2x %2x -> %6x ->  %2x %2x " % (begin, end, tmp, begin2,
                                                     end2)

            assert begin2 == begin
            assert end2 == end


POSTORDER_NODE = lambda postorder, i: (((postorder) & (0xF << (i) * 4)) >>
                                       (i) * 4)

TREE_HEIGHT = lambda numNodes: (ffs_((numNodes) + 1) - 2)
TREE_NODES = lambda height: ((0x1 << (1 + (height))) - 1)
TREE_DEPTH = lambda nodeIdx: (32 - clz_((nodeIdx)) - 1)


def test_tree():
    """
    ::

        In [421]: numNodes = [TREE_NODES(_) for _ in range(5)]
        In [422]: numNodes
        Out[422]: [1, 3, 7, 15, 31]
        In [423]: map(TREE_HEIGHT, numNodes)
        Out[423]: [0, 1, 2, 3, 4]
    """
    numNodes = [TREE_NODES(_) for _ in range(5)]
    print map(TREE_HEIGHT, numNodes)
Beispiel #3
0
class Node(object):
    def __init__(self, idx=0, l=None, r=None, **kwa):
        """
        :param idx: 1-based levelorder (aka breadth first) tree index, root at 1
        """
        self.idx = idx
        self.l = copy.deepcopy(l)  # for independence
        self.r = copy.deepcopy(r)

        self.next_ = None
        self.pidx = None

        # below needed for CSG
        self.shape = None
        self.operation = None
        self._param = None
        self.parent = None
        self.name = "unnamed"
        self.ok = False

        self.apply_(**kwa)

    def _get_param(self):
        return self._param

    def _set_param(self, v):
        self._param = np.asarray(v) if v is not None else None

    param = property(_get_param, _set_param)

    def apply_(self, **kwa):
        for k, v in kwa.items():
            if k == "shape":
                self.shape = v
            elif k == "operation":
                self.operation = v
            elif k == "param":
                self.param = v
            elif k == "name":
                self.name = v
            elif k == "ok":
                self.ok = v
            else:
                log.warning("ignored Node param %s : %s " % (k, v))
            pass

    def annotate(self):
        """
        * call this only on the root node
        * hmm should make tree complete first to get expected levelorder indices
        """
        # tree labelling
        self.maxidx = "WHERE IS THIS USED"
        self.lheight = Node.lheight_(self)

        Node.levelorder_label_i(self)
        self.maxdepth = Node.depth_r(self)

        Node.parenting_r(self)
        Node.postorder_threading_r(self)

        if self.name in ["root1", "root2", "root3", "root4"]:
            Node.dress(self)
        pass

    @classmethod
    def dress(cls, root):
        """
        Assumes perfect trees 
        """
        nno = Node.nodecount_r(root)
        lmo = Node.leftmost_leaf(root)
        rmo = Node.rightmost_leaf(root)
        nle = rmo.idx - lmo.idx + 1

        leftop = Node.leftmost(root)
        node = leftop
        while node is not None:
            if node.is_leaf:
                assert 0, "not expecting leaves"
                node.apply_(shape=CSG_.SPHERE, param=[0, 0, 0, 100])
            elif node.is_bileaf:
                node.apply_(operation=CSG_.INTERSECTION
                            )  ## causes infinite tranche loop for iterative
                node.l.apply_(shape=CSG_.BOX,
                              param=[0, node.l.side * 30 + 1, 0, 100])
                node.r.apply_(shape=CSG_.SPHERE,
                              param=[0, node.r.side * 30 + 1, 0, 100])
            else:
                node.apply_(operation=CSG_.UNION)
            pass
            node = node.next_

    @classmethod
    def NumNodes(cls, h, d=0):
        """
        Number of nodes for perfect binary tree of height h,  2^(h+1) - 1::

            In [55]: map(Node.NumNodes, range(10))
            Out[55]: [1, 3, 7, 15, 31, 63, 127, 255, 511, 1023]

        For a subtree starting at depth d, the number of nodes is  2^([h-d]+1) - 1, 
        which is the full tree NumNodes expression with h -> h-d.
        For example::

                h-d = 0   2^(0+1) - 1 = 1    at the leaf
                h-d = 1   2^(1+1) - 1 = 3    triplet bileaf
                h-d = 2   2^(2+1) - 1 = 7    2 bileafs and an op

        """
        return (0x1 << (h - d + 1)) - 1

    @classmethod
    def traverse(cls, leftop, label="traverse"):
        """
        """
        print "%s : following thread..." % label
        node = leftop
        while node is not None:
            print node
            node = node.next_

    def _get_textgrid(self):
        """

        :: 

            In [25]: Node.anno = property(lambda self:self.idx)   # levelorder index (1-based)

            In [26]: root4.txt
            Out[26]: 
            root4                                                                                                                            
                                                                          1                                                                
                                                                          o                                                                
                                          2                                                               3                                
                                          o                                                               o                                
                          4                               5                               6                               7                
                          o                               o                               o                               o                
                  8               9              10              11              12              13              14              15        
                  o               o               o               o               o               o               o               o        
             16      17      18      19      20      21      22      23      24      25      26      27      28      29      30      31    
              o       o       o       o       o       o       o       o       o       o       o       o       o       o       o       o    
                                                                                                                                           
            In [27]: Node.anno = property(lambda self:self.tag)       ## return to default


            In [42]: Node.anno = property(lambda self:self.pidx)    # postorder index (0-based)

            In [43]: root4.txt
            Out[43]: 
            root4                                                                                                                            
                                                                         14                                                                
                                                                          o                                                                
                                          6                                                              13                                
                                          o                                                               o                                
                          2                               5                               9                              12                
                          o                               o                               o                               o                
                  0               1               3               4               7               8              10              11        
                  o               o               o               o               o               o               o               o        
                                                                                                                                           
              o       o       o       o       o       o       o       o       o       o       o       o       o       o       o       o    



        """
        if not hasattr(self, 'maxdepth'):
            self.annotate()

        maxdepth = self.maxdepth

        nodes = Node.inorder_r(self, [])
        #sides = map(lambda _:_.side, nodes)
        depths = map(lambda _: _.depth, nodes)
        #hw = max(map(abs,sides))

        #print "sides %r hw %d " % (sides, hw)

        ni = 2 * (maxdepth + 1 + 1)
        nj = len(nodes) + 1

        a = np.empty((ni, nj), dtype=np.object)

        if self.name is not None:
            a[0, 0] = self.name
        pass

        for inorder_idx, node in enumerate(nodes):

            i = 2 * node.depth + 2
            j = inorder_idx

            try:
                a[i - 1, j] = node.anno
                a[i, j] = "o"  # node.tag
            except IndexError:
                print "IndexError depth:%2d inorder_idx:%2d i:%2d j:%2d : %s " % (
                    node.depth, inorder_idx, i, j, node)

        pass
        return T.init(a)

    txt = property(_get_textgrid)

    def _get_anno(self):
        return self.tag

    anno = property(_get_anno)

    #def __str__(self):
    #    tg = self.textgrid
    #    return T.__str__(tg)

    def __repr__(self):
        if self.is_bare:
            if self.l is not None and self.r is not None:
                return "Node(%d,l=%r,r=%r)" % (self.idx, self.l, self.r)
            elif self.l is None and self.r is None:
                return "Node(%d)" % (self.idx)
            else:
                assert 0
        else:
            if self.is_primitive:
                return "%s.%s" % (self.tag, CSG_.desc(self.shape))
            else:
                return "%s.%s(%r,%r)" % (self.tag, CSG_.desc(
                    self.operation), self.l, self.r)

    is_primitive = property(lambda self: self.shape is not None)
    is_operation = property(lambda self: self.operation is not None)
    is_bare = property(
        lambda self: self.operation is None and self.shape is None)

    is_leaf = property(lambda self: self.l is None and self.r is None)

    # bileaf is an operation applied to two leaf nodes, another name is a triple
    is_bileaf = property(
        lambda self: not self.is_leaf and self.l.is_leaf and self.r.is_leaf)

    is_left_requiring_levelorder = property(
        lambda self: self.idx % 2 == 0
    )  # convention using 1-based levelorder index
    is_left = property(
        lambda self: self.parent is not None and self.parent.l is self)
    is_root = property(lambda self: self.parent is None)

    def _get_tag(self):
        """
        hmm: subsequent dev fused operation and shape into single type code
        """
        if self.operation is not None:
            if self.operation in [
                    CSG_.UNION, CSG_.INTERSECTION, CSG_.DIFFERENCE
            ]:
                ty = CSG_.desc(self.operation)[0]
            else:
                assert 0
            pass
        elif self.shape is not None:
            if self.shape in [CSG_.SPHERE, CSG_.BOX, CSG_.ZERO]:
                ty = CSG_.desc(self.shape)
            else:
                assert 0
            pass
        elif self.is_leaf:
            ty = "p"
        else:
            ty = "o"
        pass
        return "%s%d" % (ty, self.idx)

    tag = property(_get_tag)

    @classmethod
    def nodecount_r(cls, root):
        if root is None:
            return 0
        return 1 + cls.nodecount_r(root.l) + cls.nodecount_r(root.r)

    @classmethod
    def is_complete_r(cls, root, index=1, nodecount=None, debug=False):
        if nodecount is None:
            nodecount = cls.nodecount_r(root)

        if index >= nodecount:
            if debug:
                print "index %d >= nodecount %d -> not complete for node %r" % (
                    index, nodecount, root)
            return False

        return cls.is_complete_r(
            root.l, index=2 * index, nodecount=nodecount,
            debug=debug) and cls.is_complete_r(
                root.r, index=2 * index + 1, nodecount=nodecount, debug=debug)

    @classmethod
    def lheight_(cls, root):
        level = 0
        p = root
        while p is not None:
            p = p.l
            level += 1
        pass
        return level

    @classmethod
    def serialize(cls, root):
        """
        Non-existing nodes left at zero, hmm operation UNION currently zero ?
        """
        height = root.maxdepth
        totNodes = Node.NumNodes(height)
        #log.info("serialize : height %d totNodes %d " % (height, totNodes))
        partBuf = np.zeros((totNodes, 4, 4), dtype=np.float32)
        cls.serialize_r(root, partBuf, 0)
        return partBuf

    @classmethod
    def fillPart(cls, part, node):
        assert part.shape == (4, 4)

        if node.param is not None:
            part[Q0] = node.param

        if node.operation is not None:
            part.view(np.uint32)[Q1, W] = node.operation

        if node.shape is not None:
            part.view(np.uint32)[Q2, W] = node.shape

    @classmethod
    def fromPart(cls, part):
        assert part.shape == (4, 4)
        node = Node()
        node.param = part[Q0]
        node.operation = part.view(np.uint32)[Q1, W]
        node.shape = part.view(np.int32)[Q2, W]
        return node

    @classmethod
    def serialize_r(cls, node, partBuf, i):
        """
        :param node:
        :param partBuf:
        :param i: index into partBuf
        """
        assert i < len(partBuf)
        cls.fillPart(partBuf[i], node)

        if node.l:
            cls.serialize_r(node.l, partBuf, 2 * i + 1)

        if node.r:
            cls.serialize_r(node.r, partBuf, 2 * i + 2)

    @classmethod
    def is_perfect_i(cls, root):
        """
        Definition of a perfect binary tree:

        * all leaves at same depth, same as maxdepth
        * all non-leaves have both left and right children

        Perfect binary tree with 1-based levelorder index::

             1

             2                  3

             4        5         6         7

             8   9   10   11   12  13    14  15    

        
        For node i, where i in range 1 to n

        * root, i=1
        * left child, 2*i (if <= n)
        * right child, 2*i + 1 (if <= n)
        * parent, i/2 (if i != 1)
        * contiguous leaves come last in levelorder

        A perfect binary tree levelorder serialized into an array
        is navigable without tree reconstruction direct from the  
        array using the above index manipulations.

        The downside is that normally will need to 
        pad a tree with EMPTY primitives and UNION operations 
        to make it perfect.

        """
        maxdepth = cls.depth_r(root)  # label nodes with depth, 0 at root

        leafdepth = None
        q = []
        q.append(root)
        while len(q) > 0:
            node = q.pop(0)  # fifo
            if node.is_leaf:
                if leafdepth is None:
                    leafdepth = node.depth
                    if leafdepth != maxdepth:
                        return False
                    pass
                pass
                if node.depth != leafdepth:
                    return False
                pass
            else:
                if node.l is None or node.r is None:
                    return False
                pass
            pass
            if node.l is not None: q.append(node.l)
            if node.r is not None: q.append(node.r)
        pass
        return True

    @classmethod
    def make_perfect_i(cls, root):
        """
        """
        while not cls.is_perfect_i(root):
            maxdepth = cls.depth_r(root)  # label nodes with depth, 0 at root
            q = []
            q.append(root)
            while len(q) > 0:
                node = q.pop(0)  # bottom of q (ie fifo)
                assert node.depth <= maxdepth

                if node.is_leaf and node.depth < maxdepth:
                    l = Node()
                    r = Node()
                    l.depth = node.depth + 1
                    r.depth = node.depth + 1
                    node.l = l
                    node.r = r
                pass
                if node.l is not None: q.append(node.l)
                if node.r is not None: q.append(node.r)
            pass
        pass

    @classmethod
    def is_almost_complete_i(cls, root):
        """
        levelorder traverse of complete binary tree should see all the 
        leaves together at the last level. 

        Hmm consider pruing 6,7 in the below so 3 becomes a leaf...
        then all leaves are together 3,4,5
        but 3 is at higher level ?

        ::

             1
 
             2            3

             4      5     6       7

        """

        if root is None: return True
        q = []
        q.append(root)

        leaves = False

        idx = 1
        while len(q) > 0:
            node = q.pop(0)  # bottom of q (ie fifo)

            if node.l is not None:
                if leaves: return False
                q.append(node.l)
            else:
                leaves = True
            pass

            if node.r is not None:
                if leaves: return False
                q.append(node.r)
            else:
                leaves = True
            pass
        pass
        return True

    @classmethod
    def levelorder_label_i(cls, root, idxbase=1):
        """
        Assign 1-based binary tree levelorder indices, eg for height 3 complete tree::

             1
 
             2            3

             4      5     6       7
 
             8   9  10 11 12  13  14   15

        """
        idx = idxbase
        for node in Node.levelorder_i(root):
            node.idx = idx
            idx += 1
        pass

    cdepth = property(lambda self: 32 - clz_(self.idx) - 1
                      )  # this assumes 1-based levelorder idx

    @classmethod
    def depth_r(cls, node, depth=0, side=0, height=None):
        """
        Marking up the tree with depth and side
        """
        if node is None:
            return

        if height is None:
            height = Node.lheight_(node)

        node.depth = depth
        node.side = side

        delta = 1 << (
            height - depth - 1
        )  # smaller side shifts as go away from root towards the leaves

        #print "depth_r depth %d side %d delta %d height %d height-depth-1 %s " % (depth, side, delta, height, height-depth-1)

        ldepth = depth
        rdepth = depth

        if node.l is not None:
            ldepth = cls.depth_r(node.l,
                                 depth + 1,
                                 side - delta,
                                 height=height)
        if node.r is not None:
            rdepth = cls.depth_r(node.r,
                                 depth + 1,
                                 side + delta,
                                 height=height)

        return max(ldepth, rdepth)

    @classmethod
    def progeny_i(cls, root):
        """

        1

        2          3

        4    5     6      7

        8 9  10 11 12 13 14 15

        """

        nodes = []
        q = []
        q.append(root)

        while len(q) > 0:
            node = q.pop(0)  # bottom of q (ie fifo)
            nodes.append(node)
            if not node.l is None: q.append(node.l)
            if not node.r is None: q.append(node.r)
        pass
        return nodes

    @classmethod
    def label_r(cls, node, idx, label):
        if node.idx == idx:
            log.info("label_r setting label %s idx %d node %r  " %
                     (label, idx, node))
            setattr(node, label, 1)

        if node.l is not None: cls.label_r(node.l, idx, label)
        if node.r is not None: cls.label_r(node.l, idx, label)

    @classmethod
    def leftmost_leaf(cls, root):
        n = root
        while n.l is not None:
            n = n.l
        return n

    @classmethod
    def rightmost_leaf(cls, root):
        n = root
        while n.r is not None:
            n = n.r
        return n

    @classmethod
    def leftmost(cls, root):
        """
        :return: leftmost internal or operation node 
        """
        n = root
        while n.l is not None:
            if n.l.is_leaf:
                break
            else:
                n = n.l
            pass
        return n

    @classmethod
    def rightmost(cls, root):
        """
        :return: rightmost internal or operation node 
        """
        n = root
        while n.r is not None:
            if n.r.is_leaf:
                break
            else:
                n = n.r
            pass
        return n

    @classmethod
    def postorder_r(cls, root, leaf=True):
        """ 
        :param root:
        :param nodes: list 
        :param leaf: bool control of inclusion of leaf nodes with internal nodes

        Recursive postorder traversal
        """
        def _postorder_r(n):
            if n.l is not None: _postorder_r(n.l)
            if n.r is not None: _postorder_r(n.r)
            if leaf or not n.is_leaf:
                nodes.append(n)
            pass

        pass
        nodes = []
        _postorder_r(root)
        return nodes

    @classmethod
    def inorder_r(cls, root, nodes=[], leaf=True, internal=True):
        """ 
        :param root:
        :param nodes: list 
        :param leaf: include leaf nodes
        :param internal: include non-leaf nodes, including root node

        Recursive inorder traversal
        """
        if root.l is not None:
            cls.inorder_r(root.l, nodes, leaf=leaf, internal=internal)

        if root.is_leaf:
            if leaf:
                nodes.append(root)
            else:
                pass
            pass
        else:
            if internal:
                nodes.append(root)
            else:
                pass
            pass
        pass
        if root.r is not None:
            cls.inorder_r(root.r, nodes, leaf=leaf, internal=internal)
        return nodes

    @classmethod
    def levelorder_i(cls, root):
        """
        Why is levelorder easier iteratively than recursively ?
        """
        nodes = []
        q = []
        q.append(root)
        while len(q) > 0:
            node = q.pop(0)  # bottom of q (ie fifo)
            nodes.append(node)
            if node.l is not None: q.append(node.l)
            if node.r is not None: q.append(node.r)
        pass
        return nodes

    @classmethod
    def postorder_threading_r(cls, root):
        nodes = cls.postorder_r(root, leaf=False)
        for i in range(len(nodes)):
            node = nodes[i]
            next_ = nodes[i + 1] if i < len(nodes) - 1 else None
            node.next_ = next_
            node.pidx = i  # postorder index
        pass

    @classmethod
    def parenting_r(cls, root, parent=None):
        if root.l is not None:
            cls.parenting_r(root.l, parent=root)
        pass
        if root.r is not None:
            cls.parenting_r(root.r, parent=root)
        pass
        root.parent = parent

    @classmethod
    def postOrderSequence(cls, root, dump=False):
        """
        Pack the postorder sequence levelorder indices (1-based)
        into a 64 bit integer. 

        ::

            In [179]: seq3 = np.array( map(lambda n:n.idx, Node.postorder_r(root3)), dtype=np.int32 )

            In [180]: seq3   # indices up to 0xf  (4 bits)   4 * 0xf  = 60 (so fits into ull 64 bit)
            Out[180]: array([ 8,  9,  4, 10, 11,  5,  2, 12, 13,  6, 14, 15,  7,  3,  1], dtype=int32)   

            In [181]: seq4 =  np.array( map(lambda n:n.idx, Node.postorder_r(root4)), dtype=np.int32 ) 

            In [182]: seq4    # indices up to 0x1f  (5 bits)  5 * 0x1f = 155  (need 3*64 bit nasty 3 -> instead 8*0x1f = 248, need 4*64 )  
            Out[182]: array([16, 17,  8, 18, 19,  9,  4, 20, 21, 10, 22, 23, 11,  5,  2, 24, 25, 12, 26, 27, 13,  6, 28, 29, 14, 30, 31, 15,  7,  3,  1], dtype=int32)
                     ## avoid the problem, limit trees to height 3, rather than 4 ??
                     ## but i like supporting height4 in order to really test algorithm in general case

            In [183]: len(seq3)
            Out[183]: 15

            In [184]: len(seq3)*4    # up to height 3 tree, there are less than 16 nodes so index fits in 4 bits, thus can fit the postorder into 64 bits
            Out[184]: 60

            In [185]: len(seq4)*8    # for height 4 tree, need to 
            Out[185]: 248
                         

        """
        postorder = Node.postorder_r(root, leaf=False)
        assert len(postorder) < 16

        post2levl = {}
        levl2post = {}

        if dump:
            print " %10s %10s " % ("postIdx", "levlIdx")
        pass
        for postIdx in range(0, len(postorder)):
            node = postorder[postIdx]
            levlIdx = node.idx
            assert levlIdx <= 0xf and postIdx <= 0xf
            post2levl[postIdx] = levlIdx
            levl2post[levlIdx] = postIdx
            if dump:
                print " %10d %10d " % (postIdx, levlIdx)
            pass
        pass

        def convert64(d):
            seq = np.uint64(0)
            for k, v in d.items():
                assert k <= 0xf and v <= 0xf
                seq |= ((np.uint64(v) & np.uint64(0xF)) << np.uint64(k * 4))
            pass
            return seq

        pass

        post2levlSeq = convert64(post2levl)
        levl2postSeq = convert64(levl2post)

        repd_ = lambda d: "".join(
            [" %x -> %x " % (k, d[k]) for k in sorted(d)])

        if dump:
            print " post2levl %16x  %s  " % (post2levlSeq, repd_(post2levl))
            print " levl2post %16x  %s  " % (levl2postSeq, repd_(levl2post))
        pass
        pass
        return post2levlSeq, levl2postSeq

    @classmethod
    def dumpSequence(cls, seq, iseq):
        n = 0
        idx = seq & np.uint64(0xF)
        while idx > 0:
            print n, idx
            n += 1
            idx = (seq & np.uint64(0xF << n * 4)) >> np.uint64(n * 4)
        pass

    @classmethod
    def postOrderIterative(cls, root):
        """
        # iterative postorder traversal using
        # two stacks : nodes processed 

        ::

              1

             [2]                 3

             [4]     [5]         6     7

             [8] [9] [10] [11]  12 13  14 15

        ::

            In [25]: Node.postOrderIterative(root3.l)
            Out[25]: 
            [Node(8),
             Node(9),
             Node(4,l=Node(8),r=Node(9)),
             Node(10),
             Node(11),
             Node(5,l=Node(10),r=Node(11)),
             Node(2,l=Node(4,l=Node(8),r=Node(9)),r=Node(5,l=Node(10),r=Node(11)))]

            In [26]: Node.postOrderIterative(root3.l.l)
            Out[26]: [Node(8), Node(9), Node(4,l=Node(8),r=Node(9))]

        """
        if root is None:
            return

        nodes = []
        s = []

        nodes.append(root)

        while len(nodes) > 0:

            node = nodes.pop()
            s.append(node)

            if node.l is not None:
                nodes.append(node.l)
            if node.r is not None:
                nodes.append(node.r)

        #while len(s) > 0:
        #    node = s.pop()
        #    print node.d,

        return list(reversed(s))
Beispiel #4
0
                          (6<<1) + (1<<1) = 14  

     '0b1110',       14
     '0b1111', #     15
      '0b111', #      7
       '0b11', #      3
        '0b1'] #      1


"""

import numpy as np
from opticks.bin.ffs import clz_

bin_ = lambda _: "{0:08b}".format(_)
len_ = lambda code: 32 - clz_(code)


class Node(object):
    """
   Used to check the no-tree postorder sequence 
   """
    def __init__(self, idx):
        self.idx = idx
        self.left = None
        self.right = None

    @classmethod
    def make_tree(cls, height):
        root = cls.make_tree_r(1, height, 0)
        return root