import java.io.*; import java.util.*; public class GraphSolve { public static BufferedReader graphsFile; public static FileReader graphsFr; public static BufferedWriter satInputFile; public static FileWriter satInputFw; public static BufferedReader satOutputFile; public static FileReader satOutputFw; public static BufferedWriter outputMappingFile; public static FileWriter outputMappinngFw; public static int numSatVars = 0; public static long numClauses = 0; public static String newLine = System.getProperty("line.separator"); public static class CNFClauseGenerator { private int sizeBigGraph; private int sizeSmallGraph; private int numSatVars = 0; private long numClauses = 0; private long numVarsInCurrentClause = 0; private BufferedWriter satInputFile; //public static StringBuilder satString; public CNFClauseGenerator(int sizeBigGraph, int sizeSmallGraph, BufferedWriter satInputFile) { this.sizeBigGraph = sizeBigGraph; this.sizeSmallGraph = sizeSmallGraph; this.numSatVars = this.sizeBigGraph * this.sizeSmallGraph; this.numClauses = 1000; //satString = new StringBuilder(1024 * 1024 * 32); this.satInputFile = satInputFile; try { satInputFile.write("c auto generated file" + newLine); satInputFile.write("p cnf " + numSatVars + " " + numClauses + GraphSolve.newLine); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // add condition public void addConditionInExistingClause(int smallGraphIndex, int bigGraphIndex, boolean varState) throws IOException { int varNum = generateVarNum(smallGraphIndex, bigGraphIndex); if (varNum > numSatVars) { numSatVars = varNum; } if (!varState) { varNum = -1 * varNum; } //satString.append(varNum); //satString.append(' '); this.satInputFile.write(Integer.toString(varNum)); this.satInputFile.write(' '); numVarsInCurrentClause++; } public void addConditionInNewClause(int smallGraphIndex, int bigGraphIndex, boolean varState) throws IOException { addNewClause(); this.addConditionInExistingClause(smallGraphIndex, bigGraphIndex, varState); } public void addNewClause() throws IOException { // append 0 to mark end of clause if (numVarsInCurrentClause > 0) { //satString.append("0"); //satString.append(GraphSolve.newLine); this.satInputFile.write('0'); this.satInputFile.newLine(); numClauses++; numVarsInCurrentClause = 0; } } public int generateVarNum(int smallGraphIndex, int bigGraphIndex) { return sizeBigGraph * (smallGraphIndex - 1) + bigGraphIndex; } public void writeOutSatFile() throws IOException { System.out.println("NumVars,Clauses: " + numSatVars + "," + numClauses); this.addNewClause(); satInputFile.close(); } } public static class Node implements Comparable<Node> { int id; int sortedId; LinkedList<Node> edges; SortedSet<Node> qEdges; LinkedList<Node> notEdges; LinkedList<Node> candidatesFromBigGraph; LinkedList<Node> notCandidatesFromBigGraph; int outGoingEdges = 0; int incomingEdges = 0; public Node(int idIn) { this.id = idIn; this.sortedId = idIn; this.edges = new LinkedList<GraphSolve.Node>(); this.notEdges = new LinkedList<GraphSolve.Node>(); this.qEdges = new TreeSet<GraphSolve.Node>(); this.candidatesFromBigGraph = new LinkedList<GraphSolve.Node>(); this.notCandidatesFromBigGraph = new LinkedList<GraphSolve.Node>(); } @Override public int compareTo(Node n) { if (this.id < n.id) { return -1; } else if (this.id > n.id) { return 1; } else { return 0; } } @Override public boolean equals (Object other) { Node otherNode = (Node) other; return this.id == otherNode.id; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("ID:"); sb.append(id + "(" + this.sortedId + ")"); sb.append(" E:"); Iterator<Node> it = edges.iterator(); while (it.hasNext()) { Node currNode = it.next(); sb.append(currNode.sortedId); if (it.hasNext()) { sb.append(", "); } } sb.append(" NE:"); it = notEdges.iterator(); while (it.hasNext()) { Node currNode = it.next(); sb.append(currNode.sortedId); if (it.hasNext()) { sb.append(", "); } } sb.append(" P(x):"); it = this.candidatesFromBigGraph.iterator(); while (it.hasNext()) { Node currNode = it.next(); sb.append(currNode.sortedId); if (it.hasNext()) { sb.append(","); } } sb.append(" (" + this.outGoingEdges + "," + this.incomingEdges + ")"); return sb.toString(); } } public static SortedMap<Integer, Node> bigGraph; public static SortedMap<Integer, Node> smallGraph; public static boolean isDebug = false; /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { if (args.length < 3 || args.length > 4) { // print usage printUsage(); } else { if (args[0].equalsIgnoreCase("solve")) { if (args.length != 3) { System.out.println("Need 2 arguments for solve"); printUsage(); } else { generateNodes(args[1]); generateSatVariables(args[2]); } } else if (args[0].equalsIgnoreCase("outputMapping")) { if (args.length != 4) { System.out.println("Need 4 arguments for outputMapping"); printUsage(); } else { generateNodes(args[1]); readSatResult(args[2], args[3]); } } else { System.out.println("Can only have solve or outputMapping"); printUsage(); } } } private static void generateNodes(String graphsFileName) throws FileNotFoundException, IOException, Exception { graphsFr = new FileReader(graphsFileName); graphsFile = new BufferedReader(graphsFr); String line = graphsFile.readLine(); bigGraph = new TreeMap<Integer, GraphSolve.Node>(); smallGraph = new TreeMap<Integer, GraphSolve.Node>(); SortedMap<Integer, Node> currentGraph = bigGraph; while (line != null) { String splits[] = line.split(" "); if (line.trim().length() > 0 && splits.length == 2) { int node1 = Integer.parseInt(splits[0]); int node2 = Integer.parseInt(splits[1]); Node n1; Node n2; if (node1 == 0 && node2 == 0) { currentGraph = smallGraph; line = graphsFile.readLine(); continue; } if (currentGraph.containsKey(node2)) { n2 = currentGraph.get(node2); } else { n2 = new Node(node2); currentGraph.put(node2, n2); } if (currentGraph.containsKey(node1)) { n1 = currentGraph.get(node1); } else { n1 = new Node(node1); currentGraph.put(node1, n1); } if (n1.edges!=null && n1.edges.contains(n2)) { //throw new Exception ("there is already a link from " + n1.id + " to " + n2.id); } else { n1.edges.add(n2); n1.qEdges.add(n2); n1.outGoingEdges++; n2.incomingEdges++; } } else { throw new Exception("bad input in graphs file: " + line); } line = graphsFile.readLine(); } updatedSortedIds(bigGraph); updateSortedIdsForNoConnects(smallGraph); generatePossibleMappings(); printNodes(bigGraph); printNodes(smallGraph); } private static void generatePossibleMappings() { for (Node i: smallGraph.values()) { for (Node j: bigGraph.values()) { if (j.incomingEdges >= i.incomingEdges && j.outGoingEdges >= i.outGoingEdges) { i.candidatesFromBigGraph.add(j); } else { i.notCandidatesFromBigGraph.add(j); //i.candidatesFromBigGraph.add(j); } } } } private static void generateSatVariables(String satFileName) throws Exception { HashSet<Integer> definitelyFalseMappings = new HashSet<Integer>(smallGraph.size() * bigGraph.size() / 5); int numEstablished=0; try { satInputFw = new FileWriter(satFileName, false /*append*/); } catch (IOException e) { System.out.println("unable to create sat file:" + satFileName); e.printStackTrace(); return; } satInputFile = new BufferedWriter(satInputFw); CNFClauseGenerator cg = new CNFClauseGenerator(bigGraph.size(), smallGraph.size(), GraphSolve.satInputFile); // generate initial list of variables for (int i = 1; i <= smallGraph.size(); i++) { for (int j = 1; j <= bigGraph.size(); j++) { cg.addConditionInExistingClause(i, j, true); } cg.addNewClause(); } // add restriction that a node from the small graph will map to the big graph for (int i = 1; i <= smallGraph.size(); i++) { for (int j = 1; j <= bigGraph.size(); j++) { for (int k = 1; k <= bigGraph.size(); k++) { if (k != j) { cg.addNewClause(); cg.addConditionInExistingClause(i, j, false); cg.addConditionInExistingClause(i, k, false); } } } } // add restriction that no two nodes from the small graph can map to the same // node in the bigger graph for (int i = 1; i <= smallGraph.size(); i++) { for (int j = 1; j <= bigGraph.size(); j++) { for (int k = 1; k <= smallGraph.size(); k++) { if (k != i) { cg.addNewClause(); cg.addConditionInExistingClause(i, j, false); cg.addConditionInExistingClause(k, j, false); } } } } for (Node i: smallGraph.values()) { if (i.candidatesFromBigGraph.size() == 1) { numEstablished++; cg.addConditionInNewClause(i.sortedId, i.candidatesFromBigGraph.element().sortedId, true); } // go through the list of nodes that you possibly can't map to and specify them for (Node jNot: i.notCandidatesFromBigGraph) { cg.addNewClause(); numEstablished++; cg.addConditionInExistingClause(i.sortedId, jNot.sortedId, false); int varNum = cg.generateVarNum(i.sortedId, jNot.sortedId); definitelyFalseMappings.add(new Integer(varNum)); } } // generate edge mappings for (Node i: smallGraph.values()) { if (i.candidatesFromBigGraph.size() < 1) { // assume that we'll always have a proper subgraph to work with // this below should not happen System.out.println ("** The smaller graph is not a subset of the larger graph"); } for (Node j: i.candidatesFromBigGraph) { // for each edge for (Node iEdgeNode : i.edges) { cg.addNewClause(); cg.addConditionInExistingClause(i.sortedId, j.sortedId, false); for (Node jEdgeNode: j.edges) { int varNum = cg.generateVarNum(iEdgeNode.sortedId, jEdgeNode.sortedId); // there is no value for adding a clause to an OR condition that we already know // is false. A | B | false | D, is really A | B | D // this will help shorten the clauses if (!definitelyFalseMappings.contains(new Integer(varNum))); { cg.addConditionInExistingClause(iEdgeNode.sortedId, jEdgeNode.sortedId, true); } } } // for each non-edge for (Node iNonEdge : i.notEdges) { for (Node jEdgeNode: j.edges) { int varNum = cg.generateVarNum(iNonEdge.sortedId, jEdgeNode.sortedId); // same situation, no value in adding a clause that doesn't give us any more information // i.e. A | True = true // it isn't affected by the value of A and doesn't help solve the equations if (!definitelyFalseMappings.contains(new Integer(varNum))) { cg.addNewClause(); cg.addConditionInExistingClause(i.sortedId, j.sortedId, false); cg.addConditionInExistingClause(iNonEdge.sortedId, jEdgeNode.sortedId, false); } } } } } for (Node i: smallGraph.values()) { if (i.edges.size() + i.notEdges.size() != smallGraph.size() - 1) { System.out.println("***" + i); } } System.out.println("Found fix vars: " + numEstablished); // generate conditions for non-edges cg.writeOutSatFile(); } private static void printNodes(SortedMap<Integer, Node> graph) { if (isDebug) { System.out.println("*Graph*"); for (Node n : graph.values()) { System.out.println(n.toString()); } System.out.println("*EndGraph*"); } } private static void updatedSortedIds(SortedMap<Integer, Node> graph) { int count = 1; for (Node n : graph.values()) { n.sortedId = count; count++; } } private static void updateSortedIdsForNoConnects(SortedMap<Integer, Node> graph) { int count = 1; int graphSize = graph.size(); for (Node n : graph.values()) { n.sortedId = count; count++; } count--; Node[] tempNodeArray = graph.values().toArray(new Node[graphSize]); for (Node n : graph.values()) { Iterator <Node> it = n.qEdges.iterator(); int lastNodeProcessed = 0; Node currNode; while (it.hasNext()) { currNode = it.next(); // fill up the "non existent edges" for (int i = lastNodeProcessed; i < currNode.sortedId - 1; i++) { if (tempNodeArray[i].sortedId != n.sortedId) { n.notEdges.add(tempNodeArray[i]); } } lastNodeProcessed = currNode.sortedId; } // fill up the "non existent edges" for any last rows for (int i = lastNodeProcessed; i < count; i++) { if (tempNodeArray[i].sortedId != n.sortedId) { n.notEdges.add(tempNodeArray[i]); } } } } private static void readSatResult(String satResultPath, String outputMappingFilePath) throws Exception { try { satOutputFw = new FileReader(satResultPath); satOutputFile = new BufferedReader(satOutputFw); outputMappinngFw = new FileWriter(outputMappingFilePath, false /*append*/); outputMappingFile = new BufferedWriter(outputMappinngFw); String line = satOutputFile.readLine().trim(); SortedSet<Integer> validAnswers = new TreeSet<Integer>(); if (line.equalsIgnoreCase("SAT")) { String[] splits = satOutputFile.readLine().trim().split(" "); if (Integer.parseInt(splits[splits.length-1]) == 0) { for (int i=0; i < splits.length-1; i++) { int varNum = Integer.parseInt(splits[i]); if (varNum > 0) { validAnswers.add(varNum); } } Iterator<Integer> smallIterator = smallGraph.keySet().iterator(); for (int i=1; i <= smallGraph.size(); i++) { Node small = smallGraph.get(smallIterator.next()); Iterator<Integer> bigIterator = bigGraph.keySet().iterator(); for (int j=1; j <= bigGraph.size(); j++) { Node big = bigGraph.get(bigIterator.next()); if (validAnswers.contains(bigGraph.size() * (i-1) + j)) { outputMappingFile.write(small.id + " " + big.id + newLine); } } } } else { throw new Exception("sat output file has invalid format. doesn't end in 0"); } } else { outputMappingFile.write("0"); } outputMappingFile.close(); } catch (FileNotFoundException e) { System.out.println("Unable to open sat output file"); e.printStackTrace(); } catch (IOException e) { System.out.println("Unable to read sat output file line"); e.printStackTrace(); } } private static void printUsage() { System.out.println("Please enter three arguments\n" + "java GraphSolve solve foo.graphs foo.satinput\n" + "java GraphSolve outputMapping foo.graphs foo.satoutput foo.mapping"); } }