diff --git a/src/main/java/org/mastodon/util/TreeUtils.java b/src/main/java/org/mastodon/util/TreeUtils.java
new file mode 100644
index 000000000..3d20e1c82
--- /dev/null
+++ b/src/main/java/org/mastodon/util/TreeUtils.java
@@ -0,0 +1,158 @@
+package org.mastodon.util;
+
+import java.util.Collection;
+
+import org.mastodon.collection.RefCollections;
+import org.mastodon.collection.RefList;
+import org.mastodon.collection.RefSet;
+import org.mastodon.collection.RefStack;
+import org.mastodon.graph.Edge;
+import org.mastodon.graph.Graph;
+import org.mastodon.graph.Vertex;
+
+public class TreeUtils
+{
+
+ private TreeUtils()
+ {
+ // prevent instantiation of utility class
+ }
+
+ /**
+ * This method finds a subset of the given {@code selectedVertices} that
+ * contains only the roots of the subtrees that are selected. The order
+ * of the returned list follows the order of the {@code roots} list and
+ * the order of the outgoing edges of the graphs vertices.
+ *
+ * Example {@code graph}:
+ *
+ * A B
+ * / \ / \
+ * a1 a2 b1 b2
+ * | / \ / \
+ * a3 b3 b4 b5 b6
+ * / \
+ * a4 a5
+ *
+ *
+ * If {@code selectedVertices} contains: {@code {a2, a4, a5, b1, b3, b4, b6}},
+ * then the returned list will be: {@code [a2, b1, b6]}.
+ */
+ public static < V extends Vertex< ? > > RefList< V > findSelectedSubtreeRoots(
+ final Graph< V, ? > graph,
+ final RefList< V > roots,
+ final RefSet< V > selectedVertices )
+ {
+ final RefList< V > selectedSubtreeRoots = RefCollections.createRefList( graph.vertices() );
+ final RefSet< V > visitedNodes = RefCollections.createRefSet( graph.vertices() );
+
+ for ( final V root : roots )
+ for ( final DepthFirstIteration.Step< V > step : DepthFirstIteration.forRoot( graph, root ) )
+ if ( ensureNoLoop( step, visitedNodes ) && !step.isSecondVisit() ) {
+
+ final V node = step.node();
+ if ( selectedVertices.contains( node ) )
+ {
+ selectedSubtreeRoots.add( node );
+ step.truncate(); // don't visit the child nodes
+ }
+ }
+
+ return selectedSubtreeRoots;
+ }
+
+ private static < V extends Vertex< ? > > boolean ensureNoLoop( DepthFirstIteration.Step< V > step, RefSet< V > visitedNodes )
+ {
+ if ( !step.isFirstVisit() )
+ return true;
+
+ boolean isLoop = !visitedNodes.add( step.node() ); // The depth first iteration enters a node for the second time. -> there's a loop.
+ if ( isLoop )
+ step.truncate(); // Break the loop by not visiting the child nodes.
+
+ return !isLoop;
+ }
+
+ /**
+ * This method returns the root nodes of the tracks (connected components)
+ * that contain any of the given {@code nodes}.
+ *
+ * Example:
+ *
+ * A B C
+ * / \ / \ /
+ * a1 a2 b1 b2
+ * | / \ / \
+ * a3 b3 b4 b5 b6
+ * / \
+ * a4 a5
+ *
+ *
+ * If {@code nodes} contains {@code {a2, a4}} then the method will return
+ * {@code {A}}.
+ *
+ * If {@code nodes} contains {@code {a2, a4, b4}} then the method will
+ * return {@code {A, B, C}}.
+ */
+ public static , E extends Edge< V > > RefSet< V > findRootsOfTheGivenNodes( Graph< V, E > graph, Collection< V > nodes )
+ {
+ return filterRoots( graph, findAllConnectedNodes( graph, nodes ) );
+ }
+
+ /**
+ * @return the set of predecessors of the given {@code nodes}. Please note
+ * that returned set also contains all the given {@code nodes}.
+ */
+ private static < V extends Vertex, E extends Edge< V > > RefSet< V > findAllConnectedNodes( Graph< V, E > graph, Collection< V > nodes )
+ {
+ // The following code performs an inverse depth first search starting
+ // from the given nodes. The set of visited nodes is returned.
+ V ref = graph.vertexRef();
+ V ref2 = graph.vertexRef();
+ try
+ {
+ final RefSet< V > visited = RefCollections.createRefSet( graph.vertices() );
+ visited.addAll( nodes );
+ final RefStack< V > stack = RefCollections.createRefStack( graph.vertices() );
+ stack.addAll( visited );
+ while ( ! stack.isEmpty() ) {
+ V node = stack.pop( ref );
+ for ( E edge : node.incomingEdges() ) {
+ V parentNode = edge.getSource( ref2 );
+ addNode( visited, stack, parentNode );
+ }
+ for ( E edge : node.outgoingEdges() ) {
+ V childNode = edge.getTarget( ref2 );
+ addNode( visited, stack, childNode );
+ }
+ }
+ return visited;
+ }
+ finally
+ {
+ graph.releaseRef( ref );
+ graph.releaseRef( ref2 );
+ }
+ }
+
+ private static < V extends Vertex, E extends Edge< V > > void addNode( RefSet< V > visited, RefStack< V > stack, V parentNode )
+ {
+ boolean firstVisit = visited.add( parentNode );
+ if ( firstVisit )
+ stack.add( parentNode );
+ }
+
+ /**
+ * @return a subset of the given {@code nodes} that contains only those
+ * nodes that are roots of the {@code graph}. (A note is considered a root
+ * if it has no incoming edges.)
+ */
+ private static < V extends Vertex, E extends Edge< V > > RefSet< V > filterRoots( Graph< V, E > graph, Collection< V > nodes )
+ {
+ final RefSet< V > roots = RefCollections.createRefSet( graph.vertices() );
+ for ( V node : nodes )
+ if ( node.incomingEdges().isEmpty() )
+ roots.add( node );
+ return roots;
+ }
+}
diff --git a/src/main/java/org/mastodon/views/trackscheme/display/ShowSelectedTracksActions.java b/src/main/java/org/mastodon/views/trackscheme/display/ShowSelectedTracksActions.java
index 02ffc5868..50835dd8a 100644
--- a/src/main/java/org/mastodon/views/trackscheme/display/ShowSelectedTracksActions.java
+++ b/src/main/java/org/mastodon/views/trackscheme/display/ShowSelectedTracksActions.java
@@ -6,13 +6,13 @@
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
- *
+ *
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -41,7 +41,7 @@
import org.mastodon.ui.keymap.CommandDescriptionProvider;
import org.mastodon.ui.keymap.CommandDescriptions;
import org.mastodon.ui.keymap.KeyConfigContexts;
-import org.mastodon.util.DepthFirstIteration;
+import org.mastodon.util.TreeUtils;
import org.mastodon.views.trackscheme.LexicographicalVertexOrder;
import org.mastodon.views.trackscheme.TrackSchemeEdge;
import org.mastodon.views.trackscheme.TrackSchemeGraph;
@@ -172,7 +172,8 @@ private RefList< TrackSchemeVertex > getSelectedSubtreeRoots()
final RefSet< TrackSchemeVertex > selectedNodes = new RefSetImp<>( viewGraph.getVertexPool() );
selectedNodes.addAll( selectionModel.getSelectedVertices() );
addEdgeTargets( selectedNodes, selectionModel.getSelectedEdges() );
- return filterRootNodes( selectedNodes );
+ RefList< TrackSchemeVertex > sortedRoots = LexicographicalVertexOrder.sort( viewGraph, viewGraph.getRoots() );
+ return TreeUtils.findSelectedSubtreeRoots( viewGraph, sortedRoots, selectedNodes );
}
private RefList< TrackSchemeVertex > getSelectedWholeTrackRoots()
@@ -192,40 +193,9 @@ private void addEdgeTargets( final RefSet< TrackSchemeVertex > selected,
viewGraph.releaseRef( targetRef );
}
- private RefList< TrackSchemeVertex > filterRootNodes( final RefSet< TrackSchemeVertex > selectedVertices )
- {
- final RefList< TrackSchemeVertex > roots = new RefArrayList<>( viewGraph.getVertexPool() );
- for ( final TrackSchemeVertex realRoot : LexicographicalVertexOrder.sort( viewGraph, viewGraph.getRoots() ) )
- for ( final DepthFirstIteration.Step< TrackSchemeVertex > step : DepthFirstIteration.forRoot( viewGraph,
- realRoot ) )
- {
- final TrackSchemeVertex node = step.node();
- if ( selectedVertices.contains( node ) )
- {
- roots.add( node );
- step.truncate();
- }
- }
- return roots;
- }
-
private RefList< TrackSchemeVertex > getRealRoots( final RefSet< TrackSchemeVertex > selectedNodes )
{
- final TrackSchemeVertex parent = viewGraph.vertexRef();
- final RefSet< TrackSchemeVertex > roots = new RefSetImp<>( viewGraph.getVertexPool() );
- A: for ( final TrackSchemeVertex vertex : selectedNodes )
- {
- parent.refTo( vertex );
- while ( !parent.incomingEdges().isEmpty() )
- {
- parent.incomingEdges().iterator().next().getSource( parent );
- if ( selectedNodes.contains( parent ) )
- continue A;
- }
- roots.add( parent );
- }
- viewGraph.releaseRef( parent );
+ RefSet< TrackSchemeVertex > roots = TreeUtils.findRootsOfTheGivenNodes( viewGraph, selectedNodes );
return LexicographicalVertexOrder.sort( viewGraph, roots );
}
-
}
diff --git a/src/test/java/org/mastodon/mamut/feature/branch/exampleGraph/ExampleGraph1.java b/src/test/java/org/mastodon/mamut/feature/branch/exampleGraph/ExampleGraph1.java
index b3b4d5bdd..a6e924a7a 100644
--- a/src/test/java/org/mastodon/mamut/feature/branch/exampleGraph/ExampleGraph1.java
+++ b/src/test/java/org/mastodon/mamut/feature/branch/exampleGraph/ExampleGraph1.java
@@ -31,17 +31,26 @@
*/
public class ExampleGraph1 extends AbstractExampleGraph
{
+ public final Spot spot0;
+
+ public final Spot spot1;
+
+ public final Spot spot2;
+
+ public final Spot spot3;
+
+ public final Spot spot4;
public final BranchSpot branchSpotA;
public ExampleGraph1()
{
super();
- Spot spot0 = addNode( "0", 0, new double[] { 1d, 2d, 3d } );
- Spot spot1 = addNode( "1", 1, new double[] { 2d, 4d, 6d } );
- Spot spot2 = addNode( "2", 2, new double[] { 3d, 6d, 9d } );
- Spot spot3 = addNode( "3", 3, new double[] { 4d, 8d, 12d } );
- Spot spot4 = addNode( "4", 3, new double[] { 5d, 10d, 15d } );
+ spot0 = addNode( "0", 0, new double[] { 1d, 2d, 3d } );
+ spot1 = addNode( "1", 1, new double[] { 2d, 4d, 6d } );
+ spot2 = addNode( "2", 2, new double[] { 3d, 6d, 9d } );
+ spot3 = addNode( "3", 3, new double[] { 4d, 8d, 12d } );
+ spot4 = addNode( "4", 3, new double[] { 5d, 10d, 15d } );
addEdge( spot0, spot1 );
addEdge( spot1, spot2 );
diff --git a/src/test/java/org/mastodon/mamut/feature/branch/exampleGraph/ExampleGraph2.java b/src/test/java/org/mastodon/mamut/feature/branch/exampleGraph/ExampleGraph2.java
index 8dc1fdcf4..8ac7a55e5 100644
--- a/src/test/java/org/mastodon/mamut/feature/branch/exampleGraph/ExampleGraph2.java
+++ b/src/test/java/org/mastodon/mamut/feature/branch/exampleGraph/ExampleGraph2.java
@@ -46,6 +46,31 @@
*/
public class ExampleGraph2 extends AbstractExampleGraph
{
+
+ public final Spot spot0;
+
+ public final Spot spot1;
+
+ public final Spot spot2;
+
+ public final Spot spot3;
+
+ public final Spot spot4;
+
+ public final Spot spot5;
+
+ public final Spot spot6;
+
+ public final Spot spot7;
+
+ public final Spot spot8;
+
+ public final Spot spot10;
+
+ public final Spot spot11;
+
+ public final Spot spot13;
+
public final BranchSpot branchSpotA;
public final BranchSpot branchSpotB;
@@ -58,18 +83,18 @@ public class ExampleGraph2 extends AbstractExampleGraph
public ExampleGraph2()
{
- Spot spot0 = addNode( "0", 0, new double[]{1d, 2d, 3d} );
- Spot spot1 = addNode( "1", 1, new double[]{0d, 0d, 0d} );
- Spot spot2 = addNode( "2", 2, new double[]{3d, 6d, 9d} );
- Spot spot3 = addNode( "3", 3, new double[]{4d, 8d, 12d} );
- Spot spot4 = addNode( "4", 4, new double[]{5d, 10d, 15d} );
- Spot spot5 = addNode( "5", 5, new double[]{6d, 12d, 18d} );
- Spot spot6 = addNode( "6", 6, new double[]{0d, 0d, 0d} );
- Spot spot7 = addNode( "7", 7, new double[]{8d, 16d, 24d} );
- Spot spot8 = addNode( "8", 5, new double[]{9d, 18d, 27d} );
- Spot spot10 = addNode( "10", 7, new double[]{11d, 22d, 33d} );
- Spot spot11 = addNode( "11", 3, new double[]{12d, 24d, 36d} );
- Spot spot13 = addNode( "13", 5, new double[]{14d, 28d, 42d} );
+ spot0 = addNode( "0", 0, new double[] { 1d, 2d, 3d } );
+ spot1 = addNode( "1", 1, new double[] { 0d, 0d, 0d } );
+ spot2 = addNode( "2", 2, new double[] { 3d, 6d, 9d } );
+ spot3 = addNode( "3", 3, new double[] { 4d, 8d, 12d } );
+ spot4 = addNode( "4", 4, new double[] { 5d, 10d, 15d } );
+ spot5 = addNode( "5", 5, new double[] { 6d, 12d, 18d } );
+ spot6 = addNode( "6", 6, new double[] { 0d, 0d, 0d } );
+ spot7 = addNode( "7", 7, new double[] { 8d, 16d, 24d } );
+ spot8 = addNode( "8", 5, new double[] { 9d, 18d, 27d } );
+ spot10 = addNode( "10", 7, new double[] { 11d, 22d, 33d } );
+ spot11 = addNode( "11", 3, new double[] { 12d, 24d, 36d } );
+ spot13 = addNode( "13", 5, new double[] { 14d, 28d, 42d } );
addEdge( spot0, spot1 );
addEdge( spot1, spot2 );
diff --git a/src/test/java/org/mastodon/util/MastodonUtils.java b/src/test/java/org/mastodon/util/MastodonUtils.java
new file mode 100644
index 000000000..88d78e198
--- /dev/null
+++ b/src/test/java/org/mastodon/util/MastodonUtils.java
@@ -0,0 +1,166 @@
+/*-
+ * #%L
+ * A Mastodon plugin data allows to show the embryo in Blender.
+ * %%
+ * Copyright (C) 2022 - 2023 Matthias Arzt
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+package org.mastodon.util;
+
+import java.io.IOException;
+
+import javax.swing.WindowConstants;
+
+import org.mastodon.graph.io.RawGraphIO;
+import org.mastodon.grouping.GroupHandle;
+import org.mastodon.mamut.MainWindow;
+import org.mastodon.mamut.MamutAppModel;
+import org.mastodon.mamut.WindowManager;
+import org.mastodon.mamut.feature.MamutRawFeatureModelIO;
+import org.mastodon.mamut.model.Link;
+import org.mastodon.mamut.model.Model;
+import org.mastodon.mamut.model.ModelGraph;
+import org.mastodon.mamut.model.Spot;
+import org.mastodon.mamut.project.MamutProject;
+import org.mastodon.mamut.project.MamutProjectIO;
+import org.mastodon.model.FocusModel;
+import org.mastodon.model.NavigationHandler;
+import org.mastodon.model.NavigationListener;
+import org.mastodon.model.TimepointModel;
+import org.mastodon.model.tag.TagSetModel;
+import org.scijava.Context;
+
+import mpicbg.spim.data.SpimDataException;
+
+public class MastodonUtils
+{
+
+ private static final boolean LOG_STACK_TRACE = false;
+
+ private MastodonUtils() {
+ // prevent from instantiation
+ }
+
+ static Model openMastodonModel( Context context, String projectPath )
+ {
+ try
+ {
+ MamutProject project = new MamutProjectIO().load( projectPath );
+ final Model model = new Model( project.getSpaceUnits(), project.getTimeUnits() );
+ final boolean isNewProject = project.getProjectRoot() == null;
+ if ( !isNewProject )
+ {
+ try (final MamutProject.ProjectReader reader = project.openForReading())
+ {
+ final RawGraphIO.FileIdToGraphMap idmap = model.loadRaw( reader );
+ // Load features.
+ MamutRawFeatureModelIO.deserialize( context, model, idmap, reader );
+ }
+ catch ( final ClassNotFoundException e )
+ {
+ e.printStackTrace();
+ }
+ }
+ return model;
+ }
+ catch ( IOException e )
+ {
+ throw new RuntimeException( e );
+ }
+ }
+
+ public static WindowManager showGui(String projectPath) {
+ try {
+ final WindowManager windowManager = new WindowManager( new Context() );
+ windowManager.getProjectManager().open( new MamutProjectIO().load( projectPath ) );
+ final MainWindow mainWindow = new MainWindow(windowManager);
+ mainWindow.setVisible( true );
+ mainWindow.setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE );
+ return windowManager;
+ } catch (IOException | SpimDataException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void logMastodonEvents( MamutAppModel appModel )
+ {
+ GroupHandle groupHandle = appModel.getGroupManager().createGroupHandle();
+ groupHandle.setGroupId( 0 );
+ logNavigationHandle( groupHandle.getModel( appModel.NAVIGATION ) );
+ logTimePointModel(groupHandle.getModel( appModel.TIMEPOINT ) );
+ logFocusModel(appModel);
+ logTagSetModel(appModel);
+ }
+
+ private static void logFocusModel( MamutAppModel appModel )
+ {
+ FocusModel focusModel = appModel.getFocusModel();
+ ModelGraph graph = appModel.getModel().getGraph();
+ focusModel.listeners().add(() -> {
+ Spot ref = graph.vertexRef();
+ Spot focusedSpot = focusModel.getFocusedVertex( ref );
+ log( "FocusModel: focused vertex: " + focusedSpot );
+ graph.releaseRef( ref );
+ });
+ }
+
+ private static void logNavigationHandle( NavigationHandler navigationHandler )
+ {
+ navigationHandler.listeners().add( new NavigationListener()
+ {
+ @Override
+ public void navigateToVertex( Spot vertex )
+ {
+ log( "NavigationHandler: navigate to vertex " + vertex );
+ }
+
+ @Override
+ public void navigateToEdge( Link edge )
+ {
+ log( "NavigationHandler: navigate to edge " + edge );
+ }
+ } );
+ }
+
+ private static void logTimePointModel( TimepointModel model )
+ {
+ model.listeners().add( () -> log( "Time point changed: (to " + model.getTimepoint() + ")" ) );
+ }
+
+ private static void logTagSetModel( MamutAppModel appModel )
+ {
+ // TODO
+ Model model = appModel.getModel();
+ TagSetModel tagSetModel = model.getTagSetModel();
+ tagSetModel.listeners().add( () -> log( "tag set changed" ) );
+ }
+
+ private static void log( String text )
+ {
+ System.out.println( text + " " + Thread.currentThread().getName() );
+ if( LOG_STACK_TRACE )
+ for ( StackTraceElement stackTraceElement : Thread.currentThread().getStackTrace() )
+ System.out.println( " " + stackTraceElement );
+ }
+}
diff --git a/src/test/java/org/mastodon/util/TreeUtilsTest.java b/src/test/java/org/mastodon/util/TreeUtilsTest.java
new file mode 100644
index 000000000..c57ee389a
--- /dev/null
+++ b/src/test/java/org/mastodon/util/TreeUtilsTest.java
@@ -0,0 +1,150 @@
+package org.mastodon.util;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Test;
+import org.mastodon.collection.RefCollections;
+import org.mastodon.collection.RefList;
+import org.mastodon.collection.RefSet;
+import org.mastodon.mamut.feature.branch.exampleGraph.ExampleGraph2;
+import org.mastodon.mamut.model.ModelGraph;
+import org.mastodon.mamut.model.Spot;
+
+/**
+ * Tests {@link TreeUtils}.
+ *
+ * @author Matthias Arzt
+ */
+public class TreeUtilsTest
+{
+ /**
+ * Test {@link TreeUtils#findSelectedSubtreeRoots} on
+ * {@link ExampleGraph2}.
+ */
+ @Test
+ public void testFindSelectedSubtreeRoots()
+ {
+ // setup
+ ExampleGraph2 exampleGraph = new ExampleGraph2();
+ ModelGraph graph = exampleGraph.getModel().getGraph();
+
+ RefList< Spot > roots = RefCollections.createRefList( graph.vertices() );
+ roots.add( exampleGraph.spot0 );
+
+ RefSet< Spot > selectedSpots = RefCollections.createRefSet( graph.vertices() );
+ selectedSpots.add( exampleGraph.spot4 );
+ selectedSpots.add( exampleGraph.spot7 );
+ selectedSpots.add( exampleGraph.spot10 );
+
+ // process
+ RefList< Spot > result = TreeUtils.findSelectedSubtreeRoots( graph, roots, selectedSpots );
+
+ // test
+ assertEquals( 1, result.size() );
+ assertEquals( exampleGraph.spot4, result.get( 0 ) );
+ }
+
+ /**
+ * Test {@link TreeUtils#findSelectedSubtreeRoots} on a simple graph
+ * with a loop.
+ */
+ @Test
+ public void testFindSelectedSubTreeRoots_dontGetStuckInLoops()
+ {
+ // setup
+ ModelGraph graph = new ModelGraph();
+ Spot a = graph.addVertex().init( 0, new double[] { 0, 0, 0 }, 0 );
+ Spot b = graph.addVertex().init( 1, new double[] { 0, 0, 0 }, 0 );
+ graph.addEdge( a, b );
+ graph.addEdge( b, a ); // loop
+
+ RefList< Spot > roots = RefCollections.createRefList( graph.vertices() );
+ roots.add( a );
+
+ RefSet< Spot > selectedSpots = RefCollections.createRefSet( graph.vertices() );
+ selectedSpots.add( b );
+
+ // process
+ RefList< Spot > result = TreeUtils.findSelectedSubtreeRoots( graph, roots, selectedSpots );
+
+ // test
+ assertEquals( Collections.singletonList( b ), result );
+ }
+
+ @Test
+ public void testFindRootsOfTheGivenNodes() {
+ // Example graph:
+ // a b
+ // |
+ // a1
+ // / \
+ // a2 a3
+
+ ModelGraph graph = new ModelGraph();
+ Spot a = graph.addVertex().init( 0, new double[] { 0, 0, 0 }, 1 );
+ Spot a1 = graph.addVertex().init( 0, new double[] { 0, 0, 0 }, 1 );
+ Spot a2 = graph.addVertex().init( 0, new double[] { 0, 0, 0 }, 1 );
+ Spot a3 = graph.addVertex().init( 0, new double[] { 0, 0, 0 }, 1 );
+ Spot b = graph.addVertex().init( 0, new double[] { 0, 0, 0 }, 1 );
+ graph.addEdge( a, a1 ).init();
+ graph.addEdge( a1, a2 ).init();
+ graph.addEdge( a2, a3 ).init();
+
+ assertEquals( Collections.singleton( a ), TreeUtils.findRootsOfTheGivenNodes( graph, Arrays.asList( a, a1, a2, a3 ) ) );
+ assertEquals( Collections.singleton( a ), TreeUtils.findRootsOfTheGivenNodes( graph, Collections.singleton( a3 ) ) );
+ assertEquals( Collections.singleton( b ), TreeUtils.findRootsOfTheGivenNodes( graph, Collections.singleton( b ) ) );
+ assertEquals( createSet( a, b ), TreeUtils.findRootsOfTheGivenNodes( graph, Arrays.asList( a2, b ) ) );
+ }
+
+ @Test
+ public void testFindRootsOfTheGivenNodes_dontGetStuckInLoops() {
+ // setup
+ ModelGraph graph = new ModelGraph();
+ Spot a = graph.addVertex().init( 0, new double[] { 0, 0, 0 }, 1 );
+ Spot b = graph.addVertex().init( 1, new double[] { 0, 0, 0 }, 0 );
+ Spot c = graph.addVertex().init( 1, new double[] { 0, 0, 0 }, 0 );
+ graph.addEdge( a, b );
+ graph.addEdge( b, c );
+ graph.addEdge( c, b ); // loop between b and c
+ // process
+ RefSet< Spot > result = TreeUtils.findRootsOfTheGivenNodes( graph, Collections.singleton( c ) );
+ // test
+ assertEquals( Collections.singleton( a ), result );
+ }
+
+ @Test
+ public void testFindRootsOfTheGivenNodes_noTree() {
+ // Example graph:
+ // a b f
+ // \ / \
+ // c e
+ // |
+ // d
+ ModelGraph graph = new ModelGraph();
+ Spot a = graph.addVertex().init( 0, new double[] { 0, 0, 0 }, 1 );
+ Spot b = graph.addVertex().init( 1, new double[] { 0, 0, 0 }, 1 );
+ Spot c = graph.addVertex().init( 2, new double[] { 0, 0, 0 }, 1 );
+ Spot d = graph.addVertex().init( 3, new double[] { 0, 0, 0 }, 1 );
+ Spot e = graph.addVertex().init( 4, new double[] { 0, 0, 0 }, 1 );
+ Spot f = graph.addVertex().init( 4, new double[] { 0, 0, 0 }, 1 );
+ graph.addEdge( a, c );
+ graph.addEdge( b, c );
+ graph.addEdge( c, d );
+ graph.addEdge( b, e );
+
+ assertEquals( createSet( a, b ), TreeUtils.findRootsOfTheGivenNodes( graph, Arrays.asList( b, c, d ) ) );
+ assertEquals( createSet( a, b ), TreeUtils.findRootsOfTheGivenNodes( graph, Arrays.asList( a, c, d ) ) );
+ assertEquals( createSet( a, b ), TreeUtils.findRootsOfTheGivenNodes( graph, Collections.singletonList( e ) ) );
+ }
+
+ @SafeVarargs
+ private static < T > Set< T > createSet( T... elements )
+ {
+ return new HashSet<>( Arrays.asList( elements ) );
+ }
+}