Commit b3b00bbf authored by Philipp Görick's avatar Philipp Görick

Merge remote-tracking branch 'origin/ML_clustering' into ML_clustering

# Conflicts:
#	src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/ClusterFromTagsHelper.java
parents 6548532b 7f36cf8c
......@@ -12,12 +12,12 @@ masterJobLinux:
only:
- master
#masterJobWindows:
# stage: windows
# script:
# - mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -B clean install --settings settings.xml
# tags:
# - Windows10
masterJobWindows:
stage: windows
script:
- mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -B clean install --settings settings.xml
tags:
- Windows10
BranchJobLinux:
stage: linux
......
......@@ -71,6 +71,8 @@ cmake C:\path\to\generated\project\source -G "MinGW Makefiles"
make
```
Please note: It is highly recommended, you stick to the exact versions as stated above. Otherwise you might run into trouble regarding the interplay between cmake/make and the Armadillo library. In particular problems have been reported using Cygwin.
### Projects with roscpp generator
Only for generated projects that contain a ROS adapter(e.g. -g=cpp,roscpp).
ROS Kinetic currently only supports Linux and the installation is described [here](http://wiki.ros.org/kinetic/Installation/Ubuntu).
......
......@@ -9,7 +9,8 @@
<groupId>de.monticore.lang.monticar</groupId>
<artifactId>embedded-montiarc-math-middleware-generator</artifactId>
<version>0.0.10-SNAPSHOT</version>
<version>0.0.12-SNAPSHOT</version>
<!-- == PROJECT DEPENDENCIES ============================================= -->
......@@ -20,8 +21,9 @@
<Embedded-montiarc-math-generator.version>0.0.26-SNAPSHOT</Embedded-montiarc-math-generator.version>
<Embedded-montiarc-math-roscpp-generator.version>0.0.4-SNAPSHOT</Embedded-montiarc-math-roscpp-generator.version>
<Embedded-montiarc-math-rosmsg-generator.version>0.0.3-SNAPSHOT</Embedded-montiarc-math-rosmsg-generator.version>
<EMADL.version>0.2.2-SNAPSHOT</EMADL.version>
<EMADL2CPP.version>0.2.2-SNAPSHOT</EMADL2CPP.version>
<EMADL.version>0.2.3</EMADL.version>
<EMADL2CPP.version>0.2.4</EMADL2CPP.version>
<!--TODO: remove with update to new emam version-->
<Tagging.version>0.0.6</Tagging.version>
<!-- .. Libraries .................................................. -->
......@@ -239,6 +241,7 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
<plugin>
......
EmbeddedMontiArc automated component clustering
Objective:
Bundle interconnected top level components of the model into different clusters. The aim is to reduce connection and communication overhead between components by grouping affine components into different clusters which then are connected using ROS.
Procedure:
1) Convert the symbol table of a component into an adjacency matrix
o Order all sub components by name (neccessary for the adjacency matrix).
o Create adjacency matrix to use with a clustering algorithm, with subcomponents as nodes and connectors between subcomponents as vertices. Sift out all connectors to the super component.
2) Feed adjacency matrix into the selected clustering algorithm
o We are using the machine learning library "smile ml" (see: https://github.com/haifengl/smile) which provides a broad range of different clustering and partitioning approaches. As a prime example we are using "spectral clustering" here. For a closer look at this approach, see the section below.
o The clustring algorithm yields multiple cluster labels with the clustered entries of the adjacency matrix assigned to them. We have to convert them back to a set of symbol tables of components representing the clusters.
3) Generate middleware tags separating the clusters
o This will build the cluster-to-ROS connections.
o We won’t take account of ports of the super component and only consider connected top level components.
o A connection will be established if the target cluster label is different from the source cluster label thus connecting different clusters with each other.
4) Feed result into existing manual clustering architecture
Spectral Clustering in a nutshell
The goal of spectral clustering is to cluster data which is connected but not compact or not clustered within convex boundaries. Data is basically seen as a connected graph and clustering is the process of finding partitions in the graph based on the affinity (similarity or adjacency) of vertices.
The general approach is to perform dimensionality reduction before clustering in fewer dimensions using a standard clustering method (like k-means) on relevant eigenvectors (the "spectrum") of the matrix representation of a graph (Laplacian matrix).
Basically we follow three steps in spectral clustering
(1) Pre-processing
Construct a matrix representation of a graph
(2) Decomposition
* Compute eigenvalues and eigenvectors of the matrix
* Map each point to a lower-dimensional representation based on one or more eigenvectors
(3) Grouping
Assign points to two or more clusters, based on the new representation
Pre Pre-processing: How to define the affinity of data points and decide upon the connectivity of a similarity graph?
We have to define both, a way to calculate affinity (similarity function), and a respective graph representation (from which then the similarity matrix is derived). Typically a similarity function evaluates the distance, this can either be the classic Euclidian distance or a Gaussian Kernel similarity function.
The most wide spread approach to a graph representation is kNN, the k nearest neighbors of a vertex. In this approach the k nearest neighbors of a vertex v vote on where v belongs and thus should be connected to. The goal is to connect vertex vi with vertex vj if vj is among the k-nearest neighbors of vi.
Because this leads to a directed graph, we need an approach to convert it to an undirected one. This can be done in two ways: Either there's an edge if p is NN of q OR q is NN of p. Or there's an edge if p is NN of q AND q is NN of p (this is called mutual kNN, which is in practice a good choice).
Other possible approaches to decide on the connectivity of a similarity graph are "epsilon neighborhood" (a threshold based approach to construct a binary adjacency matrix) or a fully connected graph in combination with a Gaussian similarity function.
Affinity evaluation: Principal Component Analysis (PCA)
It is the affinity of data points, which defines clusters, rather than the absolute (spatial) location or spatial proximity.
Within an affinity matrix, data points belonging to the same cluster have a very similar affinity vector to all other data points (eigenvector). Each eigenvector has an eigenvalue which states how prevalent its vector is in the affinity matrix. So those eigenvectors act like a fingerprint for different clusters, representing all datapoints belonging to a specific cluster, in a lower dimensional space.
The Laplacian matrix
The Laplacian matrix L is defined as L= D-A, where D is the degree matrix (a diagonal matrix, containing the number of direct neighbors of a vertex) and A is the (binary) adjacency matrix (Aji=1 if vertecies i and j are connected with an edge, 0 otherwise).
Further sources of reading
A detailed discussion of different approaches to evaluating affinity and deeper information on spectral clustering in general can be found here: https://arxiv.org/abs/0711.0189
......@@ -2,8 +2,10 @@ package de.monticore.lang.monticar.generator.middleware;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.ExpandedComponentInstanceSymbol;
import de.monticore.lang.monticar.generator.FileContent;
import de.monticore.lang.monticar.generator.middleware.clustering.ClusterFromTagsHelper;
import de.monticore.lang.monticar.generator.middleware.helpers.*;
import de.monticore.lang.monticar.generator.middleware.impls.GeneratorImpl;
import de.monticore.lang.monticar.generator.middleware.impls.MiddlewareTagGenImpl;
import de.monticore.lang.tagging._symboltable.TaggingResolver;
import org.apache.commons.io.FileUtils;
......@@ -12,6 +14,16 @@ import java.io.IOException;
import java.util.*;
public class DistributedTargetGenerator extends CMakeGenerator {
private boolean generateMiddlewareTags = false;
public boolean isGenerateMiddlewareTags() {
return generateMiddlewareTags;
}
public void setGenerateMiddlewareTags(boolean generateMiddlewareTags) {
this.generateMiddlewareTags = generateMiddlewareTags;
}
private Set<String> subDirs = new HashSet<>();
......@@ -29,7 +41,7 @@ public class DistributedTargetGenerator extends CMakeGenerator {
fixComponentInstance(componentInstanceSymbol);
List<ExpandedComponentInstanceSymbol> clusterSubcomponents = ClusterHelper.getClusterSubcomponents(componentInstanceSymbol);
List<ExpandedComponentInstanceSymbol> clusterSubcomponents = ClusterFromTagsHelper.getClusterSubcomponents(componentInstanceSymbol);
if (clusterSubcomponents.size() > 0) {
clusterSubcomponents.forEach(clusterECIS -> {
String nameTargetLanguage = NameHelper.getNameTargetLanguage(clusterECIS.getFullName());
......@@ -54,6 +66,12 @@ public class DistributedTargetGenerator extends CMakeGenerator {
files.add(generateRosMsgGen());
}
if(generateMiddlewareTags){
MiddlewareTagGenImpl middlewareTagGen = new MiddlewareTagGenImpl();
middlewareTagGen.setGenerationTargetPath(generationTargetPath + "emam/");
files.addAll(middlewareTagGen.generate(componentInstanceSymbol,taggingResolver));
}
files.add(generateCMake(componentInstanceSymbol));
return files;
}
......
package de.monticore.lang.monticar.generator.middleware.clustering;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.ConnectorSymbol;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.ExpandedComponentInstanceSymbol;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.PortSymbol;
import de.monticore.lang.embeddedmontiarc.tagging.middleware.ros.RosConnectionSymbol;
import de.monticore.lang.monticar.ts.MCTypeSymbol;
import de.monticore.lang.monticar.ts.references.MCTypeReference;
import de.se_rwth.commons.logging.Log;
import java.util.*;
public class AutomaticClusteringHelper {
public static double[][] createAdjacencyMatrix(List<ExpandedComponentInstanceSymbol> subcomps, Collection<ConnectorSymbol> connectors, Map<String, Integer> subcompLabels) {
// Nodes = subcomponents
// Verts = connectors between subcomponents
double[][] res = new double[subcomps.size()][subcomps.size()];
connectors.forEach(con -> {
Optional<ExpandedComponentInstanceSymbol> sourceCompOpt = con.getSourcePort().getComponentInstance();
Optional<ExpandedComponentInstanceSymbol> targetCompOpt = con.getTargetPort().getComponentInstance();
if (sourceCompOpt.isPresent() && targetCompOpt.isPresent()) {
int index1 = subcompLabels.get(sourceCompOpt.get().getFullName());
int index2 = subcompLabels.get(targetCompOpt.get().getFullName());
res[index1][index2] = 1.0d;
res[index2][index1] = 1.0d;
} else {
Log.error("0xADE65: Component of source or target not found!");
}
});
return res;
}
public static void annotateComponentWithRosTagsForClusters(ExpandedComponentInstanceSymbol componentInstanceSymbol, List<Set<ExpandedComponentInstanceSymbol>> clusters) {
Collection<ConnectorSymbol> connectors = componentInstanceSymbol.getConnectors();
connectors.forEach(con -> {
// -1 = super comp
int sourceClusterLabel = -1;
int targetClusterLabel = -1;
ExpandedComponentInstanceSymbol sourceComp = con.getSourcePort().getComponentInstance().get();
ExpandedComponentInstanceSymbol targetComp = con.getTargetPort().getComponentInstance().get();
for(int i = 0; i < clusters.size(); i++){
if(clusters.get(i).contains(sourceComp)){
sourceClusterLabel = i;
}
if(clusters.get(i).contains(targetComp)){
targetClusterLabel = i;
}
}
if(sourceClusterLabel != targetClusterLabel){
con.getSourcePort().setMiddlewareSymbol(new RosConnectionSymbol());
con.getTargetPort().setMiddlewareSymbol(new RosConnectionSymbol());
}
});
}
public static double getTypeCostHeuristic(PortSymbol port){
return getTypeCostHeuristic(port.getTypeReference());
}
public static double getTypeCostHeuristic(MCTypeReference<? extends MCTypeSymbol> typeReference) {
//TODO: implement
//TODO: base types (Q,Z,B,C)
//TODO: Matrix types(recursive?)
//TODO: structs
return 0;
}
}
package de.monticore.lang.monticar.generator.middleware.helpers;
package de.monticore.lang.monticar.generator.middleware.clustering;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.*;
import de.monticore.symboltable.CommonScope;
......@@ -14,9 +14,9 @@ import org.jgrapht.graph.SimpleGraph;
import java.util.*;
import java.util.stream.Collectors;
public class ClusterHelper {
public class ClusterFromTagsHelper {
private ClusterHelper() {
private ClusterFromTagsHelper() {
}
public static List<Set<ExpandedComponentInstanceSymbol>> getClusters(ExpandedComponentInstanceSymbol componentInstanceSymbol) {
......@@ -146,79 +146,4 @@ public class ClusterHelper {
}
public static double[][] createAdjacencyMatrix(ExpandedComponentInstanceSymbol component) {
// Nodes = subcomponents
// Verts = connectors between subcomponents
Collection<ExpandedComponentInstanceSymbol> subcomps = component.getSubComponents().stream()
.sorted(Comparator.comparing(ExpandedComponentInstanceSymbol::getFullName))
.collect(Collectors.toList());
Map<String, Integer> componentIndecies = new HashMap<>();
String superCompName = component.getFullName();
int[] i = {0};
subcomps.forEach(sc -> componentIndecies.put(sc.getFullName(), i[0]++));
double[][] res = new double[subcomps.size()][subcomps.size()];
Collection<ConnectorSymbol> connectors = component.getConnectors().stream()
//filter out all connectors to super component
.filter(con -> !con.getSourcePort().getComponentInstance().get().getFullName().equals(superCompName)
&& !con.getTargetPort().getComponentInstance().get().getFullName().equals(superCompName))
.collect(Collectors.toList());
connectors.forEach(con -> {
Optional<ExpandedComponentInstanceSymbol> sourceCompOpt = con.getSourcePort().getComponentInstance();
Optional<ExpandedComponentInstanceSymbol> targetCompOpt = con.getTargetPort().getComponentInstance();
if (sourceCompOpt.isPresent() && targetCompOpt.isPresent()) {
int index1 = componentIndecies.get(sourceCompOpt.get().getFullName());
int index2 = componentIndecies.get(targetCompOpt.get().getFullName());
res[index1][index2] = 1.0d;
res[index2][index1] = 1.0d;
} else {
Log.error("0xADE65: Component of source or target not found!");
}
});
return res;
}
public static List<ConnectorSymbol> findConnectorsForRosTagging(int[] clusterlabels, ExpandedComponentInstanceSymbol component){
Collection<ExpandedComponentInstanceSymbol> subcomps = component.getSubComponents().stream()
.sorted(Comparator.comparing(ExpandedComponentInstanceSymbol::getFullName))
.collect(Collectors.toList());
Map<String, Integer> componentIndecies = new HashMap<>();
String superCompName = component.getFullName();
int[] i = {0};
subcomps.forEach(sc -> componentIndecies.put(sc.getFullName(), i[0]++));
Collection<ConnectorSymbol> connectors = component.getConnectors().stream()
//filter out all connectors to super component
.filter(con -> !con.getSourcePort().getComponentInstance().get().getFullName().equals(superCompName)
&& !con.getTargetPort().getComponentInstance().get().getFullName().equals(superCompName))
.collect(Collectors.toList());
List<ConnectorSymbol> res = new LinkedList<>();
connectors.forEach(con -> {
Optional<ExpandedComponentInstanceSymbol> sourceCompOpt = con.getSourcePort().getComponentInstance();
Optional<ExpandedComponentInstanceSymbol> targetCompOpt = con.getTargetPort().getComponentInstance();
if (sourceCompOpt.isPresent() && targetCompOpt.isPresent()) {
if (clusterlabels[componentIndecies.get(sourceCompOpt.get().getFullName())] !=
clusterlabels[componentIndecies.get(targetCompOpt.get().getFullName())]){
res.add(con);
}
} else {
Log.error("0xADE65: Component of source or target not found!");
}
});
return res;
}
}
package de.monticore.lang.monticar.generator.middleware.clustering;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.ExpandedComponentInstanceSymbol;
import java.util.List;
import java.util.Set;
// product if for clustering factory
public interface ClusteringAlgorithm {
public List<Set<ExpandedComponentInstanceSymbol>> cluster(ExpandedComponentInstanceSymbol component, int numClusters, Object... args);
}
package de.monticore.lang.monticar.generator.middleware.clustering;
import de.monticore.lang.monticar.generator.middleware.clustering.algorithms.SpectralClusteringAlgorithm;
import de.se_rwth.commons.logging.Log;
public class ClusteringAlgorithmFactory {
private ClusteringAlgorithmFactory(){
}
public static ClusteringAlgorithm getFromKind(ClusteringKind kind){
switch (kind){
case SPECTRAL_CLUSTERER: return new SpectralClusteringAlgorithm();
default: Log.error("0x1D54C: No clustering algorithm found for ClusteringKind " + kind);
}
return null;
}
}
package de.monticore.lang.monticar.generator.middleware.clustering;
public enum ClusteringKind {
SPECTRAL_CLUSTERER,
}
package de.monticore.lang.monticar.generator.middleware.clustering.algorithms;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.ExpandedComponentInstanceSymbol;
import de.monticore.lang.monticar.generator.middleware.clustering.AutomaticClusteringHelper;
import de.monticore.lang.monticar.generator.middleware.clustering.ClusteringAlgorithm;
import de.monticore.lang.monticar.generator.middleware.helpers.ComponentHelper;
import smile.clustering.SpectralClustering;
import java.util.*;
// spectral clusterer product implementation
public class SpectralClusteringAlgorithm implements ClusteringAlgorithm {
@Override
public List<Set<ExpandedComponentInstanceSymbol>> cluster(ExpandedComponentInstanceSymbol component, int numClusters, Object... args) {
List<ExpandedComponentInstanceSymbol> subcompsOrderedByName = ComponentHelper.getSubcompsOrderedByName(component);
Map<String, Integer> labelsForSubcomps = ComponentHelper.getLabelsForSubcomps(subcompsOrderedByName);
double[][] adjMatrix = AutomaticClusteringHelper.createAdjacencyMatrix(subcompsOrderedByName,
ComponentHelper.getInnerConnectors(component),
labelsForSubcomps);
SpectralClustering clustering;
SpectralClusteringBuilder builder = new SpectralClusteringBuilder(adjMatrix, numClusters);
// Handle optional additional params for SpectralClustering.
// Additional params come as one or multiple key-value-pairs in the optional varargs array for this method,
// with key as a string (containing the name of the parameter to pass thru to the spectral clusterer) followed by its value as an object
String key;
Object value;
int v = 0;
while (v < args.length) {
if (args[v] instanceof String) {
key = (String)args[v];
if (v+1 < args.length) {
value = args[v + 1];
switch (key) {
case "l":
if (value instanceof Integer) {
builder.setL((Integer) value);
}
break;
case "sigma":
if (value instanceof Double) {
builder.setSigma((Double) value);
}
break;
}
}
}
v = v + 2;
}
clustering = builder.build();
//SpectralClustering clustering = new SpectralClustering(adjMatrix,numberOfClusters);
int[] labels = clustering.getClusterLabel();
List<Set<ExpandedComponentInstanceSymbol>> res = new ArrayList<>();
for(int i = 0; i < clustering.getNumClusters(); i++){
res.add(new HashSet<>());
}
subcompsOrderedByName.forEach(sc -> {
int curClusterLabel = labels[labelsForSubcomps.get(sc.getFullName())];
res.get(curClusterLabel).add(sc);
});
return res;
}
}
package de.monticore.lang.monticar.generator.middleware.clustering.algorithms;
import smile.clustering.SpectralClustering;
public class SpectralClusteringBuilder {
private double[][] data;
private Integer k;
private Integer l;
private Double sigma;
public SpectralClusteringBuilder(double[][] data, int k) {
this.data = data;
this.k = k;
}
public SpectralClusteringBuilder setData(double[][] data) {
this.data = data;
return this;
}
public SpectralClusteringBuilder setK(int k) {
this.k = k;
return this;
}
public SpectralClusteringBuilder setL(int l) {
this.l = l;
return this;
}
public SpectralClusteringBuilder setSigma(double sigma) {
this.sigma = sigma;
return this;
}
public SpectralClustering build() {
SpectralClustering sc;
if (this.l != null && this.sigma != null) sc = new SpectralClustering(data, k, l, sigma); else
if (this.sigma != null) sc = new SpectralClustering(data, k, sigma); else
sc = new SpectralClustering(data, k);
return sc;
}
}
package de.monticore.lang.monticar.generator.middleware.helpers;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.ConnectorSymbol;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.ExpandedComponentInstanceSymbol;
import java.util.*;
import java.util.stream.Collectors;
public class ComponentHelper {
public static List<ExpandedComponentInstanceSymbol> getSubcompsOrderedByName(ExpandedComponentInstanceSymbol componentInstanceSymbol){
return componentInstanceSymbol.getSubComponents().stream()
.sorted(Comparator.comparing(ExpandedComponentInstanceSymbol::getFullName))
.collect(Collectors.toList());
}
public static Collection<ConnectorSymbol> getInnerConnectors(ExpandedComponentInstanceSymbol componentInstanceSymbol){
String superCompName = componentInstanceSymbol.getFullName();
return componentInstanceSymbol.getConnectors().stream()
//filter out all connectors to super component
.filter(con -> !con.getSourcePort().getComponentInstance().get().getFullName().equals(superCompName)
&& !con.getTargetPort().getComponentInstance().get().getFullName().equals(superCompName))
.collect(Collectors.toList());
}
public static Map<String, Integer> getLabelsForSubcomps(List<ExpandedComponentInstanceSymbol> subcomps) {
Map<String, Integer> componentIndecies = new HashMap<>();
int[] i = {0};
subcomps.forEach(sc -> componentIndecies.put(sc.getFullName(), i[0]++));
return componentIndecies;
}
}
package de.monticore.lang.monticar.generator.middleware.impls;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.ExpandedComponentInstanceSymbol;
import de.monticore.lang.monticar.emadl.generator.Backend;
import de.monticore.lang.monticar.emadl.generator.EMADLGenerator;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.ExpandedComponentInstanceSymbol;
import de.monticore.lang.monticar.generator.FileContent;
import de.monticore.lang.monticar.generator.middleware.helpers.NameHelper;
import de.monticore.lang.monticar.generator.middleware.helpers.TemplateHelper;
import de.monticore.lang.tagging._symboltable.TaggingResolver;
import de.se_rwth.commons.logging.Log;
......@@ -43,7 +42,6 @@ public class EMADLGeneratorImpl implements GeneratorImpl {
files.add(emadlGenerator.getEmamGen().generateFile(fileContent));
}
files.add(emadlGenerator.getEmamGen().generateFile((generateCMake(componentInstanceSymbol))));
return files;
}
......@@ -52,13 +50,5 @@ public class EMADLGeneratorImpl implements GeneratorImpl {
this.generationTargetPath = path;
}
private FileContent generateCMake(ExpandedComponentInstanceSymbol componentInstanceSymbol) {
FileContent cmake = new FileContent();
cmake.setFileName("CMakeLists.txt");
String name = NameHelper.getNameTargetLanguage(componentInstanceSymbol.getFullName());
cmake.setFileContent(TemplateHelper.getCoordinatorCmakeListsTemplate().replace("${compName}", name));
cmake.setFileContent(cmake.getFileContent() + "target_link_libraries(" + name + " -lmxnet)");
return cmake;
}
}
package de.monticore.lang.monticar.generator.middleware.impls;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.ExpandedComponentInstanceSymbol;