<reponame>Siddharthgolecha/UDMIS
from inspect import CO_VARKEYWORDS
import sys
import pennylane as qml
from pennylane import numpy as np


def hamiltonian_coeffs_and_obs(graph):
    """Creates an ordered list of coefficients and observables used to construct
    the UDMIS Hamiltonian.

    Args:
        - graph (list((float, float))): A list of x,y coordinates. e.g. graph = [(1.0, 1.1), (4.5, 3.1)]

    Returns:
        - coeffs (list): List of coefficients for elementary parts of the UDMIS Hamiltonian
        - obs (list(qml.ops)): List of qml.ops
    """

    num_vertices = len(graph)
    E, num_edges = edges(graph)
    u = 1.35
    obs = []
    coeffs = []

    global COORDS

    COORDS = []
    for i in range(num_vertices):
        coeffs.append(-1)
    for i  in range(num_edges):
        coeffs.append(u)
    Z = np.array([[1,0],[0,-1]])
    I = np.array([[1,0],[0,1]])

    for i in range(num_vertices):
        obs.append(qml.Hermitian((Z+I)/2,wires=i))

    for i in range(len(E)):
        for j in range(len(E[i])):
            if E[i][j]:
                COORDS.append(i)
                obs.append(qml.Hermitian((Z+I)/2,wires=i)@qml.Hermitian((Z+I)/2,wires=j))

    return coeffs, obs


def edges(graph):
    """Creates a matrix of bools that are interpreted as the existence/non-existence (True/False)
    of edges between vertices (i,j).

    Args:
        - graph (list((float, float))): A list of x,y coordinates. e.g. graph = [(1.0, 1.1), (4.5, 3.1)]

    Returns:
        - num_edges (int): The total number of edges in the graph
        - E (np.ndarray): A Matrix of edges
    """

    num_vertices = len(graph)
    E = np.zeros((num_vertices, num_vertices), dtype=bool)
    for vertex_i in range(num_vertices - 1):
        xi, yi = graph[vertex_i]  # coordinates

        for vertex_j in range(vertex_i + 1, num_vertices):
            xj, yj = graph[vertex_j]  # coordinates
            dij = np.sqrt((xi - xj) ** 2 + (yi - yj) ** 2)
            E[vertex_i, vertex_j] = 1 if dij <= 1.0 else 0

    return E, np.sum(E, axis=(0, 1))


def variational_circuit(params, num_vertices):
    """A variational circuit.

    Args:
        - params (np.ndarray): your variational parameters
        - num_vertices (int): The number of vertices in the graph. Also used for number of wires.
    """
    for i in range(len(COORDS)):
        qml.RY(params[i],wires=COORDS[i])


def train_circuit(num_vertices, H):
    """Trains a quantum circuit to learn the ground state of the UDMIS Hamiltonian.

    Args:
        - num_vertices (int): The number of vertices/wires in the graph
        - H (qml.Hamiltonian): The result of qml.Hamiltonian(coeffs, obs)

    Returns:
        - E / num_vertices (float): The ground state energy density.
    """

    dev = qml.device("default.qubit", wires=num_vertices)

    @qml.qnode(dev)
    def cost(params):
        """The energy expectation value of a Hamiltonian"""
        variational_circuit(params, num_vertices)
        return qml.expval(H)

    epochs = 100
    params = np.array([np.random.random()*np.pi for i in range(len(COORDS))],
                        requires_grad=True)
    opt = qml.AdamOptimizer(stepsize=0.8)
    E = 0
    for i in range(epochs):
        params, E = opt.step_and_cost(cost, params)

    return E / float(num_vertices)


if __name__ == "__main__":

    inputs = np.array(sys.stdin.read().split(","), dtype=float, requires_grad=False)
    num_vertices = int(len(inputs) / 2)
    x = inputs[:num_vertices]
    y = inputs[num_vertices:]
    graph = []
    for n in range(num_vertices):
        graph.append((x[n].item(), y[n].item()))

    coeffs, obs = hamiltonian_coeffs_and_obs(graph)
    H = qml.Hamiltonian(coeffs, obs)
    energy_density = train_circuit(num_vertices, H)
    print(f"{energy_density:.6f}")

from inspect import CO_VARKEYWORDS
import sys
import pennylane as qml
from pennylane import numpy as np


