Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[henshin-dev] Graph isomorphy checker for state space tools -- Help needed

Hi guys,

due to a design failure, I had to reimplement the graph isomorphy checker for the state space tools from scratch. The new version (attached to this e-mail) is very slow though. As a short reminder: no polynomial-time algorithm is known for graph isomorphy checking, but it was also not shown to be NP-complete yet. There are efficient ways to decide isomorphy for trees, but I believe this does not help us, even though we have a canonical spanning tree in EMF models (the containment tree).

To make a long story short: if you have any suggestions how the implementation could be improved in terms of speed, I would highly appreciate it. Any idea is welcome -- don't be shy! I really don't know how to do better in the moment.

Cheers,
Christian
package org.eclipse.emf.henshin.statespace.equality;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.henshin.statespace.Model;
import org.eclipse.emf.henshin.statespace.StateSpacePlugin;

/**
 * Graph-based equality checker for EMF models. 
 * @author Christian Krause
 */
public class GraphEqualityHelper extends HashMap<EObject,EObject> {	
	
	// Default serial ID:
	private static final long serialVersionUID = 1L;
	
	// Helper for handling attributes:
	private EcoreEqualityHelper attributeHelper;

	// Whether node IDs should be ignored:
	private boolean ignoreNodeIDs;
	
	// Whether to ignore attribute values:
	private boolean ignoreAttributes;

	// Cached version of the current models to compare:
	private Model model1, model2;

	// Cached version of the current models to compare:
	private Map<Integer,List<EObject>> slots1, slots2;
	
	// Hash code maps:
	private HashCodeMap hashcodes1, hashcodes2;
	
	/**
	 * Default constructor.
	 * @param ignoreAttributes Whether to ignore attributes.
	 */
	public GraphEqualityHelper(boolean ignoreNodeIDs, boolean ignoreAttributes) {
		this.ignoreNodeIDs = ignoreNodeIDs;
		this.ignoreAttributes = ignoreAttributes;
		this.attributeHelper = new EcoreEqualityHelper(ignoreNodeIDs, ignoreAttributes);
	}
	
	/**
	 * Check graph equality for two models.
	 * @param model1 Model 1.
	 * @param map1 Hash codes for the elements in model 1.
	 * @param model2 Model 2.
	 * @param map2 Hash codes for the elements in model 2.
	 * @return <code>true</code> if they are equal.
	 */
	public boolean equals(Model model1, HashCodeMap map1, 
						  Model model2, HashCodeMap map2) {

		// Make sure we have both hash codes maps:
		if (map1==null || map2==null) {
			hashcodes1 = HashCodeMap.ECLASS_HASH_CODE_MAP;
			hashcodes2 = HashCodeMap.ECLASS_HASH_CODE_MAP;
			StateSpacePlugin.INSTANCE.logWarning("Using EClass-based node comparison (very slow!)");
		} else {
			hashcodes1 = map1;
			hashcodes2 = map2;
		}

		// Compute the slots for the two models:
		slots1 = generateSlots(model1, hashcodes1);
		slots2 = generateSlots(model2, hashcodes2);
		
		// Hash code sets must be equal:
		if (!slots1.keySet().equals(slots2.keySet())) {
			slots1 = null;
			slots2 = null;
			return false;
		}
		
		// Store the models:
		this.model1 = model1;
		this.model2 = model2;
		
		// Reset the match first:
		clear();
		
		// Depth-first comparison:
		boolean result = matchFirst();
		
		// Clean-up:
		slots1 = null;
		slots2 = null;
		hashcodes1 = null;
		hashcodes2 = null;
		this.model1 = null;
		this.model2 = null;
		
		// Done.
		return result;

	}
	
	/*
	 * Find the first object in model 1 that is not
	 * match yet and try to match it. This method
	 * is called recursively, thus will try to match 
	 * ALL objects.
	 */
	private boolean matchFirst() {
		
		// Find the first object to be matched:
		EObject next = null;
		Integer hash = null;
		for (Map.Entry<Integer,List<EObject>> entry : slots1.entrySet()) {
			if (!entry.getValue().isEmpty()) {
				hash = entry.getKey();
				next = entry.getValue().get(0);
			}
		}
		
		// Check if we are done already:
		if (next==null) {
			return true;
		}
		
		// Try to find a match for it:
		for (EObject match : slots2.get(hash).toArray(new EObject[0])) {
			if (match(next, match)) {
				return true;
			}
		}
		
		// No match found:
		return false;
		
	}
	
