Source code for sircuitenum.utils

__doc__ = "utils.py: Contains utilities used in other files in the package"
__author__ = "Eli Weissler, Mohit Bhat"
__version__ = "0.1.0"
__all__ = ['get_circuit_data_batch', 'find_circuit_in_db', "get_equiv_circuits", "get_equiv_circuits_uid", "graph_index_to_edges", "edges_to_graph_index"]

import itertools
import functools
from typing import Union
from pathlib import Path
from time import sleep

import sqlite3
import numpy as np
import networkx as nx
import pandas as pd
from tqdm import tqdm
import sympy as sym
from sympy import collect, expand_mul, Mul, Dummy
from sympy.core.add import Add
from sympy.core.symbol import Symbol

# Set ENUM_PARAMS at end of file
global ENUM_PARAMS
ENUM_PARAMS = {}

ELEM_DICT = {
    'C': {'default_unit': 'GHz', 'default_value': 0.2},
    'L': {'default_unit': 'GHz', 'default_value': 1.0},
    'J': {'default_unit': 'GHz', 'default_value': 5.0},
    'CJ': {'default_unit': 'GHz', 'default_value': 20.0}
}

DOWNLOAD_PATH = Path(__file__).parent.parent

# Dictionary to store loaded basegraphs, so
# you don't have to load them from storage
# every time
LOADED_BASEGRAPHS = {}


