127 lines
4.9 KiB
Python
127 lines
4.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
SPACES = 4
|
|
class Node:
|
|
path2node = {}
|
|
|
|
def add_node(self, pathname, wt, cov):
|
|
path2node = self.path2node
|
|
path, name = pathname.strip().rsplit('/', 1)
|
|
node = Node(name, wt, cov)
|
|
path2node[pathname] = node
|
|
path2node[path].child.append(node) # Link the tree
|
|
|
|
def __init__(self, name="", wt=1, cov=0.0, child=None):
|
|
if child is None:
|
|
child = []
|
|
self.name, self.wt, self.cov, self.child = name, wt, cov, child
|
|
self.delta = None
|
|
self.sum_wt = wt
|
|
if name == "":
|
|
# designate the top of the tree
|
|
self.path2node[name] = self
|
|
|
|
|
|
def __repr__(self, indent=0):
|
|
name, wt, cov, delta, child = (self.name, self.wt, self.cov,
|
|
self.delta, self.child)
|
|
lhs = ' ' * (SPACES * indent) + "Node(%r," % name
|
|
txt = '%-40s wt=%2g, cov=%-8.5g, delta=%-10s, child=[' \
|
|
% (lhs, wt, cov, ('n/a' if delta is None else '%-10.7f' % delta))
|
|
if not child:
|
|
txt += (']),\n')
|
|
else:
|
|
txt += ('\n')
|
|
for c in child:
|
|
txt += c.__repr__(indent + 1)
|
|
txt += (' ' * (SPACES * indent) + "]),\n")
|
|
return txt
|
|
|
|
def covercalc(self):
|
|
'''
|
|
Depth first weighted average of coverage
|
|
'''
|
|
child = self.child
|
|
if not child:
|
|
return self.cov
|
|
sum_covwt, sum_wt = 0, 0
|
|
for node in child:
|
|
nwt = node.wt
|
|
ncov = node.covercalc()
|
|
sum_wt += nwt
|
|
sum_covwt += ncov * nwt
|
|
cov = sum_covwt / sum_wt
|
|
self.sum_wt = sum_wt
|
|
self.cov = cov
|
|
return cov
|
|
|
|
def deltacalc(self, power=1.0):
|
|
'''
|
|
Top down distribution of weighted residuals
|
|
'''
|
|
sum_wt = self.sum_wt
|
|
self.delta = delta = (1 - self.cov) * power
|
|
for node in self.child:
|
|
node.deltacalc(power * node.wt / sum_wt)
|
|
return delta
|
|
|
|
|
|
def isclose(a, b, rel_tol=1e-9, abs_tol=1e-9):
|
|
return abs(a-b) <= max( rel_tol * max(abs(a), abs(b)), abs_tol )
|
|
|
|
|
|
if __name__ == '__main__':
|
|
top = Node() # Add placeholder for top of tree
|
|
add_node = top.add_node
|
|
|
|
add_node('/cleaning', 1, 0)
|
|
add_node('/cleaning/house1', 40, 0)
|
|
add_node('/cleaning/house1/bedrooms', 1, 0.25)
|
|
add_node('/cleaning/house1/bathrooms', 1, 0)
|
|
add_node('/cleaning/house1/bathrooms/bathroom1', 1, 0.5)
|
|
add_node('/cleaning/house1/bathrooms/bathroom2', 1, 0)
|
|
add_node('/cleaning/house1/bathrooms/outside_lavatory', 1, 1)
|
|
add_node('/cleaning/house1/attic', 1, 0.75)
|
|
add_node('/cleaning/house1/kitchen', 1, 0.1)
|
|
add_node('/cleaning/house1/living_rooms', 1, 0)
|
|
add_node('/cleaning/house1/living_rooms/lounge', 1, 0)
|
|
add_node('/cleaning/house1/living_rooms/dining_room', 1, 0)
|
|
add_node('/cleaning/house1/living_rooms/conservatory', 1, 0)
|
|
add_node('/cleaning/house1/living_rooms/playroom', 1, 1)
|
|
add_node('/cleaning/house1/basement', 1, 0)
|
|
add_node('/cleaning/house1/garage', 1, 0)
|
|
add_node('/cleaning/house1/garden', 1, 0.8)
|
|
add_node('/cleaning/house2', 60, 0)
|
|
add_node('/cleaning/house2/upstairs', 1, 0)
|
|
add_node('/cleaning/house2/upstairs/bedrooms', 1, 0)
|
|
add_node('/cleaning/house2/upstairs/bedrooms/suite_1', 1, 0)
|
|
add_node('/cleaning/house2/upstairs/bedrooms/suite_2', 1, 0)
|
|
add_node('/cleaning/house2/upstairs/bedrooms/bedroom_3', 1, 0)
|
|
add_node('/cleaning/house2/upstairs/bedrooms/bedroom_4', 1, 0)
|
|
add_node('/cleaning/house2/upstairs/bathroom', 1, 0)
|
|
add_node('/cleaning/house2/upstairs/toilet', 1, 0)
|
|
add_node('/cleaning/house2/upstairs/attics', 1, 0.6)
|
|
add_node('/cleaning/house2/groundfloor', 1, 0)
|
|
add_node('/cleaning/house2/groundfloor/kitchen', 1, 0)
|
|
add_node('/cleaning/house2/groundfloor/living_rooms', 1, 0)
|
|
add_node('/cleaning/house2/groundfloor/living_rooms/lounge', 1, 0)
|
|
add_node('/cleaning/house2/groundfloor/living_rooms/dining_room', 1, 0)
|
|
add_node('/cleaning/house2/groundfloor/living_rooms/conservatory', 1, 0)
|
|
add_node('/cleaning/house2/groundfloor/living_rooms/playroom', 1, 0)
|
|
add_node('/cleaning/house2/groundfloor/wet_room_&_toilet', 1, 0)
|
|
add_node('/cleaning/house2/groundfloor/garage', 1, 0)
|
|
add_node('/cleaning/house2/groundfloor/garden', 1, 0.9)
|
|
add_node('/cleaning/house2/groundfloor/hot_tub_suite', 1, 1)
|
|
add_node('/cleaning/house2/basement', 1, 0)
|
|
add_node('/cleaning/house2/basement/cellars', 1, 1)
|
|
add_node('/cleaning/house2/basement/wine_cellar', 1, 1)
|
|
add_node('/cleaning/house2/basement/cinema', 1, 0.75)
|
|
|
|
top = top.child[0] # Remove artificial top
|
|
cover = top.covercalc()
|
|
delta = top.deltacalc()
|
|
print('TOP COVERAGE = %g\n' % cover)
|
|
print(top)
|
|
assert isclose((delta + cover), 1.0), "Top level delta + coverage should " \
|
|
"equal 1 instead of (%f + %f)" % (delta, cover)
|