<jupyter_start><jupyter_text>Basic SQWalk commandsIn this notebook we will introduce the `SQWalker` class and review its most important features.After you successfully installed the `sqwalk` package following the [instructions](https://github.com/Buffoni/SQWalk), you are ready to start simulating quantum walkers by importing the `SQWalker` class as follows:<jupyter_code>from sqwalk import SQWalker<jupyter_output><empty_output><jupyter_text>Then we import some other libraries that will come handy in the rest of the notebook. As `sqwalk` is based on `QuTiP`, we will make use of some of its functions. In this notebook, we will additionally use `networkx` as a reference package to deal with graphs.<jupyter_code>from qutip import ket2dm, basis, Options, Qobj
import matplotlib.pyplot as plt
import networkx as nx
import numpy as np<jupyter_output><empty_output><jupyter_text>Create the graphFirst we are going to create the graph on which our walker will move. We will start by defining a simple line graph (called `path_graph` in `networkx`) with $50$ nodes, we will then plot the graph as well as its adjacency matrix.<jupyter_code>#Number of nodes in the line
N = 50
#Build the graph using networkx (or the module of your choice)
graph = nx.path_graph(N)
nx.draw_circular(graph)
plt.show()
# Plot the adjacency matrix
adj = nx.adj_matrix(graph).todense()
plt.imshow(adj)
plt.show()<jupyter_output><empty_output><jupyter_text>Initialize and run the walkerWe have thus associated th graph topology to the adjancency matrix of the graph $A$, whose elements $A_{ij}$ are $1$ is there is a link between the node $i$ and $j$, and $0$ otherwise.The walker object will then evolve according to the following master equation:> $ \frac{d \rho}{d t} = (1-p)\ \mathcal{L}_{QW} (\rho) + p\  \mathcal{L}_{CRW} (\rho) + \mathcal{L}_{sink} (\rho)$where $\mathcal{L}_{QW} (\rho) = - i[A,\rho]$ describes the coherent hoping mechanisms, $\mathcal{L}_{CRW} (\rho) = \sum_{i,j} L_{ij} \rho L_{ij}^{\dagger} - \frac{1}{2} \{L_{ij}^\dagger L_{ij}, \rho\}$ with $L_{ij} = (A_{ij}/d_j)|i>$ is the element of the basis vectors (in the Hilbert space) corresponding to the node $i$.The parameter $p$ describes how much incoherent the walker evolution is. In particular, when $p=1$ one recovers the model of a classical random walk, when $p=0$ one faces with a quantum walk, while when $0<p<1$ the walker hops via both incoherent and coherent mechanisms (stochastic quantum walker).As of now we will leave the sink_node parameter to its default value (None) and we will come back at it later in the notebook.<jupyter_code># Inizialize the quantum walker that will move on the graph
walker = SQWalker(np.array(adj), noise_param=0.0, sink_node=None)<jupyter_output><empty_output><jupyter_text>Now we can simply run the walker by specifying the initial node and the lenght of the evolution. We can time it to see how fast it executes the desired number of steps.<jupyter_code># Run the walker for 1000 steps starting from the middle node
# time the run to check efficiency
%%time
time_samples = 1000
initial_node = N // 2
result = walker.run_walker(initial_node, time_samples)
new_state = result.final_state
print('Executed', time_samples ,'steps of SQW.')<jupyter_output><empty_output><jupyter_text>Note that instead of initializing the initial node of the walker using an integer, we can pass a generic density matrix as initial state of the walker by using qutip.<jupyter_code>%%time
time_samples=1000
initial_quantum_state = ket2dm(basis(N, N//2)) #density matrix equivalent of setting initial_node = N // 2
result = walker.run_walker(initial_quantum_state, time_samples)
new_state = result.final_state
print('Executed', time_samples ,'steps of SQW.')<jupyter_output><empty_output><jupyter_text>We can now plot the population in each node by simply taking the diagonal of the final density matrix.<jupyter_code>nodelist = [i for i in range(N)]
plt.bar(nodelist, new_state.diag())
plt.show()<jupyter_output><empty_output><jupyter_text>We can then quickly compare this result with an identical classical walker (i.e. setting `noise_param=1`).<jupyter_code>walker = SQWalker(np.array(adj), noise_param=1, sink_node=None)
result = walker.run_walker(initial_node, time_samples)
plt.bar(nodelist, result.final_state.diag())
plt.show()<jupyter_output><empty_output><jupyter_text>Noise and sinksIn general one can pick any value of `noise_param` in the interval $[0,1]$ to get a stochastic quantum walker.In addition to that we can add a *sink*. But what is it?A sink is an additional node where the population of the walker is irreversibly stored, we can think of it as a "trap" for our walker or an "exit" from the graph. With `SQWalk` we can specify which node of the graph is connected to the sink via the parameter `sink_node` (multiple sink nodes will come in a following version) which takes as an input the index of the node irreversibly connected to the sink.Let's pick up again our stochastic quantum walker on a line and attach a sink to the $10th$ node.<jupyter_code># Inizialize the stochastic quantum walker with a sink connected to the 10th node
walker = SQWalker(np.array(adj), noise_param=0.15, sink_node=10)

opts = Options(store_states=True, store_final_state=True)
result = walker.run_walker(N//2, time_samples=2000, opts=opts)
new_state = result.final_state
result.final_state.shape<jupyter_output><empty_output><jupyter_text>We see that now the dimension of the final density matrix is $(51,51)$ instead of $(50,50)$, that is because the sink has been attached to the system. Indeed we can visualize the behavior of the sink by plotting the population on the nodes and see that after some time most of the population is irreversibly in the sink (the last bar).<jupyter_code>nodelist = [i for i in range(N+1)]
plt.bar(nodelist, new_state.diag())
plt.show()<jupyter_output><empty_output><jupyter_text>Since the `run_walker` function is based on the `mesolve` solver of QuTip, one can pass some `Options` to the function in the same way in which are passed to `mesolve`.As you can see above, we asked to store all the indermediate states *and* the final state, in this way we can explore the dynamics of the walker at previous timesteps. Or even use the whole data to create beautiful animations and overlays to the graph.<jupyter_code># show state at step 1000
plt.bar(nodelist, result.states[1000].diag())
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08757763975155279, 'fraction_numerical': 0.010403726708074533, 'mean_word_length': 5.653925619834711, 'pattern_counts': {'":': 0, '<': 47, '<?xml version=': 0, '>': 47, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '605641', 'license': 'Apache-2.0', 'n_tokens_mistral': 2027, 'n_tokens_neox': 1914, 'n_words': 827, 'path': 'tutorials/01-BasicCommands.ipynb', 'repo_name': 'Buffoni/SQWalk', 'size': '6440'}