	/*
	 * Check whether two objects are allowed to be matched.
	 * This does not change the current match. It performs
	 * only a basic check.
	 */
	private boolean canMatch(EObject o1, EObject o2) {
		
		// Check if the first object has been matched already:
		EObject match = get(o1);
		if (match!=null) {
			
			// If yes, it must be o2:
			return (match==o2);
			
		} else {
			
			// If not, o2 must not have been matched yet by another object.
			if (values().contains(o2)) {
				return false;
			}
			
		}
		
		// Must have the same hash code:
		if (hashcodes1.getHashCode(o1)!=hashcodes2.getHashCode(o2)) {
			return false;
		}
		
		// Compare the node IDs, if necessary:
		if (!ignoreNodeIDs && 
			model1.getNodeIDsMap().get(o1).intValue()!=model2.getNodeIDsMap().get(o2).intValue()) {
			return false;
		}
		
		// Must be of the same type:
		EClass type = o1.eClass();
		if (!o2.eClass().equals(type)) {
			return false;
		}

		// Compare all attributes:
		if (!ignoreAttributes) {
			for (EAttribute attribute : type.getEAllAttributes()) {
				if (!attributeHelper.haveEqualAttribute(o1, o2, attribute)) {
					return false;
				}
			}
		}
		
		// Ok:
		return true;

	}
	
	/*
	 * Perform a match operation.
	 */
	@SuppressWarnings("unchecked")
	private boolean match(EObject o1, EObject o2) {
		
		// Check if a match is possible:
		if (!canMatch(o1,o2)) {
			return false;
		}
		
		// Check if the objects are match already:
		if (containsKey(o1)) {
			// canMatch() already verified that it is matched with o2
			// We still need to match the rest though:
			return matchFirst();
		}
		
		// They have the same hash code:
		Integer hash = hashcodes1.get(o1);
		
		// For now we assume that the match works:
		put(o1, o2);
		slots1.get(hash).remove(o1);
		slots2.get(hash).remove(o2);
		
		// Now check the references:
		for (EReference reference : o1.eClass().getEAllReferences()) {
			if (reference.isMany()) {
				
				// List of referenced objects:
				EList<EObject> list1 = (EList<EObject>) o1.eGet(reference);
				EList<EObject> list2 = (EList<EObject>) o2.eGet(reference);
				
				// Must have the same size:
				if (list1.size()!=list2.size()) {
					remove(o1);			// abort the current match
					slots1.get(hash).add(0,o1);
					slots2.get(hash).add(0,o2);
					return false;
				}
				
				// Match all objects in the two lists:
				for (EObject l1 : list1) {
					for (EObject l2 : list2) {
						if (match(l1, l2)) {
							return true;	// done!
						}
					}
				}
				
				// No match found; we abort:
				remove(o1);
				slots1.get(hash).add(0,o1);
				slots2.get(hash).add(0,o2);
				return false;
				
			} else {
				
				// Single referenced objects:
				EObject l1 = (EObject) o1.eGet(reference);
				EObject l2 = (EObject) o2.eGet(reference);
				if ((l1==null && l2!=null) || 
					(l1!=null && l2==null) ||
					(l1!=null && !match(l1,l2))) {
					remove(o1);			// abort the current match
					slots1.get(hash).add(0,o1);
					slots2.get(hash).add(0,o2);
					return false;
				}
			}
			
		}
		
		// Still need to match the rest:
		if (matchFirst()) {
			return true;
		}
		
		// Otherwise abort:
		remove(o1);
		slots1.get(hash).add(0,o1);
		slots2.get(hash).add(0,o2);
		return false;

	}
	
	/*
	 * Generate slots for a given model.
	 */
	private static Map<Integer,List<EObject>> generateSlots(Model model, HashCodeMap map) {
		Map<Integer,List<EObject>> slots = new LinkedHashMap<Integer,List<EObject>>();
		TreeIterator<EObject> it = model.getResource().getAllContents();
		while (it.hasNext()) {
			EObject next = it.next();
			Integer hash = map.get(next);
			List<EObject> objects = slots.get(hash);
			if (objects==null) {
				slots.put(hash, objects = new ArrayList<EObject>());
			}
			objects.add(next);
		}
		return slots;
	}
	
}	

Back to the top