Example #1
0
        private ExploreChildNodesResult ExploreChildNodes <TVertex>(
            QuiverWithPotential <TVertex> qp,
            TransformationRuleTreeNode <TVertex> transformationRuleTree,
            AnalysisStateForSingleStartingVertexOld <TVertex> state,
            SearchTreeNodeOld <TVertex> parentNode,
            QPAnalysisSettings settings)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            bool pathHasNonzeroExtension = false; // Path has no nonzero extension until proven otherwise

            // Explore every child node (determine its equivalence class)
            foreach (var nextVertex in qp.Quiver.AdjacencyLists[parentNode.Vertex])
            {
                var result = ExploreChildNode(transformationRuleTree, state, parentNode, nextVertex, settings);
                switch (result)
                {
                case ExploreChildNodeResult.NotZeroEquivalent:
                    pathHasNonzeroExtension = true;
                    break;

                case ExploreChildNodeResult.NonCancellativityDetected:
                    return(ExploreChildNodesResult.NonCancellativityDetected);

                case ExploreChildNodeResult.TooLongPath:
                    return(ExploreChildNodesResult.TooLongPath);
                }
            }

            return(pathHasNonzeroExtension ? ExploreChildNodesResult.PathHasNonzeroExtension : ExploreChildNodesResult.PathHasNoNonzeroExtension);
        }
        /// <summary>
        /// Computes the equivalence class of the specified node.
        /// </summary>
        /// <typeparam name="TVertex">The type of the vertices in the quiver.</typeparam>
        /// <param name="node">The node whose equivalence class to determine.</param>
        /// <param name="state">The state of the computation.</param>
        /// <param name="transformationRuleTree">The transformation rule tree.</param>
        /// <param name="settings">The computation settings.</param>
        /// <returns><see cref="EquivalenceClassResult.TooLongPath"/> if
        /// <paramref name="settings"/> has <see cref="AnalysisSettings.UseMaxLength"/> equal to
        /// <see langword="true"/> and a path of length (in arrows) strictly greater than
        /// <see cref="AnalysisSettings.MaxPathLength"/> of <paramref name="settings"/> is
        /// encountered during the equivalence class search. Otherwise,
        /// <see cref="EquivalenceClassResult.Zero"/> if the equivalence class is the zero
        /// class, and <see cref="EquivalenceClassResult.Nonzero"/> if the equivalence class is not
        /// the zero class.</returns>
        /// <remarks>
        /// <para>This method may modify the
        /// <see cref="AnalysisStateForSingleStartingVertex{TVertex}.SearchTree"/>,
        /// <see cref="AnalysisStateForSingleStartingVertex{TVertex}.EquivalenceClasses"/>, and
        /// <see cref="AnalysisStateForSingleStartingVertex{TVertex}.LongestPathEncounteredNode"/>
        /// properties of the <paramref name="state"/> argument.</para>
        /// </remarks>
        private EquivalenceClassResult ComputeEquivalenceClass <TVertex>(
            SearchTreeNode <TVertex> startNode,
            AnalysisStateForSingleStartingVertex <TVertex> state,
            TransformationRuleTreeNode <TVertex> transformationRuleTree,
            MaximalNonzeroEquivalenceClassRepresentativeComputationSettings settings)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            // The stack or queue of nodes to process
            var equivClassStack = new Stack <SearchTreeNode <TVertex> >();

            equivClassStack.Push(startNode);
            startNode.HasBeenDiscoveredDuringEquivalenceClassComputation = true;

            while (equivClassStack.Count > 0)
            {
                var node   = equivClassStack.Pop();
                var result = ExploreNodeForEquivalenceClassSearch(node, equivClassStack, state, transformationRuleTree, settings);
                if (result == EquivalenceClassResult.TooLongPath)
                {
                    return(EquivalenceClassResult.TooLongPath);                                              // If too long, return "too long".
                }
                else if (result == EquivalenceClassResult.Zero)
                {
                    return(EquivalenceClassResult.Zero);                                            // If definitely zero-equivalent, return "zero"
                }
            }

            return(EquivalenceClassResult.Nonzero);
        }
        /// <summary>
        ///
        /// </summary>
        /// <typeparam name="TVertex">The type of the vertices in the quiver.</typeparam>
        /// <param name="quiver">The quiver.</param>
        /// <param name="startingVertex">The starting vertex of the paths to look at.</param>
        /// <param name="transformationRuleTree"></param>
        /// <returns>The state of the search after it is done.</returns>
        private AnalysisStateForSingleStartingVertex <TVertex> DoSearchInPathTree <TVertex>(
            Quiver <TVertex> quiver,
            TVertex startingVertex,
            TransformationRuleTreeNode <TVertex> transformationRuleTree,
            MaximalNonzeroEquivalenceClassRepresentativeComputationSettings settings)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            // Set up for analysis/graph search
            var state = new AnalysisStateForSingleStartingVertex <TVertex>(startingVertex);

            // Analysis/graph search
            // Keep a stack of "recently equivalence-class-computed" nodes
            // In every iteration, pop a node from the stack and determine the equivalence classes
            // of its child nodes.
            // It would be cleaner to in every iteration determine the equivalence class of the
            // *current* node and enqueue its child nodes (in other words, to have the queue
            // contain nodes whose equivalence classes to explore), but this makes it non-trivial
            // to keep track of the maximal nonzero equivalence classes
            while (state.Stack.Count > 0)
            {
                var  node             = state.Stack.Pop();
                bool isMaximalNonzero = true;

                foreach (var nextVertex in quiver.AdjacencyLists[node.Vertex])
                {
                    var child = state.GetInsertChildNode(node, nextVertex);
                    if (DoPathLengthCheck(child, state, settings) == PathLengthCheckResult.TooLongPath)
                    {
                        state.TooLongPathEncountered = true;
                        return(state);
                    }

                    var equivClassResult = DetermineEquivalenceClass(child, state, transformationRuleTree, settings);
                    if (equivClassResult == EquivalenceClassResult.Nonzero)
                    {
                        isMaximalNonzero = false;
                        state.Stack.Push(child);
                    }
                    else if (equivClassResult == EquivalenceClassResult.TooLongPath)
                    {
                        state.TooLongPathEncountered = true;
                        return(state);
                    }
                }

                if (isMaximalNonzero)
                {
                    var representative = state.EquivalenceClasses.FindSet(node);
                    if (!state.maximalPathRepresentatives.Contains(representative))
                    {
                        state.maximalPathRepresentatives.Add(representative);
                    }
                }
            }

            return(state);
        }
