diff --git a/java/com.sap.sailing.windestimation.test/src/com/sap/sailing/windestimation/aggregator/msthmm/DijkstraTest.java b/java/com.sap.sailing.windestimation.test/src/com/sap/sailing/windestimation/aggregator/msthmm/DijkstraTest.java index d762159bb62..95f97f44dde 100755 --- a/java/com.sap.sailing.windestimation.test/src/com/sap/sailing/windestimation/aggregator/msthmm/DijkstraTest.java +++ b/java/com.sap.sailing.windestimation.test/src/com/sap/sailing/windestimation/aggregator/msthmm/DijkstraTest.java @@ -1,9 +1,13 @@ package com.sap.sailing.windestimation.aggregator.msthmm; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -11,7 +15,10 @@ import org.junit.Before; import org.junit.Test; import com.sap.sailing.windestimation.aggregator.msthmm.graph.DijkstraShortestPathFinderImpl; +import com.sap.sailing.windestimation.aggregator.msthmm.graph.ElementAdjacencyQualityMetric; import com.sap.sailing.windestimation.aggregator.msthmm.graph.ElementWithQuality; +import com.sap.sailing.windestimation.aggregator.msthmm.graph.GroupOutOfWhichToPickTheBestElement; +import com.sap.sailing.windestimation.aggregator.msthmm.graph.InnerGraphSuccessorSupplier; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.impl.NamedImpl; @@ -38,6 +45,37 @@ public class DijkstraTest { } } + private static class CompositeTreeNode implements GroupOutOfWhichToPickTheBestElement { + private final CompositeTreeNode parent; + private final Node[] elements; + private final Set children; + + public CompositeTreeNode(CompositeTreeNode parent, Node... elements) { + this.parent = parent; + this.elements = elements; + this.children = new HashSet<>(); + } + + @Override + public CompositeTreeNode getParent() { + return parent; + } + + @Override + public Iterable getChildren() { + return Collections.unmodifiableCollection(children); + } + + @Override + public Iterable getElements() { + return Arrays.asList(elements); + } + + public boolean addChild(CompositeTreeNode child) { + return this.children.add(child); + } + } + @Before public void setUp() { dijkstra = new DijkstraShortestPathFinderImpl(); @@ -47,6 +85,77 @@ public class DijkstraTest { edgeQuality = new HashMap<>(); } + @Test + public void testSimpleCompositeTree() { + // set up overarching tree structure: + // root + // | + // level1 + // /\ + // level2Left level2Right + // | | + // level3Left level3Right + // Each of these tree nodes contains two inner graph nodes + final CompositeTreeNode root = new CompositeTreeNode(/* parent */ null, new Node("Root1", 0.5), new Node("Root2", 0.5)); + final CompositeTreeNode level1 = new CompositeTreeNode(/* parent */ root, new Node("L1_1", 0.5), new Node("L1_2", 0.5)); + root.addChild(level1); + final CompositeTreeNode level2Left = new CompositeTreeNode(/* parent */ level1, new Node("L2L1", 0.1), new Node("L2L2", 0.9)); + level1.addChild(level2Left); + final CompositeTreeNode level2Right = new CompositeTreeNode(/* parent */ level1, new Node("L2R1", 0.9), new Node("L2R2", 0.1)); + level1.addChild(level2Right); + final CompositeTreeNode level3Left = new CompositeTreeNode(/* parent */ level2Left, new Node("L3L1", 0.1), new Node("L3L2", 0.9)); + level2Left.addChild(level3Left); + final CompositeTreeNode level3Right = new CompositeTreeNode(/* parent */ level2Right, new Node("L3R1", 0.9), new Node("L3R2", 0.1)); + level2Right.addChild(level3Right); + // now set up the qualities of the edges connecting the inner nodes; simple model: + // staying on the same inner node index is good (quality 0.9), changing node index is bad (quality 0.1); + // this way, at the junction represented by level1 the path coming up from level2Left with its + // preferred __L2 nodes should end up in L1_2, whereas the path coming up from level2Right with its + // preferred __R1 nodes should end up in L1_1. + setEdgeQuality(level3Left, 0, level2Left, 0, 0.9); + setEdgeQuality(level3Left, 0, level2Left, 1, 0.1); + setEdgeQuality(level3Left, 1, level2Left, 0, 0.1); + setEdgeQuality(level3Left, 1, level2Left, 1, 0.9); + setEdgeQuality(level3Right, 0, level2Right, 0, 0.9); + setEdgeQuality(level3Right, 0, level2Right, 1, 0.1); + setEdgeQuality(level3Right, 1, level2Right, 0, 0.1); + setEdgeQuality(level3Right, 1, level2Right, 1, 0.9); + setEdgeQuality(level2Left, 0, level1, 0, 0.9); + setEdgeQuality(level2Left, 0, level1, 1, 0.1); + setEdgeQuality(level2Left, 1, level1, 0, 0.1); + setEdgeQuality(level2Left, 1, level1, 1, 0.9); + setEdgeQuality(level2Right, 0, level1, 0, 0.9); + setEdgeQuality(level2Right, 0, level1, 1, 0.1); + setEdgeQuality(level2Right, 1, level1, 0, 0.1); + setEdgeQuality(level2Right, 1, level1, 1, 0.9); + setEdgeQuality(level1, 0, root, 0, 0.9); + setEdgeQuality(level1, 0, root, 1, 0.1); + setEdgeQuality(level1, 1, root, 0, 0.1); + setEdgeQuality(level1, 1, root, 1, 0.9); + // now have the inner nodes connected among each other according to the overarching tree structure... + final InnerGraphSuccessorSupplier successorSupplier = new InnerGraphSuccessorSupplier<>( + ()->root, nodeName->new Node(nodeName, 1.0)); + // the edge qualities for the additional artificial nodes will all default to 1.0 + // ...and solve the inner graph's shortest path problem, once for each artificial leaf: + final Iterable bestPathStartingAtLevel3Left = new DijkstraShortestPathFinderImpl().getShortestPath( + successorSupplier.getArtificialLeaf(level3Left), successorSupplier.getArtificialRoot(), + successorSupplier, getEdgeQualitySupplier()); + final Iterable bestPathStartingAtLevel3Right = new DijkstraShortestPathFinderImpl().getShortestPath( + successorSupplier.getArtificialLeaf(level3Right), successorSupplier.getArtificialRoot(), + successorSupplier, getEdgeQualitySupplier()); + // We expect the two solutions to provide different best inner nodes for the converging level1 node. + // For the indices, note that 0 denotes the artificial leaf node, so index 1 denotes the inner + // level3 node selected, index 2 the inner level2 node, and index 3 the inner level1 node which is + // what we'd like to compare: + assertNotSame(Util.get(bestPathStartingAtLevel3Left, 3), Util.get(bestPathStartingAtLevel3Right, 3)); + } + + private void setEdgeQuality(CompositeTreeNode fromLevel, int fromInnerNodeZeroBased, CompositeTreeNode toLevel, + int toInnerNodeZeroBased, double quality) { + edgeQuality.put(new Pair<>(Util.get(fromLevel.getElements(), fromInnerNodeZeroBased), + Util.get(toLevel.getElements(), toInnerNodeZeroBased)), quality); + } + @Test public void testBasicDijkstra() { edge(startNode, endNode, 1.0); @@ -57,7 +166,19 @@ public class DijkstraTest { private Iterable getShortestPath() { return dijkstra.getShortestPath(startNode, endNode, - n -> successors.get(n), (n1, n2) -> edgeQuality.get(new Pair<>(n1, n2))); + n -> successors.get(n), getEdgeQualitySupplier()); + } + + /** + * Returns an {@link ElementAdjacencyQualityMetric} that etrieves the quality from {@link #edgeQuality} and defaults + * to {@code 1.0} if no other quality definition is found in {@link #edgeQuality} for the node pair provided. + */ + private ElementAdjacencyQualityMetric getEdgeQualitySupplier() { + return (n1, n2) -> { + final Pair nodes = new Pair<>(n1, n2); + final Double quality = edgeQuality.get(new Pair<>(n1, n2)); + return quality == null ? 1.0 : quality; + }; } @Test diff --git a/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/aggregator/msthmm/graph/InnerGraphSuccessorSupplier.java b/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/aggregator/msthmm/graph/InnerGraphSuccessorSupplier.java index 650faebb871..e97a7fc1b22 100755 --- a/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/aggregator/msthmm/graph/InnerGraphSuccessorSupplier.java +++ b/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/aggregator/msthmm/graph/InnerGraphSuccessorSupplier.java @@ -77,10 +77,11 @@ implements Function> { innerChildElements = Collections.singleton(artificialLeaf); } else { final Set allInnerChildNodes = new HashSet<>(); - for (final GroupOutOfWhichToPickTheBestElement child : node.getChildren()) { + for (final G child : node.getChildren()) { for (final T innerChildElement : child.getElements()) { allInnerChildNodes.add(innerChildElement); } + addAllEdges(child); } innerChildElements = allInnerChildNodes; } @@ -95,4 +96,21 @@ implements Function> { public Iterable apply(T t) { return successors.get(t); } + + /** + * @return the artificial single root node that is parent of all of the + * {@link GroupOutOfWhichToPickTheBestElement#getElements() elements} of the {@link Tree#getRoot() root} of + * the {@link Tree} passed to this object's constructor. + */ + public T getArtificialRoot() { + return artificialRoot; + } + + /** + * @return the artificial leaf node for which all {@link GroupOutOfWhichToPickTheBestElement#getElements() elements} + * of the {@code treeLeafNode} are configured as successors. + */ + public T getArtificialLeaf(G treeLeafNode) { + return artificialLeaves.get(treeLeafNode); + } } diff --git a/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/aggregator/msthmm/graph/Tree.java b/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/aggregator/msthmm/graph/Tree.java index 42d08f08377..3c45b560aeb 100755 --- a/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/aggregator/msthmm/graph/Tree.java +++ b/java/com.sap.sailing.windestimation/src/com/sap/sailing/windestimation/aggregator/msthmm/graph/Tree.java @@ -7,6 +7,7 @@ package com.sap.sailing.windestimation.aggregator.msthmm.graph; * * @param */ +@FunctionalInterface public interface Tree> { TN getRoot(); }