/// <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);
        }
예제 #3
0
 internal AnalysisResultsForSingleStartingVertex(
     AnalysisStateForSingleStartingVertex <TVertex> state,
     CancellativityTypes cancellativityFailuresDetected)
     : this(
         state.ZeroDummyNode,
         state.SearchTree,
         state.MaximalPathRepresentatives,
         state.EquivalenceClasses,
         cancellativityFailuresDetected,
         state.TooLongPathEncountered,
         state.LongestPathEncounteredNode)
 {
 }
        /// <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));
        }
        /// <summary>
        /// Checks if the path is too long given the specified computation settings and also updates
        /// the <see cref="AnalysisStateForSingleStartingVertex{TVertex}.LongestPathEncounteredNode"/>
        /// property if necessary.
        /// </summary>
        /// <typeparam name="TVertex">The type of the vertices in the quiver.</typeparam>
        /// <param name="node">The node whose corresponding path to check.</param>
        /// <param name="state">The state of the computation.</param>
        /// <param name="settings">The computation settings, which indicates whether to detect too
        /// long paths.</param>
        /// <returns>A value of the <see cref="PathLengthCheckResult"/> enum indicating whether the
        /// path was too long.</returns>
        private PathLengthCheckResult DoPathLengthCheck <TVertex>(
            SearchTreeNode <TVertex> node,
            AnalysisStateForSingleStartingVertex <TVertex> state,
            MaximalNonzeroEquivalenceClassRepresentativeComputationSettings settings)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            if (node.PathLength <= state.LongestPathEncounteredNode.Length)
            {
                return(PathLengthCheckResult.Fine);
            }

            // node.Path is slow, but this will be executed at most
            // min(maxPathLength, largestPathLengthInQuiver) times or so
            state.LongestPathEncounteredNode = node.Path;

            return((settings.UseMaxLength && node.PathLength > settings.MaxPathLength) ? PathLengthCheckResult.TooLongPath : PathLengthCheckResult.Fine);
        }
        /// <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>
        /// Detects failure of weak cancellativity.
        /// </summary>
        /// <typeparam name="TVertex">The type of the vertices in the quiver.</typeparam>
        /// <param name="state">The state of the analysis after having done the search in the path
        /// tree.</param>
        /// <returns><see langword="true"/> if the analysis state contains a contradiction to weak
        /// cancellativity; <see langword="false"/> otherwise.</returns>
        private bool DetectFailureOfWeakCancellativity <TVertex>(AnalysisStateForSingleStartingVertex <TVertex> state) where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            // Instead of checking weak cancellativity in the naïve way:
            //   for every (canonical representative) path p
            //     check if there an arrow a with the following:
            //       for every (canonical representative) path q
            //         pa+I = qa+I implies p+I = q+I
            //     if not, return NotWeaklyCancellative
            //
            //   return WeaklyCancellative
            //
            // the code below checks cancellativity by iterating only once over the quotient set
            // (set of all equivalence classes). The intuition is that it operates on pa+I instead
            // of on p+I. That is, instead of looking at all equivalence classes that p+I extends to,
            // we look at equivalence classes that extend into pa+I.
            // Thus, the work of checking whether a path satisfies weak cancellativity
            // (corresponding to the body of the loop over p in the pseudocode above) is split over
            // potentially several iterations (each such iteration corresponding to only one
            // extending class pa+I).
            //
            // It should be noted that the nested loop above might not be as awful as it seems,
            // because we loop only over the *representative* paths (i.e., a transversal of the
            // quotient set). The naïve implementation might thus perform better than the
            // implementation below or at least not considerably much worse and have the benefit of
            // clarity and correctness.
            //
            // Unlike in the similar implementation for strong cancellativity, where a single counter-
            // example (p,a) suffices to terminate with NotCancellative, this implementation
            // requires for some path p and *all* arrows a the pair (p,a) to fail the second
            // condition of weak cancellativity. Because the work for a single path p is typically
            // split across several iterations, we need to keep track of the state for p between
            // iterations (whether or not we have found a distinguishing ("non-counterexample")
            // arrow for p yet). This is done with the dictionary d.
            //
            // The assumption that the quiver has no parallel arrows should be necessary in order
            // for the below code to be correct. Otherwise, a path p with pa+I = pb+I = qb+I might
            // be incorrectly flagged as not having a distinguishing arrow even if a
            // distinguishes p from other paths.

            // Dictionary that will associate to every canonical representative path that is not
            // maximal nonzero-equivalent a value indicating whether the path has a
            // "distinguishing" arrow, i.e., an arrow a satisfying the second condition of weak
            // cancellativity for the path (p say):
            //   for all paths q: pa+I = qa+I implies p+I = q+I
            // Important to note is that a fourth value is stored implicitly as non-membership in d.
            // This value indicates that no nonzero extension class has been processed yet (at the
            // end, it means that the canonical representative path has no nonzero extension class,
            // i.e., is maximal nonzero-equivalent).
            var d                  = new Dictionary <SearchTreeNode <TVertex>, WeakCancellativityStateForPath>();
            var zeroNode           = state.ZeroDummyNode;
            var stationaryPathNode = state.SearchTree;

            foreach (var equivalenceClass in state.EquivalenceClasses.GetSets())
            {
                // Exclude the zero class and the stationary class -- the zero path because
                // pa+I = 0+I is not interesting for the second condition for weak cancellativity
                // and the stationary path because it is not of the form pa+I.
                if (equivalenceClass.Contains(zeroNode) || equivalenceClass.Contains(stationaryPathNode))
                {
                    continue;
                }

                // Below, equivalenceClass is denoted by pa+I.
                // Dictionary to contain the canonical representative of the parent class p+I for
                // every distinct last arrow (the *only* parent for that arrow a iff the path
                // and arrow p,a satisfy the second condition of weak cancellativity)
                // This is used to detect for each arrow whether it fails the second condition of
                // weak cancellativity (for all paths q: pa+I = qa+I implies p+I = q+I).
                var parentDict = new Dictionary <Arrow <TVertex>, SearchTreeNode <TVertex> >();

                foreach (var pathNode in equivalenceClass)
                {
                    var curParent       = pathNode.Parent;
                    var canonicalParent = state.EquivalenceClasses.FindSet(curParent);
                    var lastArrow       = pathNode.ReversePathOfArrows.First();

                    // If we have already found a distinguishing arrow for the canonical parent,
                    // there's no need for additional work.
                    if (d.TryGetValue(canonicalParent, out var pathState) && pathState == WeakCancellativityStateForPath.HasDistinguishingArrow)
                    {
                        continue;
                    }

                    if (!parentDict.TryGetValue(lastArrow, out var storedParent))
                    {
                        // Note that the parent is stored in the parentDict only if d[parent] is
                        // not HasDistinguishingArrow. This makes sure that the else clause below
                        // does not overwrite HasDistinguishingArrow with HasNoDistinguishingArrowSoFar.
                        parentDict[lastArrow] = canonicalParent;
                        // Also, this write does not overwrite HasDistinguishingArrow either
                        d[canonicalParent] = WeakCancellativityStateForPath.HasDistinguishingArrowForCurrentChildClass;
                    }
                    else if (!canonicalParent.Equals(storedParent))
                    {
                        // Insert HasNoDistinguishingArrowSoFar into d if necessary (if not, the
                        // write is a no-op) indicating that the parents are not maximal
                        // nonzero-equivalent and that we have not yet found a distinguishing arrow
                        // for them.
                        d[canonicalParent] = WeakCancellativityStateForPath.HasNoDistinguishingArrowSoFar;
                        d[storedParent]    = WeakCancellativityStateForPath.HasNoDistinguishingArrowSoFar;
                    }
                }

                foreach (var(_, storedParent) in parentDict)
                {
                    if (d[storedParent] == WeakCancellativityStateForPath.HasDistinguishingArrowForCurrentChildClass)
                    {
                        d[storedParent] = WeakCancellativityStateForPath.HasDistinguishingArrow;
                    }
                }
            }

            return(!d.Values.All(pathState => pathState == WeakCancellativityStateForPath.HasDistinguishingArrow));
        }
        /// <summary>
        /// Detects failure of cancellativity.
        /// </summary>
        /// <typeparam name="TVertex">The type of the vertices in the quiver.</typeparam>
        /// <param name="state">The state of the analysis after having done the search in the path
        /// tree.</param>
        /// <returns><see langword="true"/> if the analysis state contains a contradiction to
        /// cancellativity; <see langword="false"/> otherwise.</returns>
        private bool DetectFailureOfCancellativity <TVertex>(AnalysisStateForSingleStartingVertex <TVertex> state) where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            // Instead of checking cancellativity in the naïve way:
            //   for every (canonical representative) path p
            //     for every (canonical representative) path q
            //       for every arrow a
            //         if pa+I = qa+I but p+I != q+I, return NotCancellative
            //
            //   return Cancellative
            //
            // the code below checks cancellativity by iterating only once over the quotient set
            // (set of all equivalence classes). The intuition is that it operates on pa+I instead
            // of on p+I. That is, instead of looking at all equivalence classes that p+I extends to,
            // we look at equivalence classes that extend into pa+I.
            // Thus, the work of checking whether a path satisfies cancellativity (corresponding to
            // the body of the loop over p in the pseudocode above) is split over potentially
            // several iterations (each such iteration corresponding to only one extending class pa+I).
            //
            // It should be noted that the nested loop above might not be as awful as it seems,
            // because we loop only over the *representative* paths (i.e., a transversal of the
            // quotient set). The naïve implementation might thus perform better than the
            // implementation below or at least not considerably much worse and have the benefit of
            // clarity and correctness.
            //
            // It should be straightforward that the below code only indicates that cancellativity
            // fails when it actually fails. Conversely, if cancellativity fails, say for a path p
            // and arrow a, then the below code indicates that cancellativity fails when the
            // equivalence class pa+I is processed.

            var zeroNode           = state.ZeroDummyNode;
            var stationaryPathNode = state.SearchTree;

            foreach (var equivalenceClass in state.EquivalenceClasses.GetSets())
            {
                // Exclude the zero class and the stationary class -- the zero class because
                // pa +I = 0+I is not interesting for cancellativity and the stationary class
                // because it is not of the form pa+I.
                if (equivalenceClass.Contains(zeroNode) || equivalenceClass.Contains(stationaryPathNode))
                {
                    continue;
                }

                // Dictionary to contain the canonical representative of an arbitrary parent for every distinct last arrow
                // (the *only* parent up to equivalence if the unbound quiver is cancellative).
                var parentDict = new Dictionary <Arrow <TVertex>, SearchTreeNode <TVertex> >();
                foreach (var pathNode in equivalenceClass)
                {
                    var curParent = pathNode.Parent;
                    var lastArrow = pathNode.ReversePathOfArrows.First();
                    if (!parentDict.TryGetValue(lastArrow, out var storedParent))
                    {
                        parentDict[lastArrow] = state.EquivalenceClasses.FindSet(curParent);
                    }
                    else
                    {
                        if (!state.EquivalenceClasses.FindSet(curParent).Equals(storedParent))
                        {
                            return(true);
                        }
                    }
                }
            }

            return(false);
        }