/**
*
*/
package edu.isi.karma.modeling.research.graphmatching.algorithms;
import java.io.FileInputStream;
import java.util.Properties;
import edu.isi.karma.modeling.research.graphmatching.util.CostFunction;
import edu.isi.karma.modeling.research.graphmatching.util.EditDistance;
import edu.isi.karma.modeling.research.graphmatching.util.Graph;
import edu.isi.karma.modeling.research.graphmatching.util.GraphSet;
import edu.isi.karma.modeling.research.graphmatching.util.MatrixGenerator;
import edu.isi.karma.modeling.research.graphmatching.util.ResultPrinter;
import edu.isi.karma.modeling.research.graphmatching.xml.XMLParser;
/**
* @author riesen
*
*/
public class GraphMatching {
/**
* the sets of graphs to be matched
*/
private GraphSet source, target;
/**
* the resulting distance matrix D = (d_i,j), where d_i,j = d(g_i,g_j)
* (distances between all graphs g_i from source and all graphs g_j from target)
*/
private double[][] distanceMatrix;
/**
* the source and target graph actually to be matched (temp ist for temporarily swappings)
*/
private Graph sourceGraph, targetGraph, temp;
/**
* whether the edges of the graphs are undirected (=1) or directed (=0)
*/
private int undirected;
/**
* progess-counter
*/
private int counter;
/**
* log options:
* output the individual graphs
* output the cost matrix for bipartite graph matching
* output the matching between the nodes based on the cost matrix (considering the local substructures only)
* output the edit path between the graphs
*/
private int outputGraphs;
private int outputCostMatrix;
private int outputMatching;
private int outputEditpath;
/**
* the cost function to be applied
*/
private CostFunction costFunction;
/**
* number of rows and columns in the distance matrix
* (i.e. number of source and target graphs)
*/
private int r;
private int c;
/**
* computes an optimal bipartite matching of local graph structures
*/
private BipartiteMatching bipartiteMatching;
/**
* computes the approximated or exact graph edit distance
*/
private EditDistance editDistance;
/**
* the matching procedure defined via GUI or properties file
* possible choices are 'Hungarian', 'VJ' (VolgenantJonker)
* 'AStar' (exact tree search) or 'Beam' (approximation based on tree-search)
*/
private String matching;
/**
* the maximum number of open paths (used for beam-search)
*/
private int s;
/**
* generates the cost matrix whereon the optimal bipartite matching can
* be computed
*/
private MatrixGenerator matrixGenerator;
/**
* whether or not a similarity kernel is built upon the distance values:
* 0 = distance matrix is generated: D = (d_i,j), where d_i,j = d(g_i,g_j)
* 1 = -(d_i,j)^2
* 2 = -d_i,j
* 3 = tanh(-d)
* 4 = exp(-d)
*/
private int simKernel;
/**
* prints the results
*/
private ResultPrinter resultPrinter;
public double getDistance() {
System.out.println( distanceMatrix[0][0]);
return distanceMatrix[0][0] * 2;
}
/**
* the matching procedure
* @throws Exception
*/
public GraphMatching(String graph1, String graph2) throws Exception {
// initialize the matching
System.out.println("Initializing the matching according to the properties...");
System.out.println(graph1);
System.out.println(graph2);
this.init(graph1, graph2);
// the cost matrix used for bipartite matchings
double[][] costMatrix;
// counts the progress
this.counter = 0;
// iterate through all pairs of graphs g_i x g_j from (source, target)
System.out.println("Starting the matching...");
System.out.println("Progress...");
int numOfMatchings = this.source.size() * this.target.size();
// distance value d
double d = -1;
// swapped the graphs?
boolean swapped = false;
for (int i = 0; i < r; i++) {
sourceGraph = this.source.get(i);
for (int j = 0; j < c; j++) {
swapped = false;
targetGraph = this.target.get(j);
this.counter++;
System.out.println("Matching "+counter+" of "+numOfMatchings);
// log the current graphs on the console
if (this.outputGraphs == 1) {
System.out.println("The Source Graph:");
System.out.println(sourceGraph);
System.out.println("\n\nThe Target Graph:");
System.out.println(targetGraph);
}
// if both graphs are empty the distance is zero and no computations have to be carried out!
if (this.sourceGraph.size()<1 && this.targetGraph.size()<1){
d = 0;
} else {
// calculate the approximated or exact edit-distance using tree search algorithms
// AStar: number of open paths during search is unlimited (s=infty)
// Beam: number of open paths during search is limited to s
if (this.matching.equals("AStar") || this.matching.equals("Beam")){
d = this.editDistance.getEditDistance(
sourceGraph, targetGraph, costFunction, this.s);
} else { // approximation of graph edit distances via bipartite matching
// in order to get determinant edit costs between two graphs
if (this.sourceGraph.size()<this.targetGraph.size()){
this.swapGraphs();
swapped= true;
}
// generate the cost-matrix between the local substructures of the source and target graphs
costMatrix = this.matrixGenerator.getMatrix(sourceGraph, targetGraph);
// compute the matching using Hungarian or VolgenantJonker (defined in String matching)
int[][] matching = this.bipartiteMatching.getMatching(costMatrix);
// calculate the approximated edit-distance according to the bipartite matching
d = this.editDistance.getEditDistance(
sourceGraph, targetGraph, matching, costFunction);
}
}
// whether distances or similarities are computed
if (this.simKernel < 1){
this.distanceMatrix[i][j] = d;
} else {
switch (this.simKernel){
case 1:
this.distanceMatrix[i][j] = -Math.pow(d,2.0);break;
case 2:
this.distanceMatrix[i][j] = -d;break;
case 3:
this.distanceMatrix[i][j] = Math.tanh(-d);break;
case 4:
this.distanceMatrix[i][j] = Math.exp(-d);break;
}
}
if (swapped){
this.swapGraphs();
}
}
}
System.out.println("Finished...");
}
/**
* swap the source and target graph
*/
private void swapGraphs() {
this.temp = this.sourceGraph;
this.sourceGraph = this.targetGraph;
this.targetGraph = this.temp;
}
private void init(String graph1, String graph2) throws Exception {
// the node and edge costs, the relative weighting factor alpha
double node = 1.0;
double edge = 1.0;
double alpha = 0.5;
// the node and edge attributes (the names, the individual cost functions, the weighting factors)
int numOfNodeAttr = 1;
int numOfEdgeAttr = 1;
String[] nodeAttributes = new String[numOfNodeAttr];
String[] edgeAttributes = new String[numOfEdgeAttr];
String[] nodeCostTypes = new String[numOfNodeAttr];
String[] edgeCostTypes = new String[numOfEdgeAttr];
double[] nodeAttrImportance = new double[numOfNodeAttr];
double[] edgeAttrImportance = new double[numOfEdgeAttr];
double[] nodeCostMu = new double[numOfNodeAttr];
double[] nodeCostNu = new double[numOfNodeAttr];
nodeAttributes[0] = "label";
nodeCostTypes[0] = "discrete";
nodeCostMu[0] = 0.0;
nodeCostNu[0] = 1.0;
nodeAttrImportance[0] = 1.0;
edgeAttributes[0] = "label";
edgeCostTypes[0] = "equality";
edgeAttrImportance[0] = 1.0;
// whether or not the costs are "p-rooted"
double squareRootNodeCosts = 1.0;
double squareRootEdgeCosts = 1.0;
// whether costs are multiplied or summed
int multiplyNodeCosts = 0;
int multiplyEdgeCosts = 0;
// what is logged on the console (graphs, cost-matrix, matching, edit path)
this.outputGraphs = 0;
this.outputCostMatrix = 0;
this.outputMatching = 0;
this.outputEditpath = 0;
// whether the edges of the graphs are directed or undirected
this.undirected = 0;
// the graph matching paradigm actually employed
this.matching = "AStar";
this.s = Integer.MAX_VALUE; // AStar
// initialize the cost function according to properties
this.costFunction = new CostFunction(node, edge, alpha, nodeAttributes,
nodeCostTypes, nodeAttrImportance, edgeAttributes,
edgeCostTypes, edgeAttrImportance, squareRootNodeCosts,
multiplyNodeCosts, squareRootEdgeCosts, multiplyEdgeCosts, nodeCostMu, nodeCostNu);
// the matrixGenerator generates the cost-matrices according to the costfunction
this.matrixGenerator = new MatrixGenerator(this.costFunction,
this.outputCostMatrix);
// bipartite matching procedure (Hungarian or VolgenantJonker)
this.bipartiteMatching = new BipartiteMatching(this.matching, this.outputMatching);
// editDistance computes either the approximated edit-distance according to the bipartite matching
// or computes the exact edit distance
this.editDistance = new EditDistance(this.undirected, this.outputEditpath);
// whether or not a similarity is derived from the distances
this.simKernel=0;
// load the source and target set of graphs
System.out.println("Load the source and target graph sets...");
XMLParser xmlParser = new XMLParser();
this.source = new GraphSet();
this.target = new GraphSet();
this.source.add(xmlParser.parseGXLFromString(graph1));
this.target.add(xmlParser.parseGXLFromString(graph2));
// create a distance matrix to store the resulting dissimilarities
this.r = this.source.size();
this.c = this.target.size();
this.distanceMatrix = new double[this.r][this.c];
}
/**
* @return the progress of the matching procedure
*/
public int getCounter() {
return counter;
}
}