Example #4
0
        // After figuring out the use cases (high performance?) for these methods and figuring out
        // whether to change the interface, implement the counterparts of the methods in
        // SemimonomialUnboundQuiverAnalyzer and have the public methods of this class just be a
        // wrapper for said methods in SemimonomialUnboundQuiverAnalyzer (like the Analyze method
        // is right now).
        #region Code to refactor
        public AnalysisResultsForSingleStartingVertexOld <TVertex> AnalyzeWithStartingVertex <TVertex>(
            QuiverWithPotential <TVertex> qp,
            TVertex startingVertex,
            TransformationRuleTreeNode <TVertex> transformationRuleTree,
            QPAnalysisSettings settings)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            // Parameter validation
            if (qp == null)
            {
                throw new ArgumentNullException(nameof(qp));
            }
            if (!qp.Quiver.Vertices.Contains(startingVertex))
            {
                throw new ArgumentException($"The QP does not contain the starting vertex {startingVertex}.");
            }

            // Set up for analysis/graph search
            var  state = new AnalysisStateForSingleStartingVertexOld <TVertex>(startingVertex);
            bool cancellativityFailed   = false;
            bool tooLongPathEncountered = false;

            // Analysis/graph search
            // Keep a stack of "recently explored" nodes
            // In every iteration, pop a recently explored node from the stack and explore (determine
            // the equivalence classes of) its child nodes.
            // It would be cleaner to in every iteration explore the current node (determine its equivalence class)
            // and discover its child nodes (push new equivalence class representatives (which may overlap?) onto
            // the stack for future iterations, but this makes it non-trivial to keep track of the maximal nonzero
            // equivalence classes
            while (state.Stack.Count > 0)
            {
                var node   = state.Stack.Pop();
                var result = ExploreChildNodes(qp, transformationRuleTree, state, node, settings);
                if (result == ExploreChildNodesResult.PathHasNoNonzeroExtension)
                {
                    state.maximalPathRepresentatives.Add(node);
                }
                else if (result == ExploreChildNodesResult.NonCancellativityDetected)
                {
                    cancellativityFailed = true;
                    break;
                }
                else if (result == ExploreChildNodesResult.TooLongPath)
                {
                    tooLongPathEncountered = true;
                    break;
                }
            }

            // Return results
            var results = new AnalysisResultsForSingleStartingVertexOld <TVertex>(state, cancellativityFailed, tooLongPathEncountered);

            return(results);
        }
        /// <summary>
        /// Essentially <see cref="ComputeMaximalNonzeroEquivalenceClassRepresentativesStartingAt"/>
        /// but with a more detailed, implementation-specific return value.
        /// </summary>
        /// <typeparam name="TVertex"></typeparam>
        /// <param name="quiver"></param>
        /// <param name="startingVertex"></param>
        /// <param name="transformationRuleTree"></param>
        /// <param name="settings"></param>
        /// <returns></returns>
        private AnalysisResultsForSingleStartingVertex <TVertex> AnalyzeWithStartingVertex <TVertex>(
            Quiver <TVertex> quiver,
            TVertex startingVertex,
            TransformationRuleTreeNode <TVertex> transformationRuleTree,
            MaximalNonzeroEquivalenceClassRepresentativeComputationSettings settings)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            // Parameter validation
            if (quiver is null)
            {
                throw new ArgumentNullException(nameof(quiver));
            }
            if (!quiver.Vertices.Contains(startingVertex))
            {
                throw new ArgumentException($"The quiver does not contain the starting vertex {startingVertex}.");
            }
            if (transformationRuleTree is null)
            {
                throw new ArgumentNullException(nameof(transformationRuleTree));
            }
            if (settings is null)
            {
                throw new ArgumentNullException(nameof(settings));
            }

            // Do search in the tree of all paths starting at startingVertex
            AnalysisStateForSingleStartingVertex <TVertex> state = DoSearchInPathTree(quiver, startingVertex, transformationRuleTree, settings);

            // Do cancellativity check
            bool shouldDoCancellativityFailureDetection = settings.DetectCancellativityFailure && !state.TooLongPathEncountered;
            bool cancellativityFailureDetected          = shouldDoCancellativityFailureDetection ? DetectFailureOfCancellativity(state) : false;

            bool shouldDoWeakCancellativityFailureDetection = settings.DetectWeakCancellativityFailure && !state.TooLongPathEncountered;
            bool weakCancellativityFailureDetected          = shouldDoWeakCancellativityFailureDetection ? DetectFailureOfWeakCancellativity(state) : false;

            var cancellativityFailuresDetected = CancellativityTypes.None;

            if (cancellativityFailureDetected)
            {
                cancellativityFailuresDetected |= CancellativityTypes.Cancellativity;
            }
            if (weakCancellativityFailureDetected)
            {
                cancellativityFailuresDetected |= CancellativityTypes.WeakCancellativity;
            }

            // Return results
            var results = new AnalysisResultsForSingleStartingVertex <TVertex>(state, cancellativityFailuresDetected);

            return(results);
        }
        /// <summary>
        /// Determines the equivalence class of the specified node.
        /// </summary>
        /// <typeparam name="TVertex">The type of the vertices in the quiver.</typeparam>
        /// <param name="node">The node whose equivalence class to determine.</param>
        /// <param name="state">The state of the computation.</param>
        /// <param name="transformationRuleTree">The transformation rule tree.</param>
        /// <param name="settings">The computation settings.</param>
        /// <returns><see cref="EquivalenceClassResult.TooLongPath"/> if the equivalence class has
        /// not been computed before, <paramref name="settings"/> has
        /// <see cref="AnalysisSettings.UseMaxLength"/> equal to <see langword="true"/>, and a path
        /// of length (in arrows) strictly greater than
        /// <see cref="AnalysisSettings.MaxPathLength"/> of <paramref name="settings"/> is
        /// encountered during the equivalence class search. Otherwise,
        /// <see cref="EquivalenceClassResult.Zero"/> if the equivalence class is the zero
        /// class, and <see cref="EquivalenceClassResult.Nonzero"/> if the equivalence class is
        /// not the zero class.</returns>
        /// <remarks>
        /// <para>This method may modify the
        /// <see cref="AnalysisStateForSingleStartingVertex{TVertex}.SearchTree"/>,
        /// <see cref="AnalysisStateForSingleStartingVertex{TVertex}.EquivalenceClasses"/>, and
        /// <see cref="AnalysisStateForSingleStartingVertex{TVertex}.LongestPathEncounteredNode"/>
        /// properties of the <paramref name="state"/> argument.</para>
        /// </remarks>
        private EquivalenceClassResult DetermineEquivalenceClass <TVertex>(
            SearchTreeNode <TVertex> node,
            AnalysisStateForSingleStartingVertex <TVertex> state,
            TransformationRuleTreeNode <TVertex> transformationRuleTree,
            MaximalNonzeroEquivalenceClassRepresentativeComputationSettings settings)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            if (node.EquivalenceClassIsComputed)
            {
                return(state.NodeIsZeroEquivalent(node) ? EquivalenceClassResult.Zero : EquivalenceClassResult.Nonzero);
            }

            return(ComputeEquivalenceClass(node, state, transformationRuleTree, settings));
        }
