diff --git a/pom.xml b/pom.xml
index a28f0d186ce5df0f5e63eda5a009f7ca2ad08ae6..6c7631bf22d01b4d3d00f1391cf40c95ca8fdda3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -120,6 +120,20 @@
${Embedded-montiarc-math-rosmsg-generator.version}
+
+ org.graphstream
+ gs-core
+ 1.3
+ false
+
+
+
+ org.apache.commons
+ commons-math
+ 2.1
+ false
+
+
de.monticore.lang.monticar
diff --git a/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/AutomaticClusteringHelper.java b/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/AutomaticClusteringHelper.java
index 9d2dbd6e8ce1b83455640963761c07cf79158753..2dc1ed86dc99da4fc4be4bbb7f6ca03660f81c4b 100644
--- a/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/AutomaticClusteringHelper.java
+++ b/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/AutomaticClusteringHelper.java
@@ -20,8 +20,6 @@ import java.util.stream.Collectors;
public class AutomaticClusteringHelper {
- static double MAXCOST= 999999;
-
public static double[][] createAdjacencyMatrix(List subcomps, Collection connectors, Map subcompLabels) {
// Nodes = subcomponents
// Verts = connectors between subcomponents
diff --git a/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/SimpleModelViewer.java b/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/SimpleModelViewer.java
index e78ecb8b048095cdf7bebb01fcfd95e370961a93..4bf0b22a6223acecad745559b866a5aa0086b513 100644
--- a/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/SimpleModelViewer.java
+++ b/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/SimpleModelViewer.java
@@ -1,4 +1,43 @@
package de.monticore.lang.monticar.generator.middleware.clustering;
-public class SimpleModelViewer {
-}
+import org.graphstream.graph.Graph;
+import org.graphstream.ui.view.Viewer;
+import org.graphstream.ui.view.ViewerListener;
+import org.graphstream.ui.view.ViewerPipe;
+
+import javax.swing.*;
+
+public class SimpleModelViewer implements ViewerListener {
+
+ private Graph graph;
+ protected boolean loop = true;
+
+ public SimpleModelViewer(Graph g) {
+ this.graph = g;
+ }
+
+ public void run() {
+ Viewer viewer = this.graph.display();
+ viewer.getDefaultView().add(new JLabel(graph.getId().toString()));
+ viewer.setCloseFramePolicy(Viewer.CloseFramePolicy.CLOSE_VIEWER); // set to "HIDE_ONLY" to allow for further communication
+ ViewerPipe fromViewer = viewer.newViewerPipe();
+ fromViewer.addViewerListener(this);
+ //fromViewer.addSink(this.graph);
+ while(loop) {
+ fromViewer.pump();
+ }
+ }
+
+ public void viewClosed(String id) {
+ this.loop = false;
+ }
+
+ public void buttonPushed(String id) {
+ //System.out.println("Button pushed on node "+id);
+ }
+
+ public void buttonReleased(String id) {
+ //System.out.println("Button released on node "+id);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/de/monticore/lang/monticar/generator/middleware/helpers/ComponentHelper.java b/src/main/java/de/monticore/lang/monticar/generator/middleware/helpers/ComponentHelper.java
index 493ab97272fa504e84a9922e27564e2449cac6fa..0a5ed0797fd3eb84d80bae1d84d9f9077767dea6 100644
--- a/src/main/java/de/monticore/lang/monticar/generator/middleware/helpers/ComponentHelper.java
+++ b/src/main/java/de/monticore/lang/monticar/generator/middleware/helpers/ComponentHelper.java
@@ -29,4 +29,12 @@ public class ComponentHelper {
subcomps.forEach(sc -> componentIndecies.put(sc.getFullName(), i[0]++));
return componentIndecies;
}
+
+ public static Map getSubcompsLabels(List subcomps) {
+ Map componentIndecies = new HashMap<>();
+
+ int[] i = {0};
+ subcomps.forEach(sc -> componentIndecies.put(i[0]++, sc.getFullName()));
+ return componentIndecies;
+ }
}
diff --git a/src/test/java/de/monticore/lang/monticar/generator/middleware/AutomaticClusteringTest.java b/src/test/java/de/monticore/lang/monticar/generator/middleware/AutomaticClusteringTest.java
index 18f246cbf5459dd5b0a010d22d8783e0bc87acef..6ca0bb3c347ab93e67d544487c7c02cb1010117a 100644
--- a/src/test/java/de/monticore/lang/monticar/generator/middleware/AutomaticClusteringTest.java
+++ b/src/test/java/de/monticore/lang/monticar/generator/middleware/AutomaticClusteringTest.java
@@ -3,10 +3,7 @@ package de.monticore.lang.monticar.generator.middleware;
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.monticar.generator.middleware.clustering.AutomaticClusteringHelper;
-import de.monticore.lang.monticar.generator.middleware.clustering.ClusteringAlgorithm;
-import de.monticore.lang.monticar.generator.middleware.clustering.ClusteringAlgorithmFactory;
-import de.monticore.lang.monticar.generator.middleware.clustering.ClusteringKind;
+import de.monticore.lang.monticar.generator.middleware.clustering.*;
import de.monticore.lang.monticar.generator.middleware.clustering.algorithms.*;
import com.clust4j.algo.AffinityPropagation;
import com.clust4j.algo.AffinityPropagationParameters;
@@ -23,10 +20,17 @@ import net.sf.javaml.core.DenseInstance;
import net.sf.javaml.core.Instance;
import org.apache.commons.math3.linear.Array2DRowRealMatrix;
import org.apache.commons.math3.linear.RealMatrix;
+import org.graphstream.graph.implementations.SingleGraph;
+import org.graphstream.stream.file.FileSinkImages;
+import org.graphstream.ui.view.Viewer;
+import org.graphstream.ui.view.ViewerPipe;
import org.junit.Test;
import smile.clustering.DBSCAN;
import smile.clustering.SpectralClustering;
+import de.monticore.lang.monticar.svggenerator.SVGMain;
+import org.graphstream.graph.*;
+import javax.swing.*;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@@ -38,6 +42,7 @@ import static org.junit.Assert.assertTrue;
public class AutomaticClusteringTest extends AbstractSymtabTest{
public static final String TEST_PATH = "src/test/resources/";
+ public static final String TEST_PATH_PNG = "src/test/resources/clustering/test-images/";
@Test
@@ -435,6 +440,52 @@ public class AutomaticClusteringTest extends AbstractSymtabTest{
@Test
public void testClusteringAlgorithms(){
+ TaggingResolver taggingResolver = AbstractSymtabTest.createSymTabAndTaggingResolver(TEST_PATH);
+
+ //String modelName= "clustering.unambiguousCluster";
+ String modelName= "clustering.midSizeDemoCluster";
+
+ ExpandedComponentInstanceSymbol componentInstanceSymbol = taggingResolver.resolve(modelName, ExpandedComponentInstanceSymbol.KIND).orElse(null);
+
+ assertNotNull(componentInstanceSymbol);
+
+
+ // get stuff together for adjmatrix
+ List subcompsOrderedByName = ComponentHelper.getSubcompsOrderedByName(componentInstanceSymbol);
+ Map labelsForSubcomps = ComponentHelper.getLabelsForSubcomps(subcompsOrderedByName);
+ Map subcompsLabels = ComponentHelper.getSubcompsLabels(subcompsOrderedByName);
+ double[][] adjMatrix = AutomaticClusteringHelper.createAdjacencyMatrix(subcompsOrderedByName,
+ ComponentHelper.getInnerConnectors(componentInstanceSymbol),
+ labelsForSubcomps);
+ // build a graph from this stuff
+ Graph graph = new SingleGraph(modelName);
+ Node node= null;
+ Edge edge= null;
+ String subCompLabel= null;
+ for(int i = 0; i < adjMatrix[0].length; i++) {
+ node= graph.addNode(Integer.toString(i));
+ subCompLabel= subcompsLabels.get(Integer.parseInt(node.getId()));
+ subCompLabel= subCompLabel.substring(subCompLabel.lastIndexOf('.') + 1);
+ node.addAttribute("ui.label", node.getId() + " (" + subCompLabel + ")");
+ }
+ for(int i = 0; i < adjMatrix[0].length; i++) {
+ for(int j = i; j < adjMatrix[0].length; j++) {
+ if (adjMatrix[i][j] > 0) {
+ edge= graph.addEdge(i + "-" + j, Integer.toString(i), Integer.toString(j));
+ edge.addAttribute("ui.label", adjMatrix[i][j]);
+ }
+ }
+ }
+
+ FileSinkImages img = new FileSinkImages(FileSinkImages.OutputType.PNG, FileSinkImages.Resolutions.XGA);
+ img.setStyleSheet("graph { padding: 100px; }");
+ img.setLayoutPolicy(FileSinkImages.LayoutPolicy.COMPUTED_FULLY_AT_NEW_IMAGE);
+ try { img.writeAll(graph, TEST_PATH_PNG + modelName + ".png"); } catch (IOException e) { System.out.println("Couldn't create image file "+TEST_PATH_PNG + modelName + ".png"+
+ "\n"+e.getMessage()); };
+
+ SimpleModelViewer viewer= new SimpleModelViewer(graph);
+ viewer.run();
+
Object[] params;
for(ClusteringKind kind : ClusteringKind.values()){
params= null;
@@ -449,24 +500,148 @@ public class AutomaticClusteringTest extends AbstractSymtabTest{
};
break;
}
- testCreateClusters(ClusteringAlgorithmFactory.getFromKind(kind), params);
+ testCreateClusters(ClusteringAlgorithmFactory.getFromKind(kind), params, componentInstanceSymbol, modelName);
}
}
- private void testCreateClusters(ClusteringAlgorithm algorithm, Object[] params){
- //UnambiguousCluster
- TaggingResolver taggingResolver = AbstractSymtabTest.createSymTabAndTaggingResolver(TEST_PATH);
+ private void testCreateClusters(ClusteringAlgorithm algorithm, Object[] params, ExpandedComponentInstanceSymbol componentInstanceSymbol, String modelName){
- ExpandedComponentInstanceSymbol componentInstanceSymbol = taggingResolver.resolve("clustering.unambiguousCluster", ExpandedComponentInstanceSymbol.KIND).orElse(null);
- assertNotNull(componentInstanceSymbol);
-
- System.out.println(algorithm);
+ String algoName= algorithm.toString().substring(0, algorithm.toString().lastIndexOf("@"));
+ String algoNameShort= algoName.substring(algoName.lastIndexOf(".")+1);
+ System.out.println(algoName);
List> clusters = null;
if (params != null) clusters = algorithm.cluster(componentInstanceSymbol, params); else
clusters = algorithm.cluster(componentInstanceSymbol);
+ double colorIncrement= 1.0/clusters.size();
+ double sizeIncrement= Math.ceil(50/clusters.size());
+ double color= 0;
+ double size= 10;
+
+ // get stuff together for adjmatrix
+ List subcompsOrderedByName = ComponentHelper.getSubcompsOrderedByName(componentInstanceSymbol);
+ Map labelsForSubcomps = ComponentHelper.getLabelsForSubcomps(subcompsOrderedByName);
+ Map subcompsLabels = ComponentHelper.getSubcompsLabels(subcompsOrderedByName);
+ double[][] adjMatrix = AutomaticClusteringHelper.createAdjacencyMatrix(subcompsOrderedByName,
+ ComponentHelper.getInnerConnectors(componentInstanceSymbol),
+ labelsForSubcomps);
+ // build a graph from this stuff
+ Graph graph = new SingleGraph(algoNameShort);
+ Node node= null;
+ Edge edge= null;
+ String subCompLabel= null;
+ for(int i = 0; i < adjMatrix[0].length; i++) {
+ node= graph.addNode(Integer.toString(i));
+ subCompLabel= subcompsLabels.get(Integer.parseInt(node.getId()));
+ subCompLabel= subCompLabel.substring(subCompLabel.lastIndexOf('.') + 1);
+ node.addAttribute("ui.label", node.getId() + " (" + subCompLabel + ")");
+ }
+ for(int i = 0; i < adjMatrix[0].length; i++) {
+ for(int j = i; j < adjMatrix[0].length; j++) {
+ if (adjMatrix[i][j] > 0) {
+ edge= graph.addEdge(i + "-" + j, Integer.toString(i), Integer.toString(j));
+ edge.addAttribute("ui.label", adjMatrix[i][j]);
+ }
+ }
+ }
+
+ // style (colorize + resize) nodes for clusters
+ Node n;
+ Edge e;
+ String nodeName;
+ String nodeId;
+ Set cluster;
+ List clusterNames;
+ for(int i = 0; i < clusters.size(); i++) {
+ cluster = clusters.get(i);
+ clusterNames = cluster.stream().map(CommonSymbol::getFullName).collect(Collectors.toList());
+ for(int j = 0; j < clusterNames.size(); j++) {
+ nodeId= null;
+ nodeId= labelsForSubcomps.get(clusterNames.get(j)).toString();
+ if (nodeId!=null) {
+ n= graph.getNode(nodeId);
+ n.setAttribute("ui.style", "fill-mode: dyn-plain; fill-color: red, black; size: " + size + "px;");
+ n.setAttribute("ui.color", color);
+
+ // find cutting edges and delete or re-color them
+ for(int k = 0; k < adjMatrix[Integer.parseInt(nodeId)].length; k++) {
+ if (adjMatrix[Integer.parseInt(nodeId)][k] > 0) {
+ // target node k is not in current cluster
+ nodeName= subcompsLabels.get(k);
+ if (!clusterNames.contains(nodeName)) {
+ e= null;
+ e= graph.getEdge(Integer.parseInt(nodeId)+"-"+k);
+ //graph.removeEdge(e);
+ if (e!=null) e.setAttribute("ui.style", "fill-mode: plain; fill-color: #F0F0F0;");
+ }
+ }
+ }
+ }
+ }
+ color= color + colorIncrement;
+ size= size + sizeIncrement;
+ }
+
+ FileSinkImages img = new FileSinkImages(FileSinkImages.OutputType.PNG, FileSinkImages.Resolutions.XGA);
+ img.setStyleSheet("graph { padding: 100px; }");
+ img.setLayoutPolicy(FileSinkImages.LayoutPolicy.COMPUTED_FULLY_AT_NEW_IMAGE);
+ try { img.writeAll(graph, TEST_PATH_PNG + modelName + "/" + graph.getId() + ".png"); } catch (IOException ex) { System.out.println("Couldn't create image file "+TEST_PATH_PNG + graph.getId() + ".png"+
+ "\n"+ex.getMessage()); };
+
+ SimpleModelViewer viewer= new SimpleModelViewer(graph);
+ viewer.run();
+
+ if (modelName=="clustering.midSizeDemoCluster") {
+ assertTrue(clusters.size() == 2);
+
+ Set cluster1 = clusters.get(0);
+ Set cluster2 = clusters.get(1);
+ assertTrue((cluster1.size() == 3 && cluster2.size() == 4) ||
+ (cluster2.size() == 3 && cluster1.size() == 4)
+ );
+
+ List cluster1Names = cluster1.stream()
+ .map(CommonSymbol::getFullName)
+ .collect(Collectors.toList());
+
+ List cluster2Names = cluster2.stream()
+ .map(CommonSymbol::getFullName)
+ .collect(Collectors.toList());
+
+ if (cluster1.size() == 4) {
+ if (cluster1Names.get(0).endsWith("comp0") ||
+ cluster1Names.get(0).endsWith("comp1") ||
+ cluster1Names.get(0).endsWith("comp2") ||
+ cluster1Names.get(0).endsWith("comp3")
+ ) {
+ assertTrue(cluster1Names.contains(modelName + ".comp0"));
+ assertTrue(cluster1Names.contains(modelName + ".comp1"));
+ assertTrue(cluster1Names.contains(modelName + ".comp2"));
+ assertTrue(cluster1Names.contains(modelName + ".comp3"));
+
+ assertTrue(cluster2Names.contains(modelName + ".comp4"));
+ assertTrue(cluster2Names.contains(modelName + ".comp5"));
+ assertTrue(cluster2Names.contains(modelName + ".comp6"));
+ }
+ } else if (cluster1.size() == 3) {
+ if (cluster1Names.get(0).endsWith("comp4") ||
+ cluster1Names.get(0).endsWith("comp5") ||
+ cluster1Names.get(0).endsWith("comp6")
+ ) {
+ assertTrue(cluster2Names.contains(modelName + ".comp0"));
+ assertTrue(cluster2Names.contains(modelName + ".comp1"));
+ assertTrue(cluster2Names.contains(modelName + ".comp2"));
+ assertTrue(cluster2Names.contains(modelName + ".comp3"));
+
+ assertTrue(cluster1Names.contains(modelName + ".comp4"));
+ assertTrue(cluster1Names.contains(modelName + ".comp5"));
+ assertTrue(cluster1Names.contains(modelName + ".comp6"));
+ }
+ }
+ }
+ if (modelName=="clustering.unambiguousCluster") {
if (algorithm instanceof SpectralClusteringAlgorithm) {
assertTrue(clusters.size() == 2);
@@ -522,6 +697,7 @@ public class AutomaticClusteringTest extends AbstractSymtabTest{
assertTrue(cluster3Names.contains("clustering.unambiguousCluster.compC"));
assertTrue(cluster4Names.contains("clustering.unambiguousCluster.compD"));
}
+ }
}
diff --git a/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster.png b/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster.png
new file mode 100644
index 0000000000000000000000000000000000000000..10f3371181c4f545555f22c09ec288f43323a8b5
Binary files /dev/null and b/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster.png differ
diff --git a/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/AffinityPropagationAlgorithm.png b/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/AffinityPropagationAlgorithm.png
new file mode 100644
index 0000000000000000000000000000000000000000..b0cf79fd2a042e38aa46eac41a07c56d1a13fe34
Binary files /dev/null and b/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/AffinityPropagationAlgorithm.png differ
diff --git a/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/DBSCANClusteringAlgorithm.png b/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/DBSCANClusteringAlgorithm.png
new file mode 100644
index 0000000000000000000000000000000000000000..9152273b91a4823adc7a373ddb424d5b390e8296
Binary files /dev/null and b/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/DBSCANClusteringAlgorithm.png differ
diff --git a/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/MarkovClusteringAlgorithm.png b/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/MarkovClusteringAlgorithm.png
new file mode 100644
index 0000000000000000000000000000000000000000..b42c55d09543c84ac42b080051a48702db29e474
Binary files /dev/null and b/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/MarkovClusteringAlgorithm.png differ
diff --git a/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/SpectralClusteringAlgorithm.png b/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/SpectralClusteringAlgorithm.png
new file mode 100644
index 0000000000000000000000000000000000000000..bc4a0be31719ebdff3feda159703f495df1710c2
Binary files /dev/null and b/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/SpectralClusteringAlgorithm.png differ
diff --git a/src/test/resources/clustering/test-images/clustering.unambiguousCluster.png b/src/test/resources/clustering/test-images/clustering.unambiguousCluster.png
new file mode 100644
index 0000000000000000000000000000000000000000..8187fac0ac919984d7bb9ecb14617c27248cfe8c
Binary files /dev/null and b/src/test/resources/clustering/test-images/clustering.unambiguousCluster.png differ
diff --git a/src/test/resources/clustering/test-images/clustering.unambiguousCluster/AffinityPropagationAlgorithm.png b/src/test/resources/clustering/test-images/clustering.unambiguousCluster/AffinityPropagationAlgorithm.png
new file mode 100644
index 0000000000000000000000000000000000000000..b425daf5683a8b2463ddab429a8bda502f436e04
Binary files /dev/null and b/src/test/resources/clustering/test-images/clustering.unambiguousCluster/AffinityPropagationAlgorithm.png differ
diff --git a/src/test/resources/clustering/test-images/clustering.unambiguousCluster/DBSCANClusteringAlgorithm.png b/src/test/resources/clustering/test-images/clustering.unambiguousCluster/DBSCANClusteringAlgorithm.png
new file mode 100644
index 0000000000000000000000000000000000000000..0c3275a3633d9f6e4d75ca8d97a5e810c474c045
Binary files /dev/null and b/src/test/resources/clustering/test-images/clustering.unambiguousCluster/DBSCANClusteringAlgorithm.png differ
diff --git a/src/test/resources/clustering/test-images/clustering.unambiguousCluster/MarkovClusteringAlgorithm.png b/src/test/resources/clustering/test-images/clustering.unambiguousCluster/MarkovClusteringAlgorithm.png
new file mode 100644
index 0000000000000000000000000000000000000000..747a9e7b8e5a1843cfb09bfae527f128c0231d93
Binary files /dev/null and b/src/test/resources/clustering/test-images/clustering.unambiguousCluster/MarkovClusteringAlgorithm.png differ
diff --git a/src/test/resources/clustering/test-images/clustering.unambiguousCluster/SpectralClusteringAlgorithm.png b/src/test/resources/clustering/test-images/clustering.unambiguousCluster/SpectralClusteringAlgorithm.png
new file mode 100644
index 0000000000000000000000000000000000000000..e631d6dd8fecf17e82f1c03095c3d53187c9a35b
Binary files /dev/null and b/src/test/resources/clustering/test-images/clustering.unambiguousCluster/SpectralClusteringAlgorithm.png differ