package at.lux.retrieval.graphisomorphism;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Attribute;
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 file is part of Caliph & Emir
* Date: 16.02.2006
* Time: 22:00:57
*
* @author Mathias Lux, mathias@juggle.at
*/
public class SubgraphIsomorphism extends AbstractSubgraphIsomorphism {
private EdgeDistanceFunction eDist;
private NodeDistanceFunction nDist;
private float lambda = 0.5f;
public SubgraphIsomorphism(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 SubgraphIsomorphism(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<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);
// ToDo: Combine the min distance check with the lookup for different mappings.
ArrayList<HashMap<Element, Element>> mappings = getAllMappings(semanticBaseDoc1, semanticBaseDoc2);
float minDistance = -1f;
HashMap<Element, Element> minMapping = null;
for (HashMap<Element, Element> mapping : mappings) {
float distance = getMappingDistance(mapping, nodeMatrix, edgeMatrix, edgeCache1, edgeCache1);
if (minDistance < 0 || distance < minDistance) {
minDistance = distance;
minMapping = mapping;
}
}
return minDistance;
}
private float getMappingDistance(HashMap<Element, Element> mapping, DistanceMatrix nodeMatrix,
DistanceMatrix edgeMatrix, ArrayList<Relation> edges1, ArrayList<Relation> edges2) {
float nodeDistance = 0f;
float edgeDistance = 0f;
for (Element e1 : mapping.keySet()) {
nodeDistance += nodeMatrix.getDistance(e1, mapping.get(e1));
Relation rel1 = null;
Relation rel2 = null;
for (Element e2 : mapping.keySet()) {
String id1 = e1.getAttributeValue("id");
String id2 = e2.getAttributeValue("id");
for (Relation relation : edges1) {
if (relation.connectsNodes(id1, id2)) {
rel1 = relation;
}
}
id1 = mapping.get(e1).getAttributeValue("id");
id2 = mapping.get(e2).getAttributeValue("id");
for (Relation relation : edges2) {
if (relation.connectsNodes(id1, id2)) {
rel2 = relation;
}
}
}
if ((rel1 != null && rel2 == null) || (rel1 == null && rel2 != null)) {
edgeDistance += eDist.getMaxDistance();
} else if (rel1 != null && rel2 != null) {
edgeDistance += edgeMatrix.getDistance(rel1.getElement(), rel2.getElement());
}
}
return lambda * nodeDistance + (1f - lambda) * edgeDistance;
}
private ArrayList<HashMap<Element, Element>> getAllMappings(Collection nodes1, Collection nodes2) {
ArrayList<HashMap<Element, Element>> results = new ArrayList<HashMap<Element, Element>>(32);
for (Object aNode1 : nodes1) {
Element e1 = (Element) aNode1;
for (Object aNode2 : nodes2) {
Element e2 = (Element) aNode2;
HashMap<Element, Element> start = new HashMap<Element, Element>();
start.put(e1, e2);
ArrayList list1 = cloneAndRemoveObject(nodes1, e1);
ArrayList list2 = cloneAndRemoveObject(nodes2, e2);
results.addAll(getAllSubMappings(start, list1, list2));
}
}
return results;
}
private List<HashMap<Element, Element>> getAllSubMappings(HashMap<Element, Element> mappings, Collection nodes1, Collection nodes2) {
ArrayList<HashMap<Element, Element>> results = new ArrayList<HashMap<Element, Element>>(32);
if (!(nodes1.isEmpty() || nodes2.isEmpty())) {
for (Object aNode1 : nodes1) {
Element e1 = (Element) aNode1;
for (Object aNode2 : nodes2) {
Element e2 = (Element) aNode2;
HashMap<Element, Element> start = new HashMap<Element, Element>();
start.putAll(mappings);
start.put(e1, e2);
ArrayList list1 = cloneAndRemoveObject(nodes1, e1);
ArrayList list2 = cloneAndRemoveObject(nodes2, e2);
results.addAll(getAllSubMappings(start, list1, list2));
}
}
} else {
results.add(mappings);
}
return results;
}
private ArrayList cloneAndRemoveObject(Collection objects, Object obj) {
ArrayList result = new ArrayList(objects.size());
result.addAll(objects);
result.remove(obj);
return result;
}
}