public QuiverWithPotential(Quiver <TVertex> quiver, Potential <TVertex> potential)
        {
            if (quiver == null)
            {
                throw new ArgumentNullException(nameof(quiver));
            }
            if (potential == null)
            {
                throw new ArgumentNullException(nameof(potential));
            }

            foreach (var cycle in potential.Cycles)
            {
                if (!quiver.Vertices.Contains(cycle.CanonicalPath.StartingPoint))
                {
                    throw new ArgumentException($"The starting point {cycle.CanonicalPath.StartingPoint} of the canonical path of one of the cycles in the potential is not a vertex in the quiver.");
                }

                foreach (var arrow in cycle.CanonicalPath.Arrows.Skip(1))
                {
                    if (!quiver.Vertices.Contains(arrow.Source))
                    {
                        throw new ArgumentException(String.Format("The vertex {0} is present in one of the cycles in the potential but is not a vertex in the quiver.", arrow.Source));
                    }
                }
            }

            Quiver    = quiver;
            Potential = potential;
        }
        public SelfInjectiveQP <int> GetSelfInjectiveTriangleQP(int numRows, int firstVertex = DefaultFirstVertex)
        {
            if (numRows < 2)
            {
                throw new ArgumentOutOfRangeException(nameof(numRows));
            }

            var qp        = UsefulQPs.GetTriangleQP(numRows);
            var potential = new Potential <int>();

            // Rotation "once" clockwise to get the Nakayama permutation
            var nakayamaPermutation = new Dictionary <int, int>();
            // Start with the right-most "column" (1, 3, 6, 10, ...), which is mapped to the bottom row,
            // and go left to (2, 5, 9, ...), which is mapped to the second last row, and so on.
            var columnVertices = Enumerable.Range(1, numRows).Select(k => Utility.TriangularNumber(k));

            for (int rowIndex = numRows; rowIndex >= 1; rowIndex--)
            {
                var rowVertices      = Enumerable.Range(Utility.TriangularNumber(rowIndex - 1) + 1, rowIndex).Reverse();
                var inputOutputPairs = columnVertices.Zip(rowVertices, (x, y) => (x, y));
                foreach (var(x, y) in inputOutputPairs)
                {
                    nakayamaPermutation[x] = y;
                }

                columnVertices = columnVertices.Select(x => x - 1).Skip(1);
            }

            return(new SelfInjectiveQP <int>(qp, nakayamaPermutation));
        }
Example #3
0
        /// <summary>
        /// Gets the &quot;classic&quot; non-cancellative QP, which is not even weakly cancellative.
        /// </summary>
        /// <returns>The &quot;classic&quot; non-cancellative QP.</returns>
        public static QuiverWithPotential <int> GetClassicNonCancellativeQP()
        {
            var potential = new Potential <int>();

            potential = potential.AddCycle(new DetachedCycle <int>(1, 2, 4, 5, 1), -1);
            potential = potential.AddCycle(new DetachedCycle <int>(1, 3, 4, 5, 1), +1);
            var qp = new QuiverWithPotential <int>(potential);

            return(qp);
        }
