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