public bool Equals(QuiverWithPotential <TVertex> otherQP)
 {
     if (otherQP is null)
     {
         return(false);
     }
     return(Quiver.Equals(otherQP.Quiver) && Potential.Equals(otherQP.Potential));
 }
Exemplo n.º 2
0
        /// <summary>
        /// Gets the &quot;classic&quot; weakly but not strongly cancellative QP.
        /// </summary>
        /// <returns>The &quot;classic&quot; weakly but not strongly cancellative QP.</returns>
        public static QuiverWithPotential <int> GetClassicWeaklyButNotStronglyCancellativeQP()
        {
            var baseQP   = GetClassicNonCancellativeQP();
            var vertices = baseQP.Quiver.Vertices.Append(6);
            var arrows   = baseQP.Quiver.Arrows.Append(new Arrow <int>(4, 6));
            var quiver   = new Quiver <int>(vertices, arrows);
            var qp       = new QuiverWithPotential <int>(quiver, baseQP.Potential);

            return(qp);
        }
Exemplo n.º 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);
        }
Exemplo n.º 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));
        }
Exemplo n.º 5
0
        public static QuiverWithPotential <int> GetCycleQP(int numVertices, int firstVertex = DefaultFirstVertex)
        {
            if (!CycleParameterIsValid(numVertices))
            {
                throw new ArgumentOutOfRangeException(nameof(numVertices));
            }

            int n     = numVertices;
            var cycle = new DetachedCycle <int>(Enumerable.Range(firstVertex, n).Select(k => new Arrow <int>(k, (k + 1 - firstVertex).Modulo(n) + firstVertex)));
            var qp    = new QuiverWithPotential <int>(new Potential <int>(cycle, +1));

            return(qp);
        }
Exemplo n.º 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);
        }
Exemplo n.º 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);
        }
 /// <summary>
 /// Initializes a new instance of the <see cref="SelfInjectiveQP{TVertex}"/> class.
 /// </summary>
 /// <param name="qp">The quiver with potential.</param>
 /// <param name="nakayamaPermutation">The Nakayama permutation.</param>
 /// <remarks>Almost no sanity checks are performed on the parameters.</remarks>
 public SelfInjectiveQP(QuiverWithPotential <TVertex> qp, IReadOnlyDictionary <TVertex, TVertex> nakayamaPermutation) : this(qp, new NakayamaPermutation <TVertex>(nakayamaPermutation))
 {
 }
 /// <summary>
 /// Initializes a new instance of the <see cref="SelfInjectiveQP{TVertex}"/> class.
 /// </summary>
 /// <param name="qp">The quiver with potential.</param>
 /// <param name="nakayamaPermutation">The Nakayama permutation.</param>
 /// <remarks>Almost no sanity checks are performed on the parameters.</remarks>
 public SelfInjectiveQP(QuiverWithPotential <TVertex> qp, NakayamaPermutation <TVertex> nakayamaPermutation)
 {
     QP = qp ?? throw new ArgumentNullException(nameof(qp));
     NakayamaPermutation = nakayamaPermutation ?? throw new ArgumentNullException(nameof(nakayamaPermutation));
 }
Exemplo n.º 10
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));
        }
Exemplo n.º 11
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));
        }