Example #4
0
        public static QuiverWithPotential <int> GetCobwebQP(int numVerticesInCenterPolygon, int firstVertex = DefaultFirstVertex)
        {
            if (!CobwebParameterIsValid(numVerticesInCenterPolygon))
            {
                throw new ArgumentOutOfRangeException(nameof(numVerticesInCenterPolygon));
            }

            int numLayers = (numVerticesInCenterPolygon - 1) / 2;
            int numVerticesInFullLayer = 2 * numVerticesInCenterPolygon;

            var potential = new Potential <int>();

            // Center polygon
            potential = potential.AddCycle(new DetachedCycle <int>(Enumerable.Range(firstVertex, numVerticesInCenterPolygon).AppendElement(firstVertex)), +1);

            if (numLayers > 1)
            {
                // First layer (the  squares and triangles between the first and second layers)
                var curLayer  = GetLayerVertices(0);
                var nextLayer = GetLayerVertices(1);
                for (int indexInLayer = 0; indexInLayer < numVerticesInCenterPolygon; indexInLayer++)
                {
                    var triangleVertices = new int[] { curLayer[indexInLayer], nextLayer[2 * indexInLayer - 1], nextLayer[2 * indexInLayer], curLayer[indexInLayer] };
                    potential = potential.AddCycle(new DetachedCycle <int>(triangleVertices), +1);

                    var squareVertices = new int[] { curLayer[indexInLayer], curLayer[indexInLayer + 1], nextLayer[2 * indexInLayer + 1], nextLayer[2 * indexInLayer], curLayer[indexInLayer] };
                    potential = potential.AddCycle(new DetachedCycle <int>(squareVertices), -1);
                }

                // Remaining layers (only squares)
                for (int layerIndex = 1; layerIndex < numLayers - 1; layerIndex++) // 0-based layer index
                {
                    curLayer  = GetLayerVertices(layerIndex);
                    nextLayer = GetLayerVertices(layerIndex + 1);
                    for (int indexInLayer = 0; indexInLayer < numVerticesInFullLayer; indexInLayer++)
                    {
                        int sign = (layerIndex + indexInLayer).Modulo(2) == 1 ? +1 : -1;

                        var squareVertices = sign == +1 ?
                                             new int[] { curLayer[indexInLayer + 1], curLayer[indexInLayer], nextLayer[indexInLayer], nextLayer[indexInLayer + 1], curLayer[indexInLayer + 1] } :
                        new int[] { curLayer[indexInLayer], curLayer[indexInLayer + 1], nextLayer[indexInLayer + 1], nextLayer[indexInLayer], curLayer[indexInLayer] };
                        potential = potential.AddCycle(new DetachedCycle <int>(squareVertices), sign);
                    }
                }
            }

            var qp = new QuiverWithPotential <int>(potential);

            return(qp);

            CircularList <int> GetLayerVertices(int layerIndex) => new CircularList <int>(GetVerticesInCobwebQPLayer(numVerticesInCenterPolygon, layerIndex, firstVertex));
        }
        public QuiverWithPotential(Potential <TVertex> potential)
        {
            if (potential == null)
            {
                throw new ArgumentNullException(nameof(potential));
            }

            var vertices = new HashSet <TVertex>(potential.Cycles.SelectMany(c => c.CanonicalPath.Vertices));
            var arrows   = new HashSet <Arrow <TVertex> >(potential.Cycles.SelectMany(c => c.CanonicalPath.Arrows));
            var quiver   = new Quiver <TVertex>(vertices, arrows);

            Quiver    = quiver;
            Potential = potential;
        }
Example #6
0
        public static QuiverWithPotential <int> GetTriangleQP(int numRows, int firstVertex = DefaultFirstVertex)
        {
            if (!TriangleParameterIsValid(numRows))
            {
                throw new ArgumentOutOfRangeException(nameof(numRows));
            }

            if (numRows == 1)
            {
                var quiver = UsefulQuivers.GetTriangleQuiver(numRows, firstVertex);
                return(new QuiverWithPotential <int>(quiver, new Potential <int>()));
            }

            var potential = new Potential <int>();

            var curRowVertices = new List <int>()
            {
                firstVertex
            };
            int nextVertex = firstVertex + 1;

            for (int rowIndex = 1; rowIndex <= numRows - 1; rowIndex++) // 1-based row index
            {
                var nextRowVertices = Enumerable.Range(nextVertex, rowIndex + 1).ToList();
                nextVertex += rowIndex + 1;

                // (2*rowIndex - 1) triangles to add. Draw a small triangle QP to realize that
                // the cycles below are the ones to add.
                for (int indexInRow = 0; indexInRow < rowIndex; indexInRow++)
                {
                    var cycleVertices = new int[] { curRowVertices[indexInRow], nextRowVertices[indexInRow + 1], nextRowVertices[indexInRow], curRowVertices[indexInRow] };
                    potential = potential.AddCycle(new DetachedCycle <int>(cycleVertices), +1); // Positive for clockwise cycles.
                }

                for (int indexInRow = 0; indexInRow < rowIndex - 1; indexInRow++)
                {
                    var cycleVertices = new int[] { curRowVertices[indexInRow + 1], curRowVertices[indexInRow], nextRowVertices[indexInRow + 1], curRowVertices[indexInRow + 1] };
                    potential = potential.AddCycle(new DetachedCycle <int>(cycleVertices), -1); // Negative for counterclockwise cycles.
                }

                curRowVertices = nextRowVertices;
            }

            var qp = new QuiverWithPotential <int>(potential);

            return(qp);
        }