[docs]def graph_index_to_edges(graph_index: int, n_nodes: int): """ Returns a list of edges [(from, to), (from, to)] for the specified base graph Args: graph_index (int): base graph number n_nodes (int): number of nodes in the base graph Returns: list of len 2 tuples where each tuple represents the starting and ending nodes for an edge in the graph [(from, to), (from, to),...] """ return list(get_basegraphs(n_nodes)[graph_index].edges)
[docs]def edges_to_graph_index(edges: list, return_mapping: bool = False) -> int: """ Matches a given set of edges to an isomorphic base graph. This function finds a base graph that is isomorphic to the input edge set. Parameters ---------- edges : list of tuple of int A list of edge connections representing the desired circuit. Example: ``[(0,1), (0,2), (1,2)]``. return_mapping : bool, optional If `True`, returns the mapping of edges to the base graph. Defaults to `False`. Returns ------- int The index of the graph matching the given edges within the set of graphs with the same number of nodes. dict, optional If `return_mapping=True`, also returns a dictionary mapping edges to the base graph. """ # Graph object to use in comparison G1 = nx.Graph() G1.add_edges_from(edges) n_nodes = get_num_nodes(edges) n_edges = len(edges) possible_graphs = get_basegraphs(n_nodes) for i, G2 in enumerate(possible_graphs): if G2.number_of_edges() == n_edges: GM = nx.isomorphism.GraphMatcher(G1, G2) if GM.is_isomorphic(): if return_mapping: return i, GM.mapping return i raise ValueError("Error: No Isomorphic Graph Found")
def encoding_to_components(circuit_raw: str, char_mapping: dict = None): """Maps the raw circuit encoding to a list of lists of elements e.g. 261 -> [["J"], ["C", "J", "L"], ["L"]] Args: circuit_raw (str): string that represents base n number where each character maps to a combination of circuit componenets char_mapping (dict, optional): mapping from characters to circuit components. Defaults to CHAR_TO_COMBINATION. Returns: list of lists that represent the circuit elements along an edge: e.g. [["J"], ["C", "J", "L"], ["L"]] """ if char_mapping is None: char_mapping = ENUM_PARAMS["CHAR_TO_COMBINATION"] return [char_mapping[str(e)] for e in circuit_raw] def components_to_encoding(circuit: list, elem_mapping: dict = None): """Maps the list of circuit components to the database encoding e.g. [["J"], ["C", "J", "L"], ["L"]] -> 261 Args: circuit (list): a list of element labels for the desired circuit e.g. [["J"],["L", "J"], ["C"]] elem_mapping (dict, optional): mapping from circuit components to characters. Defaults to COMBINATION_TO_CHAR. Returns: list of lists that represent the circuit elements along an edge: e.g. [["J"], ["C", "J", "L"], ["L"]] """ if elem_mapping is None: elem_mapping = ENUM_PARAMS["COMBINATION_TO_CHAR"] return "".join([elem_mapping[tuple(comps)] for comps in circuit]) def convert_loaded_df(df: pd.DataFrame, n_nodes: int, char_mapping: dict = None): """Load the edges/circuit element labels for a freshly-loaded df Args: df (pd.Dataframe): dataframe of circuits n_nodes (int): number of nodes in the circuits char_mapping (dict, optional): mapping from circuit components to characters. Defaults to CHAR_TO_COMBINATION. Returns: Nothing, modifies the dataframe """ if char_mapping is None: char_mapping = ENUM_PARAMS["CHAR_TO_COMBINATION"] # Get the edges df['edges'] = [graph_index_to_edges(int(i), n_nodes) for i in df.graph_index.values] df['circuit_encoding'] = df.circuit.values.copy() df['circuit'] = [encoding_to_components(c, char_mapping=char_mapping) for c in df.circuit.values] def get_basegraphs(n_nodes: int): """ Loads the base graphs for a specific number of nodes Args: n_nodes (int): number of nodes in the graph """ if int(n_nodes) > 6: raise ValueError("Only basegraphs up to 6 nodes are included. See https://users.cecs.anu.edu.au/~bdm/data/graphs.html for larger sets of graphs.") # Load it if it hasn't been loaded if str(n_nodes) not in LOADED_BASEGRAPHS: f = Path(DOWNLOAD_PATH, 'sircuitenum', 'graphs', f"graph{n_nodes}c.g6") all_graphs = nx.read_graph6(f) # Fix two vertex case so it always returns a list if n_nodes == 2: all_graphs = [all_graphs] LOADED_BASEGRAPHS[str(n_nodes)] = all_graphs # Return if it has already been loaded return LOADED_BASEGRAPHS[str(n_nodes)] def count_elems(circuit: list, base: int): """ Counts the total number of each element label in the circuit, for use with the unmapped integer labels Args: circuit (list of str): a list of element labels for the desired circuit (i.e., ['0','2','5','1']) base (int): The number of possible edges. By default this is 7: (i.e., J, C, I, JI, CI, JC, JCI) Returns: list of length base, where each entry is the number of the element found at that index of ENUM_PARAMS["CHAR_LIST"] """ counts = [0]*base for part in circuit: counts[ENUM_PARAMS["CHAR_LIST"].index(part)] += 1 return counts def count_elems_mapped(circuit: list, **kwargs): """ Counts the total number of each mapped circuit element in the circuit Args: circuit (list): a list of element labels for the desired circuit e.g. [["J"],["L", "J"], ["C"]] possible_elems (list): list of possible elements, default is the unique set in CHAR_TO_COMBINATION Returns: dict: each entry is element -> number, i.e. "J" -> 2 """ possible_elems = kwargs.get("possible_elems", list_single_elems()) counts = {} for elem in possible_elems: counts[elem] = 0 for elems in circuit: for elem in elems: counts[elem] += 1 return counts def add_elem_number(circuit: list, **kwargs): """ Counts the total number of each mapped circuit element in the circuit Args: circuit (list): a list of element labels for the desired circuit e.g. [["J"],["L", "J"], ["C"]] possible_elems (list): list of possible elements, default is the unique set in CHAR_TO_COMBINATION Returns: dict: each entry is element -> number, i.e. "J" -> 2 """ possible_elems = kwargs.get("possible_elems", list_single_elems()) circuit_new = [] counts = {} for elem in possible_elems: counts[elem] = 0 for elems in circuit: elems_new = [] for elem in elems: counts[elem] += 1 elems_new.append(elem+"_"+str(counts[elem])) circuit_new.append(tuple(elems_new)) return circuit_new def circuit_entry_dict(circuit: list, graph_index: int, n_nodes: int, circuit_num: int, base: int): """Creates a dictionary that can serve as a row of a dataframe of circuits, or can be used to write an individual row to a database Args: circuit (list of str): a list of element labels for the desired circuit (i.e., ['0','2','5','1']) graph index (int): the index of the graph for the written circuit within the file for the number of nodes n_nodes (int): Number of nodes in circuit circuit_num (int): n-th circuit generated from the basegraph, to make a unique key. base (int): The number of possible edges. By default this is 7: (i.e., J, C, I, JI, CI, JC, JCI) Returns: dictionary with circuit, graph_index, edge_counts, n_nodes """ c_dict = {} c_dict['circuit'] = "".join(circuit) c_dict['graph_index'] = graph_index c_dict['unique_key'] = f"n{n_nodes}_g{graph_index}_c{circuit_num}" c_dict['in_non_iso_set'] = 0 c_dict['no_series'] = 0 c_dict['filter'] = 0 c_dict['equiv_circuit'] = "" counts = [str(c) for c in count_elems(circuit, base)] c_dict['edge_counts'] = ",".join(counts) c_dict['n_nodes'] = n_nodes c_dict['base'] = base return c_dict def gen_param_dict(circuit, edges, vals=ELEM_DICT, rand_amp=0, min_val=1e-06): """ Generates a dictionary of parameters for use with the circuit conversion functions. Sets all components to the same values. Maps (edge, elem) to (value, unit): i.e., ((0,1), "J") -> (5.0, "GHz") Args: circuit (list): a list of element labels for the desired circuit e.g. [("J",),("L", "J"), ("C",)] edges (list): a list of edge connections for the desired circuit e.g. [(0,1), (0,2), (1,2)] vals (dict of dicts): Dictionary with entries for each circuit element. Shows default values Returns: dict: (edge, elem) -> (val, unit) """ param_dict = {} for elems, edge in zip(circuit, edges): for elem in elems: key = (edge, elem) val = vals[elem]['default_value'] if rand_amp > 0 and val > 0: val = max(min_val, val*np.random.normal(1, rand_amp)) param_dict[key] = (val, vals[elem]['default_unit']) # Junction capacitance key = (edge, "CJ") if elem == "J" and "CJ" in vals: val = vals["CJ"]['default_value'] if rand_amp > 0 and val > 0: max(min_val, val*np.random.normal(1, rand_amp)) param_dict[key] = (val, vals["CJ"]['default_unit']) return param_dict def convert_circuit_to_graph(circuit: list, edges: list, **kwargs): """ Encodes a circuit as a simple, undirected nx graph with labels on the edges for the circuit element, unit, and value Args: circuit (list of str): a list of elements for the desired circuit (i.e., [[['C'],['C'],['L'],['C','J']]) edges (list of tuples of ints): a list of edge connections for the desired circuit (i.e., [(0,1),(1,2),(2,3),(3,0)]) params (dict): dictionary with entries C, L, J, CJ, which represent the paramaters for the circuit elements. Additionally entries of C_units, L_units, J_units, and CJ_units. Inputting nothing uses the default parameter values/units from utils.ELEM_DICT. """ params = kwargs.get("params", gen_param_dict(circuit, edges, ELEM_DICT)) circuit_graph = nx.MultiGraph() for elems, edge in zip(circuit, edges): for elem in elems: value, unit = params[(edge, elem)] circuit_graph.add_edge(edge[0], edge[1], element=elem, unit=unit, value=value) # Junction capacitance if elem == "J": if (edge, "CJ") in params: value, unit = params[(edge, "CJ")] if value > 0: circuit_graph.add_edge(edge[0], edge[1], element="CJ", unit=unit, value=value) return circuit_graph def circuit_degree(circuit: list, edges: list): """ Counts the number of elements connected to each node Args: circuit (list): a list of element labels for the desired circuit e.g. [["J"],["L", "J"], ["C"]] edges (list): a list of edge connections for the desired circuit e.g. [(0,1), (0,2), (1,2)] Returns: list of how many elements are connected to each node e.g. [1, 2, 1] """ node_repr = circuit_node_representation(circuit, edges) return list(sum([np.array(x) for x in node_repr.values()])) def jj_present(circuit: list): """ Simple function that returns true if there is at least one JJ in the circuit and false if there isn't Args: circuit (list): a list of element labels for the desired circuit e.g. [["J"],["L", "J"], ["C"]] """ for edge in circuit: for device in edge: if device == "J": return True return False def qps_present(circuit: list): """ Simple function that returns true if there is at least one qps in the circuit and false if there isn't Args: circuit (list): a list of element labels for the desired circuit e.g. [["J"],["L", "J"], ["Q"]] """ for edge in circuit: for device in edge: if device == "Q": return True return False def circuit_node_representation(circuit: list, edges: list): """ Converts a circuit into its "node representation" that shows how many of each component are connected to each node. Args: circuit (list): a list of element labels for the desired circuit e.g. [["J"],["L", "J"], ["C"]] edges (list): a list of edge connections for the desired circuit e.g. [(0,1), (0,2), (1,2)] Returns: dictionary that maps component label to how many are connected to each node: e.g. {'J': [0,0,1,2,0]} """ # Extract number of nodes from edge list n_nodes = get_num_nodes(edges) # Dictionary that maps component to a list that says # how many of that component connect to a given node # i.e. 'J': [0,0,1,2,0] component_counts = {} for comp in list_single_elems(): component_counts[comp] = [0] * n_nodes # Go through each component code in the circuit # and loop through the circuit element that it entails # and add counts to the appropriate nodes for components, edge in zip(circuit, edges): for comp in components: component_counts[comp][edge[0]] += 1 component_counts[comp][edge[1]] += 1 return component_counts def list_single_elems(): """ Simple function to list all single characters in CHAR_TO_COMBINATION Returns: list[str]: list of characters """ return list(np.unique(np.concatenate(list(ENUM_PARAMS["CHAR_TO_COMBINATION"].values())))) def get_num_nodes(edges: list): """ Simple function that returns the number of unique nodes in edges """ return np.unique(np.concatenate(edges)).size def renumber_nodes(edges: list): """ Renumbers nodes so that there is a continuous range of integers between 0 and the max number Args: edges (list): a list of edge connections for the desired circuit e.g. [(0,1), (0,2), (1,2)] Returns: new version of edges with nodes relabeled so that the max number present is equal to the number of nodes + 1 """ new_edges = edges[:] nodes = np.unique(np.concatenate(new_edges)) if nodes[-1] != nodes.shape[0]-1: relabel_map = {} for i in range(len(nodes)): relabel_map[nodes[i]] = i for i in range(len(new_edges)): edge = new_edges[i] new_edges[i] = tuple([relabel_map[x] for x in edge]) return new_edges def combine_redundant_edges(circuit: list, edges: list): """ Combines edges that are between the same two nodes Args: circuit (list): a list of element labels for the desired circuit e.g. [["J"],["L", "J"], ["C"]] edges (list): a list of edge connections for the desired circuit e.g. [(0,1), (0,2), (1,2)] Returns: New version of circuit/edges with any redundant edges combined. If multiple edges have the same element, then a single """ edge_dict = {} for i in range(len(edges)): edge = tuple(sorted(edges[i])) comps = circuit[i] if edge in edge_dict: edge_dict[edge] = edge_dict[edge] + comps else: edge_dict[edge] = comps new_edges = list(edge_dict.keys()) new_circuit = [tuple(sorted(set(edge_dict[x]))) for x in new_edges] return new_circuit, new_edges def circuit_in_set(circuit: list, c_set: list): """Helper function to see if a particular circuit (list/tuple of tuples) is in a set of circuits (list of list/tuple of tuples) Args: cir (list): a list of element labels for the desired circuit e.g. [("J"),("L", "J"), ("C")] c_set (list of lists): list of cir-like elements Returns: True if cir is present in c_set, False if it isn't """ for c2 in c_set: if len(circuit) == len(c2): if all(circuit[i] == c2[i] for i in range(len(circuit))): return True return False ############################################################################### # I/O Functions for Circuit Database ############################################################################### def write_df(file: str, df: pd.DataFrame, n_nodes: int, overwrite=False): """ Writes the given dataframe to a database file. Appends it if the table is already there. Args: file (str, optional): Database file to write to. df (pd.Dataframe): dataframe that represents the circuit entries n_nodes (int, optional): number of nodes in the circuit. Defaults to 7. overwrite (bool, optional): overwrite the table or append to it if it exists Returns: None, writes the dataframe to the database """ to_write = df.copy() # drop list columns to save circuit back into saving format del to_write['edges'] # Rename circuit encoding column to_write['circuit'] = to_write['circuit_encoding'] del to_write['circuit_encoding'] if_exists = "append" if overwrite: if_exists = "replace" with sqlite3.connect(file) as con: to_write.to_sql(f"CIRCUITS_{n_nodes}_NODES", con, if_exists=if_exists, index=False) def update_db_from_df(file: str, df: pd.DataFrame, to_update: list, str_cols: list = [], float_cols: list = []): """ Updates the given columns listed in to_update for entries within df. Assumes all columns not listed as str cols or float cols are integers. Args: file (str, optional): Database file to write to. df (pd.Dataframe): dataframe that represents the circuit entries to_update (list): columns to update str_cols (list): columns that are string valued float_cols (list): columns that are float valued Returns: None, writes the dataframe info to the database """ n_fields = len(to_update) with sqlite3.connect(file, timeout=5000) as con: cur = con.cursor() # sql_str = "" for _, row in df.iterrows(): n_nodes = row['n_nodes'] sql_str = f"UPDATE CIRCUITS_{n_nodes}_NODES SET " for i, col in enumerate(to_update): val = row[col] if col not in str_cols and col not in float_cols: val = int(val) elif col in str_cols: val = str(val).replace("'", "") else: val = float(val) if i < n_fields - 1: sql_str += f"{col} = '{val}', " else: sql_str += f"{col} = '{val}' " sql_str += f"WHERE unique_key = '{row['unique_key']}';\n" written = False while not written: try: cur.executescript(sql_str) written = True except sqlite3.OperationalError: # Database is locked, wait random amount # of time and try again print("Write Conflict") sleep(np.abs(np.random.random())) con.commit() def delete_circuit_data(file: str, n_nodes: int, indices: Union[list, str]): """ Deletes the specified graphs (num nodes/indices) from the database file Args: file (str, optional): path to the databse file. n_nodes (int): number of nodes for the graph indices (list or str): unique key (or list of keys) of the graph(s) to be deleted Returns: None, just modifies the database """ # Convert individual entry for batch use if isinstance(indices, str): indices = [indices] connection_obj = sqlite3.connect(file) cursor_obj = connection_obj.cursor() table_name = 'CIRCUITS_' + str(n_nodes) + '_NODES' for index in indices: cursor_obj.execute('''DELETE FROM {table} WHERE unique_key = '{index}'; '''.format(table=table_name, index=str(index))) connection_obj.commit() connection_obj.close() return def get_circuit_data(file: str, unique_key: str, char_mapping: dict = None): """ gets circuit data from database Args: n_nodes (int): The number of nodes in the circuit unique_key (str): Unique Idenitifier of the circuit file (str): path to the database to get circuit from char_mapping (dict, optional): mapping from character to list of circuit elements Returns: circuit (list) : a list of element labels for the desired circuit (i.e., ['0','2','5','1']) edges (list of tuples of ints): a list of edge connections for the desired circuit (i.e., [(0,1),(1,2),(2,3),(3,0)]) """ if char_mapping is None: char_mapping = ENUM_PARAMS["CHAR_TO_COMBINATION"] # Parse uid to get number of nodes n_nodes = unique_key[unique_key.find("n") + 1:unique_key.find("_")] # Fetch entry from database connection_obj = sqlite3.connect(file, uri=True) cursor_obj = connection_obj.cursor() table_name = 'CIRCUITS_' + str(n_nodes) + '_NODES' query_str = f"SELECT * FROM {table_name} WHERE unique_key = '{unique_key}'" cursor_obj.execute(query_str) output = cursor_obj.fetchone() connection_obj.commit() connection_obj.close() # Map the edges and circuit component info edges = graph_index_to_edges(int(output[1]), n_nodes) circuit = encoding_to_components(output[0], char_mapping=char_mapping) return circuit, edges
[docs]def get_circuit_data_batch(db_file: str, n_nodes: int, char_mapping: dict = None, filter_str: str = '') -> pd.DataFrame: """ Retrieve all circuits from the database for a specified number of nodes, with optional filtering criteria. Parameters ---------- db_file : str, optional Path to the SQLite database file. Defaults to ``"circuits.db"``. n_nodes : int Number of nodes in the circuit. char_mapping : dict, optional A mapping from characters to lists of circuit elements. filters : str, optional SQL filter statement for refining the query. Example: ``"WHERE circuit_index = 100"``. Returns ------- pandas.DataFrame A DataFrame where each row represents a circuit matching the query. """ if char_mapping is None: char_mapping = ENUM_PARAMS["CHAR_TO_COMBINATION"] table_name = 'CIRCUITS_' + str(n_nodes) + '_NODES' connection_obj = sqlite3.connect(db_file, timeout=5000) query = "SELECT * FROM {table} {filter_str}".format( table=table_name, filter_str=filter_str) df = pd.read_sql_query(query, connection_obj) connection_obj.commit() connection_obj.close() convert_loaded_df(df, n_nodes, char_mapping) # Make a useful index if it's there if 'unique_key' in df.columns: df.index = df['unique_key'] # Convert int to bool columns int_to_bool = ["no_series", "filter", "in_non_iso_set"] for col in int_to_bool: df[col] = df[col].astype(bool) return df
def get_unique_qubits(db_file: str, n_nodes: str): """ Loads all entries corresponding to unique qubits from the specified file for the specified number of nodes Args: db_file (str): sqlite db_file to look in. Defaults to "circuits.db" n_nodes (int): number of nodes in the circuit Returns: pd.DataFrame: set of unique qubit circuits """ filter_str = "WHERE in_non_iso_set = 1 AND " filter_str += "filter = 1 AND no_series = 1" return get_circuit_data_batch(db_file, n_nodes, filter_str=filter_str)
[docs]def get_equiv_circuits_uid(db_file: str, unique_key: str) -> pd.DataFrame: """ Finds all circuits in the database that match a given unique key or are equivalent to it. This function searches for circuits in an SQLite database that either: - Have the specified `unique_key`, or - Are considered equivalent circuits by component-like isomorphism. Parameters ---------- db_file : str Path to the SQLite database file. Defaults to `"circuits.db"`. unique_key : str The unique identifier for the circuit. Returns ------- pandas.DataFrame A DataFrame containing all matching circuits. """ tables = list_all_tables(db_file) entries = [] filt_str = f"WHERE equiv_circuit LIKE '{unique_key}'\ OR unique_key LIKE '{unique_key}'" for tbl in tables: n_nodes = int([n for n in tbl if n.isdigit()][0]) entries.append(get_circuit_data_batch(db_file, n_nodes, filter_str=filt_str)) return pd.concat(entries).sort_values(by="equiv_circuit")
[docs]def get_equiv_circuits(db_file: str, circuit: list, edges: list) -> Union[pd.DataFrame, None]: """ Finds all circuits in the database that are equivalent to a given circuit. Parameters ---------- db_file : str Path to the SQLite database file. Defaults to `"circuits.db"`. circuit : list A list of element labels defining the desired circuit. Example: `[["J"], ["L", "J"], ["C"]]` edges : list A list of edge connections defining how circuit elements are connected. Example: `[(0,1), (0,2), (1,2)]` Returns ------- pandas.DataFrame or None A DataFrame containing all equivalent circuits found in the database. Returns `None` if no equivalent circuits are found. """ entry = find_circuit_in_db(db_file, circuit, edges) if entry.shape[0] > 1: raise ValueError("Getting too many circuits") elif entry.empty: return None else: entry = entry.iloc[0] if entry["in_non_iso_set"]: uid = entry["unique_key"] elif entry["equiv_circuit"] != "not found": uid = entry["equiv_circuit"] else: return [entry] return get_equiv_circuits_uid(db_file, uid)
[docs]def find_circuit_in_db(db_file: str, circuit: list, edges: list): """ Finds the database entry for a given circuit/edges combination Args: db_file (str): sqlite db_file to look in. Defaults to "circuits.db" circuit (list): a list of element labels for the desired circuit e.g. [["J"],["L", "J"], ["C"]] edges (list): a list of edge connections for the desired circuit e.g. [(0,1), (0,2), (1,2)] """ n_nodes = get_num_nodes(edges) graph_index, mapping = edges_to_graph_index(edges, return_mapping=True) # Re-order circuit to match basegraph edges new_edge_order = graph_index_to_edges(graph_index, n_nodes) circuit_in_order = [None]*len(circuit) for i in range(len(edges)): n0, n1 = edges[i] if (mapping[n0], mapping[n1]) in new_edge_order: new_i = new_edge_order.index((mapping[n0], mapping[n1])) else: new_i = new_edge_order.index((mapping[n1], mapping[n0])) circuit_in_order[new_i] = circuit[i] encoding = components_to_encoding(circuit_in_order) filters = f"WHERE circuit LIKE '{encoding}' AND\ graph_index = '{graph_index}'" return get_circuit_data_batch(db_file, n_nodes, filter_str=filters)
def write_circuit(cursor_obj, c_dict: dict, to_commit: bool = False): """Appends an individual circuit to a database Args: cursor_obj: sqllite cursor object pointing to the desired database c_dict: dictionary that represents a circuit entry to_commit: commit the database (i.e., save changes) """ table = f"CIRCUITS_{c_dict['n_nodes']}_NODES" sql_str = f"INSERT INTO {table} VALUES (" sql_fields = ["circuit", "graph_index", "edge_counts", "unique_key", "n_nodes", "base", "no_series", "filter", "in_non_iso_set", "equiv_circuit"] n_fields = len(sql_fields) for i, field in enumerate(sql_fields): if i < n_fields - 1: sql_str += f"'{c_dict[field]}', " else: sql_str += f"'{c_dict[field]}')" cursor_obj.execute(sql_str) if to_commit: cursor_obj.connection.commit() def list_all_tables(db_file: str): """ Lists all the tables in the database file Args: db_file (str): file to examine """ with sqlite3.connect(db_file, uri=True) as connection_obj: cursor_obj = connection_obj.cursor() tables = cursor_obj.execute("SELECT name FROM sqlite_master\ WHERE type='table'").fetchall() return [x[0] for x in tables] def list_all_columns(db_file: str, table_name: str): """ Lists all the tables in the database file Args: db_file (str): file to examine table_name (str): table to get """ with sqlite3.connect(db_file, uri=True) as connection_obj: cursor_obj = connection_obj.cursor() info = cursor_obj.execute(f"PRAGMA table_info('{table_name}')").fetchall() cols = [x[1] for x in info] return cols def collect_H_terms(H: Add, zero_ext: bool = True, periodic_charge="n", periodic_phase="θ", extended_charge="q", extended_phase="φ", ext_charge: str = "ng", ext_flux: str = "_{ext}", no_coeff: bool = False, collect_phase: bool = True) -> Add: """ Groups terms in the Hamiltonian. Default settings are for our operator convention -- not Scqubits (q -> Q and \varphi -> \theta). Args: H (Add): Hamiltonian zero_ext (bool, optional): Whether to zero all gate voltages/external fluxes. Defaults to True. periodic_charge (str, optional): symbol used for periodic charges. Defaults to "n". extended_charge (str, optional): symbol used for extended charges. Defaults to "Q". periodic_phase (str, optional): symbol used for periodic phases. Defaults to "θ". extended_phase (str, optional): symbol used for extended phases. Defaults to "θ". ext_charge (str, optional): symbol used in external charges. Defaults to "ng". ext_flux (str, optional): symbol used in external fluxes. Defaults to "_{ext}" no_coef (bool, optional): Remove all the coefficients, only leaving operators. collect_phase (bool, optional): for speed, don't collect the phase terms. slightly messier, but faster. Returns: Add: Hamiltonian with terms grouped """ # List of variable types q_list = [q for q in H.free_symbols if extended_charge in str(q)] n_list = [q for q in H.free_symbols if periodic_charge in str(q) and ext_charge not in str(q)] theta_list = [th for th in H.free_symbols if (periodic_phase in str(th) or extended_phase in str(th)) and ext_flux not in str(th)] ext_list = [q for q in H.free_symbols if ext_charge in str(q) or ext_flux in str(q)] n_modes = len(theta_list) # Set all external parameters to 0 if zero_ext: for ext in ext_list: H = H.subs(ext, 0) # Terms to group # Q and n combosQ = {} for terms in itertools.product(q_list + n_list, repeat=2): combo = functools.reduce(lambda x, y: x*y, terms) indices = np.unique([str(x)[-1] for x in terms]) combosQ[combo] = "E_{C"+''.join(indices)+"}" # Phase combos = [] combos_trig = [] if collect_phase: for num_terms in range(1, n_modes + 1): # Straight products combos += list(set([functools.reduce(lambda x, y: x*y, z) for z in itertools.product(theta_list, repeat=num_terms)])) # Trig products # Encoding signals cos or sin for encoding in itertools.product([0, 1], repeat=num_terms): # Modes is which num_terms modes are being considered for modes in itertools.combinations(range(n_modes), num_terms): trig_prod = 1 for i, term in enumerate(encoding): if term: trig_prod *= sym.cos(theta_list[modes[i]]) else: trig_prod *= sym.sin(theta_list[modes[i]]) combos_trig += [trig_prod] # Explicitly add theta squared terms if only one mode if n_modes == 1: combos += list(set([functools.reduce(lambda x, y: x*y, z) for z in itertools.product(theta_list, repeat=2)])) H = collect(H, list(combosQ.keys()) + combos, func=sym.ratsimp) if collect_phase: H = collect(H, combos_trig) if no_coeff: H = _remove_coeff(H, list(combosQ.keys()) + combos + combos_trig) return H, combos+combos_trig, combosQ def _remove_coeff(H, all_combos): H_class = H.copy() for combo in all_combos: H_class = H_class.replace(lambda x: x.is_Mul # Dividing removes all the terms in combo and all([sym not in combo.free_symbols for sym in (x/combo).free_symbols]) # And all theta/n terms in x are also in # combo and all([sym in combo.free_symbols for sym in x.free_symbols if sym in all_combos]), lambda x: -combo if str(x)[0] == "-" else combo) return H_class def zero_start_edges(edges): """ Helper function to convert a list of edges from 1 indexing to 0 indexing of the nodes Args: edges (list of tuples of ints): a list of edge connections for the desired circuit (i.e., [(0,1),(1,2),(2,3),(3,0)]) Returns: list[tuple[int]]: edges modified to have zero as the lowest index """ min_node = min([min(edge) for edge in edges]) if min_node > 0: edges = [(edge[0] - min_node, edge[1] - min_node) for edge in edges] return edges def set_enum_params(char_to_combo = {'0': ('C',), '1': ('J',), '2': ('L',), '3': ('C', 'L'), '4': ('J', 'L'), '5': ('C', 'J'), '6': ('C', 'J', 'L')}, filter = None): if filter is None: filter = jj_present ENUM_PARAMS["filter"] = filter ENUM_PARAMS["CHAR_TO_COMBINATION"] = char_to_combo ENUM_PARAMS["COMBINATION_TO_CHAR"] = {} ENUM_PARAMS["EDGE_COLOR_DICT"] = {} ENUM_PARAMS["CHAR_LIST"] = [] for i, c in enumerate(ENUM_PARAMS["CHAR_TO_COMBINATION"].keys()): ENUM_PARAMS["CHAR_LIST"].append(c) ENUM_PARAMS["COMBINATION_TO_CHAR"][ENUM_PARAMS["CHAR_TO_COMBINATION"][c]] = c ENUM_PARAMS["EDGE_COLOR_DICT"][ENUM_PARAMS["CHAR_TO_COMBINATION"][c]] = i # set parameters if len(ENUM_PARAMS) == 0: set_enum_params()