Example #1
0
 def __init__(self, net):
     # net is a dictionary of all the nodes
     self.net = net
     self.levels = self.findNumDeps()
     typeFuncs = {"marginal": self.execMarginal, "joint": self.execJoint, "conditional": self.execConditional}
     self.predictive = PredictiveReasoning(self.net, self.levels, typeFuncs)
     self.diagnostic = DiagnosticReasoning(self.net, self.levels, typeFuncs)
     self.intercausalAndCombined = IntercausalAndCombinedReasoning(self.net, self.levels, typeFuncs)
Example #2
0
class BayesNetCalculator(object):
    def __init__(self, net):
        # net is a dictionary of all the nodes
        self.net = net
        self.levels = self.findNumDeps()
        typeFuncs = {"marginal": self.execMarginal, "joint": self.execJoint, "conditional": self.execConditional}
        self.predictive = PredictiveReasoning(self.net, self.levels, typeFuncs)
        self.diagnostic = DiagnosticReasoning(self.net, self.levels, typeFuncs)
        self.intercausalAndCombined = IntercausalAndCombinedReasoning(self.net, self.levels, typeFuncs)

    # Executes a given command. Commands are expected to be in tuple form.
    # Returns list of tuples where first element is the result and the
    # second element is the command that produced that result.
    def execCommand(self, command):
        if command[0][1:] == "g":
            toReturn = self.loopThroughCapitals(command[1], self.execConditional)
        elif command[0][1:] == "j":
            toReturn = self.loopThroughCapitals(command[1], self.execJoint)
        elif command[0][1:] == "m":
            toReturn = self.execMarginal(command[1])
        elif command[0][1:] == "p":
            toReturn = self.changePrior(command[1])
        else:
            raise Exception("Unknown flag passed in.")
        if not isinstance(toReturn, list):
            toReturn = [toReturn]
        return toReturn

    def loopThroughCapitals(self, command, func, prevArgs=(None, None)):
        toReturn = []
        # Set args and see if there any capital letters
        startPoint = 0
        capitalFound = False
        if prevArgs[0] is not None:
            args = prevArgs[0]
            startPoint = prevArgs[1]
        else:
            args = self.parseArgs(command)
        while startPoint < len(args) and not capitalFound:
            if args[startPoint] == "|":
                startPoint += 1
            if args[startPoint].upper() == args[startPoint]:
                capitalFound = True
                argCopy = list(args)
                argCopy[startPoint] = argCopy[startPoint].lower()
                toReturn += self.loopThroughCapitals("".join(argCopy), func, (argCopy, startPoint + 1))
                argCopy[startPoint] = "~" + argCopy[startPoint]
                toReturn += self.loopThroughCapitals("".join(argCopy), func, (argCopy, startPoint + 1))
            startPoint += 1

        # Calculate the actual probability
        if not capitalFound:
            toReturn.append((func(args), "P(" + command + ")"))
        return toReturn

    def execConditional(self, args):
        # Seperate the subject from the conditions
        index = 0
        while args[index] != "|" and index < len(args):
            index += 1
        if index == len(args):
            raise Exception("Invalid input, no conditions")
        subject = args[:index]
        conditions = args[index + 1 :]

        # Check to see if the subject is in the condition. If it is remove it
        # from the subject list since we know it must be true. If subject is
        # empty then it must mean all subjects were true.
        for s in subject:
            for c in conditions:
                if s == c:
                    subject.remove(s)
        if len(subject) == 0:
            return 1
        # Check to see if any of the subjects conflict with the conditions
        for s in subject:
            prefix = "" if s[:1] == "~" else "~"
            for c in conditions:
                if prefix == "~" and c[:1] == "~" and c[1:] == s:
                    return 0
                elif prefix == "" and c == s[1:]:
                    return 0

        # Check to see if there is no dependence between subject and condition
        isIndependent = True
        for s in self.getNodes(subject):
            if not isinstance(self.getFromNet(s.upper()), PriorNode):
                isIndependent = False
        for c in self.getNodes(conditions):
            if not isinstance(self.getFromNet(c.upper()), PriorNode):
                isIndependent = False
        if isIndependent:
            if len(subject) == 1:
                return self.execMarginal(subject[0])[0][0]
            else:
                return self.execJoint(subject)

        # Check to see what kind of reasoning
        # diagnostic if condition levels > subject level
        diagnostic = True
        # predictive if condition levels < subject level
        predictive = True
        processedS = self.getNodes(subject)
        processedC = self.getNodes(conditions)
        for s in processedS:
            for c in processedC:
                if self.levels[c] <= self.levels[s]:
                    diagnostic = False
                if self.levels[c] >= self.levels[s]:
                    predictive = False
        if diagnostic:
            return self.diagnostic.compute(subject, conditions)
        elif predictive:
            return self.predictive.compute(subject, conditions)
        else:
            return self.intercausalAndCombined.compute(subject, conditions)

    def execJoint(self, args):
        # Check if we should use marginal instead
        if len(args) == 1:
            return self.execMarginal(args[0])[0][0]
        argNames = self.getNodes(args)
        # cluster the args based on the level which the occur
        clusteredArgs = []
        seenLevels = []
        for i, aN1 in enumerate(argNames):
            currLevel = self.levels[aN1]
            if currLevel not in seenLevels:
                nodesInLevel = [args[i]]
                for j, aN2 in enumerate(argNames):
                    if i != j and self.levels[aN2] == currLevel:
                        nodesInLevel.append(args[j])
                seenLevels.append(currLevel)
                clusteredArgs.append((currLevel, nodesInLevel))
        # Check if there is only one level present, check if independent or Make
        # independent
        if len(clusteredArgs) == 1:
            return self.jointOneLevel(args, clusteredArgs)
        else:
            # Sort clusteredArgs in descending level order
            clusteredArgs.sort()
            clusteredArgs = clusteredArgs[::-1]
            # Make the joint probability into a series of conditionals
            result = 1
            for i in range(len(clusteredArgs)):
                if i == len(clusteredArgs) - 1:
                    result *= self.execJoint(clusteredArgs[i][1])
                else:
                    former = clusteredArgs[i][1]
                    latter = []
                    for j in range(i + 1, len(clusteredArgs)):
                        latter += clusteredArgs[j][1]
                    result *= self.execConditional(former + ["|"] + latter)
            return result

    # Helper function for execJoint that deals with the case where there is
    # only one level (how many dependents) of events passed in
    def jointOneLevel(self, args, clusteredArgs):
        result = 1
        # If all nodes are prior they will be independent
        if clusteredArgs[0][0] == 0:
            for a in clusteredArgs[0][1]:
                result *= self.execMarginal(a)[0][0]
            return result
        # Can still be made independent if they nodes share a common
        # dependency
        ns = self.getNodes(clusteredArgs[0][1])
        possibleCommon = self.getFromNet(ns[0]).getDependencies()
        for pC in possibleCommon[::-1]:
            foundTally = len(ns) - 1
            for i in range(1, len(ns)):
                commonFound = False
                for otherDep in self.getFromNet(ns[i]).getDependencies():
                    if otherDep == pC:
                        commonFound = True
                if commonFound:
                    foundTally -= 1
            if foundTally != 0:
                possibleCommon.remove(pC)
        if len(possibleCommon) == 0:
            raise NotImplementedError("Logic to compute " + "".join(args) + " does not exist.")
        # Condition on the common dependent and multiply together
        else:
            commonDep = possibleCommon[0]
            addToResult = 1
            for arg in args:
                result *= self.execConditional([arg] + ["|"] + [commonDep.lower()])
                addToResult *= self.execConditional([arg] + ["|"] + ["~" + commonDep.lower()])
            result *= self.execMarginal(commonDep.lower())[0][0]
            addToResult *= self.execMarginal("~" + commonDep.lower())[0][0]
            return result + addToResult

    def execMarginal(self, command):
        if command.upper() == command:
            # Case that all options are wanted (capital letter entered)
            toReturn = []
            toReturn += self.execMarginal(command.lower())
            toReturn += self.execMarginal("~" + command.lower())
            return toReturn
        else:
            result = 0
            resultString = "P(" + command + ")"
            negated = command[:1] == "~"
            if negated:
                command = command[1:]
            try:
                node = self.getFromNet(command.upper())
            except KeyError:
                raise Exception("Invalid command given " + command)
            if isinstance(node, PriorNode):
                result = node.getProbability()
            else:
                deps = node.getDependencies()
                # Iterate on possibilites of dependencies
                for i in range(1 << len(deps)):
                    resultComponent = 1
                    # Bit logic magic to generate all combos of queries
                    query = tuple([1 == ((i >> j) & 1) for j in range(len(deps))])
                    resultComponent *= node.getProbability(query)
                    for k in range(len(deps)):
                        nestedCommand = "~" if not query[k] else ""
                        nestedCommand += deps[k].lower()
                        resultComponent *= self.execMarginal(nestedCommand)[0][0]
                    result += resultComponent
            if negated:
                result = 1 - result
            return [(result, resultString)]

    def changePrior(self, command):
        toChange = command[:1]
        newVal = float(command[2:])
        try:
            self.getFromNet(toChange).setProbability(newVal)
        except KeyError:
            raise Exception("Invalid command given " + command)
        return (newVal, "P(" + toChange + ")")

    # HELPER FUNCTIONS
    def getFromNet(self, key):
        result = None
        try:
            result = self.net[key]
        except KeyError as err:
            traceback.print_stack()
            print "  Node", err, "not found in net.\n"
        return result

    # Performs depth first search to map how far down in the net each node is.
    # Note that if there are multiple paths the highest will be selected.
    def findNumDeps(self):
        deps = {}
        # Nested helper function
        def depHelper(curr, level):
            if curr.getName() in deps.keys():
                if level < deps[curr.getName()]:
                    deps[curr.getName()] = level
            else:
                deps[curr.getName()] = level
            for child in curr.getChildren():
                depHelper(self.getFromNet(child), level + 1)

        # Main function logic
        for value in self.net.values():
            if isinstance(value, PriorNode):
                depHelper(value, 0)
        return deps

    # Return an array of events for a given string
    def parseArgs(self, command):
        args = []
        i = 0
        for char in command:
            if i == len(args):
                args.append(char)
            else:
                args[i] = args[i] + char
            if char != "~":
                i += 1
        return args

    # Strips negates and capitalizes. Makes copy so don't have to worry about
    # a change in the data.
    def getNodes(self, l):
        l = list(l)
        for i, n in enumerate(l):
            if n[:1] == "~":
                l[i] = n[1:]
            l[i] = l[i].upper()
        return l