Example #7
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="numRows">The number of rows of vertices.</param>
        /// <param name="firstVertex"></param>
        /// <returns></returns>
        public static QuiverWithPotential <int> GetSquareQP(int numRows, int firstVertex = DefaultFirstVertex)
        {
            if (!SquareParameterIsValid(numRows))
            {
                throw new ArgumentOutOfRangeException(nameof(numRows));
            }

            int numVerticesInRow = numRows;
            int numVertices      = numVerticesInRow * numRows;

            if (numRows == 1)
            {
                var quiver = UsefulQuivers.GetSquareQuiver(numRows, firstVertex);
                return(new QuiverWithPotential <int>(quiver, new Potential <int>()));
            }

            var potential = new Potential <int>();

            for (int rowIndex = 0; rowIndex < numRows - 1; rowIndex++)
            {
                var curRow  = GetVerticesInSquareQPRow(numRows, rowIndex, firstVertex).ToList();
                var nextRow = GetVerticesInSquareQPRow(numRows, rowIndex + 1, firstVertex).ToList();

                for (int indexInRow = 0; indexInRow < numVerticesInRow - 1; indexInRow++)
                {
                    int sign          = (rowIndex + indexInRow).Modulo(2) == 0 ? +1 : -1;
                    var cycleVertices = sign == +1 ?
                                        new int[] { curRow[indexInRow], curRow[indexInRow + 1], nextRow[indexInRow + 1], nextRow[indexInRow], curRow[indexInRow] } :
                    new int[] { curRow[indexInRow + 1], curRow[indexInRow], nextRow[indexInRow], nextRow[indexInRow + 1], curRow[indexInRow + 1] };
                    potential = potential.AddCycle(new DetachedCycle <int>(cycleVertices), sign);
                }
            }

            var qp = new QuiverWithPotential <int>(potential);

            return(qp);
        }
