├── .gitignore ├── nodes_1.png ├── nodes_2.png ├── README.md ├── bonds.jl ├── plot_magnetization.jl ├── projmpo1.jl ├── nodes.py ├── main_groundstate.jl ├── generate_MPO.jl ├── main.jl ├── convergence.jl ├── observer_overload.jl ├── convergence.py ├── dmrg1.jl └── lattices.jl /.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | output/ 3 | figs/ 4 | .DS_Store -------------------------------------------------------------------------------- /nodes_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallerinos/skyrmion_mps/HEAD/nodes_1.png -------------------------------------------------------------------------------- /nodes_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hallerinos/skyrmion_mps/HEAD/nodes_2.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # https://arxiv.org/abs/2112.12475 2 | [![DOI](https://zenodo.org/badge/440820134.svg)](https://zenodo.org/badge/latestdoi/440820134) 3 | -------------------------------------------------------------------------------- /bonds.jl: -------------------------------------------------------------------------------- 1 | struct Bond 2 | s1::Int 3 | s2::Int 4 | r1::Vector{Float64} 5 | r2::Vector{Float64} 6 | desc::String 7 | end 8 | 9 | function Bond(s1::Int, s2::Int) 10 | return Bond(s1, s2, [0.0], [0.0], "") 11 | end 12 | 13 | Bond(s1::Int, s2::Int, r1::Vector, r2::Vector, desc::String="") = Bond(s1, s2, convert(Vector{Float64}, r1), convert(Vector{Float64}, r2), desc); 14 | 15 | const Graph = Vector{Bond} -------------------------------------------------------------------------------- /plot_magnetization.jl: -------------------------------------------------------------------------------- 1 | using PyPlot, PyCall 2 | function plot_magnetization(psi::MPS, graph::Graph, fn::String, ps::Int64) 3 | ns = unique([[(b.s1, b.r1) for b in graph]; [(b.s2, b.r2) for b in graph]]) 4 | xs = [n[2][1] for n in ns] 5 | ys = [n[2][2] for n in ns] 6 | lobs = [expect(psi, lob) for lob in ["Sx", "Sy", "Sz"]] 7 | fig = plt.figure(figsize=2.0.*((3+3/8), (3+3/8)/1.2)) 8 | plt.scatter(xs, ys, cmap="RdBu_r", c=lobs[3], marker="h", s=ps, vmin=-0.5, vmax=0.5) 9 | plt.quiver(xs, ys, lobs[1], lobs[2], scale=1, units="xy", pivot="middle", color="white") 10 | maxx = maximum(xs) 11 | maxy = maximum(ys) 12 | plt.ylim(-maxy-1/sqrt(3),maxy+1/sqrt(3)) 13 | plt.xlim(-maxx-1,maxx+1) 14 | plt.axis("off") 15 | plt.tight_layout() 16 | plt.savefig(fn) 17 | plt.close() 18 | end -------------------------------------------------------------------------------- /projmpo1.jl: -------------------------------------------------------------------------------- 1 | import ITensors.AbstractProjMPO 2 | 3 | """ 4 | A ProjMPO computes and stores the projection of an 5 | MPO into a basis defined by an MPS, leaving a 6 | certain number of site indices of the MPO unprojected. 7 | Which sites are unprojected can be shifted by calling 8 | the `position!` method. 9 | 10 | Drawing of the network represented by a ProjMPO `P(H)`, 11 | showing the case of `nsite(P)==1` and `position!(P,psi,5)` 12 | for an MPS `psi`: 13 | 14 | ``` 15 | o--o--o--o- -o--o--o--o--o--o 20 | ``` 21 | """ 22 | mutable struct ProjMPO1 <: AbstractProjMPO 23 | lpos::Int 24 | rpos::Int 25 | nsite::Int 26 | H::MPO 27 | LR::Vector{ITensor} 28 | ProjMPO1(H::MPO) = new(0, length(H) + 1, 1, H, Vector{ITensor}(undef, length(H))) 29 | end 30 | -------------------------------------------------------------------------------- /nodes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | import numpy as np 4 | 5 | from matplotlib.pyplot import cm, colorbar, tight_layout 6 | import matplotlib.colors as mcolors 7 | from mpl_toolkits.axes_grid1 import ImageGrid 8 | import matplotlib 9 | import matplotlib.pyplot as plt 10 | matplotlib.rcParams['text.usetex'] = True 11 | matplotlib.rcParams['figure.figsize'] = ((3+3/8), (3+3/8)) 12 | plt.rc('text.latex', preamble=r'\usepackage{bm}') 13 | 14 | import matplotlib.pyplot as plt 15 | import pandas as pd 16 | 17 | import os 18 | 19 | find_str = '*.csv' 20 | path_in = './output/slow' 21 | path_out = './figs' 22 | 23 | indicate_bond = True 24 | fns = [] 25 | for fn in os.popen("find " + str(path_in) + " -path " 26 | + '"' + str(find_str) + '"').read().split('\n')[0:-1]: 27 | fns.append(fn) 28 | fns = fns[0:1] 29 | dfs = [pd.read_csv(fn) for fn in fns] 30 | fig, ax = plt.subplots() 31 | for (df,fn) in zip(dfs,fns): 32 | # ax.scatter(df['X'], df['Y'], c='white', marker='h', edgecolors='black', s=600) 33 | energy = np.unique(df['E'])[0] 34 | df = df[df['E']==energy] 35 | for (id,(x,y)) in enumerate(zip(df['X'], df['Y'])): 36 | print(id) 37 | ax.scatter(x, y, c='white', marker='h', edgecolors='black', s=600) 38 | # ax.text(x+0.04,y-0.02,f'${id+1}$',va='center',ha='center') 39 | # plt.colorbar(im) 40 | mx = 1.175*np.max(np.abs(df['X'])) 41 | ax.set_xlim(-mx,mx) 42 | ax.set_ylim(-mx,mx) 43 | fn_rpl = fn.replace('.csv','') 44 | if not os.path.isdir(f'{path_out}/{fn_rpl}/'): 45 | os.makedirs(f'{path_out}/{fn_rpl}/') 46 | ax.axis('off') 47 | plt.tight_layout() 48 | plt.savefig(f'{path_out}/{fn_rpl}/nodes.png',dpi=300) 49 | plt.cla() -------------------------------------------------------------------------------- /main_groundstate.jl: -------------------------------------------------------------------------------- 1 | using ITensors, LinearAlgebra 2 | 3 | include("bonds.jl") 4 | include("lattices.jl") 5 | include("generate_MPO.jl") 6 | include("dmrg1.jl") 7 | include("projmpo1.jl") 8 | 9 | let 10 | # --------------- system settings ---------------- 11 | Ny = 8 # lattice dimension in y-direction 12 | Nx = Ny # lattice dim. in x direction (redundant for some lattices) 13 | absD = 1.0 # DMI amplitude 14 | J = -1/2*absD # Heisenberg interaction 15 | K = -0 # uniaxial anisotropy 16 | φ = 0 # polar angle of the magnetic field 17 | ϑ = 0 # axial angle of the magnetic field 18 | params = Dict("-D"=>absD, "-J"=>J, "-K"=>K, "-phi"=>φ, "-theta"=>ϑ) 19 | for B in (1:20).*(-absD/10) 20 | params["-B0"] = B 21 | # --------------- MPS settings ---------------- 22 | M = 128 # set the maximum bond dimension 23 | Ns = 1000 # set the maximum number of sweeps 24 | etresh = 1e-6 # naïve stopping criterion 25 | outputlevel = 1 # increase output from 0,1,2 26 | 27 | # --------------- initialization ---------------- 28 | sweeps = Sweeps(Ns) # initialize sweeps object 29 | maxdim!(sweeps, M) 30 | cutoff!(sweeps, 1e-12) 31 | N, graph = triangular_disk(Nx, Ny) # see available graphs in lattices.jl 32 | sites = siteinds("S=1", N) 33 | psi = randomMPS(sites, M) 34 | H = generate_MPO(graph, sites, params) 35 | obs = DMRGObserver(; energy_tol=etresh) 36 | 37 | # --------------- perform 1-site DMRG ---------------- 38 | ene, psi = dmrg1(H, psi, sweeps, weight=1000, observer=obs, outputlevel=outputlevel) 39 | 40 | # --------------- plot the magnetization ---------------- 41 | include("plot_magnetization.jl") 42 | fn = "magnetization_B$(B)_J$(J)_K$(K)_D$(absD)_M$(M).jpg" 43 | plot_magnetization(psi, graph, fn, 1200) 44 | end 45 | end -------------------------------------------------------------------------------- /generate_MPO.jl: -------------------------------------------------------------------------------- 1 | using LinearAlgebra 2 | # some convenient definitions 3 | dvec(r) = cross([0,0,1], r) 4 | function epsilon(i,j,k) 5 | if [i,j,k] in [[1,2,3], [3,1,2], [2,3,1]] 6 | return +1 7 | elseif [i,j,k] in [[2,1,3], [3,2,1], [1,3,2]] 8 | return -1 9 | else 10 | return 0 11 | end 12 | end 13 | 14 | function generate_MPO(graph::Graph, sites::Vector{Index{Int64}}, params::Dict) 15 | # get parameters 16 | mag_field = params["-B0"] 17 | heis_exch = params["-J"] 18 | absD = params["-D"] 19 | K = params["-K"] 20 | φ = params["-phi"] 21 | ϑ = params["-theta"] 22 | 23 | 24 | n𝐁 = [sin(ϑ)*cos(φ), sin(ϑ)*sin(φ), cos(ϑ)] # the unit vector defining the direction of the magnetic field 25 | 𝐒 = ["Sx", "Sy", "Sz"] # vector consisting of the spin matrices 0.5σᵢ 26 | 27 | # automated MPO generation by a sum of operator expressions 28 | ampo = OpSum() 29 | nodes = unique([[(b.s1, b.r1) for b in graph]; [(b.s2, b.r2) for b in graph]]) 30 | # uniform magnetic field on all sites 31 | for n in nodes 32 | for s in 1:length(𝐒) 33 | ampo += mag_field*n𝐁[s], 𝐒[s], n[1] 34 | end 35 | end 36 | # loop over all bonds of the graph 37 | for b in graph 38 | dir = b.r2 .- b.r1 # the direction vector between lattice nodes 39 | dist = norm(dir) # not really used here, but for completeness... 40 | 𝐃 = absD.*dvec(dir) 41 | if dist ≈ 1 42 | # define uniform Heisenberg interaction 43 | for s in 1:length(𝐒) 44 | ampo .+= +heis_exch, 𝐒[s], b.s1, 𝐒[s], b.s2 45 | ampo .+= +K*n𝐁[s], 𝐒[s], b.s1, 𝐒[s], b.s2 46 | end 47 | 48 | # the DMI interaction 49 | for i in 1:3, j in 1:3, k in 1:3 50 | if epsilon(i,j,k) != 0 # only add nonzero terms 51 | ampo .+= 𝐃[i]*epsilon(i,j,k), 𝐒[j], b.s1, 𝐒[k], b.s2 52 | end 53 | end 54 | end 55 | end 56 | # @show ampo 57 | return MPO(ampo,sites) 58 | end -------------------------------------------------------------------------------- /main.jl: -------------------------------------------------------------------------------- 1 | using ITensors, LinearAlgebra, Plots 2 | 3 | include("bonds.jl") 4 | include("lattices.jl") 5 | include("generate_MPO.jl") 6 | 7 | # --------------- system settings ---------------- 8 | Ny = 8 # lattice dimension in y-direction 9 | Nx = Ny # lattice dim. in x direction (redundant for some lattices) 10 | ns = 1 # how many low lying energy states to compute 11 | absD = 1.0 # DMI amplitude 12 | J = -1/2*absD # Heisenberg interaction 13 | B = -1/2*absD # magnetic field 14 | K = -0 # uniaxial anisotropy 15 | φ = 0 # polar angle of the magnetic field 16 | ϑ = 0 # axial angle of the magnetic field 17 | params = Dict("-B0"=>B, "-D"=>absD, "-J"=>J, "-K"=>K, "-phi"=>φ, "-theta"=>ϑ) 18 | 19 | # --------------- MPS settings ---------------- 20 | M = 32 # set the maximum bond dimension 21 | Ns = 30 # set the maximum number of sweeps 22 | etresh = 1e-6 # naïve stopping criterion 23 | restart = false # restarting from states MPS 24 | outputlevel = 1 # increase output from 0,1,2 25 | 26 | # --------------- initialization ---------------- 27 | sweeps = Sweeps(Ns) # initialize sweeps object 28 | maxdim!(sweeps, M) 29 | N, graph = triangular_disk(Nx, Ny) # see available graphs in lattices.jl 30 | psi0 = nothing 31 | if restart 32 | psi0 = states[1] 33 | sites = siteinds(psi0) 34 | else 35 | sites = siteinds("S=1/2", N) 36 | psi0 = randomMPS(sites, M) 37 | end 38 | H = generate_MPO(graph, sites, params) 39 | obs = DMRGObserver(; energy_tol=etresh) 40 | 41 | # --------------- perform 2-site DMRG ---------------- 42 | psi = psi0 43 | states = Vector{MPS}(undef, ns) 44 | for n=1:ns 45 | global ene, psi = (n==1 ? dmrg(H, psi, sweeps, observer=obs, outputlevel=outputlevel) : dmrg(H, states[1:n-1], psi, sweeps, weight=1000, observer=obs, outputlevel=outputlevel)) 46 | global states[n] = psi 47 | end 48 | 49 | # --------------- plot the magnetization (using Plots.jl) ---------------- 50 | ns = unique([[(b.s1, b.r1) for b in graph]; [(b.s2, b.r2) for b in graph]]) 51 | xs = [n[2][1] for n in ns] 52 | ys = [n[2][2] for n in ns] 53 | for (i, state) in enumerate(states) 54 | lobs = [expect(state, lob) for lob in ["Sx", "Sy", "Sz"]] 55 | fig = scatter(xs, ys, marker_z=-lobs[3], marker=:h, markersize=11, size=(200,200/1.15), c=:RdBu, legend=false) 56 | quiver!(xs, ys, quiver=(lobs[1],lobs[2]), color=:white, showaxis=false, ticks=false, legend=false) 57 | savefig("magnetization_B$(B)_J$(J)_K$(K)_M$(M)_state_$(i).pdf") 58 | end 59 | 0; -------------------------------------------------------------------------------- /convergence.jl: -------------------------------------------------------------------------------- 1 | using ITensors, Observers, ITensorTDVP, DataFrames, JLD, UUIDs, CSV 2 | 3 | include("bonds.jl") 4 | include("lattices.jl") 5 | include("generate_MPO.jl") 6 | include("dmrg1.jl") 7 | include("projmpo1.jl") 8 | include("observer_overload.jl") 9 | 10 | # --------------- system settings ---------------- 11 | dir, plot_all, outputlevel, Ns = "./output/all_optimizations", true, 1, 4 12 | dir, plot_all, outputlevel, Ns = "./output/once_per_sweep", false, 1, 240 13 | if !ispath(dir) 14 | mkpath(dir) 15 | end 16 | Ny = 8 # lattice dimension in y-direction 17 | Nx = Ny # lattice dim. in x direction (redundant for some lattices) 18 | absD = 1.0 # DMI amplitude 19 | J = -1/2*absD # Heisenberg interaction 20 | K = -0 # uniaxial anisotropy 21 | φ = 0 # polar angle of the magnetic field 22 | ϑ = 0 # axial angle of the magnetic field 23 | p = Dict("-D"=>absD, "-J"=>J, "-K"=>K, "-phi"=>φ, "-theta"=>ϑ) 24 | etresh = 1e-12 # naïve stopping criterion 25 | # --------------- MPS settings ---------------- 26 | M = 32 # set the maximum bond dimension 27 | # --------------- initialization ---------------- 28 | sweeps = Sweeps(Ns) # initialize sweeps object 29 | maxdim!(sweeps, M) 30 | cutoff!(sweeps, 1e-12) 31 | N, graph = triangular_disk(Nx, Ny) # see available graphs in lattices.jl 32 | nodes = sort(unique([[(b.s1, b.r1) for b in graph]; [(b.s2, b.r2) for b in graph]])) 33 | nps = getindex.(nodes,2) 34 | sites = siteinds("S=1/2", N) 35 | obs_dmrg = DMRGObserver(; energy_tol=etresh) 36 | state = ["Dn" for n=1:N] 37 | # --------------- main loop --------------------- 38 | Bs = (0:10).*(-absD/10) 39 | # Bs = [0.1, 0.5, 1.0] 40 | for B in Bs 41 | df = DataFrame() 42 | p["-B0"] = B 43 | psi = randomMPS(sites; linkdims=M) 44 | H = generate_MPO(graph, sites, p) 45 | for s in ["Sx","Sy","Sz"] 46 | df[!, s] = expect(psi,s) 47 | end 48 | ene = inner(psi', H, psi) 49 | df[!, "X"] = getindex.(nps,1) 50 | df[!, "Y"] = getindex.(nps,2) 51 | df[!, "Z"] = getindex.(nps,3) 52 | df[!, "sweep"] = [0 for i=1:size(df,1)] 53 | df[!, "half_sweep"] = [0 for i=1:size(df,1)] 54 | df[!, "bond"] = [0 for i=1:size(df,1)] 55 | df[!, "E"] = [real(ene) for i=1:size(df,1)] 56 | 57 | # --------------- perform 1-site DMRG ---------------- 58 | fn = "$dir/$(uuid1())" 59 | ene, psi = dmrg1(H, psi, sweeps, weight=1000, observer=obs_dmrg, nps=nps, df=df, outputlevel=outputlevel, fn=fn, pa=plot_all) 60 | 61 | # --------------- plot the magnetization ---------------- 62 | include("plot_magnetization.jl") 63 | # plot_magnetization(psi, graph, "$(fn).png", 2000) 64 | jldopen("$(fn).jld", "w") do file 65 | write(file, "fn", fn) 66 | write(file, "psi", psi) 67 | write(file, "graph", graph) 68 | write(file, "parameters", p) 69 | end 70 | CSV.write("$(fn).csv", df) 71 | end -------------------------------------------------------------------------------- /observer_overload.jl: -------------------------------------------------------------------------------- 1 | # checkdone overload based on changes of local observables 2 | function ITensors.checkdone!(o::DMRGObserver;kwargs...) 3 | lobs = o.measurements 4 | 5 | diff = 0.0 6 | if kwargs[:sweep] > 1 && lobs.count > 0 7 | for key in keys(lobs) 8 | # update maximum change of observables 9 | diff = maximum([diff, maximum(abs.(lobs[key][end-1] .- lobs[key][end]))]) 10 | end 11 | if diff < o.etol 12 | println("Difference in local observables $diff < $(o.etol). Stopping DMRG.") 13 | return true 14 | end 15 | elseif (length(energies(o)) > o.minsweeps && abs(energies(o)[end] - energies(o)[end - 1]) < o.etol) 16 | println("Energy difference less than $(o.etol). Stopping DMRG.") 17 | return true 18 | end 19 | 20 | # exit sweeping gracefully 21 | try 22 | if isfile("stop") 23 | println("Stop file in root directory. Exit gracefully.") 24 | return true 25 | end 26 | catch err 27 | @error("Could not exit gracefully.", err) 28 | return 29 | end 30 | 31 | return false 32 | end 33 | 34 | # measure overload based on changes of local observables 35 | function ITensors.measure!(o::DMRGObserver;kwargs...) 36 | measured = false 37 | if kwargs[:pa]==true 38 | df = DataFrame() 39 | for s in ["Sx", "Sy", "Sz"] 40 | df[!, s] = expect(kwargs[:psi],s) 41 | end 42 | df[!, "X"] = getindex.(kwargs[:nps],1) 43 | df[!, "Y"] = getindex.(kwargs[:nps],2) 44 | df[!, "Z"] = getindex.(kwargs[:nps],3) 45 | df[!, "sweep"] = [kwargs[:sweep] for i=1:size(df,1)] 46 | df[!, "half_sweep"] = [kwargs[:half_sweep] for i=1:size(df,1)] 47 | df[!, "bond"] = [kwargs[:bond] for i=1:size(df,1)] 48 | df[!, "E"] = [real(kwargs[:energy]) for i=1:size(df,1)] 49 | append!(kwargs[:df], df) 50 | 51 | CSV.write("$(kwargs[:fn]).csv", kwargs[:df]) 52 | measured = true 53 | end 54 | if kwargs[:bond]==length(kwargs[:psi])÷2 && kwargs[:half_sweep]==2 55 | push!(o.energies, kwargs[:energy]) 56 | 57 | if !measured 58 | df = DataFrame() 59 | for s in ["Sx", "Sy", "Sz"] 60 | df[!, s] = expect(kwargs[:psi],s) 61 | end 62 | df[!, "X"] = getindex.(kwargs[:nps],1) 63 | df[!, "Y"] = getindex.(kwargs[:nps],2) 64 | df[!, "Z"] = getindex.(kwargs[:nps],3) 65 | df[!, "sweep"] = [kwargs[:sweep] for i=1:size(df,1)] 66 | df[!, "half_sweep"] = [kwargs[:half_sweep] for i=1:size(df,1)] 67 | df[!, "bond"] = [kwargs[:bond] for i=1:size(df,1)] 68 | df[!, "E"] = [real(kwargs[:energy]) for i=1:size(df,1)] 69 | append!(kwargs[:df], df) 70 | 71 | CSV.write("$(kwargs[:fn]).csv", kwargs[:df]) 72 | end 73 | end 74 | return 75 | end -------------------------------------------------------------------------------- /convergence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | import numpy as np 4 | 5 | from matplotlib.pyplot import cm, colorbar, tight_layout 6 | import matplotlib.colors as mcolors 7 | from mpl_toolkits.axes_grid1 import ImageGrid 8 | import matplotlib 9 | import matplotlib.pyplot as plt 10 | matplotlib.rcParams['text.usetex'] = True 11 | matplotlib.rcParams['figure.figsize'] = ((3+3/8), (3+3/8)) 12 | plt.rc('text.latex', preamble=r'\usepackage{bm}') 13 | 14 | import matplotlib.pyplot as plt 15 | import pandas as pd 16 | 17 | import os 18 | 19 | find_str = '*.csv' 20 | path_in, indicate_bond = './output/all_optimizations', True 21 | # path_in, indicate_bond = './output/once_per_sweep', False 22 | path_out = './figs' 23 | fns = [] 24 | for fn in os.popen("find " + str(path_in) + " -path " 25 | + '"' + str(find_str) + '"').read().split('\n')[0:-1]: 26 | fns.append(fn) 27 | dfs = [pd.read_csv(fn) for fn in fns] 28 | fig, ax = plt.subplots() 29 | for (df,fn) in zip(dfs,fns): 30 | bonds = np.sort(np.unique(df['bond'])) 31 | sweeps = np.sort(np.unique(df['sweep'])) 32 | half_sweeps = np.sort(np.unique(df['half_sweep'])) 33 | 34 | s,hs,b = sweeps[0],half_sweeps[0],bonds[0] 35 | mx = 1.175*np.max(np.abs(df['X'])) 36 | data = df[df['sweep']==s] 37 | data = data[data['half_sweep']==hs] 38 | data = data[data['bond']==b] 39 | if len(data.iloc[:]) < 1: 40 | continue 41 | energies = np.unique(data['E']) 42 | mz = data['Sz'].mean() 43 | im = ax.scatter(data['X'], data['Y'], c=data['Sz'], marker='h', s=600, cmap='RdBu_r', vmin=-0.5, vmax=0.5) 44 | if indicate_bond: 45 | ax.scatter(data['X'].iloc[0], data['Y'].iloc[0], c='black', s=600, marker='h', cmap='RdBu_r', vmin=-0.5, vmax=0.5, alpha=0.6) 46 | ax.quiver(data['X'], data['Y'], data['Sx'], data['Sy'], units="xy", width=0.07, scale=1, pivot="middle", color="white") 47 | # plt.colorbar(im) 48 | ax.set_xlim(-mx,mx) 49 | ax.set_ylim(-mx,mx) 50 | s_str = f'{s}'.zfill(4) 51 | sweep_step = 0 52 | b_str = f'{sweep_step}'.zfill(3) 53 | ax.axis('off') 54 | ene_str = '{:.12f}'.format(energies[0]).zfill(16) 55 | mz_str = '\overline{m}_z' 56 | mz_val = '{:.12f}'.format(mz) 57 | ax.text(0,4.25,f'$E={ene_str}$',ha='center',va='center') 58 | ax.text(0,-4.35,f'${mz_str}={mz_val}$',ha='center',va='center') 59 | fn_rpl = fn.replace('.csv','') 60 | if not os.path.isdir(f'{path_out}/{fn_rpl}/'): 61 | os.makedirs(f'{path_out}/{fn_rpl}/') 62 | plt.tight_layout() 63 | plt.savefig(f'{path_out}/{fn_rpl}/{s_str}_{b_str}.png',dpi=600) 64 | plt.cla() 65 | 66 | bonds = bonds[1:] 67 | sweeps = sweeps[1:] 68 | half_sweeps = half_sweeps[1:] 69 | mx = 1.175*np.max(np.abs(df['X'])) 70 | for s in sweeps: 71 | for hs in half_sweeps: 72 | for b in bonds: 73 | print(s, hs, b, sweep_step) 74 | s_str = f'{s}'.zfill(4) 75 | fn_rpl = fn.replace('.csv','') 76 | sweep_step = int((hs-1)*121+((-1)**(hs-1)*b)) 77 | b_str = f'{sweep_step}'.zfill(3) 78 | if os.path.exists(f'{path_out}/{fn_rpl}/{s_str}_{b_str}.png'): 79 | continue 80 | data = df[df['sweep']==s] 81 | data = data[data['half_sweep']==hs] 82 | data = data[data['bond']==b] 83 | if len(data.iloc[:]) < 1: 84 | continue 85 | energies = np.unique(data['E']) 86 | mz = data['Sz'].mean() 87 | im = ax.scatter(data['X'], data['Y'], c=data['Sz'], marker='h', s=600, cmap='RdBu_r', vmin=-0.5, vmax=0.5) 88 | if indicate_bond: 89 | ax.scatter(data['X'].iloc[b-(hs-1)], data['Y'].iloc[b-(hs-1)], c='black', s=600, marker='h', cmap='RdBu_r', vmin=-0.5, vmax=0.5, alpha=0.6) 90 | ax.quiver(data['X'], data['Y'], data['Sx'], data['Sy'], units="xy", width=0.07, scale=1, pivot="middle", color="white") 91 | # plt.colorbar(im) 92 | ax.set_xlim(-mx,mx) 93 | ax.set_ylim(-mx,mx) 94 | ax.axis('off') 95 | ene_str = '{:.12f}'.format(energies[0]).zfill(16) 96 | mz_str = '\overline{m}_z' 97 | mz_val = '{:.12f}'.format(mz) 98 | ax.text(0,4.25,f'$E={ene_str}$',ha='center',va='center') 99 | ax.text(0,-4.35,f'${mz_str}={mz_val}$',ha='center',va='center') 100 | if not os.path.isdir(f'{path_out}/{fn_rpl}/'): 101 | os.makedirs(f'{path_out}/{fn_rpl}/') 102 | plt.tight_layout() 103 | plt.savefig(f'{path_out}/{fn_rpl}/{s_str}_{b_str}.png',dpi=1200) 104 | plt.cla() -------------------------------------------------------------------------------- /dmrg1.jl: -------------------------------------------------------------------------------- 1 | import ITensors.@debug_check 2 | import ITensors.@timeit_debug 3 | import ITensors.@printf 4 | import ITensors.factorize 5 | import ITensors.leftlim 6 | import ITensors.setleftlim! 7 | import ITensors.rightlim 8 | import ITensors.setrightlim! 9 | import ITensors.orthocenter 10 | import ITensors.check_hascommoninds 11 | import KrylovKit.eigsolve 12 | 13 | function dmrg1(H::MPO, psi0::MPS, sweeps::Sweeps; kwargs...)::Tuple{Number, MPS, Vector{Float64}} 14 | check_hascommoninds(siteinds, H, psi0) 15 | check_hascommoninds(siteinds, H, psi0') 16 | # Permute the indices to have a better memory layout 17 | # and minimize permutations 18 | H = permute(H, (linkind, siteinds, linkind)) 19 | PH = ProjMPO1(H) 20 | 21 | if length(psi0) == 1 22 | error( 23 | "`dmrg` currently does not support system sizes of 1. You can diagonalize the MPO tensor directly with tools like `LinearAlgebra.eigen`, `KrylovKit.eigsolve`, etc.", 24 | ) 25 | end 26 | 27 | @debug_check begin 28 | # Debug level checks 29 | # Enable with ITensors.enable_debug_checks() 30 | checkflux(psi0) 31 | checkflux(PH) 32 | end 33 | 34 | which_decomp::Union{String,Nothing} = get(kwargs, :which_decomp, nothing) 35 | svd_alg::String = get(kwargs, :svd_alg, "divide_and_conquer") 36 | obs = get(kwargs, :observer, NoObserver()) 37 | outputlevel::Int = get(kwargs, :outputlevel, 1) 38 | 39 | write_when_maxdim_exceeds::Union{Int,Nothing} = get( 40 | kwargs, :write_when_maxdim_exceeds, nothing 41 | ) 42 | 43 | # eigsolve kwargs 44 | eigsolve_tol::Float64 = get(kwargs, :eigsolve_tol, 1e-14) 45 | eigsolve_krylovdim::Int = get(kwargs, :eigsolve_krylovdim, 3) 46 | eigsolve_maxiter::Int = get(kwargs, :eigsolve_maxiter, 1) 47 | eigsolve_verbosity::Int = get(kwargs, :eigsolve_verbosity, 0) 48 | 49 | # TODO: add support for non-Hermitian DMRG 50 | ishermitian::Bool = get(kwargs, :ishermitian, true) 51 | 52 | # TODO: add support for targeting other states with DMRG 53 | # (such as the state with the largest eigenvalue) 54 | # get(kwargs, :eigsolve_which_eigenvalue, :SR) 55 | eigsolve_which_eigenvalue::Symbol = :SR 56 | 57 | # TODO: use this as preferred syntax for passing arguments 58 | # to eigsolve 59 | #default_eigsolve_args = (tol = 1e-14, krylovdim = 3, maxiter = 1, 60 | # verbosity = 0, ishermitian = true, 61 | # which_eigenvalue = :SR) 62 | #eigsolve = get(kwargs, :eigsolve, default_eigsolve_args) 63 | 64 | # Keyword argument deprecations 65 | if haskey(kwargs, :maxiter) 66 | error("""maxiter keyword has been replaced by eigsolve_krylovdim. 67 | Note: compared to the C++ version of ITensor, 68 | setting eigsolve_krylovdim 3 is the same as setting 69 | a maxiter of 2.""") 70 | end 71 | 72 | if haskey(kwargs, :errgoal) 73 | error("errgoal keyword has been replaced by eigsolve_tol.") 74 | end 75 | 76 | if haskey(kwargs, :quiet) 77 | error("quiet keyword has been replaced by outputlevel") 78 | end 79 | 80 | psi = copy(psi0) 81 | N = length(psi) 82 | 83 | if !isortho(psi) || orthocenter(psi) != 1 84 | orthogonalize!(psi, 1) 85 | end 86 | @assert isortho(psi) && orthocenter(psi) == 1 87 | 88 | position!(PH, psi, 1) 89 | energy = 0.0 90 | 91 | sw_times = [] 92 | for sw in 1:nsweep(sweeps) 93 | sw_time = @elapsed begin 94 | maxtruncerr = 0.0 95 | 96 | if !isnothing(write_when_maxdim_exceeds) && 97 | maxdim(sweeps, sw) > write_when_maxdim_exceeds 98 | if outputlevel >= 2 99 | println( 100 | "write_when_maxdim_exceeds = $write_when_maxdim_exceeds and maxdim(sweeps, sw) = $(maxdim(sweeps, sw)), writing environment tensors to disk", 101 | ) 102 | end 103 | PH = disk(PH) 104 | end 105 | 106 | for (b, ha) in sweepnext(N) 107 | @debug_check begin 108 | checkflux(psi) 109 | checkflux(PH) 110 | end 111 | 112 | @timeit_debug timer "dmrg: position!" begin 113 | if ha==1 114 | position!(PH, psi, b) 115 | else 116 | position!(PH, psi, b+1) 117 | end 118 | end 119 | 120 | @debug_check begin 121 | checkflux(psi) 122 | checkflux(PH) 123 | end 124 | 125 | @timeit_debug timer "dmrg: psi[b/b+1]" begin 126 | if ha == 1 127 | phi = psi[b] 128 | else 129 | phi = psi[b+1] 130 | end 131 | end 132 | 133 | @timeit_debug timer "dmrg: eigsolve" begin 134 | vals, vecs = eigsolve( 135 | PH, 136 | phi, 137 | 1, 138 | eigsolve_which_eigenvalue; 139 | ishermitian=ishermitian, 140 | tol=eigsolve_tol, 141 | krylovdim=eigsolve_krylovdim, 142 | maxiter=eigsolve_maxiter, 143 | ) 144 | end 145 | energy::Number = vals[1] 146 | phi::ITensor = vecs[1] 147 | 148 | ortho = ha == 1 ? "left" : "right" 149 | 150 | @debug_check begin 151 | checkflux(phi) 152 | end 153 | 154 | @timeit_debug timer "dmrg: factorize" begin 155 | if ha==1 156 | indsMb = inds(psi[b]) 157 | if b==1 158 | qrlinks = indsMb[1] 159 | else 160 | qrlinks = indsMb[1:2] 161 | end 162 | else 163 | indsMb = inds(psi[b+1]) 164 | if b==length(psi)-1 165 | qrlinks = indsMb[2] 166 | else 167 | qrlinks = indsMb[2:3] 168 | end 169 | end 170 | Q, R, spec = factorize(phi, qrlinks; which_decomp=which_decomp, positive=true, tags=tags(linkind(psi, b))) 171 | if ortho == "left" 172 | psi[b] = Q 173 | psi[b+1] = R*psi[b+1] 174 | leftlim(psi) == b - 1 && setleftlim!(psi, leftlim(psi) + 1) 175 | rightlim(psi) == b + 1 && setrightlim!(psi, rightlim(psi) + 1) 176 | (psi[b + 1] ./= norm(psi[b + 1])) 177 | elseif ortho == "right" 178 | psi[b] = psi[b]*R 179 | psi[b+1] = Q 180 | leftlim(psi) == b && setleftlim!(psi, leftlim(psi) - 1) 181 | rightlim(psi) == b + 2 && setrightlim!(psi, rightlim(psi) - 1) 182 | (psi[b] ./= norm(psi[b])) 183 | else 184 | error( 185 | "In replacebond!, got ortho = $ortho, only currently supports `left` and `right`." 186 | ) 187 | end 188 | end 189 | maxtruncerr = max(maxtruncerr, spec.truncerr) 190 | 191 | @debug_check begin 192 | checkflux(psi) 193 | checkflux(PH) 194 | end 195 | 196 | if outputlevel >= 2 197 | @printf( 198 | "Sweep %d, half %d, bond (%d,%d) energy=%.12f\n", sw, ha, b, b + 1, energy 199 | ) 200 | @printf( 201 | " Truncated using cutoff=%.1E maxdim=%d mindim=%d\n", 202 | cutoff(sweeps, sw), 203 | maxdim(sweeps, sw), 204 | mindim(sweeps, sw) 205 | ) 206 | flush(stdout) 207 | end 208 | sweep_is_done = (b == 1 && ha == 2) 209 | measure!( 210 | obs; 211 | energy=energy, 212 | psi=psi, 213 | bond=b, 214 | sweep=sw, 215 | half_sweep=ha, 216 | spec=spec, 217 | outputlevel=outputlevel, 218 | sweep_is_done=sweep_is_done, 219 | kwargs... 220 | ) 221 | end 222 | end 223 | if outputlevel >= 1 224 | @printf( 225 | "After sweep %d energy=%.12f maxlinkdim=%d maxerr=%.2E time=%.3f\n", 226 | sw, 227 | energy, 228 | maxlinkdim(psi), 229 | maxtruncerr, 230 | sw_time 231 | ) 232 | flush(stdout) 233 | end 234 | append!(sw_times, sw_time) 235 | isdone = checkdone!(obs; energy=energy, psi=psi, sweep=sw, outputlevel=outputlevel) 236 | 237 | isdone && break 238 | end 239 | return (energy, psi, sw_times) 240 | end -------------------------------------------------------------------------------- /lattices.jl: -------------------------------------------------------------------------------- 1 | # rhomboid lattice (zigzag order) 2 | function rhomboid_zigzag(Nx::Int, Ny::Int; kwargs...) :: Tuple{Int64, Graph} 3 | yperiodic = get(kwargs, :yperiodic, false) 4 | yperiodic = yperiodic && (Nz > 2) 5 | yperiodic == true ? error("PBC not yet implemented.") : nothing 6 | 7 | a1 = [1.0, 0.0, 0.] 8 | a2 = [0.5, sqrt(3)/2, 0.] 9 | 10 | lattPos = [] 11 | for nx in 0:Nx-1, ny in 0:Ny-1 12 | pos = a1.*nx + a2.*ny 13 | append!(lattPos, [pos]) 14 | end 15 | lattPos = unique(lattPos) 16 | 17 | nns = [a1, -a2, -a1+a2] # consider only nearest neighbors 18 | 19 | latt = [] 20 | for (idr, r) in enumerate(lattPos), (idrpr, rpr) in enumerate(lattPos) 21 | rmrpr = rpr .- r 22 | for (iddir, dir) in enumerate(nns) 23 | rmrpr ≈ dir ? append!(latt, [Bond(idr, idrpr, r, rpr, "$iddir")]) : nothing 24 | end 25 | end 26 | 27 | return length(lattPos), latt 28 | end 29 | 30 | # rhomboid lattice (spiral order) 31 | function rhomboid_spiral(Nx::Int, Ny::Int; kwargs...) :: Tuple{Int64, Graph} 32 | yperiodic = get(kwargs, :yperiodic, false) 33 | yperiodic = yperiodic && (Nz > 2) 34 | yperiodic == true ? error("PBC not yet implemented.") : nothing 35 | 36 | a1 = [1.0, 0.0, 0.] 37 | a2 = [0.5, sqrt(3)/2, 0.] 38 | a3 = [-0.5, sqrt(3)/2, 0.] 39 | 40 | lattPos = [] 41 | pos = 0.0.*a1 42 | append!(lattPos, [pos]) 43 | for n=1:Nx-1 44 | pos += a1 45 | append!(lattPos, [pos]) 46 | for n2=1:2n-1 47 | pos += a2 48 | append!(lattPos, [pos]) 49 | end 50 | for uc in [-a1, -a2, a1], n2=1:2n 51 | pos += uc 52 | append!(lattPos, [pos]) 53 | end 54 | end 55 | # @show lattPos 56 | lattPos = unique(lattPos) 57 | # plt.scatter([b[1] for b in lattPos],[b[2] for b in lattPos]) 58 | 59 | 60 | nns = [a1, -a2, -a1+a2] # consider only nearest neighbors 61 | 62 | latt = [] 63 | for (idr, r) in enumerate(lattPos), (idrpr, rpr) in enumerate(lattPos) 64 | rmrpr = rpr .- r 65 | for (iddir, dir) in enumerate(nns) 66 | rmrpr ≈ dir ? append!(latt, [Bond(idr, idrpr, r, rpr, "$iddir")]) : nothing 67 | end 68 | end 69 | 70 | return length(lattPos), latt 71 | end 72 | 73 | # triangular lattice with disk boundary conditions 74 | function triangular_disk(Nx::Int, Ny::Int; kwargs...) :: Tuple{Int64, Graph} 75 | yperiodic = get(kwargs, :yperiodic, false) 76 | yperiodic = yperiodic && (Nz > 2) 77 | yperiodic == true ? error("PBC not yet implemented.") : nothing 78 | 79 | a1 = [1.0, 0.0, 0.] 80 | a2 = [0.5, sqrt(3)/2, 0.] 81 | 82 | lattPos = [] 83 | Nxs = -4Nx:4Nx 84 | for nx in Nxs, ny in Nxs 85 | pos = a1.*nx + a2.*ny 86 | if norm(pos) <= minimum(([Nx, Ny])./2) 87 | append!(lattPos, [pos]) 88 | end 89 | end 90 | lattPos = unique(lattPos) 91 | # for (idlp,lp) in enumerate(lattPos) 92 | # x, y = getindex(lp,1), getindex(lp,2) 93 | # plt.scatter(x,y) 94 | # plt.text(x,y,"$idlp") 95 | # end 96 | # plt.savefig("nodes.png") 97 | # plt.close() 98 | 99 | nns = [a1, -a2, -a1+a2] # consider only nearest neighbors 100 | 101 | latt = [] 102 | for (idr, r) in enumerate(lattPos), (idrpr, rpr) in enumerate(lattPos) 103 | rmrpr = rpr .- r 104 | for (iddir, dir) in enumerate(nns) 105 | rmrpr ≈ dir ? append!(latt, [Bond(idr, idrpr, r, rpr, "$iddir")]) : nothing 106 | end 107 | end 108 | 109 | return length(lattPos), latt 110 | end 111 | 112 | # square lattice 113 | function square(Nx::Int, Ny::Int; kwargs...) :: Tuple{Int64, Graph} 114 | yperiodic = get(kwargs, :yperiodic, false) 115 | yperiodic = yperiodic && (Nz > 2) 116 | yperiodic == true ? error("PBC not yet implemented.") : nothing 117 | 118 | a1 = [1.0, 0.0, 0.] 119 | a2 = [0.0, 1.0, 0.] 120 | 121 | lattPos = [] 122 | for nx in 0:Nx-1, ny in 0:Ny-1 123 | pos = a1.*nx + a2.*ny 124 | append!(lattPos, [pos]) 125 | end 126 | lattPos = unique(lattPos) 127 | 128 | nns = [a1, a2] # consider only nearest neighbors 129 | 130 | latt = [] 131 | for (idr, r) in enumerate(lattPos), (idrpr, rpr) in enumerate(lattPos) 132 | rmrpr = rpr .- r 133 | for (iddir, dir) in enumerate(nns) 134 | rmrpr ≈ dir ? append!(latt, [Bond(idr, idrpr, r, rpr, "$iddir")]) : nothing 135 | end 136 | end 137 | 138 | return length(lattPos), latt 139 | end 140 | 141 | # square lattice with disk boundary conditions 142 | function square_disk(Nx::Int, Ny::Int; kwargs...) :: Tuple{Int64, Graph} 143 | yperiodic = get(kwargs, :yperiodic, false) 144 | yperiodic = yperiodic && (Nz > 2) 145 | yperiodic == true ? error("PBC not yet implemented.") : nothing 146 | 147 | a1 = [1.0, 0.0, 0.] 148 | a2 = [0.0, 1.0, 0.] 149 | 150 | lattPos = [] 151 | Nxs = -4Nx:4Nx 152 | for nx in Nxs, ny in Nxs 153 | pos = a1.*nx + a2.*ny 154 | if norm(pos) <= minimum(([Nx, Ny])./2) 155 | append!(lattPos, [pos]) 156 | end 157 | end 158 | lattPos = unique(lattPos) 159 | 160 | nns = [a1, -a2, -a1+a2] # consider only nearest neighbors 161 | 162 | latt = [] 163 | for (idr, r) in enumerate(lattPos), (idrpr, rpr) in enumerate(lattPos) 164 | rmrpr = rpr .- r 165 | for (iddir, dir) in enumerate(nns) 166 | rmrpr ≈ dir ? append!(latt, [Bond(idr, idrpr, r, rpr, "$iddir")]) : nothing 167 | end 168 | end 169 | 170 | return length(lattPos), latt 171 | end 172 | 173 | # kagome lattice 174 | function kagome(Nx::Int, Ny::Int; kwargs...) :: Tuple{Int64, Graph} 175 | yperiodic = get(kwargs, :yperiodic, false) 176 | yperiodic = yperiodic && (Nz > 2) 177 | yperiodic == true ? error("PBC not yet implemented.") : nothing 178 | 179 | a1 = [1.0, 0.0, 0.] 180 | a1 /= norm(a1) # ensure normalized lattice vectors 181 | a2 = [0.5, sqrt(3)/2, 0.] 182 | a2 /= norm(a1) # ensure normalized lattice vectors 183 | b1 = 2.0.*a1 184 | b2 = 2.0.*a2 185 | 186 | lattPos = [] 187 | for nx in 0:Nx-1, ny in 0:Ny-1, uc in [0.0.*a1, a1, a2] 188 | if (nx==Nx-1)&&(ny 2) 225 | yperiodic == true ? error("PBC not yet implemented.") : nothing 226 | 227 | a1 = [1.0, 0.0, 0.] 228 | a1 /= norm(a1) # ensure normalized lattice vectors 229 | a2 = [0.5, sqrt(3)/2, 0.] 230 | a2 /= norm(a1) # ensure normalized lattice vectors 231 | b1 = 2.0.*a1 232 | b2 = 2.0.*a2 233 | 234 | lattPos = [] 235 | Nxs = -4Nx:4Nx 236 | for nx in Nxs, ny in Nxs, uc in [0.0.*a1, a1, a2] 237 | pos = b1.*nx + b2.*ny + uc 238 | if norm(pos) <= minimum(([Nx-1, Ny-1])./2) 239 | append!(lattPos, [pos]) 240 | end 241 | end 242 | lattPos = unique(lattPos) 243 | 244 | nns = [a1, -a2, -a1+a2] # consider only nearest neighbors 245 | 246 | latt = [] 247 | for (idr, r) in enumerate(lattPos), (idrpr, rpr) in enumerate(lattPos) 248 | rmrpr = rpr .- r 249 | for (iddir, dir) in enumerate(nns) 250 | rmrpr ≈ dir ? append!(latt, [Bond(idr, idrpr, r, rpr, "$iddir")]) : nothing 251 | end 252 | end 253 | 254 | return length(lattPos), latt 255 | end 256 | 257 | # honeycomb lattice 258 | function honeycomb(Nx::Int, Ny::Int; kwargs...) :: Tuple{Int64, Graph} 259 | yperiodic = get(kwargs, :yperiodic, false) 260 | yperiodic = yperiodic && (Nz > 2) 261 | yperiodic == true ? error("PBC not yet implemented.") : nothing 262 | 263 | b1 = 0.5.*[3.0, +sqrt(3), 0.] 264 | b2 = 0.5.*[3.0, -sqrt(3), 0.] 265 | a1 = [-1, +sqrt(3), 0.] 266 | a1 /= norm(a1) # ensure normalized lattice vectors 267 | a2 = [+1, +sqrt(3), 0.] 268 | a2 /= norm(a2) # ensure normalized lattice vectors 269 | 270 | lattPos = [] 271 | for nx in 0:Nx-1, ny in 0:Ny-1, uc in [a1, a2] 272 | if (nx==0&&ny==0) 273 | pos = b1.*nx + b2.*ny + a2 274 | elseif (nx==Nx-1&&ny==Ny-1) 275 | pos = b1.*nx + b2.*ny + a1 276 | else 277 | pos = b1.*nx + b2.*ny + uc 278 | end 279 | append!(lattPos, [pos]) 280 | end 281 | lattPos = unique(lattPos) 282 | 283 | nns = [a1-a2, a1, a2] # consider only nearest neighbors 284 | 285 | latt = [] 286 | for (idr, r) in enumerate(lattPos), (idrpr, rpr) in enumerate(lattPos) 287 | rmrpr = rpr .- r 288 | for (iddir, dir) in enumerate(nns) 289 | rmrpr ≈ dir ? append!(latt, [Bond(idr, idrpr, r, rpr, "$iddir")]) : nothing 290 | end 291 | end 292 | 293 | return length(lattPos), latt 294 | end --------------------------------------------------------------------------------