VIEWS: 4 PAGES: 35 CATEGORY: Education POSTED ON: 11/25/2009 Public Domain
A Differential Diagnostic System based on a Java Framework for Intelligent Systems by Holger Flier mail: thesis@holgerflier.de www: http://www.holgerflier.de/thesis This software is part of my diploma thesis Construction of Differential Diagnostic Systems. It comprises of • a framework for the development of logic-based intelligent systems, providing classes for literals, logic formulas, CNF-Systems, etc. (see chapter 6 of my thesis, which is appended to this document) • an encapsulation of the MINSAT-solver functionality of the lbcc compiler of the Leibniz System, including a pre-compiled DLL • some smaller examples on how to use the library • an implementation, including example data, of a Differential Diagnostic System (DDS) as suggested in Klaus Truemper's book Design of Logic-based Intelligent Systems (see chapter 7 of my thesis, which is appended to this document) System Requirements: • JDK 5.0 (available at http://java.sun.com/j2se/1.5.0/download.jsp ) • Windows XP (or compatible) Legal note: Except for the DLL, which is distributed under the license terms applying to the Leibniz System, this software is freely available. The software is distributed “as is”, without any guarantee. Installation instructions for experienced developers: Unzip the file dds_1.0.zip to a folder of your choice. In the „examples“ package, you can find several small classes illustrating the use of the framework, i.e. the packages “logic” and “leibniz”. The implementation of the DDS is found in the „diffDiagSys“ package. Run the class DifferentialDiagnosticSystem. Installation instructions for Java beginners: Please note that a part of the software (the DLL) will only run on Windows XP (or compatible)! I recommend you install the following software, which will enable you to try out the library without running into technical problems: 1. Install the JDK 5.0 (available at http://java.sun.com/j2se/1.5.0/download.jsp ) 2. Install BlueJ (available at http://www.bluej.org/download/download.html ) 3. Download dds_1.0.zip and extract it, for example, to your Desktop. After installation, try the following steps: Start BlueJ. (You might be asked to select a Virtual Machine, which is the JDK 5.0 installed in step 1. On my computer, this is “c:\Program Files\Java\jdk1.5.0_06”.) From the menu, select „Project“ and then „Open Non BlueJ“. Select the „dds_1.0“ folder: In the next screen, you see all subdirectories of „dds_1.0“. Double click on the package „diffDiagSys“. Now, you see all classes contained in the package „diffDiagSys“. Right click on the class „DifferentialDiagnosticSystem“ and select „void main(String[])“. This is the main routine, starting the DDS. BlueJ will prompt you for parameters to pass on to the main function. Simply press „OK“. The application should now start with the following screen: You can now try out the DDS by clicking on the button „1. Get Record“ and filling in values. To try out the library, you may also want to take a look at the examples in the „examples“ package (execute them by right-clicking and selecting „void main(String[])“ as shown above for the DDS). To view the source code of a class, simply double click on the class in BlueJ Further information on the library and the DDS are provided in chapters 6 and 7 of the thesis, which are appended at the end of this document. You may also download the full thesis at http://www.holgerflier.de/thesis/. Chapter 6 A Framework for Logic Computation The motivation behind the framework presented in this thesis is clearly to fa- cilitate the implementation of logic-based intelligent systems. With the Leibniz System, a powerful set of tools is already available. It is programmed in C, a programming language that allows for eﬃcient use of hardware resources, espe- cially processor time. The choice of such a programming language seems to be mandatory if one is interested in developing fast code. However, for fast devel- oping other tools are more adequate, at least for those parts of the code that are not critical concerning speed. While C allows for fast execution of programs, it usually takes considerably more time to implement applications compared to other programming languages, such as Java. This is especially the case when dealing with graphical user interfaces (GUI). For the purposes of both research and practice, being able to rapidly implement prototypes and applications is of interest. For example, a student might want to work on some of the programming exer- cises of the book Design of logic-based intelligent Systems [10] to gain a better understanding of the algorithms presented there. It would be motivating to get working results in short time, instead of having to worry about the usual problems encountered when programming in C. Furthermore, the student might appreciate to use a library that already provides implementations of essential concepts such as a CNF system, thus leaving the student more time to work on the exercises. Or consider the practitioner who realized that one of the applications presented in [10] is suitable to solve a problem she is confronted with. Although she is an experienced programmer, her boss does not leave her much time to play with new ideas. To convince her boss, she would have to create a prototype of the application quickly. There is no time to get acquainted with detailed technical manuals. She needs a library that is intuitive to use. 41 42 A Framework for Logic Computation For all these reasons, it is desirable to develop tools that allow for rapid devel- opment of logic-based intelligent systems. With the work of this thesis, a step in this direction has been made. We provide a framework of classes written in the object-oriented programming language Java. Java is easy to learn, widely spread and available on virtually all platforms. Through the design of Java, many pitfalls encountered in C can be avoided. Excellent development tools are available for free, e. g. the Eclipse software development kit [3], as well as literally millions of websites providing support. In this spirit, the framework will be freely available. It is the hope of the author that it will be used and developed further. Users are strongly encouraged to contribute to this project. A clear disadvantage of Java as compared to C is the speed of execution. However, as the time critical parts are already implemented in the Leibniz System, this does not imply that applications developed with the framework are necessarily slow. Even if the use of Java is infeasible for a certain application, the framework may still be of use for rapid prototyping. We start with an overview of the framework. It comprises of several classes that reﬂect the basic concepts of propositional logic, i. e. variables, literals, clauses, and formulas, as well as further concepts like learned formulas, records, and CNF systems. The corresponding classes of these concepts are bundled in a package called logic. A further package, called leibniz, contains a class that provides an interface to the lbcc compiler of the Leibniz System. With the help of that class, SAT and MINSAT problems can be solved conveniently. As will be shown in chapter 7, implementation of logic-based intelligent systems is straightforward with the use of this framework. Here, we describe the interfaces of the framework’s classes. We will make use of terms as Collections, Lists or Sets. By that we denote interfaces of data structures available in the Java language. The interpretation of List and Set is obvious, whereas a Collection may stand for any kind of data structure, including the latter ones. For a compre- hensive introduction to these interfaces, their underlying implementations, and the Java language in general consult [2]. Since the source code is listed in the appendix, we omit types of both signatures and return values of methods, as well as getter and setter methods which have an obvious interpretation. 6.1 Classes Clause, Formula, Literal, and Variable The purpose of creating classes for basic entities of propositional logic is mainly to encourage clarity in the design of implementations. The behavior of the methods is mostly self-explanatory. Since there is a one-to-one correspondence of classes and concepts, i. e. class Variable and the concept of a “variable”, we will use such 6.1 Classes Clause, Formula, Literal, and Variable 43 terms interchangeably. We start with introducing the methods of class Variable. This class has a special interface since its constructor is not public. The reason for this is that each variable must have a unique identiﬁer. Allowing many objects of Variable with the same identiﬁer could lead to mistakes that are hard to debug. Instead, the following methods are available: Variable.createOrGet(name, trueCost, falseCost) With the help of this static method, variables are instantiated with name as identiﬁer. At the same time, costs for the variable are deﬁned in case that MINSAT instances will be solved. If, however, a variable with the same name has already been created, this variable is returned without changing its costs. Variable.exists(name) A static method that checks whether a variable for the identiﬁer already exists, but that does not create a new instance. Variable.getAllVariables() Returns a Collection of all instances of the class Variable. Variable.getByName(name) This static method returns an existing variable for the identiﬁer or null if such an instance does not exist. It does not create a new variable. Class Literal consists of a variable and a boolean stating if the literal is a negated variable or not. For a single variable, arbitrary many literals may exist. Literal(isPositive, variable) Setting the argument isPositive to true means that the literal is a non-negated variable. getCopy() Returns a copy of the literal. not() Returns a negated copy of the literal, i. e. if the literal is a negated variable the copy is non-negated and vice versa. Class Clause comprises of a set of literals. A Clause is either in CNF or DNF. Thus, all literals of a clause are connected by the same operator. To specify the normal form, the class Formula provides constants NormalForm.CNF and NormalForm.DNF. Clause(normalForm) Constructs an empty CNF or DNF clause. addLiteral(literal) Adds a literal to the clause. The operator combining the literals depends on the normal form. 44 A Framework for Logic Computation containsUnavailableLiteral(record) Returns true if and only if the clause contains a literal of a variable that has an Unavailable value in a record of the yet to be described class Record. evaluateRecord(record) Returns the True/False/Undecided value, to which the clause evaluates on record. Any open xj will be treated as Absent. Constants representing the True/False/Undecided values are deﬁned in the static enum member Formula.Value. getCopy() Creates a copy of the clause and its literals. getVariables() Returns a list of variables that occur as literals in the clause. not() Applies the “¬” (not) operator to the clause, thus turning a CNF clause into a DNF clause and vice versa, e. g. ¬(x1 ∧ ¬x2 ∧ x3 ) = (¬x1 ∨ x2 ∨ ¬x3 ). Class Formula consists of a set of clauses. A Formula is either in CNF or DNF. Thus, all of its clauses must also be in the same normal form. Formula(normalForm) Constructs a formula in CNF or DNF having no clauses. addClause(clause) Adds a clause to the formula. If necessary, the clause is ﬁrst transformed into the normal form of the formula. addClauses(string) Creates a clause from a string. For example, the clause (x1 ∧ x2 ) ∨ (¬x3 ) is created with "x1 AND x2 OR NOT x3". This method is intended for testing purposes only. Operators have to be given in capital letters, parenthesis have to be omitted. convertedToCNF() Returns a copy of the formula which has been converted to CNF via the distributive law. evaluateRecord(record) Evaluates a record by evaluating the clauses of the formula for that record. The True/False/Undecided values which may be returned are deﬁned in the static enum Formula.Value. getUsedVariables() Returns a set of all variables having a literal in any of the formula’s clauses. In the following we give some examples that demonstrate the use of some of the classes described so far. All examples are part of the package examples. They can be executed as a simple console application. We start with an example on how to create variables, literals, and clauses. 01: package examples; 02: 6.1 Classes Clause, Formula, Literal, and Variable 45 03: import logic.Clause; 04: import logic.Formula; 05: import logic.Literal; 06: import logic.Variable; 07: 08: public class LogicExample1 { 09: public static void main(String[] args) { 10: 11: Variable x1 = Variable.createOrGet("x1"); 12: Variable x2 = Variable.createOrGet("x2"); 13: Literal l1 = new Literal(true, x1); 14: Literal l2 = new Literal(false, x2); 15: Clause c1 = new Clause(Formula.NormalForm.DNF); 16: c1.addLiteral(l1); 17: c1.addLiteral(l2); 18: c1.addLiteral(new Literal(true, Variable.createOrGet("x3"))); 19: System.out.println("Clause c1 in DNF:"); 20: System.out.println(c1); 21: System.out.println(); 22: System.out.println("Negated copy of c1 in CNF:"); 23: System.out.println(c1.not()); 24: } 25: } The ﬁrst six lines are declarations required in Java. The main method of line 9 is automatically called when executing this code. For details about the Java language, see [2] or search the internet for “java tutorial”. Executing the above example yields: Clause c1 in DNF: x1 AND NOT x2 AND x3 Negated copy of c1 in CNF: NOT x1 OR x2 OR NOT x3 The following example demonstrates the use of class Formula. For brevity, the addClauses method is used. In the implementation of the diﬀerential diagnostic system, we will merely use method addClause. 01: package examples; 02: 03: import logic.Clause; 04: import logic.Formula; 05: import logic.Literal; 06: import logic.Variable; 46 A Framework for Logic Computation 07: import logic.Formula.NormalForm; 08: 09: public class LogicExample2 { 10: public static void main(String[] args) { 11: 12: Formula f = new Formula(NormalForm.DNF); 13: f.addClauses("x1 AND x2 OR NOT x3"); 14: System.out.println("Formula f in DNF, one clause per line:"); 15: System.out.println(f); 16: 17: Clause c = new Clause(Formula.NormalForm.DNF); 18: c.addLiteral(new Literal(true, Variable.createOrGet("x4"))); 19: c.addLiteral(new Literal(true, Variable.createOrGet("x5"))); 20: f.addClause(c); 21: System.out.println("Formula f, with an additional clause:"); 22: System.out.println(f); 23: 24: Formula g = f.convertedToCNF(); 25: System.out.println("Formula g, a copy of f in CNF:"); 26: System.out.println(g); 27: } 28: } This example leads to the following output: Formula f in DNF, one clause per line: x1 AND x2 NOT x3 Formula f, with an additional clause: x1 AND x2 NOT x3 x4 AND x5 Formula g, a copy of f in CNF: x1 OR NOT x3 OR x4 x1 OR NOT x3 OR x5 x2 OR NOT x3 OR x4 x2 OR NOT x3 OR x5 Notice that, e. g. in line 15, the method f.toString is implicitly called. All classes described so far have such a method. The format produced by these methods is useful when writing .log ﬁles used as input for the lbcc compiler. We will make use of the classes described so far in the following sections covering classes Record, LearnedFormula, CNFSystem, and ﬁnally Lbcc. 6.2 Class Record 47 6.2 Class Record A Record is a collection of Record.Values of variables. Possible values are TRUE, FALSE, ABSENT, UNDECIDED and OPEN. According to previous chapters a variable may be regarded as open, but “open” was not deﬁned as a value. However, in order to simplify the implementation, we allow for the value OPEN, too. In case of evaluation of formulas or reduction of CNF systems, an open variable will be interpreted as having value Absent. Since both open variables as well as variables with value Absent do not lead to reduction of CNF systems, the implementation chosen here is in accordance with deﬁnitions of the previous chapters. Since a record may be used in various contexts, a default value for Variables that have not been assigned a value yet must be given when instantiating a Record. Further, a Collection of Variables must be given for which the record stores values. When using the setter methods on variables not yet in this collection, the variables will be incorporated into the collection. Note that there may be many Records storing Values for the same Variable. Record(defaultValue, variables) Constructs a record containing values for a collection of variables. The variables of this collection are initialized to the defaultValue. allVariablesFixed() Returns true if and only if the record contains only True/False/Unavailable values. compareTo(record) Allows for sorting and comparing records. Records are com- pared by creating a String for each record, consisting of name/value pairs, and then lexicographically comparing these Strings. This method is used to count the number of copies of equal records in ﬁles. A meaningful inter- pretation of the ordering induced by this method is not important. countCoveredOpenVars(record) Counts the number of True/False values that this record has for variables that are open in record. countOppositeTrueFalse(record) Counts the number of True/False values that this record has for variables that have the opposite True/False value in record. countCommonTrueFalse(record) Counts the number of True/False values this record has in common with record. covers(variable) Returns true if the record has a True/False value for the variable. coversOpenXjOf(record) Returns true if the record has at least one True/False value for an open variable of record. 48 A Framework for Logic Computation getAllVariables() Returns the collection of variables for which the record con- tains values. getCopyWithOpenValuesFilledBy(record) Creates a copy of the record with values of open variables substituted by values of record. getVariableValuePairs() Creates an Object[][] used by the GUI of the dif- ferential diagnostic system. All other getter and setter methods are self-explanatory, as can be seen in the following example: 01: package examples; 02: 03: import logic.Record; 04: import logic.Variable; 05: 06: public class RecordExample { 07: public static void main(String[] args) { 08: 09: // create variables first 10: Variable.createOrGet("a"); 11: Variable.createOrGet("b"); 12: Variable.createOrGet("c"); 13: Variable.createOrGet("d"); 14: Variable.createOrGet("e"); 15: // define record on these variables 16: Record r = new Record(Record.Value.OPEN, Variable 17: .getAllVariables()); 18: r.setTrue(Variable.getByName("a")); 19: r.setFalse(Variable.getByName("b")); 20: r.setAbsent(Variable.getByName("c")); 21: r.setUnavailable(Variable.getByName("d")); 22: System.out.println("r = " + r); 23: // define another record on these variables 24: Record s = new Record(Record.Value.OPEN, Variable 25: .getAllVariables()); 26: s.setValue(Variable.getByName("a"), Record.Value.TRUE); 27: s.setValue(Variable.getByName("b"), Record.Value.TRUE); 28: s.setValue(Variable.getByName("c"), Record.Value.TRUE); 29: s.setValue(Variable.getByName("d"), Record.Value.TRUE); 30: s.setValue(Variable.getByName("e"), Record.Value.TRUE); 31: System.out.println("s = " + s); 32: // test some properties 33: System.out.println(""); 34: System.out.print("Common True/False values: "); 6.3 Class LearnedFormula 49 35: System.out.println(r.countCommonTrueFalse(s)); 36: System.out.print("Open variables of r covered by s: "); 37: System.out.println(s.countCoveredOpenVars(r)); 38: } 39: } This outputs: r = (a=True ,b=False ,c=Abs. ,d=Unav. ,e=Open ) s = (a=True ,b=True ,c=True ,d=True ,e=True ) Common True/False values: 1 Open variables of r covered by s: 1 6.3 Class LearnedFormula A LearnedFormula is a direct subclass of class Formula extended by several properties. First, a learned formula may evaluate to True on either set A or set B, thus being of type “D” or “E” (according to the notation of section 3.3). Second, a learned formula consists of clauses with either a minimum or a maximum number of literals. Third, it has an index 1 ≤ j ≤ 40. A learned formula is always in DNF, and there is no distinction between optimized and non-optimized formulas. LearnedFormula(index, trueSet, minOrMax) The constructor takes three ar- guments: the index j, the training set ("A" or "B") for whose records the formula evaluates to True, and the type of the formula ("min" or "max"). evaluatesTrueOnSetA() Returns true if the learned formula is of type “D”, i. e. evaluates to True on records of training set A. voteOnRecord(record) Transforms the True/False/Undecided value of the in- herited evaluateRecord(record method to a {1, 0, −1} vote, depending on the type of the learned formula. It is possible to associate record sets HF g and HP g with the formula via getter and setter methods. If these sets are provided, low-cost futile ﬁle F g and low-cost proof ﬁle P g may be derived by corresponding getter methods. We include an example in which a hypothetical formula is created. 01: package examples; 02: 03: import logic.Clause; 50 A Framework for Logic Computation 04: import logic.Formula; 05: import logic.LearnedFormula; 06: import logic.Literal; 07: import logic.Record; 08: import logic.Variable; 09: 10: public class LearnedFormulaExample { 11: public static void main(String[] args) { 12: // set up a hypothetical formula 13: LearnedFormula formula = new LearnedFormula(1, "B", "max"); 14: // create clauses 15: Clause clause1 = new Clause(Formula.NormalForm.DNF); 16: clause1.addLiteral(new Literal(false, Variable 17: .createOrGet("x1"))); 18: clause1.addLiteral(new Literal(true, Variable 19: .createOrGet("x2"))); 20: Clause clause2 = new Clause(Formula.NormalForm.DNF); 21: clause2.addLiteral(new Literal(false, Variable 22: .createOrGet("x3"))); 23: // add clauses to formula 24: formula.addClause(clause1); 25: formula.addClause(clause2); 26: System.out.println(formula); 27: // create a record 28: Record record = new Record(Record.Value.OPEN, 29: Variable.getAllVariables()); 30: record.setTrue(Variable.getByName("x1")); 31: record.setTrue(Variable.getByName("x2")); 32: record.setFalse(Variable.getByName("x3")); 33: // let the formula evaluate the record 34: System.out.println(); 35: System.out.println("record = " + record); 36: System.out.print("on record, fomula evaluates to "); 37: System.out.println(formula.evaluateRecord(record)); 38: System.out.print("on record, fomula votes with "); 39: System.out.println(formula.voteOnRecord(record)); 40: // effect of the Absent value 41: record.setAbsent(Variable.getByName("x3")); 42: System.out.println(); 43: System.out.println("record = " + record); 44: System.out.print("on record, fomula evaluates to "); 45: System.out.println(formula.evaluateRecord(record)); 46: System.out.print("on record, fomula votes with "); 47: System.out.println(formula.voteOnRecord(record)); 48: } 6.4 Class CNFSystem 51 49: } Note in the output, that since the formula is of type “E”, it evaluates to True on records of B. Thus, if evaluating to True, the vote is deﬁned to be −1. LearnedFormula (1), Type E MAX : (NOT x1 AND x2) (NOT x3) record = (x1=True ,x2=True ,x3=False ) on record, fomula evaluates to TRUE on record, fomula votes with -1 record = (x1=True ,x2=True ,x3=Abs. ) on record, fomula evaluates to UNDECIDED on record, fomula votes with 0 6.4 Class CNFSystem In class CNFSystem we make use of many of the above concepts. It provides meth- ods for solving SAT and MINSAT problems, where the CNF system itself is the instance to solve. A CNFSystem has collections of both Clauses and Variables as ﬁelds, thus allowing for the special cases mentioned in section 2.1. Correct insertion of DNF formulas is assured. Given a certain Record, reduction of CNF systems is possible. CNFSystem(variables) The constructor takes a set of variables as arguments. The CNF system has no clauses at the beginning. getReducedCNFSystem(record) This method returns a copy of the CNF system which has been reduced according to the deﬁnitions of section 2.1 using the values of the given record. addClause(clause) Inserts a single DNF clause into the CNF system. It may be used for testing, but is not needed for the diﬀerential diagnostic system. addIfThenClauses(condition, conclusion) For the purposes of the diﬀeren- tial diagnostic system, we need to insert CNF clauses corresponding to the expression d → D, where either the condition d is a literal and D is a for- mula or vice versa. Besides a formula, also a single clause may be inserted. Several methods with corresponding signatures are provided. In case of d being a literal, D has to be reduced according to given Unavailable values before insertion. Therefore, the method with the corresponding signature demands a Record as a third argument. 52 A Framework for Logic Computation fixVariable(variable, trueFalseValue) Fixing a variable xj in the CNF system simply adds another clause (xj ) or (¬xj ) to the system. getWithVarsDeleted(variables) Returns a copy of the CNF system in which all literals of all variables given in the list of variables are removed. isSatisfiable() Checks satisﬁability with use of class Lbcc, i. e. the lbcc com- piler. Special cases of trivial satisﬁability are checked without using Lbcc. isUnsatisfiable() This is a shortcut with the obvious interpretation. solveMINSAT() Returns the objective value of the MINSAT solution solved via class Lbcc. The constant defaultObjValOfUnsatisfiableMINSAT is sub- stituted for the objective value if the CNF system is not satisﬁable. There- fore, calling isSatisfiable ﬁrst is unnecessary. The following example demonstrates how one can set up CNF systems to prove certain conclusions. For a given formula, three records are set up on which the formula evaluates to True/False/Undecided , respectively. Then, for each of these records, a CNF system is set up that includes a variable d that is constrained to take on the same True/False value to which the formula evaluates, or is not constrained if the formula takes on the value Undecided . 001: package examples; 002: 003: import logic.CNFSystem; 004: import logic.Clause; 005: import logic.Formula; 006: import logic.Literal; 007: import logic.Record; 008: import logic.Variable; 009: 010: public class CNFSystemExample { 011: public static void main(String[] args) { 012: 013: /* 014: * set up a hypothetical formula 015: */ 016: Formula formula = new Formula(Formula.NormalForm.DNF); 017: // create clauses 018: Clause clause1 = new Clause(Formula.NormalForm.DNF); 019: clause1.addLiteral(new Literal(true, Variable 020: .createOrGet("x1"))); 021: clause1.addLiteral(new Literal(true, Variable 022: .createOrGet("x2"))); 023: Clause clause2 = new Clause(Formula.NormalForm.DNF); 6.4 Class CNFSystem 53 024: clause2.addLiteral(new Literal(true, Variable 025: .createOrGet("x3"))); 026: Clause clause3 = new Clause(Formula.NormalForm.DNF); 027: clause3.addLiteral(new Literal(true, Variable 028: .createOrGet("x4"))); 029: clause3.addLiteral(new Literal(true, Variable 030: .createOrGet("x5"))); 031: // add clauses to formula 032: formula.addClause(clause1); 033: formula.addClause(clause2); 034: formula.addClause(clause3); 035: System.out.println("formula: "); 036: System.out.println(formula); 037: // create a literal 038: Literal d = new Literal(true, Variable.createOrGet("d")); 039: /* 040: * create record for which the formula evaluates to True 041: */ 042: Record record = new Record(Record.Value.OPEN, Variable 043: .getAllVariables()); 044: record.setTrue(Variable.getByName("x1")); 045: record.setTrue(Variable.getByName("x2")); 046: record.setFalse(Variable.getByName("x3")); 047: record.setTrue(Variable.getByName("x4")); 048: record.setUnavailable(Variable.getByName("x5")); 049: // let the formula evaluate the record 050: System.out.println("record: " + record); 051: System.out.print("on record, formula evaluates to "); 052: System.out.println(formula.evaluateRecord(record)); 053: // set up a CNF system 054: System.out.println("Setting up a new CNF system S."); 055: CNFSystem S = new CNFSystem(Variable.getAllVariables()); 056: System.out.println( 057: "S after inserting implication (d->formula):"); 058: S.addIfThenClauses(d, formula, record); 059: System.out.println(S); 060: System.out.println( 061: "S after inserting implication (formula->d):"); 062: S.addIfThenClauses(formula, d); 063: System.out.println(S); 064: CNFSystem Sprime = S.getReducedCNFSystem(record); 065: System.out.println("CNF system reduced according to record"); 066: System.out.println(Sprime); 067: /* 068: * change record such that the formula evaluates to False 54 A Framework for Logic Computation 069: */ 070: record.setFalse(Variable.getByName("x1")); 071: // let the formula evaluate the record 072: System.out.println("record: " + record); 073: System.out.print("on record, formula evaluates to "); 074: System.out.println(formula.evaluateRecord(record)); 075: // set up a new CNF system 076: System.out.println("Setting up a new CNF system S."); 077: S = new CNFSystem(Variable.getAllVariables()); 078: System.out.println("Inserting implication (d->formula) and"); 079: S.addIfThenClauses(d, formula, record); 080: System.out 081: .println("inserting implication (formula->d) yields"); 082: S.addIfThenClauses(formula, d); 083: System.out.println(S); 084: System.out.println("CNF system reduced according to record"); 085: Sprime = S.getReducedCNFSystem(record); 086: System.out.println(Sprime); 087: /* 088: * change record such that the formula evaluates to 089: * Undecided 090: */ 091: record.setAbsent(Variable.getByName("x3")); 092: // let the formula evaluate the record 093: System.out.println("record: " + record); 094: System.out.print("on record, formula evaluates to "); 095: System.out.println(formula.evaluateRecord(record)); 096: // set up a new CNF system 097: System.out.println("Setting up a new CNF system S."); 098: S = new CNFSystem(Variable.getAllVariables()); 099: System.out.println("Inserting implication (d->formula) and"); 100: S.addIfThenClauses(d, formula, record); 101: System.out 102: .println("inserting implication (formula->d) yields"); 103: S.addIfThenClauses(formula, d); 104: System.out.println(S); 105: System.out.println("CNF system reduced according to record"); 106: Sprime = S.getReducedCNFSystem(record); 107: System.out.println(Sprime); 108: } 109: } In the output, notice that clauses of the DNF formula which are aﬀected by any Unavailable values are not inserted, according to the rule from section 2.2. 6.4 Class CNFSystem 55 formula: x1 AND x2 x3 x4 AND x5 record: (d=Open ,x1=True ,x2=True ,x3=False ,x4=True ,x5=Unav. ) on record, formula evaluates to TRUE Setting up a new CNF system S. S after inserting implication (d->formula): (NOT d OR x1 OR x3) (NOT d OR x2 OR x3) S after inserting implication (formula->d): (NOT d OR x1 OR x3) (NOT d OR x2 OR x3) (NOT x1 OR NOT x2 OR d) (NOT x3 OR d) (NOT x4 OR NOT x5 OR d) CNF system reduced according to record (d) record: (d=Open ,x1=False ,x2=True ,x3=False ,x4=True ,x5=Unav. ) on record, formula evaluates to FALSE Setting up a new CNF system S. Inserting implication (d->formula) and inserting implication (formula->d) yields (NOT d OR x1 OR x3) (NOT d OR x2 OR x3) (NOT x1 OR NOT x2 OR d) (NOT x3 OR d) (NOT x4 OR NOT x5 OR d) CNF system reduced according to record (NOT d) record: (d=Open ,x1=False ,x2=True ,x3=Abs. ,x4=True ,x5=Unav. ) on record, formula evaluates to UNDECIDED Setting up a new CNF system S. Inserting implication (d->formula) and inserting implication (formula->d) yields (NOT d OR x1 OR x3) (NOT d OR x2 OR x3) (NOT x1 OR NOT x2 OR d) (NOT x3 OR d) 56 A Framework for Logic Computation (NOT x4 OR NOT x5 OR d) CNF system reduced according to record (NOT d OR x3) (NOT x3 OR d) A further example of how to use this class is discussed in chapter 7 (p. 59). 6.5 Class Lbcc The framework is based on the ability to connect to the lbcc compiler of the Leib- niz System. The class Lbcc encapsulates the lbcc compiler, using the Java Native Interface (JNI) technology [8]. It provides methods solveSAT and solveMINSAT, which are given a CNF system S as input parameters. Class Lbcc then handles all the work necessary to solve the respective problem for S. First, it sets all nec- essary parameters for the lbcc compiler, including size of memory to allocate, input directory, various ﬁlenames, and writing the leibnizparams.dat ﬁle. Sec- ond, the CNF system S is transformed to the input format of the lbcc compiler and written to a .log ﬁle. Finally, the lbcc compiler is called. The solution can be obtained via methods isSatisfiable and, in case of a MINSAT problem, getObjectiveValue. The usage of this class facilitates the use of the lbcc compiler, since the pro- grammer does not have to deal with the parametrization of the lbcc compiler anymore, which may be subject to pitfalls for the novice. Furthermore, it en- hances readability of the code since checking whether a CNF system is satisﬁable can be done with a single call of a method, as described above. Finally, encapsulating the lbcc compiler has the advantage of decoupling depen- dencies of applications on the current version of the Leibniz System. Suppose a future version of the lbcc compiler uses a diﬀerent interface. The changes made to the interface only need to be reﬂected in the class Lbcc. From the viewpoint of the application, there is no need to change source code since class Lbcc can still keep the same interface. Since the Lbcc class is encapsulated once more in class CNFSystem, it is possible to shift functionality between the CNFSystem to the lbcc compiler without changing other parts of an implementation. Class Lbcc is located in the leibniz package, together with C code of leibniz.c that connects to both the Leibniz System and the Lbcc class via JNI. As a naming convention, all ﬁelds and methods that access or are accessed by corresponding C code through the JNI have the preﬁx lbcc. A shared library is compiled from leibniz.c as described in the makeﬁle in the same package. Technical details on calling procedures of the lbcc compiler can be found in the reference manual 6.5 Class Lbcc 57 of the Leibniz System [11]. A comprehensive introduction to the JNI technology can be found at [8]. For reference, we list public and private methods that are not self explanatory. getLogFileName() A private method returning the .log ﬁle’s name and rela- tive path to the shared library. lbccInitValues() This method transfers all the “lbcc” parameters deﬁned in class Lbcc to the shared library. A change of parameters can be conve- niently handled in the Java code. Recompiling the shared library is thereby rendered unnecessary. lbccSolveProblem() Calls the lbcc compiler, which reads parameters from the .dat ﬁle and the CNF system to be solved from the .log ﬁle. performTest() Solves a test problem and prints detailed information on the solution by calling lbccTest(). writeLeibnizParamsDat() Writes the leibnizparams.dat ﬁle that contains parameters for the lbcc compiler. The parameters of that ﬁle are discussed in detail in [11]. writeLogFile(CNFSystem, boolean) Writes a CNF system in a speciﬁed for- mat to a .log ﬁle, which the lbcc compiler reads as input. Since class Lbcc is used internally by class CNFSystem, we omit an example here. With all these classes at hand we are ﬁnally able to implement the diﬀerential diagnostic system. 58 A Framework for Logic Computation Chapter 7 An Implementation of the Diﬀerential Diagnostic System This implementation of the diﬀerential diagnostic system is deliberately kept similar to the description of the algorithms in section 4.5. Thereby, not only the one-to-one correspondence of concepts and classes becomes clear, but also the alikeness of verbal descriptions and implementation of the algorithms. The diﬀerential diagnostic system is implemented in the package diffDiagSys. Despite some auxiliary classes that provide the graphical user interface, the classes Diagnosis, Goal, LowCostAssignmentFile, and Test implement further con- cepts of the previous chapters. Instead of describing these classes now, we would ﬁrst like to delve into the implementation of a core method of the diﬀerential diagnostic system, namely the step of eliminating diagnoses (section 4.5.1). The following code, taken from class DifferentialDiagnosticSystem makes use of classes and variables that have not yet been introduced. However, the reader will have no problem to follow the steps assumed the elimination step has been understood. 01: private void eliminateImpossibleDiagnoses(Record record) { 02: 03: // If there are no OPEN variables, we are done. 04: if (record.noOpenVariables()) { 05: // Declare pStar to be the only, i.e. final diagnosis. 06: this.possibleDiagnoses = new TreeSet<Diagnosis>(); 07: this.possibleDiagnoses.add(pStar); 08: return; 09: } 10: myGui.out("Current diagnosis: " + pStar); 11: // Compute diagnoses that can be eliminated. 12: Set<Diagnosis> toBeEliminated = new HashSet<Diagnosis>(); 59 60 An Implementation of the Diﬀerential Diagnostic System 13: for (Diagnosis p : this.possibleDiagnoses) { 14: if (p == pStar) 15: continue; 16: myGui.out("\nCheck whether diagnosis " + p 17: + " can be eliminated."); 18: CNFSystem S = new CNFSystem(Diagnosis 19: .getSymptomVariables()); 20: /* 21: * Create MINSAT instance. Simplification of formulas 22: * is done by following the insertion rule (handled in 23: * CNFSystem) and by reducing S after insertion of the 24: * formulas 25: */ 26: int i = 0; 27: for (LearnedFormula formula : p.getHp()) { 28: i = formula.getIndex(); 29: if (formula.isTypeD()) { 30: // add (prop -> D) 31: S.addIfThenClauses(new Literal(true, 32: Variable.createOrGet("prop" + i, -1, 1)), 33: formula, record); 34: } else { 35: // add (E -> conp) 36: S.addIfThenClauses(formula, new Literal(true, 37: Variable.createOrGet("conp" + i, 1, -1))); 38: } 39: } 40: for (LearnedFormula formula : pStar.getHp()) { 41: i = formula.getIndex(); 42: if (formula.isTypeD()) { 43: // add (D* -> prop*) 44: S.addIfThenClauses(formula, new Literal(true, 45: Variable.createOrGet("propStar" + i, 1, -1))); 46: } else { 47: // add (conp* -> E*) 48: S.addIfThenClauses(new Literal(true, 49: Variable.createOrGet("conpStar" + i, -1, 1)), 50: formula, record); 51: } 52: } 53: S = S.getReducedCNFSystem(record); 54: /* 55: * Solve MINSAT instance. S is satisfiable by 61 56: * construction. 57: */ 58: int zdiff = S.solveMINSAT(); 59: zdiff += pStar.getDecisionStrategyValue() 60: - p.getDecisionStrategyValue(); 61: myGui.out("Result: zdiff=" + zdiff); 62: if ((zdiff > 0) 63: || ((zdiff == 0) && (pStar.winsTieBreakOver(p)))) { 64: myGui.out("Eliminate " + p.getId()); 65: toBeEliminated.add(p); 66: } else { 67: myGui.out(p.getId() + " cannot be eliminated."); 68: } 69: } 70: // Eliminate impossible diagnoses for the case at hand. 71: this.possibleDiagnoses.removeAll(toBeEliminated); 72: } At ﬁrst it is checked whether all values of the current record are ﬁxed to True/False/Unavailable (line 4). If this is the case, the current tentative di- agnosis pStar becomes the ﬁnal diagnosis. The set P of possible diagnoses, here denoted by possibleDiagnoses, is set to P = {p∗ }, and the method returns to the main program. For the beneﬁt of the user, messages are shown on the graph- ical user interface via myGui.out, e. g. on line 10. In the following, we ignore these messages. Otherwise, a set of diagnoses that will be eliminated is created (line 12). Then, for every diagnosis p = p∗ (lines 13-15), a CNF system S is created (line 18). For ∗ each formula of Hp (lines 27-39) and Hp (lines 40-52), implications are inserted into S as described in section 4.5.1. Correct insertion of the formulas is handled by methods addIfThenClauses where needed. Notice that diﬀerent signatures for method addIfThenClauses are used, one to insert implications of type d → D (lines 31-33 and 48-50), and another one to insert implications of type D → d (lines 36-37 and 44-45). The former signature demands the current record to be given such that the CNFSystem object knows which clauses have to be deleted due to Unavailable values. Finally, the whole CNF system is reduced according to the values of the cur- rent record (line 53). The MINSAT problem deﬁned by S is solved and the objective value saved in zdiff (line 58). After adding the diﬀerence between the two decision strategy values of p and p∗ to zdiff (lines 59-60), it is decided whether p can be eliminated or not (lines 62-68). Finally, all diagnoses in the set toBeEliminated are actually deleted from the possibleDiagnoses (line 71). In the following, we do not discuss every method in detail. All methods that 62 An Implementation of the Diﬀerential Diagnostic System implement procedures of the previous chapters have been commented carefully. Therefore, we only give an overview of the structure of the implementation. For further details about the classes, please consult the corresponding part of the appendix. 7.1 Package diﬀDiagSys First, we go through classes that represent concepts of previous chapters, i. e. classes Diagnosis, Goal, LowCostAssignmentFile, and Test. After that, we turn to DifferentialDiagnosticSystem, which contains the algorithms of the diﬀerential diagnostic system. We conclude with a brief overview of classes deal- ing with the graphical user interface. Class Diagnosis contains any information related to a diagnosis p, i. e. a set of optimized formulas Hp , optimized and non-optimized record sets Ap , A∗p , B p , and B ∗p , a decision strategy value dp , a prior probability PAp , and an identiﬁer (id). The class is able to automatically read in almost all these values from ﬁles that can be created with the Leibniz System. Exceptions are the values for dp and PAp , which for the purposes of this implementation of the diﬀerential diagnostic system are set to reasonable dummy values. To the knowledge of the author, there is no software available yet that computes a decision strategy as described in section 4.3.3. We therefore simply substitute dp = 0 until a suitable software is available. To read in the available data, class Diagnosis uses several classes that can parse the input ﬁles. These classes are located in the package fileParser, which will not be discussed here. They are, however, listed in the appendix. Diagnosis(id) The constructor of this class asks for an id, which is then used to ﬁnd the necessary input ﬁles. The directory structure is deﬁned as follows. On the same level as package diffDiagSys, there is a data directory con- taining the ﬁle data/<id>/dataoptcc.trn of training records Ap and B p . The optimized record set is located at data/<id>/dataoptcc.opt. The formulas of set Hp are found in ﬁles data/<id>/AStar/datalsqcc.sep, containing the formulas that evaluate to True on records of set Ap , and data/<id>/BStar/datalsqcc.sep, containing the formulas that evaluate to True on the records of set B p . This directory structure is useful when the Leibniz System is used to create optimized records and formulas as described in chapter 5. initializeSetsHPgandHFg() This method has to be called before the sets HP g and HF g are accessed. winsTieBreakOver(Diagnosis) Here, the tie breaker T from section 4.3 is im- plemented. 7.1 Package diﬀDiagSys 63 We omit obvious getter and setter methods as well as private methods that are merely used internally. Class Goal consists of a variable and of low cost assignment ﬁles Fg and Pg . A goal is in one of the following states: FUTILE, IMPOSSIBLE, PROVED, or UNDECIDED. The class merely consists of trivial getters and setters. Class LowCostAssignmentFile is constructed from a list of records. If two or more records of this list share the same values for all variables, only a represen- tative record is kept along with the number of equal records in the list. Class Test stores the name, cost, and obtainable values of a test Ti . Test(name, cost, variables) Constructs a test which can obtain values for a Collection of variables. Test.getAllTests() A static method that returns all tests constructed so far. Test.reduceAllTestsByAssignedValues(record) If values have already been obtained by a test, they may be deleted from the set of obtainable val- ues of other sets. This method is used in order to correctly compute the eﬀectiveness-score when selecting the next test. obtainValues(myGui, record) This method implements Algorithm Obtain Values of section 4.5.2.3. It creates a dialog where the user is asked to supply test values. For technical reasons, the main application myGui has to be given. Finally, we describe class DifferentialDiagnosticSystem. It contains the main method which is called at program start by default. This method creates a single instance of class DifferentialDiagnosticSystem. DifferentialDiagnosticSystem() The constructor is called without any pa- rameters. It creates instances for all diagnoses of the diﬀerential diagnosis. All tests are instantiated as well. run() This method starts the main window of the application, which then reacts to actions performed by the user. Whenever the user clicks a button to perform the next step of the algorithm, method performNextStep is called. performNextStep() Delegates program control to the next procedure according to Algorithm Differential Diagnostic System and the current state of computation. eliminateImpossibleDiagnoses(record) This method has already been de- scribed at the beginning of this chapter. 64 An Implementation of the Diﬀerential Diagnostic System getAdditionalSymptomValues(record) Implements the procedure of section 4.5.2. getInitialRecord() Creates a dialog in which the user is asked for initial symp- tom values. getTentativeDiagnosis(record) Evaluates all formulas and decides on a ten- tative diagnosis as described in step 2 of Algorithm Differential Diag- nostic System. isFinalDiagnosis() Checks whether the set P of possible diseases has a cardi- nality of one. proveConclusions(S, w, G, H) Implements Algorithm Prove Conclusions of section 4.5.2.1 with CNF system S, record w, goal set G, and result set H. selectTest(tests, S, w, G) This method selects a cost-eﬀective test from a given set of tests with regard to a CNF system S, a record w, and a goal set G. It implements Algorithm Select Test of section 4.5.2.2. The classes Gui, and GuiDialogObtainValues implement methods for the main and dialog windows, respectively. Class Gui is actually in control of the program ﬂow once it is called by class DifferentialDiagnosticSystem(). It associates certain actionListeners with elements of the graphical user interface, which call methods according to the actions of the user. We do not go into the details of these classes and refer the reader to the appendix and [2]. Instead, we would like to ﬁnish this thesis by showing screenshots of an example run of our diﬀerential diagnostic system. 7.2 An Example Run For the demonstration to follow we created a small test instance comprising of ﬁctitious patient records for which three diﬀerent diagnoses, in the following denoted by x, y, and z, are possible. The records follow a pattern suggested in [10, exercise 8.9.2, p. 228]. With help of the Leibniz System, optimized records and formulas were derived as described in chapter 5. The program starts with a main screen as shown in ﬁgure 7.1. On the top right, the steps of Algorithm Differential Diagnostic System (section 4.5) are summarized and the current step is highlighted. On the left, some tabs list information about the current record, formulas, and tests. Click on button “1. Get Record” to start with the ﬁrst step. As seen in ﬁgure 7.2, a dialog appears asking the user for initial values. We do so by arbitrarily selecting value True for x1 , x2 , and x3 while leaving the other 7.2 An Example Run 65 variables open. For these values, a tentative diagnosis is made. According to the vote total, the tentative diagnosis is x, see ﬁgure 7.3. Notice that on the left, the “Formulas” tab was selected. For each diagnosis p, the set of formulas Hp along with the vote total is listed. In order to keep the example concise, only four formulas were chosen to be computed per disease. Of course, this implementation also works with 40 formulas per diagnosis. The next step to be performed is to eliminate diagnoses which are impossible for the current record. In ﬁgure 7.4, the details of this step are shown. Diagnosis y can already be eliminated since z diﬀ > 0, as described in section 4.5.1. However, diagnosis z has z diﬀ = 0. It cannot be eliminated, because tie breaker T prefers z over x because z has the largest “index”. Note that on the left, only the formulas of the not yet eliminated diagnoses are shown. In the following step, it is checked whether there is only one possible diagnosis left, see ﬁgure 7.5. Since this is not the case, we need to get additional symptom values. In ﬁgure 7.6, the results of Algorithm Prove Conclusions from section 4.5.2.1 and of Algorithm Select Test from section 4.5.2.2 are displayed. Notice that goal “dx_01” is proved, although the corresponding formula number 1 of x evaluates to undecided, as shown on the left. Notice further, on the right, that “Test_3” got the highest rating and is thus selected to be the next test to be performed. Arbitrarily, we select the value True to get to the next step. The algorithms starts another iteration by making a new tentative diagnosis. Again, it is x as shown in ﬁgure 7.7. In the following elimination step, it is possible to eliminate diagnosis z for the values at hand, as seen in ﬁgure 7.8. Hence, x is the ﬁnal diagnosis and the algorithms stops, see ﬁgure 7.9. 66 An Implementation of the Diﬀerential Diagnostic System Figure 7.1: The program starts. Figure 7.2: Entering initial values. 7.2 An Example Run 67 Figure 7.3: Deciding upon a tentative diagnosis. Figure 7.4: Eliminating diagnosis y. 68 An Implementation of the Diﬀerential Diagnostic System Figure 7.5: Checking whether the tentative diagnosis is the ﬁnal one. Figure 7.6: Getting additional symptom values. 7.2 An Example Run 69 Figure 7.7: Starting another iteration. Figure 7.8: Eliminating diagnosis z. 70 An Implementation of the Diﬀerential Diagnostic System Figure 7.9: Concluding on the ﬁnal diagnosis x. 7.3 Summary and Outlook 71 7.3 Summary and Outlook This is the ﬁrst implementation of a diﬀerential diagnostic system as proposed by Truemper [10]. It is one of two major components used for construction of diﬀer- ential diagnostic systems. The ﬁrst component has already been implemented in the Leibniz System, except for the parts concerning the computation of a decision strategy. With this thesis, a further step has been taken to reach the aim of an automated construction of diﬀerential diagnostic systems. Both the implementation and the user interface are designed for clarity. There is still room for improvement concerning execution speed. For example, it is not necessary to prove conclusions over and over again in each iteration, provided that no unexpected Unavailable value occurs. However, it is the hope of the author that this work will be further developed. Parts of the implementation of the diﬀerential diagnostic system are of general use when implementing logic-based intelligent systems. All source code will be available at no charge over the internet and may be used and modiﬁed as desired.