Example #8
0
        public static QuiverWithPotential <int> GetPointedFlowerQP(int numPeriods, int firstVertex = DefaultFirstVertex)
        {
            if (!PointedFlowerParameterIsValid(numPeriods))
            {
                throw new ArgumentOutOfRangeException(nameof(numPeriods));
            }

            int numLayers = UsefulQuivers.GetNumberOfLayersInPointedFlowerQuiver(numPeriods);
            int numVerticesInFullInnerLayer = 2 * numPeriods;
            int numVerticesInOuterLayer     = 3 * numPeriods;

            var potential = new Potential <int>();

            if (numLayers == 2)
            {
                int centerVertex = GetLayerVertices(0).Single();
                var nextLayer    = GetLayerVertices(1);
                for (int periodIndex = 0; periodIndex < numPeriods; periodIndex++)
                {
                    int indexInNextLayer = 3 * periodIndex;

                    // Square
                    var positiveCycleVertices = new int[] { centerVertex, nextLayer[indexInNextLayer], nextLayer[indexInNextLayer + 1], nextLayer[indexInNextLayer + 2], centerVertex };

                    // Triangle
                    var negativeCycleVertices = new int[] { centerVertex, nextLayer[indexInNextLayer + 3], nextLayer[indexInNextLayer + 2], centerVertex };

                    potential = potential.AddCycle(new DetachedCycle <int>(positiveCycleVertices), +1);
                    potential = potential.AddCycle(new DetachedCycle <int>(negativeCycleVertices), -1);
                }

                return(new QuiverWithPotential <int>(potential));
            }

            // Polygons between first and second layer
            {
                int centerVertex = GetLayerVertices(0).Single();
                var nextLayer    = GetLayerVertices(1);
                for (int periodIndex = 0; periodIndex < numPeriods; periodIndex++)
                {
                    int indexInNextLayer = 2 * periodIndex;

                    // Triangles
                    var positiveCycleVertices = new int[] { centerVertex, nextLayer[indexInNextLayer], nextLayer[indexInNextLayer + 1], centerVertex };
                    var negativeCycleVertices = new int[] { centerVertex, nextLayer[indexInNextLayer + 2], nextLayer[indexInNextLayer + 1], centerVertex };

                    potential = potential.AddCycle(new DetachedCycle <int>(positiveCycleVertices), +1);
                    potential = potential.AddCycle(new DetachedCycle <int>(negativeCycleVertices), -1);
                }
            }

            // Cobweb layers
            for (int layerIndex = 1; layerIndex < numLayers - 2; layerIndex++) // 0-based layer index
            {
                var curLayer  = GetLayerVertices(layerIndex);
                var nextLayer = GetLayerVertices(layerIndex + 1);
                for (int indexInLayer = 0; indexInLayer < numVerticesInFullInnerLayer; indexInLayer++)
                {
                    int sign = (layerIndex + indexInLayer).Modulo(2) == 0 ? +1 : -1;

                    var squareVertices = sign == +1 ?
                                         new int[] { curLayer[indexInLayer + 1], curLayer[indexInLayer], nextLayer[indexInLayer], nextLayer[indexInLayer + 1], curLayer[indexInLayer + 1] } :
                    new int[] { curLayer[indexInLayer], curLayer[indexInLayer + 1], nextLayer[indexInLayer + 1], nextLayer[indexInLayer], curLayer[indexInLayer] };
                    potential = potential.AddCycle(new DetachedCycle <int>(squareVertices), sign);
                }
            }

            // Outermost layer of polygons
            {
                int layerIndex = numLayers - 2;
                var curLayer   = GetLayerVertices(layerIndex);
                var nextLayer  = GetLayerVertices(layerIndex + 1);
                for (int indexInPeriod = 0; indexInPeriod < numPeriods; indexInPeriod++)
                {
                    // Add pentagon
                    int indexInPenultimateLayer = 2 * indexInPeriod;
                    int indexInUltimateLayer    = 3 * indexInPeriod;
                    int pentagonSign            = layerIndex.Modulo(2) == 0 ? +1 : -1; // Remember that layerIndex is the index of the *penultimate* layer
                    var pentagonVertices        = pentagonSign == +1 ?
                                                  new int[] { curLayer[indexInPenultimateLayer + 1], curLayer[indexInPenultimateLayer], nextLayer[indexInUltimateLayer], nextLayer[indexInUltimateLayer + 1], nextLayer[indexInUltimateLayer + 2], curLayer[indexInPenultimateLayer + 1] } :
                    new int[] { curLayer[indexInPenultimateLayer], curLayer[indexInPenultimateLayer + 1], nextLayer[indexInUltimateLayer + 2], nextLayer[indexInUltimateLayer + 1], nextLayer[indexInUltimateLayer], curLayer[indexInPenultimateLayer] };
                    potential = potential.AddCycle(new DetachedCycle <int>(pentagonVertices), pentagonSign);

                    // Add square
                    int squareSign = -pentagonSign;
                    indexInPenultimateLayer += 1;
                    indexInUltimateLayer    += 2;
                    var squareVertices = squareSign == +1 ?
                                         new int[] { curLayer[indexInPenultimateLayer + 1], curLayer[indexInPenultimateLayer], nextLayer[indexInUltimateLayer], nextLayer[indexInUltimateLayer + 1], curLayer[indexInPenultimateLayer + 1] } :
                    new int[] { curLayer[indexInPenultimateLayer], curLayer[indexInPenultimateLayer + 1], nextLayer[indexInUltimateLayer + 1], nextLayer[indexInUltimateLayer], curLayer[indexInPenultimateLayer] };
                    potential = potential.AddCycle(new DetachedCycle <int>(squareVertices), squareSign);
                }
            }

            var qp = new QuiverWithPotential <int>(potential);

            return(qp);

            CircularList <int> GetLayerVertices(int layerIndex)
            => new CircularList <int>(GetVerticesInPointedFlowerQPLayer(numPeriods, layerIndex, firstVertex));
        }
