diff --git a/.project b/.project new file mode 100644 index 0000000..5ca30de --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + DedekindNumber + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/src/solamanDedekind/.gitignore b/src/solamanDedekind/.gitignore new file mode 100644 index 0000000..14b9f47 --- /dev/null +++ b/src/solamanDedekind/.gitignore @@ -0,0 +1,28 @@ +# Compiled source # +################### +*.com +*.class +*.dll +*.exe +*.o +*.so +*.pyc + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Output Files # +################ +DedekindLattice/GeneratedDedekindLattices/* +!DedekindLattice/GeneratedDedekindLattices/preserve.txt + +# Experimental Files # +###################### +**/.noworkflow/** \ No newline at end of file diff --git a/src/solamanDedekind/Dedekind/Algorithms/BottomUp/BottomUp.py b/src/solamanDedekind/Dedekind/Algorithms/BottomUp/BottomUp.py new file mode 100644 index 0000000..0e0fc61 --- /dev/null +++ b/src/solamanDedekind/Dedekind/Algorithms/BottomUp/BottomUp.py @@ -0,0 +1,65 @@ +''' +Created on May 4, 2015 + +@author: Solaman +''' +from sets import ImmutableSet + +def computeAllMIS(setDescription, constraints): + ''' + Finds the Minimum Inconsistent Subsets for the given constraints and set Description + using a bottom up approach (where bottom is empty set). We check the powerset of constraints, and rule out possible + MIS based on whether or not a given set is consistent. + NOTE: There are probably far more optimal implementations of such an algorithm. However, + for the sake of comparison, we are only interested in the number of times + 'isConsistent' is called and so we refrain from these optimizations. + @param setDescription- A set of rules linking several items together. + Think of this as boolean equation in Conjunctive Normal Form. + @param Constraints- a set of items we would like to include. + Think of this as a value assignment for the previous boolean equation. + ''' + setsToCheck = generatePowerset(constraints) + misSet = set() + while len(setsToCheck) > 0: + checkNextSet(setsToCheck, misSet, setDescription) + + return misSet + + +def checkNextSet(setsToCheck, misSet, setDescription): + ''' + Takes the smallest set of possible MIS and finds its parents. Then checks if it is consistent. + If it is, then it is an MIS and we should rule out all of its parents. + If it is not, then we rule it out as a possible MIS. + ''' + parentSets = set() + + chosenSet = setsToCheck.pop() + setsToCheck.add(chosenSet) + for aSet in setsToCheck: + if len(chosenSet) > len(aSet): + chosenSet = aSet + + for possibleParent in setsToCheck: + if possibleParent.issuperset(chosenSet) and possibleParent != chosenSet: + parentSets.add(possibleParent) + + if setDescription.isConsistent(chosenSet): + misSet.add(chosenSet) + for parentSet in parentSets: + setsToCheck.remove(parentSet) + + setsToCheck.remove(chosenSet) + + + +def generatePowerset(theSet): + ''' + Generates powerset of a given set. + Original code found at http://stackoverflow.com/questions/18826571/python-powerset-of-a-given-set-with-generators + ''' + powerSet = set() + from itertools import chain, combinations + for subset in chain.from_iterable(combinations(theSet, r) for r in range(len(theSet)+1)): + powerSet.add( ImmutableSet(subset)) + return powerSet \ No newline at end of file diff --git a/src/solamanDedekind/Dedekind/Algorithms/BottomUp/__init__.py b/src/solamanDedekind/Dedekind/Algorithms/BottomUp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/solamanDedekind/Dedekind/Algorithms/Hitting_Set_Tree/HittingSetTree.py b/src/solamanDedekind/Dedekind/Algorithms/Hitting_Set_Tree/HittingSetTree.py new file mode 100644 index 0000000..a9e7fc8 --- /dev/null +++ b/src/solamanDedekind/Dedekind/Algorithms/Hitting_Set_Tree/HittingSetTree.py @@ -0,0 +1,90 @@ +''' +Created on Apr 23, 2015 + +@author: Solaman +''' +from LogarithmicExtraction import computeSingleMIS +import LogarithmicExtraction +from sets import ImmutableSet + +def computeAllMIS(setDescription, constraints): + ''' + Taken from 'A Hybrid Diagnosis Approach Combining Black-Box + and White-Box Reasoning'. This attempts to find all Minimal Subset of Constraints + that will be inconsistent for the given Set Description. + @param setDescription- A set of rules linking several items together. + Think of this as boolean equation in Conjunctive Normal Form. + @param Constraints- a set of items we would like to include. + Think of this as an assignment for the previous boolean equation. + ''' + misSet= set() + currPath = ImmutableSet() + paths = set() + LogarithmicExtraction.newRun = True + computeAllMISHelper(setDescription, constraints, + misSet, currPath, paths) + return misSet + +def computeAllMISHelper(setDescription, constraints, misSet, currPath, paths): + #paths holds all previously visited paths of the hitting set tree + #currPath is the current. If any previous path is a subset of this one + #the we have already computed all MIS that would be found in the current path's subtree. + for path in paths: + if path in currPath: + return + + #if the current set of constraints is consistent + #Then there cannot be anymore MIS in its subtree + #so we add the current path to the set of paths enumerated and return. + if not setDescription.isConsistent(constraints): + paths.add(currPath) + return + #In order to avoid redundant MIS computations + #We check the current set of MIS misSet + #If it is possible to find any of the already computed MIS in the current iteration + #(it does not share an element in the currPath) then we just use that MIS + #and continue down the tree + currentMIS = ImmutableSet() + for mis in misSet: + if len(mis.intersection(currPath)) == 0: + currentMIS = mis + break + #If not MIS matches the previous description, we will need to + #compute a new one. + if currentMIS == ImmutableSet(): + currentMIS = computeSingleMIS(setDescription, constraints) + misSet.add(currentMIS) + + #iterate through the children of the current path + for element in currentMIS: + childPath = currPath.union( set(element)) + computeAllMISHelper(setDescription, constraints - ImmutableSet(element), misSet, childPath, paths) + +import sets +def computeAllJust(setDescription, artSet, justSet, curpath, allpaths): + ''' + Implementation of Hitting Set Tree found directly from EulerX. + A few modifications are made to ensure that it is compatible with this library's + implementation of logarathmic Extraction, otherwise everything else is the same + ''' + for path in allpaths: + if path.issubset(curpath): + return + #must be 'not' to be consistent with this library's implementation. + #Without it, it does not compute the MIS properly + #i.e. it does not pass any of the algorithm tests. + if not setDescription.isConsistent(artSet): + allpaths.add(curpath) + return + j = sets.Set() + for s in justSet: + if len(s.intersection(curpath)) == 0: + j = s + if len(j) == 0: + j = computeSingleMIS(setDescription, artSet) + if len(j) != 0: + justSet.add(j) + for a in j: + tmpcur = curpath.union( set(a)) + tmpart = artSet - ImmutableSet(a) + computeAllJust(setDescription, tmpart, justSet, tmpcur, allpaths) diff --git a/src/solamanDedekind/Dedekind/Algorithms/Hitting_Set_Tree/LogarithmicExtraction.py b/src/solamanDedekind/Dedekind/Algorithms/Hitting_Set_Tree/LogarithmicExtraction.py new file mode 100644 index 0000000..cc78c04 --- /dev/null +++ b/src/solamanDedekind/Dedekind/Algorithms/Hitting_Set_Tree/LogarithmicExtraction.py @@ -0,0 +1,60 @@ +''' +Created on Apr 23, 2015 + +@author: Solaman +''' +import random +from sets import ImmutableSet + +#to avoid checking the empty set as an MIS more than once for +#a given set description, an algorithm can set this to "True" before calling. +#This way the Logarithmic Extraction will know that the check is not excessive +newRun = False + +def computeSingleMIS(setDescription, constraints): + ''' + Taken from 'A Hybrid Diagnosis Approach Combining Black-Box + and White-Box Reasoning'. This attempts to find the Minimal Subset of Constraints + that will be inconsistent for the given Set Description. + @param setDescription- A set of rules linking several items together. + Think of this as boolean equation in Conjunctive Normal Form. + @param Constraints- a set of items we would like to include. + Think of this as a value assignment for the previous boolean equation. + ''' + potentialMIS = computeSingleMISHelper(setDescription, ImmutableSet(), constraints) + + #The Euler Implentation does not correctly compute the MIS for a set description + #where everything is always inconsistent (an empty set is inconsistent) + #This makes sense, but this library also considers this set description, + #so we must check the empty configuration here. + global newRun + if newRun == True and len(potentialMIS) == 1 \ + and setDescription.isConsistent(ImmutableSet()): + newRun = False + return ImmutableSet() + else: + newRun = False + return potentialMIS + +def computeSingleMISHelper(setDescription, currentConstraints, constraintsToAdd): + if len(constraintsToAdd) <= 1: + return constraintsToAdd + + constraintsToAddLeft = ImmutableSet(random.sample(constraintsToAdd, len(constraintsToAdd)/2)) + constraintsToAddRight = constraintsToAdd - constraintsToAddLeft + + #If either subset unioned with the current constraints is inconsistent + #then an MIS exists in the subset of them + if setDescription.isConsistent( currentConstraints.union(constraintsToAddLeft) ): + return computeSingleMISHelper(setDescription, currentConstraints, constraintsToAddLeft) + if setDescription.isConsistent( currentConstraints.union(constraintsToAddRight)): + return computeSingleMISHelper(setDescription, currentConstraints, constraintsToAddRight) + + #If both subsets unioned with the current constraints is consistent + #Then an MIS of the current constraints must use elements from both subsets. + #This will find such an MIS + potentialSolutionLeft = computeSingleMISHelper(setDescription, + currentConstraints.union(constraintsToAddRight), constraintsToAddLeft) + potentialSolutionRight = computeSingleMISHelper(setDescription, + currentConstraints.union(potentialSolutionLeft), constraintsToAddRight) + return potentialSolutionLeft.union(potentialSolutionRight) \ No newline at end of file diff --git a/src/solamanDedekind/Dedekind/Algorithms/Hitting_Set_Tree/__init__.py b/src/solamanDedekind/Dedekind/Algorithms/Hitting_Set_Tree/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/solamanDedekind/Dedekind/Algorithms/Random/Random.py b/src/solamanDedekind/Dedekind/Algorithms/Random/Random.py new file mode 100644 index 0000000..fd0df3e --- /dev/null +++ b/src/solamanDedekind/Dedekind/Algorithms/Random/Random.py @@ -0,0 +1,81 @@ +''' +Created on May 4, 2015 + +@author: Solaman +''' +from sets import ImmutableSet +checkedMap = {} + +def computeAllMIS(setDescription, constraints): + ''' + Finds the Minimum Inconsistent Subsets for the given constraints and set Description + using a randomized approach. We check the powerset of constraints, and rule out possible + MIS based on whether or not a given set is consistent. + NOTE: There are probably far more optimal implementations of such a random algorithm. However, + for the sake of comparison, we are only interested in the number of times + 'isConsistent' is called and so we refrain from these optimizations. + @param setDescription- A set of rules linking several items together. + Think of this as boolean equation in Conjunctive Normal Form. + @param Constraints- a set of items we would like to include. + Think of this as a value assignment for the previous boolean equation. + ''' + global checkedMap + checkedMap = {} + setsToCheck = generatePowerset(constraints) + misSet = set() + while len(setsToCheck) > 0: + checkRandomSet(setsToCheck, misSet, setDescription) + + return misSet + + +def checkRandomSet(setsToCheck, misSet, setDescription): + ''' + Takes a random set from the possible sets available and first finds its parent and children sets. + If it has none, then we know it is an MIS and add it to our solution. + If it has some, then we check if it is consistent and rule out the respective sets. + ''' + global checkedMap + import random + chosenSet = random.sample(setsToCheck, 1)[0] + parentSets = set() + childrenSets = set() + + for aSet in setsToCheck: + if chosenSet == aSet: + continue + elif aSet.issubset(chosenSet): + childrenSets.add(aSet) + elif aSet.issuperset(chosenSet): + parentSets.add(aSet) + + if chosenSet in checkedMap: + if len(parentSets) + len(childrenSets) == 0: + setsToCheck.remove(chosenSet) + misSet.add(chosenSet) + return + else: + if setDescription.isConsistent(chosenSet): + #If consistent, then all parents are consistent and also not MIS + for aSet in parentSets: + setsToCheck.remove(aSet) + else: + #If inconsistent, then all children including this one are inconsistent and also not MIS + for aSet in childrenSets: + setsToCheck.remove(aSet) + setsToCheck.remove(chosenSet) + checkedMap[chosenSet] = 1 + + + + +def generatePowerset(theSet): + ''' + Generates powerset of a given set. + Original code found at http://stackoverflow.com/questions/18826571/python-powerset-of-a-given-set-with-generators + ''' + powerSet = set() + from itertools import chain, combinations + for subset in chain.from_iterable(combinations(theSet, r) for r in range(len(theSet)+1)): + powerSet.add( ImmutableSet(subset)) + return powerSet \ No newline at end of file diff --git a/src/solamanDedekind/Dedekind/Algorithms/Random/__init__.py b/src/solamanDedekind/Dedekind/Algorithms/Random/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/solamanDedekind/Dedekind/Algorithms/TopDown/TopDown.py b/src/solamanDedekind/Dedekind/Algorithms/TopDown/TopDown.py new file mode 100644 index 0000000..292362d --- /dev/null +++ b/src/solamanDedekind/Dedekind/Algorithms/TopDown/TopDown.py @@ -0,0 +1,71 @@ +''' +Created on May 4, 2015 + +@author: Solaman +''' +from sets import ImmutableSet + +def computeAllMIS(setDescription, constraints): + ''' + Finds the Minimum Inconsistent Subsets for the given constraints and set Description + using a top down approach (where top is full set). We check the powerset of constraints, and rule out possible + MIS based on whether or not a given set is consistent. + NOTE: There are probably far more optimal implementations of such an algorithm. However, + for the sake of comparison, we are only interested in the number of times + 'isConsistent' is called and so we refrain from these optimizations. + @param setDescription- A set of rules linking several items together. + Think of this as boolean equation in Conjunctive Normal Form. + @param Constraints- a set of items we would like to include. + Think of this as a value assignment for the previous boolean equation. + ''' + + setsToCheck = generatePowerset(constraints) + misSet = set() + findMISSets(setsToCheck, misSet, setDescription) + + return misSet + + +def findMISSets(setsToCheck, misSet, setDescription): + ''' + Takes the largest set from potential MIS and find its children. Check if it is consistent + If it is, and it has children, then rule it out as an MIS, else add it to the MIS + If it is not, then rule it and its children out as MIS. + ''' + setsToCheckList = list(setsToCheck) + setsToCheckList.sort(key = lambda self: len(self), reverse = True) + + for currentSet in setsToCheckList: + if currentSet not in setsToCheck: + continue + childrenSets = set() + parentSets = set() + for aSet in setsToCheck: + if aSet.issubset(currentSet) and aSet != currentSet: + childrenSets.add(aSet) + + if aSet.issuperset(currentSet) and aSet != currentSet: + parentSets.add(aSet) + + if not setDescription.isConsistent(currentSet): + for child in childrenSets: + setsToCheck.remove(child) + setsToCheck.remove(currentSet) + else: + for parent in parentSets: + setsToCheck.remove(parent) + + for currentSet in setsToCheckList: + if currentSet in setsToCheck: + misSet.add(currentSet) + +def generatePowerset(theSet): + ''' + Generates powerset of a given set. + Original code found at http://stackoverflow.com/questions/18826571/python-powerset-of-a-given-set-with-generators + ''' + powerSet = set() + from itertools import chain, combinations + for subset in chain.from_iterable(combinations(theSet, r) for r in range(len(theSet)+1)): + powerSet.add( ImmutableSet(subset)) + return powerSet \ No newline at end of file diff --git a/src/solamanDedekind/Dedekind/Algorithms/TopDown/__init__.py b/src/solamanDedekind/Dedekind/Algorithms/TopDown/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/solamanDedekind/Dedekind/Algorithms/__init__.py b/src/solamanDedekind/Dedekind/Algorithms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/solamanDedekind/Dedekind/Algorithms/analysis.py b/src/solamanDedekind/Dedekind/Algorithms/analysis.py new file mode 100644 index 0000000..a01ce23 --- /dev/null +++ b/src/solamanDedekind/Dedekind/Algorithms/analysis.py @@ -0,0 +1,110 @@ +''' +Created on Apr 28, 2015 + +@author: Solaman + +Used to perform analysis on various +algorithms for computing Minimum Inconsistent Subsets. +''' +from Algorithms.Hitting_Set_Tree import HittingSetTree +from Algorithms.Random import Random +from Algorithms.BottomUp import BottomUp +from Algorithms.TopDown import TopDown + +from Model.DedekindLattice import DedekindLattice +from Model.DedekindSetMapping import getFullSet, getConfAsSet + +algorithms = {} +algorithms["hitting_set_tree"] = HittingSetTree.computeAllMIS +algorithms["random"] = Random.computeAllMIS +algorithms["bottom_up"] = BottomUp.computeAllMIS +algorithms["top_down"] = TopDown.computeAllMIS +isAcceptedOriginalFunction = None + + +def runAnalysis(args): + if len(args) != 2: + print "must provide an algorithm and input size!" + return + algorithmKey = args[0] + if algorithmKey not in algorithms: + print "sorry! That algorithm was not recognized."\ + , "\nHere are the options:" + for _algorithmKey in algorithms.keys(): + print "\n\t", _algorithmKey + return + algorithm = algorithms[algorithmKey] + + if int(args[1]) >5: + print "input larger than 5 is terribly slow, don't do it!" + return + inputSize = int(args[1]) + lattice = DedekindLattice(inputSize) + callCounts = {} + + + while True: + currentNode = lattice.getNextNode() + + if currentNode == None: + break + + modifyisAccepted(currentNode) + fullConstraints = getFullSet(inputSize) + algorithm(currentNode, fullConstraints) + + if currentNode.callCount not in callCounts: + callCounts[currentNode.callCount] = 1 + else: + callCounts[currentNode.callCount] += 1 + + + printStatistics(callCounts) + +def printStatistics(callCounts): + print "min: ", min ( callCounts.keys()) + print "max: ", max( callCounts.keys()) + + totalAlgorithmCalls = sum( callCounts.values()) + totalIsConsistentCalls = 0 + for key in callCounts.keys(): + totalIsConsistentCalls += key * callCounts[key] + + print "mean: ", totalIsConsistentCalls/ totalAlgorithmCalls + + mean = totalIsConsistentCalls/ totalAlgorithmCalls + variance = 0.0 + for key in callCounts: + variance += (callCounts[key] - mean)**2 + variance = float(variance)/float(totalAlgorithmCalls) + + print "standard deviation: ", variance **.5 + medianCount = totalAlgorithmCalls/2 + for key in callCounts.keys(): + medianCount -= callCounts[key] + if medianCount <= 0: + print "median: ", key + break + + print "mode: ", max( callCounts.keys(), key= lambda x: callCounts[x]) + +def modifyisAccepted(setDescription): + ''' + In order to properly analyze an algorithm, we need to check how + many times it has to call "isAccepted" on the given setDescription. + We make this modification here. + ''' + + setDescription.callCount = 0 + setDescription.tries = [] + import types + setDescription.isConsistent = types.MethodType(isConsistent, setDescription) + + + + +def isConsistent(self, configuration): + from Model.DedekindNode import isConsistent as isConsistentOld + self.callCount += 1 + return isConsistentOld(self, configuration) + diff --git a/src/solamanDedekind/Dedekind/Controller/CommandOptions.py b/src/solamanDedekind/Dedekind/Controller/CommandOptions.py new file mode 100644 index 0000000..2ac4f94 --- /dev/null +++ b/src/solamanDedekind/Dedekind/Controller/CommandOptions.py @@ -0,0 +1,85 @@ +''' +Created on Mar 31, 2015 + +@author: Solaman + +Used to store relevant information about command options. +''' +import sys + +class CommandOption(object): + ''' + class for a particular console option + ''' + + + def __init__(self, helpString, commandFunction): + ''' + Constructor + ''' + #Should describe what the option does + self.helpString = helpString + + #Function used to execute this option + self.commandFunction = commandFunction + + +class CommandOptions(object): + ''' + Used to store and order console options + ''' + + def __init__(self): + #If the user wants to request a command by its command string, they can do so with this + self._commands = {} + + self._commands["help"] = CommandOption("displays all available commands"\ + + " along with a description for each", self.printCommandOptions) + + def addCommand(self, commandString, helpString, commandFunction): + ''' + Adds a command option. This option then can be requested by calling + 'selectOption'. + ''' + if commandString == "help": + exceptMessage = "attempted to overwrite \"help\" option in ConsoleOptions. Don't do that plz." + raise Exception(exceptMessage) + + if not callable(commandFunction): + exceptMessage = "Command function must be a function! (must be callable)" + raise Exception(exceptMessage) + + self._commands[commandString] = CommandOption(helpString, commandFunction) + + def printCommandOptions(self): + ''' + Prints all commands available along with the help string associated with each + respective command. + ''' + print "-----" + print "Available Commands" + print "-----" + for command in self._commands.iterkeys(): + print command, ": ", self._commands[command].helpString + print "-----" + + + def selectCommand(self, userInput): + ''' + Calls the function associated with a particular command. + ''' + if len(userInput) == 0: + sys.stderr.write( "Must enter a command, type \"help\" to list all commands\n" ) + return + + command = userInput[0] + inputParams = userInput[1:] + + if command not in self._commands: + sys.stderr.write("Command not recognized, type \"help\" to list all commands\n") + return + + if len(inputParams) == 0: + self._commands[command].commandFunction() + else: + self._commands[command].commandFunction(inputParams) \ No newline at end of file diff --git a/src/solamanDedekind/Dedekind/Controller/Console.py b/src/solamanDedekind/Dedekind/Controller/Console.py new file mode 100644 index 0000000..8acf541 --- /dev/null +++ b/src/solamanDedekind/Dedekind/Controller/Console.py @@ -0,0 +1,33 @@ +''' +Created on Mar 31, 2015 + +@author: Solaman +This is the main interface with the application if the user chooses to use +it as a console application. +''' +import os + +class Console(object): + + def __init__(self, commandOptions): + self.commandOptions = commandOptions + + self.commandOptions.addCommand("exit", "exits the console.", self.endConsole) + + self.shouldContinue = True + + + def selectCommand(self, userInput): + self.commandOptions.selectCommand(userInput) + + def endConsole(self): + print "Bye!" + self.shouldContinue = False + +def run(commandOptions): + console = Console(commandOptions) + print "hello! To list commands, type \"help\"" + while console.shouldContinue: + userInput = raw_input("enter command: ") + userInput = userInput.split(" ") + console.selectCommand(userInput) \ No newline at end of file diff --git a/src/solamanDedekind/Dedekind/Controller/__init__.py b/src/solamanDedekind/Dedekind/Controller/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/solamanDedekind/Dedekind/Dedekind.py b/src/solamanDedekind/Dedekind/Dedekind.py new file mode 100644 index 0000000..b42fdbd --- /dev/null +++ b/src/solamanDedekind/Dedekind/Dedekind.py @@ -0,0 +1,37 @@ +''' +Created on Apr 1, 2015 + +@author: Solaman +''' +from Controller.CommandOptions import CommandOptions +from Controller import Console +from Model.DedekindLattice import getDedekindNumber, generateDotFiles + +import sys +from Algorithms.analysis import runAnalysis + +def runConsole(): + global standardCommands + + del standardCommands._commands["console"] + Console.run(standardCommands) + +standardCommands = CommandOptions() + +standardCommands.addCommand("getNumber", "Finds Dedekind Number for a given input size."\ + + "\n\tInput:" + " function input size", getDedekindNumber) + +standardCommands.addCommand("dotFiles", "Generates dot files of all monotone boolean functions"\ + + " for a given input size." + "\n\tInput:" + " function input size", generateDotFiles) + +standardCommands.addCommand("analyze", "Runs a System-Diagnostic algorithm"\ + + " and perform analysis on varying MBF's" +"\n\tInput: algorithm name, input size", runAnalysis ) +standardCommands.addCommand("console", "Run the program as a console.", runConsole) + + +def main(): + userInput = sys.argv[1:] + standardCommands.selectCommand(userInput) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/solamanDedekind/Dedekind/DedekindAnalysis.py b/src/solamanDedekind/Dedekind/DedekindAnalysis.py new file mode 100644 index 0000000..3baa304 --- /dev/null +++ b/src/solamanDedekind/Dedekind/DedekindAnalysis.py @@ -0,0 +1,22 @@ +''' +Created on Mar 13, 2015 + +@author: Solaman +''' +import cProfile +from Model.DedekindNode import DedekindNode +from Model.DedekindLattice import DedekindLattice + +node = DedekindNode(4, [15]) +lattice = DedekindLattice(5, lean = True) + +def analyzeDedekindNode(): + cProfile.run("node.generatePossibleConfigurations()") + +def analyzeFindUniqueFunctions(): + cProfile.run("lattice.getDedekindNumber()") + +if __name__ == '__main__': + #analyzeDedekindNode() + #analyzeDedekindLattice() + analyzeFindUniqueFunctions() \ No newline at end of file diff --git a/src/solamanDedekind/Dedekind/GeneratedDedekindLattices/preserve.txt b/src/solamanDedekind/Dedekind/GeneratedDedekindLattices/preserve.txt new file mode 100644 index 0000000..13497a6 --- /dev/null +++ b/src/solamanDedekind/Dedekind/GeneratedDedekindLattices/preserve.txt @@ -0,0 +1,6 @@ +March 3rd, 2015: GeneratedDedekindLattices will not be included in a git commit +unless a file is included. This is that file. + +Note: We could have the application generate this folder as needed, but I believe +that because the folder's existence isn't tied to a run of the application +("dedekind world 5" would be a folder that is tied) the user might expect the folder to already exist. \ No newline at end of file diff --git a/src/solamanDedekind/Dedekind/Model/DedekindLattice.py b/src/solamanDedekind/Dedekind/Model/DedekindLattice.py new file mode 100644 index 0000000..3d53f7c --- /dev/null +++ b/src/solamanDedekind/Dedekind/Model/DedekindLattice.py @@ -0,0 +1,203 @@ +''' +Created on Feb 26, 2015 + +@author: Solaman +''' +from DedekindNode import DedekindNode +from Model.LevelPermutor import LevelPermutor +from DedekindNode import getIndex + +class LatticeFiller(object): + ''' + Constructed when the Lattice is constructed if the user wants to fill in the + lattice with each Monotone Boolean Function individually. + This class provides a level of abstraction from the inner workings of the module. + ''' + def __init__(self, lattice): + self.lattice = lattice + self.nodeList = [] + self.nodeList.append(lattice.emptyFunction) + self.nodeList.append(lattice.baseFunction) + self.wasFilled = False + + def getNextNode(self): + ''' + Returns the most recently added node to the queue + if it is not empty. + ''' + if self.nodeList == []: + return None + node = self.nodeList.pop() + children = node.generateChildren() + for child in children: + self.nodeList.append(child) + self.lattice.lattice[getIndex(child)] = child + return node + + def fillLattice(self): + while self.getNextNode() != None: + continue + + def getDedekindNumber(self): + if self.wasFilled == False: + self.fillLattice() + self.wasFilled = True + return len(self.lattice.lattice.values()) + +class LatticeFillerUnique(object): + ''' + Constructed when the Lattice is constructed if the user would like to fill in + the lattice with Monotone Boolean Functions that are equivalent by level. + This class provides a level of abstraction from the inner workings of the module. + ''' + def __init__(self, lattice, lean= False): + from Model.DedekindNodeLean import DedekindNodeLean + if lean == True: + lattice.baseFunction = DedekindNodeLean(lattice.baseFunction.inputSize,\ + lattice.baseFunction.acceptedConfigurations[-1]) + + self.lattice = lattice + self.nodeList = [] + lattice.emptyFunction.isVisited = False + lattice.emptyFunction.parent = None + self.nodeList.append(lattice.emptyFunction) + lattice.baseFunction.isVisited = False + lattice.baseFunction.parent = None + self.nodeList.append(lattice.baseFunction) + self.levelPermutor = LevelPermutor(lattice.inputSize) + self.mileMarker = 10000 + + self.wasFilled = False + + def getNextNode(self): + #We don't need to compute functions that are isomorphisms of each other. + #We store each function by there level, and then counts by the number of possible children for the function + #It is proven that functions by level that have the same number of possible children are isomorphisms + #{"level" : {"isomorphismCount": count, "children" : children } } + if self.nodeList == []: + return None + node = self.nodeList.pop() + if node.isVisited == True: + if node.parent == None: + return node + else: + node.parent.childrenCount += node.childrenCount + if node.parent.childrenCount >= self.mileMarker: + print "marker: ", self.mileMarker + self.mileMarker = node.parent.childrenCount * 2 + return self.getNextNode() + + if self.getKey(node) in self.levelPermutor: + node.parent.childrenCount += self.levelPermutor[self.getKey(node)].childrenCount + return self.getNextNode() + else: + self.lattice.lattice[ getIndex(node.getAcceptedConfigurationsAsList()) ] = node + node.childrenCount = 1 + self.levelPermutor[node.getLastLevel()] = node + children = node.generateChildren() + node.isVisited = True + self.nodeList.append(node) + for child in children: + child.parent = node + child.isVisited = False + self.nodeList.append(child) + return node + + def getKey(self, node): + if hasattr(node, "key"): + return node.key + else: + node.key = getIndex(node.getLastLevel()) + return node.key + + def fillLattice(self): + while self.getNextNode() != None: + continue + + def getDedekindNumber(self): + if self.wasFilled == False: + self.fillLattice() + self.wasFilled = True + return self.lattice.baseFunction.childrenCount + 1 + +class DedekindLattice(object): + ''' + We aim to generate the Dedekind Lattices using this class. Namely, + We want to generate all monotone boolean functions given an n input size. + Currently, we will aim to only generate the lattices in working memory. + Future implementations will hopefully be able to dynamically + Generate a node of a given Lattice. + ''' + + def __init__(self, inputSize, generateUnique = True, lean = False): + ''' + Constructor. For now, we will store each monotone boolean function + as an object. Future implementations will store them as a single bit + for lean memory usage + ''' + if inputSize < 0: + raise Exception("Input size must be greater than or equal to 0") + self.lattice = {} + + #bit mask refers to the possible bit values + #of a given configuration. E.G. boolean functions with 4 inputs + #Will have a bit mask of 0xF + self.bitMask = 2**(inputSize) - 1 + self.inputSize = inputSize + + self.emptyFunction = DedekindNode(self.inputSize, []) + self.lattice[ getIndex(self.emptyFunction)] = self.emptyFunction + + self.baseFunction = DedekindNode(self.inputSize, [self.bitMask]) + self.lattice[ getIndex(self.baseFunction)] = self.baseFunction + + if generateUnique: + self.latticeFiller = LatticeFillerUnique(self, lean) + + else: + self.latticeFiller = LatticeFiller(self) + + def getDedekindNumber(self): + return self.latticeFiller.getDedekindNumber() + + def getNextNode(self): + return self.latticeFiller.getNextNode() + + + def generateDotFiles(self): + ''' + + ''' + import os + directoryName = os.path.join("GeneratedDedekindLattices", str(self.inputSize) + "_DedekindLattice") + if not os.path.exists(directoryName): + os.mkdir(directoryName) + updateTime = self.monotoneCount/10 + generatedFiles = 0 + for function in self.lattice.itervalues(): + function.writeToDotFile(directoryName) + generatedFiles += 1 + if generatedFiles % updateTime == 0: + print generatedFiles, " written so far" + print "Done" + + +def getDedekindNumber(userInput): + ''' + Constructs the Dedekind Lattice for the given input size and + returns the dedekind number associated with that input. + Values that return within a minute are currently n <= 5. + ''' + inputSize = int(userInput[0]) + dedekindLattice = DedekindLattice(inputSize, lean =True) + print dedekindLattice.getDedekindNumber() + +def generateDotFiles(userInput): + ''' + Constructs the Dedekind Lattice for the given input size and + generates the dot files for each monotone boolean function with the given input size. + ''' + inputSize = int(userInput[0]) + dedekindLattice = DedekindLattice(inputSize) + dedekindLattice.fillLattice() + dedekindLattice.generateDotFiles() \ No newline at end of file diff --git a/src/solamanDedekind/Dedekind/Model/DedekindNode.py b/src/solamanDedekind/Dedekind/Model/DedekindNode.py new file mode 100644 index 0000000..261734d --- /dev/null +++ b/src/solamanDedekind/Dedekind/Model/DedekindNode.py @@ -0,0 +1,290 @@ +''' +Created on Mar 3, 2015 + +@author: Solaman +''' +from itertools import combinations as genCombinations +import os +from collections import Iterable +from DedekindNodeIter import DedekindNodeIter +from DedekindSetMapping import getConfAsInt + +#Used for Dot file generation +#Once a node is written to a dot file +#A full Node is created to help +fullNodes = {} + +#Used for Dot file generation +#Once a node is written to a dot file +#labels for each configuration are made +configurationLabelss = {} + +#Used for Dot file Generation +#Once a node is written to a dot file +#edges between configurations are written as dot edges. +dotEdgess = {} + +#To avoid cost of calculating configuration levels continuously, +#We will calculate the levels from the very beginning. +configurationLevelss = {} + +class DedekindNode(Iterable): + ''' + A boolean function. It is up to the user to ensure that it is monotone. + ''' + + + def __init__(self, inputSize, acceptedConfigurations, nodeToCopy = None): + ''' + Each function has a set of accepted configurations. To aid in the book keeping of this algorithm, + each accepted configuration is stored by "level", namely, how many of the given inputs must be "off" + in the configuration. so if the configuration 0b0001 is accepted and the bitmask is 0b1111, + then the configuration would be stored at the 3rd level. + For rudimentary checking, the accepted configurations are added by descending level. + ''' + self.childrenSize = 0 + self.inputSize = inputSize + self.bitMask = self.bitMask = 2**(inputSize) - 1 + + + if nodeToCopy != None: + temp = nodeToCopy.getAcceptedConfigurationsAsList() + temp.extend(acceptedConfigurations) + acceptedConfigurations = temp + self.acceptedConfigurations = [] + + for acceptedConfiguration in acceptedConfigurations: + level = getConfigurationLevel(self.inputSize, acceptedConfiguration) + if level > len( self.acceptedConfigurations): + exceptionMessage = "Cannot add configurations beyond the highest level + 1" \ + + "\n attempted level: " + str(level) \ + +"\n level limit: " + str(len(self.acceptedConfigurations)) + raise Exception( exceptionMessage) + + if level == len(self.acceptedConfigurations): + self.acceptedConfigurations.append( [acceptedConfiguration]) + else: + self.acceptedConfigurations[level].append( acceptedConfiguration) + + self.index = -1 + + + def getAcceptedConfigurationsAsList(self): + acceptedConfigurations = [] + for level in self.acceptedConfigurations: + acceptedConfigurations.extend(level) + + return acceptedConfigurations + + def isConsistent(self, configuration): + ''' + Checks if a configuration would be accepted or not. + ''' + return isConsistent(self, configuration) + + def _generatePossibleConfigurations(self): + ''' + Generates possible configurations to add to the function such that the new functions would be monotone + (given that this function is monotone). We observe that this can be done by level combinations. + E.G. if the input size is 4, and the last level of the function is 1->[0b1011, 0b0111], then the children + of the node in the lattice can have the level 2 configuration [0b0011]. + (if any other, then it would not be monotone). + ''' + possibleConfigurations = [] + newMaxLevel = len(self.acceptedConfigurations) + + #current max configuration level is [self.bitMask] + if newMaxLevel == 1: + possibleConfigurations = getLevelOneConfigurations(self.inputSize) + + #Entire Dedekind Node Lattice is filled, can add [0] as an accepted configuration + elif newMaxLevel == self.inputSize \ + and len(self.acceptedConfigurations[-1]) == self.inputSize: + possibleConfigurations = [0] + #current max configuration level is [] (none are accepted) + elif newMaxLevel == 0: + return [] + elif newMaxLevel< self.inputSize: + combinations = genCombinations(self.acceptedConfigurations[-1], newMaxLevel) + possibleConfigurations = [] + for combination in combinations: + possibleConfiguration = self.bitMask + for configuration in combination: + possibleConfiguration &= configuration + if getConfigurationLevel(self.inputSize, possibleConfiguration) == newMaxLevel: + possibleConfigurations.append(possibleConfiguration) + return possibleConfigurations + + return possibleConfigurations + + + def generateChildren(self): + ''' + Generates all Dedekind Nodes that would be considered the children of this + DedekindNode + ''' + children = [] + possibleConfigurations = self._generatePossibleConfigurations() + + for numberOfConfigurations in range(1, len(possibleConfigurations) + 1): + combinations = genCombinations(possibleConfigurations, numberOfConfigurations) + for combination in combinations: + children.append( DedekindNode(self.inputSize, combination, self)) + + return children + + def __iter__(self): + ''' + Implemented this with good design in mind, however + If you want something fast, it is better to use + getAcceptedConfigurationsAsList + ''' + return DedekindNodeIter(self) + + def getLastLevel(self): + if len(self.acceptedConfigurations) > 0: + return self.acceptedConfigurations[-1] + else: + return [] + + def writeToDotFile(self, writeLocation): + global fullNodes, configurationLabelss, dotEdgess + + dotFileName = os.path.join(writeLocation, "n_" + str(self.inputSize)\ + + "." + "world_" + str(getIndex(self.getAcceptedConfigurationsAsList()))\ + + ".dot") + dotFile = open( dotFileName, "w") + dotFile.write("""digraph{ + rankdir=BT + node[shape=circle, style=filled, label=""] + edge[dir=none]\n""") + + initDotVariables(self.inputSize) + fullNode = fullNodes[self.inputSize] + configurationLabels = configurationLabelss[self.inputSize] + dotEdges = dotEdgess[self.inputSize] + + #configurationList = self.getAcceptedConfigurationsAsList() + for configuration in fullNode: + if configuration in self: + dotFile.write( configurationLabels[configuration] +" [ color = green, "\ + + "label = \""+ configurationLabels[configuration] + "\"]\n") + else: + dotFile.write( configurationLabels[configuration] +" [ color = red, "\ + + "label = \""+ configurationLabels[configuration] + "\"]\n") + + dotFile.write(dotEdges) + dotFile.write("}") + + dotFile.close() + +def isConsistent(node, configuration): + ''' + Checks if a configuration would be deemed "inconsistent". + This is confusing! The DedekindNode represents a faulty system, and the + "accepted configurations" represent sets such that, if you deemed the given + components (represented by bits) as "faulty" and all others as safe, you would explain + erroneous output. + ''' + from sets import ImmutableSet + if isinstance(configuration, ImmutableSet): + configuration = getConfAsInt(configuration, node.inputSize) + if (getIndex(node.getAcceptedConfigurationsAsList()) & 1 << configuration ) == 0: + return False + else: + return True + +def getFullNode(inputSize): + global fullNodes + if inputSize not in fullNodes: + bitMask = (1< " \ + + configurationLabels[configuration] + "\n" + dotEdges += edgeString + + fullNodes[inputSize] = fullNode + configurationLabelss[inputSize] = configurationLabels + dotEdgess[inputSize] = dotEdges + +def getIndex(configurationList): + ''' + If we treat each configuration as its own integer value, we can combine each value into an integer + of size 2**inputSize bits. E.G. if the input size is 4, then each configuration has a value between 0-15. + So an integer of 16 bits, where each bit is for each configuration, will represent the function + and its accepted configurations. Since this value is unique, we can also use it as an index for the function + ''' + index = 0 + for configuration in configurationList: + index |= (1 << configuration) + + return index + +def getConfigurationLevel(inputSize, configuration): + global configurationLevelss + if inputSize not in configurationLevelss: + bitMask = (1<= len(self.dedekindNode.acceptedConfigurations): + raise StopIteration + + result = self.dedekindNode.acceptedConfigurations[self.levelIndex][self.configIndex] + self.configIndex += 1 + + if self.configIndex >= len(self.dedekindNode.acceptedConfigurations[self.levelIndex]): + self.configIndex = 0 + self.levelIndex += 1 + + return result + \ No newline at end of file diff --git a/src/solamanDedekind/Dedekind/Model/DedekindNodeLean.py b/src/solamanDedekind/Dedekind/Model/DedekindNodeLean.py new file mode 100644 index 0000000..4f756a9 --- /dev/null +++ b/src/solamanDedekind/Dedekind/Model/DedekindNodeLean.py @@ -0,0 +1,133 @@ +''' +Created on Apr 30, 2015 + +@author: Solaman +''' +from itertools import combinations as genCombinations +from DedekindSetMapping import getConfAsInt + +#To avoid cost of calculating configuration levels continuously, +#We will calculate the levels from the very beginning. +configurationLevelss = {} + +class DedekindNodeLean(object): + ''' + Attempt to speed up generation of nodes. For computing the Dedekind number, + we only need the "highest" level of configurations, so we attempt + to reduce overhead by keeping only this level. + ''' + + def __init__(self, inputSize, acceptedConfigurations): + ''' + Each function has a set of accepted configurations. To aid in the book keeping of this algorithm, + each accepted configuration is stored by "level", namely, how many of the given inputs must be "off" + in the configuration. so if the configuration 0b0001 is accepted and the bitmask is 0b1111, + then the configuration would be stored at the 3rd level. + For rudimentary checking, the accepted configurations are added by descending level. + ''' + self.childrenSize = 0 + self.inputSize = inputSize + self.bitMask = self.bitMask = 2**(inputSize) - 1 + self.acceptedConfigurations = acceptedConfigurations + self.level = -1 + if acceptedConfigurations != []: + self.level = getConfigurationLevel(inputSize, acceptedConfigurations[0]) + + self.index = -1 + + def _generatePossibleConfigurations(self): + ''' + Generates possible configurations to add to the function such that the new functions would be monotone + (given that this function is monotone). We observe that this can be done by level combinations. + E.G. if the input size is 4, and the last level of the function is 1->[0b1011, 0b0111], then the children + of the node in the lattice can have the level 2 configuration [0b0011]. + (if any other, then it would not be monotone). + ''' + possibleConfigurations = [] + + #current max configuration level is [self.bitMask] + if self.level == 0: + possibleConfigurations = getLevelOneConfigurations(self.inputSize) + + #Entire Dedekind Node Lattice is filled, can add [0] as an accepted configuration + elif self.level == self.inputSize - 1 \ + and len(self.acceptedConfigurations) == self.inputSize: + possibleConfigurations = [0] + #current max configuration level is [] (none are accepted) + elif self.level == -1: + return [] + elif self.level< self.inputSize - 1: + combinations = genCombinations(self.acceptedConfigurations, self.level + 1) + possibleConfigurations = [] + for combination in combinations: + possibleConfiguration = self.bitMask + for configuration in combination: + possibleConfiguration &= configuration + if getConfigurationLevel(self.inputSize, possibleConfiguration) == self.level + 1: + possibleConfigurations.append(possibleConfiguration) + return possibleConfigurations + + return possibleConfigurations + + + def generateChildren(self): + ''' + Generates all Dedekind Nodes that would be considered the children of this + DedekindNode + ''' + children = [] + possibleConfigurations = self._generatePossibleConfigurations() + + for numberOfConfigurations in range(1, len(possibleConfigurations) + 1): + combinations = genCombinations(possibleConfigurations, numberOfConfigurations) + for combination in combinations: + children.append( DedekindNodeLean(self.inputSize, combination)) + + return children + + def getLastLevel(self): + return self.acceptedConfigurations + + def getAcceptedConfigurationsAsList(self): + return self.acceptedConfigurations + +def getConfigurationLevel(inputSize, configuration): + global configurationLevelss + if inputSize not in configurationLevelss: + bitMask = (1< 0111 +1111 -> 1011 +1111 -> 1101 +1111 -> 1110 +0111 -> 0011 +1011 -> 0011 +0111 -> 0101 +1101 -> 0101 +0111 -> 0110 +1110 -> 0110 +1011 -> 1001 +1101 -> 1001 +1011 -> 1010 +1110 -> 1010 +1101 -> 1100 +1110 -> 1100 +0011 -> 0001 +0101 -> 0001 +1001 -> 0001 +0011 -> 0010 +0110 -> 0010 +1010 -> 0010 +0101 -> 0100 +0110 -> 0100 +1100 -> 0100 +1001 -> 1000 +1010 -> 1000 +1100 -> 1000 +0001 -> 0000 +0010 -> 0000 +0100 -> 0000 +1000 -> 0000 +} \ No newline at end of file diff --git a/src/solamanDedekind/Dedekind/Test/ModelTest/n_4.world_57344.pdf b/src/solamanDedekind/Dedekind/Test/ModelTest/n_4.world_57344.pdf new file mode 100644 index 0000000..2b580d6 Binary files /dev/null and b/src/solamanDedekind/Dedekind/Test/ModelTest/n_4.world_57344.pdf differ diff --git a/src/solamanDedekind/Dedekind/Test/ModelTest/writeToDotTest.dot b/src/solamanDedekind/Dedekind/Test/ModelTest/writeToDotTest.dot new file mode 100644 index 0000000..b828616 --- /dev/null +++ b/src/solamanDedekind/Dedekind/Test/ModelTest/writeToDotTest.dot @@ -0,0 +1,53 @@ +digraph{ + rankdir=BT + node[shape=circle, style=filled, label=""] + edge[dir=none] +1111 [ color = green, label = "1111"] +0111 [ color = red, label = "0111"] +1011 [ color = red, label = "1011"] +1101 [ color = green, label = "1101"] +1110 [ color = green, label = "1110"] +0011 [ color = red, label = "0011"] +0101 [ color = red, label = "0101"] +0110 [ color = red, label = "0110"] +1001 [ color = red, label = "1001"] +1010 [ color = red, label = "1010"] +1100 [ color = red, label = "1100"] +0001 [ color = red, label = "0001"] +0010 [ color = red, label = "0010"] +0100 [ color = red, label = "0100"] +1000 [ color = red, label = "1000"] +0000 [ color = red, label = "0000"] +1111 -> 0111 +1111 -> 1011 +1111 -> 1101 +1111 -> 1110 +0111 -> 0011 +1011 -> 0011 +0111 -> 0101 +1101 -> 0101 +0111 -> 0110 +1110 -> 0110 +1011 -> 1001 +1101 -> 1001 +1011 -> 1010 +1110 -> 1010 +1101 -> 1100 +1110 -> 1100 +0011 -> 0001 +0101 -> 0001 +1001 -> 0001 +0011 -> 0010 +0110 -> 0010 +1010 -> 0010 +0101 -> 0100 +0110 -> 0100 +1100 -> 0100 +1001 -> 1000 +1010 -> 1000 +1100 -> 1000 +0001 -> 0000 +0010 -> 0000 +0100 -> 0000 +1000 -> 0000 +} \ No newline at end of file diff --git a/src/solamanDedekind/Dedekind/Test/__init__.py b/src/solamanDedekind/Dedekind/Test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/solamanDedekind/Dedekind/TransitionError.py b/src/solamanDedekind/Dedekind/TransitionError.py new file mode 100644 index 0000000..75be7a4 --- /dev/null +++ b/src/solamanDedekind/Dedekind/TransitionError.py @@ -0,0 +1,16 @@ +''' +Created on Mar 2, 2015 + +@author: Solaman +''' +class TransitionError(Exception): + ''' + Raised when an operation attempts a state transition that is + not allowed. + ''' + + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return repr(self.value) \ No newline at end of file diff --git a/src/solamanDedekind/Dedekind/resources/__init__.py b/src/solamanDedekind/Dedekind/resources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/solamanDedekind/Dedekind/resources/setValues.csv b/src/solamanDedekind/Dedekind/resources/setValues.csv new file mode 100644 index 0000000..1b91aad --- /dev/null +++ b/src/solamanDedekind/Dedekind/resources/setValues.csv @@ -0,0 +1 @@ +A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z \ No newline at end of file diff --git a/src/solamanDedekind/Explanation.pdf b/src/solamanDedekind/Explanation.pdf new file mode 100644 index 0000000..afc2edf Binary files /dev/null and b/src/solamanDedekind/Explanation.pdf differ diff --git a/src/solamanDedekind/README.md b/src/solamanDedekind/README.md new file mode 100644 index 0000000..7a6c8d8 --- /dev/null +++ b/src/solamanDedekind/README.md @@ -0,0 +1,17 @@ +# System-Diagnostic-Analysis +How to run: python Dedekind.py
+type "-help" to see a list of all available commands
+This project is meant to be used either as a command line, console, or library.
+If you wish create the monotone boolean functions and .dot files for each, try +running "python Dedekind.py" and use the command line or console.
+Else, all of these functions are available through Model.DedekindLattice.py . Which also have useful +features for System Diagnostic Analysis:
+by instantiated an instance of DedekindLattice.py, you can call "getNextNode()" which will return
+a new monotone boolean function within the DedekindLattice. From here, you can then call "isConsistent", passing + in a configuration, to see if the function would accept it.
+ DO NOT BE CONFUSED BY computeALLMIS and the "isConsistent" function of DedekindNodes!
+ In System-Diagnostic-Analysis, when we ask for a minimal inconsistent subset, we ask for + the smallest set which explains the inconsistency of an input to an answer.
+ When we ask if a set "isConsistent" however, we are asking if the set explains the inConsistency + of the input to an answer (would assuming the set is erroneous make the answer consistent with the input?). +