{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "e962901f-d10e-4306-9a9e-a18876734d45", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "We have 324 graphs.\n", "\n", "There are 54 graphs that evaluate to 0 under the morhpism from graphs to multivectors.\n", "These graphs are:\n", "graph 32\n", "graph 38\n", "graph 63\n", "graph 75\n", "graph 85\n", "graph 88\n", "graph 112\n", "graph 113\n", "graph 115\n", "graph 119\n", "graph 139\n", "graph 142\n", "graph 144\n", "graph 156\n", "graph 166\n", "graph 167\n", "graph 169\n", "graph 172\n", "graph 173\n", "graph 174\n", "graph 181\n", "graph 182\n", "graph 183\n", "graph 193\n", "graph 196\n", "graph 202\n", "graph 203\n", "graph 204\n", "graph 220\n", "graph 223\n", "graph 233\n", "graph 234\n", "graph 239\n", "graph 240\n", "graph 247\n", "graph 250\n", "graph 252\n", "graph 253\n", "graph 254\n", "graph 255\n", "graph 262\n", "graph 263\n", "graph 264\n", "graph 274\n", "graph 277\n", "graph 287\n", "graph 288\n", "graph 293\n", "graph 294\n", "graph 301\n", "graph 304\n", "graph 322\n", "graph 323\n", "graph 324\n", "\n", "The vector fields have 201 linear relations among themselves.\n", "\n", " A maximal subset of linearly independent graphs is given by: [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 22, 23, 24, 25, 26, 28, 29, 32, 34, 35, 38, 40, 41, 43, 44, 47, 50, 52, 53, 56, 59, 68, 71, 80, 81, 82, 83, 93, 94, 95, 96, 97, 102, 104, 105, 106, 107, 108, 109, 110, 113, 116, 117, 119, 126, 128, 135, 137, 144, 153, 162, 164, 167, 170, 174, 176, 179, 185, 186, 187, 188, 189, 190, 191, 194, 196, 197, 205, 206, 207, 209, 210, 212, 213, 214, 215, 216, 217, 218, 223, 235, 240, 241, 242, 243, 244, 255, 267, 268, 270, 282, 284, 295, 297, 298, 299, 301, 302, 306, 309, 310, 311, 312, 313] \n", "\n", "We have 64 linearly independent skewed vector fields obtained from the graphs. \n", "\n", "A maximal subset of linearly independent skewed vector fields is given by graphs [0, 1, 3, 4, 5, 9, 10, 11, 12, 13, 14, 15, 16, 24, 25, 26, 27, 28, 30, 31, 32, 33, 35, 36, 43, 44, 46, 47, 48, 49, 50, 56, 57, 58, 59, 60, 61, 63, 69, 70, 71, 73, 74, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 90, 91, 92, 93, 94, 95, 96, 100, 101]\n", "Q_tetra is computed.\n", "\n", "There exist a solution over skewed vector fields from graphs. It is given by (-8, 0, 0, -48, 0, 0, 0, -16, -48, -48, -16, -48, 0, 12, 0, 0, 0, 0, -24, 0, 0, 24, 0, -24, -16, 0, 0, 0, 0, 0, 0, -32, -12, 0, 48, -48, -32, -32, -48, -48, -96, 0, -48, -48, 0, 0, -48, 0, 24, 0, 0, 0, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) \n", "\n" ] } ], "source": [ "# 18-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, printing of output and rewriting some code to be able to run it parallelized). It is supplementary material \n", "# accompanying proceedings written for the ISQS28 (Integrable Systems and Quantum Symmetries) conference (see also: arxiv link??).\n", "\n", "# Extra packages that are useful\n", "# for Pool, note that the # of processors throughout this is set to 24. Depending on your machine, you want to change this \n", "# amount. A larger problem in probably the amount of memory you have available. You can split up parts of the code, and run\n", "# smaller amounts, while saving necessary vector fields, lists of encodings etc on disc (eg via the pickle library). Once you\n", "# need the saved information, load it back in. On a 32GB RAM machine, the code ran after splitting it into 6 smaller pieces\n", "from multiprocessing import Pool\n", "import itertools\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", "# We start by generating the encodings for the descendants of Gamma_11^2D and Gamma_12^2D\n", "X_graph_encodings = []\n", "for (i2,j1,j2,k) in itertools.product([2,5,8],[1,4,7],[1,4,7],[3,6,9]):\n", " X_graph_encodings.append((0,1,4,7,j1,k,5,8,j2,i2,6,9))\n", "\n", "for (i1,i2,j1,j2,k) in itertools.product([2,5,8],[2,5,8], [1,4,7], [1,4,7], [3,6,9]):\n", " X_graph_encodings.append((0,i1,4,7,j1,k,5,8,j2,i2,6,9))\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 targets by ,. A graph is returned on 3 Levi-Civita vertices, 3 corresponding a^1 \n", "# Casimir vertices, 3 corresponding a^2 Casimir vertices, 1 sink, and with edges (original vertex, target vertex). \n", "def encoding_to_graph(encoding):\n", " targets = [encoding[0:4], encoding[4:8], encoding[8:12]]\n", " edges = sum([[(k+1,v) for v in t] for (k,t) in enumerate(targets)], [])\n", " return FormalityGraph(1, 9, edges)\n", "\n", "# The computation below still goes reasonably fast as there's only 324 graphs. When working with significantly more graphs\n", "# (for instance with gamma_5 rather than gamma_3) you will want to parallelize these computations as well.\n", "X_graphs = [encoding_to_graph(e) for e in X_graph_encodings]\n", "print('We have', len(X_graphs), 'graphs.\\n')\n", "\n", "# Below, we check the how many graphs that were created via the encodings are (non)isomorphic. It does so in the following way:\n", "# for each formality graph in X_graphs, it computes the edges of the canonical form (canonical in the sense that isomorphic\n", "# graphs return the same canonical form) of said formality graph. If a graph with these edges already appears in X_graphs_iso,\n", "# the new (isomorphic) graph is not added to the list. If the graph does not yet appear, it is added to the list. The list is\n", "# indexed by h, NOT by an index from 0,...,40. For this, unmute the line X_graphs_iso=list(X_graphs_iso.values())\n", "#X_graphs_iso={}\n", "#for g in X_graphs:\n", "# h=tuple(g.canonical_form().edges())\n", "# if not h in X_graphs_iso:\n", "# X_graphs_iso[h]=g\n", "#X_graphs_iso=list(X_graphs_iso.values())\n", "#print ('There are', len(X_graphs_iso), 'nonisomorphic graphs.\\n')\n", "\n", "# Create the differential polynomial ring. We are working in 4D, so we have even coordinates x,y,z,w and the corresponding \n", "# odd coordinates xi[0], xi[1], xi[2] and xi[3]. rho is exactly the rho in a 4D Nambu-determinant Poisson bracket, \n", "# (P(f,g)= rho d(f,g,a^1,a^2)/d(x,y,z,w)), and a^1, a^2 are the Casimirs. Finally, max_differential_orders tells the \n", "# programme how many times rho and a^1/a^2 can be differentiated. The maximum is stipulated by the graphs (we cannot have \n", "# double edges). Thus, the maximum in degree of each rho vertex is 4. As we will be looking at [[P,X]], we add an extra +1 to \n", "# the differential orders since taking the Schouten bracket with P introduces an extra derivative.\n", "D4 = DifferentialPolynomialRing(QQ, ('rho','a1', 'a2'), ('x','y','z', 'w'), max_differential_orders=[3+1,3+1,3+1]) \n", "rho, a1, a2 = D4.fibre_variables()\n", "x,y,z,w = D4.base_variables()\n", "even_coords = [x,y,z,w]\n", "\n", "S4. = SuperfunctionAlgebra(D4, D4.base_variables()) \n", "xi = S4.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. Note that compared to 2D and \n", "# 3D, this is now a definition rather than a for loop; this is to be able to parallelize the computations.\n", "epsilon = xi[0]*xi[1]*xi[2]*xi[3]\n", "E = x*xi[0] + y*xi[1] + z*xi[2] + w*xi[3]\n", "def evaluate_graph(g): \n", " result = S4.zero()\n", " for index_choice in itertools.product(itertools.permutations(range(4)), repeat=3):\n", " sign = epsilon[index_choice[0]] * epsilon[index_choice[1]] * epsilon[index_choice[2]]\n", " vertex_content = [E, S4(rho), S4(rho), S4(rho), S4(a1), S4(a1), S4(a1), S4(a2), S4(a2), S4(a2)]\n", " for ((source, target), index) in zip(g.edges(), sum(map(list, index_choice), [])):\n", " vertex_content[target] = vertex_content[target].derivative(even_coords[index])\n", " result += sign * prod(vertex_content)\n", " return result\n", "\n", "# We compute the formulas.\n", "X_vector_fields = []\n", "with Pool(processes=24) as pool:\n", " X_vector_fields = list(pool.imap(evaluate_graph, X_graphs))\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", "# 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", "print('These graphs are:')\n", "for (k,X) in enumerate(X_vector_fields):\n", " if X==0:\n", " print('graph', k+1,)\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], xi[1], xi[2] and xi[3] part of the vector fields, and store them in X_monomial_basis. \n", "X_monomial_basis = [set([]) for i in range(4)]\n", "for i in range(4):\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_count = sum(len(b) for b in X_monomial_basis)\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_monomial_to_index = [{monomial : idx for (idx,monomial) in enumerate(X_monomial_basis[j])} for j in range(4)] \n", "X_evaluation_matrix = matrix(QQ, X_monomial_count, len(X_vector_fields))\n", "for i in range(len(X_vector_fields)):\n", " v = vector(QQ, X_monomial_count)\n", " index_shift = 0\n", " for j in range(4):\n", " f = X_vector_fields[i][j]\n", " for coeff, monomial in zip(f.coefficients(), f.monomials()):\n", " monomial_index = X_monomial_to_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 relations 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')\n", "\n", "\n", "# Unmute the next line if you want to explicitly see the 28 linear relations among the vector fields.\n", "#print('These relations are expressed by \\n', X_evaluation_matrix.right_kernel().basis(), '\\n')\n", "\n", "# We save a list of linearly independent vector fields.\n", "pivots = X_evaluation_matrix.pivots()\n", "print(' A maximal subset of linearly independent graphs is given by:', list(pivots),'\\n')\n", "\n", "# Next, let us create the swapped encodings in order to be able skew-symmetrize the vector fields (skew-symmetrizing is with \n", "# respect to a^1 and a^2).\n", "swapped_X_graph_encodings= []\n", "for i in range(len(X_graph_encodings)):\n", " new_encoding=[]\n", " for j in range(12):\n", " if X_graph_encodings[i][j]==4:\n", " new_encoding.append(7)\n", " elif X_graph_encodings[i][j]==7:\n", " new_encoding.append(4)\n", " elif X_graph_encodings[i][j]==5:\n", " new_encoding.append(8)\n", " elif X_graph_encodings[i][j]==8:\n", " new_encoding.append(5)\n", " elif X_graph_encodings[i][j]==6:\n", " new_encoding.append(9)\n", " elif X_graph_encodings[i][j]==9:\n", " new_encoding.append(6)\n", " else:\n", " new_encoding.append(X_graph_encodings[i][j])\n", " swapped_X_graph_encodings.append(new_encoding)\n", "\n", "# Now, we compute the formulas corresponding to the swapped encodings.\n", "swapped_graphs = [encoding_to_graph(e) for e in swapped_X_graph_encodings]\n", "\n", "# We find the corresponding vector fields.\n", "swapped_vector_fields = []\n", "with Pool(processes=24) as pool:\n", " swapped_vector_fields = list(pool.imap(evaluate_graph, swapped_graphs))\n", "\n", "# We create skew vector fields from the linearly independent non-skewed vector fields. \n", "skew_vector_fields=[]\n", "for i in pivots:\n", " skew_vector_fields.append(1/2*(X_vector_fields[i]-swapped_vector_fields[i]))\n", "\n", "# We also create a new list storing the corresponding encodings (only the original encodings, not also the swapped encodings).\n", "independent_encodings=[]\n", "for i in pivots:\n", " independent_encodings.append(X_graph_encodings[i])\n", "\n", "# While we got rid of linear dependencies in X_vector_fields, after skewing the vector fields, new dependencies show up. We\n", "# again get rid of these in the exact same manner. \n", "X_skew_monomial_basis = [set([]) for i in range(4)]\n", "for i in range(4):\n", " for X in skew_vector_fields:\n", " X_skew_monomial_basis[i] |= set(X[i].monomials())\n", "X_skew_monomial_basis = [list(b) for b in X_skew_monomial_basis]\n", "X_skew_monomial_index = [{m : k for k, m in enumerate(b)} for b in X_skew_monomial_basis]\n", "\n", "X_skew_monomial_count = sum(len(b) for b in X_skew_monomial_basis)\n", "\n", "X_skew_monomial_to_index = [{monomial : idx for (idx,monomial) in enumerate(X_skew_monomial_basis[j])} for j in range(4)] \n", "X_skew_evaluation_matrix = matrix(QQ, X_skew_monomial_count, len(skew_vector_fields), sparse=True)\n", "for i in range(len(skew_vector_fields)):\n", " v = vector(QQ, X_skew_monomial_count, sparse=True)\n", " index_shift = 0\n", " for j in range(4):\n", " f = skew_vector_fields[i][j]\n", " for coeff, monomial in zip(f.coefficients(), f.monomials()):\n", " monomial_index = X_skew_monomial_to_index[j][monomial]\n", " v[index_shift + monomial_index] = coeff\n", " index_shift += len(X_skew_monomial_basis[j])\n", " X_skew_evaluation_matrix.set_column(i, v)\n", "\n", "print('We have', X_skew_evaluation_matrix.rank(), 'linearly independent skewed vector fields obtained from the graphs. \\n')\n", "\n", "# We collect the linearly independent skewed vector fields and the corresponding encodings\n", "pivots_2 = X_skew_evaluation_matrix.pivots()\n", "print('A maximal subset of linearly independent skewed vector fields is given by graphs', list(pivots_2), flush=True)\n", "independent_encodings_skew = [independent_encodings[k] for k in pivots_2]\n", "skew_vector_fields_independent = [skew_vector_fields[k] for k in pivots_2]\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).bracket(a1).bracket(a2)\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 Poisson 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", "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= S4.graph_operation(tetrahedron_oriented_filtered)\n", "Q_tetra= tetrahedron_operation(P, P, P, P) \n", "# !!! You don't want to print Q_tetra, it is very large\n", "# print('The tetrahedral flow in 4D is', Q_tetra, '\\n')\n", "\n", "print('Q_tetra is computed.\\n')\n", "\n", "# Instead of solving the (very large) linear system directly, we use another method in 4D. This method is explained \n", "# in https://arxiv.boxedpaper.com/abs/2112.03897. Essentially, we solve 2 (somewhat) smaller linear systems.\n", "def casimir_flow(f):\n", " return 4*tetrahedron_operation(P,P,P,f)\n", "\n", "a = [S4(a1), S4(a2)]\n", "adot = [casimir_flow(a[0]), casimir_flow(a[1])]\n", "\n", "X_a_multivectors = [[vector_field.bracket(casimir) for vector_field in skew_vector_fields_independent] for casimir in a]\n", "\n", "X_a_basis = [set(flow_of_casimir[()].monomials()) for flow_of_casimir in adot]\n", "for k in range(len(a)):\n", " for X_a_multivector in X_a_multivectors[k]:\n", " X_a_basis[k] |= set(X_a_multivector[()].monomials())\n", "X_a_basis = [list(B) for B in X_a_basis]\n", "\n", "X_a_monomial_index = [{m : k for k, m in enumerate(B)} for B in X_a_basis]\n", "X_a_evaluation_matrix = [matrix(QQ, len(B), len(skew_vector_fields_independent), sparse=True) for B in X_a_basis]\n", "for i in range(len(a)):\n", " for j in range(len(skew_vector_fields_independent)):\n", " v = vector(QQ, len(X_a_basis[i]), sparse=True)\n", " multivector = X_a_multivectors[i][j][()]\n", " for coeff, monomial in zip(multivector.coefficients(), multivector.monomials()):\n", " monomial_index = X_a_monomial_index[i][monomial]\n", " v[monomial_index] = coeff\n", " X_a_evaluation_matrix[i].set_column(j, v)\n", "\n", "adot_vector = [vector(QQ, len(B)) for B in X_a_basis]\n", "for i in range(len(a)):\n", " f = adot[i][()]\n", " for coeff, monomial in zip(f.coefficients(), f.monomials()):\n", " monomial_index = X_a_monomial_index[i][monomial]\n", " adot_vector[i][monomial_index] = coeff\n", "\n", "P0 = (rho*epsilon).bracket(adot[0]).bracket(a2)\n", "P1 = (rho*epsilon).bracket(a1).bracket(adot[1])\n", "Q_remainder = Q_tetra - P0 - P1\n", "P_without_rho = epsilon.bracket(a1).bracket(a2)\n", "rhodot = Q_remainder[0,1] // P_without_rho[0,1]\n", "\n", "X_rho_multivectors = [vector_field.bracket(rho*epsilon) for vector_field in skew_vector_fields_independent]\n", "\n", "X_rho_basis = set(rhodot.monomials())\n", "for X_rho_multivector in X_rho_multivectors:\n", " X_rho_basis |= set(X_rho_multivector[0,1,2,3].monomials())\n", "X_rho_basis = list(X_rho_basis)\n", "\n", "X_rho_monomial_index = {m : k for k, m in enumerate(X_rho_basis)}\n", "X_rho_evaluation_matrix = matrix(QQ, len(X_rho_basis), len(skew_vector_fields_independent), sparse=True)\n", "for j in range(len(skew_vector_fields_independent)):\n", " f = X_rho_multivectors[j][0,1,2,3]\n", " v = vector(QQ, len(X_rho_basis), sparse=True)\n", " for coeff, monomial in zip(f.coefficients(), f.monomials()):\n", " monomial_index = X_rho_monomial_index[monomial]\n", " v[monomial_index] = coeff\n", " X_rho_evaluation_matrix.set_column(j, v)\n", "\n", "rhodot_vector = vector(QQ, len(X_rho_basis), sparse=True)\n", "for coeff, monomial in zip(rhodot.coefficients(), rhodot.monomials()):\n", " monomial_index = X_rho_monomial_index[monomial]\n", " rhodot_vector[monomial_index] = coeff\n", "\n", "big_matrix = X_a_evaluation_matrix[0].stack(X_a_evaluation_matrix[1]).stack(X_rho_evaluation_matrix)\n", "big_vector = vector(list(-adot_vector[0]) + list(-adot_vector[1]) + list(-rhodot_vector))\n", "X_solution_vector = big_matrix.solve_right(big_vector)\n", "print('Proposition 14: There exist a solution over skewed vector fields from graphs. It is given by', X_solution_vector, '\\n')\n", "\n", "# Now, we create the evaluation matrix of the skewed independent vector fields; we need this in order to find a basis\n", "# for the cocycle space. \n", "X_skew_ind_monomial_basis = [set([]) for i in range(4)]\n", "for i in range(4):\n", " for vector_field in skew_vector_fields_independent:\n", " X_skew_ind_monomial_basis[i] |= set(vector_field[i].monomials())\n", "X_skew_ind_monomial_basis = [list(b) for b in X_skew_ind_monomial_basis]\n", "X_skew_ind_monomial_index = [{m : k for k, m in enumerate(b)} for b in X_skew_ind_monomial_basis]\n", "\n", "X_skew_ind_monomial_count = sum(len(b) for b in X_skew_ind_monomial_basis)\n", "\n", "X_skew_ind_evaluation_matrix = matrix(QQ, X_skew_ind_monomial_count, len(skew_vector_fields_independent), sparse=True)\n", "for i in range(len(skew_vector_fields_independent)):\n", " v = vector(QQ, X_skew_ind_monomial_count, sparse=True)\n", " index_shift = 0\n", " for j in range(4):\n", " vector_field = skew_vector_fields_independent[i][j]\n", " for coeff, monomial in zip(vector_field.coefficients(), vector_field.monomials()):\n", " monomial_index = X_skew_ind_monomial_index[j][monomial]\n", " v[index_shift + monomial_index] = coeff\n", " index_shift += len(X_skew_ind_monomial_basis[j])\n", " X_skew_ind_evaluation_matrix.set_column(i, v)\n", "\n", "# We compute a basis for the cocycle space.\n", "X_cocycle_space = big_matrix.right_kernel().quotient(X_skew_ind_evaluation_matrix.right_kernel())\n", "X_cocycles=[X_cocycle_space.lift(v) for v in X_cocycle_space.basis()]\n", "print('Proposition 15: The cocycle space has dimension',X_cocycle_space.dimension(), '. The shifts are given by', X_cocycles, '\\n')\n", "\n", "# We compute the vector fields corresponding to the computed basis elements of the cocycle space.\n", "shifts_formulas = [sum(X_cocycle[j]*skew_vector_fields_independent[j] for j in range(len(skew_vector_fields_independent))) for X_cocycle in X_cocycles]\n", "\n", "# Now, we start the procedure of checking whether the shifts appearing in the cocycle space are Hamiltonian. \n", "hamiltonian_encodings= [(0, 1, 2, 4, 0, 1, 3, 5), (0, 1, 2, 4, 1, 2, 3, 5), (0, 1, 2, 4, 1, 3, 4, 5), (0, 2, 3, 4, 1, 2, 3, 5), (0, 2, 3, 4, 1, 3, 4, 5), (0, 2, 4, 5, 1, 3, 4, 5), (0, 1, 2, 4, 0, 2, 3, 5), (0, 1, 2, 4, 0, 3, 4, 5), (0, 1, 2, 4, 2, 3, 4, 5), (0, 2, 3, 4, 0, 2, 3, 5), (0, 2, 4, 5, 0, 2, 3, 5), (0, 2, 3, 4, 0, 3, 4, 5), (0, 2, 4, 5, 0, 3, 4, 5), (0, 2, 3, 4, 2, 3, 4, 5), (0, 2, 4, 5, 2, 3, 4, 5), (1, 2, 3, 4, 0, 2, 3, 5), (1, 2, 4, 5, 0, 3, 4, 5), (1, 2, 3, 4, 0, 3, 4, 5), (1, 2, 3, 4, 2, 3, 4, 5), (1, 2, 4, 5, 2, 3, 4, 5), (2, 3, 4, 5, 2, 3, 4, 5)]\n", "\n", "# We find the graphs corresponding to the encodings. \n", "def hamiltonian_encoding_to_graph(encoding):\n", " targets = [encoding[0:4], encoding[4:8]]\n", " edges = sum([[(k+1,v) for v in t] for (k,t) in enumerate(targets)], [])\n", " return FormalityGraph(0, 6, edges)\n", "\n", "hamiltonian_graphs = [encoding_to_graph(e) for e in hamiltonian_encodings]\n", "\n", "# Since our vector fields are skew with respect to the Casimirs a1 and a2, we require that the Hamiltonians are symmetric with\n", "# respect to a1 and a2; in this way, as the Poisson bivector P itself is skew (wrt a1 and a2), we guarantee that the \n", "# Hamiltonian vector fields are also skew symmetric wrt a1 and a2. \n", "\n", "hamiltonian_encodings_swapped= [(0, 1, 4, 2, 0, 1, 5, 3), (0, 1, 4, 2, 1, 4, 5, 3), (0, 1, 4, 2, 1, 5, 2, 3), (0, 4, 5, 2, 1, 4, 5, 3), (0, 4, 5, 2, 1, 5, 2, 3), (0, 4, 2, 3, 1, 5, 2, 3), (0, 1, 4, 2, 0, 4, 5, 3), (0, 1, 4, 2, 0, 5, 2, 3), (0, 1, 4, 2, 4, 5, 2, 3), (0, 4, 5, 2, 0, 4, 5, 3), (0, 4, 2, 3, 0, 4, 5, 3), (0, 4, 5, 2, 0, 5, 2, 3), (0, 4, 2, 3, 0, 5, 2, 3), (0, 4, 5, 2, 4, 5, 2, 3), (0, 4, 2, 3, 4, 5, 2, 3), (1, 4, 5, 2, 0, 4, 5, 3), (1, 4, 2, 3, 0, 5, 2, 3), (1, 4, 5, 2, 0, 5, 2, 3), (1, 4, 5, 2, 4, 5, 2, 3), (1, 4, 2, 3, 4, 5, 2, 3), (4, 5, 2, 3, 4, 5, 2, 3)]\n", "\n", "hamiltonian_graphs_swapped = [encoding_to_graph(e) for e in hamiltonian_encodings_swapped]\n", "\n", "# We create a new function to evaluate the Hamiltonian graphs to Hamiltonian formulas. \n", "def evaluate_ham(g):\n", " E = x*xi[0] + y*xi[1] + z*xi[2] + w*xi[3] \n", " result = S4.zero()\n", " for index_choice in itertools.product(itertools.permutations(range(4)), repeat=2):\n", " sign = epsilon[index_choice[0]] * epsilon[index_choice[1]]\n", " for ((source, target), index) in zip(g.edges(), sum(map(list, index_choice), [])):\n", " vertex_content[target] = vertex_content[target].derivative(even_coords[index])\n", " result += sign * prod(vertex_content)\n", " return result\n", "\n", "# We compute the Hamiltonian formulas.\n", "hamiltonian_formulas = []\n", "with Pool(processes=24) as pool:\n", " hamiltonian_formulas = list(pool.imap(evaluate_ham, hamiltonian_graphs))\n", "\n", "# We compute the Hamiltonian formulas with a1 and a2 swapped.\n", "hamiltonian_formulas_swapped = []\n", "with Pool(processes=24) as pool:\n", " hamiltonian_formulas_swapped = list(pool.imap(evaluate_ham, hamiltonian_graphs_swapped))\n", "\n", "# We obtain the symmetrized Hamiltonian formulas.\n", "hamiltonian_formulas_symm=[]\n", "for i in range(len(hamiltonian_formulas)):\n", " hamiltonian_formulas_symm.append(1/2*(hamiltonian_formulas[i]+hamiltonian_formulas_interchanged[i]))\n", "\n", "# We check if we have symmetrized Hamiltonian formulas that evaluate to 0. \n", "print('We have', hamiltonian_formulas_symm.count(0), 'Hamiltonian graph that evaluates to 0.')\n", "print('The Hamiltonian graph evaluated to 0 is:\\n')\n", "for (k,ham) in enumerate(hamiltonian_formulas_symm):\n", " if ham==0:\n", " print('Hamiltonian', k+1,'\\n')\n", "\n", "#From the symmetrized Hamiltonians we compute the skew-symmetrized Hamiltonian vector fields.\n", "hamiltonian_vector_fields_skew=[]\n", "for formula in hamiltonian_formulas_symm:\n", " hamiltonian_vector_fields_skew.append(P.bracket(formula))\n", "\n", "# We create the Hamiltonian monomial basis to be able to express the basis of the cocycle space in terms of the monomials\n", "# appearing in the skewed Hamiltonian vector fields. \n", "hamiltonian_monomial_basis = {}\n", "for j in range(len(shifts_formulas)):\n", " for i in range(4): \n", " hamiltonian_monomial_basis[i] = set(shifts_formulas[j][i].monomials())\n", " for formula in hamiltonian_vector_fields:\n", " hamiltonian_monomial_basis[i] |= set(formula[i].monomials())\n", "\n", "hamiltonian_monomial_basis = {idx: list(b) for idx, b in hamiltonian_monomial_basis.items()}\n", "hamiltonian_monomial_index = {idx: {m : k for k, m in enumerate(b)} for idx, b in hamiltonian_monomial_basis.items()}\n", "hamiltonian_monomial_count = sum(len(b) for b in hamiltonian_monomial_basis.values())\n", "\n", "def shift_formula_to_vector(shift_formula):\n", " shift_vector = vector(QQ,hamiltonian_monomial_count, sparse=True)\n", " index_offset = 0\n", " for i in hamiltonian_monomial_basis:\n", " for coeff, monomial in shift_formula[i]:\n", " monomial_index = hamiltonian_monomial_index[i][monomial]\n", " shift_vector[monomial_index + index_offset] = coeff\n", " index_offset += len(hamiltonian_monomial_basis[i])\n", " return shift_vector\n", "\n", "shifts_vectors = [shift_formula_to_vector(shift_formula) for shift_formula in shifts_formulas]\n", "\n", "# Let us collect which monomials appear in which skewed Hamiltonians vector fields, and store this in an evaluation matrix. \n", "hamiltonian_evaluation_matrix = matrix(QQ,hamiltonian_monomial_count,len(hamiltonian_vector_fields),sparse=True)\n", "for k in range(len(hamiltonian_vector_fields)):\n", " vector_field = hamiltonian_vector_fields[k] \n", " v = vector(QQ, hamiltonian_monomial_count, sparse=True) \n", " index_shift = 0\n", " for i in hamiltonian_monomial_basis: \n", " for coeff, monomial in vector_field[i]:\n", " monomial_index = hamiltonian_monomial_index[i][monomial]\n", " v[monomial_index +index_shift] = coeff\n", " index_shift += len(hamiltonian_monomial_basis[i])\n", " hamiltonian_evaluation_matrix.set_column(k, v)\n", "\n", "# Let us check the nullity of the skewed Hamiltonian vector fields among themselves.\n", "nullity=hamiltonian_evaluation_matrix.right_nullity()\n", "kernel_basis= hamiltonian_evaluation_matrix.right_kernel().basis()\n", "print('The nullity among the skewed Hamiltonian vector fields is ', nullity, '. The dimension of the skewed Hamiltonian vector fields is', hamiltonian_evaluation_matrix.dimension(), '\\n')\n", "# Note that the below is on the level of vector fields, not formulas. Unfortunately, the evaluation_matrix procedure only\n", "# works from vector fields onward, and not for 0-vectors (formulas). You can explictly check that the linear relations ARE \n", "# already preserved on the level of formulas by simply evaluating hamiltonian_formulas[x]==hamiltonian_formulas[y] etc.\n", "print('Explicitly, these are the linear combinations that evaluate to 0:', kernel_basis, '\\n')\n", "\n", "# We can now solve the shifts! Note that if (at least one) shift is NOT Hamiltonian, the code will break here as no solution \n", "# can be found.\n", "shifts_solutions = [hamiltonian_evaluation_matrix.solve_right(shift_vector) for shift_vector in shifts_vectors]\n", "\n", "\n", "# Write the solutions above from combinations of graphs to their vector field forms to check validity.\n", "solution_formulas = [sum(shift_solution[i]*hamiltonian_vector_fields[i] for i in range(len(shift_solution))) for shift_solution in shifts_solutions]\n", "\n", "\n", "# We check that the solutions we found in terms of the skewed Hamiltonian vector fields are truly correct; we check \n", "# explicitly whether they agree on the previously computed formulass for the basis elements of the cocycle space, and\n", "# that they ARE elements of the cocycle space (i.e. they evaluate to 0 under [[P, ]]). \n", "if any(P.bracket(solution_formula) != 0 for solution_formula in solution_formulas) or solution_formulas != shifts_formulas:\n", " print('There is an error in computing the shifts.')\n", "else:\n", " print('Theorem 17: The shifts in the cocycle space are Hamiltonian.')\n", "\n", "for k, shift_solution in enumerate(shifts_solutions):\n", " print('The shift #', k+1, 'is the following linear combination of Hamiltonian vector fields:', shift_solution)" ] }, { "cell_type": "code", "execution_count": null, "id": "24e9983c-7012-4d46-b56d-dfdfba7df7c0", "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 }