Source code for qmla.model_building_utilities

from __future__ import print_function

import numpy as np
import copy
import pandas as pd

import qmla.logging

__all__ = [
    "core_operator_dict",
    "get_num_qubits",
    "get_constituent_names_from_name",
    "alph",
    "unique_model_pair_identifier",
]

##########
# Section: Core operators as arrays
##########

core_operator_dict = {
    "i": np.array([[1 + 0.0j, 0 + 0.0j], [0 + 0.0j, 1 + 0.0j]]),  # Identity
    "x": np.array([[0 + 0.0j, 1 + 0.0j], [1 + 0.0j, 0 + 0.0j]]),  # Pauli-X
    "y": np.array([[0 + 0.0j, 0 - 1.0j], [0 + 1.0j, 0 + 0.0j]]),  # Pauli-Y
    "z": np.array([[1 + 0.0j, 0 + 0.0j], [0 + 0.0j, -1 + 0.0j]]),  # Pauli-Z
    "a": np.array([[0 + 0.0j, 1 + 0.0j], [0 + 0.0j, 0 + 0.0j]]),  # Add
    "s": np.array([[0 + 0.0j, 0 + 0.0j], [1 + 0.0j, 0 + 0.0j]]),  # Subtract
    "b": np.array([[1 + 0.0j, 0 + 0.0j], [0 + 0.0j, 0 + 0.0j]]),  # Subtract
    "d": np.array([[0 + 0.0j, 0 + 0.0j], [0 + 0.0j, 1 + 0.0j]]),  # Subtract
}

##########
# Section: functions for constructing models.
# compte methods are called recursively on names to
# construct matrices corresponding to input model names
##########


def compute_t(inp):
    """
    Assuming largest instance of action on inp is tensor product, T.
    Parse string.
    Recursively call compute() function.
    Tensor product resulting lists.
    Return operator which is specified by inp.
    """
    max_t, t_str = find_max_letter(inp, "T")
    max_p, p_str = find_max_letter(inp, "P")

    if max_p == 0 and max_t == 0:
        pauli_symbol = inp
        return core_operator_dict[pauli_symbol]

    elif max_t == 0:
        return compute(inp)
    else:
        to_tens = inp.split(t_str)
        running_tens_prod = compute(to_tens[0])
        for i in range(1, len(to_tens)):
            max_p, p_str = find_max_letter(to_tens[i], "P")
            max_t, t_str = find_max_letter(to_tens[i], "T")
            rhs = compute(to_tens[i])
            running_tens_prod = np.kron(running_tens_prod, rhs)
        return running_tens_prod


def compute_p(inp):
    """
    Assuming largest instance of action on inp is addition, P.
    Parse string.
    Recursively call compute() function.
    Sum resulting lists.
    Return operator which is specified by inp.
    """
    max_p, p_str = find_max_letter(inp, "P")
    max_t, t_str = find_max_letter(inp, "T")

    if "+" in inp:
        p_str = "+"
    elif max_p == 0 and max_t == 0:
        pauli_symbol = inp
        return core_operator_dict[pauli_symbol]
    elif max_p == 0:
        return compute(inp)
    to_add = inp.split(p_str)
    running_sum = empty_array_of_same_dim(to_add[0])
    for i in range(len(to_add)):
        max_p, p_str = find_max_letter(to_add[i], "P")
        max_t, t_str = find_max_letter(to_add[i], "T")
        rhs = compute(to_add[i])
        running_sum += rhs

    return running_sum


def compute_m(inp):
    """
    Assuming largest instance of action on inp is multiplication, M.
    Parse string.
    Recursively call compute() function.
    Multiple resulting lists.
    Return operator which is specified by inp.
    """

    max_m, m_str = find_max_letter(inp, "M")
    max_p, p_str = find_max_letter(inp, "P")
    max_t, t_str = find_max_letter(inp, "T")

    if max_m == 0 and max_t == 0 and max_p == 0:
        pauli_symbol = inp
        return core_operator_dict[pauli_symbol]

    elif max_m == 0:
        return compute(inp)

    else:
        to_mult = inp.split(m_str)
        # print("To mult : ", to_mult)
        t_str = ""
        while inp.count(t_str + "T") > 0:
            t_str = t_str + "T"

        num_qubits = len(t_str) + 1
        dim = 2 ** num_qubits

        running_product = np.eye(dim)

        for i in range(len(to_mult)):
            running_product = np.dot(running_product, compute(to_mult[i]))

        return running_product


def compute(inp):
    """
    Parse string.
    Recursively call compute() functions (compute_t, compute_p, compute_m).
    Tensor product, multiply or sum resulting lists.
    Return operator which is specified by inp.
    """
    from qmla.process_string_to_matrix import process_basic_operator

    max_p, p_str = find_max_letter(inp, "P")
    max_t, t_str = find_max_letter(inp, "T")
    max_m, m_str = find_max_letter(inp, "M")

    if "+" in inp:
        return compute_p(inp)
    if max_m == 0 and max_t == 0 and max_p == 0:
        basic_operator = inp
        # call subroutine which can interpret a "basic operator"
        # basic operators are defined with the function
        # they are terms which can not be separated further by P,M,T or +
        return process_basic_operator(basic_operator)
    elif max_m > max_t:
        return compute_m(inp)
    elif max_t >= max_p:
        return compute_t(inp)
    else:
        return compute_p(inp)


##########
# Section: functions for dissecting models
##########


