136136######################################################################
137137# We must first encode this combinatorial problem into a cost Hamiltonian :math:`H_c.` This ends up being
138138#
139- # .. math:: H_c = 3 \sum_{(i, j) \in E(\bar{G})} (Z_i Z_j - Z_i - Z_j) + \displaystyle\sum_{i \in V(G)} Z_i,
139+ # .. math:: H_c = \frac{3}{4} \sum_{(i, j) \in E(\bar{G})} (Z_i Z_j - Z_i - Z_j) + \displaystyle\sum_{i \in V(G)} Z_i,
140140#
141141# where each qubit is a node in the graph, and the states :math:`|0\rangle` and :math:`|1\rangle`
142142# represent whether the vertex has been marked as part of the clique, as is the case for `most standard QAOA encoding
143143# schemes <https://arxiv.org/abs/1709.03489>`__.
144144# Note that :math:`\bar{G}` is the complement of :math:`G:` the graph formed by connecting all nodes that **do not** share
145145# an edge in :math:`G.`
146+ # The factor of :math:`\frac{3}{4}` (rather than :math:`3`) comes from a normalization applied
147+ # internally by :func:`~pennylane.qaoa.cost.edge_driver`, which is used by
148+ # :func:`~pennylane.qaoa.cost.max_clique` to build the edge penalty terms.
146149#
147150# In addition to defining :math:`H_c,` we also require a driver Hamiltonian :math:`H_d` which does not commute
148151# with :math:`H_c.` The driver Hamiltonian's role is similar to that of the mixer Hamiltonian in QAOA.
164167# One of the main ingredients in the FALQON algorithm is the operator :math:`i [H_d, H_c].` In
165168# the case of MaxClique, we can write down the commutator :math:`[H_d, H_c]` explicitly:
166169#
167- # .. math:: [H_d, H_c] = 3 \displaystyle\sum_{k \in V(G)} \displaystyle\sum_{(i, j) \in E(\bar{G})} \big( [X_k, Z_i Z_j] - [X_k, Z_i]
168- # - [X_k, Z_j] \big) + 3 \displaystyle\sum_{i \in V(G)} \displaystyle\sum_{j \in V(G)} [X_i, Z_j].
170+ # .. math:: [H_d, H_c] = \frac{3}{4} \displaystyle\sum_{k \in V(G)} \displaystyle\sum_{(i, j) \in E(\bar{G})} \big( [X_k, Z_i Z_j] - [X_k, Z_i]
171+ # - [X_k, Z_j] \big) + \displaystyle\sum_{i \in V(G)} \displaystyle\sum_{j \in V(G)} [X_i, Z_j].
169172#
170173# There are two distinct commutators that we must calculate, :math:`[X_k, Z_j]` and :math:`[X_k, Z_i Z_j].`
171174# This is straightforward as we know exactly what the
177180# where :math:`\delta_{kj}` is the `Kronecker delta <https://en.wikipedia.org/wiki/Kronecker_delta>`__. Therefore it
178181# follows from substitution into the above equation and multiplication by :math:`i` that:
179182#
180- # .. math:: i [H_d, H_c] = 6 \displaystyle\sum_{k \in V(G)} \displaystyle\sum_{(i, j) \in E(\bar{G})} \big( \delta_{ki} Y_k Z_j +
181- # \delta_{kj} Z_{i} Y_{k} - \delta_{ki} Y_k - \delta_{kj} Y_k \big) + 6 \displaystyle\sum_{i \in V(G)} Y_{i}.
183+ # .. math:: i [H_d, H_c] = \frac{3}{2} \displaystyle\sum_{k \in V(G)} \displaystyle\sum_{(i, j) \in E(\bar{G})} \big( \delta_{ki} Y_k Z_j +
184+ # \delta_{kj} Z_{i} Y_{k} - \delta_{ki} Y_k - \delta_{kj} Y_k \big) + 2 \displaystyle\sum_{i \in V(G)} Y_{i}.
182185#
183186# This new operator has quite a few terms! Therefore, we write a short method which computes it for us, and returns
184187# a :class:`~.pennylane.Hamiltonian` object. Note that this method works for any graph:
185188#
186189
190+
187191def build_hamiltonian (graph ):
188192 H = qml .Hamiltonian ([], [])
189193
@@ -195,45 +199,53 @@ def build_hamiltonian(graph):
195199 for edge in graph_c .edges :
196200 i , j = edge
197201 if k == i :
198- H += 6 * (qml .PauliY (k ) @ qml .PauliZ (j ) - qml .PauliY (k ))
202+ H += 1.5 * (qml .PauliY (k ) @ qml .PauliZ (j ) - qml .PauliY (k ))
199203 if k == j :
200- H += 6 * (qml .PauliZ (i ) @ qml .PauliY (k ) - qml .PauliY (k ))
204+ H += 1.5 * (qml .PauliZ (i ) @ qml .PauliY (k ) - qml .PauliY (k ))
201205 # Adds the terms in the second sum
202- H += 6 * qml .PauliY (k )
206+ H += 2 * qml .PauliY (k )
203207
204208 return H
205209
206210
207211print ("MaxClique Commutator" )
208212print (build_hamiltonian (graph ))
209213
214+ ######################################################################
215+ # .. note::
216+ #
217+ # For general graphs, the commutator :math:`i[H_d, H_c]` can also be computed
218+ # directly using :func:`~pennylane.commutator`, which is more concise and avoids
219+ # the need to expand the algebra manually:
220+ #
221+ # .. code-block:: python
222+ #
223+ # cost_h, driver_h = qaoa.max_clique(graph, constrained=False)
224+ # comm_h = qml.simplify(1j * qml.commutator(driver_h, cost_h))
225+
210226######################################################################
211227# We can now build the FALQON algorithm. Our goal is to evolve some initial state under the Hamiltonian :math:`H,`
212228# with our chosen :math:`\beta(t).` We first define one layer of the Trotterized time evolution, which is of
213229# the form :math:`U_d(\beta_k) U_c.` Note that we can use the :class:`~.pennylane.templates.ApproxTimeEvolution` template:
214230
231+
215232def falqon_layer (beta_k , cost_h , driver_h , delta_t ):
216233 qml .ApproxTimeEvolution (cost_h , delta_t , 1 )
217234 qml .ApproxTimeEvolution (driver_h , delta_t * beta_k , 1 )
218235
236+
219237######################################################################
220238# We then define a method which returns a FALQON ansatz corresponding to a particular cost Hamiltonian, driver
221239# Hamiltonian, and :math:`\Delta t.` This involves multiple repetitions of the "FALQON layer" defined above. The
222240# initial state of our circuit is an even superposition:
223241
242+
224243def build_maxclique_ansatz (cost_h , driver_h , delta_t ):
225244 def ansatz (beta , ** kwargs ):
226245 layers = len (beta )
227246 for w in dev .wires :
228247 qml .Hadamard (wires = w )
229- qml .layer (
230- falqon_layer ,
231- layers ,
232- beta ,
233- cost_h = cost_h ,
234- driver_h = driver_h ,
235- delta_t = delta_t
236- )
248+ qml .layer (falqon_layer , layers , beta , cost_h = cost_h , driver_h = driver_h , delta_t = delta_t )
237249
238250 return ansatz
239251
@@ -243,27 +255,36 @@ def expval_circuit(beta, measurement_h):
243255 ansatz (beta )
244256 return qml .expval (measurement_h )
245257
258+
246259######################################################################
247260# Finally, we implement the recursive process, where FALQON is able to determine the values
248261# of :math:`\beta_k,` feeding back into itself as the number of layers increases. This is
249262# straightforward using the methods defined above:
250263
264+
251265def max_clique_falqon (graph , n , beta_1 , delta_t , dev ):
252- comm_h = build_hamiltonian (graph ) # Builds the commutator
253- cost_h , driver_h = qaoa .max_clique (graph , constrained = False ) # Builds H_c and H_d
254- cost_fn = qml .QNode (expval_circuit , dev , interface = "autograd" ) # The ansatz + measurement circuit is executable
266+ comm_h = build_hamiltonian (graph ) # Builds the commutator
267+ cost_h , driver_h = qaoa .max_clique (graph , constrained = False ) # Builds H_c and H_d
268+ cost_fn = qml .QNode (
269+ expval_circuit , dev
270+ ) # The ansatz + measurement circuit is executable
255271
256- beta = [beta_1 ] # Records each value of beta_k
257- energies = [] # Records the value of the cost function at each step
272+ beta = [beta_1 ] # Records each value of beta_k
273+ energies = [] # Records the value of the cost function at each step
258274
259275 for i in range (n ):
260276 # Adds a value of beta to the list and evaluates the cost function
261- beta .append (- 1 * cost_fn (beta , measurement_h = comm_h )) # this call measures the expectation of the commuter hamiltonian
262- energy = cost_fn (beta , measurement_h = cost_h ) # this call measures the expectation of the cost hamiltonian
277+ beta .append (
278+ - 1 * cost_fn (beta , measurement_h = comm_h )
279+ ) # this call measures the expectation of the commuter hamiltonian
280+ energy = cost_fn (
281+ beta , measurement_h = cost_h
282+ ) # this call measures the expectation of the cost hamiltonian
263283 energies .append (energy )
264284
265285 return beta , energies
266286
287+
267288######################################################################
268289# Note that we return both the list of :math:`\beta_k` values, as well as the expectation value of the cost Hamiltonian
269290# for each step.
@@ -277,15 +298,15 @@ def max_clique_falqon(graph, n, beta_1, delta_t, dev):
277298beta_1 = 0.0
278299delta_t = 0.03
279300
280- dev = qml .device ("default.qubit" , wires = graph .nodes ) # Creates a device for the simulation
301+ dev = qml .device ("default.qubit" , wires = graph .nodes ) # Creates a device for the simulation
281302res_beta , res_energies = max_clique_falqon (graph , n , beta_1 , delta_t , dev )
282303
283304######################################################################
284305# We can then plot the expectation value of the cost Hamiltonian over the
285306# iterations of the algorithm:
286307#
287308
288- plt .plot (range (n + 1 )[1 :], res_energies )
309+ plt .plot (range (n + 1 )[1 :], res_energies )
289310plt .xlabel ("Iteration" )
290311plt .ylabel ("Cost Function Value" )
291312plt .show ()
@@ -297,18 +318,20 @@ def max_clique_falqon(graph, n, beta_1, delta_t, dev):
297318# we can create a graph showing the probability of measuring each possible bit string.
298319# We define the following circuit, feeding in the optimal values of :math:`\beta_k:`
299320
300- @qml .qnode (dev , interface = "autograd" )
321+
322+ @qml .qnode (dev )
301323def prob_circuit ():
302324 ansatz = build_maxclique_ansatz (cost_h , driver_h , delta_t )
303325 ansatz (res_beta )
304326 return qml .probs (wires = dev .wires )
305327
328+
306329######################################################################
307330# Running this circuit gives us the following probability distribution:
308331#
309332
310333probs = prob_circuit ()
311- plt .bar (range (2 ** len (dev .wires )), probs )
334+ plt .bar (range (2 ** len (dev .wires )), probs )
312335plt .xlabel ("Bit string" )
313336plt .ylabel ("Measurement Probability" )
314337plt .show ()
@@ -320,7 +343,7 @@ def prob_circuit():
320343#
321344
322345graph = nx .Graph (edges )
323- cmap = ["#00b4d9" ]* 3 + ["#e377c2" ]* 2
346+ cmap = ["#00b4d9" ] * 3 + ["#e377c2" ] * 2
324347positions = nx .spring_layout (graph , seed = 1 )
325348nx .draw (graph , with_labels = True , node_color = cmap , pos = positions )
326349plt .show ()
@@ -411,30 +434,33 @@ def prob_circuit():
411434# Creates the cost and mixer Hamiltonians
412435cost_h , mixer_h = qaoa .max_clique (new_graph , constrained = False )
413436
437+
414438# Creates a layer of QAOA
415439def qaoa_layer (gamma , beta ):
416440 qaoa .cost_layer (gamma , cost_h )
417441 qaoa .mixer_layer (beta , mixer_h )
418442
443+
419444# Creates the full QAOA circuit as an executable cost function
420445def qaoa_circuit (params , ** kwargs ):
421446 for w in dev .wires :
422447 qml .Hadamard (wires = w )
423448 qml .layer (qaoa_layer , depth , params [0 ], params [1 ])
424449
425450
426- @qml .qnode (dev , interface = "autograd" )
451+ @qml .qnode (dev )
427452def qaoa_expval (params ):
428453 qaoa_circuit (params )
429454 return qml .expval (cost_h )
430455
456+
431457######################################################################
432458# Now all we have to do is run FALQON for :math:`5` steps to get our initial QAOA parameters.
433459# We set :math:`\Delta t = 0.02:`
434460
435461delta_t = 0.02
436462
437- res , res_energy = max_clique_falqon (new_graph , depth - 1 , 0.0 , delta_t , dev )
463+ res , res_energy = max_clique_falqon (new_graph , depth - 1 , 0.0 , delta_t , dev )
438464
439465params = np .array ([[delta_t for k in res ], [delta_t * k for k in res ]], requires_grad = True )
440466
@@ -455,13 +481,15 @@ def qaoa_expval(params):
455481# define a circuit which outputs the probabilities of measuring each bit string, and
456482# create a bar graph:
457483
458- @qml .qnode (dev , interface = "autograd" )
484+
485+ @qml .qnode (dev )
459486def prob_circuit (params ):
460487 qaoa_circuit (params )
461488 return qml .probs (wires = dev .wires )
462489
490+
463491probs = prob_circuit (params )
464- plt .bar (range (2 ** len (dev .wires )), probs )
492+ plt .bar (range (2 ** len (dev .wires )), probs )
465493plt .xlabel ("Bit string" )
466494plt .ylabel ("Measurement Probability" )
467495plt .show ()
0 commit comments