From 044e4b2a4011f3286be2e7fecaa4bf4078ed40d8 Mon Sep 17 00:00:00 2001 From: Eddie Groh <groh@fortiss.org> Date: Mon, 31 Jul 2023 13:46:04 +0200 Subject: [PATCH] Added betweeness centrality calculation Issue-Ref: 4310 Issue-Url: https://git.fortiss.org/af3/af3/-/issues/4310 Signed-off-by: Eddie Groh <groh@fortiss.org> --- .../tooling/ext/quality/data/MetricKey.java | 2 + .../quality/service/ModelQualityService.java | 163 ++++++++++++++++++ 2 files changed, 165 insertions(+) diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricKey.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricKey.java index 148d1d752..3d494172e 100644 --- a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricKey.java +++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricKey.java @@ -40,6 +40,8 @@ public enum MetricKey { SAFETY_LEVEL(false, true), /** How deeply nested the corresponding element is */ NESTING_LEVEL(false), + /** The betweenness centrality of the corresponding element */ + BETWEENESS_CENTRALITY(false), // // Metrics which are collected over all children nodes diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/ModelQualityService.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/ModelQualityService.java index 0991e64a1..2447695e8 100644 --- a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/ModelQualityService.java +++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/ModelQualityService.java @@ -29,15 +29,25 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.Queue; +import java.util.Set; +import java.util.Stack; import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; +import org.fortiss.tooling.base.model.base.EntryConnectorBase; +import org.fortiss.tooling.base.model.base.ExitConnectorBase; +import org.fortiss.tooling.base.model.element.IConnection; +import org.fortiss.tooling.base.model.element.IConnector; import org.fortiss.tooling.base.model.element.IHierarchicElement; import org.fortiss.tooling.ext.quality.IMetricProvider; import org.fortiss.tooling.ext.quality.data.MetricDataManager; @@ -189,6 +199,159 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider } manager.getHierarchicLookupTable().put(currentElement, node); + + calculateBetweennessCentrality(currentElement, manager); + } + } + + /** + * Creates a list of nodes contained in the graph centered around the provided element + * It will contain all elements contained within the provided element as well as all elements + * connected to the provided element + * + * @param scopeElement + * element defining the scope of the graph + * @return node list + */ + private static Set<IHierarchicElement> getLocalGraphView(IHierarchicElement scopeElement) { + // Create a view consisting of all nodes inside this element, as well as all elements + // connected to this element + + // This ensures that elements inside this element communicating with elements outside + // this element are properly recognized + Set<IHierarchicElement> graphNodes = new HashSet<>(); + graphNodes.addAll(scopeElement.getContainedElements()); + + // Collect all elements to which currentElement has a outgoing connection + for(ExitConnectorBase exitConnector : EcoreUtils.pickInstanceOf(ExitConnectorBase.class, + scopeElement.getConnectors())) { + for(IConnection exitConnection : exitConnector.getOutgoing()) { + graphNodes.add(exitConnection.getTarget().getOwner()); + } + } + // Collect all elements to which currentElement has a incoming connection + for(EntryConnectorBase entryConnector : EcoreUtils.pickInstanceOf(EntryConnectorBase.class, + scopeElement.getConnectors())) { + for(IConnection entryConnection : entryConnector.getIncoming()) { + graphNodes.add(entryConnection.getSource().getOwner()); + } + } + return graphNodes; + } + + /** + * Calculates and saves the betweenness centrality of all elements contained inside the provided + * scopeElement + * + * @param scopeElement + * the scope of this calculation + * @param manager + * location to save the metrics + */ + private static void calculateBetweennessCentrality(IHierarchicElement scopeElement, + MetricDataManager manager) { + + // Abort if no elements are found + if(!scopeElement.getContainedElements().isEmpty()) { + + // Calculate betweenness centrality + + Set<IHierarchicElement> graphNodes = getLocalGraphView(scopeElement); + // Initialize the betweenness values for all nodes to zero + Map<IHierarchicElement, Double> betweenness = new HashMap<>(); + for(IHierarchicElement elementNode : graphNodes) { + betweenness.put(elementNode, 0.0); + } + + // Iterate over all collected nodes + for(IHierarchicElement elementNode : graphNodes) { + + // Initialize all variables + Stack<IHierarchicElement> stack = new Stack<>(); + Map<IHierarchicElement, List<IHierarchicElement>> pred = new HashMap<>(); + Map<IHierarchicElement, Integer> dist = new HashMap<>(); + Map<IHierarchicElement, Double> sigma = new HashMap<>(); + Queue<IHierarchicElement> queue = new LinkedList<>(); + + for(IHierarchicElement v : graphNodes) { + pred.put(v, new ArrayList<>()); + dist.put(v, -1); + sigma.put(v, 0.0); + } + sigma.put(elementNode, 1.0); + dist.put(elementNode, 0); + queue.add(elementNode); + + while(!queue.isEmpty()) { + IHierarchicElement v = queue.remove(); + stack.push(v); + + // Iterate over all connectors going out from the element + for(ExitConnectorBase exitConnectors : EcoreUtils + .pickInstanceOf(ExitConnectorBase.class, v.getConnectors())) { + // Iterate over all connections + for(IConnection outgoingConnection : exitConnectors.getOutgoing()) { + + List<IHierarchicElement> targetElements = new ArrayList<>(); + IHierarchicElement primaryTargetElement = + outgoingConnection.getTarget().getOwner(); + // When we have a connection to the scopeElement, we have a connection + // leaving the local scope + if(scopeElement == primaryTargetElement) { + // Continue searching for outgoing connections on the target + // Connector + IConnector targetConnector = outgoingConnection.getTarget(); + for(IConnection secondaryOutgoingConnection : targetConnector + .getOutgoing()) { + targetElements.add( + secondaryOutgoingConnection.getTarget().getOwner()); + } + } else { + targetElements.add(primaryTargetElement); + } + + for(IHierarchicElement targetElement : targetElements) { + + // Check if element is in node list, to avoid creating data for + // nodes outside of our graph + if(graphNodes.contains(targetElement)) { + if(dist.get(targetElement) < 0) { + queue.add(targetElement); + dist.put(targetElement, dist.get(v) + 1); + } + if(dist.get(targetElement) == dist.get(v) + 1) { + sigma.put(targetElement, + sigma.get(targetElement) + sigma.get(v)); + pred.get(targetElement).add(v); + } + } + } + } + } + } + // Initialize delta + Map<IHierarchicElement, Double> delta = new HashMap<>(); + for(IHierarchicElement v : graphNodes) { + delta.put(v, 0.0); + } + + while(!stack.isEmpty()) { + IHierarchicElement w = stack.pop(); + for(IHierarchicElement v : pred.get(w)) { + double c = (sigma.get(v) / sigma.get(w)) * (1.0 + delta.get(w)); + delta.put(v, delta.get(v) + c); + } + if(!w.equals(elementNode)) { + betweenness.put(w, betweenness.get(w) + delta.get(w)); + } + } + } + + // Save metric + for(IHierarchicElement child : scopeElement.getContainedElements()) { + manager.getHierarchicLookupTable().get(child).getStoredMetrics() + .put(MetricKey.BETWEENESS_CENTRALITY, betweenness.get(child)); + } } } -- GitLab