def hamiltonian_coeffs_and_obs(graph):
    """Creates an ordered list of coefficients and observables used to construct
    the UDMIS Hamiltonian.

    Args:
        - graph (list((float, float))): A list of x,y coordinates. e.g. graph = [(1.0, 1.1), (4.5, 3.1)]

    Returns:
        - coeffs (list): List of coefficients for elementary parts of the UDMIS Hamiltonian
        - obs (list(qml.ops)): List of qml.ops
    """

    num_vertices = len(graph)
    E, num_edges = edges(graph)
    u = 1.35
    obs = []
    coeffs = []

    global COORDS

    COORDS = []
    for i in range(num_vertices):
        coeffs.append(-1)
    for i  in range(num_edges):
        coeffs.append(u)
    Z = np.array([[1,0],[0,-1]])
    I = np.array([[1,0],[0,1]])

    for i in range(num_vertices):
        obs.append(qml.Hermitian((Z+I)/2,wires=i))

    for i in range(len(E)):
        for j in range(len(E[i])):
            if E[i][j]:
                COORDS.append(i)
                obs.append(qml.Hermitian((Z+I)/2,wires=i)@qml.Hermitian((Z+I)/2,wires=j))

    return coeffs, obs


def edges(graph):
    """Creates a matrix of bools that are interpreted as the existence/non-existence (True/False)
    of edges between vertices (i,j).

    Args:
        - graph (list((float, float))): A list of x,y coordinates. e.g. graph = [(1.0, 1.1), (4.5, 3.1)]

    Returns:
        - num_edges (int): The total number of edges in the graph
        - E (np.ndarray): A Matrix of edges
    """

    num_vertices = len(graph)
    E = np.zeros((num_vertices, num_vertices), dtype=bool)
    for vertex_i in range(num_vertices - 1):
        xi, yi = graph[vertex_i]  # coordinates

        for vertex_j in range(vertex_i + 1, num_vertices):
            xj, yj = graph[vertex_j]  # coordinates
            dij = np.sqrt((xi - xj) ** 2 + (yi - yj) ** 2)
            E[vertex_i, vertex_j] = 1 if dij <= 1.0 else 0

    return E, np.sum(E, axis=(0, 1))


def variational_circuit(params, num_vertices):
    """A variational circuit.

    Args:
        - params (np.ndarray): your variational parameters
        - num_vertices (int): The number of vertices in the graph. Also used for number of wires.
    """
    for i in range(len(COORDS)):
        qml.RY(params[i],wires=COORDS[i])


def train_circuit(num_vertices, H):
    """Trains a quantum circuit to learn the ground state of the UDMIS Hamiltonian.

    Args:
        - num_vertices (int): The number of vertices/wires in the graph
        - H (qml.Hamiltonian): The result of qml.Hamiltonian(coeffs, obs)

    Returns:
        - E / num_vertices (float): The ground state energy density.
    """

    dev = qml.device("default.qubit", wires=num_vertices)

    @qml.qnode(dev)
    def cost(params):
        """The energy expectation value of a Hamiltonian"""
        variational_circuit(params, num_vertices)
        return qml.expval(H)

    epochs = 100
    params = np.array([np.random.random()*np.pi for i in range(len(COORDS))],
                        requires_grad=True)
    opt = qml.AdamOptimizer(stepsize=0.8)
    E = 0
    for i in range(epochs):
        params, E = opt.step_and_cost(cost, params)

    return E / float(num_vertices)


if __name__ == "__main__":

    inputs = np.array(sys.stdin.read().split(","), dtype=float, requires_grad=False)
    num_vertices = int(len(inputs) / 2)
    x = inputs[:num_vertices]
    y = inputs[num_vertices:]
    graph = []
    for n in range(num_vertices):
        graph.append((x[n].item(), y[n].item()))

    coeffs, obs = hamiltonian_coeffs_and_obs(graph)
    H = qml.Hamiltonian(coeffs, obs)
    energy_density = train_circuit(num_vertices, H)
    print(f"{energy_density:.6f}")
Creates an ordered list of coefficients and observables used to construct     the UDMIS Hamiltonian.      Args:         - graph (list((float, float))): A list of x,y coordinates. e.g. graph = [(1.0, 1.1), (4.5, 3.1)]      Returns:         - coeffs (list): List of coefficients for elementary parts of the UDMIS Hamiltonian         - obs (list(qml.ops)): List of qml.ops Creates a matrix of bools that are interpreted as the existence/non-existence (True/False)     of edges between vertices (i,j).      Args:         - graph (list((float, float))): A list of x,y coordinates. e.g. graph = [(1.0, 1.1), (4.5, 3.1)]      Returns:         - num_edges (int): The total number of edges in the graph         - E (np.ndarray): A Matrix of edges # coordinates # coordinates A variational circuit.      Args:         - params (np.ndarray): your variational parameters         - num_vertices (int): The number of vertices in the graph. Also used for number of wires. Trains a quantum circuit to learn the ground state of the UDMIS Hamiltonian.      Args:         - num_vertices (int): The number of vertices/wires in the graph         - H (qml.Hamiltonian): The result of qml.Hamiltonian(coeffs, obs)      Returns:         - E / num_vertices (float): The ground state energy density. The energy expectation value of a Hamiltonian