Example #9
0
        public static QuiverWithPotential <int> GetEvenFlowerType2QP(int numVerticesInCenterPolygon, int firstVertex = DefaultFirstVertex)
        {
            if (!EvenFlowerType2ParameterIsValid(numVerticesInCenterPolygon))
            {
                throw new ArgumentOutOfRangeException(nameof(numVerticesInCenterPolygon));
            }

            int numLayers = UsefulQuivers.GetNumberOfLayersInEvenFlowerType2Quiver(numVerticesInCenterPolygon);
            int numVerticesInFullInnerLayer = 2 * numVerticesInCenterPolygon;
            int numVerticesInOuterLayer     = 3 * numVerticesInCenterPolygon;

            var potential = new Potential <int>();

            // Center polygon
            potential = potential.AddCycle(new DetachedCycle <int>(Enumerable.Range(firstVertex, numVerticesInCenterPolygon).AppendElement(firstVertex)), +1);

            // Full inner layers
            if (numLayers > 2)
            {
                // First layer (the squares and triangles between the first and second layers)
                var curLayer  = GetLayerVertices(0);
                var nextLayer = GetLayerVertices(1);
                for (int indexInLayer = 0; indexInLayer < numVerticesInCenterPolygon; indexInLayer++)
                {
                    var triangleVertices = new int[] { curLayer[indexInLayer], nextLayer[2 * indexInLayer - 1], nextLayer[2 * indexInLayer], curLayer[indexInLayer] };
                    potential = potential.AddCycle(new DetachedCycle <int>(triangleVertices), +1);

                    var squareVertices = new int[] { curLayer[indexInLayer], curLayer[indexInLayer + 1], nextLayer[2 * indexInLayer + 1], nextLayer[2 * indexInLayer], curLayer[indexInLayer] };
                    potential = potential.AddCycle(new DetachedCycle <int>(squareVertices), -1);
                }

                // Remaining layers (only squares)
                for (int layerIndex = 1; layerIndex < numLayers - 2; layerIndex++) // 0-based layer index
                {
                    curLayer  = GetLayerVertices(layerIndex);
                    nextLayer = GetLayerVertices(layerIndex + 1);
                    for (int indexInLayer = 0; indexInLayer < numVerticesInFullInnerLayer; indexInLayer++)
                    {
                        int sign = (layerIndex + indexInLayer).Modulo(2) == 1 ? +1 : -1;

                        var squareVertices = sign == +1 ?
                                             new int[] { curLayer[indexInLayer + 1], curLayer[indexInLayer], nextLayer[indexInLayer], nextLayer[indexInLayer + 1], curLayer[indexInLayer + 1] } :
                        new int[] { curLayer[indexInLayer], curLayer[indexInLayer + 1], nextLayer[indexInLayer + 1], nextLayer[indexInLayer], curLayer[indexInLayer] };
                        potential = potential.AddCycle(new DetachedCycle <int>(squareVertices), sign);
                    }
                }
            }

            // Outer layer
            {
                int layerIndex = numLayers - 2;
                var curLayer   = GetLayerVertices(layerIndex);
                var nextLayer  = GetLayerVertices(layerIndex + 1);
                if (numLayers == 2)
                {
                    // Add squares and diamonds in this degenerate case
                    for (int indexInLayer = 0; indexInLayer < curLayer.Count; indexInLayer++)
                    {
                        var diamondVertices = new int[] { curLayer[indexInLayer], nextLayer[3 * indexInLayer - 2], nextLayer[3 * indexInLayer - 1], nextLayer[3 * indexInLayer], curLayer[indexInLayer] };
                        potential = potential.AddCycle(new DetachedCycle <int>(diamondVertices), +1);

                        var squareVertices = new int[] { curLayer[indexInLayer], curLayer[indexInLayer + 1], nextLayer[3 * indexInLayer + 1], nextLayer[3 * indexInLayer], curLayer[indexInLayer] };
                        potential = potential.AddCycle(new DetachedCycle <int>(squareVertices), -1);
                    }
                }
                else
                {
                    // Add squares and pentagons in the non-degenerate case
                    for (int indexInCenterLayer = 0; indexInCenterLayer < numVerticesInCenterPolygon; indexInCenterLayer++)
                    {
                        // Add square
                        int indexInPenultimateLayer = 2 * indexInCenterLayer;
                        int indexInUltimateLayer    = 3 * indexInCenterLayer;
                        int squareSign     = layerIndex.Modulo(2) == 1 ? +1 : -1; // Remember that layerIndex is the index of the *penultimate* layer
                        var squareVertices = squareSign == +1 ?
                                             new int[] { curLayer[indexInPenultimateLayer + 1], curLayer[indexInPenultimateLayer], nextLayer[indexInUltimateLayer], nextLayer[indexInUltimateLayer + 1], curLayer[indexInPenultimateLayer + 1] } :
                        new int[] { curLayer[indexInPenultimateLayer], curLayer[indexInPenultimateLayer + 1], nextLayer[indexInUltimateLayer + 1], nextLayer[indexInUltimateLayer], curLayer[indexInPenultimateLayer] };
                        potential = potential.AddCycle(new DetachedCycle <int>(squareVertices), squareSign);

                        // Add pentagon
                        int pentagonSign = -squareSign;
                        indexInPenultimateLayer += 1;
                        indexInUltimateLayer    += 1;
                        var pentagonVertices = pentagonSign == +1 ?
                                               new int[] { curLayer[indexInPenultimateLayer + 1], curLayer[indexInPenultimateLayer], nextLayer[indexInUltimateLayer], nextLayer[indexInUltimateLayer + 1], nextLayer[indexInUltimateLayer + 2], curLayer[indexInPenultimateLayer + 1] } :
                        new int[] { curLayer[indexInPenultimateLayer], curLayer[indexInPenultimateLayer + 1], nextLayer[indexInUltimateLayer + 2], nextLayer[indexInUltimateLayer + 1], nextLayer[indexInUltimateLayer], curLayer[indexInPenultimateLayer] };
                        potential = potential.AddCycle(new DetachedCycle <int>(pentagonVertices), pentagonSign);
                    }
                }
            }

            var qp = new QuiverWithPotential <int>(potential);

            return(qp);

            CircularList <int> GetLayerVertices(int layerIndex)
            => new CircularList <int>(GetVerticesInEvenFlowerType2QPLayer(numVerticesInCenterPolygon, layerIndex, firstVertex));
        }
