<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 This tutorial will setup the necessary system for running a simple graphene.
Step1:  Instead of manually defining the graphene system with associated atomic coordinates and lattice vectors we use the build-in sisl capability of defining the graphene structure with a default atomic distance of $d = 1.42\,A$.
Step2:  Some basic information of the geometry is shown above
Step3:  Now H is a Hamiltonian object. It works equivalently to a matrix and one may assign elements and extract elements as though it were a matrix, we will return to the intricate details of the Hamiltonian object later.
There are only 3 more things that specifies the Hamiltonian. 
1. non-zero is the current number of non zero elements specified in the Hamiltonian
2. orthogonal specifies whether an overlap matrix is associated with the Hamiltonian, i.e. False for a non-orthogonal basis-set.
3. The number of spin-components are specified via the Spin class which in this case is an unpolarized system. I.e. only one spin-component.
4. The Geometry class is repeated because the Hamiltonian is associated with the Geometry passed when instantiating the Hamiltonian (sisl.Hamiltonian(graphene)).
You are now ready to add matrix elements to the Hamilton.
Explicilty specifying the matrix elements
In this small sub-section we will specify the required matrix elements manually. You should already now know that we only plan on adding nearest neighbour interactions.
We will proceed with an on-site of $0\,\mathrm{eV}$ and a coupling element of $t=-2.7\,\mathrm{eV}$.
First we set the on-site elements
Step4:  Now we need to set the coupling elements
Step5:  This will only couple the first and second atom in the primary unit-cell. But we also require couplings from the primary unit-cell to the neighbouring supercells. Remember that nsc = [3, 3, 1].
In this case we know that the first atom couples to the (-1, 0) and (0, -1), while the second atom couples to (1, 0) and (0, 1).
The coupling elements may then be specified via
Step6:  Now all matrix elements are set, i.e. 2 on-site and 6 nearest neighbour couplings, lets assert this
Step7:  We find 8 non-zero elements, as there should be. Remark, even though we set the on-site terms to $0$, they are interpreted as non-zero elements due to explicitly setting them.
After having setup the Hamilton, we may easily calculate the eigenvalues at any $\mathbf k$ (in reduced coordinates $\mathbf k\in]-0.5
Step8:  Looping the atoms and orbitals in the Hamiltonian
The above specification of the elements is tedious for anything with more elements than 2 orbitals. There are easier and better ways to set all the coupling elements.
In the following we will introduce a new function
Step9:  The above loop is equivalent to the previously explicitly set values, so printing the structure will yield the same information, we have just specified all values again.
The above loop is also only 4 lines of code, irrespective of the number of atoms in the geometry.
Step10:  After having setup the Hamilton, we may easily calculate the eigenvalues at any $\mathbf k$ (in reduced coordinates $\mathbf k\in]-0.5
Step11:  We may also create a bandstructure of the Hamiltonian.
First we define the path we wish to take in the Brillouin zone. Start at $\Gamma$, continue to $K$, then $M$ and finish where we started.
We will calculate the band-structure with a total of 301 points and we explicitly name the special points.
Step12:  Now eigs contains all the eigenvalues of the Hamiltonian object for all the $k$-points.
<ASSISTANT_TASK:>
Python Code:
import sisl
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
Explanation: This tutorial will setup the necessary system for running a simple graphene.
End of explanation
graphene = sisl.geom.graphene()
print(graphene)
Explanation: Instead of manually defining the graphene system with associated atomic coordinates and lattice vectors we use the build-in sisl capability of defining the graphene structure with a default atomic distance of $d = 1.42\,A$.
End of explanation
H = sisl.Hamiltonian(graphene)
print(H)
Explanation: Some basic information of the geometry is shown above:
It contains 2 atoms, na is number of atoms
It contains 2 orbitals, no is number of orbitals
Then there is a list of unique atoms associated. In this case there is 1 unqiue specie, species: 1 while the subsequent lines specifies the number of times it is occuring in the Geometry. 
nsc specifies the number of supercells which is a practical way of dealing with periodic structures.
In this case we have periodicity along the first and second lattice vector. And there are 3 images along each of these directions. The primary, and one at $+$ and $-$. It should be apparent that the values in nsc are always uneven numbers. Note that for molecules it is always [1, 1, 1].
The last item, maxR is the maximal orbital range in the geometry. In this case it is equal to maxR as listed for the atomic Carbon specie. However, in case there are more than one specie it will be the maximum for the individual species.
Now we are ready to proceed with creating the Hamiltonian for the graphene lattice. But before doing so you should now consider which type of system we are going towards? Are we going to implement a nearest, next nearest or third nearest neighbour interaction?
HINT: this should be apparent from the maxR variable above.
End of explanation
H[0, 0] = 0.0
H[1, 1] = 0.0
Explanation: Now H is a Hamiltonian object. It works equivalently to a matrix and one may assign elements and extract elements as though it were a matrix, we will return to the intricate details of the Hamiltonian object later.
There are only 3 more things that specifies the Hamiltonian. 
1. non-zero is the current number of non zero elements specified in the Hamiltonian
2. orthogonal specifies whether an overlap matrix is associated with the Hamiltonian, i.e. False for a non-orthogonal basis-set.
3. The number of spin-components are specified via the Spin class which in this case is an unpolarized system. I.e. only one spin-component.
4. The Geometry class is repeated because the Hamiltonian is associated with the Geometry passed when instantiating the Hamiltonian (sisl.Hamiltonian(graphene)).
You are now ready to add matrix elements to the Hamilton.
Explicilty specifying the matrix elements
In this small sub-section we will specify the required matrix elements manually. You should already now know that we only plan on adding nearest neighbour interactions.
We will proceed with an on-site of $0\,\mathrm{eV}$ and a coupling element of $t=-2.7\,\mathrm{eV}$.
First we set the on-site elements:
End of explanation
H[0, 1] = -2.7
H[1, 0] = -2.7
Explanation: Now we need to set the coupling elements:
End of explanation
H[0, 1, (-1, 0)] = -2.7
H[0, 1, (0, -1)] = -2.7
H[1, 0, (1, 0)] = -2.7
H[1, 0, (0, 1)] = -2.7
Explanation: This will only couple the first and second atom in the primary unit-cell. But we also require couplings from the primary unit-cell to the neighbouring supercells. Remember that nsc = [3, 3, 1].
In this case we know that the first atom couples to the (-1, 0) and (0, -1), while the second atom couples to (1, 0) and (0, 1).
The coupling elements may then be specified via:
End of explanation
print(H)
Explanation: Now all matrix elements are set, i.e. 2 on-site and 6 nearest neighbour couplings, lets assert this:
End of explanation
print("Gamma:", H.eigh())
print("K:", H.eigh(k=[2./3,1./3,0]))
Explanation: We find 8 non-zero elements, as there should be. Remark, even though we set the on-site terms to $0$, they are interpreted as non-zero elements due to explicitly setting them.
After having setup the Hamilton, we may easily calculate the eigenvalues at any $\mathbf k$ (in reduced coordinates $\mathbf k\in]-0.5:0.5]$).
End of explanation
for ia, io in H.geometry.iter_orbitals(local=False):
    # This loops over all atoms and the orbitals
    # corresponding to the atom.
    # In this case the geometry has one orbital per atom, hence
    #   ia == io
    # in all cases.
    
    # In order to figure out which atoms atom `ia` is connected
    # to, we must find those atoms.
    # To do this we access the geometry attached to the 
    # Hamiltonian (H.geom)
    # and use a function called `close` which returns ALL 
    # atomic indices within certain ranges of a given point or atom
    idx = H.geometry.close(ia, R = [0.1, 1.43])
    # the argument R has two entries:
    #   0.1 and 1.43
    # Each value represents a radii of a sphere.
    # The `close` function will then return
    # a list of equal length of the R argument (i.e. a list with
    # two values).
    # idx[0] is the first element and is also a list
    # of all atoms within a sphere of 0.1 AA of atom `ia`.
    # This should obviously only contain the atom it-self.
    # The second element, idx[1], contains all atoms within a sphere
    # with radius of 1.43 AA, but not including those within 0.1 AA.
    # In this case this is then all atoms that are the nearest neighbour
    # atoms.
   
    # Now we know the on-site atoms (idx[0]) and the nearest neighbour
    # atoms (idx[1]), all we need to do is set the Hamiltonian
    # elements:
    # on-site (0. eV)
    H[io, idx[0]] = 0.
   
    # nearest-neighbour (-2.7 eV)
    H[io, idx[1]] = -2.7
