package at.lux.retrieval.graphisomorphism;
import org.jdom.Document;
import org.jdom.Element;
import java.util.*;
import at.lux.fotoretrieval.RetrievalToolkit;
/*
* This file is part of Caliph & Emir.
*
* Caliph & Emir is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Caliph & Emir is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Caliph & Emir; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Copyright statement:
* --------------------
* (c) 2002-2006 by Mathias Lux (mathias@juggle.at)
* http://www.juggle.at, http://www.SemanticMetadata.net
*/
/**
* This implementation features a suboptimal approach to
* the error correcting subgraph isomorphism problem.
* Using discrete relaxation the problem is solved with
* better runtime performance.
* <p/>
* This file is part of Caliph & Emir
* Date: 16.02.2006
* Time: 22:00:57
*
* @author Mathias Lux, mathias@juggle.at
*/
public class SubOptimalSubgraphIsomorphism extends AbstractSubgraphIsomorphism {
private EdgeDistanceFunction eDist;
private NodeDistanceFunction nDist;
private float lambda = 0.5f;
public SubOptimalSubgraphIsomorphism(NodeDistanceFunction nDist, EdgeDistanceFunction eDist) {
this.eDist = eDist;
this.nDist = nDist;
}
/**
* Creates a new SubgraphIsomorphism calculator. Lambda measures how much weight
* is put on the nodes. The rest of the weight is put on the edges. Default is 0.5.
*
* @param nDist node distance function
* @param eDist edge distance function
* @param lambda measures how much weight is put on the nodes. The rest of the weight is put on the edges. Default is 0.5.
*/
public SubOptimalSubgraphIsomorphism(NodeDistanceFunction nDist, EdgeDistanceFunction eDist, float lambda) {
this.eDist = eDist;
this.lambda = lambda;
this.nDist = nDist;
}
public float getDistance(Document mpeg7Document1, Document mpeg7Document2) {
List semanticBaseDoc1 = RetrievalToolkit.xpathQuery(mpeg7Document1.getRootElement(), "//Semantic/SemanticBase", null);
List semanticRelationsDoc1 = RetrievalToolkit.xpathQuery(mpeg7Document1.getRootElement(), "//Semantic/Graph/Relation", null);
List semanticBaseDoc2 = RetrievalToolkit.xpathQuery(mpeg7Document2.getRootElement(), "//Semantic/SemanticBase", null);
List semanticRelationsDoc2 = RetrievalToolkit.xpathQuery(mpeg7Document2.getRootElement(), "//Semantic/Graph/Relation", null);
ArrayList<Relation> edgeCache1 = createEdgesCache(semanticRelationsDoc1);
ArrayList<SubOptimalSubgraphIsomorphism.Relation> edgeCache2 = createEdgesCache(semanticRelationsDoc2);
ArrayList<Element> edges = new ArrayList<Element>(semanticRelationsDoc1.size() + semanticRelationsDoc2.size());
for (Object relation : semanticRelationsDoc1) {
edges.add((Element) relation);
}
for (Object relation : semanticRelationsDoc2) {
edges.add((Element) relation);
}
ArrayList<Element> nodes = new ArrayList<Element>(semanticBaseDoc1.size() + semanticBaseDoc2.size());
for (Object relation : semanticBaseDoc1) {
nodes.add((Element) relation);
}
for (Object relation : semanticBaseDoc2) {
nodes.add((Element) relation);
}
// ToDo: rows and cols should not get mixed up ...
DistanceMatrix edgeMatrix = new DistanceMatrix(edges, eDist);
DistanceMatrix nodeMatrix = new DistanceMatrix(nodes, nDist);
HashMap<Element, HashSet<Element>> bestFitAssignment = getBestFitAssignment(semanticBaseDoc1, semanticBaseDoc2, nodeMatrix);
float[][] v = new float[semanticBaseDoc2.size()][semanticBaseDoc1.size()];
for (int i = 0; i < semanticBaseDoc1.size(); i++) {
float minimum = Float.MAX_VALUE;
for (int j = 0; j < semanticBaseDoc2.size(); j++) {
v[i][j] = nodeMatrix.getDistance(((Element) semanticBaseDoc2.get(i)), ((Element) semanticBaseDoc1.get(j)));
if (v[i][j] < minimum) minimum = v[i][j];
}
for (int j = 0; j < semanticBaseDoc2.size(); j++) {
v[i][j] = v[i][j] - minimum;
}
}
// TODO: finish eventually ...
// while (isOverAssigned(bestFitAssignment)) {
// getShortestPath(bestFitAssignment, v, semanticBaseDoc1, semanticBaseDoc2);
// }
float distance = -1f;
return distance;
}
private static float getMinimumInRow(float[] floats) {
float min = Float.MAX_VALUE;
for (float aFloat : floats) {
if (aFloat > 0 && aFloat < min) min = aFloat;
}
return min;
}
private boolean isOverAssigned(HashMap<Element, HashSet<Element>> assignment) {
boolean result = false;
for (Element e : assignment.keySet()) {
if (assignment.get(e).size() > 1) {
result = true;
break;
}
}
return result;
}
private HashMap<Element, HashSet<Element>> getBestFitAssignment(List nodes1, List nodes2, DistanceMatrix nodeMatrix) {
// todo: this is absolutely no performance ... change that in time ?!?
HashMap<Element, HashSet<Element>> results = new HashMap<Element, HashSet<Element>>(nodes1.size());
for (Object o1 : nodes1) {
Element e1 = (Element) o1;
results.put(e1, new HashSet<Element>());
}
for (Object o2 : nodes2) {
Element e2 = (Element) o2;
Element bestFit = null;
float bestDistance = -1;
for (Object o1 : nodes1) {
Element e1 = (Element) o1;
float distance = nodeMatrix.getDistance(e1, e2);
if (bestFit == null) {
bestFit = e1;
bestDistance = distance;
} else {
if (distance < bestDistance) {
bestFit = e1;
bestDistance = distance;
}
}
}
results.get(bestFit).add(e2);
}
return results;
}
}