""" Copyright (c) 2011, E. R. Vaughan. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import sys import fractions import getopt import json import itertools try: from sage.all import * using_sage = True except ImportError: using_sage = False should_print_help = False try: opts, args = getopt.gnu_getopt(sys.argv[1:], "", ["help", "admissible-graphs", "flags", "r-matrices", "qdash-matrices", "pair-densities", "q-matrices", "verify-bound", "sharp-graphs", "flag-algebra-coefficients"]) except getopt.GetoptError: should_print_help = True opts, args = ((), ()) if len(args) != 1: should_print_help = True action = "" for o, a in opts: if o == "--help": should_print_help = True elif o == "--admissible-graphs": action = "print admissible graphs" elif o == "--flags": action = "print flags" elif o == "--r-matrices": action = "print r_matrices" elif o == "--qdash-matrices": action = "print qdash_matrices" elif o == "--pair-densities": action = "print pair densities" elif o == "--q-matrices": action = "print q_matrices" elif o == "--verify-bound": action = "verify bound" elif o == "--sharp-graphs": action = "print sharp graphs" elif o == "--flag-algebra-coefficients": action = "print flag algebra coefficients" if should_print_help: sys.stdout.write("""Usage: inspect_certificate.py CERTIFICATE OPTIONS Possible options: --help Display this message. --admissible-graphs Display the admissible graphs. --flags Display the types and flags. --r-matrices Display the R matrices. --qdash-matrices Display the Q' matrices. --q-matrices Compute and display the Q matrices. --pair-densities Compute and display the flag pair densities. --verify-bound Verify the bound. --sharp-graphs Display the admissible graphs that are sharp. --flag-algebra-coefficients Display each admissible graph's flag algebra coefficient. """ ) sys.exit(0) certificate_filename = args[0] try: if certificate_filename[-3:] == ".gz": import gzip certf = gzip.open(certificate_filename) elif certificate_filename[-4:] == ".bz2": import bz2 certf = bz2.BZ2File(certificate_filename) else: certf = open(certificate_filename) except IOError: sys.stdout.write("Could not open certificate.\n") sys.exit(1) certificate = json.load(certf) sys.stdout.write("Problem is \"%s\".\n" % certificate["description"]) sys.stdout.write("Claimed bound is %s.\n" % certificate["bound"]) minimize = "minimize" in certificate["description"] if using_sage: x = polygen(QQ) else: x = None if "field" in certificate.keys(): if certificate["field"] != "RationalField()": sys.stdout.write("Field used is \"%s\".\n" % certificate["field"]) if not using_sage: sys.stdout.write("This script must be run using Sage's python, as it does not use the rational field.\n") sys.exit(1) try: K = sage_eval(certificate["field"], locals={'x': x}) except AttributeError: K = RationalField() x = K.gen() if action == "": sys.exit(0) if "3-graph" in certificate["description"]: edge_size = 3 elif "2-graph" in certificate["description"]: edge_size = 2 else: sys.stdout.write("Unsupported graph kind.\n") sys.exit(1) oriented = "oriented" in certificate["description"] def induced_subgraph (g, S): global oriented good_edges = tuple(e for e in g[1] if all(x in S for x in e)) p = [0 for i in range(g[0] + 1)] for i in range(len(S)): p[S[i]] = i + 1 if oriented: edges = tuple(sorted([tuple([p[x] for x in e]) for e in good_edges])) else: edges = tuple(sorted([tuple(sorted([p[x] for x in e])) for e in good_edges])) return (len(S), edges) def minimal_typed_isomorph (g, t): global oriented s = t[0] n = g[0] min_edges = g[1] for p in itertools.permutations(range(s + 1, n + 1), n - s): pt = tuple(range(1, s + 1)) + p if oriented: edges = tuple(sorted([tuple([pt[x - 1] for x in e]) for e in g[1]])) else: edges = tuple(sorted([tuple(sorted([pt[x - 1] for x in e])) for e in g[1]])) if edges < min_edges: min_edges = edges return (n, min_edges) def graph_to_string (g): s = "%d:" % g[0] for e in g[1]: for x in e: s += "%d" % x return s def string_to_graph (s): global edge_size n = int(s[0]) if n < 0 or n > 9 or s[1] != ":": return None e = len(s) - 2 if e == 0: return (n, ()) if e % edge_size != 0: return None m = e / edge_size edges = [] for i in range(0, m): # N.B. +2 because of n: header edge = [int(s[(edge_size * i) + 2 + j]) for j in range(edge_size)] es = set(edge) if len(es) != edge_size or not es <= set(range(1, n + 1)): return None edges.append(tuple(edge)) return (n, tuple(edges)) admissible_graphs = [string_to_graph(s) for s in certificate["admissible_graphs"]] nag = len(admissible_graphs) types = [string_to_graph(s) for s in certificate["types"]] flags = [[string_to_graph(s) for s in f] for f in certificate["flags"]] if action == "print admissible graphs": sys.stdout.write("There are %d admissible graphs:\n" % nag) for i in range(nag): sys.stdout.write("%d. %s\n" % (i + 1, graph_to_string(admissible_graphs[i]))) sys.exit(0) if action == "print flags": for t in range(len(types)): nf = len(flags[t]) sys.stdout.write("Type %d (%s) has %d flags:\n" % (t + 1, graph_to_string(types[t]), nf)) for i in range(nf): sys.stdout.write(" %d. %s\n" % (i + 1, graph_to_string(flags[t][i]))) sys.exit(0) def to_string (a): if a == None: return "Not used." if isinstance(a, list): return map(to_string, a) s = str(a) try: n = int(s) return n except ValueError: return s if action == "print r_matrices": for t in range(len(types)): sys.stdout.write("R matrix for type %d (%s):\n" % (t + 1, graph_to_string(types[t]))) sys.stdout.write("%s\n" % to_string(certificate["r_matrices"][t])) sys.exit(0) if action == "print qdash_matrices": for t in range(len(types)): sys.stdout.write("Q' matrix for type %d (%s):\n" % (t + 1, graph_to_string(types[t]))) sys.stdout.write("%s\n" % to_string(certificate["qdash_matrices"][t])) sys.exit(0) n = certificate["order_of_admissible_graphs"] vertices = tuple(range(1, n + 1)) sys.stdout.write("Computing Q matrices...\n") Qs = [] for t in range(len(types)): if certificate["qdash_matrices"][t] == None: Qs.append(None) continue if using_sage: QD = [[sage_eval(str(s), locals={'x': x}) for s in row] for row in certificate["qdash_matrices"][t]] else: QD = [[fractions.Fraction(s) for s in row] for row in certificate["qdash_matrices"][t]] if certificate["r_matrices"][t] == None: Qs.append(QD) continue if using_sage: R = [[sage_eval(str(s), locals={'x': x}) for s in row] for row in certificate["r_matrices"][t]] else: R = [[fractions.Fraction(s) for s in row] for row in certificate["r_matrices"][t]] nq = len(QD) nf = len(flags[t]) Q = [[0 for j in range(i, nf)] for i in range(nf)] for l in range(nq): for k in range(l, nq): qlk = QD[l][k - l] if qlk != 0: for i in range(nf): for j in range(i, nf): Q[i][j - i] += R[i][l] * R[j][k] * qlk if k != l: Q[i][j - i] += R[i][k] * R[j][l] * qlk Qs.append(Q) if action == "print q_matrices": for t in range(len(types)): sys.stdout.write("Q matrix for type %d (%s):\n" % (t + 1, graph_to_string(types[t]))) sys.stdout.write("%s\n" % to_string(Qs[t])) sys.exit(0) sys.stdout.write("Computing pair densities...\n") pair_densities = {} for t in range(len(types)): s = types[t][0] m = flags[t][0][0] # assumes all flags are same order nf = len(flags[t]) for g in admissible_graphs: pairs_found = [[0 for j in range(k, nf)] for k in range(nf)] total = 0 for p in itertools.permutations(vertices): if p[s] > p[m]: continue is_good_permutation = True for c in range(s, n): if c == s or c == m: continue if p[c - 1] > p[c]: is_good_permutation = False break if not is_good_permutation: continue total += 1 it = induced_subgraph(g, p[:s]) if it != types[t]: continue def induced_flag(g, tv, ov): type_vertices = list(tv) other_vertices = list(ov) edges = [] for e in g[1]: if any(not (x in tv or x in ov) for x in e): continue edge = [] for x in e: if x in tv: edge.append(1 + type_vertices.index(x)) else: edge.append(len(tv) + 1 + other_vertices.index(x)) edges.append(tuple(edge)) return (len(tv + ov), tuple(edges)) f1 = minimal_typed_isomorph(induced_flag(g, p[:s], p[s:m]), types[t]) f2 = minimal_typed_isomorph(induced_flag(g, p[:s], p[m:2*m-s]), types[t]) if1 = flags[t].index(f1) if2 = flags[t].index(f2) if if1 <= if2: pairs_found[if1][if2 - if1] += 1 else: pairs_found[if2][if1 - if2] += 1 for k in range(nf): for j in range(k, nf): pf = pairs_found[k][j - k] if pf > 0: if j == k: pf *= 2 if using_sage: pair_densities[(t, admissible_graphs.index(g), k, j)] = Integer(pf) / (total * 2) else: pair_densities[(t, admissible_graphs.index(g), k, j)] = fractions.Fraction(pf, total * 2) if action == "print pair densities": for i in range(nag): sys.stdout.write("Pair densities for admissible graph %d (%s):\n" % (i + 1, graph_to_string(admissible_graphs[i]))) for t in range(len(types)): sys.stdout.write(" Non-zero densities for type %d (%s):\n" % (t + 1, graph_to_string(types[t]))) for key, d in pair_densities.iteritems(): if key[0] != t or key[1] != i: continue sys.stdout.write(" Flags %d and %d (%s and %s): %s\n" % (key[2] + 1, key[3] + 1, graph_to_string(flags[t][key[2]]), graph_to_string(flags[t][key[3]]), d)) sys.exit(0) sys.stdout.write("Computing bound...\n") if using_sage: bounds = [sage_eval(str(s), locals={'x': x}) for s in certificate["admissible_graph_densities"]] else: bounds = [fractions.Fraction(s) for s in certificate["admissible_graph_densities"]] for key in pair_densities.keys(): t, i, j, k = key if Qs[t] == None: # check that type is used continue d = pair_densities[key] v = Qs[t][j][k-j] if minimize: v *= -1 if j == k: bounds[i] += d * v else: bounds[i] += d * v * 2 if minimize: bound = min(bounds) else: # sage seems to be unable to return max of a list containing 0! nonzero_bounds = [b for b in bounds if b != 0] if len(nonzero_bounds) == 0: bound = 0 else: bound = max(nonzero_bounds) sys.stdout.write("Bound is %s.\n" % bound) if action == "print sharp graphs": sys.stdout.write("Sharp graphs are:\n") for i in range(nag): if bounds[i] == bound: sys.stdout.write("%d. %s\n" % (i + 1, graph_to_string(admissible_graphs[i]))) sys.exit(0) if action == "print flag algebra coefficients": sys.stdout.write("There are %d admissible graphs:\n" % nag) for i in range(nag): sys.stdout.write("%d. (%s) : %s\n" % (i + 1, graph_to_string(admissible_graphs[i]), bounds[i])) sys.exit(0)