Simulating spin chains and programmable quantum
matter using a D-Wave OneTM System.
Aim, audience and required background
This material was developed to help those interested in the physics of the D-Wave OneTM System to perform some simple experiments and explore some of the interesting science behind the processor technology. This tutorial series is designed to introduce you to quantum computer programming using a practical, hands-on approach. By the end of this tutorial you should understand the way that the processor operates on a device level, and have learned how to create, manipulate and measure the properties of spin chains.
All of the software necessary for developers to begin learning how to program D-Wave quantum computing systems can be downloaded from the DOWNLOAD DEVKITS page. As the developer portal is currently in BETA test stage, you will need to have a D-Wave developer account to download these packs. To follow this tutorial it may be helpful to have read two of the previous installments: Quantum Computing and Energy Programming Primer and Getting Started: Installing the D-Wave developer pack and an introductory 'hello multiverse' tutorial.
The high level programming language used in the included examples and source code is Python 2.6. Some experience of using the Python programming language is helpful but not required.
What you will learn
By following through the material in this white paper, you will learn:
In order to proceed you should install several free software packages.
1.1 - Introduction
Each qubit element on a D-Wave processor is physically realized as a programmable spin, making D-Wave's processor a fabric upon which interesting physics experiments can be performed. Because the processor embodies what is known as the Ising model, it is possible to use it as a quantum physics simulator, in addition to operating in the native problem solving mode (as discussed in previous tutorials).
D-Wave's quantum processor is constructed from a number of qubits and couplers, connected together in a fabric, as shown in Figure ###. An energy program is supplied by assigning numbers to these qubits and couplers. In the Rainier processor architecture there are 128 qubits and 352 couplers. In this tutorial we will program only a small subset of this large fabric, a unit cell containing 8 spins. Figure 1 shows how the 8 spins are natively connected in the processor architecture. The qubits numbering scheme is shown in red, our spin notation (S1-8) shown in black is mapped on to these qubit numbers.
Figure 1.The connectivity graph of a single 'unit cell' within a Rainier processor
containing a group of 8 qubits (spins). The qubits are connected in a bipartite graph structure
and their numbering scheme is shown.
How programmable spin chains work
A set of biases (h_i) and a set of coupling strengths (J_ij) can be applied to each qubit (circle) and coupler (line). When the system is annealed, the quantum computer will select the best configuration of spins (+1, -1) that minimize the energy of the system subject to the h and J values. We can apply many different h and J values, and observe the resulting spin chains that arise.
1.2 - Creating a ferromagnetic spin chain
Let's start by creating a simple ferromagnetic spin chain. We would like to set all the spin biases (h_i) to zero (no local or global fields) and all the couplings we want to include in the chain to fully ferromagnetic. Couplings that are not required for the spin chain should be set to zero.
Figure 2.Creating a ferromagnetic spin chain from the unit cell architecture
The spin chain shown in Figure 1 can be implemented using the following code snippet:
Code Snippet 1: Building a ferromagnetic spin chain
# Import D-Wave's Python API from dwave_sapi import local_connection solver = local_connection.get_solver('c4-sw_sample') #define the problem h = *128 J = dict() #we will use a chain of 8 spins in these experiments spins = [48, 49, 50, 51, 52, 53, 54, 55] couplings = [(48,52),(52,49),(49,53),(53,50),(50,54),(54,51),(51,55)] for spin in spins: h[spin] = 0 for coupling in couplings: J[coupling] = -1 answer = solver.solve_ising(h,J,num_reads = 1)['solutions'] print '48 = ', answer print '52 = ', answer print '49 = ', answer print '53 = ', answer print '50 = ', answer print '54 = ', answer print '51 = ', answer print '55 = ', answer
Run this code snippet using the D-Wave SAPI library and your Python interface. You should find that the answers returned will all be the same, either a string of eight +1's or eight -1's. Why are there two answers? We did not specify any bias on the spins themselves, the only condition that needs to be fulfilled is the ferromagnetic coupling, i.e. the spins prefer to align in the same direction. But with no h bias on the spins, they don't care which direction they align in. Run the code snippet a few times and see if you can get both +1 and -1 states. This demonstrates the probabilistic nature of the processor - it does not necessarily return the same answer each time a computation is run.
1.3 - Creating an antiferromagnetic spin chain
To create the antiferromagnetic version, we simply change all the couplings from J=-1 to J=+1. This involves modifying just one line of the above code:
J[coupling] = +1
This time all the couplings are set to antiferromagnetic, so neighbouring spins should prefer to be anti-aligned.
Figure 3.Creating an anti-ferromagnetic spin chain from the unit cell architecture
Running the program again should result in a configuration of alternating +1 and -1 answers returned. You should check that this is indeed the case. One preferred spin configuration is shown schematically in Figure 3. Again there should be a second prefered states (in which every spin is reversed) which is degenerate with the first state due to the h bias being set to 0. Run the code a few times to check that both states are returned.
1.4 - Simulating a domain wall
Domain walls occur in spin chains between regions that prefer the spin up configuration and regions that prefer the spin down configuration. We will now force a domain wall to occur in our spin chain. Up until now we have not set any bias on the individual spins, i.e. the h value has always remained set to 0. By setting each end of the chain to have strong and opposite h biases and keeping all the links ferromagnetic, we see that somewhere along the chain the ferromagnetic link must be broken. There is now frustration in the chain, i.e. it is impossible to create a chain that meets all the demands of the conditions on h and J.
Figure 4.Forcing a domain wall to occur in the spin chain by biasing each end of a
ferromagnetic chain in opposite directions.
Let's see what happens when we program this spin chain into the hardware. Again not much of the code will change, just the part where the h values are applied. We now need two extra lines of code that apply the additional h biases at the ends of the chain:
Code Snippet 2: Creating a domain wall in the ferromagnetic spin chain
# Import D-Wave's Python API from dwave_sapi import local_connection solver = local_connection.get_solver('c4-sw_sample') #define the problem h = *128 J = dict() #we will use a chain of 8 spins in these experiments spins = [48, 49, 50, 51, 52, 53, 54, 55] couplings = [(48,52),(52,49),(49,53),(53,50),(50,54),(54,51),(51,55)] for spin in spins: h[spin] = 0 h = +2 #force the ends of the chain in opposite directions h = -2 for coupling in couplings: J[coupling] = -1 answer = solver.solve_ising(h,J,num_reads = 1)['solutions'] print '48 = ', answer print '52 = ', answer print '49 = ', answer print '53 = ', answer print '50 = ', answer print '54 = ', answer print '51 = ', answer print '55 = ', answer
Run this code several times. You should see that the domain wall (the position where the -1's meet the +1's) moves around with each run of the code. This is because the 'twist' in the chain can occur anywhere along the chain.
1.5 - Pinning the domain wall
Now that we have seen that the domain wall can exist in several places along the chain, let's try to make it prefer one position over the others. This simulates a real phenomenon which occurs in condensed matter physics systems (such as magnetic materials). Domain walls can become stuck near areas where defects are present in the material. This occurs because a defect in a material can appear to a domain wall as an area of lower energy. The domain wall will prefentially sit at such positions, a process known as pinning.
In order to create our pinning site, we need to lower the energy seen by the domain wall at a point along the chain. We can do this by adjusting one of the ferromagnetic links. If we lower the energy of one of the J terms, it will cost less energy for the whole spin system (the frustration will be lowered) if the domain wall arises at this point. As the system tries to lower the overall energy, we should see the domain wall preferentially choose this site.
Figure 5.Pinning the domain wall by lowering the energy of a link in the chain
Adjust the code so that the 54-51 coupling is set to -0.5 instead of -1.
You should find that when you run the code now, the domain wall is much more likely to occur in the position between 54 and 51. Notice that it doesn't always end up pinned in the right place - it is just more likely to. This is due to the finite temperature of the chip, which we will explore in the next section.
1.6 - Changing the energy scales
Let's go back to the ferromagnetic chain with zero h bias to illustrate a second interesting feature of the processor's ability to simulate real physics.
Imagine that we wanted to investigate the behaviour of the spin chain as a function of tempertaure. The temperature at which the chip operates - i.e. the temperature at which you are simulating the quantum spin chain - is around 40mK. Although it is possible to change the temperature of the physical chip by applying a small but steady heat current into the fridge system, this is a lengthy process as the refrigerator takes a long time to equilibrate. But there is another way we can mimic changing the temperature of the system without actually applying any heat.
Recall the numbers applied to the h and J biases. These values have the dimensionality of energy (remember we adjusted one to cause a low-energy domain wall pinning site). The energy scale of the spin chain can be compared to thermal energy of the system kT. By lowering ALL the h and J values in the spin chain in the same ratio, we are effectively increasing the temperature. For example, halving all h and J values has the same effect as doubling the temperature of the spin chain.
Let's see this in action.
Revert the code back to snippet 1 - the ferromagnetic chain. Now change the code so that the energy scale is lowered by changing the ferromagnetic link values to -0.1. This is equivalent to raising the temperature of the chain by a factor of ten. (Note that as h is set to zero we do not need to adjust it).
J[coupling] = -0.1
Now run the code snippet. You should find that the chain does not always return +1's and -1's, but now there should be states where +1 and -1 occur along the chain. We have increased the effective temperature of the chain now that there is enough energy in the system to overcome the ferromagnetic condition. When the temperature is high enough domain walls spontaneously arise along the spin chain.
1.7 - A simple phase transition experiment
Let's take this process a little further and see what happens in more detail between these two states. We would like to see graphically what happens to the spin chain as we change J, effectively adjusting the temperature of the spin chain. Because the spin chain configuration that arises is probabilistic, we will look at a statistical property of the system. In order to do this we will take many samples and compute an average measure of the 'magnetization' of the chain.
Code Snippet 3: Observing a phase transition
from __future__ import division # Import D-Wave's Python API from dwave_sapi import local_connection solver = local_connection.get_solver('c4-sw_sample') #define the problem h = *128 #we will use a chain of 8 spins in these experiments J = dict() spins = [48, 49, 50, 51, 52, 53, 54, 55] couplings = [(48,52),(52,49),(49,53),(53,50),(50,54),(54,51),(51,55)] for spin in spins: h[spin] = 0 num_points = 20 magArray =  for i in range (0,num_points): for coupling in couplings: J[coupling] = (i*-1/num_points)-1/num_points netMagnetization = 0 for k in range(0,1000): answer = solver.solve_ising(h,J,num_reads = 1)['solutions'] netMagnetization += abs(answer + answer + answer + answer + answer + answer + answer + answer) magArray.append(netMagnetization/80) print 'percentage magentization at J', J[(48,52)], ' ....... ', magArray[i]
The above code takes 20 values of J from -0.05 to -1.0, takes 1000 samples at each J value and returns the average magnetization, which is computed by adding the spin values returned. A magnetization of 0% means that an equal number of spins are pointing up and down, and a value of 100% means that the spins are all polarized in the same direction.
You can plot the results using the Python package matplotlib.
import matplotlib.pyplot as plt plt.plot(magArray, 'ro-') plt.ylabel('% magnetization of spin chain') plt.xlabel('J value') plt.title('Magnetization of a spin chain - example') plt.show()
Figure 6.Observing the magnetization of the chain as a function of the energy scale, J.
A lower J value is equivalent to a higher temperature.
By lowering the temperature (increasing J), the spin chain transitions from being dominated by thermal fluctuations which try to randomize the orientation of each spin, to a regime where the ferromagnetic interactions dominate and the spins want to align. This is the Curie transition of the spin chain. We can see that the Curie 'temperature' of the spin chain is equivalent to a J of around 3.0.