From 656de45bc7ba44c69434845b2f269212f3b07a46 Mon Sep 17 00:00:00 2001 From: anthayes92 Date: Thu, 6 May 2021 19:07:45 +0100 Subject: [PATCH 01/25] add rough outline of max_weight_cycle helper function --- pennylane/qaoa/cost.py | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index be98a140d80..c099eacbb8b 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -471,3 +471,46 @@ def max_clique(graph, constrained=True): mixer_h = qaoa.x_mixer(graph.nodes) return (cost_h, mixer_h) + + +def max_weight_cycle(graph: nx.DiGraph, constrained=True) -> Tuple[qml.Hamiltonian, qml.Hamiltonian, dict]: + r"""Returns the QAOA cost Hamiltonian and the recommended mixer corresponding to the maximum weighted cycle + problem, for a given graph. + + Args: + graph (nx.Graph): the graph on which the Hamiltonians are defined + constrained (bool): specifies the variant of QAOA that is performed (constrained or unconstrained) + + Returns: + (.Hamiltonian, .Hamiltonian): The cost and mixer Hamiltonians + + .. UsageDetails:: + + There are two variations of QAOA for this problem, constrained and unconstrained: + + **Constrained** + + The maximum weighted cycle cost Hamiltonian for constrained QAOA is defined as: + + .. math:: H_C \ = H_loss + H_netflow + H_outflow + + The returned mixer Hamiltonian is :func:`~qaoa.x_mixer` applied to all wires. + + **Unconstrained** + + The maximum weighted cycle cost Hamiltonian for unconstrained QAOA is defined as: + + .. math:: H_C = H_loss + + The returned mixer Hamiltonian is :func:`~qaoa.cycle.cycle_mixer` applied to the given graph + + """ + + if not isinstance(graph, nx.Graph): + raise ValueError("Input graph must be a nx.Graph, got {}".format(type(graph).__name__)) + + if constrained: + return (qaoa.cycle.loss_hamiltonian(graph), qaoa.cycle.cycle_mixer(graph)) + + cost_h = qaoa.cycle.loss_hamiltonian(graph) + 3*(net_flow_constraint(graph) + out_flow_constraint(graph)) + mixer = qaoa.x_mixer(graph.nodes) From de7fff899e05c948bc89c2e912789460e67de6b0 Mon Sep 17 00:00:00 2001 From: anthayes92 Date: Thu, 6 May 2021 19:15:17 +0100 Subject: [PATCH 02/25] add return statement --- pennylane/qaoa/cost.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index c099eacbb8b..586831d07f5 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -513,4 +513,6 @@ def max_weight_cycle(graph: nx.DiGraph, constrained=True) -> Tuple[qml.Hamiltoni return (qaoa.cycle.loss_hamiltonian(graph), qaoa.cycle.cycle_mixer(graph)) cost_h = qaoa.cycle.loss_hamiltonian(graph) + 3*(net_flow_constraint(graph) + out_flow_constraint(graph)) - mixer = qaoa.x_mixer(graph.nodes) + mixer_h = qaoa.x_mixer(graph.nodes) + + return (cost_h, mixer_h) From 166207e7e9c2b074bc29e94cae222ad0353d6b9e Mon Sep 17 00:00:00 2001 From: anthayes92 Date: Fri, 7 May 2021 18:52:54 +0100 Subject: [PATCH 03/25] add unit test for helper function --- pennylane/qaoa/cost.py | 4 +- tests/test_qaoa.py | 156 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 2 deletions(-) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index 586831d07f5..018b075f06a 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -473,7 +473,7 @@ def max_clique(graph, constrained=True): return (cost_h, mixer_h) -def max_weight_cycle(graph: nx.DiGraph, constrained=True) -> Tuple[qml.Hamiltonian, qml.Hamiltonian, dict]: +def max_weight_cycle(graph, constrained=True): r"""Returns the QAOA cost Hamiltonian and the recommended mixer corresponding to the maximum weighted cycle problem, for a given graph. @@ -512,7 +512,7 @@ def max_weight_cycle(graph: nx.DiGraph, constrained=True) -> Tuple[qml.Hamiltoni if constrained: return (qaoa.cycle.loss_hamiltonian(graph), qaoa.cycle.cycle_mixer(graph)) - cost_h = qaoa.cycle.loss_hamiltonian(graph) + 3*(net_flow_constraint(graph) + out_flow_constraint(graph)) + cost_h = qaoa.cycle.loss_hamiltonian(graph) + 3*(qaoa.cycle.net_flow_constraint(graph) + qaoa.cycle.out_flow_constraint(graph)) mixer_h = qaoa.x_mixer(graph.nodes) return (cost_h, mixer_h) diff --git a/tests/test_qaoa.py b/tests/test_qaoa.py index 09428e4a455..ddedbc7c621 100644 --- a/tests/test_qaoa.py +++ b/tests/test_qaoa.py @@ -46,6 +46,11 @@ non_consecutive_graph = Graph([(0, 4), (3, 4), (2, 1), (2, 0)]) +digraph_complete = nx.complete_graph(3).to_directed() +complete_edge_weight_data = {edge: (i + 1) * 0.5 for i, edge in enumerate(digraph_complete.edges)} +for k, v in complete_edge_weight_data.items(): + digraph_complete[k[0]][k[1]]["weight"] = v + def decompose_hamiltonian(hamiltonian): @@ -471,6 +476,148 @@ def test_bit_flip_mixer_output(self, graph, n, target_hamiltonian): EDGE_DRIVER = zip(GRAPHS, REWARDS, HAMILTONIANS) +"""GENERATES THE CASES TO TEST THE MAXIMUM WEIGHTED CYCLE PROBLEM""" + +DIGRAPHS = [digraph_complete] + +MWS_CONSTRAINED = [True, False] + +COST_COEFFS = [ + [ + -0.6931471805599453, + 0.0, + 0.4054651081081644, + 0.6931471805599453, + 0.9162907318741551, + 1.0986122886681098, + ], + [ + -6.693147180559945, + -6.0, + -5.594534891891835, + -5.306852819440055, + -5.083709268125845, + -4.90138771133189, + 54, + 12, + -12, + -6, + -6, + -12, + 6, + 12, + -6, + -6, + -12, + 6, + 12, + -6, + -6, + 6, + ], +] + +COST_TERMS = [ + [ + qml.PauliZ(wires=[0]), + qml.PauliZ(wires=[1]), + qml.PauliZ(wires=[2]), + qml.PauliZ(wires=[3]), + qml.PauliZ(wires=[4]), + qml.PauliZ(wires=[5]), + ], + [ + qml.PauliZ(wires=[0]), + qml.PauliZ(wires=[1]), + qml.PauliZ(wires=[2]), + qml.PauliZ(wires=[3]), + qml.PauliZ(wires=[4]), + qml.PauliZ(wires=[5]), + qml.Identity(wires=[0]), + qml.PauliZ(wires=[0]) @ qml.PauliZ(wires=[1]), + qml.PauliZ(wires=[0]) @ qml.PauliZ(wires=[2]), + qml.PauliZ(wires=[0]) @ qml.PauliZ(wires=[4]), + qml.PauliZ(wires=[1]) @ qml.PauliZ(wires=[2]), + qml.PauliZ(wires=[1]) @ qml.PauliZ(wires=[4]), + qml.PauliZ(wires=[2]) @ qml.PauliZ(wires=[4]), + qml.PauliZ(wires=[2]) @ qml.PauliZ(wires=[3]), + qml.PauliZ(wires=[2]) @ qml.PauliZ(wires=[5]), + qml.PauliZ(wires=[0]) @ qml.PauliZ(wires=[3]), + qml.PauliZ(wires=[3]) @ qml.PauliZ(wires=[5]), + qml.PauliZ(wires=[0]) @ qml.PauliZ(wires=[5]), + qml.PauliZ(wires=[4]) @ qml.PauliZ(wires=[5]), + qml.PauliZ(wires=[3]) @ qml.PauliZ(wires=[4]), + qml.PauliZ(wires=[1]) @ qml.PauliZ(wires=[5]), + qml.PauliZ(wires=[1]) @ qml.PauliZ(wires=[3]), + ], +] + +COST_HAMILTONIANS = [qml.Hamiltonian(COST_COEFFS[i], COST_TERMS[i]) for i in range(2)] + +MIXER_COEFFS = [ + [ + 0.25, + 0.25, + 0.25, + -0.25, + 0.25, + 0.25, + 0.25, + -0.25, + 0.25, + 0.25, + 0.25, + -0.25, + 0.25, + 0.25, + 0.25, + -0.25, + 0.25, + 0.25, + 0.25, + -0.25, + 0.25, + 0.25, + 0.25, + -0.25, + ], + [1, 1, 1], +] + +MIXER_TERMS = [ + [ + qml.PauliX(wires=[0]) @ qml.PauliX(wires=[1]) @ qml.PauliX(wires=[5]), + qml.PauliY(wires=[0]) @ qml.PauliY(wires=[1]) @ qml.PauliX(wires=[5]), + qml.PauliY(wires=[0]) @ qml.PauliX(wires=[1]) @ qml.PauliY(wires=[5]), + qml.PauliX(wires=[0]) @ qml.PauliY(wires=[1]) @ qml.PauliY(wires=[5]), + qml.PauliX(wires=[1]) @ qml.PauliX(wires=[0]) @ qml.PauliX(wires=[3]), + qml.PauliY(wires=[1]) @ qml.PauliY(wires=[0]) @ qml.PauliX(wires=[3]), + qml.PauliY(wires=[1]) @ qml.PauliX(wires=[0]) @ qml.PauliY(wires=[3]), + qml.PauliX(wires=[1]) @ qml.PauliY(wires=[0]) @ qml.PauliY(wires=[3]), + qml.PauliX(wires=[2]) @ qml.PauliX(wires=[3]) @ qml.PauliX(wires=[4]), + qml.PauliY(wires=[2]) @ qml.PauliY(wires=[3]) @ qml.PauliX(wires=[4]), + qml.PauliY(wires=[2]) @ qml.PauliX(wires=[3]) @ qml.PauliY(wires=[4]), + qml.PauliX(wires=[2]) @ qml.PauliY(wires=[3]) @ qml.PauliY(wires=[4]), + qml.PauliX(wires=[3]) @ qml.PauliX(wires=[2]) @ qml.PauliX(wires=[1]), + qml.PauliY(wires=[3]) @ qml.PauliY(wires=[2]) @ qml.PauliX(wires=[1]), + qml.PauliY(wires=[3]) @ qml.PauliX(wires=[2]) @ qml.PauliY(wires=[1]), + qml.PauliX(wires=[3]) @ qml.PauliY(wires=[2]) @ qml.PauliY(wires=[1]), + qml.PauliX(wires=[4]) @ qml.PauliX(wires=[5]) @ qml.PauliX(wires=[2]), + qml.PauliY(wires=[4]) @ qml.PauliY(wires=[5]) @ qml.PauliX(wires=[2]), + qml.PauliY(wires=[4]) @ qml.PauliX(wires=[5]) @ qml.PauliY(wires=[2]), + qml.PauliX(wires=[4]) @ qml.PauliY(wires=[5]) @ qml.PauliY(wires=[2]), + qml.PauliX(wires=[5]) @ qml.PauliX(wires=[4]) @ qml.PauliX(wires=[0]), + qml.PauliY(wires=[5]) @ qml.PauliY(wires=[4]) @ qml.PauliX(wires=[0]), + qml.PauliY(wires=[5]) @ qml.PauliX(wires=[4]) @ qml.PauliY(wires=[0]), + qml.PauliX(wires=[5]) @ qml.PauliY(wires=[4]) @ qml.PauliY(wires=[0]), + ], + [qml.PauliX(wires=[0]), qml.PauliX(wires=[1]), qml.PauliX(wires=[2])], +] + +MIXER_HAMILTONIANS = [qml.Hamiltonian(MIXER_COEFFS[i], MIXER_TERMS[i]) for i in range(2)] + +MWC = list(zip(DIGRAPHS, MWS_CONSTRAINED, COST_HAMILTONIANS, MIXER_HAMILTONIANS)) + def decompose_hamiltonian(hamiltonian): @@ -578,6 +725,15 @@ def test_max_clique_output(self, graph, constrained, cost_hamiltonian, mixer_ham assert decompose_hamiltonian(cost_hamiltonian) == decompose_hamiltonian(cost_h) assert decompose_hamiltonian(mixer_hamiltonian) == decompose_hamiltonian(mixer_h) + @pytest.mark.parametrize(("graph", "constrained", "cost_hamiltonian", "mixer_hamiltonian"), MWC) + def test_max_weight_cycle_output(self, graph, constrained, cost_hamiltonian, mixer_hamiltonian): + """Tests that the output of the maximum weighted cycle method is correct""" + + cost_h, mixer_h = qaoa.max_weight_cycle(graph, constrained=constrained) + + assert decompose_hamiltonian(cost_hamiltonian) == decompose_hamiltonian(cost_h) + assert decompose_hamiltonian(mixer_hamiltonian) == decompose_hamiltonian(mixer_h) + class TestUtils: """Tests that the utility functions are working properly""" From 8b551ae30dfe0af00f898f557616a4bef8c41997 Mon Sep 17 00:00:00 2001 From: anthayes92 Date: Fri, 7 May 2021 18:55:30 +0100 Subject: [PATCH 04/25] apply black --- pennylane/qaoa/cost.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index 018b075f06a..b7075369819 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -512,7 +512,9 @@ def max_weight_cycle(graph, constrained=True): if constrained: return (qaoa.cycle.loss_hamiltonian(graph), qaoa.cycle.cycle_mixer(graph)) - cost_h = qaoa.cycle.loss_hamiltonian(graph) + 3*(qaoa.cycle.net_flow_constraint(graph) + qaoa.cycle.out_flow_constraint(graph)) + cost_h = qaoa.cycle.loss_hamiltonian(graph) + 3 * ( + qaoa.cycle.net_flow_constraint(graph) + qaoa.cycle.out_flow_constraint(graph) + ) mixer_h = qaoa.x_mixer(graph.nodes) return (cost_h, mixer_h) From a83ad7ce3efc119b6ee00cccb655a70b37511438 Mon Sep 17 00:00:00 2001 From: anthayes92 Date: Fri, 7 May 2021 19:08:28 +0100 Subject: [PATCH 05/25] add unit test for incorrect input type --- tests/test_qaoa.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_qaoa.py b/tests/test_qaoa.py index ddedbc7c621..b5337c2accb 100644 --- a/tests/test_qaoa.py +++ b/tests/test_qaoa.py @@ -673,6 +673,12 @@ def test_edge_driver_output(self, graph, reward, hamiltonian): """Tests the cost Hamiltonians""" + def test_max_weight_cycle_errors(self): + """Tests that the max weight cycle Hamiltonian throws the correct errors""" + + with pytest.raises(ValueError, match=r"Input graph must be a nx.Graph"): + qaoa.max_weight_cycle([(0, 1), (1, 2)]) + def test_cost_graph_error(self): """Tests that the cost Hamiltonians throw the correct error""" From 0bce78f35f3a7258fad21c9647dcc9cd58883472 Mon Sep 17 00:00:00 2001 From: anthayes92 Date: Mon, 10 May 2021 12:17:53 +0100 Subject: [PATCH 06/25] add unit test for combined constraints --- tests/test_qaoa.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/test_qaoa.py b/tests/test_qaoa.py index b5337c2accb..681da3b3cc2 100644 --- a/tests/test_qaoa.py +++ b/tests/test_qaoa.py @@ -1412,3 +1412,61 @@ def test_net_flow_constraint_undirected_raises_error(self): with pytest.raises(ValueError): h = net_flow_constraint(g) + + def test_net_flow_and_out_flow_constraint(self): + """Test the combined net-flow and out-flow constraint Hamiltonian is minimised by states that correspond to subgraphs + that qualify as simple_cycles + """ + g = nx.complete_graph(3).to_directed() + h = net_flow_constraint(g) + out_flow_constraint(g) + m = wires_to_edges(g) + wires = len(g.edges) + + # Find the energies corresponding to each possible bitstring + dev = qml.device("default.qubit", wires=wires) + + def states(basis_state, **kwargs): + qml.BasisState(basis_state, wires=range(wires)) + + cost = qml.ExpvalCost(states, h, dev, optimize=True) + + # Calculate the set of all bitstrings + bitstrings = itertools.product([0, 1], repeat=wires) + + # Calculate the corresponding energies + energies_bitstrings = ((cost(bitstring).numpy(), bitstring) for bitstring in bitstrings) + + def find_simple_cycle(list_of_edges): + """Returns True if list_of_edges contains a permutation corresponding to a simple cycle""" + permutations = list(itertools.permutations(list_of_edges)) + + for edges in permutations: + if edges[0][0] != edges[-1][-1]: # check first node is equal to last node + continue + all_nodes = [] + for edge in edges: + for n in edge: + all_nodes.append(n) + inner_nodes = all_nodes[ + 1:-1 + ] # find all nodes in all edges excluding the first and last nodes + nodes_out = [ + inner_nodes[i] for i in range(len(inner_nodes)) if i % 2 == 0 + ] # find the nodes each edge is leaving + node_in = [ + inner_nodes[i] for i in range(len(inner_nodes)) if i % 2 != 0 + ] # find the nodes each edge is entering + if nodes_out == node_in and ( + len([all_nodes[0]] + nodes_out) == len(set([all_nodes[0]] + nodes_out)) + ): # check that each edge connect to the next via a common node and that no node is crossed more than once + return True + + for energy, bs in energies_bitstrings: + # convert binary string to wires then wires to edges + wires_ = tuple(i for i, s in enumerate(bs) if s != 0) + edges = tuple(m[w] for w in wires_) + + if len(edges) and find_simple_cycle(edges): + assert energy == min(energies_bitstrings)[0] + elif len(edges) and not find_simple_cycle(edges): + assert energy > min(energies_bitstrings)[0] From 9a594184af77e9ecc6fee354516df6d92e99bc13 Mon Sep 17 00:00:00 2001 From: anthayes92 Date: Mon, 10 May 2021 12:58:56 +0100 Subject: [PATCH 07/25] update helper function docstring --- pennylane/qaoa/cost.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index b7075369819..f2b84525068 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -492,18 +492,46 @@ def max_weight_cycle(graph, constrained=True): The maximum weighted cycle cost Hamiltonian for constrained QAOA is defined as: - .. math:: H_C \ = H_loss + H_netflow + H_outflow + .. math:: H_C \ = H_{loss} + H_{netflow} + H_{outflow} + + where the loss Hamiltonian :func:`~qaoa.cycle.loss_hamiltonian` is given by + + .. math:: H_{loss} = \sum_{(i, j) \in E} Z_{ij}\log c_{ij} + + where :math:`E` are the edges of the graph and :math:`Z_{ij}` is a qubit Pauli-Z matrix acting + upon the wire specified by the edge :math:`(i, j)`. The netflow constraint Hamiltonian + :func:`~qaoa.cycle.net_flow_constraint` is given by + + .. math:: H_{netflow} = \sum_{i \in V} \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}, + + where :math:`V` are the graph vertices and :math:`d_{i}^{\rm out}` and :math:`d_{i}^{\rm in}` are + the outdegree and indegree, respectively, of node :math:`i`. The outflow constraint + Hamiltonian :func:`~qaoa.cycle.out_flow_constraint` is given by + + .. math:: H_{outflow} = \sum_{i\in V}\left(d_{i}^{out}(d_{i}^{out} - 2)\mathbb{I} + - 2(d_{i}^{out}-1)\sum_{j,(i,j)\in E}\hat{Z}_{ij} + + \left( \sum_{j,(i,j)\in E}\hat{Z}_{ij} \right)^{2}\right) The returned mixer Hamiltonian is :func:`~qaoa.x_mixer` applied to all wires. **Unconstrained** - The maximum weighted cycle cost Hamiltonian for unconstrained QAOA is defined as: + The maximum weighted cycle cost Hamiltonian for unconstrained QAOA is the loss Hamiltonian + :func:`~qaoa.cycle.loss_hamiltonian`, as defined above. Note that the netflow and outflow + constraints are not included in this cost Hamiltonian + + .. math:: H_C = H_{loss} - .. math:: H_C = H_loss + The returned mixer Hamiltonian is :func:`~qaoa.cycle.cycle_mixer` given by - The returned mixer Hamiltonian is :func:`~qaoa.cycle.cycle_mixer` applied to the given graph + .. math:: H_M = \frac{1}{4}\sum_{(i, j)\in E} + \left(\sum_{k \in V, k\neq i, k\neq j, (i, k) \in E, (k, j) \in E} + \left[X_{ij}X_{ik}X_{kj} +Y_{ij}Y_{ik}X_{kj} + Y_{ij}X_{ik}Y_{kj} - X_{ij}Y_{ik}Y_{kj}\right] + \right) + where a valid cycle is defined as a subset of edges in :math:`E` such that all of the + graph's nodes :math:`V` have zero net flow (see the :func:`~.net_flow_constraint` function). """ if not isinstance(graph, nx.Graph): From 5cea43b3ce4060e18ca846122aed9c61dad85d41 Mon Sep 17 00:00:00 2001 From: anthayes92 Date: Mon, 10 May 2021 13:46:32 +0100 Subject: [PATCH 08/25] fix typos --- tests/test_qaoa.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_qaoa.py b/tests/test_qaoa.py index 681da3b3cc2..39506f4e29f 100644 --- a/tests/test_qaoa.py +++ b/tests/test_qaoa.py @@ -365,7 +365,7 @@ def test_bit_flip_mixer_output(self, graph, n, target_hamiltonian): MIS = list(zip(GRAPHS, CONSTRAINED, COST_HAMILTONIANS, MIXER_HAMILTONIANS)) -"""GENERATES THE CASES TO TEST THE MIn VERTEX COVER PROBLEM""" +"""GENERATES THE CASES TO TEST THE MIN VERTEX COVER PROBLEM""" COST_COEFFS = [[-1, -1, -1], [-1, -1, -1], [0.75, -0.25, 0.5, 0.75, -0.25]] @@ -480,7 +480,7 @@ def test_bit_flip_mixer_output(self, graph, n, target_hamiltonian): DIGRAPHS = [digraph_complete] -MWS_CONSTRAINED = [True, False] +MWC_CONSTRAINED = [True, False] COST_COEFFS = [ [ @@ -616,7 +616,7 @@ def test_bit_flip_mixer_output(self, graph, n, target_hamiltonian): MIXER_HAMILTONIANS = [qml.Hamiltonian(MIXER_COEFFS[i], MIXER_TERMS[i]) for i in range(2)] -MWC = list(zip(DIGRAPHS, MWS_CONSTRAINED, COST_HAMILTONIANS, MIXER_HAMILTONIANS)) +MWC = list(zip(DIGRAPHS, MWC_CONSTRAINED, COST_HAMILTONIANS, MIXER_HAMILTONIANS)) def decompose_hamiltonian(hamiltonian): From e88199c79f1f7cd18496ce612ff300a273e6bc67 Mon Sep 17 00:00:00 2001 From: anthayes92 Date: Mon, 10 May 2021 13:48:30 +0100 Subject: [PATCH 09/25] add WIP entry to CHANGELOG.md --- .github/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 8e8c9129674..855ca26b02b 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -63,7 +63,8 @@ [(#1213)](https://github.com/PennyLaneAI/pennylane/pull/1213) [(#1220)](https://github.com/PennyLaneAI/pennylane/pull/1220) [(#1214)](https://github.com/PennyLaneAI/pennylane/pull/1214) - + [(#1283)](https://github.com/PennyLaneAI/pennylane/pull/1283) + * Adds `QubitCarry` and `QubitSum` operations for basic arithmetic. [(#1169)](https://github.com/PennyLaneAI/pennylane/pull/1169) From 632598738d0d018ac4e6e8a3d6c43ac3b4f65394 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 12:00:09 -0400 Subject: [PATCH 10/25] Extend to constrained=False --- tests/test_qaoa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_qaoa.py b/tests/test_qaoa.py index 39506f4e29f..02b1d1a5fdb 100644 --- a/tests/test_qaoa.py +++ b/tests/test_qaoa.py @@ -478,7 +478,7 @@ def test_bit_flip_mixer_output(self, graph, n, target_hamiltonian): """GENERATES THE CASES TO TEST THE MAXIMUM WEIGHTED CYCLE PROBLEM""" -DIGRAPHS = [digraph_complete] +DIGRAPHS = [digraph_complete] * 2 MWC_CONSTRAINED = [True, False] From 4a1396a76441b04dfb1ade5757f30ee69005aaa5 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 12:10:17 -0400 Subject: [PATCH 11/25] Add problem definition --- pennylane/qaoa/cost.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index f2b84525068..ddc06a0a6af 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -474,8 +474,19 @@ def max_clique(graph, constrained=True): def max_weight_cycle(graph, constrained=True): - r"""Returns the QAOA cost Hamiltonian and the recommended mixer corresponding to the maximum weighted cycle - problem, for a given graph. + r"""Returns the QAOA cost Hamiltonian and the recommended mixer corresponding to the maximum + weighted cycle problem, for a given graph. + + The maximum-weighted cycle is defined in the following way (see + `here `__ for more details). + The product of weights of a subset of edges in a graph is given by + + .. math:: P = \prod_{(i, j) \in E} x_{ij} c_{ij} + + 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. Args: graph (nx.Graph): the graph on which the Hamiltonians are defined From 81fe0b5a09fb31343a2838825da07ae64e20b3e7 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 12:16:44 -0400 Subject: [PATCH 12/25] Update docstring --- pennylane/qaoa/cost.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index ddc06a0a6af..d1aea08b62a 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -474,10 +474,10 @@ def max_clique(graph, constrained=True): def max_weight_cycle(graph, constrained=True): - r"""Returns the QAOA cost Hamiltonian and the recommended mixer corresponding to the maximum - weighted cycle problem, for a given graph. + r"""Returns the QAOA cost Hamiltonian and the recommended mixer corresponding to the + maximum-weighted cycle problem, for a given graph. - The maximum-weighted cycle is defined in the following way (see + The maximum-weighted cycle problem is defined in the following way (see `here `__ for more details). The product of weights of a subset of edges in a graph is given by @@ -486,10 +486,10 @@ def max_weight_cycle(graph, constrained=True): 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. + our subset of edges composes a `cycle `__. Args: - graph (nx.Graph): the graph on which the Hamiltonians are defined + graph (nx.Graph): the directed graph on which the Hamiltonians are defined constrained (bool): specifies the variant of QAOA that is performed (constrained or unconstrained) Returns: From b949fdb55d7957f0c51639a2b23dcba3660a6f93 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 12:18:32 -0400 Subject: [PATCH 13/25] Fix links --- pennylane/qaoa/cost.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index d1aea08b62a..1b86a0c5c3b 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -505,36 +505,36 @@ def max_weight_cycle(graph, constrained=True): .. math:: H_C \ = H_{loss} + H_{netflow} + H_{outflow} - where the loss Hamiltonian :func:`~qaoa.cycle.loss_hamiltonian` is given by + where the loss Hamiltonian :func:`~.loss_hamiltonian` is given by .. math:: H_{loss} = \sum_{(i, j) \in E} Z_{ij}\log c_{ij} where :math:`E` are the edges of the graph and :math:`Z_{ij}` is a qubit Pauli-Z matrix acting upon the wire specified by the edge :math:`(i, j)`. The netflow constraint Hamiltonian - :func:`~qaoa.cycle.net_flow_constraint` is given by + :func:`~.net_flow_constraint` is given by .. math:: H_{netflow} = \sum_{i \in V} \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}, where :math:`V` are the graph vertices and :math:`d_{i}^{\rm out}` and :math:`d_{i}^{\rm in}` are the outdegree and indegree, respectively, of node :math:`i`. The outflow constraint - Hamiltonian :func:`~qaoa.cycle.out_flow_constraint` is given by + Hamiltonian :func:`~.out_flow_constraint` is given by .. math:: H_{outflow} = \sum_{i\in V}\left(d_{i}^{out}(d_{i}^{out} - 2)\mathbb{I} - 2(d_{i}^{out}-1)\sum_{j,(i,j)\in E}\hat{Z}_{ij} + \left( \sum_{j,(i,j)\in E}\hat{Z}_{ij} \right)^{2}\right) - The returned mixer Hamiltonian is :func:`~qaoa.x_mixer` applied to all wires. + The returned mixer Hamiltonian is :func:`~.x_mixer` applied to all wires. **Unconstrained** The maximum weighted cycle cost Hamiltonian for unconstrained QAOA is the loss Hamiltonian - :func:`~qaoa.cycle.loss_hamiltonian`, as defined above. Note that the netflow and outflow + :func:`~.loss_hamiltonian`, as defined above. Note that the netflow and outflow constraints are not included in this cost Hamiltonian .. math:: H_C = H_{loss} - The returned mixer Hamiltonian is :func:`~qaoa.cycle.cycle_mixer` given by + The returned mixer Hamiltonian is :func:`~.cycle_mixer` given by .. math:: H_M = \frac{1}{4}\sum_{(i, j)\in E} \left(\sum_{k \in V, k\neq i, k\neq j, (i, k) \in E, (k, j) \in E} From c177b51fad0cb5c404c1b65c85d0a162d1d704c4 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 12:20:16 -0400 Subject: [PATCH 14/25] Fix coeffs --- pennylane/qaoa/cost.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index 1b86a0c5c3b..0af023949f8 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -503,7 +503,7 @@ def max_weight_cycle(graph, constrained=True): The maximum weighted cycle cost Hamiltonian for constrained QAOA is defined as: - .. math:: H_C \ = H_{loss} + H_{netflow} + H_{outflow} + .. math:: H_C \ = H_{loss} + 3 H_{netflow} + 3 H_{outflow} where the loss Hamiltonian :func:`~.loss_hamiltonian` is given by From 355c388cdeedcc60bb2fd0512e80826572ead13e Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 12:23:58 -0400 Subject: [PATCH 15/25] Add initialization hints --- pennylane/qaoa/cost.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index 0af023949f8..1d54e04af7b 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -526,6 +526,11 @@ def max_weight_cycle(graph, constrained=True): The returned mixer Hamiltonian is :func:`~.x_mixer` applied to all wires. + .. note:: + + **Recommended initialization circuit:** + Even superposition over all basis states. + **Unconstrained** The maximum weighted cycle cost Hamiltonian for unconstrained QAOA is the loss Hamiltonian @@ -543,6 +548,12 @@ def max_weight_cycle(graph, constrained=True): where a valid cycle is defined as a subset of edges in :math:`E` such that all of the graph's nodes :math:`V` have zero net flow (see the :func:`~.net_flow_constraint` function). + + .. note:: + + **Recommended initialization circuit:** + Your circuit must prepare a state (or a superposition of states) that corresponds + to a cycle. Follow the example code below to see how this is done. """ if not isinstance(graph, nx.Graph): From 6f2450daca2d0924c15e0a57002207b578e773fc Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 12:35:15 -0400 Subject: [PATCH 16/25] Update wording --- pennylane/qaoa/cost.py | 68 +++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index 1d54e04af7b..6a695175fb5 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -501,59 +501,65 @@ def max_weight_cycle(graph, constrained=True): **Constrained** - The maximum weighted cycle cost Hamiltonian for constrained QAOA is defined as: - - .. math:: H_C \ = H_{loss} + 3 H_{netflow} + 3 H_{outflow} + The maximum weighted cycle cost Hamiltonian for unconstrained QAOA is - where the loss Hamiltonian :func:`~.loss_hamiltonian` is given by + .. math:: H_C = H_{loss}. - .. math:: H_{loss} = \sum_{(i, j) \in E} Z_{ij}\log c_{ij} + Here, :math:`H_{loss}` is a loss Hamiltonian: - where :math:`E` are the edges of the graph and :math:`Z_{ij}` is a qubit Pauli-Z matrix acting - upon the wire specified by the edge :math:`(i, j)`. The netflow constraint Hamiltonian - :func:`~.net_flow_constraint` is given by + .. math:: H_{C} = \sum_{(i, j) \in E} Z_{ij}\log c_{ij} - .. math:: H_{netflow} = \sum_{i \in V} \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}, + where :math:`E` are the edges of the graph and :math:`Z_{ij}` is a qubit Pauli-Z matrix + acting upon the wire specified by the edge :math:`(i, j)` (see :func:`~.loss_hamiltonian` + for more details). - where :math:`V` are the graph vertices and :math:`d_{i}^{\rm out}` and :math:`d_{i}^{\rm in}` are - the outdegree and indegree, respectively, of node :math:`i`. The outflow constraint - Hamiltonian :func:`~.out_flow_constraint` is given by + The returned mixer Hamiltonian is :func:`~.cycle_mixer` given by - .. math:: H_{outflow} = \sum_{i\in V}\left(d_{i}^{out}(d_{i}^{out} - 2)\mathbb{I} - - 2(d_{i}^{out}-1)\sum_{j,(i,j)\in E}\hat{Z}_{ij} + - \left( \sum_{j,(i,j)\in E}\hat{Z}_{ij} \right)^{2}\right) + .. math:: H_M = \frac{1}{4}\sum_{(i, j)\in E} + \left(\sum_{k \in V, k\neq i, k\neq j, (i, k) \in E, (k, j) \in E} + \left[X_{ij}X_{ik}X_{kj} +Y_{ij}Y_{ik}X_{kj} + Y_{ij}X_{ik}Y_{kj} - X_{ij}Y_{ik}Y_{kj}\right] + \right). - The returned mixer Hamiltonian is :func:`~.x_mixer` applied to all wires. + This mixer provides transitions between collections of cycles, i.e., any subset of edges + in :math:`E` such that all the graph's nodes :math:`V` have zero net flow + (see the :func:`~.net_flow_constraint` function). .. note:: **Recommended initialization circuit:** - Even superposition over all basis states. + Your circuit must prepare a state (or a superposition of states) that corresponds + to a cycle. Follow the example code below to see how this is done. **Unconstrained** - The maximum weighted cycle cost Hamiltonian for unconstrained QAOA is the loss Hamiltonian - :func:`~.loss_hamiltonian`, as defined above. Note that the netflow and outflow - constraints are not included in this cost Hamiltonian + The maximum weighted cycle cost Hamiltonian for constrained QAOA is defined as: - .. math:: H_C = H_{loss} + .. math:: H_C \ = H_{loss} + 3 H_{netflow} + 3 H_{outflow}. - The returned mixer Hamiltonian is :func:`~.cycle_mixer` given by + The netflow constraint Hamiltonian :func:`~.net_flow_constraint` is given by - .. math:: H_M = \frac{1}{4}\sum_{(i, j)\in E} - \left(\sum_{k \in V, k\neq i, k\neq j, (i, k) \in E, (k, j) \in E} - \left[X_{ij}X_{ik}X_{kj} +Y_{ij}Y_{ik}X_{kj} + Y_{ij}X_{ik}Y_{kj} - X_{ij}Y_{ik}Y_{kj}\right] - \right) + .. math:: H_{netflow} = \sum_{i \in V} \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}, + + where :math:`d_{i}^{\rm out}` and :math:`d_{i}^{\rm in}` are + the outdegree and indegree, respectively, of node :math:`i`. It is minimized whenever a + subset of edges in :math:`E` results in zero net flow from each node in :math:`V`. - where a valid cycle is defined as a subset of edges in :math:`E` such that all of the - graph's nodes :math:`V` have zero net flow (see the :func:`~.net_flow_constraint` function). + The outflow constraint Hamiltonian :func:`~.out_flow_constraint` is given by + + .. math:: H_{outflow} = \sum_{i\in V}\left(d_{i}^{out}(d_{i}^{out} - 2)\mathbb{I} + - 2(d_{i}^{out}-1)\sum_{j,(i,j)\in E}\hat{Z}_{ij} + + \left( \sum_{j,(i,j)\in E}\hat{Z}_{ij} \right)^{2}\right). + + It is minimized whenever a subset of edges in :math:`E` results in an outflow of at most one + from each node in :math:`V`. + + The returned mixer Hamiltonian is :func:`~.x_mixer` applied to all wires. .. note:: **Recommended initialization circuit:** - Your circuit must prepare a state (or a superposition of states) that corresponds - to a cycle. Follow the example code below to see how this is done. + Even superposition over all basis states. """ if not isinstance(graph, nx.Graph): From 21ebeb575789b42cd435ed9ba87cd737154b98e8 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 12:39:52 -0400 Subject: [PATCH 17/25] Update return --- pennylane/qaoa/cost.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index 6a695175fb5..990bfbebc3b 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -493,7 +493,8 @@ def max_weight_cycle(graph, constrained=True): constrained (bool): specifies the variant of QAOA that is performed (constrained or unconstrained) Returns: - (.Hamiltonian, .Hamiltonian): The cost and mixer Hamiltonians + (.Hamiltonian, .Hamiltonian, dict): The cost and mixer Hamiltonians, as well as a dictionary + mapping from wires to the graph's edges .. UsageDetails:: @@ -573,4 +574,4 @@ def max_weight_cycle(graph, constrained=True): ) mixer_h = qaoa.x_mixer(graph.nodes) - return (cost_h, mixer_h) + return (cost_h, mixer_h, qaoa.cycle.wires_to_edges(graph)) From 91690c39cbce1df593e5e4029a6a9a50ae5a84b4 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 12:46:13 -0400 Subject: [PATCH 18/25] Fix tests --- pennylane/qaoa/cost.py | 7 ++++--- tests/test_qaoa.py | 11 +++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index 990bfbebc3b..d79b963608c 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -562,16 +562,17 @@ def max_weight_cycle(graph, constrained=True): **Recommended initialization circuit:** Even superposition over all basis states. """ - if not isinstance(graph, nx.Graph): raise ValueError("Input graph must be a nx.Graph, got {}".format(type(graph).__name__)) + mapping = qaoa.cycle.wires_to_edges(graph) + if constrained: - return (qaoa.cycle.loss_hamiltonian(graph), qaoa.cycle.cycle_mixer(graph)) + return (qaoa.cycle.loss_hamiltonian(graph), qaoa.cycle.cycle_mixer(graph), mapping) cost_h = qaoa.cycle.loss_hamiltonian(graph) + 3 * ( qaoa.cycle.net_flow_constraint(graph) + qaoa.cycle.out_flow_constraint(graph) ) mixer_h = qaoa.x_mixer(graph.nodes) - return (cost_h, mixer_h, qaoa.cycle.wires_to_edges(graph)) + return (cost_h, mixer_h, mapping) diff --git a/tests/test_qaoa.py b/tests/test_qaoa.py index 02b1d1a5fdb..bb3413142b1 100644 --- a/tests/test_qaoa.py +++ b/tests/test_qaoa.py @@ -616,7 +616,9 @@ def test_bit_flip_mixer_output(self, graph, n, target_hamiltonian): MIXER_HAMILTONIANS = [qml.Hamiltonian(MIXER_COEFFS[i], MIXER_TERMS[i]) for i in range(2)] -MWC = list(zip(DIGRAPHS, MWC_CONSTRAINED, COST_HAMILTONIANS, MIXER_HAMILTONIANS)) +MAPPINGS = [qaoa.cycle.wires_to_edges(digraph_complete)] * 2 + +MWC = list(zip(DIGRAPHS, MWC_CONSTRAINED, COST_HAMILTONIANS, MIXER_HAMILTONIANS, MAPPINGS)) def decompose_hamiltonian(hamiltonian): @@ -731,12 +733,13 @@ def test_max_clique_output(self, graph, constrained, cost_hamiltonian, mixer_ham assert decompose_hamiltonian(cost_hamiltonian) == decompose_hamiltonian(cost_h) assert decompose_hamiltonian(mixer_hamiltonian) == decompose_hamiltonian(mixer_h) - @pytest.mark.parametrize(("graph", "constrained", "cost_hamiltonian", "mixer_hamiltonian"), MWC) - def test_max_weight_cycle_output(self, graph, constrained, cost_hamiltonian, mixer_hamiltonian): + @pytest.mark.parametrize(("graph", "constrained", "cost_hamiltonian", "mixer_hamiltonian", "mapping"), MWC) + def test_max_weight_cycle_output(self, graph, constrained, cost_hamiltonian, mixer_hamiltonian, mapping): """Tests that the output of the maximum weighted cycle method is correct""" - cost_h, mixer_h = qaoa.max_weight_cycle(graph, constrained=constrained) + cost_h, mixer_h, m = qaoa.max_weight_cycle(graph, constrained=constrained) + assert mapping == m assert decompose_hamiltonian(cost_hamiltonian) == decompose_hamiltonian(cost_h) assert decompose_hamiltonian(mixer_hamiltonian) == decompose_hamiltonian(mixer_h) From 24a5fadd9d2dd1a6aff703a4d7a369293c2ab905 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 13:35:10 -0400 Subject: [PATCH 19/25] Add to init --- pennylane/qaoa/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane/qaoa/__init__.py b/pennylane/qaoa/__init__.py index 944b874ac6f..ee59f91fdf0 100644 --- a/pennylane/qaoa/__init__.py +++ b/pennylane/qaoa/__init__.py @@ -18,3 +18,4 @@ from .mixers import * from .cost import * from .layers import * +import pennylane.qaoa.cycle From 4d6c747d3772fe9b7aa7472fb0ecf79ce688b2ac Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 13:37:58 -0400 Subject: [PATCH 20/25] Prevent circular imports --- pennylane/qaoa/cycle.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/pennylane/qaoa/cycle.py b/pennylane/qaoa/cycle.py index 638ae08f40d..b725f4e96d7 100644 --- a/pennylane/qaoa/cycle.py +++ b/pennylane/qaoa/cycle.py @@ -19,6 +19,7 @@ import networkx as nx import numpy as np import pennylane as qml +from pennylane.vqe import Hamiltonian def edges_to_wires(graph: nx.Graph) -> Dict[Tuple, int]: @@ -79,7 +80,7 @@ def wires_to_edges(graph: nx.Graph) -> Dict[int, Tuple]: return {i: edge for i, edge in enumerate(graph.edges)} -def cycle_mixer(graph: nx.DiGraph) -> qml.Hamiltonian: +def cycle_mixer(graph: nx.DiGraph) -> Hamiltonian: r"""Calculates the cycle-mixer Hamiltonian. Following methods outlined `here `__, the @@ -132,7 +133,7 @@ def cycle_mixer(graph: nx.DiGraph) -> qml.Hamiltonian: Returns: qml.Hamiltonian: the cycle-mixer Hamiltonian """ - hamiltonian = qml.Hamiltonian([], []) + hamiltonian = Hamiltonian([], []) for edge in graph.edges: hamiltonian += _partial_cycle_mixer(graph, edge) @@ -140,7 +141,7 @@ def cycle_mixer(graph: nx.DiGraph) -> qml.Hamiltonian: return hamiltonian -def _partial_cycle_mixer(graph: nx.DiGraph, edge: Tuple) -> qml.Hamiltonian: +def _partial_cycle_mixer(graph: nx.DiGraph, edge: Tuple) -> Hamiltonian: r"""Calculates the partial cycle-mixer Hamiltonian for a specific edge. For an edge :math:`(i, j)`, this function returns: @@ -184,10 +185,10 @@ def _partial_cycle_mixer(graph: nx.DiGraph, edge: Tuple) -> qml.Hamiltonian: coeffs.extend([0.25, 0.25, 0.25, -0.25]) - return qml.Hamiltonian(coeffs, ops) + return Hamiltonian(coeffs, ops) -def loss_hamiltonian(graph: nx.Graph) -> qml.Hamiltonian: +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 @@ -271,7 +272,7 @@ def loss_hamiltonian(graph: nx.Graph) -> qml.Hamiltonian: coeffs.append(np.log(weight)) ops.append(qml.PauliZ(wires=edges_to_qubits[edge])) - return qml.Hamiltonian(coeffs, ops) + return Hamiltonian(coeffs, ops) def _square_hamiltonian_terms( @@ -308,7 +309,7 @@ def _square_hamiltonian_terms( return squared_coeffs, squared_ops -def out_flow_constraint(graph: nx.DiGraph) -> qml.Hamiltonian: +def out_flow_constraint(graph: nx.DiGraph) -> Hamiltonian: r"""Calculates the `out flow constraint `__ Hamiltonian for the maximum-weighted cycle problem. @@ -346,7 +347,7 @@ def out_flow_constraint(graph: nx.DiGraph) -> qml.Hamiltonian: if not hasattr(graph, "out_edges"): raise ValueError("Input graph must be directed") - hamiltonian = qml.Hamiltonian([], []) + hamiltonian = Hamiltonian([], []) for node in graph.nodes: hamiltonian += _inner_out_flow_constraint_hamiltonian(graph, node) @@ -354,7 +355,7 @@ def out_flow_constraint(graph: nx.DiGraph) -> qml.Hamiltonian: return hamiltonian -def net_flow_constraint(graph: nx.DiGraph) -> qml.Hamiltonian: +def net_flow_constraint(graph: nx.DiGraph) -> Hamiltonian: r"""Calculates the `net flow constraint `__ Hamiltonian for the maximum-weighted cycle problem. @@ -391,7 +392,7 @@ def net_flow_constraint(graph: nx.DiGraph) -> qml.Hamiltonian: if not hasattr(graph, "in_edges") or not hasattr(graph, "out_edges"): raise ValueError("Input graph must be directed") - hamiltonian = qml.Hamiltonian([], []) + hamiltonian = Hamiltonian([], []) for node in graph.nodes: hamiltonian += _inner_net_flow_constraint_hamiltonian(graph, node) @@ -399,7 +400,7 @@ def net_flow_constraint(graph: nx.DiGraph) -> qml.Hamiltonian: return hamiltonian -def _inner_out_flow_constraint_hamiltonian(graph: nx.DiGraph, node) -> qml.Hamiltonian: +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: @@ -438,13 +439,13 @@ def _inner_out_flow_constraint_hamiltonian(graph: nx.DiGraph, node) -> qml.Hamil coeffs.append(d * (d - 2)) ops.append(qml.Identity(0)) - H = qml.Hamiltonian(coeffs, ops) + H = Hamiltonian(coeffs, ops) H.simplify() return H -def _inner_net_flow_constraint_hamiltonian(graph: nx.DiGraph, node) -> qml.Hamiltonian: +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`. @@ -484,7 +485,7 @@ def _inner_net_flow_constraint_hamiltonian(graph: nx.DiGraph, node) -> qml.Hamil ops.append(qml.PauliZ(wires)) coeffs, ops = _square_hamiltonian_terms(coeffs, ops) - H = qml.Hamiltonian(coeffs, ops) + H = Hamiltonian(coeffs, ops) H.simplify() return H From d6846e5856f021bf18233a3e935a542fef7f347e Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 13:47:35 -0400 Subject: [PATCH 21/25] Add example --- pennylane/qaoa/cost.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index d79b963608c..7289343605c 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -561,6 +561,47 @@ def max_weight_cycle(graph, constrained=True): **Recommended initialization circuit:** Even superposition over all basis states. + + **Example** + + First set up a simple example: + + .. code-block:: python + + import pennylane as qml + import numpy as np + import networkx as nx + + a = np.random.random((4, 4)) + np.fill_diagonal(a, 0) + g = nx.DiGraph(a) + + The cost and mixer Hamiltonian as well as the mapping from wires to edges can be loaded + using: + + >>> cost, mixer, mapping = qml.qaoa.max_weight_cycle(g, constrained=True) + + Since we are using ``constrained=True``, we must ensure that the input state to the QAOA + algorithm corresponds to a cycle. Consider the mapping: + + >>> mapping + {0: (0, 1), + 1: (0, 2), + 2: (0, 3), + 3: (1, 0), + 4: (1, 2), + 5: (1, 3), + 6: (2, 0), + 7: (2, 1), + 8: (2, 3), + 9: (3, 0), + 10: (3, 1), + 11: (3, 2)} + + A simple cycle is given by the edges ``(0, 1)`` and ``(1, 0)`` and corresponding wires + ``0`` and ``3``. Hence, the state :math:`|100100000000\rangle` corresponds to a cycle and + can be prepared using :class:`~.BasisState` or simple :class:`~.PauliX` rotations on the + ``0`` and ``3`` wires. """ if not isinstance(graph, nx.Graph): raise ValueError("Input graph must be a nx.Graph, got {}".format(type(graph).__name__)) From bc246c31b9a6b09de31e94ef47bd930db77de3a2 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 13:52:54 -0400 Subject: [PATCH 22/25] Add note --- pennylane/qaoa/cost.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index 7289343605c..59c852a81ad 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -502,6 +502,11 @@ def max_weight_cycle(graph, constrained=True): **Constrained** + .. note:: + + This method of constrained QAOA was introduced by Hadfield, Wang, Gorman, Rieffel, + Venturelli, and Biswas in `arXiv:1709.03489 `__. + The maximum weighted cycle cost Hamiltonian for unconstrained QAOA is .. math:: H_C = H_{loss}. From 8c4af31e5a8077f1a733f6c8dc7319b0199dda57 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 13:58:36 -0400 Subject: [PATCH 23/25] Update after read through --- pennylane/qaoa/cost.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index 59c852a81ad..357e798cb49 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -509,11 +509,11 @@ def max_weight_cycle(graph, constrained=True): The maximum weighted cycle cost Hamiltonian for unconstrained QAOA is - .. math:: H_C = H_{loss}. + .. math:: H_C = H_{\rm loss}. - Here, :math:`H_{loss}` is a loss Hamiltonian: + Here, :math:`H_{\rm loss}` is a loss Hamiltonian: - .. math:: H_{C} = \sum_{(i, j) \in E} Z_{ij}\log c_{ij} + .. math:: H_{\rm loss} = \sum_{(i, j) \in E} Z_{ij}\log c_{ij} where :math:`E` are the edges of the graph and :math:`Z_{ij}` is a qubit Pauli-Z matrix acting upon the wire specified by the edge :math:`(i, j)` (see :func:`~.loss_hamiltonian` @@ -533,18 +533,18 @@ def max_weight_cycle(graph, constrained=True): .. note:: **Recommended initialization circuit:** - Your circuit must prepare a state (or a superposition of states) that corresponds - to a cycle. Follow the example code below to see how this is done. + Your circuit must prepare a state that corresponds to a cycle (or a superposition + of cycles). Follow the example code below to see how this is done. **Unconstrained** The maximum weighted cycle cost Hamiltonian for constrained QAOA is defined as: - .. math:: H_C \ = H_{loss} + 3 H_{netflow} + 3 H_{outflow}. + .. math:: H_C \ = H_{\rm loss} + 3 H_{\rm netflow} + 3 H_{\rm outflow}. The netflow constraint Hamiltonian :func:`~.net_flow_constraint` is given by - .. math:: H_{netflow} = \sum_{i \in V} \left((d_{i}^{\rm out} - d_{i}^{\rm in})\mathbb{I} - + .. math:: H_{\rm netflow} = \sum_{i \in V} \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}, where :math:`d_{i}^{\rm out}` and :math:`d_{i}^{\rm in}` are @@ -553,7 +553,7 @@ def max_weight_cycle(graph, constrained=True): The outflow constraint Hamiltonian :func:`~.out_flow_constraint` is given by - .. math:: H_{outflow} = \sum_{i\in V}\left(d_{i}^{out}(d_{i}^{out} - 2)\mathbb{I} + .. math:: H_{\rm outflow} = \sum_{i\in V}\left(d_{i}^{out}(d_{i}^{out} - 2)\mathbb{I} - 2(d_{i}^{out}-1)\sum_{j,(i,j)\in E}\hat{Z}_{ij} + \left( \sum_{j,(i,j)\in E}\hat{Z}_{ij} \right)^{2}\right). @@ -569,7 +569,7 @@ def max_weight_cycle(graph, constrained=True): **Example** - First set up a simple example: + First set up a simple graph: .. code-block:: python From 9d6e7624656fcca41ae247cc72519d8d25317f09 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 14:12:05 -0400 Subject: [PATCH 24/25] Fix unconstrained --- pennylane/qaoa/cost.py | 2 +- tests/test_qaoa.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/qaoa/cost.py b/pennylane/qaoa/cost.py index 357e798cb49..ab43abcbe54 100644 --- a/pennylane/qaoa/cost.py +++ b/pennylane/qaoa/cost.py @@ -619,6 +619,6 @@ def max_weight_cycle(graph, constrained=True): cost_h = qaoa.cycle.loss_hamiltonian(graph) + 3 * ( qaoa.cycle.net_flow_constraint(graph) + qaoa.cycle.out_flow_constraint(graph) ) - mixer_h = qaoa.x_mixer(graph.nodes) + mixer_h = qaoa.x_mixer(mapping.keys()) return (cost_h, mixer_h, mapping) diff --git a/tests/test_qaoa.py b/tests/test_qaoa.py index bb3413142b1..127e670a10c 100644 --- a/tests/test_qaoa.py +++ b/tests/test_qaoa.py @@ -581,7 +581,7 @@ def test_bit_flip_mixer_output(self, graph, n, target_hamiltonian): 0.25, -0.25, ], - [1, 1, 1], + [1] * 6, ] MIXER_TERMS = [ @@ -611,7 +611,7 @@ def test_bit_flip_mixer_output(self, graph, n, target_hamiltonian): qml.PauliY(wires=[5]) @ qml.PauliX(wires=[4]) @ qml.PauliY(wires=[0]), qml.PauliX(wires=[5]) @ qml.PauliY(wires=[4]) @ qml.PauliY(wires=[0]), ], - [qml.PauliX(wires=[0]), qml.PauliX(wires=[1]), qml.PauliX(wires=[2])], + [qml.PauliX(wires=i) for i in range(6)], ] MIXER_HAMILTONIANS = [qml.Hamiltonian(MIXER_COEFFS[i], MIXER_TERMS[i]) for i in range(2)] From 98d4aa67d927ff626d6e37404e9c3d38bedccc5b Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 10 May 2021 14:29:04 -0400 Subject: [PATCH 25/25] Apply black --- tests/test_qaoa.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_qaoa.py b/tests/test_qaoa.py index 127e670a10c..5fb1c04ed4d 100644 --- a/tests/test_qaoa.py +++ b/tests/test_qaoa.py @@ -733,8 +733,12 @@ def test_max_clique_output(self, graph, constrained, cost_hamiltonian, mixer_ham assert decompose_hamiltonian(cost_hamiltonian) == decompose_hamiltonian(cost_h) assert decompose_hamiltonian(mixer_hamiltonian) == decompose_hamiltonian(mixer_h) - @pytest.mark.parametrize(("graph", "constrained", "cost_hamiltonian", "mixer_hamiltonian", "mapping"), MWC) - def test_max_weight_cycle_output(self, graph, constrained, cost_hamiltonian, mixer_hamiltonian, mapping): + @pytest.mark.parametrize( + ("graph", "constrained", "cost_hamiltonian", "mixer_hamiltonian", "mapping"), MWC + ) + def test_max_weight_cycle_output( + self, graph, constrained, cost_hamiltonian, mixer_hamiltonian, mapping + ): """Tests that the output of the maximum weighted cycle method is correct""" cost_h, mixer_h, m = qaoa.max_weight_cycle(graph, constrained=constrained)