Source code for boolean_cayley_graphs.classification_database_psycopg2

r"""
Interface to a classification database using psycopg2
=====================================================

The ``classification_database_psycopg2`` module defines interfaces
to manipulate a PostgreSQL database of Cayley graph classifications.

AUTHORS:

- Paul Leopardi (2017-10-28)

"""
#*****************************************************************************
#       Copyright (C) 2017-2018 Paul Leopardi paul.leopardi@gmail.com
#
#  Distributed under the terms of the GNU General Public License (GPL)
#  as published by the Free Software Foundation; either version 2 of
#  the License, or (at your option) any later version.
#                  http://www.gnu.org/licenses/
#*****************************************************************************

from builtins import object
import binascii
import hashlib
import psycopg2
import psycopg2.extras

from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, quote_ident

from sage.matrix.constructor import matrix

from boolean_cayley_graphs.bent_function import BentFunction
from boolean_cayley_graphs.bent_function_cayley_graph_classification import BentFunctionCayleyGraphClassification, default_algorithm
from boolean_cayley_graphs.weight_class import weight_class


encoding = "UTF-8"


[docs]class Psycopg2Default(object): """ A default psycopg2 value. NOTE: :: From: Daniele Varrazzo Date: `24 August 2014, 15:50:38` Source: `https://postgrespro.com/list/thread-id/1544890` See: `http://initd.org/psycopg/docs/advanced.html#adapting-new-python-types-to-sql-syntax` TESTS: :: sage: from boolean_cayley_graphs.classification_database_psycopg2 import * sage: PSYCOPG2_DEFAULT = Psycopg2Default() """ def __conform__(self, proto): """ See: http://initd.org/psycopg/docs/advanced.html TESTS: :: sage: from boolean_cayley_graphs.classification_database_psycopg2 import * sage: PSYCOPG2_DEFAULT = Psycopg2Default() sage: type(PSYCOPG2_DEFAULT.__conform__(psycopg2.extensions.ISQLQuote)) <class 'boolean_cayley_graphs.classification_database_psycopg2.Psycopg2Default'> """ if proto is psycopg2.extensions.ISQLQuote: return self
[docs] def getquoted(self): """ See: http://initd.org/psycopg/docs/advanced.html TESTS: :: sage: from boolean_cayley_graphs.classification_database_psycopg2 import * sage: PSYCOPG2_DEFAULT = Psycopg2Default() sage: PSYCOPG2_DEFAULT.getquoted() 'DEFAULT' """ return 'DEFAULT'
PSYCOPG2_DEFAULT = Psycopg2Default()
[docs]def create_database( dbname, user=None, password=None, host=None): """ Create a database. INPUT: - ``dbname`` -- string. The name of the database to be created. - ``user`` -- string, optional. A Postgres user with appropriate permissions on the host. - ``password`` -- string, optional. The Postgres password of ``user``. - ``host`` -- string, optional. The machine running the Postgres database management system that hosts the database. OUTPUT: a database connection object. EXAMPLE: Create a database using a standardized name, then drop the database. :: sage: from boolean_cayley_graphs.classification_database_psycopg2 import * sage: from psycopg2 import ProgrammingError sage: dbname = 'doctest_create_database_dbname' sage: drop_database(dbname) sage: conn = create_database(dbname) sage: type(conn) <class 'psycopg2.extensions.connection'> sage: conn.close() sage: drop_database(dbname) """ conn = psycopg2.connect( dbname="postgres", user=user, password=password, host=host) conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) curs = conn.cursor() curs.execute( "CREATE DATABASE %s" % quote_ident(dbname, curs)) conn.commit() return conn
[docs]def connect_to_database( dbname, user=None, password=None, host=None): """ Connect to an existing database. INPUT: - ``dbname`` -- string. The name of the existing database. - ``user`` -- string, optional. A Postgres user with appropriate permissions on the host. - ``password`` -- string, optional. The Postgres password of ``user``. - ``host`` -- string, optional. The machine running the Postgres database management system that hosts the database. OUTPUT: a database connection object. EXAMPLE: Create a database using a standardized name, connect to it, then drop the database. :: sage: from boolean_cayley_graphs.classification_database_psycopg2 import * sage: from psycopg2 import ProgrammingError sage: dbname = 'doctest_connect_to_database_dbname' sage: drop_database(dbname) sage: conn = create_database(dbname) sage: conn.close() sage: con2 = connect_to_database(dbname) sage: type(con2) <class 'psycopg2.extensions.connection'> sage: con2.close() sage: drop_database(dbname) """ conn = psycopg2.connect( dbname=dbname, user=user, password=password, host=host) return conn
[docs]def drop_database( dbname, user=None, password=None, host=None): """ Drop a database, if it exists. INPUT: - ``dbname`` -- string. The name of the existing database. - ``user`` -- string, optional. A Postgres user with appropriate permissions on the host. - ``password`` -- string, optional. The Postgres password of ``user``. - ``host`` -- string, optional. The machine running the Postgres database management system that hosts the database. OUTPUT: None. EXAMPLE: Create a database using a standardized name, then drop the database. :: sage: from boolean_cayley_graphs.classification_database_psycopg2 import * sage: dbname = 'doctest_drop_database_dbname' sage: drop_database(dbname) sage: conn = create_database(dbname) sage: type(conn) <class 'psycopg2.extensions.connection'> sage: conn.close() sage: drop_database(dbname) sage: drop_database(dbname) """ conn = psycopg2.connect( dbname="postgres", user=user, password=password, host=host) conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) with conn.cursor() as curs: try: curs.execute( "DROP DATABASE %s" % quote_ident(dbname, curs)) except psycopg2.ProgrammingError: pass conn.close()
[docs]def create_classification_tables( dbname, user=None, password=None, host=None): """ Create the tables used for a database of Cayley graph classifications. INPUT: - ``dbname`` -- string. The name of an existing database. - ``user`` -- string, optional. A Postgres user with appropriate permissions on the host. - ``password`` -- string, optional. The Postgres password of ``user``. - ``host`` -- string, optional. The machine running the Postgres database management system that hosts the database. OUTPUT: a database connection object. EXAMPLE: Create a database, with tables, using a standardized name, list the table names, then drop the database. :: sage: from boolean_cayley_graphs.classification_database_psycopg2 import * sage: dbname = 'doctest_create_classification_tables_dbname' sage: drop_database(dbname) sage: conn = create_database(dbname) sage: conn.close() sage: conn = create_classification_tables(dbname) sage: curs = conn.cursor() sage: curs.execute( ....: "SELECT table_name " + ....: "FROM information_schema.tables " + ....: "WHERE table_schema='public' AND table_type='BASE TABLE' " + ....: "ORDER BY table_name") sage: for row in curs: ....: for x in row: ....: print(x) ....: bent_function cayley_graph graph matrices sage: conn.close() sage: drop_database(dbname) """ conn = connect_to_database( dbname, user=user, password=password, host=host) curs = conn.cursor() curs.execute(""" CREATE TABLE bent_function( nvariables INTEGER, bent_function BYTEA, name TEXT UNIQUE, PRIMARY KEY(nvariables, bent_function))""") curs.execute(""" CREATE TABLE graph( graph_id SERIAL PRIMARY KEY, canonical_label_hash BYTEA UNIQUE, canonical_label TEXT)""") curs.execute(""" CREATE TABLE cayley_graph( nvariables INTEGER, bent_function BYTEA, cayley_graph_index INTEGER, graph_id INTEGER, FOREIGN KEY(nvariables, bent_function) REFERENCES bent_function(nvariables, bent_function), FOREIGN KEY(graph_id) REFERENCES graph(graph_id), PRIMARY KEY(bent_function, cayley_graph_index))""") curs.execute(""" CREATE TABLE matrices( nvariables INTEGER, bent_function BYTEA, b INTEGER, c INTEGER, bent_cayley_graph_index INTEGER, dual_cayley_graph_index INTEGER, weight_class INTEGER, FOREIGN KEY(nvariables, bent_function) REFERENCES bent_function(nvariables, bent_function), FOREIGN KEY(bent_function, bent_cayley_graph_index) REFERENCES cayley_graph(bent_function, cayley_graph_index), FOREIGN KEY(bent_function, dual_cayley_graph_index) REFERENCES cayley_graph(bent_function, cayley_graph_index), PRIMARY KEY(bent_function, b, c))""") conn.commit() return conn
[docs]def canonical_label_hash(canonical_label): r""" Hash function for Graph canonical labels. INPUT: - ``canonical_label`` -- string. A graph6_string encoding a Graph canonical label. OUTPUT: The sha256 hash of ``canonical_label`` as a ``psycopg2.Binary`` buffer. TESTS: :: sage: from boolean_cayley_graphs.classification_database_psycopg2 import * sage: clh = canonical_label_hash("Arbitrary string") sage: type(clh) <class 'psycopg2.extensions.Binary'> """ # return psycopg2.Binary(buffer(hashlib.sha256(canonical_label).digest())) encoded_canonical_label = canonical_label.encode(encoding) return psycopg2.Binary(hashlib.sha256(encoded_canonical_label).digest())
[docs]def insert_classification( conn, bfcgc, name=None): """ Insert a Cayley graph classification into a database. INPUT: - ``conn`` -- a connection object for the database. - ``bfcgc`` -- a Cayley graph classification. - ``name`` -- string (default: `None`). The name of the bent function. OUTPUT: None. A cursor object corresponding to state of the database after the classification is inserted. EXAMPLE: Create a database, with tables, using a standardized name, insert a classification, retrieve it by bent function, then drop the database. :: sage: from boolean_cayley_graphs.classification_database_psycopg2 import * sage: from boolean_cayley_graphs.bent_function import BentFunction sage: from boolean_cayley_graphs.bent_function_cayley_graph_classification import BentFunctionCayleyGraphClassification sage: bentf = BentFunction([0,0,0,1]) sage: bfcgc = BentFunctionCayleyGraphClassification.from_function(bentf) sage: bfcgc.algebraic_normal_form x0*x1 sage: dbname = 'doctest_insert_classification_dbname' sage: drop_database(dbname) sage: conn = create_database(dbname) sage: conn.close() sage: conn = create_classification_tables(dbname) sage: insert_classification(conn, bfcgc, 'bentf') sage: result = select_classification_where_bent_function(conn, bentf) sage: result.algebraic_normal_form x0*x1 sage: conn.close() sage: drop_database(dbname) """ bentf = BentFunction(bfcgc.algebraic_normal_form) dim = bentf.nvariables() nvar = int(dim) bftt = psycopg2.Binary(bentf.tt_buffer()) cgcl = bfcgc.cayley_graph_class_list bcim = bfcgc.bent_cayley_graph_index_matrix dcim = bfcgc.dual_cayley_graph_index_matrix wcm = bfcgc.weight_class_matrix curs = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) curs.execute(""" INSERT INTO bent_function VALUES (%s,%s,%s)""", (nvar, bftt, name)) for n in range(len(cgcl)): cgc = cgcl[n] cgc_hash = canonical_label_hash(cgc) curs.execute(""" INSERT INTO graph VALUES (%s,%s,%s) ON CONFLICT DO NOTHING""", (PSYCOPG2_DEFAULT, cgc_hash, cgc)) curs.execute(""" SELECT graph_id FROM graph WHERE canonical_label_hash = (%s)""", (cgc_hash,)) row = curs.fetchone() graph_id = row["graph_id"] curs.execute(""" INSERT INTO cayley_graph VALUES (%s,%s,%s,%s)""", (nvar, bftt, n, graph_id)) v = 2 ** dim for b in range(v): matrices_b_rows = ( ( nvar, bftt, b, c, int(bcim[c,b]), int(dcim[c,b]), int(wcm[c,b])) for c in range(v)) curs.executemany(""" INSERT INTO matrices VALUES (%s,%s,%s,%s,%s,%s,%s)""", matrices_b_rows) conn.commit()
[docs]def select_classification_where_bent_function( conn, bentf): """ Retrieve a Cayley graph classification for a given bent function from a database. INPUT: - ``conn`` -- a connection object for the database. - ``bentf`` -- class BentFunction. A bent function. OUTPUT: class BentFunctionCayleyGraphClassification. The corresponding a Cayley graph classification. EXAMPLE: Create a database, with tables, using a standardized name, insert a classification, retrieve it by bent function, then drop the database. :: sage: from boolean_cayley_graphs.classification_database_psycopg2 import * sage: from boolean_cayley_graphs.bent_function import BentFunction sage: from boolean_cayley_graphs.bent_function_cayley_graph_classification import BentFunctionCayleyGraphClassification sage: bentf = BentFunction([0,0,0,1]) sage: bfcgc = BentFunctionCayleyGraphClassification.from_function(bentf) sage: bfcgc.algebraic_normal_form x0*x1 sage: dbname = 'doctest_select_classification_where_bent_function_dbname' sage: drop_database(dbname) sage: conn = create_database(dbname) sage: conn.close() sage: conn = create_classification_tables(dbname) sage: insert_classification(conn, bfcgc, 'bentf') sage: result = select_classification_where_bent_function(conn, bentf) sage: result.algebraic_normal_form x0*x1 sage: type(result) <class 'boolean_cayley_graphs.bent_function_cayley_graph_classification.BentFunctionCayleyGraphClassification'> sage: result.report(report_on_matrix_details=True) Algebraic normal form of Boolean function: x0*x1 Function is bent. <BLANKLINE> Weight class matrix: [0 0 0 1] [0 1 0 0] [0 0 1 0] [1 0 0 0] <BLANKLINE> SDP design incidence structure t-design parameters: (True, (1, 4, 1, 1)) <BLANKLINE> Classification of Cayley graphs and classification of Cayley graphs of duals are the same: <BLANKLINE> There are 2 extended Cayley classes in the extended translation class. <BLANKLINE> Matrix of indices of Cayley graphs: [0 0 0 1] [0 1 0 0] [0 0 1 0] [1 0 0 0] sage: conn.close() sage: drop_database(dbname) """ dim = bentf.nvariables() nvar = int(dim) bftt = psycopg2.Binary(bentf.tt_buffer()) curs = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) curs.execute(""" SELECT COUNT(*) FROM cayley_graph WHERE nvariables = (%s) AND bent_function = (%s)""", (nvar, bftt)) row = curs.fetchone() if row == None: return None cgcl_len = row[0] cgcl = [None] * cgcl_len curs.execute(""" SELECT cayley_graph_index, canonical_label FROM cayley_graph, graph WHERE nvariables = (%s) AND bent_function = (%s) AND cayley_graph.graph_id = graph.graph_id""", (nvar, bftt)) for row in curs: cayley_graph_index = row["cayley_graph_index"] canonical_label = row["canonical_label"] cgcl[cayley_graph_index] = str(canonical_label) v = 2 ** dim bcim = matrix(v, v) dcim = matrix(v, v) wcm = matrix(v, v) curs.execute(""" SELECT * FROM matrices WHERE nvariables = (%s) AND bent_function = (%s)""", (nvar, bftt)) for row in curs: b = row["b"] c = row["c"] bent_cayley_graph_index = row["bent_cayley_graph_index"] bcim[c, b] = bent_cayley_graph_index dual_cayley_graph_index = row["dual_cayley_graph_index"] dcim[c, b] = dual_cayley_graph_index weight_class = row["weight_class"] wcm[c, b] = weight_class return BentFunctionCayleyGraphClassification( algebraic_normal_form=bentf.algebraic_normal_form(), cayley_graph_class_list=cgcl, bent_cayley_graph_index_matrix=bcim, dual_cayley_graph_index_matrix=dcim, weight_class_matrix=wcm)
[docs]def select_classification_where_bent_function_cayley_graph( conn, bentf, algorithm=default_algorithm): """ Given a bent function ``bentf``, retrieve all classifications that contain a Cayley graph isomorphic to the Cayley graph of ``bentf``. INPUT: - ``conn`` -- a connection object for the database. - ``bentf`` -- class BentFunction. A bent function. - ``algorithm`` -- string (default: BentFunctionCayleyGraphClassification.default_algorithm). Algorithm used for canonical labelling. OUTPUT: A list where each entry has class BentFunctionCayleyGraphClassification. The corresponding list of Cayley graph classifications. NOTE: :: The list is not sorted in any way. EXAMPLE: Create a database, with tables, using a standardized name, insert a classification, retrieve all related classifications by bent function Cayley graph, then drop the database. :: sage: from boolean_cayley_graphs.classification_database_psycopg2 import * sage: from boolean_cayley_graphs.bent_function import BentFunction sage: from boolean_cayley_graphs.bent_function_cayley_graph_classification import BentFunctionCayleyGraphClassification sage: dbname = 'doctest_select_classification_where_bent_function_dbname' sage: drop_database(dbname) sage: conn = create_database(dbname) sage: conn.close() sage: conn = create_classification_tables(dbname) sage: bentf0 = BentFunction([0,0,0,1]) sage: bfcgc0 = BentFunctionCayleyGraphClassification.from_function(bentf0) sage: bfcgc0.algebraic_normal_form x0*x1 sage: insert_classification(conn, bfcgc0, 'bentf0') sage: bentf1 = BentFunction([1,0,0,0]) sage: bfcgc1 = BentFunctionCayleyGraphClassification.from_function(bentf1) sage: bfcgc1.algebraic_normal_form x0*x1 + x0 + x1 + 1 sage: insert_classification(conn, bfcgc1, 'bentf1') sage: result = select_classification_where_bent_function_cayley_graph(conn, bentf1) sage: type(result) <class 'list'> sage: len(result) 2 sage: sorted_result = sorted(result, key=lambda c: str(c.algebraic_normal_form)) sage: for c in sorted_result: ....: type(c) ....: c.algebraic_normal_form ....: c.report() <class 'boolean_cayley_graphs.bent_function_cayley_graph_classification.BentFunctionCayleyGraphClassification'> x0*x1 Algebraic normal form of Boolean function: x0*x1 Function is bent. <BLANKLINE> <BLANKLINE> SDP design incidence structure t-design parameters: (True, (1, 4, 1, 1)) <BLANKLINE> Classification of Cayley graphs and classification of Cayley graphs of duals are the same: <BLANKLINE> There are 2 extended Cayley classes in the extended translation class. <class 'boolean_cayley_graphs.bent_function_cayley_graph_classification.BentFunctionCayleyGraphClassification'> x0*x1 + x0 + x1 + 1 Algebraic normal form of Boolean function: x0*x1 + x0 + x1 + 1 Function is bent. <BLANKLINE> <BLANKLINE> SDP design incidence structure t-design parameters: (True, (1, 4, 1, 1)) <BLANKLINE> Classification of Cayley graphs and classification of Cayley graphs of duals are the same: <BLANKLINE> There are 2 extended Cayley classes in the extended translation class. sage: conn.close() sage: drop_database(dbname) """ cayley_graph = bentf.extended_cayley_graph() cgcl = cayley_graph.canonical_label(algorithm=algorithm).graph6_string() cgcl_hash = canonical_label_hash(cgcl) curs = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) curs.execute(""" SELECT graph_id, canonical_label FROM graph WHERE canonical_label_hash = (%s)""", (cgcl_hash,)) row = curs.fetchone() graph_id = row["graph_id"] canonical_label = row["canonical_label"] # The result is a list of classifications. result = [] # Check for a hash collision -- very unlikely. if canonical_label != cgcl: return result curs.execute(""" SELECT DISTINCT nvariables, bent_function FROM cayley_graph WHERE graph_id = (%s)""", (graph_id,)) for row in curs: nvar = row["nvariables"] bftt = row["bent_function"] row_bentf = BentFunction.from_tt_buffer(nvar, bftt) result.append( select_classification_where_bent_function( conn, row_bentf)) return result
[docs]def select_classification_where_name( conn, name): """ Retrieve a Cayley graph classification for a bent function with a given name from a database. INPUT: - ``conn`` -- a connection object for the database. - ``name`` -- string. The name of the bent function. OUTPUT: class BentFunctionCayleyGraphClassification. The corresponding a Cayley graph classification. EXAMPLE: Create a database, with tables, using a standardized name, insert a classification, retrieve it by bent function, then drop the database. :: sage: from boolean_cayley_graphs.classification_database_psycopg2 import * sage: from boolean_cayley_graphs.bent_function import BentFunction sage: from boolean_cayley_graphs.bent_function_cayley_graph_classification import BentFunctionCayleyGraphClassification sage: bentf = BentFunction([0,0,0,1]) sage: bfcgc = BentFunctionCayleyGraphClassification.from_function(bentf) sage: bfcgc.algebraic_normal_form x0*x1 sage: dbname = 'doctest_select_classification_where_bent_function_dbname' sage: drop_database(dbname) sage: conn = create_database(dbname) sage: conn.close() sage: conn = create_classification_tables(dbname) sage: insert_classification(conn, bfcgc, 'bentf') sage: result = select_classification_where_name(conn, 'bentf') sage: result.algebraic_normal_form x0*x1 sage: type(result) <class 'boolean_cayley_graphs.bent_function_cayley_graph_classification.BentFunctionCayleyGraphClassification'> sage: result.report(report_on_matrix_details=True) Algebraic normal form of Boolean function: x0*x1 Function is bent. <BLANKLINE> Weight class matrix: [0 0 0 1] [0 1 0 0] [0 0 1 0] [1 0 0 0] <BLANKLINE> SDP design incidence structure t-design parameters: (True, (1, 4, 1, 1)) <BLANKLINE> Classification of Cayley graphs and classification of Cayley graphs of duals are the same: <BLANKLINE> There are 2 extended Cayley classes in the extended translation class. <BLANKLINE> Matrix of indices of Cayley graphs: [0 0 0 1] [0 1 0 0] [0 0 1 0] [1 0 0 0] sage: conn.close() sage: drop_database(dbname) """ curs = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) curs.execute(""" SELECT nvariables, bent_function FROM bent_function WHERE name = (%s)""", (name,)) row = curs.fetchone() if row == None: return None nvar = row["nvariables"] bftt = row["bent_function"] bentf = BentFunction.from_tt_buffer(nvar, bftt) return select_classification_where_bent_function( conn, bentf)