[docs]def alph(name): r""" Alphabetise the model name. If name newer follows convention where terms are separated by +, simply separate them. If name follows older convention, analyse to separate terms and then alphabetise them. Parse string and recursively call alph function to alphabetise substrings. :param str name: name of model to alphabetise """ if "+" in name: separate_terms = name.split("+") alphabetised = "+".join(sorted(separate_terms)) return alphabetised t_max, t_str = find_max_letter(name, "T") p_max, p_str = find_max_letter(name, "P") m_max, m_str = find_max_letter(name, "M") if p_max == 0 and t_max == 0 and p_max == 0: return name if p_max > t_max and p_max > m_max: ltr = "P" string = p_str elif t_max >= p_max: string = t_str ltr = "T" elif m_max >= p_max: string = m_str ltr = "M" elif t_max > m_max: string = t_str ltr = "T" else: ltr = "M" string = m_str spread = name.split(string) if p_max == m_max and p_max > t_max: string = p_str list_elements = name.split(p_str) for i in range(len(list_elements)): list_elements[i] = alph(list_elements[i]) sorted_list = sorted(list_elements) linked_sorted_list = p_str.join(sorted_list) return linked_sorted_list if ltr == "P" and p_max == 1: sorted_spread = sorted(spread) out = string.join(sorted_spread) return out elif ltr == "P" and p_max > 1: list_elements = name.split(string) sorted_list = sorted(list_elements) for i in range(len(sorted_list)): sorted_list[i] = alph(sorted_list[i]) linked_sorted_list = string.join(sorted_list) return linked_sorted_list else: for i in range(len(spread)): spread[i] = alph(spread[i]) out = string.join(spread) return out
[docs]def get_num_qubits(name): r""" Parse string and determine number of qubits this operator acts on. Default convention is to use a naming mechanism specified by :func:`~qmla.string_processing_functions`. In all such constructions, the final element of each term is `dN`, so we can extract the number of qubits N. If using old convention where terms are tensor-producted by T, TT, TTT... , we find the largest T string instance, from which we deduce the number of qubits. - xTx = pauli_x TENSOR_PRODUCT pauli_x --> 2 qubits - yTyTTy = pauliy_y TENSOR_PRODUCT pauli_y TENSOR_PRODUCT pauli_y --> N=3 i.e. the largest tensor product of of length is N-1. :param str name: name of model """ individual_terms = get_constituent_names_from_name(name) for term in individual_terms: if ( term[0:1] == "h_" or "1Dising" in term or "Heis" in term or "nv" in term or "pauliSet" in term or "transverse" in term or "FH" in term or "pauliLikewise" in term ): terms = term.split("_") dim_term = terms[-1] dim = int(dim_term[1:]) num_qubits = dim return num_qubits max_t_found = 0 t_str = "" while name.count(t_str + "T") > 0: t_str = t_str + "T" num_qubits = len(t_str) + 1 return num_qubits
[docs]def get_constituent_names_from_name(name): r""" Separate into separate terms in model name. e.g. 'pauliSet_1_x_d2+pauliSet_1_y_d2' -> ['pauliSet_1_x_d2', 'pauliSet_1_y_d2'] :param str name: name of model """ return name.split("+")
def empty_array_of_same_dim(name): """ Parse name to find size of system it acts on. Produce an empty matrix of that dimension and return it. """ num_qubits = get_num_qubits(name) dim = 2 ** num_qubits empty_mtx = np.zeros([dim, dim], dtype=np.complex128) return empty_mtx def find_max_letter(string, letter): r""" Find largest instance of consecutive given 'letter'. Return largest instance and length of that instance. """ letter_str = "" while string.count(letter_str + letter) > 0: letter_str = letter_str + letter return len(letter_str), letter_str def ideal_probe(name): """ Returns a probe state which is the normalised sum of the given operator's eigenvectors, ideal for probing that operator. """ mtx = BaseModel(name).matrix eigvalues = np.linalg.eig(mtx)[1] summed_eigvals = np.sum(eigvalues, axis=0) normalised_probe = summed_eigvals / np.linalg.norm(summed_eigvals) return normalised_probe def get_eigenvectors(name): r""" Get eigenvectors of a model from its name. """ mtx = BaseModel(name).matrix eigvectors = np.linalg.eig(mtx)[0] return eigvectors def unique_model_pair_identifier(model_a_id, model_b_id): r""" Pair uniquely coupling to model ids, for consistency. Formatted as 'low_id,high_id', """ a = int(float(model_a_id)) b = int(float(model_b_id)) std = sorted([a, b]) id_str = "" for i in range(len(std)): id_str += str(std[i]) if i != len(std) - 1: id_str += "," return id_str ########## # Section: deprecated functions, to be removed when safe to do so ########## def verbose_naming_mechanism_separate_terms(name): r""" Separate terms of a model name according to old "verbose" naming scheme. """ t_str, p_str, max_t, max_p = get_t_p_strings(name) if max_t >= max_p: # if more T's than P's in name, # it has only one constituent. return [name] else: # More P's indicates a sum at the highest dimension. return name.split(p_str) def get_t_p_strings(name): r""" Find largest instance of consecutive P's and T's. Return those instances and lengths of those instances. """ t_str = "" p_str = "" while name.count(t_str + "T") > 0: t_str = t_str + "T" while name.count(p_str + "P") > 0: p_str = p_str + "P" max_t = len(t_str) max_p = len(p_str) return t_str, p_str, max_t, max_p