Explanation: Looping the atoms and orbitals in the Hamiltonian
The above specification of the elements is tedious for anything with more elements than 2 orbitals. There are easier and better ways to set all the coupling elements.
In the following we will introduce a new function:
 Geometry.close
which is a very convenient function to return all atomic indices within a certain radii from a given coordinate.
End of explanation
print(H)
Explanation: The above loop is equivalent to the previously explicitly set values, so printing the structure will yield the same information, we have just specified all values again.
The above loop is also only 4 lines of code, irrespective of the number of atoms in the geometry.
End of explanation
print("Gamma:", H.eigh())
print("K:", H.eigh(k=[2./3,1./3,0]))
Explanation: After having setup the Hamilton, we may easily calculate the eigenvalues at any $\mathbf k$ (in reduced coordinates $\mathbf k\in]-0.5:0.5]$).
End of explanation
band = sisl.BandStructure(H, [[0., 0.], [2./3, 1./3],
                              [1./2, 1./2], [1., 1.]], 301,
                              [r'$\Gamma$', 'K', 'M', r'$\Gamma$'])
eigs = band.apply.array.eigh()
Explanation: We may also create a bandstructure of the Hamiltonian.
First we define the path we wish to take in the Brillouin zone. Start at $\Gamma$, continue to $K$, then $M$ and finish where we started.
We will calculate the band-structure with a total of 301 points and we explicitly name the special points.
End of explanation
# Retrieve the tick-marks and the linear k points
xtick, xtick_label = band.lineartick()
lk = band.lineark()
plt.plot(lk, eigs)
plt.ylabel('Eigenspectrum [eV]')
plt.gca().xaxis.set_ticks(xtick)
plt.gca().set_xticklabels(xtick_label)
# Also plot x-major lines at the ticks
ymin, ymax = plt.gca().get_ylim()
for tick in xtick:
    plt.plot([tick,tick], [ymin,ymax], 'k')
Explanation: Now eigs contains all the eigenvalues of the Hamiltonian object for all the $k$-points.
End of explanation
<END_TASK>