{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "84fbda16", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "We have 14 graphs.\n", "\n", "There are 0 graphs that evaluate to 0 under the morhpism from graphs to multivectors.\n", "\n", "Here are the vector fields the graphs are evaluated into:\n", "graph 1 : (-rho_y*rho_xy^2 + rho_y*rho_xx*rho_yy)*xi0 + (rho_x*rho_xy^2 - rho_x*rho_xx*rho_yy)*xi1\n", "graph 2 : (rho_y^2*rho_xxy - 2*rho_x*rho_y*rho_xyy + rho_x^2*rho_yyy)*xi0 + (-rho_y^2*rho_xxx + 2*rho_x*rho_y*rho_xxy - rho_x^2*rho_xyy)*xi1\n", "graph 3 : (rho*rho_yy*rho_xxy - 2*rho*rho_xy*rho_xyy + rho*rho_xx*rho_yyy)*xi0 + (-rho*rho_yy*rho_xxx + 2*rho*rho_xy*rho_xxy - rho*rho_xx*rho_xyy)*xi1\n", "graph 4 : (rho_y^2*rho_xxy - 2*rho_x*rho_y*rho_xyy + rho_x^2*rho_yyy)*xi0 + (-rho_y^2*rho_xxx + 2*rho_x*rho_y*rho_xxy - rho_x^2*rho_xyy)*xi1\n", "graph 5 : (-rho_y*rho_xy^2 + rho_y*rho_xx*rho_yy)*xi0 + (rho_x*rho_xy^2 - rho_x*rho_xx*rho_yy)*xi1\n", "graph 6 : (-rho_y*rho_xy^2 + rho_y*rho_xx*rho_yy)*xi0 + (rho_x*rho_xy^2 - rho_x*rho_xx*rho_yy)*xi1\n", "graph 7 : (rho_y*rho_xy^2 - rho_y*rho_xx*rho_yy)*xi0 + (-rho_x*rho_xy^2 + rho_x*rho_xx*rho_yy)*xi1\n", "graph 8 : (-2*rho_y*rho_xy^2 + 2*rho_y*rho_xx*rho_yy)*xi0 + (2*rho_x*rho_xy^2 - 2*rho_x*rho_xx*rho_yy)*xi1\n", "graph 9 : (-rho_y^2*rho_xxy + 2*rho_x*rho_y*rho_xyy - rho_x^2*rho_yyy)*xi0 + (rho_y^2*rho_xxx - 2*rho_x*rho_y*rho_xxy + rho_x^2*rho_xyy)*xi1\n", "graph 10 : (rho*rho_yy*rho_xxy - 2*rho*rho_xy*rho_xyy + rho*rho_xx*rho_yyy)*xi0 + (-rho*rho_yy*rho_xxx + 2*rho*rho_xy*rho_xxy - rho*rho_xx*rho_xyy)*xi1\n", "graph 11 : (rho_y^2*rho_xxy - 2*rho_x*rho_y*rho_xyy + rho_x^2*rho_yyy)*xi0 + (-rho_y^2*rho_xxx + 2*rho_x*rho_y*rho_xxy - rho_x^2*rho_xyy)*xi1\n", "graph 12 : (-rho_y*rho_xy^2 + rho_y*rho_xx*rho_yy)*xi0 + (rho_x*rho_xy^2 - rho_x*rho_xx*rho_yy)*xi1\n", "graph 13 : (-rho_y*rho_xy^2 + rho_y*rho_xx*rho_yy)*xi0 + (rho_x*rho_xy^2 - rho_x*rho_xx*rho_yy)*xi1\n", "graph 14 : (rho*rho_yy*rho_xxy - 2*rho*rho_xy*rho_xyy + rho*rho_xx*rho_yyy)*xi0 + (-rho*rho_yy*rho_xxx + 2*rho*rho_xy*rho_xxy - rho*rho_xx*rho_xyy)*xi1\n", "\n", "The vector fields have 11 linear relations among themselves.\n", "Claim 2: These relations are expressed by \n", " Vector space of degree 14 and dimension 11 over Rational Field\n", "Basis matrix:\n", "[ 1 0 0 0 0 0 0 0 0 0 0 0 -1 0]\n", "[ 0 1 0 0 0 0 0 0 0 0 -1 0 0 0]\n", "[ 0 0 1 0 0 0 0 0 0 0 0 0 0 -1]\n", "[ 0 0 0 1 0 0 0 0 0 0 -1 0 0 0]\n", "[ 0 0 0 0 1 0 0 0 0 0 0 0 -1 0]\n", "[ 0 0 0 0 0 1 0 0 0 0 0 0 -1 0]\n", "[ 0 0 0 0 0 0 1 0 0 0 0 0 1 0]\n", "[ 0 0 0 0 0 0 0 1 0 0 0 0 -2 0]\n", "[ 0 0 0 0 0 0 0 0 1 0 1 0 0 0]\n", "[ 0 0 0 0 0 0 0 0 0 1 0 0 0 -1]\n", "[ 0 0 0 0 0 0 0 0 0 0 0 1 -1 0] \n", "\n", "The tetrahedral flow in 2D is (rho_y^3*rho_xxx - 3*rho_x*rho_y^2*rho_xxy + 3*rho_x^2*rho_y*rho_xyy - rho_x^3*rho_yyy)*xi0*xi1 \n", "\n", "There are 3 vector fields in X_vector_fields that evaluate to 0 bivectors after taking the Schouten bracket with P.\n", "These vector fields that evaluate to 0 are obtained from the graphs:\n", "graph 3\n", "graph 10\n", "graph 14\n", "\n", "Proposition 3: The solution on vector fields is given by (2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) \n", "\n", "The space of solutions to the homogeneous system has dimension 1\n", "This shift is given by (Proposition 4) [(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)] and the corresponding vector field is (rho*rho_yy*rho_xxy - 2*rho*rho_xy*rho_xyy + rho*rho_xx*rho_yyy)*xi0 + (-rho*rho_yy*rho_xxx + 2*rho*rho_xy*rho_xxy - rho*rho_xx*rho_xyy)*xi1 \n", "\n", "The Hamiltonian vector field is given by (2*rho*rho_yy*rho_xxy - 4*rho*rho_xy*rho_xyy + 2*rho*rho_xx*rho_yyy)*xi0 + (-2*rho*rho_yy*rho_xxx + 4*rho*rho_xy*rho_xxy - 2*rho*rho_xx*rho_xyy)*xi1 \n", "\n", "Theorem 6: The shift in the cocycle space is given by 1/2 the Hamiltonian vector field.\n" ] } ], "source": [ "# 17-09-2024 The code below is a combination of already existing code by R. Buring, and adjustments made by F. Schipper \n", "# (particularly, the biggest adjustments are checking that the basis element(s) of the cocycle space are Hamiltonian, \n", "# commentary and printing of output). It is supplementary material accompanying proceedings written for the ISQS28 (Integrable \n", "#Systems and Quantum Symmetries) conference (see also: arxiv link??).\n", "\n", "# We import the following (see https://github.com/rburing/gcaops) to be able to run the code.\n", "from gcaops.graph.formality_graph import FormalityGraph\n", "from gcaops.algebra.differential_polynomial_ring import DifferentialPolynomialRing\n", "from gcaops.algebra.superfunction_algebra import SuperfunctionAlgebra\n", "from gcaops.graph.undirected_graph_complex import UndirectedGraphComplex\n", "from gcaops.graph.directed_graph_complex import DirectedGraphComplex\n", "\n", "# These are the encodings of the 14 Kontsevich graphs on 3 vertices and 1 sink in 2D.\n", "two_d_graphs=\"(0,1;2,3;1,3)+(0,1;1,2;1,3)+(0,3;2,3;2,3)+(0,3;2,3;1,3)+(0,2;2,3;1,3)+(0,3;1,2;1,3)+(0,3;2,3;1,2)+(0,3;1,2;1,2)+(0,2;2,3;1,2)+(0,2;1,2;1,2)+(0,1;1,3;1,2)+(0,3;1,3;1,2)+(0,1;1,3;2,3)+(0,1;1,3;1,3)\"\n", "\n", "# To move from the encodings of the graphs to actual graphs, we use the next function. The function splits the \n", "# encoding by vertex via ;, and then the target vertices by ,. A graph is returned on 3 vertices, 1 sink, and with edges\n", "# (origin vertex, target vertex). As an example, the first encoding (0,1;2,3;1,3) correspond to a graph with 1 sink (vertex 0),\n", "# and 3 regular vertices (vertices 1,2,3), with 6 edges (1,0), (1,1), (2,2), (2,3), (3,1), (3,3).\n", "def encoding_to_graph(encoding):\n", " targets = [tuple(int(v) for v in t.split(',')) for t in encoding[1:-1].split(\";\")] \n", " edges = sum([[(k+1,v) for v in t] for (k,t) in enumerate(targets)], [])\n", " return FormalityGraph(1, 3, edges) \n", " \n", "# Split the encodings and compute the corresponding graphs\n", "encodings = two_d_graphs.split(\"+\") \n", "graphs = [encoding_to_graph(e) for e in encodings]\n", "print('We have', len(graphs), 'graphs.\\n')\n", "\n", "# Create the differential polynomial ring. We are working in 2D, so we only have even coordinates x,y and the corresponding \n", "# odd coordinates xi[0] and xi[1]. rho is exactly the rho in a 2D Poisson bracket (P= rho dx dy). Finally, \n", "# max_differential_orders tells the programme how many times rho can be differentiated. The maximum is stipulated by the graphs, \n", "# as we cannot have double edges. Thus, the maximum in degree of each vertex is 3. As we will be looking at [[P,X]], we add\n", "# an extra +1 to the differential orders since taking the Schouten bracket with P introduces an extra derivative.\n", "D2=DifferentialPolynomialRing(QQ,('rho', ), ('x','y'), max_differential_orders=[3+1])\n", "rho, =D2.fibre_variables()\n", "x,y= D2.base_variables()\n", "even_coords=[x,y]\n", "\n", "S2.=SuperfunctionAlgebra(D2, D2.base_variables())\n", "xi=S2.gens()\n", "odd_coords=xi\n", "\n", "# We now compute the vector fields corresponding to the graphs. E is the Euler vector field in the sink (vertex 0), and\n", "# epsilon is the Levi-Civita tensor. Note that we have a Levi-Civita tensor at each of the vertices 1, 2, 3. We first compute\n", "# the sign of each term appearing in the formulas, and then compute the differential polynomial.\n", "X_vector_fields=[]\n", "E=x*xi[0]+y*xi[1] \n", "epsilon = xi[0]*xi[1] \n", "import itertools\n", "for g in graphs:\n", " term = S2.zero()\n", " for index_choice in itertools.product(itertools.permutations(range(2)), repeat=3): \n", " sign = epsilon[index_choice[0]] * epsilon[index_choice[1]]* epsilon[index_choice[2]]\n", " vertex_content = [E, S2(rho), S2(rho), S2(rho)]\n", " for ((source, target), index) in zip(g.edges(), sum(map(list, index_choice), [])):\n", " vertex_content[target] = vertex_content[target].diff(even_coords[index])\n", " term += sign * prod(vertex_content)\n", " X_vector_fields.append(term)\n", "\n", "# We check how many (if any) graphs evaluate to 0. \n", "zeros=X_vector_fields.count(0)\n", "print('There are', zeros, 'graphs that evaluate to 0 under the morhpism from graphs to multivectors.\\n')\n", "\n", "# In case the are graphs that evaluate to 0, the line below shows which graphs do so (note that the counting starts from 1!).\n", "#[k+1 for (k,X) in enumerate (X_vector_fields) if X==0]\n", "\n", "# In 2D on only 3 vertices and 1 sink, the multivectors are pretty small and can be printed easily.\n", "print('Here are the vector fields the graphs are evaluated into:')\n", "for i in range(len(X_vector_fields)):\n", " print('graph', i+1,':', X_vector_fields[i])\n", "print()\n", "\n", "# The next part is to find out linear relations of the vector fields we just computed. We look at the monomials that appear\n", "# in each xi[0] and xi[1] parts of the vector field, and store them in X_monomial_basis. \n", "X_monomial_basis = [set([]) for i in range(2)] \n", "for i in range(2): \n", " for X in X_vector_fields:\n", " X_monomial_basis[i]|=set(X[i].monomials())\n", "X_monomial_basis=[list(b) for b in X_monomial_basis]\n", "X_monomial_index= [{m:k for k,m in enumerate(b)} for b in X_monomial_basis]\n", "X_monomial_count= sum(len(b) for b in X_monomial_basis); X_monomial_count\n", "\n", "# Next, we use this monomial basis to create a matrix that identifies each vector field by the monomials that appear in it.\n", "X_evaluation_matrix= matrix(QQ, X_monomial_count, len(X_vector_fields), sparse=True)\n", "for i in range(len(X_vector_fields)):\n", " v=vector(QQ, X_monomial_count, sparse=True)\n", " index_shift=0\n", " for j in range(2): \n", " f=X_vector_fields[i][j]\n", " for coeff, monomial in zip(f.coefficients(), f.monomials()):\n", " monomial_index=X_monomial_index[j][monomial]\n", " v[index_shift+monomial_index]=coeff\n", " index_shift+=len(X_monomial_basis[j])\n", " X_evaluation_matrix.set_column(i,v)\n", "\n", "# We can now detect linear relation by computing the nullity of this matrix\n", "nullity = X_evaluation_matrix.right_nullity()\n", "print('The vector fields have', nullity, 'linear relations among themselves.')\n", "print('Claim 2: These relations are expressed by \\n', X_evaluation_matrix.right_kernel(), '\\n')\n", "\n", "# We now compute the tetrahedral flow. First, we create the Poisson bivector P and check that it satisfies [[P,P]]=0\n", "P= rho*epsilon \n", "if P.bracket(P)!=0:\n", " print('P is not a Poisson bivector. \\n')\n", "\n", "# Introduce the graph complex, and find the tetrahedron as a graph on 4 vertices and 6 edges in the cohomology. This \n", "# tetrahedron is unoriented, so we orient it. The next step is to make sure that the graph is built of wedges. This means \n", "# that there cannot be more than 2 outgoing edges at each vertex. This gives 2 graphs; one where 3 vertices have 2 outgoing\n", "# edges, and one where 2 vertices have 2 outgoing edges and 2 vertices have 1 outgoing edge. \n", "# tetrahedron_oriented_filtered.show() gives a drawing of these graphs. \n", "# Finally, the bivector corresponding to the graph is computed.\n", "\n", "GC=UndirectedGraphComplex(QQ, implementation='vector', sparse=True)\n", "tetrahedron= GC.cohomology_basis(4,6)[0]\n", "dGC=DirectedGraphComplex(QQ, implementation='vector')\n", "tetrahedron_oriented= dGC(tetrahedron)\n", "tetrahedron_oriented_filtered= tetrahedron_oriented.filter(max_out_degree=2)\n", "# tetrahedron_oriented_filtered.show()\n", "tetrahedron_operation= S2.graph_operation(tetrahedron_oriented_filtered)\n", "Q_tetra= tetrahedron_operation(P, P, P, P) /8\n", "print('The tetrahedral flow in 2D is', Q_tetra, '\\n')\n", "\n", "# Now that we have the tetrahedral flow, we see if we can create a vectorfield X from our previously computed \n", "# graphs-to-vector fields such that [[P,X]]= Q_tetra. We first look which bivectors X_bivectors the vector fields become \n", "# after taking the Schouten bracket with P.\n", "X_bivectors=[]\n", "for X in X_vector_fields:\n", " X_bivectors.append(P.bracket(X))\n", " \n", "zero_bivectors = X_bivectors.count(0)\n", "print('There are', zero_bivectors, 'vector fields in X_vector_fields that evaluate to 0 bivectors after taking the Schouten bracket with P.')\n", "print('These vector fields that evaluate to 0 are obtained from the graphs:')\n", "for (k,X) in enumerate(X_bivectors):\n", " if X==0:\n", " print('graph', k+1,)\n", "print()\n", "\n", "# Now, we extract the monomials appearing in these bivectors (as well as in Q_tetra).\n", "Q_monomial_basis={}\n", "from itertools import combinations\n", "for i,j in combinations(range(2),2):\n", " Q_monomial_basis[i,j]=set(Q_tetra[i,j].monomials())\n", " for P_X in X_bivectors:\n", " Q_monomial_basis[i,j]|= set(P_X[i,j].monomials())\n", " \n", "Q_monomial_basis={idx: list(b) for idx, b in Q_monomial_basis.items()}\n", "Q_monomial_index= {idx:{m:k for k,m in enumerate(b)} for idx, b in Q_monomial_basis.items()}\n", "Q_monomial_count=sum(len(b) for b in Q_monomial_basis.values());\n", "\n", "# We create the vector representation of Q_tetra in terms of the monomials.\n", "Q_tetra_vector= vector(QQ, Q_monomial_count, sparse=True)\n", "index_shift=0\n", "for i,j in Q_monomial_basis:\n", " for coeff, monomial in Q_tetra[i,j]:\n", " monomial_index= Q_monomial_index[i,j][monomial]\n", " Q_tetra_vector[monomial_index+index_shift]=coeff\n", " index_shift+=len(Q_monomial_basis[i,j])\n", "\n", "# We create the matrix that represents the X_bivectors in terms of the monomials.\n", "X_bivector_evaluation_matrix= matrix(QQ, Q_monomial_count, len(X_bivectors), sparse=True)\n", "for k in range(len(X_bivectors)):\n", " P_X=X_bivectors[k]\n", " v=vector(QQ,Q_monomial_count, sparse=True)\n", " index_shift=0\n", " for i,j in Q_monomial_basis:\n", " for coeff, monomial in P_X[i,j]:\n", " monomial_index=Q_monomial_index[i,j][monomial]\n", " v[monomial_index+index_shift]=coeff\n", " index_shift+=len(Q_monomial_basis[i,j])\n", " X_bivector_evaluation_matrix.set_column(k,v)\n", "\n", "# We solve the linear system.\n", "X_solution=X_bivector_evaluation_matrix.solve_right(Q_tetra_vector)\n", "print('Proposition 3: The solution on vector fields is given by', X_solution,' \\n')\n", "# So if we take 2*graph 1 +1*graph 2, evaluate this to a vector field X, then [[P, X]]= Q_tetra.\n", "# Note that because of the linear relations, this solution is not unique on the level of graphs.\n", "\n", "# Finally, we will check the homogeneous system. We look at the kernel of the X_bivector_evaluation_matrix and filter out the \n", "# nullity of the X_evaluation matrix\n", "X_cocycle_space= X_bivector_evaluation_matrix.right_kernel().quotient(X_evaluation_matrix.right_kernel())\n", "X_cocycles=[X_cocycle_space.lift(v) for v in X_cocycle_space.basis()]; X_cocycles\n", "if all(X_bivector_evaluation_matrix*X_cocycle==0 for X_cocycle in X_cocycles)!= True:\n", " print('There is an error in the cocycle space.')\n", "print('The space of solutions to the homogeneous system has dimension', X_cocycle_space.dimension(), )\n", "print ('This shift is given by (Proposition 4) ', X_cocycles, 'and the corresponding vector field is', X_vector_fields[2], '\\n')\n", "\n", "# We check that this shift is induced by the Hamiltonian vector field\n", "hamiltonian=\"(0,1;0,1)\"\n", "\n", "# We create a new encoding to graph definition, as this is a graph on 2 vertices and no sink.\n", "def hamiltonian_encoding_to_graph(encoding):\n", " targets = [tuple(int(v) for v in t.split(',')) for t in encoding[1:-1].split(\";\")]\n", " edges = sum([[(k,v) for v in t] for (k,t) in enumerate(targets)], [])\n", " return FormalityGraph(0, 2, edges)\n", " \n", "hamiltonian_graph= encoding_to_graph(hamiltonian)\n", "\n", "# This computation is also slightly different; we do not have the Euler vector field since we do not have a sink, and as\n", "# we have only 2 copies of the Poisson bivector, the index_choice for the sign is only on 2 repeats as well. Moreover, there\n", "# are only 2 vertices to give vertex_content to. \n", "epsilon= xi[0]*xi[1]\n", "import itertools\n", "hamiltonian_formula=S2.zero()\n", "for index_choice in itertools.product(itertools.permutations(range(2)),repeat=2):\n", " vertex_content = [S2(rho), S2(rho)]\n", " sign = epsilon[index_choice[0]] * epsilon[index_choice[1]]\n", " for ((source, target), index) in zip(hamiltonian_graph.edges(), sum(map(list,index_choice), [])):\n", " vertex_content[target] = diff(vertex_content[target],even_coords[index])\n", " hamiltonian_formula += sign * prod(vertex_content)\n", "\n", "hamiltonian_vector_field=P.bracket(hamiltonian_formula)\n", "print('The Hamiltonian vector field is given by', hamiltonian_vector_field, '\\n')\n", "\n", "if hamiltonian_vector_field== 2*X_vector_fields[2]:\n", " print('Theorem 6: The shift in the cocycle space is given by 1/2 the Hamiltonian vector field.') " ] }, { "cell_type": "code", "execution_count": null, "id": "5a7b2c63-50b3-4fa7-9a50-c3fb92d4f9d3", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "SageMath 10.1", "language": "sage", "name": "sagemath" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.18" } }, "nbformat": 4, "nbformat_minor": 5 }