Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-09 07:48:52

0001 #!/usr/bin/env python 
0002 """
0003 CSGFoundryAB.py
0004 =================
0005 
0006 """
0007 
0008 import numpy as np, os, textwrap, builtins
0009 from opticks.ana.fold import Fold, STR
0010 from opticks.CSG.CSGFoundry import CSGFoundry, CSGPrim, CSGNode
0011 from opticks.sysrap.stree import sn, snode, stree
0012 
0013 
0014 class CSGFoundryAB(object):
0015     def __init__(self, a, b, _ip=0):
0016         self.a = a 
0017         self.b = b 
0018         self.ip = _ip
0019 
0020         self.check_4x4("node")
0021         self.check_4x4("prim")
0022 
0023         qwns = "npa nbb pbb".split()
0024         for qwn in qwns:
0025             self.compare_qwn(qwn)
0026         pass
0027 
0028     def _set_ip(self, ip):
0029         self._ip = ip 
0030 
0031         a = self.a 
0032         b = self.b 
0033 
0034         ann = a.pr.numNode[ip]
0035         bnn = b.pr.numNode[ip]
0036         ano = a.pr.nodeOffset[ip]
0037         bno = b.pr.nodeOffset[ip]
0038         alv = a.pr.meshIdx[ip]
0039         blv = b.pr.meshIdx[ip]
0040         amn = a.meshname[alv]
0041         bmn = b.meshname[blv]
0042         anode = a.node[ano:ano+ann]  
0043         bnode = b.node[bno:bno+bnn]  
0044 
0045         atc = a.ni.typecode[ano:ano+ann]
0046         btc = b.ni.typecode[bno:bno+bnn]
0047 
0048         aco = a.ni.comptran[ano:ano+ann] >> 31
0049         bco = b.ni.comptran[bno:bno+bnn] >> 31
0050 
0051         self.alv = alv
0052         self.blv = blv
0053         self.amn = amn
0054         self.bmn = bmn
0055         self.anode = anode
0056         self.bnode = bnode
0057         self.atc = atc
0058         self.btc = btc
0059         self.aco = aco
0060         self.bco = bco
0061 
0062     def __repr__(self):
0063         return "AB %d alv %s:%s blv %s:%s " % ( self._ip, self.alv, self.amn, self.blv, self.bmn )
0064 
0065     def brief(self):
0066         a = self.a
0067         b = self.b
0068         lines = []
0069         lines.append("CSGFoundryAB.brief")
0070         lines.append(a.brief())
0071         lines.append(b.brief())
0072         return "\n".join(lines)
0073 
0074     def _get_ip(self):
0075         return self._ip
0076 
0077     ip = property(_get_ip, _set_ip) 
0078 
0079 
0080     def check_pr(self):
0081         a = self.a 
0082         b = self.b
0083         assert a.pr.dtype.names == b.pr.dtype.names
0084 
0085         lines = []
0086         lines.append("CSGFoundryAB.check_pr")
0087         expr_ = "len(np.where(a.pr.%(name)s != b.pr.%(name)s )[0])"
0088         for name in a.pr.dtype.names:
0089             expr = expr_ % locals()  
0090             lines.append("%80s : %s " % (expr, eval(expr)))
0091         pass
0092         return "\n".join(lines) 
0093 
0094     def check_prim_lv(self):
0095         a = self.a 
0096         b = self.b 
0097         a_lv = a.prim.view(np.int32)[:,1,1]  
0098         b_lv = b.prim.view(np.int32)[:,1,1]  
0099         assert np.all(a_lv==b_lv)    ## same G4VSolid => CSGPrim 
0100        
0101         assert np.all(np.unique(a_lv)==np.unique(b_lv)) 
0102         assert np.all(np.unique(a_lv,return_counts=True)[1]==np.unique(b_lv,return_counts=True)[1]) 
0103 
0104         a_lvu = np.unique(a_lv) 
0105         b_lvu = np.unique(b_lv) 
0106         assert np.all(np.arange(len(a_lvu))==a_lvu) 
0107         assert np.all(np.arange(len(b_lvu))==b_lvu) 
0108         assert np.all(a.meshname==b.meshname) 
0109 
0110     def check_node_tr(self):
0111         """
0112         """
0113         a = self.a 
0114         b = self.b 
0115 
0116         atr = a.node[:,3,3].view(np.int32) & 0x7fffffff
0117         btr = b.node[:,3,3].view(np.int32) & 0x7fffffff
0118 
0119         np.c_[np.unique(atr,return_counts=True)] 
0120         np.c_[np.unique(btr,return_counts=True)] 
0121 
0122     def check_node_index(self):
0123         """
0124         For A the node index increments until 15927, 
0125         thats probably the repeated unbalanced nodes 
0126         """
0127         a = self.a 
0128         b = self.b 
0129 
0130         aidx = a.node[:,1,3].view(np.int32)
0131         aidx_ = aidx[:15927]  
0132         assert np.all( aidx_ == np.arange(len(aidx_)) ) 
0133 
0134         bidx = b.node[:,1,3].view(np.int32)
0135         assert np.all( bidx == np.arange(len(bidx)) ) 
0136 
0137 
0138     def descLVDetail(self, lvid):
0139         a = self.a 
0140         b = self.b 
0141         lines = []
0142         lines.append("CSGFoundryAB.descLVDetail")
0143         lines.append(a.descLVDetail(lvid))
0144         lines.append(b.descLVDetail(lvid))
0145         return "\n".join(lines) 
0146  
0147     def check_prim(self):
0148         """
0149         np.all(a.prim.view(np.int32)[:,:2].reshape(-1,8)==b.prim.view(np.int32)[:,:2].reshape(-1,8)) 
0150         """
0151         a = self.a 
0152         b = self.b 
0153         ab = self
0154         setattr(self,"prim_numNode",np.where(a.prim[:,0,0].view(np.int32)!=b.prim[:,0,0].view(np.int32))[0])
0155 
0156         lines = []
0157         lines.append("CSGFoundryAB.check_prim")
0158         EXPR = filter(None,textwrap.dedent(r"""
0159 
0160         #(numNode,nodeOffset,tranOffset,planOffset)(sbtIndexOffset,meshIdx,repeatIdx,primIdx)
0161         a.prim.view(np.int32)[:,:2].reshape(-1,8)
0162         b.prim.view(np.int32)[:,:2].reshape(-1,8)
0163 
0164         #(numNode,nodeOffset,tranOffset,planOffset)
0165         a.prim[:,0].view(np.int32) 
0166         #(numNode,nodeOffset,tranOffset,planOffset)
0167         b.prim[:,0].view(np.int32) 
0168         len(np.where(a.prim[:,0]!=b.prim[:,0])[0])
0169         #numNode
0170         len(np.where(a.prim[:,0,0].view(np.int32)!=b.prim[:,0,0].view(np.int32))[0]) 
0171         len(ab.prim_numNode)
0172         np.c_[np.unique(a.primname[ab.prim_numNode],return_counts=True)]
0173         #nodeOffset
0174         len(np.where(a.prim[:,0,1].view(np.int32)!=b.prim[:,0,1].view(np.int32))[0]) 
0175         #tranOffset
0176         len(np.where(a.prim[:,0,2].view(np.int32)!=b.prim[:,0,2].view(np.int32))[0]) 
0177         #planOffset
0178         len(np.where(a.prim[:,0,3].view(np.int32)!=b.prim[:,0,3].view(np.int32))[0]) 
0179         #(sbtIndexOffset,meshIdx,repeatIdx,primIdx)
0180         a.prim[:,1].view(np.int32) 
0181         #(sbtIndexOffset,meshIdx,repeatIdx,primIdx)
0182         b.prim[:,1].view(np.int32) 
0183         len(np.where(a.prim[:,1]!=b.prim[:,1])[0])
0184 
0185         # A : (sbtIndexOffset,meshIdx,repeatIdx,primIdx) where prim_numNode discrepant 
0186         a.prim[ab.prim_numNode,1].view(np.int32)   
0187         # B : (sbtIndexOffset,meshIdx,repeatIdx,primIdx) where prim_numNode discrepant 
0188         b.prim[ab.prim_numNode,1].view(np.int32)   
0189 
0190         # A : (numNode,nodeOffset,tranOffset,planOffset) where prim_numNode discrepant
0191         a.prim[ab.prim_numNode,0].view(np.int32)  
0192 
0193         # B : (numNode,nodeOffset,tranOffset,planOffset) where prim_numNode discrepant
0194         b.prim[ab.prim_numNode,0].view(np.int32)  
0195 
0196         """).split("\n"))
0197         for expr in EXPR: 
0198             val = str(eval(expr)) if not expr.startswith("#") else "" 
0199             fmt = "%-80s \n%s\n" if len(val.split("\n")) > 1 else "%-80s : %s"
0200             lines.append(fmt % (expr, val))
0201         pass
0202         return "\n".join(lines) 
0203 
0204 
0205     def check_inst(self):
0206         a = self.a
0207         b = self.b
0208         ab = self
0209         setattr(self, "inst", np.max(np.abs(a.inst[:,:,:3]-b.inst[:,:,:3]).reshape(-1,12),axis=1))  
0210 
0211         lines = []
0212         lines.append("CSGFoundryAB.check_inst")
0213         EXPR = filter(None,textwrap.dedent(r"""
0214         np.all(a.inst[:,0,3].view(np.int32)==b.inst[:,0,3].view(np.int32)) 
0215         np.all(a.inst[:,1,3].view(np.int32)==b.inst[:,1,3].view(np.int32)) 
0216         np.all(a.inst[:,2,3].view(np.int32)==b.inst[:,2,3].view(np.int32)) 
0217         np.all(a.inst[:,3,3].view(np.int32)==b.inst[:,3,3].view(np.int32)) 
0218         np.all(a.inst[:,:,3].view(np.int32)==b.inst[:,:,3].view(np.int32)) 
0219         # deviation counts in the 12 floats of the inst transform
0220         len(np.where(ab.inst>1e-4)[0])
0221         len(np.where(ab.inst>1e-3)[0]) 
0222         len(np.where(ab.inst>1e-2)[0]) 
0223         """).split("\n"))
0224 
0225         for expr in EXPR: 
0226             val = str(eval(expr)) if not expr.startswith("#") else "" 
0227             fmt = "%-80s \n%s\n" if len(val.split("\n")) > 1 else "%-80s : %s"
0228             lines.append(fmt % (expr, val))
0229         pass
0230         return "\n".join(lines) 
0231 
0232     def check_solid(self):
0233         """
0234         [:,1] houses (numPrim, primOffset, type, padding)
0235         """
0236         a = self.a 
0237         b = self.b 
0238         lines = []
0239         lines.append("CSGFoundryAB.check_solid")
0240         EXPR = filter(None,textwrap.dedent(r"""
0241         np.all(a.solid[:,1]==b.solid[:,1]) 
0242         np.c_[a.solid[:,1,:2],b.solid[:,1,:2]]#(numPrim,primOffset) 
0243         """).split("\n"))
0244         for expr in EXPR: 
0245             val = str(eval(expr)) if not expr.startswith("#") else "" 
0246             fmt = "%-80s \n%s\n" if len(val.split("\n")) > 1 else "%-80s : %s"
0247             lines.append(fmt % (expr, val))
0248         pass
0249         return "\n".join(lines) 
0250 
0251     def check_names(self):
0252         lines = []
0253         lines.append("CSGFoundryAB.check_names")
0254         EXPR = filter(None,textwrap.dedent(r"""
0255         np.all(a.mmlabel==b.mmlabel) 
0256         np.all(a.primname==b.primname) 
0257         np.all(a.meshname==b.meshname) 
0258         """).split("\n"))
0259         for expr in EXPR: 
0260             val = str(eval(expr)) if not expr.startswith("#") else "" 
0261             fmt = "%-80s \n%s\n" if len(val.split("\n")) > 1 else "%-80s : %s"
0262             lines.append(fmt % (expr, val))
0263         pass
0264         return "\n".join(lines) 
0265 
0266 
0267 
0268     def check_4x4(self, name):
0269         a = self.a 
0270         b = self.b        
0271         ab = self
0272         am = getattr(a, name)
0273         bm = getattr(b, name)
0274 
0275         lines = []
0276         lines.append("CSGFoundryAB.check_4x4 %s " % name )
0277         lines.append(" a.%s.shape : %s " % (name, str(am.shape)) )
0278         lines.append(" b.%s.shape : %s " % (name, str(bm.shape)) )
0279         if am.shape == bm.shape:
0280             abm = np.max(np.abs(am-bm).reshape(-1,16), axis=1 )
0281             setattr(self,name, abm )
0282             expr = "len(np.where(ab.%(name)s>0.01)[0])" % locals()
0283             lines.append(" %s : %s " % (expr, str(eval(expr))))
0284         else:
0285             setattr(self,name,None) 
0286         pass
0287         return STR("\n".join(lines))
0288 
0289     def compare_qwn(self, qwn="npa"):
0290         a = self.a 
0291         b = self.b        
0292         ab = self
0293         aq = getattr(a, qwn)
0294         bq = getattr(b, qwn)
0295         setattr(ab, qwn, np.max(np.abs(aq-bq), axis=1 ))
0296    
0297 
0298     def check_tran(self):
0299         lines = []
0300         lines.append(self.check_4x4("tran"))
0301         lines.append(self.check_4x4("itra"))
0302         return "\n".join(lines)
0303 
0304 
0305 
0306 
0307 def checktran(a, b, ip, order="A" ):
0308     """
0309     ::
0310  
0311         at,bt,atran,btran,dtran = checktran(a,b,2000,"S")
0312 
0313         # order A:asis, S:sum_sort, U:unique
0314         #
0315 
0316     """
0317     ann = a.pr.numNode[ip]
0318     bnn = b.pr.numNode[ip]
0319 
0320     ano = a.pr.nodeOffset[ip]
0321     bno = b.pr.nodeOffset[ip]
0322 
0323     atr = ( a.ni.comptran[ano:ano+ann] & 0x7fffffff ) - 1  
0324     btr = ( b.ni.comptran[bno:bno+bnn] & 0x7fffffff ) - 1 
0325 
0326     at = atr[atr>-1]   # not -1 as thats done above 
0327     bt = btr[btr>-1]
0328 
0329     if len(at) == len(bt):
0330         if order == "U":
0331             atran = np.unique( a.tran[at], axis=0 )  
0332             btran = np.unique( b.tran[bt], axis=0 )  
0333         elif order == "S":
0334             # sort by the sum of the 16 elements 
0335             a_tran = a.tran[at]
0336             b_tran = b.tran[bt]
0337 
0338             ss_atran = np.argsort(np.sum(a_tran, axis=(1,2)))
0339             ss_btran = np.argsort(np.sum(b_tran, axis=(1,2)))
0340 
0341             atran = a_tran[ss_atran]  
0342             btran = b_tran[ss_btran]  
0343         elif order == "A":
0344             atran = a.tran[at]   
0345             btran = b.tran[bt] 
0346         pass
0347         dtran = np.sum( np.abs( atran - btran), axis=(1,2) ) 
0348     else:
0349         dtran = None
0350     pass
0351     return at,bt,atran,btran,dtran
0352  
0353 
0354 
0355 def checkprim(a, b, ip, dump=False, order="A"):
0356     """
0357     :param a: cf
0358     :param b: cf
0359     :param ip: prim index
0360     :param dump:
0361     :param order: control tran order with one of "ASIS" "SS" "U"
0362 
0363     * A : default 
0364     * S : sorting by sum of 16 elements
0365     * U : apply np.unique 
0366  
0367     """
0368     fmt = "ip:%(ip)4d " 
0369     alv = a.pr.meshIdx[ip]
0370     blv = b.pr.meshIdx[ip]
0371     slv = "/" if alv == blv else "*"
0372     fmt += "lv:%(alv)3d%(slv)s%(blv)3d "
0373 
0374     ann = a.pr.numNode[ip]
0375     bnn = b.pr.numNode[ip]
0376     snn = "/" if ann == bnn  else "*"
0377     fmt += "nn:%(ann)3d%(snn)s%(bnn)3d "
0378 
0379     ano = a.pr.nodeOffset[ip]
0380     bno = b.pr.nodeOffset[ip]
0381     sno = "/" if ano == bno  else "%"   # not "*" as this is bound to get stuck after first deviation
0382     fmt += "no:%(ano)5d%(sno)s%(bno)5d " 
0383 
0384     anode = a.node[ano:ano+ann]  
0385     bnode = b.node[bno:bno+bnn]  
0386 
0387     amn = a.meshname[alv]
0388     bmn = b.meshname[blv]
0389     smn = "/" if amn == bmn  else "*"
0390 
0391     atc = a.ni.typecode[ano:ano+ann]
0392     btc = b.ni.typecode[bno:bno+bnn]
0393 
0394     aco = a.ni.comptran[ano:ano+ann] >> 31
0395     bco = b.ni.comptran[bno:bno+bnn] >> 31
0396 
0397     at,bt,atran,btran,dtran = checktran(a,b,ip,order=order)
0398 
0399     if not dtran is None:
0400         wtran = np.where( dtran > 1e-2 )[0]   # large epsilon to avoid any float/double diffs
0401         ltran = len(wtran)
0402     else:
0403         ltran = -1 
0404     pass  
0405     stran = ":" if ltran == 0 else "*"
0406     fmt += " %(order)s tr%(stran)s%(ltran)2d "
0407 
0408     fmt += "mn:%(amn)40s%(smn)s%(bmn)40s "
0409 
0410 
0411     if ip == 0: print(fmt)
0412     line = fmt % locals()
0413     if "*" in line or dump:
0414         print(line)
0415     pass
0416 
0417     if snn == "/":
0418         expr = "np.c_[atran, btran], np.c_[atc, aco, btc, bco], dtran"
0419         return expr, np.c_[atran, btran], np.c_[atc, aco, btc, bco], dtran
0420     else:
0421         return None
0422     pass
0423 
0424 
0425 def checkprims(a, b, ip0=-1, ip1=-1, order="A"):
0426     """
0427     ::
0428 
0429         In [1]: checkprims(a,b)
0430          ip:2375  lv: 93/ 93 nn: 15*127 no:15209/15209 mn:        solidSJReceiverFastern/        solidSJReceiverFastern 
0431          ip:2376  lv: 93/ 93 nn: 15*127 no:15224%15336 mn:        solidSJReceiverFastern/        solidSJReceiverFastern 
0432          ip:2377  lv: 93/ 93 nn: 15*127 no:15239%15463 mn:        solidSJReceiverFastern/        solidSJReceiverFastern 
0433          ip:2378  lv: 93/ 93 nn: 15*127 no:15254%15590 mn:        solidSJReceiverFastern/        solidSJReceiverFastern 
0434          ip:2379  lv: 93/ 93 nn: 15*127 no:15269%15717 mn:        solidSJReceiverFastern/        solidSJReceiverFastern 
0435          ip:2380  lv: 93/ 93 nn: 15*127 no:15284%15844 mn:        solidSJReceiverFastern/        solidSJReceiverFastern 
0436          ip:2381  lv: 93/ 93 nn: 15*127 no:15299%15971 mn:        solidSJReceiverFastern/        solidSJReceiverFastern 
0437          ip:2382  lv: 93/ 93 nn: 15*127 no:15314%16098 mn:        solidSJReceiverFastern/        solidSJReceiverFastern 
0438          ip:3126  lv: 99/ 99 nn: 31*1023 no:23372%24268 mn:                          uni1/                          uni1 
0439 
0440     """
0441     if ip0 < 0 and len(a.pr.numNode) == len(b.pr.numNode):
0442         ip0 = 0 
0443         ip1 = len(a.pr.numNode)
0444     pass
0445     for i in range(ip0, ip1):
0446         checkprim(a,b, i, order=order)
0447     pass
0448 
0449 
0450 def eprint(_lines, _globals, _locals ):
0451     """
0452     Double hash "##" on the line suppresses printing the repr 
0453     of the evaluation. 
0454 
0455     Lines are first search for the position of the first "=" character.
0456     If present the position is used to extract the key and expression
0457     to be evaluated in the scope provided by the arguments. 
0458     The key with value is planted into the calling scope using builtins. 
0459 
0460 
0461     HMM : PARSE NEEDS TO DETECT PARAMETER EQUAL 
0462 
0463     ACTUALLY MAYBE EASIER TO PREFIX WITH "_ =" IF NO "KEY =" prefix is found 
0464 
0465     np.c_[np.unique(a.npa[a.pno[a.pnn>1],0].view(np.int32),return_counts=True)]
0466 
0467 
0468     TODO : support semicolon on the lines 
0469     """
0470     lines = list(filter(None, textwrap.dedent(_lines).split("\n") ))
0471 
0472     print("-" * 100)
0473     print("\n".join(lines))
0474     print("-" * 100)
0475 
0476     for line in lines:
0477         eq = line.find("=") 
0478         eeq = line.find("==") 
0479         no_key = eq == -1 or ( eq > -1 and eq == eeq ) # no "=" or first "=" is from "==" 
0480         if no_key: 
0481             exp = line
0482             val = eval(exp, _globals, _locals )
0483             print(exp)
0484             print(repr(val))
0485         elif eq > -1:
0486             key, exp = line[:eq].strip(), line[1+eq:]  # before and after first "="
0487             val = eval(exp, _globals, _locals )
0488             #print("set [%s] " % key )
0489             setattr(builtins, key, val)
0490             print(line)
0491             if line.find("##") == -1:
0492                 print(repr(val))
0493             pass
0494         else:
0495             print(line)
0496         pass
0497     pass       
0498     print("-" * 100)
0499 
0500 
0501 
0502 if __name__ == '__main__':
0503 
0504     np.set_printoptions(edgeitems=10) 
0505 
0506     CSGPrim.Type()
0507 
0508     A = Fold.Load("$A_CFBASE/CSGFoundry", symbol="A")
0509     B = Fold.Load("$B_CFBASE/CSGFoundry", symbol="B")
0510 
0511     print(repr(A))
0512     print(repr(B))
0513 
0514     a = CSGFoundry.Load("$A_CFBASE", symbol="a")
0515     b = CSGFoundry.Load("$B_CFBASE", symbol="b")
0516     print(a.brief())
0517     print(b.brief())
0518 
0519     ab = CSGFoundryAB(a,b)
0520 
0521     print(ab.check_names())
0522     print(ab.check_pr())
0523     print(ab.check_prim())
0524     print(ab.check_solid())
0525     print(ab.check_tran())
0526     print(ab.check_inst())
0527 
0528     print(ab.brief())
0529 
0530     a_meshname = np.char.partition(a.meshname.astype("U"),"0x")[:,0]  
0531     b_meshname = b.meshname.astype("U")
0532     assert np.all( a_meshname == b_meshname ) 
0533 
0534     a_primname = np.char.partition(a.primname.astype("U"),"0x")[:,0]  
0535     b_primname = b.primname.astype("U")
0536  
0537     an = A.SSim.stree._csg.sn
0538     bn = B.SSim.stree._csg.sn
0539 
0540 
0541     a_tc = a.node[:,3,2].view(np.int32)
0542     b_tc = b.node[:,3,2].view(np.int32)
0543     w_tc = np.where( a_tc != b_tc )[0]
0544 
0545 
0546     eprint(r"""
0547 
0548     w_solid = np.where( a.solid != b.solid )[0]  ##
0549     w_solid.shape
0550 
0551     np.all( a.nbd == b.nbd )  # node boundary : ONLY REMAINING DEVIANT 
0552 
0553     np.all( a.nix == b.nix )  # nodeIdx local to the compound solid 
0554     w_nix = np.where(a.nix != b.nix)[0] ## 
0555     w_nix.shape
0556 
0557     w_npa3 = np.where( np.abs(a.npa - b.npa) > 1e-3 )[0] ##  node param deviations
0558     w_npa3.shape
0559 
0560     w_nbb3 = np.where( np.abs(a.nbb - b.nbb) > 3e-2 )[0]  ## node bbox deviations
0561     w_nbb3.shape
0562     w_nbb2 = np.where( np.abs(a.nbb - b.nbb) > 1e-2 )[0]  ## node bbox deviations
0563     w_nbb2.shape
0564 
0565     w_nbb = np.where( a.nbb != b.nbb )  ##
0566     np.abs( a.nbb[w_nbb] - b.nbb[w_nbb] ).max()   # max deviation in bbox 
0567     
0568 
0569     np.all( a.ntc == b.ntc )  # node typecode
0570     np.all( a.ncm == b.ncm )  # node complement 
0571     np.all( a.ntr == b.ntr )  # node transform idx + 1 
0572 
0573     np.all( a.pnn == b.pnn )  # prim numNode
0574     np.all( a.pno == b.pno )  # prim nodeOffset
0575     np.all( a.pto == b.pto )  # prim tranOffset
0576     np.all( a.ppo == b.ppo )  # prim planOffset
0577     
0578     np.all( a.psb == b.psb )  # prim sbtIndexOffset
0579     np.all( a.plv == b.plv )  # prim lvid/meshIdx
0580     np.all( a.prx == b.prx )  # prim ridx/repeatIdx
0581     np.all( a.pix == b.pix )  # primIdx 
0582 
0583 
0584     w_pbb3 = np.where( np.abs(a.pbb-b.pbb) > 1e-3 )[0]  ## prim bbox deviations
0585     w_pbb3.shape
0586 
0587     w_pbb2 = np.where( np.abs(a.pbb-b.pbb) > 1e-2 )[0]  ## prim bbox deviations
0588     w_pbb2.shape
0589 
0590 
0591     np.all( a.snp == b.snp )  # solid numPrim 
0592     np.all( a.spo == b.spo )  # solid primOffset
0593 
0594     np.all( a.sce == b.sce )  # solid center_extent 
0595 
0596     w_sce = np.where( np.abs( a.sce - b.sce ) > 1e-3 )[0]    # solid center_extent
0597     w_sce.shape 
0598 
0599     w_npa = np.where( a.npa != b.npa )[0]   ## int32 3,7,15 in first param slot 
0600     w_npa.shape   # these are subNum on compound root nodes : and new workflow omits it
0601 
0602     np.all( a.npa == b.npa )  # after setting crn_subnum in b the node param match exactly  
0603 
0604 
0605     tab = np.c_[np.unique(a.npa[a.pno[a.pnn>1],0].view(np.int32),return_counts=True)] ## subNum picked from node param 0 of compound root nodes
0606     tab 
0607 
0608     tab2 = np.c_[np.unique( a.crn_subnum, return_counts=True )]  ## encapsulate subnum using "crn" compound root node  
0609     tab2
0610 
0611     _ = np.c_[np.unique(a.pnn[a.pnn>1], return_counts=True )] # looks like subnum same as the prim num nodes
0612 
0613     np.all( a.crn_subnum == b.crn_subnum) 
0614     np.all( a.crn_suboff == b.crn_suboff) 
0615 
0616 
0617     an = A.SSim.extra.GGeo.bnd_names      ## A side still GGeo in charge
0618     bn = B.SSim.stree.standard.bnd_names  ## B side using stree 
0619     w_bnd_names = np.where( an != bn )[0] ##
0620     w_bnd_names
0621 
0622     np.c_[an[w_bnd_names],bn[w_bnd_names]]   # discrepant bnd names
0623 
0624     """, globals(), locals() )
0625 pass        
0626