Exemplo n.º 12
0
        // TODO: Output the isomorphism as well!
        public bool AreIsomorphic <TVertex1, TVertex2>(QuiverWithPotential <TVertex1> qp1, QuiverWithPotential <TVertex2> qp2)
            where TVertex1 : IEquatable <TVertex1>, IComparable <TVertex1>
            where TVertex2 : IEquatable <TVertex2>, IComparable <TVertex2>
        {
            if (qp1 == null)
            {
                throw new ArgumentNullException(nameof(qp1));
            }
            if (qp2 == null)
            {
                throw new ArgumentNullException(nameof(qp2));
            }

            if (qp1.Quiver.Vertices.Count != qp2.Quiver.Vertices.Count)
            {
                return(false);
            }

            var positiveCycleGroups1 = GetCyclesGroupedByLength(qp1, +1);
            var negativeCycleGroups1 = GetCyclesGroupedByLength(qp1, -1);
            var positiveCycleGroups2 = GetCyclesGroupedByLength(qp2, +1);
            var negativeCycleGroups2 = GetCyclesGroupedByLength(qp2, -1);

            // Check that the number of cycles of a fixed length and sign in qp1 and qp2 are equal, for every length and sign
            var posTuples1 = positiveCycleGroups1.Select(group => (group.First().Length, group.Count()));
            var posTuples2 = positiveCycleGroups2.Select(group => (group.First().Length, group.Count()));

            if (!posTuples1.EqualUpToOrder(posTuples2))
            {
                return(false);
            }

            var negTuples1 = negativeCycleGroups1.Select(group => (group.First().Length, group.Count()));
            var negTuples2 = negativeCycleGroups2.Select(group => (group.First().Length, group.Count()));

            if (!negTuples1.EqualUpToOrder(negTuples2))
            {
                return(false);
            }

            GetVertexMaps(out var vertexMap1, out var vertexMap2, out var abstractVertices);
            var z3   = new Context();
            var goal = z3.MkGoal(models: true, unsatCores: true, proofs: false);

            string[] vertexNames = abstractVertices.Select(k => $"V{k}").ToArray();

            var vertexSort    = z3.MkEnumSort("Vertex", vertexNames);
            var z3Vertices    = vertexSort.Consts;
            var vertexProduct = from v1 in abstractVertices
                                from v2 in abstractVertices
                                select(v1, v2);

            // Define uninterpreted adjacency functions and provide assertions for their every value.
            // This is the only way to "define" a function in the API it seems.
            // The alternative, to muddy up the assertions on f, does not seem appetizing.
            var a1 = z3.MkFuncDecl("a1", new Sort[] { vertexSort, vertexSort }, z3.MkBoolSort());

            foreach (var(v1, v2) in vertexProduct)
            {
                var  z3Vertex1  = z3Vertices[v1];
                var  z3Vertex2  = z3Vertices[v2];
                bool arrowInQp1 = qp1.Quiver.AdjacencyLists[vertexMap1[v1]].Contains(vertexMap1[v2]);
                goal.Assert(z3.MkEq(a1.Apply(z3Vertex1, z3Vertex2), z3.MkBool(arrowInQp1)));
            }

            var a2 = z3.MkFuncDecl("a2", new Sort[] { vertexSort, vertexSort }, z3.MkBoolSort());

            foreach (var(v1, v2) in vertexProduct)
            {
                var  z3Vertex1  = z3Vertices[v1];
                var  z3Vertex2  = z3Vertices[v2];
                bool arrowInQp2 = qp2.Quiver.AdjacencyLists[vertexMap2[v1]].Contains(vertexMap2[v2]);
                goal.Assert(z3.MkEq(a2.Apply(z3Vertex1, z3Vertex2), z3.MkBool(arrowInQp2)));
            }

            var f = z3.MkFuncDecl("f", vertexSort, vertexSort);

            goal.Assert(z3.MkDistinct(vertexSort.Consts.Select(v => f.Apply(v)).ToArray()));

            foreach (var(v1, v2) in vertexProduct)
            {
                var  z3Vertex1  = z3Vertices[v1];
                var  z3Vertex2  = z3Vertices[v2];
                bool arrowInQp1 = qp1.Quiver.AdjacencyLists[vertexMap1[v1]].Contains(vertexMap1[v2]);
                goal.Assert(z3.MkEq(a1.Apply(z3Vertex1, z3Vertex2), a2.Apply(f.Apply(z3Vertex1), f.Apply(z3Vertex2))));
            }

            // Define uninterpreted constants for the cycle vertices
            // Store the constants in a dictionary indexed by cycle length and values a nested list indexed by cycleIndex (for cycles of that length and sign) and vertexIndex (in cycle)
            var positiveCycleLengthToCycleConsts1 = GetCycleConsts(1, +1, positiveCycleGroups1);
            var negativeCycleLengthToCycleConsts1 = GetCycleConsts(1, -1, negativeCycleGroups1);
            var positiveCycleLengthToCycleConsts2 = GetCycleConsts(2, +1, positiveCycleGroups2);
            var negativeCycleLengthToCycleConsts2 = GetCycleConsts(2, -1, negativeCycleGroups2);

            AssertThatFMapsEveryCycleToAnotherCycleOfTheSameSign(positiveCycleLengthToCycleConsts1, positiveCycleLengthToCycleConsts2);
            AssertThatFMapsEveryCycleToAnotherCycleOfTheSameSign(negativeCycleLengthToCycleConsts1, negativeCycleLengthToCycleConsts2);

            var solver = z3.MkSolver();
            var status = solver.Check();

            switch (status)
            {
            case Status.SATISFIABLE: return(true);

            case Status.UNSATISFIABLE: return(false);

            case Status.UNKNOWN: throw new NotImplementedException();

            default: throw new NotImplementedException();
            }

            void AssertThatFMapsEveryCycleToAnotherCycleOfTheSameSign(
                Dictionary <int, List <CircularList <Expr> > > cycleLengthToCycleConsts1,
                Dictionary <int, List <CircularList <Expr> > > cycleLengthToCycleConsts2)
            {
                foreach (var cycleLength in cycleLengthToCycleConsts1.Keys)
                {
                    var cycles1 = cycleLengthToCycleConsts1[cycleLength];
                    var cycles2 = cycleLengthToCycleConsts2[cycleLength];
                    foreach (var cycle in cycles1)
                    {
                        var expr = GetBooleanExpressionForCycleBeingMappedToOneOfManyCycles(cycle, cycles2);
                        goal.Assert(expr);
                    }
                }
            }

            BoolExpr GetBooleanExpressionForCycleBeingMappedToOneOfManyCycles(
                CircularList <Expr> cycle1,
                IEnumerable <CircularList <Expr> > cycles)
            {
                return(z3.MkOr(cycles.Select(cycle2 => GetBooleanExpressionForCycleBeingMappedToCycle(cycle1, cycle2))));
            }

            BoolExpr GetBooleanExpressionForCycleBeingMappedToCycle(
                CircularList <Expr> cycle1,
                CircularList <Expr> cycle2)
            {
                return(z3.MkOr(Enumerable.Range(0, cycle1.Count).Select(_ =>
                {
                    cycle2.RotateLeft(1);
                    return GetBooleanExpressionForPathBeingMappedToPath(cycle1, cycle2);
                })));
            }

            BoolExpr GetBooleanExpressionForPathBeingMappedToPath(
                IEnumerable <Expr> path1,
                IEnumerable <Expr> path2)
            {
                return(z3.MkAnd(path1.Zip(path2, (v1, v2) => z3.MkEq(f.Apply(v1), v2))));
            }

            Dictionary <int, List <CircularList <Expr> > > GetCycleConsts <TVertex>(
                int qpNum,
                int sign,
                IEnumerable <IGrouping <int, DetachedCycle <TVertex> > > cycleGroups)
                where TVertex : IEquatable <TVertex>, IComparable <TVertex>
            {
                string signString = sign > 0 ? "P" : "N";

                var output = new Dictionary <int, List <CircularList <Expr> > >();

                foreach (var cycleGroup in cycleGroups)
                {
                    var cycleGroupLength = cycleGroup.First().Length;
                    output[cycleGroupLength] = new List <CircularList <Expr> >();

                    foreach (var(cycle, cycleIndex) in cycleGroup.EnumerateWithIndex())
                    {
                        output[cycleGroupLength].Add(new CircularList <Expr>());
                        foreach (var(vertex, vertexIndex) in cycle.CanonicalPath.Vertices.EnumerateWithIndex())
                        {
                            var constant = z3.MkConst($"q{qpNum}{signString}L{cycleGroupLength}C{cycleIndex}V{vertexIndex}", vertexSort);
                            output[cycleGroupLength][cycleIndex].Add(constant);
                        }
                    }
                }

                return(output);
            }

            IEnumerable <IGrouping <int, DetachedCycle <TVertex> > > GetCyclesGroupedByLength <TVertex>(QuiverWithPotential <TVertex> qp, int sign)
                where TVertex : IEquatable <TVertex>, IComparable <TVertex>
            {
                // Can't deconstruct the argument as (pair, coeff), because it matches the (pair, index) overload of Where and Select
                return(qp.Potential.LinearCombinationOfCycles.ElementToCoefficientDictionary.Where(pair => pair.Value == sign)
                       .Select(pair => pair.Key)
                       .GroupBy(c => c.Length));
            }

            // Would be better to do this with only one QP and call the function twice imo
            void GetVertexMaps(out Dictionary <int, TVertex1> _vertexMap1, out Dictionary <int, TVertex2> _vertexMap2, out int[] _abstractVertices)
            {
                _vertexMap1 = new Dictionary <int, TVertex1>();
                int vertexNumber = 0;

                foreach (var vertex in qp1.Quiver.Vertices)
                {
                    _vertexMap1[vertexNumber++] = vertex;
                }

                _vertexMap2  = new Dictionary <int, TVertex2>();
                vertexNumber = 0;
                foreach (var vertex in qp2.Quiver.Vertices)
                {
                    _vertexMap2[vertexNumber++] = vertex;
                }

                int _vertexCount = vertexNumber;

                _abstractVertices = Enumerable.Range(0, _vertexCount).ToArray();
            }
        }
        /// <summary>
        /// Creates a new instance of the <see cref="SemimonomialUnboundQuiver{TVertex}"/> class
        /// whose ideal is the Jacobian ideal of the potential of the given QP.
        /// </summary>
        /// <param name="qp">The QP whose corresponding semimonomial unbound quiver to create.</param>
        /// <returns>The semimonomial unbound quiver induced by <paramref name="qp"/>.</returns>
        /// <exception cref="NotSupportedException">The potential of <paramref name="qp"/> has a
        /// cycle with coefficient not equal to either of -1 and +1,
        /// or some arrow occurs multiple times in a single cycle of the potential of
        /// <paramref name="qp"/>.</exception>
        /// <exception cref="ArgumentException">For some arrow in the potential of
        /// <paramref name="qp"/> and sign, the arrow is contained in more than one cycle of that
        /// sign.</exception>
        /// <remarks>
        /// <para>The preconditions on the potential of <paramref name="qp"/> 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 SemimonomialUnboundQuiver <TVertex> CreateSemimonomialUnboundQuiverFromQP <TVertex>(QuiverWithPotential <TVertex> qp)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>
        {
            var semimonomialIdeal         = SemimonomialIdealFactory.CreateSemimonomialIdealFromPotential(qp.Potential);
            var semimonomialUnboundQuiver = new SemimonomialUnboundQuiver <TVertex>(qp.Quiver, semimonomialIdeal);

            return(semimonomialUnboundQuiver);
        }