├── REQUIRE ├── .gitignore ├── src ├── HeisenbergED.jl ├── sparseS2.jl ├── sparseHermitian.jl ├── utils.jl ├── basis.jl ├── lattice.jl └── sparseHam.jl ├── README.md └── main ├── main.jl └── main.ipynb /REQUIRE: -------------------------------------------------------------------------------- 1 | --------- 2 | julia 0.5 3 | --------- 4 | julia packages: 5 | ParseArgs 6 | DataFrames 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | plots/ 2 | data/ 3 | .ipynb_checkpoints/ 4 | testing/* 5 | *.sh* 6 | *.out* 7 | temp/* 8 | -------------------------------------------------------------------------------- /src/HeisenbergED.jl: -------------------------------------------------------------------------------- 1 | 2 | include("utils.jl") 3 | include("lattice.jl") 4 | include("basis.jl") 5 | include("sparseHermitian.jl") 6 | include("sparseHam.jl") 7 | include("sparseS2.jl") 8 | -------------------------------------------------------------------------------- /src/sparseS2.jl: -------------------------------------------------------------------------------- 1 | # sparseS2.jl 2 | # sparse S^2 operator 3 | # Alan Morningstar 4 | # June 2017 5 | 6 | 7 | # function which takes in a matrix E whose columns are vectors and returns a 8 | # list of S^2 expectation values for those column vectors 9 | function S2expectations(basis::reducedBasis,s::sector,l::lattice,E::Array{Complex128,2}) 10 | 11 | # lattice 12 | Lx::Int64 = l.Lx 13 | Ly::Int64 = l.Ly 14 | N::Int64 = l.N 15 | # momentum 16 | kx::Float64 = s.kx 17 | ky::Float64 = s.ky 18 | # spin-inversion number 19 | z::Float64 = s.z 20 | 21 | # list of S^2 expectations to be filled 22 | numVecs::Int64 = size(E)[2] 23 | S2s = zeros(Complex128,numVecs) 24 | 25 | # basis type 26 | bType::Type = eltype(basis) 27 | 28 | # allocate memory once before the loops 29 | # ------------------------------------- 30 | b::bType = 0 31 | sb::Array{bType,1} = Array{bType,1}(N) 32 | nb::Float64 = 0.0 33 | S2bb::Complex128 = 0.0+0.0im 34 | sPwij::Array{Int64,2} = Array{Int64,2}(N,N) 35 | a::bType = convert(bType,0) 36 | aRep::bType = convert(bType,0) 37 | lx::Int64 = ly::Int64 = g::Int64 = 0 38 | aRepIndex::Int32 = Int32(0) 39 | S2ij::Complex128 = 0.0+0.0im 40 | # ------------------------------------- 41 | 42 | # loop over basis 43 | for bIndex::Int64 in 1:basis.dim 44 | 45 | # spin states of basis 46 | b = basis.b[bIndex] # integer rep. 47 | for bit in 1:l.N 48 | sb[bit] = readBit(b,bit) # spin array rep. 49 | end 50 | 51 | # normalization constant 52 | nb = basis.n[bIndex] 53 | 54 | # fill the sPwij table 55 | for i::Int64 in 1:N 56 | for j::Int64 in 1:(i-1) 57 | sPwij[i,j] = simplePower(sb[i]+sb[j]) 58 | end 59 | end 60 | 61 | # initialize the diagonal matrix element 62 | S2bb = 0.75*N + 0.0im 63 | 64 | # loop over pairs of lattice sites 65 | for i::Int64 in 1:N 66 | for j::Int64 in 1:(i-1) 67 | 68 | # contribute to the diagonal matrix element 69 | # ----------------------------------------- 70 | S2bb += 0.5*sPwij[i,j] 71 | 72 | # compute off diagonal matrix elements 73 | # ------------------------------------ 74 | 75 | if sb[i] != sb[j] 76 | # the bra 77 | a = XiXj(b,i,j) 78 | # the rep and translation of the bra 79 | aRep,lx,ly,g = representative(a,l) 80 | # search for this rep in the basis 81 | aRepIndex = basisIndex(aRep,basis) 82 | if aRepIndex != 0 83 | # the matrix element 84 | S2ij = 0.5*(1-sPwij[i,j])*(z^g)*exp(-1.0im*(kx*lx+ky*ly))*sqrt(basis.n[aRepIndex]/nb) 85 | # contributing to the output 86 | for vec in 1:numVecs 87 | S2s[vec] += E[bIndex,vec]*conj(E[aRepIndex,vec])*S2ij 88 | end 89 | end 90 | end 91 | 92 | end 93 | end 94 | 95 | # contributing to the output from the diagonal matrix elements 96 | for vec in 1:numVecs 97 | S2s[vec] += E[bIndex,vec]*conj(E[bIndex,vec])*S2bb 98 | end 99 | 100 | end 101 | 102 | return S2s::Vector{Complex128} 103 | end 104 | -------------------------------------------------------------------------------- /src/sparseHermitian.jl: -------------------------------------------------------------------------------- 1 | # sparseHermitian.jl 2 | # sparse Hermitian matrix type 3 | # Alan Morningstar 4 | # June 2017 5 | 6 | 7 | immutable sparseHermitian{TI<:Integer,TM<:Number} 8 | 9 | # NOTE NOTE NOTE: diagonal elements here should have been divided by 2 10 | # and only the upper triangle of the off diagonal 11 | # elements should be given 12 | 13 | # number of rows in the matrix 14 | nRows::Int64 15 | # number of columns in the matrix 16 | nCols::Int64 17 | # row numbers of the first non-zero value in each column 18 | colPntrs::Vector{Int64} 19 | # row indices of non-zero elements 20 | rowIndcs::Vector{TI} 21 | # non-zero matrix element values 22 | nzVals::Vector{TM} 23 | 24 | # NOTE: optional, use for matrices with few unique matrix element values 25 | # to save memory 26 | # non-zero matrix element pointers 27 | nzPntrs::Vector{TI} 28 | # is this a sparse pointer matrix? i.e. did you give nzPntrs? 29 | sparsePntrs::Bool 30 | 31 | # constructors 32 | 33 | # not a sparse matrix of pointers to matrix elements 34 | sparseHermitian{TI,TM}(nRows::Int64, nCols::Int64, colPntrs::Vector{Int64}, rowIndcs::Vector{TI}, nzVals::Vector{TM}) = new(nRows::Int64, nCols::Int64, colPntrs::Vector{Int64}, rowIndcs::Vector{TI}, nzVals::Vector{TM},Vector{TI}(),false) 35 | 36 | # a sparse matrix of pointers to matrix elements 37 | sparseHermitian{TI,TM}(nRows::Int64, nCols::Int64, colPntrs::Vector{Int64}, rowIndcs::Vector{TI}, nzVals::Vector{TM}, nzPntrs::Vector{TI}) = new(nRows::Int64, nCols::Int64, colPntrs::Vector{Int64}, rowIndcs::Vector{TI}, nzVals::Vector{TM}, nzPntrs::Vector{TI}, true) 38 | 39 | end 40 | 41 | 42 | ## methods 43 | 44 | 45 | # in place matrix * vector multiplication 46 | function Base.A_mul_B!(y::AbstractVector,M::sparseHermitian,x::AbstractVector) 47 | 48 | # NOTE NOTE NOTE: this assumes the sparse Hermitian matrix has been built 49 | # properly, with diagonal matrix elements given already 50 | # divided by 2, and only the upper triangle of the 51 | # off-diagonal elements given 52 | 53 | # clear output vector 54 | y .= 0.0 55 | 56 | # fill output vector 57 | if !M.sparsePntrs 58 | 59 | for j::Int64 in 1:M.nCols 60 | for i::Int64 in M.colPntrs[j]:(M.colPntrs[j+1]-1) 61 | y[M.rowIndcs[i]] += x[j]*M.nzVals[i] 62 | y[j] += x[M.rowIndcs[i]]*conj(M.nzVals[i]) 63 | end 64 | end 65 | 66 | else 67 | 68 | for j::Int64 in 1:M.nCols 69 | for i::Int64 in M.colPntrs[j]:(M.colPntrs[j+1]-1) 70 | y[M.rowIndcs[i]] += x[j]*M.nzVals[M.nzPntrs[i]] 71 | y[j] += x[M.rowIndcs[i]]*conj(M.nzVals[M.nzPntrs[i]]) 72 | end 73 | end 74 | 75 | end 76 | 77 | return y::AbstractVector 78 | 79 | end 80 | 81 | 82 | # size of the matrix representation 83 | Base.size(M::sparseHermitian) = (M.nRows, M.nCols) 84 | 85 | 86 | # this type is a 2D matrix 87 | Base.ndims(M::sparseHermitian) = 2 88 | 89 | 90 | # type of matrix elements 91 | Base.eltype(M::sparseHermitian) = eltype(M.nzVals) 92 | 93 | 94 | # Hermitian is not, in general, symmetric 95 | Base.issymmetric(M::sparseHermitian) = false 96 | 97 | 98 | # this type is for Hermitian matrices 99 | Base.ishermitian(M::sparseHermitian) = true 100 | 101 | 102 | # number of non-zero matrix elements 103 | Base.nnz(M::sparseHermitian) = max(length(M.nzVals),length(M.nzPntrs)) 104 | 105 | 106 | # matrix * vector multiplication 107 | Base.:*(M::sparseHermitian,x::AbstractVector) = Base.A_mul_B!(similar(x,promote_type(Base.eltype(M),eltype(x)),size(M,1)),M,x) 108 | 109 | 110 | # conjugate transpose matrix * vector multiplication 111 | # NOTE: same as M*v for Hermitian matrices 112 | Base.Ac_mul_B!(y::AbstractVector,M::sparseHermitian,x::AbstractVector) = Base.A_mul_B!(y,M,x) 113 | -------------------------------------------------------------------------------- /src/utils.jl: -------------------------------------------------------------------------------- 1 | # utils.jl 2 | # useful functions for ED project 3 | # Alan Morningstar 4 | # May 2017 5 | 6 | 7 | # set a bit to 1 8 | function setBit{I<:Integer}(i::I,bit::Int64) 9 | return (i | (1 << (bit-1)))::I 10 | end 11 | 12 | 13 | # set a bit to 0 14 | function clearBit{I<:Integer}(i::I,bit::Int64) 15 | return convert(I,(Int64(i) & (~(1 << (bit-1))))) 16 | end 17 | 18 | 19 | # toggle a bit 0<->1 20 | function toggleBit{I<:Integer}(i::I,bit::Int64) 21 | return (i $ (1 << (bit-1)))::I 22 | end 23 | 24 | 25 | # read a bit 26 | function readBit{I<:Integer}(i::I,bit::Int64) 27 | return Int64((i >> (bit-1)) & 1) 28 | end 29 | 30 | 31 | # swap two bits 32 | function swapBits{I<:Integer}(i::I,bit1::Int64,bit2::Int64) 33 | if readBit(i,bit1) == readBit(i,bit2) 34 | return i::I 35 | else 36 | return (i $ ( (1<<(bit1-1)) | (1<<(bit2-1)) ))::I 37 | end 38 | end 39 | 40 | 41 | # count number of unique elements in an array of real numbers 42 | # NOTE: also sorts the list in place 43 | function numUnique!{I<:Real}(Tbs::Array{I,1}) 44 | # first sort the list in place 45 | sort!(Tbs) 46 | # num unique elements 47 | num::Int64 = 1 48 | # scan over elements looking in previous elements 49 | for i::Int64 in 2:length(Tbs) 50 | if Tbs[i] > Tbs[i-1] 51 | num += 1 52 | end 53 | end 54 | 55 | return num::Int64 56 | end 57 | 58 | 59 | # function for flipping two spins of a basis state 60 | function XiXj{I<:Integer}(b::I,i::Int64,j::Int64) 61 | # b - integer representation of spin state 62 | # i,j - sites (bits) to be flipped 63 | return (b $ ( (1<<(i-1)) | (1<<(j-1)) ))::I 64 | end 65 | 66 | 67 | # function for flipping four spins of a basis state 68 | function XiXjXkXl{I<:Integer}(b::I,i::Int64,j::Int64,k::Int64,l::Int64) 69 | # b - integer representation of spin state 70 | # i,j,k,l - sites (bits) to be flipped 71 | return (b $ ( (1<<(i-1)) | (1<<(j-1)) | (1<<(k-1)) | (1<<(l-1)) ))::I 72 | end 73 | 74 | 75 | # function for computing (-1)^(positive integer) 76 | # NOTE: this is faster and simpler than using teh built in exp() function 77 | # for this specific case 78 | function simplePower{I<:Integer}(i::I) 79 | if readBit(i,1) == 0 80 | return 1 81 | else 82 | return -1 83 | end 84 | end 85 | 86 | 87 | # function for sorting multiple lists (M) in place based on the values of 88 | # another list (I) of real numbers which also gets sorted 89 | # NOTE: this is a basic implementation of quicksort 90 | function sortTwo!{TI<:Real}(low::Int64,high::Int64,I::Vector{TI},M...) 91 | if low < high 92 | p::Int64 = partition!(low,high,I,M...) 93 | sortTwo!(low,p,I,M...) 94 | sortTwo!(p+1,high,I,M...) 95 | end 96 | end 97 | function partition!{TI<:Real}(low::Int64,high::Int64,I::Vector{TI},M...) 98 | piv::TI = I[low] 99 | i::Int64 = low 100 | j::Int64 = high 101 | 102 | while true 103 | while I[i] < piv 104 | i += 1 105 | end 106 | while I[j] > piv 107 | j -= 1 108 | end 109 | if i >= j 110 | return j 111 | end 112 | I[i],I[j] = I[j],I[i] 113 | for m in M 114 | m[i],m[j] = m[j],m[i] 115 | end 116 | i += 1 117 | j -= 1 118 | end 119 | end 120 | 121 | 122 | # find the index of the first instance of an element in a vector (set), if it's 123 | # not in the vector then append it as the element and return it's index 124 | # NOTE: there is a hard-coded threshold set to say when two elements are close 125 | # enough to be considered the same 126 | function appendSet!{T<:Number}(elem::T,set::Vector{T}) 127 | 128 | for (index::Int64,item::T) in enumerate(set) 129 | if abs(elem - item) < 0.000001 130 | return index::Int64 131 | end 132 | end 133 | 134 | # if it wasn't found, append the element 135 | push!(set,elem) 136 | 137 | return length(set)::Int64 138 | 139 | end 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Exact Diagonalization of a Spin Hamiltonian 2 | Arnoldi diagonalization of a spin Hamiltonian on a rectangular, two-dimensional lattice, implemented in julia. Uses Sz, translational, and spin-flip symmetry to reduce the Hilbert space dimension, and an ARPACK wrapper to find eigenvalues and eigenstates. 3 | 4 | Outputs energies, and total-spin, spin-inversion, z-spin, and momentum quantum numbers for low lying eigenstates. 5 | 6 | The provided spin Hamiltonian is the J1-J2-J3-J4-K model from Equation 4 of [Sachdev's paper (2017)](https://arxiv.org/abs/1705.06289), with J3 and J4 fixed to 0 currently. 7 | 8 | For an almost-complete overview of methods used in this code, see Section 4.1 and 4.4 of [Sandvik's notes (2011)](https://arxiv.org/abs/1101.3281). 9 | 10 | ## Requirements 11 | Julia 0.5, DataFrames, ArgParse 12 | 13 | ## Running a Simulation 14 | There is a file named "main.jl" for running simulations. Command line arguments control Hamiltonian and lattice parameters, the symmetry sector of the Hamiltonian to be diagonalized, and the parameters of the Arnoldi diagonalization algorithm. Available command line arguments are: 15 | 16 | | Argument | Description | Argument Type | Default Value | 17 | | ------------- |-------------| -----| ----| 18 | | Lx | width of lattice | Int | 4 | 19 | | Ly | length of lattice | Int | 4 | 20 | | J1 | n.n. coupling | Float | 1.0 | 21 | | J2 | n.n.n. coupling | Float | 0.0 | 22 | | K | plaquette coupling | Float | 0.0 | 23 | | Sz | total spin along the z-axis | Int | 0 | 24 | | mx | momentum kx = 2 pi mx / Lx | Int | 0 | 25 | | my | momentum ky = 2 pi my / Ly | Int | 0 | 26 | | z | spin-inversion number | Int | 0 | 27 | | verbose | print details of simulation? | Bool | true | 28 | | numEigs | number of eigenvalues calculated | Int | 4 | 29 | | tol | error tolerance of Arnoldi algorithm | Float | 10^(-8) | 30 | | numKrylovVecs | dimension of the Krylov subspace | Int | 10 | 31 | | maxIter | maximum number of Arnoldi iterations | Int | 200 | 32 | 33 | For example, a slurm job script could look like 34 | 35 | ``` 36 | #!/bin/bash 37 | #SBATCH --mem=100G 38 | #SBATCH --time=2-00:00:00 39 | julia main.jl --Lx 6 --Ly 6 --J1 1.0 --J2 0.0 --K 0.0 --Sz 0 --mx 0 --my 0 --z 1 --numEigs 4 --tol 0.00000001 --numKrylovVecs 10 --maxIter 200 --verbose true 40 | ``` 41 | 42 | where a 6x6 lattice is given 100 GB and 2 days to run (this should actually take ~85 GB and ~30 hours for the largest symmetry sectors of a 6x6 J-K Hamiltonian). 43 | 44 | ## Possible Improvements 45 | 46 | *This section will be updated with more strategies for improvements to this code. 47 | 48 | 1. The current version uses julia's eigs(), a wrapper around ARPACK, which uses implicitely restarted Arnoldi iterations to find low lying eigenvalues. This method stores on the order of numKrylovVecs vectors, which is significantly larger than a Lanczos method, which only requires you to store three. So, need to implement a simple Lanczos algorithm such as that found in Section 4.2 of [(Sandvik, 2011)](https://arxiv.org/abs/1101.3281). This will also speed up the time to find eigenvalues because you can leave out any re-orthonormalization procedure and just make sure to monitor the Lanczos algorithm to avoid "ghost states" which arise when the Lanczos fails due to loss of orthogonality of the eigenvectors it is calculating. 49 | 50 | 2. Modify the code to accomodate more complex lattices. For example, there is a 32-site square lattice depicted in Figure 1 of [(Leung and Gooding, 1995)](https://journals.aps.org/prb/pdf/10.1103/PhysRevB.52.R15711) which could be useful. 51 | 52 | 3. If the matrix elements of the Hamiltonian could be generated fast enough, the Hamiltonian would never need to be stored. It could just be calculated on the fly every time a matrix-vector product is needed, and this would greatly reduce the memory cost of running the code. The bottleneck in calculating matrix elements are the following two problems, the first of which is only a bottleneck if translational symmetry is used, which it currently is in this code. 53 | 54 | a) given a spin state, find it's representative (if you don't know what this means, see the topic of momentum states in Section 4.1 of [Sandvik's notes 2011](https://arxiv.org/abs/1101.3281)) 55 | 56 | b) given a representative state, find its location in the basis of representatives 57 | 58 | A strategy for solving these problems efficiently is given by [Weisse (2013)](https://arxiv.org/abs/1210.1701), with reference to initial work by [Lin (1990)](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.42.6561). If translational symmetry is not used, [Lin's strategy](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.42.6561) will be enough to avoid storing the Hamiltonian. 59 | 60 | 4. If spin systems with more than 36 spins are desired, the code would need to be modified to be massively parallel. This means dividing the Hamiltonian up into chunks which fit on single cores, and communicating between cores only when absolutely necessary. When using this strategy, one should carefully consider whether using translational symmetry is worth it. 61 | 62 | ## Author 63 | 64 | * **Alan Morningstar** 65 | 66 | -------------------------------------------------------------------------------- /src/basis.jl: -------------------------------------------------------------------------------- 1 | # basis.jl 2 | # reduced basis in the Sz,kx,ky,z symmetry sector 3 | # Alan Morningstar 4 | # May 2017 5 | 6 | 7 | # container for symmetry sector 8 | immutable sector 9 | 10 | # spin along the z-axis 11 | Sz::Int64 12 | # momentum in x and y directions 13 | kx::Float64 14 | ky::Float64 15 | # spin inversion quantum number 16 | z::Int64 17 | 18 | end 19 | 20 | 21 | # function for computing the normalization constant Na of 22 | # the Tx,Ty,Z symmetrized state 23 | function normConstant{I<:Integer}(b::I,l::lattice,s::sector) 24 | 25 | # sum of phases in the symmetrization 26 | F::Complex128 = 0.0+0.0im 27 | # currrent T and Z transformed states 28 | Tb::I = b 29 | ZTb::I = Z(b,l) 30 | # set of transformed states in integer rep. 31 | Tbs::Vector{I} = Vector{I}() 32 | 33 | # perform all transformations 34 | for y::Int64 in 0:l.Ly-1 35 | for x::Int64 in 0:l.Lx-1 36 | 37 | # add to set of translated states 38 | appendSet!(Tb,Tbs) 39 | 40 | if Tb == b 41 | # add to sum of phases 42 | F += exp(-1.0im*(s.kx*x+s.ky*y)) 43 | elseif Tb < b 44 | return 0.0 45 | end 46 | 47 | # spin-invert the state 48 | ZTb = Z(Tb,l) 49 | 50 | # add inverted state to set of states 51 | appendSet!(ZTb,Tbs) 52 | 53 | if ZTb == b 54 | # add to sum of phases 55 | F += s.z*exp(-1.0im*(s.kx*x+s.ky*y)) 56 | elseif ZTb < b 57 | return 0.0 58 | end 59 | 60 | # translate state in x direction 61 | Tb = Tx(Tb,l) 62 | 63 | end 64 | 65 | # translate state in y direction 66 | Tb = Ty(Tb,l) 67 | 68 | end 69 | 70 | # compute and return normalization constant 71 | return abs2(F)*length(Tbs) 72 | 73 | end 74 | 75 | 76 | # the basis 77 | immutable reducedBasis{I<:Integer} 78 | 79 | # list of representatives of momentum basis states in integer representation 80 | b::Array{I,1} 81 | # list of corresponding normalization constants 82 | n::Array{Float64,1} 83 | # dimension of reduced Hilbert space 84 | dim::Int64 85 | 86 | # constructor 87 | reducedBasis(l::lattice,s::sector) = 88 | begin 89 | # initialize list of reps of momentum basis elements 90 | bList::Array{I,1} = Array{I,1}() 91 | # initialize list of normalization constants 92 | nList::Array{Float64,1} = Float64[] 93 | 94 | # run up the binary odometer of Sz states 95 | # start with first n1 bits in state 1(down) 96 | n1::Int64 = div(l.N,2)-s.Sz 97 | b::I = convert(I,2^n1-1) 98 | 99 | # allocate memory once here 100 | n::Float64 = 0.0 101 | i::Int64 = 0 102 | bit::Int64 = 0 103 | 104 | while true 105 | # check if this state has normalization constant = 0, if yes 106 | # then add it to the basis 107 | n = normConstant(b,l,s) 108 | 109 | if n > 0.000001 110 | push!(bList,b) 111 | push!(nList,n) 112 | end 113 | 114 | # counter of 1 bits to the right (in binary convention) 115 | # NOTE: these are just temporary variables for the 'binary odometer' 116 | i = 0 117 | # position in bit array 118 | bit = 1 119 | 120 | while bit < l.N 121 | 122 | # find a 1 bit whose following neighbor is 0 123 | if readBit(b,bit) == 1 124 | 125 | if readBit(b,bit+1) == 1 126 | i += 1 127 | else 128 | 129 | # shuffle bits over 130 | for J::Int64 in 1:i 131 | b = setBit(b,J) 132 | end 133 | 134 | for J::Int64 in i+1:bit 135 | b = clearBit(b,J) 136 | end 137 | 138 | b = setBit(b,bit+1) 139 | 140 | # then break to next loop 141 | break 142 | 143 | end 144 | 145 | end 146 | 147 | # to the next bit 148 | bit += 1 149 | 150 | end 151 | 152 | # if all 1s got shifted completely to the left, then all states 153 | # are explored 154 | if bit == l.N 155 | break 156 | end 157 | 158 | end 159 | 160 | # construct basis 161 | new(bList,nList,length(bList)) 162 | 163 | end 164 | 165 | end 166 | 167 | 168 | # search ordered basis for index (or location in the basis) of an integer 169 | # representation of a spin state 170 | # NOTE: basis has less than ~ 2 billion states, else modify to use Int64 171 | function basisIndex{I<:Integer}(b::I,basis::reducedBasis{I}) 172 | 173 | bIndex::UnitRange{Int64} = searchsorted(basis.b::Array{I,1},b)::UnitRange{Int64} 174 | 175 | if !isempty(bIndex) 176 | # return Int32 because basis has less than 2 billion elements 177 | # and need to save these in sparse Hamiltonian 178 | return Int32(bIndex[1])::Int32 179 | else 180 | return Int32(0)::Int32 181 | end 182 | 183 | end 184 | 185 | 186 | # return the type of the basis elements 187 | Base.eltype(basis::reducedBasis) = eltype(basis.b) 188 | -------------------------------------------------------------------------------- /src/lattice.jl: -------------------------------------------------------------------------------- 1 | # lattice.jl 2 | # defining the lattice connectivity for rectangular 2D lattice with 3 | # periodic boundaries 4 | # Alan Morningstar 5 | # May 2017 6 | 7 | 8 | # convert single integer site index to xy indexing 9 | # NOTE: lattice sites are labelled like the following example 10 | # 11 | # integer labelling 12 | # ----------------- 13 | # 34 14 | # 12 15 | # 16 | # xy labelling 17 | # ------------ 18 | # (0,1)(1,1) 19 | # (0,0)(1,0) 20 | # 21 | function xyIndex(site::Int64,Lx::Int64,Ly::Int64) 22 | 23 | y::Int64,x::Int64 = divrem(site-1,Lx) 24 | 25 | return x,y 26 | 27 | end 28 | 29 | 30 | # convert xy site indexing to single integer index 31 | function siteIndex(xy::Tuple{Int64,Int64},Lx::Int64,Ly::Int64) 32 | 33 | x::Int64 = xy[1] 34 | y::Int64 = xy[2] 35 | 36 | # move xy into primary lattice cell 37 | while x > (Lx-1) 38 | x -= Lx 39 | end 40 | while x < 0 41 | x += Lx 42 | end 43 | while y > (Ly-1) 44 | y -= Ly 45 | end 46 | while y < 0 47 | y += Ly 48 | end 49 | 50 | # map to integer index 51 | site::Int64 = y*Lx + x + 1 52 | 53 | return site 54 | 55 | end 56 | 57 | 58 | # find indices of neighbors specified by a translation vectors T. 59 | # takes in tuple of vectors pointing to so-called neighbors (this is loosely 60 | # defined and can be used to collect---for each site---a list of 'neighboring' 61 | # sites that are connected to it by the Hamiltonian) 62 | # ex: if the Hamiltonian couples plaquette spins and next nearest neighbors, 63 | # then include those in this list so they are easily accessed by other 64 | # customizable functions (like building the Hamiltonian) 65 | function neighbors(site::Int64,T::Array{Tuple{Int64,Int64},1},Lx::Int64,Ly::Int64) 66 | 67 | # use xy indexing just to compute neighbors 68 | x::Int64,y::Int64 = xyIndex(site,Lx,Ly) 69 | 70 | # neighbor in xy indexing 71 | neighborxy::Array{Tuple{Int64,Int64},1} = [(x+t[1],y+t[2]) for t in T] 72 | 73 | # back to single integer indexing 74 | neighborIndex::Array{Int64,1} = [siteIndex(nxy,Lx,Ly) for nxy in neighborxy] 75 | 76 | # return neighbor index 77 | return neighborIndex 78 | 79 | end 80 | 81 | 82 | # NOTE: plaquette sites corresponding to a given site (p1) in the bottom left 83 | # of the plaquette. 84 | # p3L indicates p3 on the plaquette to the left, similarly for p1D on the 85 | # plaquette in the downwards direction 86 | # 87 | # p3L--p3--p4 88 | # | |Plq| 89 | # p1L--p1--p2 90 | # | | | 91 | # p1D p2D 92 | # 93 | # this is useful for J1-J2-K model 94 | 95 | 96 | # container for lattice properties 97 | immutable lattice 98 | 99 | # size of lattice, number of sites 100 | Lx::Int64 101 | Ly::Int64 102 | N::Int64 103 | 104 | # neighbors of each site detailed in diagram above 105 | nbrs::Array{Array{Int64,1},1} 106 | 107 | # useful constants for translation and inversion operators 108 | TxMask1::UInt64 109 | TxMask2::UInt64 110 | TyMask1::UInt64 111 | TyMask2::UInt64 112 | LxMinus1::Int64 113 | NMinusLx::Int64 114 | ZMask::UInt64 115 | 116 | function lattice(Lx::Int64,Ly::Int64,neighborVectors::Array{Tuple{Int64,Int64},1}) 117 | 118 | neighborsList = [neighbors(site,neighborVectors,Lx,Ly) for site in 1:Lx*Ly] 119 | 120 | # rough code, needs cleanup. 121 | # just makes the useful masks 122 | # ------------------------------------------ 123 | TxMx1::UInt64 = UInt64(1)<<(Lx-1) 124 | TxMx2::UInt64 = TxMx1 - UInt64(1) 125 | TxMask1::UInt64,TxMask2::UInt64 = TxMx1,TxMx2 126 | 127 | for y in 1:Ly 128 | TxMask1 = TxMask1 | (TxMx1 << y*Lx) 129 | TxMask2 = TxMask2 | (TxMx2 << y*Lx) 130 | end 131 | 132 | TyMask1 = ((UInt64(1)<> (l.LxMinus1) | ((b & l.TxMask2) << 1)) 146 | end 147 | 148 | 149 | # y translation operator, shifts spin values up on the lattice 150 | function Ty{I<:Integer}(b::I,l::lattice) 151 | return ((b & l.TyMask1) >> (l.NMinusLx) | ((b & l.TyMask2) << l.Lx)) 152 | end 153 | 154 | 155 | # spin flip operator, flips all spins (bits) 156 | function Z{I<:Integer}(b::I,l::lattice) 157 | return ( l.ZMask $ b ) 158 | end 159 | 160 | 161 | # find the related representative state and what translation and spin flip 162 | # relate the two states 163 | function representative{I<:Integer}(b::I,l::lattice) 164 | 165 | # rep. state 166 | rep::I = b 167 | # transformed states 168 | Tb::I = b 169 | ZTb::I = Z(b,l) 170 | # translations to get to rep. state 171 | lx::Int64 = 0 172 | ly::Int64 = 0 173 | # inversion to get to rep. state 174 | g::Int64 = 0; 175 | 176 | # perform all transformations 177 | for y::Int64 in 0:l.Ly-1 178 | for x::Int64 in 0:l.Lx-1 179 | 180 | if Tb < rep 181 | # then this is a better representative 182 | rep = Tb 183 | lx = x 184 | ly = y 185 | g = 0 186 | end 187 | 188 | # now invert the spins 189 | ZTb = Z(Tb,l) 190 | 191 | if ZTb < rep 192 | # then this is a better representative 193 | rep = ZTb 194 | lx = x 195 | ly = y 196 | g = 1 197 | end 198 | 199 | 200 | Tb = Tx(Tb,l) 201 | 202 | end 203 | 204 | Tb = Ty(Tb,l) 205 | 206 | end 207 | 208 | # return lx, ly, and g are the powers of their respective operators 209 | # Tx,Ty,Z which---when applied to the state b---yield the representative 210 | return rep::I,lx::Int64,ly::Int64,g::Int64 211 | 212 | end 213 | -------------------------------------------------------------------------------- /main/main.jl: -------------------------------------------------------------------------------- 1 | # main.jl 2 | # main function for ED study of J-K Hamiltonian 3 | # Alan Morningstar 4 | # June 2017 5 | 6 | 7 | using ArgParse 8 | using DataFrames 9 | 10 | include("../src/HeisenbergED.jl") 11 | 12 | 13 | # main function 14 | function main( 15 | Lx::Int64, # lattice length in x direction 16 | Ly::Int64, # lattice length in y direction 17 | J1::Float64, # nearest neighbor coupling 18 | J2::Float64, # next nearest neighbor coupling 19 | K::Float64, # plaquette coupling 20 | Sz::Int64, # spin along the z-axis 21 | mx::Int64, # momentum-related number, kx=2*pi*mx/Lx 22 | my::Int64, # momentum-related number, ky=2*pi*my/Ly 23 | z::Int64, # spin-inversion number 24 | numEigs::Int64, # number of eigenvalues desired 25 | tolerance::Float64, # strictness of convergence criterion 26 | numKrylovVecs::Int64, # number of vectors used in Krylov subspace 27 | maxIter::Int64, # max number of Arnoldi iterations 28 | verbose::Bool, # print details while running? 29 | ) 30 | 31 | 32 | # number of lattice sites 33 | N = Lx*Ly 34 | # momentum 35 | kx = 2.0*pi*mx/Lx 36 | ky = 2.0*pi*my/Ly 37 | 38 | # print details 39 | verbose ? 40 | begin 41 | println("N = ",N) 42 | println("J1 = ",J1) 43 | println("J2 = ",J2) 44 | println("K = ",K) 45 | println("Sz = ",Sz) 46 | println("mx = ",mx) 47 | println("my = ",my) 48 | println("z = ",z) 49 | end : nothing 50 | 51 | # plaquette (x,y) vectors, locating p1,p2,p3,p4 on the plaquette of the p1 site and p1D,p2D,p1L,p3L on adjacent plaquettes 52 | neighborVectors = [(0,0),(1,0),(0,1),(1,1),(0,-1),(1,-1),(-1,0),(-1,1)] 53 | # define the lattice 54 | l = lattice(Lx,Ly,neighborVectors) 55 | 56 | # specify symmetry sector 57 | s = sector(Sz,kx,ky,z) 58 | 59 | # construct the basis 60 | verbose ? println("Constructing the basis.") : nothing 61 | basis = reducedBasis{UInt64}(l,s) 62 | verbose ? println("Dimension of reduced Hilbert space is ",basis.dim,".") : nothing 63 | 64 | # couplings type to make passing J1,J2,K easier 65 | c = couplings(J1,J2,K) 66 | 67 | # build the sparse Hamiltonian 68 | verbose ? println("Building the Hamiltonian.") : nothing 69 | H = constructSparseHam(basis,c,s,l) 70 | 71 | # compute eigenvalues 72 | # ritzVec = true if you want the eigenvectors returned too 73 | #:LM stands for largest magnitude, :SR for smallest real part 74 | verbose ? println("Computing eigenvalues and eigenvectors.") : nothing 75 | eigsResult = eigs(H; nev=numEigs,ncv=numKrylovVecs,maxiter=maxIter, which=:SR, tol=tolerance, ritzvec=true) 76 | 77 | # clear Hamiltonian memory manually 78 | H = nothing 79 | 80 | # compile data 81 | verbose ? println("Compiling data.") : nothing 82 | # energies 83 | EData = real(eigsResult[1]) 84 | # Sz values 85 | SzData = fill(Sz,numEigs) 86 | # mx values 87 | mxData = fill(mx,numEigs) 88 | # my values 89 | myData = fill(my,numEigs) 90 | # z values 91 | zData = fill(z,numEigs); 92 | # S(S+1) values 93 | S2Data = round(Int64,real(S2expectations(basis,s,l,eigsResult[2]))); 94 | 95 | # clear eigsResult memory manually 96 | eigsResult = nothing 97 | 98 | # create DataFrame 99 | df = DataFrame(E=EData,Ssqrd=S2Data,Sz=SzData,mx=mxData,my=myData,z=zData) 100 | verbose ? println(df) : nothing 101 | # sort by energy 102 | sort!(df) 103 | 104 | return df 105 | 106 | end 107 | 108 | 109 | #-- parse command line arguments 110 | s = ArgParseSettings() 111 | 112 | @add_arg_table s begin 113 | 114 | "--Lx" 115 | help = "length of lattice in x direction" 116 | arg_type = Int64 117 | default = 4 118 | "--Ly" 119 | help = "length of lattice in y direction" 120 | arg_type = Int64 121 | default = 4 122 | "--J1" 123 | help = "nearest neighbor coupling" 124 | arg_type = Float64 125 | default = 1.0 126 | "--J2" 127 | help = "next nearest neighbor coupling" 128 | arg_type = Float64 129 | default = 0.0 130 | "--K" 131 | help = "ring exchange coupling" 132 | arg_type = Float64 133 | default = 0.0 134 | "--Sz" 135 | help = "total z spin" 136 | arg_type = Int64 137 | default = 0 138 | "--mx" 139 | help = "x momentum mx such that kx = 2 pi mx / Lx" 140 | arg_type = Int64 141 | default = 0 142 | "--my" 143 | help = "y momentum my such that ky = 2 pi my / Ly" 144 | arg_type = Int64 145 | default = 0 146 | "--z" 147 | help = "z = +-1 spin-inversion quantum number" 148 | arg_type = Int64 149 | default = 1 150 | "--verbose" 151 | help = "print info during computation? true or false" 152 | arg_type = Bool 153 | default = true 154 | "--numEigs" 155 | help = "number of eigenvalues desired" 156 | arg_type = Int64 157 | default = 4 158 | "--tol" 159 | help = "tolerance for error in Arnoldi convergence" 160 | arg_type = Float64 161 | default = 10.^(-8.) 162 | "--numKrylovVecs" 163 | help = "number of vectors in the Krylov subspace" 164 | arg_type = Int64 165 | default = 10 166 | "--maxIter" 167 | help = "nmax number of Arnoldi iterations" 168 | arg_type = Int64 169 | default = 200 170 | 171 | end 172 | 173 | const argsDict = parse_args(s) 174 | 175 | 176 | #-- execute main function 177 | data = main( 178 | argsDict["Lx"], 179 | argsDict["Ly"], 180 | argsDict["J1"], 181 | argsDict["J2"], 182 | argsDict["K"], 183 | argsDict["Sz"], 184 | argsDict["mx"], 185 | argsDict["my"], 186 | argsDict["z"], 187 | argsDict["numEigs"], 188 | argsDict["tol"], 189 | argsDict["numKrylovVecs"], 190 | argsDict["maxIter"], 191 | argsDict["verbose"] 192 | ) 193 | 194 | 195 | #-- save data 196 | dataFileName = "specData/Lx=" * string(argsDict["Lx"]) * "_Ly=" * string(argsDict["Ly"]) * "_J1=" * string(argsDict["J1"]) * "_J2=" * string(argsDict["J2"]) * "_K=" * string(argsDict["K"]) * "_Sz=" * string(argsDict["Sz"]) * "_mx=" * string(argsDict["mx"]) * "_my=" * string(argsDict["my"]) * "_z=" * string(argsDict["z"]) * ".csv" 197 | 198 | writetable(dataFileName, data) 199 | -------------------------------------------------------------------------------- /main/main.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Exact Diagonalization of $J$-$K$ Hamiltonian\n", 8 | " Alan Morningstar\n", 9 | " May 2017" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": { 15 | "collapsed": true 16 | }, 17 | "source": [ 18 | "## Include Source" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 1, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "include(\"../src/HeisenbergED.jl\");" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "## Main" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "#### specify parameters" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 2, 47 | "metadata": {}, 48 | "outputs": [], 49 | "source": [ 50 | "# square lattice length\n", 51 | "const Lx = 4\n", 52 | "const Ly = 4\n", 53 | "# number of sites\n", 54 | "const N = Lx*Ly\n", 55 | "# NN coupling\n", 56 | "const J1 = 1.0\n", 57 | "# NNN coupling\n", 58 | "const J2 = 0.4\n", 59 | "# plaquette coupling\n", 60 | "const K = 0.3\n", 61 | "\n", 62 | "# choose Sz sector\n", 63 | "const Sz = 0\n", 64 | "# choose kx,ky by specifying mi such that mi is in 0:Li-1\n", 65 | "const mx = 0\n", 66 | "const my = 0\n", 67 | "const kx = 2*pi*mx/Lx\n", 68 | "const ky = 2*pi*my/Ly\n", 69 | "# choose spin inversion quantum number\n", 70 | "const z = 1\n", 71 | "\n", 72 | "# number of eigenvalues desired\n", 73 | "const numEigs = 4\n", 74 | "# a tolerance for error\n", 75 | "const tolerance = 10.^(-8.)\n", 76 | "# ritzVec = true if you want the eigenvectors returned too\n", 77 | "const ritzVec = true\n", 78 | "# number of Krylov vectors in eigenvalue calculation\n", 79 | "const numKrylovVecs = 10\n", 80 | "# maximum number of iterations to converge eigenvalues\n", 81 | "const maxIter = 200\n", 82 | "\n", 83 | "# plaquette (x,y) vectors, locating p1,p2,p3,p4 on the plaquette of the p1 site and p1D,p2D,p1L,p3L on adjacent plaquettes\n", 84 | "const neighborVectors = [(0,0),(1,0),(0,1),(1,1),(0,-1),(1,-1),(-1,0),(-1,1)];" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "metadata": {}, 90 | "source": [ 91 | "#### define lattice, symmetry sector, and basis" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": 3, 97 | "metadata": {}, 98 | "outputs": [], 99 | "source": [ 100 | "# define the lattice\n", 101 | "const l = lattice(Lx,Ly,neighborVectors);" 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 4, 107 | "metadata": {}, 108 | "outputs": [], 109 | "source": [ 110 | "# specify symmetry sector\n", 111 | "const s = sector(Sz,kx,ky,z);" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": 5, 117 | "metadata": {}, 118 | "outputs": [ 119 | { 120 | "name": "stdout", 121 | "output_type": "stream", 122 | "text": [ 123 | " 0.102415 seconds (164.93 k allocations: 7.569 MB, 5.67% gc time)\n", 124 | "Dimension of reduced Hilbert space is 441.\n" 125 | ] 126 | } 127 | ], 128 | "source": [ 129 | "# construct the basis\n", 130 | "# 0.34 seconds for 6x4 lattice, basis is ~0.002 GB\n", 131 | "@time const basis = reducedBasis{UInt64}(l,s)\n", 132 | "println(\"Dimension of reduced Hilbert space is \",basis.dim,\".\");" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": 6, 138 | "metadata": {}, 139 | "outputs": [ 140 | { 141 | "data": { 142 | "text/plain": [ 143 | "7.056e-6" 144 | ] 145 | }, 146 | "execution_count": 6, 147 | "metadata": {}, 148 | "output_type": "execute_result" 149 | } 150 | ], 151 | "source": [ 152 | "(sizeof(basis.b)+sizeof(basis.n))/10^9" 153 | ] 154 | }, 155 | { 156 | "cell_type": "markdown", 157 | "metadata": {}, 158 | "source": [ 159 | "#### build the Hamiltonian" 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": 10, 165 | "metadata": {}, 166 | "outputs": [ 167 | { 168 | "name": "stdout", 169 | "output_type": "stream", 170 | "text": [ 171 | " 0.582349 seconds (663.35 k allocations: 23.633 MB, 1.67% gc time)\n" 172 | ] 173 | } 174 | ], 175 | "source": [ 176 | "# couplings type to make passing J1,K easier\n", 177 | "const c = couplings(J1,J2,K)\n", 178 | "\n", 179 | "# build the sparse Hamiltonian\n", 180 | "@time const H = constructSparseHam(basis,c,s,l);" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": 11, 186 | "metadata": {}, 187 | "outputs": [ 188 | { 189 | "name": "stdout", 190 | "output_type": "stream", 191 | "text": [ 192 | "H: 8.8584e-5 GB of memory.\n" 193 | ] 194 | } 195 | ], 196 | "source": [ 197 | "println(\"H: \",(sizeof(H.colPntrs)+sizeof(H.rowIndcs)+sizeof(H.nzVals)+sizeof(H.nzPntrs))/10^9,\" GB of memory.\")" 198 | ] 199 | }, 200 | { 201 | "cell_type": "markdown", 202 | "metadata": {}, 203 | "source": [ 204 | "#### find eigenvalues and eigenvectors" 205 | ] 206 | }, 207 | { 208 | "cell_type": "code", 209 | "execution_count": 15, 210 | "metadata": {}, 211 | "outputs": [ 212 | { 213 | "name": "stdout", 214 | "output_type": "stream", 215 | "text": [ 216 | " 1.695641 seconds (2.21 M allocations: 85.179 MB, 1.22% gc time)\n" 217 | ] 218 | } 219 | ], 220 | "source": [ 221 | "# compute eigenvalues\n", 222 | "@time eigsResult = eigs(H; nev=numEigs,ncv=numKrylovVecs,maxiter=maxIter, which=:SR, tol=tolerance, ritzvec=ritzVec);" 223 | ] 224 | }, 225 | { 226 | "cell_type": "code", 227 | "execution_count": 19, 228 | "metadata": {}, 229 | "outputs": [ 230 | { 231 | "name": "stdout", 232 | "output_type": "stream", 233 | "text": [ 234 | "Energies are: \n", 235 | "-6.694896080039535\n", 236 | "-6.735843696892488\n", 237 | "-6.730790539793388\n", 238 | "-6.2082326260702425\n", 239 | "Number of iterations = 4\n", 240 | "Number of matrix-vector multiplications = 35\n" 241 | ] 242 | } 243 | ], 244 | "source": [ 245 | "# print energies\n", 246 | "println(\"Energies are: \")\n", 247 | "for en in real(eigsResult[1])\n", 248 | " println(en)\n", 249 | "end\n", 250 | "# print algorithm performance\n", 251 | "println(\"Number of iterations = \",eigsResult[3])\n", 252 | "println(\"Number of matrix-vector multiplications = \",eigsResult[4])" 253 | ] 254 | }, 255 | { 256 | "cell_type": "markdown", 257 | "metadata": {}, 258 | "source": [ 259 | "#### compute $S^2$ values and save spectral data\n", 260 | "Save in format | E | S(S+1) | Sz | mx | my | z |" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": 21, 266 | "metadata": {}, 267 | "outputs": [ 268 | { 269 | "name": "stderr", 270 | "output_type": "stream", 271 | "text": [ 272 | "WARNING: Method definition describe(AbstractArray) in module StatsBase at /Users/aormorningstar/.julia/v0.5/StatsBase/src/scalarstats.jl:560 overwritten in module DataFrames at /Users/aormorningstar/.julia/v0.5/DataFrames/src/abstractdataframe/abstractdataframe.jl:407.\n" 273 | ] 274 | } 275 | ], 276 | "source": [ 277 | "using DataFrames" 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": 22, 283 | "metadata": {}, 284 | "outputs": [], 285 | "source": [ 286 | "# energies\n", 287 | "EData = real(eigsResult[1])\n", 288 | "# Sz values\n", 289 | "SzData = fill(Sz,numEigs)\n", 290 | "# mx values\n", 291 | "mxData = fill(mx,numEigs)\n", 292 | "# my values\n", 293 | "myData = fill(my,numEigs)\n", 294 | "# z values\n", 295 | "zData = fill(z,numEigs);" 296 | ] 297 | }, 298 | { 299 | "cell_type": "code", 300 | "execution_count": 23, 301 | "metadata": {}, 302 | "outputs": [ 303 | { 304 | "name": "stdout", 305 | "output_type": "stream", 306 | "text": [ 307 | " 0.223021 seconds (154.56 k allocations: 5.733 MB)\n" 308 | ] 309 | } 310 | ], 311 | "source": [ 312 | "# S(S+1) values\n", 313 | "@time S2Data = round(Int64,real(S2expectations(basis,s,l,eigsResult[2])));" 314 | ] 315 | }, 316 | { 317 | "cell_type": "code", 318 | "execution_count": 25, 319 | "metadata": {}, 320 | "outputs": [ 321 | { 322 | "data": { 323 | "text/html": [ 324 | "
ESsqrdSzmxmyz
1-6.99489608003953500001
2-6.73584369689248800001
3-6.73079053979338800001
4-6.208232626070242560001
" 325 | ], 326 | "text/plain": [ 327 | "4×6 DataFrames.DataFrame\n", 328 | "│ Row │ E │ Ssqrd │ Sz │ mx │ my │ z │\n", 329 | "├─────┼──────────┼───────┼────┼────┼────┼───┤\n", 330 | "│ 1 │ -6.9949 │ 0 │ 0 │ 0 │ 0 │ 1 │\n", 331 | "│ 2 │ -6.73584 │ 0 │ 0 │ 0 │ 0 │ 1 │\n", 332 | "│ 3 │ -6.73079 │ 0 │ 0 │ 0 │ 0 │ 1 │\n", 333 | "│ 4 │ -6.20823 │ 6 │ 0 │ 0 │ 0 │ 1 │" 334 | ] 335 | }, 336 | "execution_count": 25, 337 | "metadata": {}, 338 | "output_type": "execute_result" 339 | } 340 | ], 341 | "source": [ 342 | "# create DataFrame\n", 343 | "df = DataFrame(E=EData,Ssqrd=S2Data,Sz=SzData,mx=mxData,my=myData,z=zData)" 344 | ] 345 | }, 346 | { 347 | "cell_type": "code", 348 | "execution_count": null, 349 | "metadata": { 350 | "collapsed": true 351 | }, 352 | "outputs": [], 353 | "source": [] 354 | } 355 | ], 356 | "metadata": { 357 | "anaconda-cloud": {}, 358 | "kernelspec": { 359 | "display_name": "Julia 1.3.0", 360 | "language": "julia", 361 | "name": "julia-1.3" 362 | }, 363 | "language_info": { 364 | "file_extension": ".jl", 365 | "mimetype": "application/julia", 366 | "name": "julia", 367 | "version": "1.3.0" 368 | } 369 | }, 370 | "nbformat": 4, 371 | "nbformat_minor": 1 372 | } 373 | -------------------------------------------------------------------------------- /src/sparseHam.jl: -------------------------------------------------------------------------------- 1 | # sparseHam.jl 2 | # sparse J1-J2-K Hamiltonian 3 | # Alan Morningstar 4 | # May 2017 5 | 6 | 7 | # container for Hamiltonian couplings 8 | immutable couplings 9 | 10 | # 1st nearest neighbor 11 | J1::Float64 12 | # 2nd nearest neighbor 13 | J2::Float64 14 | # plaquette coupling 15 | K::Float64 16 | 17 | end 18 | 19 | # build the sparse J-K Hamiltonian for storage 20 | function constructSparseHam(basis::reducedBasis,c::couplings,s::sector,l::lattice) 21 | 22 | # lattice properties 23 | Lx::Int64 = l.Lx 24 | Ly::Int64 = l.Ly 25 | N::Int64 = l.N 26 | # Hamiltonian couplings 27 | J1::Float64 = c.J1 28 | J2::Float64 = c.J2 29 | K::Float64 = c.K 30 | # symmetry sector (Sz isn't explicitely needed here) 31 | kx::Float64 = s.kx 32 | ky::Float64 = s.ky 33 | z::Int64 = s.z 34 | 35 | # non-zero couplings (don't waste time calculating matrix elements that 36 | # will certainly be zero) 37 | J1nz::Bool = (J1 != 0.0) 38 | J2nz::Bool = (J2 != 0.0) 39 | Knz::Bool = (K != 0.0) 40 | 41 | # type of states in the basis (could be Int64, Int32, etc.) 42 | bType::Type = eltype(basis) 43 | 44 | # store location and value of non-zero matrix elements in CSC format 45 | # NOTE: since we are using the sparseHermitian type, diagonal elements must 46 | # be divided by 2, and we must only store the upper triangle of the 47 | # matrix 48 | Jpointers::Vector{Int64} = Vector{Int64}(basis.dim+1) 49 | I::Vector{Int32} = Int32[] 50 | M::Vector{Complex128} = Complex128[] 51 | Mpointers::Vector{Int32} = Int32[] 52 | 53 | # allocate memory once before the loops 54 | # ------------------------------------- 55 | # basis element 56 | b::bType = 0 57 | # array of spins for basis element b 58 | sb::Array{bType,1} = Array{bType,1}(l.N) 59 | # normalization constant for the symmetrized state corresponding to b 60 | nb::Float64 = 0.0 61 | # the diagonal matrix element 62 | Hbb::Complex128 = 0.0+0.0im 63 | # the neighbors (as contained in lattice) of a site 64 | p::Array{Int64,1} = Array{Int64,1}(length(l.nbrs[1])) 65 | # the spins at those neighboring sites for basis element b 66 | sp::Array{bType,1} = Array{UInt64,1}(length(p)) 67 | # some integers determined by spin values 68 | sPw12::Int64 = sPw34::Int64 = sPw13::Int64 = sPw24::Int64 = sPw14::Int64 = sPw23::Int64 = sPw1234::Int64 = sPw1D2D::Int64 = sPw1L3L::Int64 = 0 69 | # conditions for certain terms to be calculated (if False, certain matrix 70 | # elements will be zero and not computed) 71 | c12::Bool = c13::Bool = c14::Bool = c23::Bool = c1234::Bool = true 72 | # the resulting state when applying some spin flip operators to b 73 | a::bType = convert(bType,0) 74 | # the representative of the orbit which state |a> belongs to 75 | aRep::bType = convert(bType,0) 76 | # powers of Tx,Ty,Z operators to get to aRep from a 77 | lx::Int64 = ly::Int64 = g::Int64 = 0 78 | # the position of aRep in the basis 79 | aRepIndex::Int32 = Int32(0) 80 | # a contribution to an off diagonal matrix element due to one off-diagonal 81 | # operator in the Hamiltonian 82 | Hsite::Complex128 = 0.0+0.0im 83 | # ------------------------------------- 84 | 85 | # loop over basis states 86 | for bIndex::Int64 in 1:basis.dim 87 | 88 | # CSC formatting 89 | Jpointers[bIndex] = length(I)+1 90 | 91 | # spin states of basis 92 | b = basis.b[bIndex] # integer rep. 93 | for bit in 1:l.N 94 | sb[bit] = readBit(b,bit) # spin array rep. 95 | end 96 | # normalization constant 97 | nb = basis.n[bIndex] 98 | 99 | # clear the diagonal matrix element 100 | Hbb = 0.0 101 | 102 | # loop over lattice sites 103 | for site::Int64 in 1:N 104 | 105 | # nearby sites p1,p2,p3,p4,p1D,p2D,p1L,p3L 106 | p = l.nbrs[site] 107 | # and spins at those sites 108 | sp = sb[p] 109 | 110 | # some common factors in the matrix elements 111 | sPw12 = simplePower(sp[1] + sp[2]) 112 | sPw34 = simplePower(sp[3] + sp[4]) 113 | sPw13 = simplePower(sp[1] + sp[3]) 114 | sPw24 = simplePower(sp[2] + sp[4]) 115 | sPw14 = simplePower(sp[1] + sp[4]) 116 | sPw23 = simplePower(sp[2] + sp[3]) 117 | sPw1234 = simplePower(sp[1] + sp[2] + sp[3] + sp[4]) 118 | sPw1D2D = simplePower(sp[5] + sp[6]) 119 | sPw1L3L = simplePower(sp[7] + sp[8]) 120 | 121 | # some conditions to determine when a matrix element will 122 | # certainly be zero 123 | c12 = ((sPw12 == -1) && (Knz || J1nz)) 124 | c13 = ((sPw13 == -1) && (Knz || J1nz)) 125 | c14 = ((sPw14 == -1) && (Knz || J2nz)) 126 | c23 = ((sPw23 == -1) && (Knz || J2nz)) 127 | c1234 = ((sp[1] + sp[2] + sp[3] + sp[4] == 2) && Knz) 128 | 129 | 130 | # contributions to the diagonal matrix element 131 | # -------------------------------------------- 132 | if J1nz 133 | Hbb += 0.25*J1*( sPw12 + sPw13 ) 134 | end 135 | if J2nz 136 | Hbb += 0.25*J2*( sPw14 + sPw23 ) 137 | end 138 | if Knz 139 | Hbb += 0.125*K*sPw1234 140 | end 141 | 142 | # compute off diagonal matrix elements 143 | # ------------------------------------ 144 | 145 | # the 12 term (coupling between p1 and p2 sites, nearest nbr.) 146 | if c12 147 | # the bra 148 | a = XiXj(b,p[1],p[2]) 149 | # the rep and translation of the bra 150 | aRep,lx,ly,g = representative(a,l) 151 | # search for this rep in the basis 152 | aRepIndex = basisIndex(aRep,basis) 153 | if aRepIndex != 0 && (bIndex > aRepIndex) # only keep upper triangle 154 | # the matrix element 155 | Hsite = (0.5*J1+0.125*K*(sPw34-sPw1234+2*sPw1D2D))*(z^g)*exp(-1.0im*(kx*lx+ky*ly))*sqrt(basis.n[aRepIndex]/nb) 156 | 157 | push!(I,aRepIndex) 158 | push!(Mpointers,appendSet!(Hsite,M)) 159 | elseif bIndex == aRepIndex 160 | Hbb += (0.5*J1+0.125*K*(sPw34-sPw1234+2*sPw1D2D))*(z^g)*exp(-1.0im*(kx*lx+ky*ly))*sqrt(basis.n[aRepIndex]/nb) 161 | end 162 | end 163 | 164 | # the 13 term (coupling between p1 and p3 sites, nearest nbr.) 165 | if c13 166 | # the bra 167 | a = XiXj(b,p[1],p[3]) 168 | # the rep and translation of the bra 169 | aRep,lx,ly,g = representative(a,l) 170 | # search for this rep in the basis 171 | aRepIndex = basisIndex(aRep,basis) 172 | if aRepIndex != 0 && (bIndex > aRepIndex) # only keep upper triangle 173 | # the matrix element 174 | Hsite = (0.5*J1+0.125*K*(sPw24-sPw1234+2*sPw1L3L))*(z^g)*exp(-1.0im*(kx*lx+ky*ly))*sqrt(basis.n[aRepIndex]/nb) 175 | 176 | push!(I,aRepIndex) 177 | push!(Mpointers,appendSet!(Hsite,M)) 178 | elseif bIndex == aRepIndex 179 | Hbb += (0.5*J1+0.125*K*(sPw24-sPw1234+2*sPw1L3L))*(z^g)*exp(-1.0im*(kx*lx+ky*ly))*sqrt(basis.n[aRepIndex]/nb) 180 | end 181 | end 182 | 183 | # the 14 term, (coupling between p1 and p4 sites, next nearest nbr.) 184 | if c14 185 | # the bra 186 | a = XiXj(b,p[1],p[4]) 187 | # the rep and translation of the bra 188 | aRep,lx,ly,g = representative(a,l) 189 | # search for this rep in the basis 190 | aRepIndex = basisIndex(aRep,basis) 191 | if aRepIndex !=0 && (bIndex > aRepIndex) # only keep upper triangle 192 | # the matrix element 193 | Hsite = (0.5*J2-0.25*K*sPw23)*(z^g)*exp(-1.0im*(kx*lx+ky*ly))*sqrt(basis.n[aRepIndex]/nb) 194 | 195 | push!(I,aRepIndex) 196 | push!(Mpointers,appendSet!(Hsite,M)) 197 | elseif bIndex == aRepIndex 198 | Hbb += (0.5*J2-0.25*K*sPw23)*(z^g)*exp(-1.0im*(kx*lx+ky*ly))*sqrt(basis.n[aRepIndex]/nb) 199 | end 200 | end 201 | 202 | # the 23 term, (coupling between p2 and p4 sites, next nearest nbr.) 203 | if c23 204 | # the bra 205 | a = XiXj(b,p[2],p[3]) 206 | # the rep and translation of the bra 207 | aRep,lx,ly,g = representative(a,l) 208 | # search for this rep in the basis 209 | aRepIndex = basisIndex(aRep,basis) 210 | if aRepIndex != 0 && (bIndex > aRepIndex) # only keep upper triangle 211 | # the matrix element 212 | Hsite= (0.5*J2-0.25*K*sPw14)*(z^g)*exp(-1.0im*(kx*lx+ky*ly))*sqrt(basis.n[aRepIndex]/nb) 213 | 214 | push!(I,aRepIndex) 215 | push!(Mpointers,appendSet!(Hsite,M)) 216 | elseif bIndex == aRepIndex 217 | Hbb += (0.5*J2-0.25*K*sPw14)*(z^g)*exp(-1.0im*(kx*lx+ky*ly))*sqrt(basis.n[aRepIndex]/nb) 218 | end 219 | end 220 | 221 | # the 1234 term, (four-site coupling, plaquette term) 222 | if c1234 223 | # the bra 224 | a = XiXjXkXl(b,p[1],p[2],p[3],p[4]) 225 | # the rep and translation of the bra 226 | aRep,lx,ly,g = representative(a,l) 227 | # search for this rep in the basis 228 | aRepIndex = basisIndex(aRep,basis) 229 | if aRepIndex != 0 && (bIndex > aRepIndex) # only keep upper triangle 230 | # the matrix element 231 | Hsite = 0.125*K*(2-sPw12-sPw34-sPw13-sPw24+sPw14+sPw23)*(z^g)*exp(-1.0im*(kx*lx+ky*ly))*sqrt(basis.n[aRepIndex]/nb) 232 | 233 | push!(I,aRepIndex) 234 | push!(Mpointers,appendSet!(Hsite,M)) 235 | elseif bIndex == aRepIndex 236 | Hbb += (0.125*K*(2-sPw12-sPw34-sPw13-sPw24+sPw14+sPw23))*(z^g)*exp(-1.0im*(kx*lx+ky*ly))*sqrt(basis.n[aRepIndex]/nb) 237 | end 238 | end 239 | 240 | end 241 | 242 | # NOTE: the leading factor of 0.5 in each diagonal term is due to the 243 | # sparseHermitian type which requires diagonal elements to be 244 | # divided by 2 before being passed 245 | Hbb *= 0.5 246 | # push diagonal matrix element to list of matrix elements 247 | push!(I,bIndex) 248 | push!(Mpointers,appendSet!(Hbb,M)) 249 | 250 | end 251 | 252 | # CSC formatting requires this 253 | Jpointers[end] = length(I)+1 254 | 255 | # return a sparseHermitian representation of the Hamiltonian matrix 256 | H::sparseHermitian{Int32,Complex128} = sparseHermitian{Int32,Complex128}(basis.dim,basis.dim,Jpointers,I,M,Mpointers) 257 | return H 258 | end 259 | --------------------------------------------------------------------------------