blob: eee8053e15d81e1e1f1eb816b6478b1f64dd1dae [file] [log] [blame]
package org.eclipse.stem.graphsynchronizer.impl;
/*******************************************************************************
* Copyright (c) 2011 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.impl.ENotificationImpl;
import org.eclipse.stem.core.Utility;
import org.eclipse.stem.core.common.Identifiable;
import org.eclipse.stem.core.graph.DynamicLabel;
import org.eclipse.stem.core.graph.Edge;
import org.eclipse.stem.core.graph.Graph;
import org.eclipse.stem.core.graph.Node;
import org.eclipse.stem.core.graph.impl.GraphPartitionerImpl;
import org.eclipse.stem.core.model.Decorator;
import org.eclipse.stem.data.geography.GeographicMapper;
import org.eclipse.stem.definitions.labels.RelativePhysicalRelationshipLabel;
import org.eclipse.stem.graphsynchronizer.GraphsynchronizerPackage;
import org.eclipse.stem.graphsynchronizer.SynchGraphPartitioner;
import org.eclipse.stem.graphsynchronizer.util.GlobalTileGenerator;
import org.eclipse.stem.graphsynchronizer.Activator;
import org.eclipse.stem.jobs.simulation.ISimulation;
/**
* <!-- begin-user-doc -->
* An implementation of the model object '<em><b>Synch Graph Partitioner</b></em>'. This class subdivides the graphs
* among multiple running instances of STEM. The algorithm attempts to minimize the "surface" (# of edges) and maximize
* the volume (# of nodes) for each running instance, while also keeping the volume approximately the same for each
* running instance.
*
* <!-- end-user-doc -->
* <p>
* The following features are implemented:
* <ul>
* <li>{@link org.eclipse.stem.graphsynchronizer.impl.SynchGraphPartitionerImpl#getNumNodes <em>Num Nodes</em>}</li>
* <li>{@link org.eclipse.stem.graphsynchronizer.impl.SynchGraphPartitionerImpl#getNodeRank <em>Node Rank</em>}</li>
* </ul>
* </p>
*
* @generated
*/
public class SynchGraphPartitionerImpl extends GraphPartitionerImpl implements SynchGraphPartitioner {
/**
* This map stores for each decorator, another map where the key is the processor ID and the value is the labels updated by that processor.
* -1 is a special key containing all labels updated by the decorator
*/
protected Map<Decorator, Map<Integer, EList<DynamicLabel>>> decoratorProcessorNumberLabelMap = new HashMap<Decorator, Map<Integer, EList<DynamicLabel>>>();
/**
* The default value of the '{@link #getNumNodes() <em>Num Nodes</em>}' attribute.
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @see #getNumNodes()
* @generated
* @ordered
*/
protected static final int NUM_NODES_EDEFAULT = 1;
/**
* The cached value of the '{@link #getNumNodes() <em>Num Nodes</em>}' attribute.
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @see #getNumNodes()
* @generated
* @ordered
*/
protected int numNodes = NUM_NODES_EDEFAULT;
/**
* The default value of the '{@link #getNodeRank() <em>Node Rank</em>}' attribute.
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @see #getNodeRank()
* @generated
* @ordered
*/
protected static final int NODE_RANK_EDEFAULT = 0;
/**
* The cached value of the '{@link #getNodeRank() <em>Node Rank</em>}' attribute.
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @see #getNodeRank()
* @generated
* @ordered
*/
protected int nodeRank = NODE_RANK_EDEFAULT;
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
protected SynchGraphPartitionerImpl() {
super();
}
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
@Override
protected EClass eStaticClass() {
return GraphsynchronizerPackage.Literals.SYNCH_GRAPH_PARTITIONER;
}
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
public int getNumNodes() {
return numNodes;
}
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
public void setNumNodes(int newNumNodes) {
int oldNumNodes = numNodes;
numNodes = newNumNodes;
if (eNotificationRequired())
eNotify(new ENotificationImpl(this, Notification.SET, GraphsynchronizerPackage.SYNCH_GRAPH_PARTITIONER__NUM_NODES, oldNumNodes, numNodes));
}
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
public int getNodeRank() {
return nodeRank;
}
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
public void setNodeRank(int newNodeRank) {
int oldNodeRank = nodeRank;
nodeRank = newNodeRank;
if (eNotificationRequired())
eNotify(new ENotificationImpl(this, Notification.SET, GraphsynchronizerPackage.SYNCH_GRAPH_PARTITIONER__NODE_RANK, oldNodeRank, nodeRank));
}
/**
* Override to clear map
*/
@Override
public void setNumProcesses(int newNumProcesses) {
if(getNumProcesses() != newNumProcesses)
decoratorProcessorNumberLabelMap.clear();
super.setNumProcesses(newNumProcesses);
}
private ISimulation currentSimulation;
private HashMap<String, Integer> rankCache = new HashMap<String, Integer>();
/**
* initialize the partitioner from a simulation
*
* @param s ISimulation
*/
public void initialize(ISimulation s) {
if(s.equals(currentSimulation)) return; // done
HashSet<String>countries = new HashSet<String>();
HashMap<String, Set<String>> nodeIdMap = new HashMap<String, Set<String>>();
Graph g = s.getScenario().getCanonicalGraph();
EMap<URI, Node>nodes = g.getNodes();
for(Entry<URI, Node> entry : nodes.entrySet()) {
Node n = entry.getValue();
String id = n.getURI().lastSegment();
String ctry = getCountry(id);
if(ctry != null && nodeIdMap.containsKey(ctry)) // It's a country
nodeIdMap.get(ctry).add(id);
else if(ctry != null && !nodeIdMap.containsKey(ctry)) {
HashSet<String>ids = new HashSet<String>();
ids.add(id);
nodeIdMap.put(ctry, ids);
}
}
Set<String[]> commonBorderIdPairsSet = new HashSet<String[]>();
for(Entry<URI, Edge> entry:g.getEdges()) {
Edge e = entry.getValue();
if(e.getLabel() instanceof RelativePhysicalRelationshipLabel)
continue; // skip
String [] st = new String[2];
if(e.getA() != null && e.getB() != null) {
st[0] = e.getA().getURI().lastSegment();
st[1] = e.getB().getURI().lastSegment();
commonBorderIdPairsSet.add(st);
}
}
// Now determine the partitioning using Jamie's algorithm
GlobalTileGenerator gtg = new GlobalTileGenerator(this.getNumNodes(), nodeIdMap, commonBorderIdPairsSet);
List<Set<String>>partitionedNodes = gtg.getParitionedNodes();
int rank = 0;
for(Set<String> allIds:partitionedNodes) {
for(String id:allIds)
rankCache.put(id, rank);
++rank;
}
currentSimulation = s;
}
/**
* Return the country a given id is inside
* @param id 2 or 3 letter admin code
* @return
*/
private String getCountry(String id) {
if(Utility.keyLevel(id) == 0 ) // It's a country
return id;
else if(Utility.keyLevel(id) == -1 ) // ZZZ
return id;
else {
// It's higher admin than level 0. We need to find the country the code is in
String ctrcode = GeographicMapper.getAlpha3(id.substring(0, 2));
return ctrcode;
}
}
/**
*
*/
@Override
public EList<DynamicLabel> partitionDecoratorLabels(Decorator decorator, int processRank) {
synchronized(decoratorProcessorNumberLabelMap) {
if(decoratorProcessorNumberLabelMap != null && decoratorProcessorNumberLabelMap.containsKey(decorator) &&
decoratorProcessorNumberLabelMap.get(decorator).containsKey(processRank)) {
EList<DynamicLabel> res = decoratorProcessorNumberLabelMap.get(decorator).get(processRank);
return res;
}
synchronized(this) {
EList<DynamicLabel> temp = new BasicEList<DynamicLabel>();
// Find the node and partition the labels according to their node.
// TreeSet guarantees O(log(n)) add/contains
TreeSet<String>nodeURIs = new TreeSet<String>();
for(DynamicLabel lab:decorator.getLabelsToUpdate()) {
String uri = lab.getIdentifiable().getURI().toString();
if(!nodeURIs.contains(uri) &&
getNodeRank() == getIdentifiableRank(lab.getIdentifiable()))
nodeURIs.add(uri);
}
int size = nodeURIs.size();
if(processRank == 0 || size > 1) { // If size is 1 and processRank is 0 the single node gets assigned to the first CPU.
int nodesPerPartition = size / getNumProcesses();
int start = processRank*nodesPerPartition;
int end;
if(processRank == getNumProcesses() -1) end = size; // The last threads grabs all nodes until the last one
else end = start + nodesPerPartition;
String first=nodeURIs.first();
for(int i=0;i<start;++i) first = nodeURIs.higher(first);
String last = first;
for(int i=start;i<end-1;++i) last = nodeURIs.higher(last);
NavigableSet<String>processURIs = nodeURIs.subSet(first, true, last, true);
for(DynamicLabel dl:decorator.getLabelsToUpdate()) {
if(processURIs.contains(dl.getIdentifiable().getURI().toString())) {
temp.add(dl);
}
}
}
Map<Integer, EList<DynamicLabel>> partitionMap = null;
if(decoratorProcessorNumberLabelMap.containsKey(decorator))
partitionMap = decoratorProcessorNumberLabelMap.get(decorator);
else {
partitionMap = new HashMap<Integer, EList<DynamicLabel>>();
decoratorProcessorNumberLabelMap.put(decorator, partitionMap);
}
partitionMap.put(processRank, temp);
Activator.logInformation("Decorator decorator updates "+temp.size()+" labels");
return temp;
}
}
}
/**
* Same as above but return all labels handled by this decorator on this node
*/
@Override
public EList<DynamicLabel> partitionDecoratorLabels(Decorator decorator) {
if(decoratorProcessorNumberLabelMap != null && decoratorProcessorNumberLabelMap.containsKey(decorator) &&
decoratorProcessorNumberLabelMap.get(decorator).containsKey(-1)) { // -1 is special key, it means all labels
EList<DynamicLabel> res = decoratorProcessorNumberLabelMap.get(decorator).get(-1);
return res;
}
synchronized(this) {
EList<DynamicLabel> temp = new BasicEList<DynamicLabel>();
// Find the node and partition the labels according to their node.
ArrayList<String>nodeURIs = new ArrayList<String>();
for(DynamicLabel lab:decorator.getLabelsToUpdate()) {
String uri = lab.getIdentifiable().getURI().toString();
if(!nodeURIs.contains(uri) &&
getNodeRank() == getIdentifiableRank(lab.getIdentifiable()))
temp.add(lab);
}
Map<Integer, EList<DynamicLabel>> partitionMap = null;
if(decoratorProcessorNumberLabelMap.containsKey(decorator))
partitionMap = decoratorProcessorNumberLabelMap.get(decorator);
else {
partitionMap = new HashMap<Integer, EList<DynamicLabel>>();
decoratorProcessorNumberLabelMap.put(decorator, partitionMap);
}
partitionMap.put(-1, temp);
return temp;
}
}
/**
* Given an identifiable, return the rank (node number) where the identifiable
* is managed.
*
* @generated NOT
*/
public int getIdentifiableRank(Identifiable identifiable) {
String key = identifiable.getURI().lastSegment();
if(rankCache.containsKey(key))
return rankCache.get(key);
Activator.logError("Unable to find server rank for node "+key+". Assigning to first server", new Exception());
return 0;
// Random
// int hash = identifiable.getURI().toString().hashCode();
// hash = Math.abs(hash);
// int node = hash % getNumNodes();
// return node;
// USA Mexico
// if((identifiable.getURI().lastSegment().startsWith("MX") || identifiable.getURI().lastSegment().startsWith("MEX"))) return 1;
// return 0; // everything else is on the first node.
}
/**
* Check if the identifiable passed in is managed by this STEM instance
*/
@Override
public boolean isManaged(Identifiable identifiable) {
return this.getNodeRank() == this.getIdentifiableRank(identifiable);
}
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
@Override
public Object eGet(int featureID, boolean resolve, boolean coreType) {
switch (featureID) {
case GraphsynchronizerPackage.SYNCH_GRAPH_PARTITIONER__NUM_NODES:
return getNumNodes();
case GraphsynchronizerPackage.SYNCH_GRAPH_PARTITIONER__NODE_RANK:
return getNodeRank();
}
return super.eGet(featureID, resolve, coreType);
}
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
@Override
public void eSet(int featureID, Object newValue) {
switch (featureID) {
case GraphsynchronizerPackage.SYNCH_GRAPH_PARTITIONER__NUM_NODES:
setNumNodes((Integer)newValue);
return;
case GraphsynchronizerPackage.SYNCH_GRAPH_PARTITIONER__NODE_RANK:
setNodeRank((Integer)newValue);
return;
}
super.eSet(featureID, newValue);
}
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
@Override
public void eUnset(int featureID) {
switch (featureID) {
case GraphsynchronizerPackage.SYNCH_GRAPH_PARTITIONER__NUM_NODES:
setNumNodes(NUM_NODES_EDEFAULT);
return;
case GraphsynchronizerPackage.SYNCH_GRAPH_PARTITIONER__NODE_RANK:
setNodeRank(NODE_RANK_EDEFAULT);
return;
}
super.eUnset(featureID);
}
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
@Override
public boolean eIsSet(int featureID) {
switch (featureID) {
case GraphsynchronizerPackage.SYNCH_GRAPH_PARTITIONER__NUM_NODES:
return numNodes != NUM_NODES_EDEFAULT;
case GraphsynchronizerPackage.SYNCH_GRAPH_PARTITIONER__NODE_RANK:
return nodeRank != NODE_RANK_EDEFAULT;
}
return super.eIsSet(featureID);
}
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
@Override
public String toString() {
if (eIsProxy()) return super.toString();
StringBuffer result = new StringBuffer(super.toString());
result.append(" (numNodes: ");
result.append(numNodes);
result.append(", nodeRank: ");
result.append(nodeRank);
result.append(')');
return result.toString();
}
} //SynchGraphPartitionerImpl