Example #1
0
def _inner_net_flow_constraint_hamiltonian(graph: nx.DiGraph,
                                           node) -> Hamiltonian:
    r"""Calculates the squared inner portion of the Hamiltonian in :func:`net_flow_constraint`.


    For a given :math:`i`, this function returns:

    .. math::

        \left((d_{i}^{\rm out} - d_{i}^{\rm in})\mathbb{I} -
        \sum_{j, (i, j) \in E} Z_{ij} + \sum_{j, (j, i) \in E} Z_{ji} \right)^{2}.

    Args:
        graph (nx.DiGraph): the directed graph specifying possible edges
        node: a fixed node

    Returns:
        qml.Hamiltonian: The inner part of the net-flow constraint Hamiltonian.
    """
    edges_to_qubits = edges_to_wires(graph)

    coeffs = []
    ops = []

    out_edges = graph.out_edges(node)
    in_edges = graph.in_edges(node)

    coeffs.append(len(out_edges) - len(in_edges))
    ops.append(qml.Identity(0))

    for edge in out_edges:
        wires = (edges_to_qubits[edge], )
        coeffs.append(-1)
        ops.append(qml.PauliZ(wires))

    for edge in in_edges:
        wires = (edges_to_qubits[edge], )
        coeffs.append(1)
        ops.append(qml.PauliZ(wires))

    coeffs, ops = _square_hamiltonian_terms(coeffs, ops)
    H = Hamiltonian(coeffs, ops)
    H.simplify()
    # store the valuable information that all observables are in one commuting group
    H.grouping_indices = [list(range(len(H.ops)))]
    return H
Example #2
0
def _inner_out_flow_constraint_hamiltonian(graph: nx.DiGraph,
                                           node) -> Hamiltonian:
    r"""Calculates the inner portion of the Hamiltonian in :func:`out_flow_constraint`.
    For a given :math:`i`, this function returns:

    .. math::

        d_{i}^{out}(d_{i}^{out} - 2)\mathbb{I}
        - 2(d_{i}^{out}-1)\sum_{j,(i,j)\in E}\hat{Z}_{ij} +
        ( \sum_{j,(i,j)\in E}\hat{Z}_{ij} )^{2}

    Args:
        graph (nx.DiGraph): the directed graph specifying possible edges
        node: a fixed node

    Returns:
        qml.Hamiltonian: The inner part of the out-flow constraint Hamiltonian.
    """
    coeffs = []
    ops = []

    edges_to_qubits = edges_to_wires(graph)
    out_edges = graph.out_edges(node)
    d = len(out_edges)

    for edge in out_edges:
        wire = (edges_to_qubits[edge], )
        coeffs.append(1)
        ops.append(qml.PauliZ(wire))

    coeffs, ops = _square_hamiltonian_terms(coeffs, ops)

    for edge in out_edges:
        wire = (edges_to_qubits[edge], )
        coeffs.append(-2 * (d - 1))
        ops.append(qml.PauliZ(wire))

    coeffs.append(d * (d - 2))
    ops.append(qml.Identity(0))

    H = Hamiltonian(coeffs, ops)
    H.simplify()
    # store the valuable information that all observables are in one commuting group
    H.grouping_indices = [list(range(len(H.ops)))]

    return H