Example #7
0
        public MaximalNonzeroEquivalenceClassRepresentativesResults <TVertex> ComputeMaximalNonzeroEquivalenceClassRepresentativesStartingAt <TVertex>(
            QuiverWithPotential <TVertex> qp,
            TVertex startingVertex,
            TransformationRuleTreeNode <TVertex> transformationRuleTree,
            QPAnalysisSettings settings)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            var bogusPath       = new Path <TVertex>(qp.Quiver.Vertices.First());
            var analysisResults = AnalyzeWithStartingVertex(qp, startingVertex, transformationRuleTree, settings);
            var outputResults   = new MaximalNonzeroEquivalenceClassRepresentativesResults <TVertex>(
                analysisResults.NonCancellativityDetected ? CancellativityTypes.Cancellativity : CancellativityTypes.None,
                analysisResults.TooLongPathEncountered,
                analysisResults.MaximalPathRepresentatives.Select(node => node.Path),
                longestPathEncountered: bogusPath); // bogus path because this is an old method and the longest path feature is new

            return(outputResults);
        }
        /// <summary>
        /// Creates a transformation rule tree for a <see cref="SemimonomialUnboundQuiver{TVertex}"/>.
        /// </summary>
        /// <typeparam name="TVertex">The type of the vertices of the quiver.</typeparam>
        /// <param name="unboundQuiver">The unbound quiver.</param>
        /// <returns>A transformation rule tree for <paramref name="semimonomialIdeal"/>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="semimonomialIdeal"/> is <see langword="null"/>.</exception>
        /// <exception cref="NotSupportedException"><paramref name="semimonomialIdeal"/> has two
        /// distinct non-monomial generators <c>p1 - q1</c> and <c>p2 - q2</c> where
        /// <c>p1, q1, p2, q2</c> are not all distinct.</exception>
        /// <exception cref="ArgumentException"><paramref name="semimonomialIdeal"/> has a
        /// non-monomial generator <c>p - q</c> where the paths <c>p</c> and <c>q</c> do not have
        /// the same endpoints.</exception>
        public TransformationRuleTreeNode <TVertex> CreateTransformationRuleTree <TVertex>(SemimonomialIdeal <TVertex> semimonomialIdeal)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            var root = new TransformationRuleTreeNode <TVertex>(false, null, null);

            // Monomial generators (kill rules)
            foreach (var path in semimonomialIdeal.Paths)
            {
                var node = GetOrInsertDefaultNode(path, root);
                node.CanBeKilled = true;
            }

            // Non-monomial generators (replace rules)
            foreach (var difference in semimonomialIdeal.DifferencesOfPaths)
            {
                // Replacement rules where the paths have different starting or ending points are disallowed
                if (!difference.Minuend.StartingPoint.Equals(difference.Subtrahend.StartingPoint) || !difference.Minuend.EndingPoint.Equals(difference.Subtrahend.EndingPoint))
                {
                    throw new NotSupportedException("Ideals with a non-monomial generator whose two paths have different starting points or ending points are not supported.");
                }

                InsertReplacementRule(difference.Minuend, difference.Subtrahend);
                InsertReplacementRule(difference.Subtrahend, difference.Minuend);
            }

            void InsertReplacementRule(Path <TVertex> originalPath, Path <TVertex> replacementPath)
            {
                var node = GetOrInsertDefaultNode(originalPath, root);

                if (node.ReplacementPath != null)
                {
                    // Shouldn't be too much work to add support for this imo
                    throw new NotSupportedException("Ideals with a path occurring in more than one non-monomial generator are not supported.");
                }

                node.ReplacementPath = replacementPath;
            }

            return(root);
        }
        /// <inheritdoc/>
        public MaximalNonzeroEquivalenceClassRepresentativesResults <TVertex> ComputeMaximalNonzeroEquivalenceClassRepresentativesStartingAt <TVertex>(
            Quiver <TVertex> quiver,
            TVertex startingVertex,
            TransformationRuleTreeNode <TVertex> transformationRuleTree,
            MaximalNonzeroEquivalenceClassRepresentativeComputationSettings settings)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            var analysisResults = AnalyzeWithStartingVertex(quiver, startingVertex, transformationRuleTree, settings);

            // The .ToList() call to eagerly evaluate the maximal path representatives is really
            // important here; otherwise, the MaximalPathRepresentatives property of the
            // MaximalNonzeroEquivalenceClassRepresentativesResult prevents the entire search tree
            // of analysisResults from being freed.
            var maximalPathRepresentatives = analysisResults.MaximalPathRepresentatives.Select(node => node.Path).ToList();

            var outputResults = new MaximalNonzeroEquivalenceClassRepresentativesResults <TVertex>(
                analysisResults.CancellativityFailuresDetected,
                analysisResults.TooLongPathEncountered,
                maximalPathRepresentatives,
                analysisResults.LongestPathEncountered);

            return(outputResults);
        }
        /// <remarks>This method should be made obsolete (prefer converting the QP to a
        /// semimonomial unbound quiver, and analyzing that instead (or at least creating the
        /// transformation rule tree for that instead).</remarks>
        public TransformationRuleTreeNode <TVertex> CreateTransformationRuleTree <TVertex>(QuiverWithPotential <TVertex> qp)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            if (qp is null)
            {
                throw new ArgumentNullException(nameof(qp));
            }

            var root = new TransformationRuleTreeNode <TVertex>(false, null, null);

            foreach (var arrow in qp.Quiver.Arrows)
            {
                var linComb = new LinearCombination <Path <TVertex> >();
                foreach (var(cycle, coefficient) in qp.Potential.LinearCombinationOfCycles.ElementToCoefficientDictionary)
                {
                    linComb = linComb.Add(cycle.DifferentiateCyclically(arrow).Scale(coefficient));
                }

                AddRulesFromSingleLinearCombination(linComb, root);
            }

            return(root);
        }
