<jupyter_start><jupyter_text>Before you begin, execute this cell to import numpy and packages from the D-Wave Ocean suite, and all necessary functions for the gate-model framework you are going to use, whether that is the Forest SDK or Qiskit. In the case of Forest SDK, it also starts the qvm and quilc servers.<jupyter_code>%run -i "assignment_helper.py"
%matplotlib inline<jupyter_output><empty_output><jupyter_text>Quantum annealing**Exercise 1** (2 points). The thermal state $\rho=\frac{1}{Z} \sum_n e^{-E_n/T} |n\rangle \langle n|$ is a Boltzmann distribution over the energy eigenvalues $E_n$ of some Hamiltonian $H$. An open quantum system equilibrates with the environment to this state. Equilibration is hard to simulate classically and therefore this process could be exploited for calculations. Using dimod, create a random Ising model over 5 spins.<jupyter_code>### BEGIN SOLUTION
n_spins = 5
h = {v: np.random.uniform(-2, 2) for v in range(n_spins)}
J = {}
for u, v in itertools.combinations(h, 2):
    if np.random.random() < .05:
        J[(u, v)] = np.random.uniform(-1, 1)
model = dimod.BinaryQuadraticModel(h, J, 0.0, dimod.SPIN)
### END SOLUTION
assert isinstance(model, dimod.binary_quadratic_model.BinaryQuadraticModel), "Wrong model type"
assert model.vartype == dimod.SPIN, "Wrong variables: binary model instead of spin system"
assert len(model.variables) == 5, "Wrong number of spins"<jupyter_output><empty_output><jupyter_text>**Exercise 2** (2 points). Sample the model at near-zero temperature (0.01) and at a high temperature (100). Write the list of energies in two arrays, `energies_low` and `energies_high`. Note that $\beta$ denotes the inverse temperature $1/T$.<jupyter_code>n_samples = 100
sampler = dimod.SimulatedAnnealingSampler()
### BEGIN SOLUTION
temperature_0 = 0.01
response = sampler.sample(model, beta_range=[1/temperature_0, 1/temperature_0], num_reads=n_samples)
energies_low = [solution.energy for solution in response.data()]
temperature_1 = 100
response = sampler.sample(model, beta_range=[1/temperature_1, 1/temperature_1], num_reads=n_samples)
energies_high = [solution.energy for solution in response.data()]
### END SOLUTION
assert np.isclose(min(energies_low), max(energies_low)), "At near-zero temperature, we should only see one energy value, the ground state energy"
assert np.isclose(min(energies_low), min(energies_high)), "The ground state should appear at both temperatures"<jupyter_output><empty_output><jupyter_text>Just as we saw in the introduction, the distribution flattens out at a high temperature ($T_3$). On the other hand, the energy is peaked for a low temperature, and we do not even have samples for high-energy states.  Quantum Approximate Thermalization**Exercise 3** (3 points). In QAOA, we approximated the ground state of a target Hamiltonian, starting from the ground state of a mixer Hamiltonian. We can actually perform the exact same optimization for approximating the thermal state of a target system, starting from the thermal state of some system.We exploit that if we trace out a subsystem of an entangled system, we end up with a mixed state.It can be shown that $| \psi \rangle =1/\sqrt{2 \cosh \frac{1}{2T}} \sum_{z \in {-1,1}} e^{- z/T} |z \rangle_{\mathcal{H_1}} \otimes | z \rangle_{\mathcal{H_2}}$ purifies $\rho=\frac{1}{Z}e^{- H_m/T}$ [[1](1)], where $H_m$ is the mixing Hamiltonian in QAOA. This state can be built with a circuit composed uniquely of RX gates and CNOT gates. Since the overall structure of the algorithm resembles QAOA closely, it is less important to go through the details. Most of it is more about forcing the quantum computing packages to do what we want than about the problem itself. The biggest difference to QAOA is the preparation of the initial state. Prepare this state at inverse temperature $\beta=0$ in your preferred framework. Note that $\beta=1/T$, so this corresponds to the infinite temperature. The state preparation should be in an object called `circuit`.<jupyter_code>n_qubits = 1
β = 0
### BEGIN SOLUTION
qr = QuantumRegister(n_qubits * 2)
cr = ClassicalRegister(n_qubits)
circuit = QuantumCircuit(qr, cr)
α = 2 * np.arctan(np.exp(- β))
for i in range(n_qubits):
    circuit.rx(α, qr[n_qubits+i])
    circuit.cx(qr[n_qubits+i], qr[i])
# Forest version
# circuit = Program()
# α = 2 * np.arctan(np.exp(- β))
# for i in range(n_qubits):
#     circuit += RX(α, n_qubits+i)
#     circuit += CNOT(n_qubits+i, i)
### END SOLUTION  
amplitudes = get_amplitudes(circuit)
assert np.allclose(amplitudes, np.array([1/np.sqrt(2), 0, 0, -1j/np.sqrt(2)]))<jupyter_output><empty_output><jupyter_text>**Exercise 4** (1 points).  Create the density matrix for this quantum state in a variable called `dm`.<jupyter_code>### BEGIN SOLUTION
amplitudes = amplitudes.reshape(4, 1)
dm = amplitudes.dot(amplitudes.T.conj())
### END SOLUTION
### BEGIN HIDDEN TESTS
assert np.allclose(dm, np.array([[0.5+0.j , 0. +0.j , 0. +0.j , 0. +0.5j],
                                 [0. +0.j , 0. +0.j , 0. +0.j , 0. +0.j ],
                                 [0. +0.j , 0. +0.j , 0. +0.j , 0. +0.j ],
                                 [0. -0.5j, 0. -0.j , 0. -0.j , 0.5+0.j ]]))
### END HIDDEN TESTS<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11614135625596944, 'fraction_numerical': 0.021012416427889206, 'mean_word_length': 4.386831275720165, 'pattern_counts': {'":': 0, '<': 22, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 5, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '144817', 'license': 'MIT', 'n_tokens_mistral': 1789, 'n_tokens_neox': 1660, 'n_words': 623, 'path': 'coding_assignments/solutions/08_Sampling_a_Thermal_State.ipynb', 'repo_name': 'pedrorohde/qml-mooc', 'size': '5235'}