File indexing completed on 2026-04-09 07:48:52
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021 """
0022
0023 """
0024 import os, logging, numpy as np
0025 log = logging.getLogger(__name__)
0026
0027 from opticks.analytic.csg import CSG
0028
0029
0030 class TreeBuilder(object):
0031 @classmethod
0032 def balance(cls, tree):
0033 """
0034 Note that positivization is done inplace whereas
0035 the balanced tree is created separately
0036 """
0037 if not tree.is_positive_form():
0038 log.fatal("cannot balance tree that is not in positive form")
0039 assert 0
0040 pass
0041 ops = tree.operators_()
0042 hops = tree.operators_(minsubdepth=2)
0043
0044 if len(ops) == 1:
0045 op = ops[0]
0046 prims = tree.primitives()
0047 balanced = cls.commontree(op, prims, tree.name+"_prim_balanced" )
0048 elif len(hops) == 1:
0049 op = hops[0]
0050 bileafs = tree.subtrees_(subdepth=1)
0051 balanced = cls.bileaftree(op, bileafs, tree.name+"_bileaf_balanced" )
0052 else:
0053 log.warning("balancing trees of this structure not implemented, tree %r " % tree)
0054 tree.meta.update(err="TreeBuilder.balance fail")
0055 balanced = tree
0056 pass
0057 return balanced
0058
0059 @classmethod
0060 def bileaftree(cls, operator, bileafs, name):
0061 tb = TreeBuilder(bileafs, operator=operator, bileaf=True)
0062 root = tb.root
0063 root.name = name
0064 root.analyse()
0065 return root
0066
0067 @classmethod
0068 def commontree(cls, operator, primitives, name):
0069 """
0070 :param operator: CSG enum int
0071 :param primitives: list of CSG instances
0072 :param name:
0073 :return root: CSG instance for root node
0074 """
0075 tb = TreeBuilder(primitives, operator=operator)
0076 root = tb.root
0077 root.name = name
0078 root.analyse()
0079 return root
0080
0081 @classmethod
0082 def uniontree(cls, primitives, name):
0083 return cls.commontree(CSG.UNION, primitives, name )
0084
0085 @classmethod
0086 def intersectiontree(cls, primitives, name):
0087 return cls.commontree(CSG.INTERSECTION, primitives, name )
0088
0089
0090 def __init__(self, subs, operator=CSG.UNION, bileaf=False):
0091 """
0092 :param subs: list of CSG instance primitives or bileafs when bileaf is True
0093 """
0094 self.operator = operator
0095
0096 if not bileaf:
0097 nprim = len(list(map(lambda n:n.is_primitive, subs)))
0098 assert nprim == len(subs) and nprim > 0
0099 else:
0100 nbileaf = len(list(map(lambda n:n.is_bileaf, subs)))
0101 assert nbileaf == len(subs) and nbileaf > 0
0102 nprim = 2*nbileaf
0103 log.debug("TreeBuilder bileaf mode, nbileaf: %s nprim:%s " % (nbileaf, nprim))
0104 pass
0105 self.nprim = nprim
0106 self.subs = subs
0107
0108
0109
0110
0111
0112
0113 height = -1
0114 for h in range(10):
0115 tprim = 1 << h
0116 if tprim >= nprim:
0117 height = h
0118 break
0119 pass
0120 pass
0121 assert height > -1
0122
0123 log.debug("TreeBuilder nprim:%d required height:%d " % (nprim, height))
0124 self.height = height
0125
0126 if not bileaf:
0127 if height == 0:
0128 assert len(subs) == 1
0129 root = subs[0]
0130 else:
0131 root = self.build(height, placeholder=CSG.ZERO)
0132 self.populate(root, subs[::-1])
0133 self.prune(root)
0134 pass
0135 else:
0136 assert height > 0
0137 root = self.build(height-1, placeholder=CSG.ZERO)
0138 self.populate(root, subs[::-1])
0139 self.prune(root)
0140 pass
0141 self.root = root
0142
0143 def build(self, height, placeholder=CSG.ZERO):
0144 """
0145 Build complete binary tree with all operators the same
0146 and CSG.ZERO placeholders for elevation 0
0147 """
0148 def build_r(elevation, operator):
0149 if elevation > 1:
0150 node = CSG(operator, name="treebuilder_midop")
0151 node.left = build_r(elevation-1, operator)
0152 node.right = build_r(elevation-1, operator)
0153 else:
0154 node = CSG(operator, name="treebuilder_bileaf")
0155 node.left = CSG(placeholder, name="treebuilder_bileaf_left")
0156 node.right = CSG(placeholder, name="treebuilder_bilead_right")
0157 pass
0158 return node
0159 pass
0160 root = build_r(height, self.operator )
0161 return root
0162
0163 def populate(self, root, subs):
0164 """
0165 :param root: CSG operator instance for root node
0166 :param subs: reversed list of the CSG instance primitive baubles to be hung on the tree
0167
0168 During an inorder traverse of the complete binary tree,
0169 the placeholder CSG.ZERO leaves are replaced with primitives
0170 popped off the subs list.
0171 """
0172 inorder = root.inorder_()
0173 log.debug("populate filling tree of %d nodes with %d subs " % (len(inorder),len(subs)))
0174
0175 for node in inorder:
0176 if node.is_operator:
0177 try:
0178 if node.left.is_zero:
0179 node.left = subs.pop()
0180 if node.right.is_zero:
0181 node.right = subs.pop()
0182 pass
0183 except IndexError:
0184 pass
0185 pass
0186 pass
0187 pass
0188
0189 def prune(self, root):
0190 """
0191 Pulling leaves partnered with CSG.ZERO up to a higher elevation.
0192 """
0193 def prune_r(node):
0194 if node is None:return
0195 if node.is_operator:
0196 prune_r(node.left)
0197 prune_r(node.right)
0198
0199
0200 if node.left.is_lrzero:
0201 node.left = CSG(CSG.ZERO)
0202 elif node.left.is_rzero:
0203 ll = node.left.left
0204 node.left = ll
0205 pass
0206 if node.right.is_lrzero:
0207 node.right = CSG(CSG.ZERO)
0208 elif node.right.is_rzero:
0209 rl = node.right.left
0210 node.right = rl
0211 pass
0212 pass
0213 pass
0214 prune_r(root)
0215
0216
0217
0218
0219 def test_treebuilder():
0220 log.info("test_treebuilder")
0221 sprim = "sphere box cone zsphere cylinder trapezoid"
0222 primitives = map(CSG, sprim.split() + sprim.split() )
0223 nprim = len(primitives)
0224
0225 for n in range(0,nprim):
0226 tb = TreeBuilder(primitives[0:n+1])
0227 tb.root.analyse()
0228 print(tb.root.txt)
0229
0230 def test_uniontree():
0231 log.info("test_uniontree")
0232
0233 sprim = "sphere box cone zsphere cylinder trapezoid"
0234 primitives = map(CSG, sprim.split())
0235
0236 for n in range(0,len(primitives)):
0237 root = TreeBuilder.uniontree(primitives[0:n+1], name="test_uniontree")
0238 print("\n%s" % root.txt)
0239
0240
0241
0242
0243
0244 def test_balance():
0245 log.info("test_balance")
0246
0247 sprim = "sphere box cone zsphere cylinder trapezoid"
0248 primitives = map(CSG, sprim.split())
0249
0250 sp,bo,co,zs,cy,tr = primitives
0251
0252
0253 root = sp - bo - co - zs - cy - tr
0254
0255 root.analyse()
0256
0257
0258 print(root.txt)
0259
0260 prims = root.primitives()
0261 print("prims : %s " % repr(prims))
0262
0263 root.positivize()
0264
0265 balanced = TreeBuilder.balance(root)
0266 print(balanced.txt)
0267
0268
0269 def make_bileaf(name):
0270 outer = CSG("cylinder", name=name+"_outer")
0271 inner = CSG("cylinder", name=name+"_inner")
0272 tube = outer - inner
0273 return tube
0274
0275 def test_balance_bileaf():
0276 log.info("test_balance_bileaf")
0277
0278 a = make_bileaf("a")
0279 b = make_bileaf("b")
0280 c = make_bileaf("c")
0281 d = make_bileaf("d")
0282 e = make_bileaf("e")
0283 f = make_bileaf("f")
0284 g = make_bileaf("g")
0285
0286 roots = []
0287 roots.append(a)
0288 roots.append(a+b)
0289 roots.append(a+b+c)
0290 roots.append(a+b+c+d)
0291 roots.append(a+b+c+d+e)
0292 roots.append(a+b+c+d+e+f)
0293 roots.append(a+b+c+d+e+f+g)
0294
0295 for root in roots:
0296 root.analyse()
0297 root.positivize()
0298
0299 print(root.txt)
0300 balanced = TreeBuilder.balance(root)
0301
0302 print(balanced.txt)
0303 pass
0304
0305
0306
0307 if __name__ == '__main__':
0308 pass
0309 logging.basicConfig(level=logging.INFO)
0310
0311
0312
0313 test_balance()
0314
0315
0316