Example #11
0
        /// <returns>A boolean value indicating whether the node was found to be zero-equivalent.
        /// That is, the returned value is <see langword="true"/> <em>only</em> if the node is
        /// zero-equivalent but may be <see langword="false"/> even if the node is zero-equivalent.</returns>
        /// <remarks>A node is found to be zero-equivalent either if its path can be killed or if
        /// the path is equivalent to (up to replacement) to a path that has previously been
        /// determined to be zero-equivalent.</remarks>
        private static bool EquivClassExploreNode <TVertex>(
            TransformationRuleTreeNode <TVertex> transformationRuleTree,
            SearchTreeNodeOld <TVertex> node,
            AnalysisStateForSingleStartingVertexOld <TVertex> state,
            Stack <SearchTreeNodeOld <TVertex> > equivClassStack,
            SearchTreeNodeOld <TVertex> origin)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            node.Explored = true;

            var trailingVertexPath = new List <TVertex>();      // In reversed order

            foreach (var endingNode in node.ReversePathOfNodes) // The last vertex of endingNode is the last vertex in the subpath
            {
                var transformationNode = transformationRuleTree;
                foreach (var startingNode in endingNode.ReversePathOfNodes) // The last vertex of startingNode is the first vertex in the subpath
                {
                    var pathVertex = startingNode.Vertex;
                    if (!transformationNode.Children.TryGetValue(pathVertex, out transformationNode))
                    {
                        break;
                    }

                    if (transformationNode.CanBeKilled)
                    {
                        state.EquivalenceClasses.Union(node, state.ZeroDummyNode);
                        return(true);
                    }

                    // If replacement is possible, do the replacement on the subpath
                    // and add a search tree node for the entire resulting path
                    if (transformationNode.CanBeReplaced)
                    {
                        // Add search tree nodes for replacement path

                        // This doesn't work when pathNode is the root ...
                        // var lastUnreplacedNode = pathNode.Parent;
                        // var curNode = lastUnreplacedNode;

                        // ... so instead do the following (with Skip), which assumes that the replacement
                        // path has the same first vertex (which it does for the QPs under consideration)
                        var firstReplacementNode = startingNode;
                        var curNode = firstReplacementNode;

                        foreach (var vertex in transformationNode.ReplacementPath.Vertices.Skip(1))
                        {
                            curNode = state.GetInsertChildNode(curNode, vertex, origin);
                        }

                        // Add search tree nodes for the trailing path
                        foreach (var vertex in trailingVertexPath.Reversed())
                        {
                            curNode = state.GetInsertChildNode(curNode, vertex, origin);
                        }

                        var transformedNode = curNode;
                        if (state.NodeIsZeroEquivalent(transformedNode))
                        {
                            state.EquivalenceClasses.Union(node, transformedNode);
                            return(true);
                        }

                        // transformedNode.Explored may be true and is then a node that we have
                        // already encountered during the equivalence class search.
                        // (It cannot be an explored node of some *other* equivalence class,
                        // because then this current equivalence class search would not have
                        // started in the first place.)
                        //if (!transformedNode.Explored)
                        if (!state.EquivalenceClasses.FindSet(node).Equals(state.EquivalenceClasses.FindSet(transformedNode)))
                        {
                            state.EquivalenceClasses.Union(node, transformedNode);
                            equivClassStack.Push(transformedNode);
                        }
                    }
                }

                trailingVertexPath.Add(endingNode.Vertex);
            }

            return(false);
        }