Example #10
0
        /// <summary>
        /// Creates a new instance of the <see cref="SemimonomialIdeal{TVertex}"/> class that
        /// represents the Jacobian ideal of the given potential (in some unspecified quiver
        /// containing all the arrows in the potential).
        /// </summary>
        /// <param name="potential">The potential whose semimonomial Jacobian ideal to create.</param>
        /// <returns>The semimonomial Jacobian ideal of <paramref name="potential"/>.</returns>
        /// <exception cref="NotSupportedException"><paramref name="potential"/> has a cycle with
        /// coefficient not equal to either of -1 and +1,
        /// or some arrow occurs multiple times in a single cycle of <paramref name="potential"/>.</exception>
        /// <exception cref="ArgumentException">For some arrow in <paramref name="potential"/> and
        /// sign, the arrow is contained in more than one cycle of that sign.</exception>
        /// <remarks>
        /// <para>The preconditions on <paramref name="potential"/> as of this writing is that the
        /// the scalars are <c>-1</c> or <c>+1</c>, every arrow occurs in at most one cycle per
        /// sign, and every arrow occurs at most once per cycle.</para>
        /// <para>The reasoning behind the differentiation between <see cref="NotSupportedException"/>
        /// and <see cref="ArgumentException"/> is that <see cref="NotSupportedException"/> is
        /// thrown when things might work out in theory (e.g., all scalars could be -2 or +2
        /// instead of -1 or +1, or an arrow could appear more than once in a cycle if the cycle is
        /// a suitable power of some other cycle (and stars align if the arrow is also contained in
        /// a cycle of the opposite sign)), while <see cref="ArgumentException"/> is thrown when
        /// things do not work out even in theory (i.e., semimonomiality fails to hold). That is,
        /// <see cref="NotSupportedException"/> is thrown in cases that might be &quot;fixed&quot;
        /// in the future.</para>
        /// </remarks>
        public static SemimonomialIdeal <TVertex> CreateSemimonomialIdealFromPotential <TVertex>(Potential <TVertex> potential)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            // Validation
            var cycleCoefficients = new HashSet <int>(potential.LinearCombinationOfCycles.ElementToCoefficientDictionary.Values);

            if (!cycleCoefficients.IsSubsetOf(new int[] { -1, +1 }))
            {
                throw new NotSupportedException("Only potentials with all coefficients equal to +1 or -1 are supported.");
            }

            if (potential.Cycles.Any(cycle => cycle.Arrows.HasDuplicate()))
            {
                throw new NotSupportedException("Potentials with an arrow occurring more than once in a cycle are not supported.");
            }

            var signedCycles = potential.LinearCombinationOfCycles.ElementToCoefficientDictionary;
            var signedArrows = signedCycles.SelectMany(pair =>
            {
                var cycle = pair.Key;
                var sign  = pair.Value;
                return(cycle.Arrows.Select(arrow => (arrow, sign)));
            });

            if (signedArrows.TryGetDuplicate(out var duplicate))
            {
                throw new ArgumentException($"The potential has a signed arrow {duplicate} occurring in more than one cycle.", nameof(potential));
            }

            var monomialGenerators    = new List <Path <TVertex> >();
            var nonMonomialGenerators = new List <DifferenceOfPaths <TVertex> >();

            var arrows = potential.Cycles.SelectMany(cycle => cycle.Arrows).WithoutDuplicates();

            foreach (var arrow in arrows)
            {
                // Guaranteed by the validation to have only terms with coefficients +1 or -1
                // Also guaranteed to have at most 2 terms
                var linCombOfPaths = potential.DifferentiateCyclically(arrow);

                var numTerms = linCombOfPaths.NumberOfTerms;
                if (numTerms == 1)
                {
                    monomialGenerators.Add(linCombOfPaths.Elements.Single());
                }
                else if (numTerms == 2)
                {
                    var paths             = linCombOfPaths.Elements.ToList();
                    var differenceOfPaths = new DifferenceOfPaths <TVertex>(paths[0], paths[1]);
                    nonMonomialGenerators.Add(differenceOfPaths);
                }
                else
                {
                    System.Diagnostics.Debug.Fail($"{numTerms} terms in the cyclic derivative with respect to {arrow}. Expected at most two terms.");
                }
            }

            var semimonomialIdeal = new SemimonomialIdeal <TVertex>(monomialGenerators, nonMonomialGenerators);

            return(semimonomialIdeal);
        }