Example #3
0
def loss_hamiltonian(graph: nx.Graph) -> Hamiltonian:
    r"""Calculates the loss Hamiltonian for the maximum-weighted cycle problem.

    We consider the problem of selecting a cycle from a graph that has the greatest product of edge
    weights, as outlined `here <https://1qbit.com/whitepaper/arbitrage/>`__. The product of weights
    of a subset of edges in a graph is given by

    .. math:: P = \prod_{(i, j) \in E} [(c_{ij} - 1)x_{ij} + 1]

    where :math:`E` are the edges of the graph, :math:`x_{ij}` is a binary number that selects
    whether to include the edge :math:`(i, j)` and :math:`c_{ij}` is the corresponding edge weight.
    Our objective is to maximimize :math:`P`, subject to selecting the :math:`x_{ij}` so that
    our subset of edges composes a cycle.

    The product of edge weights is maximized by equivalently considering

    .. math:: \sum_{(i, j) \in E} x_{ij}\log c_{ij},

    assuming :math:`c_{ij} > 0`.

    This can be restated as a minimization of the expectation value of the following qubit
    Hamiltonian:

    .. math::

        H = \sum_{(i, j) \in E} Z_{ij}\log c_{ij}.

    where :math:`Z_{ij}` is a qubit Pauli-Z matrix acting upon the wire specified by the edge
    :math:`(i, j)`. Mapping from edges to wires can be achieved using :func:`~.edges_to_wires`.

    .. note::
        The expectation value of the returned Hamiltonian :math:`H` is not equal to :math:`P`, but
        minimizing the expectation value of :math:`H` is equivalent to maximizing :math:`P`.

        Also note that the returned Hamiltonian does not impose that the selected set of edges is
        a cycle. This constraint can be enforced using a penalty term or by selecting a QAOA
        mixer Hamiltonian that only transitions between states that correspond to cycles.

    **Example**

    >>> import networkx as nx
    >>> g = nx.complete_graph(3).to_directed()
    >>> edge_weight_data = {edge: (i + 1) * 0.5 for i, edge in enumerate(g.edges)}
    >>> for k, v in edge_weight_data.items():
            g[k[0]][k[1]]["weight"] = v
    >>> h = loss_hamiltonian(g)
    >>> print(h)
      (-0.6931471805599453) [Z0]
    + (0.0) [Z1]
    + (0.4054651081081644) [Z2]
    + (0.6931471805599453) [Z3]
    + (0.9162907318741551) [Z4]
    + (1.0986122886681098) [Z5]

    Args:
        graph (nx.Graph): the graph specifying possible edges

    Returns:
        qml.Hamiltonian: the loss Hamiltonian

    Raises:
        ValueError: if the graph contains self-loops
        KeyError: if one or more edges do not contain weight data
    """
    edges_to_qubits = edges_to_wires(graph)
    coeffs = []
    ops = []

    edges_data = graph.edges(data=True)

    for edge_data in edges_data:
        edge = edge_data[:2]

        if edge[0] == edge[1]:
            raise ValueError("Graph contains self-loops")

        try:
            weight = edge_data[2]["weight"]
        except KeyError as e:
            raise KeyError(f"Edge {edge} does not contain weight data") from e

        coeffs.append(np.log(weight))
        ops.append(qml.PauliZ(wires=edges_to_qubits[edge]))

    H = Hamiltonian(coeffs, ops)
    # store the valuable information that all observables are in one commuting group
    H.grouping_indices = [list(range(len(H.ops)))]

    return H
Example #4
0
def _inner_net_flow_constraint_hamiltonian(graph: Union[nx.DiGraph,
                                                        rx.PyDiGraph],
                                           node: int) -> Hamiltonian:
    r"""Calculates the squared inner portion of the Hamiltonian in :func:`net_flow_constraint`.


    For a given :math:`i`, this function returns:

    .. math::

        \left((d_{i}^{\rm out} - d_{i}^{\rm in})\mathbb{I} -
        \sum_{j, (i, j) \in E} Z_{ij} + \sum_{j, (j, i) \in E} Z_{ji} \right)^{2}.

    Args:
        graph (nx.DiGraph or rx.PyDiGraph): the directed graph specifying possible edges
        node: a fixed node

    Returns:
        qml.Hamiltonian: The inner part of the net-flow constraint Hamiltonian.
    """
    if not isinstance(graph, (nx.DiGraph, rx.PyDiGraph)):
        raise ValueError(
            f"Input graph must be a nx.DiGraph or rx.PyDiGraph, got {type(graph).__name__}"
        )

    edges_to_qubits = edges_to_wires(graph)

    coeffs = []
    ops = []

    is_rx = isinstance(graph, rx.PyDiGraph)

    out_edges = graph.out_edges(node)
    in_edges = graph.in_edges(node)

    # To ensure out_edges and in_edges methods in both RX and NX return
    # the lists of edges in the same order, we sort results.
    if is_rx:
        out_edges = sorted(out_edges)
        in_edges = sorted(in_edges)

    # In RX each node is assigned to an integer index starting from 0;
    # thus, we use the following lambda function to get node-values.
    get_nvalues = lambda T: (graph.nodes().index(T[0]), graph.nodes().index(T[
        1])) if is_rx else T

    coeffs.append(len(out_edges) - len(in_edges))
    ops.append(qml.Identity(0))

    for edge in out_edges:
        if len(edge) > 2:
            edge = tuple(edge[:2])
        wires = (edges_to_qubits[get_nvalues(edge)], )
        coeffs.append(-1)
        ops.append(qml.PauliZ(wires))

    for edge in in_edges:
        if len(edge) > 2:
            edge = tuple(edge[:2])
        wires = (edges_to_qubits[get_nvalues(edge)], )
        coeffs.append(1)
        ops.append(qml.PauliZ(wires))

    coeffs, ops = _square_hamiltonian_terms(coeffs, ops)
    H = Hamiltonian(coeffs, ops)
    H.simplify()
    # store the valuable information that all observables are in one commuting group
    H.grouping_indices = [list(range(len(H.ops)))]
    return H