Example #12
0
        /// <returns>A boolean value indicating whether the path correspondings to the explored
        /// child node is nonzero (up to equivalence).</returns>
        private ExploreChildNodeResult ExploreChildNode <TVertex>(
            TransformationRuleTreeNode <TVertex> transformationRuleTree,
            AnalysisStateForSingleStartingVertexOld <TVertex> state,
            SearchTreeNodeOld <TVertex> parentNode,
            TVertex nextVertex,
            QPAnalysisSettings settings)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            // Either the child node has not already been discovered, or the child node was
            // discovered during an equivalence class search.

            if (parentNode.Children.TryGetValue(nextVertex, out var childNode))
            {
                // The child node has already been discovered.
                // Do a non-cancellativity check (if we are to detect non-cancellativity):
                //     If the child node is zero, everything is fine.
                //     If a different arrow was added to the origin than to the parent to get child node (i.e., origin and parent have different last vertices), things are fine.
                //     Else, make sure that the origin is equivalent to the parent
                // (otherwise, the QP is not cancellative).
                if (
                    settings.DetectCancellativityFailure &&
                    !state.NodeIsZeroEquivalent(childNode) &&
                    parentNode.Vertex.Equals(childNode.Origin.Vertex) &&
                    state.EquivalenceClasses.FindSet(parentNode) != state.EquivalenceClasses.FindSet(childNode.Origin))
                {
                    return(ExploreChildNodeResult.NonCancellativityDetected);
                }
            }
            else
            {
                // The child node has not been discovered, so let's discover it by inserting it
                // into the search tree.
                childNode = state.InsertChildNode(parentNode, nextVertex, parentNode);
            }

            if (childNode.Explored)
            {
                return(state.NodeIsZeroEquivalent(childNode) ? ExploreChildNodeResult.ZeroEquivalent : ExploreChildNodeResult.NotZeroEquivalent);
            }

            // Code for equivalence class searching begins here
            // Stack containing all the nodes to explore (by path transformation)
            var equivClassStack = new Stack <SearchTreeNodeOld <TVertex> >(state.EquivalenceClasses.GetSet(childNode));

            // This could happen with the current code, namely when the current childNode was encountered
            // in a previous call to ExploreChildNodes (in EquivClassExploreNode, as transformationNode)
            // and 'node' was found to be zero equivalent (in which case the search is cancelled
            // before transformationNode is explored in EquivClassExploreNode)
            // Thought: Would probably be good to have several Explored properties (or an
            // enum-valued Explored property) containing information about the extent to which the
            // node is explored (explored-as-in-encountered-in-EquivClassExploreNode would be
            // useful here; more easily understood and maintained than this comment methinks)
            if (equivClassStack.Count != 1)
            {
                return(ExploreChildNodeResult.ZeroEquivalent);
            }

            while (equivClassStack.Count > 0)
            {
                var equivalentNode = equivClassStack.Pop();
                if (equivalentNode.Explored)
                {
                    continue;
                }
                if (settings.UseMaxLength && equivalentNode.PathLengthInVertices > settings.MaxPathLength + 1)
                {
                    return(ExploreChildNodeResult.TooLongPath);
                }
                var exploredNodeWasFoundZeroEquivalent = EquivClassExploreNode(transformationRuleTree, equivalentNode, state, equivClassStack, parentNode);
                if (exploredNodeWasFoundZeroEquivalent)
                {
                    return(ExploreChildNodeResult.ZeroEquivalent);
                }
            }

            // Child node was not zero-equivalent
            state.Stack.Push(childNode);
            return(ExploreChildNodeResult.NotZeroEquivalent);
        }
        /// <summary>
        /// Explores the specified node in the equivalence class search.
        /// </summary>
        /// <typeparam name="TVertex">The type of the vertices in the quiver.</typeparam>
        /// <param name="node">The node to explore.</param>
        /// <param name="equivClassStack">The stack or queue of nodes to explore.</param>
        /// <param name="state">The state of the computation.</param>
        /// <param name="transformationRuleTree">The transformation rule tree.</param>
        /// <param name="settings">The computation settings.</param>
        /// <returns>A value of the <see cref="EquivalenceClassResult"/> enum indicating whether
        /// the node was <em>found</em> to be zero-equivalent. That is, the returned value is
        /// <see cref="EquivalenceClassResult.Zero"/> <em>only</em> if the node is zero-equivalent
        /// but may be <see cref="EquivalenceClassResult.Nonzero"/> even if the node is
        /// zero-equivalent.
        /// <remarks>
        /// <para>A node is found to be zero-equivalent either if its path can be killed or if
        /// the path is equivalent (up to replacement) to a path that has previously been
        /// determined to be zero-equivalent.</para>
        /// <para>This method may modify the
        /// <see cref="AnalysisStateForSingleStartingVertex{TVertex}.SearchTree"/>,
        /// <see cref="AnalysisStateForSingleStartingVertex{TVertex}.EquivalenceClasses"/>, and
        /// <see cref="AnalysisStateForSingleStartingVertex{TVertex}.LongestPathEncounteredNode"/>
        /// properties of the <paramref name="state"/> argument.</para>
        /// </remarks>
        private EquivalenceClassResult ExploreNodeForEquivalenceClassSearch <TVertex>(
            SearchTreeNode <TVertex> node,
            Stack <SearchTreeNode <TVertex> > equivClassStack,
            AnalysisStateForSingleStartingVertex <TVertex> state,
            TransformationRuleTreeNode <TVertex> transformationRuleTree,
            MaximalNonzeroEquivalenceClassRepresentativeComputationSettings settings)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            // A list of the vertices that appear after the path being considered from transformation.
            // The vertices are in reversed order.
            var trailingVertexPath = new List <TVertex>();

            foreach (var endingNode in node.ReversePathOfNodes) // The vertex (i.e., last vertex) of endingNode is the last vertex in the subpath
            {
                var transformationNode = transformationRuleTree;
                foreach (var startingNode in endingNode.ReversePathOfNodes) // The vertex (i.e., last vertex) of startingNode is the first vertex in the subpath
                {
                    var pathVertex = startingNode.Vertex;
                    if (!transformationNode.Children.TryGetValue(pathVertex, out transformationNode))
                    {
                        break;
                    }

                    if (transformationNode.CanBeKilled)
                    {
                        state.EquivalenceClasses.Union(node, state.ZeroDummyNode);
                        return(EquivalenceClassResult.Zero);
                    }

                    // If replacement is possible, do the replacement on the subpath
                    // and add a search tree node for the entire resulting path
                    if (transformationNode.CanBeReplaced)
                    {
                        // Add search tree nodes for replacement path

                        // This doesn't work when pathNode is the root ...
                        // var lastUnreplacedNode = pathNode.Parent;
                        // var curNode = lastUnreplacedNode;

                        // ... so instead do the following (with Skip), which assumes that the replacement
                        // path has the same first vertex (which it does for the semimonomial unbound
                        // quivers under consideration)
                        var firstNodeInTransformedPath = startingNode;
                        var curNode = firstNodeInTransformedPath;

                        foreach (var vertex in transformationNode.ReplacementPath.Vertices.Skip(1))
                        {
                            curNode = state.GetInsertChildNode(curNode, vertex);
                        }

                        // Add search tree nodes for the trailing path
                        foreach (var vertex in trailingVertexPath.Reversed())
                        {
                            curNode = state.GetInsertChildNode(curNode, vertex);
                        }

                        // We now have the node obtained by applying the replacement rule.
                        var transformedNode = curNode;

                        // Do stuff with its path length.
                        bool tooLong = DoPathLengthCheck(transformedNode, state, settings) == PathLengthCheckResult.TooLongPath;
                        if (tooLong)
                        {
                            return(EquivalenceClassResult.TooLongPath);
                        }

                        if (state.NodeIsZeroEquivalent(transformedNode))
                        {
                            state.EquivalenceClasses.Union(node, transformedNode); // Unioning with state.ZeroDummyNode would work equally well
                            return(EquivalenceClassResult.Zero);
                        }

                        // If transformedNode.HasBeenDiscoveredDuringEquivalenceClassComputation
                        // at this point, then it was discovered during this equivalence class
                        // search (not during an earlier search that ended with zero equivalence),
                        // and in this case, we should ignore it!

                        if (!transformedNode.HasBeenDiscoveredDuringEquivalenceClassComputation)
                        {
                            transformedNode.HasBeenDiscoveredDuringEquivalenceClassComputation = true;
                            state.EquivalenceClasses.Union(node, transformedNode);
                            equivClassStack.Push(transformedNode);
                        }
                    }
                }

                trailingVertexPath.Add(endingNode.Vertex);
            }

            return(EquivalenceClassResult.Nonzero);
        }
        /// <summary>
        /// Gets a node according to a specified paths, inserting nodes without any transformation
        /// data into the tree as necessary along the way.
        /// </summary>
        /// <typeparam name="TVertex">The type of the vertices.</typeparam>
        /// <param name="path">The path according to which to get the node from the tree.</param>
        /// <param name="root">The root node of the tree.</param>
        /// <returns>The retrieved node.</returns>
        /// <remarks>Additional nodes without any transformation data are inserted into the tree as
        /// necessary between the root node and the node to insert.</remarks>
        private TransformationRuleTreeNode <TVertex> GetOrInsertDefaultNode <TVertex>(Path <TVertex> path, TransformationRuleTreeNode <TVertex> root)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            var curNode = root;

            foreach (var vertex in path.Vertices.Reverse())
            {
                if (!curNode.Children.ContainsKey(vertex))
                {
                    curNode.children[vertex] = new TransformationRuleTreeNode <TVertex>(false, null, curNode);
                }
                curNode = curNode.Children[vertex];
            }

            return(curNode);
        }
        private void AddRulesFromSingleLinearCombination <TVertex>(LinearCombination <Path <TVertex> > linComb, TransformationRuleTreeNode <TVertex> root)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            switch (linComb.ElementToCoefficientDictionary.Count)
            {
            case 0: return;

            case 1:
                var path = linComb.Elements.Single();
                var node = GetOrInsertDefaultNode(path, root);
                node.CanBeKilled = true;
                break;

            case 2:
                var coefficients = linComb.ElementToCoefficientDictionary.Values.ToList();
                var paths        = linComb.Elements.ToList(); // Could be in different order from the coefficients, but don't care
                if (coefficients[1] == -coefficients[0])
                {
                    GetOrInsertDefaultNode(paths[0], root).ReplacementPath = paths[1];
                    GetOrInsertDefaultNode(paths[1], root).ReplacementPath = paths[0];
                }
                else
                {
                    throw new NotSupportedException("Linear combinations of length 2 with coefficients that are not the additive inverse of each other are not supported.");
                }
                break;

            default:
                throw new NotSupportedException("Linear combinations of length greater than 2 are not supported.");
            }
        }