/*
* 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-2005 by Mathias Lux (mathias@juggle.at)
* http://www.juggle.at, http://caliph-emir.sourceforge.net
*/
package at.lux.fotoretrieval.lucene;
import java.util.*;
/**
* Date: 01.11.2004
* Time: 11:56:22
*
* @author Mathias Lux, mathias@juggle.at
*/
public class Graph implements Comparable {
LinkedList<Node> nodes;
LinkedList<Relation> relations;
private static final boolean MCS_DISTANCE_TAKE_RELATIONS_INTO_ACCOUNT = false;
public Graph(List<Node> nodes, List<Relation> relations) {
this.nodes = new LinkedList<Node>();
this.relations = new LinkedList<Relation>();
this.nodes.addAll(nodes);
this.relations.addAll(relations);
}
/**
* Creates a graph from its String representation
*
* @param graph as String like output from toString method
*/
public Graph(String graph) {
StringTokenizer st = new StringTokenizer(graph, "[");
this.nodes = new LinkedList<Node>();
this.relations = new LinkedList<Relation>();
Relation currentRelation = null;
boolean nodesFinished = false;
while (st.hasMoreTokens()) {
String s = st.nextToken().trim();
s = s.substring(0, s.length() - 1);
try {
if (s.matches("\\d+")) {
// String defines a node
int node = Integer.parseInt(s);
nodes.add(new Node(node));
} else {
// String defines a relation
StringTokenizer relationTokenizer = new StringTokenizer(s);
String relation = relationTokenizer.nextToken();
String sourceNode = relationTokenizer.nextToken();
String targetNode = relationTokenizer.nextToken();
relations.add(new Relation(Integer.parseInt(sourceNode), Integer.parseInt(targetNode), relation));
}
} catch (NumberFormatException e) {
System.err.println("Error parsing!");
}
}
}
/**
* Returns the power set of all possible subgraphs
*
* @return the power set of all possible sub graphs
*/
public List<Graph> getPowerSet() {
List<Graph> pws1 = getPowerSetOfNodes();
List<Graph> resultList = new LinkedList<Graph>();
for (Iterator<Graph> iterator = pws1.iterator(); iterator.hasNext();) {
Graph graph = iterator.next();
resultList.addAll(graph.getPowerSetOfRelations());
}
// remove redundant entries:
Collections.sort(resultList);
Graph lastGraph = null;
List<Graph> newResultList = new LinkedList<Graph>();
for (Iterator<Graph> iterator = resultList.iterator(); iterator.hasNext();) {
Graph graph = iterator.next();
if (lastGraph != null && !graph.toString().equals(lastGraph.toString())) {
newResultList.add(graph);
}
lastGraph = graph;
}
return resultList;
}
/**
* Returns the power set of all possible subgraphs only taking the nodes into account
*
* @return
*/
public List<Graph> getPowerSetOfNodes() {
if (nodes.size() > 1) {
LinkedList<Graph> powerSet = new LinkedList<Graph>();
LinkedList<Node> nodeList = new LinkedList<Node>();
LinkedList<Relation> relationList = new LinkedList<Relation>();
LinkedList<Relation> removedRelationList = new LinkedList<Relation>();
nodeList.addAll(nodes);
Node first = nodeList.removeFirst();
for (Iterator<Relation> iterator = relations.iterator(); iterator.hasNext();) {
Relation relation = iterator.next();
if (!relation.isSourceOrTarget(first.getNodeID())) {
relationList.add(relation);
} else {
removedRelationList.add(relation);
}
}
Graph g = new Graph(nodeList, relationList);
// add all available from the smaller graphs powerset:
List<Graph> pwSet = g.getPowerSet();
powerSet.addAll(pwSet);
// add all from the smaller graphs powerset in union with removed node:
for (Iterator<Graph> iterator = pwSet.iterator(); iterator.hasNext();) {
Graph graph = iterator.next().clone();
for (Iterator<Relation> iterator1 = removedRelationList.iterator(); iterator1.hasNext();) {
Relation relation = iterator1.next();
for (Iterator<Node> iterator2 = graph.getNodes().iterator(); iterator2.hasNext();) {
Node node = iterator2.next();
if (relation.isSourceOrTarget(node.getNodeID())) {
if (!graph.getRelations().contains(relation)) {
graph.getRelations().add(relation);
} else {
// System.out.println("Oops! Relation was already there!");
}
}
}
}
// check if the node is not already inside :)
boolean doNotAdd = false;
for (Iterator<Node> iterator1 = graph.getNodes().iterator(); iterator1.hasNext();) {
Node node = iterator1.next();
if (node.getNodeID() == first.getNodeID()) {
doNotAdd = true;
// System.out.println("Oops!");
}
}
// add it if not already there
if (!doNotAdd) {
graph.getNodes().add(first);
}
powerSet.add(graph);
}
return powerSet;
} else {
// if there is only one element:
LinkedList<Node> nodeList = new LinkedList<Node>();
nodeList.addAll(nodes);
Graph gr = new Graph(nodes, new LinkedList<Relation>());
LinkedList<Graph> graphs = new LinkedList<Graph>();
// Graph with one element
graphs.add(gr);
// empty graph:
graphs.add(new Graph(new LinkedList<Node>(), new LinkedList<Relation>()));
return graphs;
}
}
/**
* Returns the power set taking relations into account
*
* @return
*/
public List<Graph> getPowerSetOfRelations() {
if (relations.size() > 1) {
LinkedList<Relation> rels = new LinkedList<Relation>(relations);
Relation firstRel = rels.removeFirst();
Graph g = new Graph(nodes, rels);
List<Graph> tmpResults = g.getPowerSetOfRelations();
LinkedList<Graph> results = new LinkedList<Graph>();
for (Iterator<Graph> iterator = tmpResults.iterator(); iterator.hasNext();) {
Graph graph = iterator.next();
results.add(graph);
LinkedList<Relation> tmp = new LinkedList<Relation>(graph.getRelations());
tmp.add(firstRel);
results.add(new Graph(graph.nodes, tmp));
}
return results;
} else {
LinkedList<Graph> result = new LinkedList<Graph>();
if (relations.size() > 0) {
// there is one single relation:
// creating a graph without relation
Graph g1 = new Graph(nodes, new LinkedList<Relation>());
// adding the one with one relation
result.add(this);
// adding the one with zero relations
result.add(g1);
} else {
result.add(this);
}
return result;
}
}
public List<Node> getNodes() {
return nodes;
}
public List<Relation> getRelations() {
return relations;
}
public Graph clone() {
LinkedList<Node> nList = new LinkedList<Node>();
LinkedList<Relation> rList = new LinkedList<Relation>();
for (Iterator<Node> iterator = nodes.iterator(); iterator.hasNext();) {
nList.add(iterator.next().clone());
}
for (Iterator<Relation> iterator = relations.iterator(); iterator.hasNext();) {
rList.add(iterator.next().clone());
}
return new Graph(nList, rList);
}
/**
* Creates a String representation from a Graph
*
* @return a String representation
*/
public String toString() {
// DecimalFormat df = (DecimalFormat) DecimalFormat.getInstance();
// df.setMinimumIntegerDigits(5);
// df.setMaximumFractionDigits(0);
// df.setGroupingUsed(false);
StringBuilder sb = new StringBuilder(64);
Collections.sort(nodes);
Collections.sort(relations);
for (Iterator<Node> iterator = nodes.iterator(); iterator.hasNext();) {
Node n = iterator.next();
sb.append("[");
sb.append(n.getNodeID());
sb.append("] ");
}
for (Iterator<Relation> iterator = relations.iterator(); iterator.hasNext();) {
Relation relation = iterator.next();
sb.append("[");
sb.append(relation.toString());
sb.append("] ");
}
return sb.toString().trim();
}
/**
* Returns true if the graph contains the same relations and nodes,
* that is when the string representation is equal.
*
* @param o
* @return true if the graph contains the same relations and nodes
*/
public boolean equals(Object o) {
if (o instanceof Graph) {
Graph g = (Graph) o;
if (g.toString().equals(toString())) {
return true;
} else {
return false;
}
} else {
return false;
}
}
public float getMcsSimilarity(Graph g) {
return getMcsSimilarity(g, false);
}
public float getMcsSimilarity(Graph g, boolean useRelations) {
float similarity = 1f;
boolean matched = false;
LinkedList<Integer> matchedNodes = new LinkedList<Integer>();
LinkedList<Relation> matchedRelations = new LinkedList<Relation>();
for (Iterator<Node> iterator = nodes.iterator(); iterator.hasNext();) {
Node node = iterator.next();
for (Iterator<Node> iterator1 = g.getNodes().iterator(); iterator1.hasNext();) {
Node n = iterator1.next();
if (node.equals(n)) {
similarity = similarity * node.getWeight() * n.getWeight();
matched = true;
matchedNodes.add(node.getNodeID());
}
}
}
if (useRelations) {
for (Iterator<Relation> iterator = relations.iterator(); iterator.hasNext();) {
Relation relation = iterator.next();
if (matchedNodes.contains(relation.getSource()) && matchedNodes.contains(relation.getTarget())) {
for (Iterator<Relation> it = g.getRelations().iterator(); it.hasNext();) {
Relation r = it.next();
int tgt1 = relation.getTarget();
int tgt2 = r.getTarget();
int src1 = relation.getSource();
int src2 = r.getSource();
if ( (Math.min(tgt1, src1) == Math.min(tgt2, src2)) && (Math.max(tgt1, src1) == Math.max(tgt2, src2))) {
// relation matches!
matchedRelations.add(relation);
}
}
}
}
}
if (!matched)
similarity = 0f;
else {
if (useRelations) {
float thisSize = (float) (nodes.size());
float gSize = (float) (g.getNodes().size());
similarity = similarity / Math.max(thisSize, gSize);
} else {
float thisSize = (float) (nodes.size() + relations.size());
float gSize = (float) (g.getNodes().size() + g.getRelations().size());
float mcsSize = (float) (matchedNodes.size() + matchedRelations.size());
similarity = similarity * mcsSize / Math.max(thisSize, gSize);
}
}
return similarity;
}
/**
* Calculates the maximum common subgraph metric: The maximum common
* subgraph is searched and its size compared to the max size of either of
* the input graphs is the similarity.
* @param g defines which graph has to be compared to this one
* @return a distance in [0,1]
*/
public float getMcsDistance(Graph g) {
int matched = 0;
for (Iterator<Node> iterator = nodes.iterator(); iterator.hasNext();) {
Node node = iterator.next();
if (g.nodes.contains(node)) {
matched++;
}
}
for (Iterator<Relation> iterator = relations.iterator(); iterator.hasNext();) {
Relation relation = iterator.next();
if (g.relations.contains(relation)) {
matched++;
}
}
float similarity = (float) matched / (float) Math.max(nodes.size() + relations.size(), g.nodes.size() + g.relations.size());
return 1-similarity;
}
public float getSuffixTreeDistance() {
float distance = 0;
return distance;
}
public int compareTo(Object o) {
return toString().compareTo(((Graph) o).toString());
}
/**
* Returns all relations where the given node is involveld (target or source)
* @param node
* @return
*/
public Set<Relation> getRelations(Node node) {
return getRelations(node.getNodeID());
}
public Set<Relation> getRelations(int nodeID) {
HashSet<Relation> result = new HashSet<Relation>(relations.size()/2);
for (Iterator<Relation> iterator = relations.iterator(); iterator.hasNext();) {
Relation relation = iterator.next();
if (relation.getSource() == nodeID || relation.getTarget() == nodeID) {
result.add(relation);
}
}
return result;
}
}