Repository navigation
Description
I had some code that I decided to migrate from TenPy, but unfortunately performance is far worse for some reason. The relevant part of the Julia code is this:
disordered_distance(i, j, dx) = abs(j - i + dx[j] - dx[i])
function build_rydberg_hamiltonian(L, Δ, Ω, σ, t, V, seed)
# Random displacements per site, uniform in [-sigma/2, sigma/2]
rng = MersenneTwister(seed)
dx = (rand(rng, L) .- 0.5) .* σ
Vphys = ℂ^2
physical_spaces = fill(Vphys, L)
σx = TensorMap([0 1; 1 0], Vphys ← Vphys)
σz = TensorMap([1 0; 0 -1], Vphys ← Vphys)
Id = id(Vphys)
Nop = (Id + σz) / 2
Sp = TensorMap([0 1; 0 0], Vphys ← Vphys)
Sm = TensorMap([0 0; 1 0], Vphys ← Vphys)
lattice = fill(Vphys, L)
on_site_terms = [(i,) => -Δ * Nop + Ω * σx for i in 1:L]
interaction_terms = []
for i in 1:L, j in 1:L
if i < j
r = disordered_distance(i,j,dx)
Vij = V/r^6
tij = t/r^3
push!(interaction_terms, (i,j) => Vij*Nop⊗Nop + tij * ( Sp ⊗ Sm + Sm⊗Sp))
end
end
H = FiniteMPOHamiltonian(lattice, on_site_terms..., interaction_terms...)
return H, physical_spaces
end
# Sample values
Δ = 1.0; Ω = 1.0; V = 1.0; t=1.0;
L = 25
chi_max = 50
psi = FiniteMPS(L, ComplexSpace(2), ComplexSpace(chi_max))
H, physical_spaces = build_rydberg_hamiltonian(L, Δ, Ω, sigma, t, V, seed)
psi, envs, dmrg_info = find_groundstate!(psi, H, DMRG(; maxiter=10))
As you can see this implements a Hamiltonian with long range interactions. For comparison, the TenPy code model definition is:
class RydbergModel(CouplingMPOModel):
def init_sites(self, model_params):
# symmetry settings
conserve = model_params.get('conserve', None, str)
sort_charge = model_params.get('sort_charge', True, bool)
# define spin-1/2 local Hilbert space
site = SpinHalfSite(conserve=conserve, sort_charge=sort_charge)
n = np.diag([1,0])
# site.add_op('N', 0.5 * site.Sigmaz + 0.5 * site.Id)
site.add_op('N',n)
return site
def init_terms(self, model_params):
counter = 0
# read parameters
Delta = model_params.get('Delta', 0.0)
Omega = model_params.get('Omega', 0.0)
sigma = model_params.get('sigma', 0.0)
t = model_params.get('t', 0.0)
V = model_params.get('V', 1.0)
seed = model_params.get('seed', 0.0)
N = self.lat.N_sites
L = self.lat.Ls[0]
#for dx in range(1, L):
# randomness on possition dxi:
# so true position of each atom is no longer i, but i + dxi
rng = np.random.default_rng(seed)
dx_randomness = rng.uniform(-0.5, 0.5, size=L) * sigma
# This is for non-periodic boundary conditions
for i in range(L):
for j in range(i+1,L): # This ensures i < j
dx = abs((j+dx_randomness[j]) - (i + dx_randomness[i]))
Vij = V / dx**6
tij = t / ( dx**3) # run again with this value
self.add_coupling_term(tij, i,j, 'Sp', 'Sm', plus_hc=True)
self.add_coupling_term(Vij, i, j,'N','N')
self.add_onsite(-Delta, 0, 'N')
self.add_onsite(Omega, 0, 'Sigmax')
and then for the actual ground state computation I run something like
model = MyTFIChain(model_params)
psi = MPS.from_lat_product_state(model.lat, [['up']])
dmrg_params = {
'mixer': True, # setting this to True helps to escape local minima
'max_E_err': 1.e-6,
'trunc_params': {
'chi_max': chi_max,
'svd_min': 1.e-10,
},
'verbose': True,
'combine': True
}
# get the data:
eng = dmrg.TwoSiteDMRGEngine(psi, model, dmrg_params)
E, psi = eng.run()
The TenPy version runs a lot faster. In my actual code, I perform sweeps over regions of Δ and Ω. The TenPy version took about 4.5 minutes to run a sweep over a 5x5 region, while MPSKit takes almost as much time for even the first DMRG calculation (I don't count the time for compilation).
Am I using the wrong algorithm for something, is it a problem with my code, or is TenPy just faster currently?
Activity
lkdvos commentedon Jul 14, 2026
Hi Andreas, thanks for reporting this!
My biggest hunch here is that our default MPO constructor is not actually trying to do anything smart (in fact, it is doing the most naive thing possible), so I'm guessing that for this model with many long-range terms, TenPy is just generating a hamiltonian that is a lot more efficient.
To confirm this and possibly try improve MPSKit's version, something you could try is to see if doing a (quasi-exact) compression of the MPO yields any improvements:
A second issue (that I'm currently trying to resolve, see #455) is that TenPy is a little bit smarter at the moment with its use of adaptive tolerances in the 2site scheme, which likely also makes their sweeps a little bit faster.
You could try and play around a bit with the default tolerances in MPSKit right now, but I am hoping to get this fixed soon.
I would definitely be happy to hear if this is improving the runtimes at all, and investigate what is going on otherwise!
AndreasChristophilopoulos commentedon Jul 15, 2026
Thanks a lot for responding. Alright, for starters I will my full program, the way it was originally. For reference, here is the whole thing, in case someone wants to copy-paste and try it out.
I have your version of compressing the MPO commented out in the "build_rydberg_hamiltonian" function. I ran it with the following command:
After it's done I will record the time it took and run it your way next. Here is the full code for replication purposes:
AndreasChristophilopoulos commentedon Jul 15, 2026
Ok, so I ended up running with --D -4,0,2 and --Omega -4,0,2 instead because it was taking too much time. I had also made a couple of typos in the code that I posted, I edited the post to fix it. The first run took about 1009 seconds. Now I am waiting for the second one (that uses your recommendation) to be completed.
AndreasChristophilopoulos commentedon Jul 15, 2026
Uh oh, SVDCut wasn't found... Does it come from some other package I haven't imported?
lkdvos commentedon Jul 15, 2026
Oh I'm sorry, it should have been
SvdCut(note the capitalization)AndreasChristophilopoulos commentedon Jul 15, 2026
That was far better, it took about 214 seconds overall. In fact I think there's some compilation related stuff leaking into it, I'll benchmark it so that it doesn't count that, and then compare to TenPy.
AndreasChristophilopoulos commentedon Jul 15, 2026
Ok, now that I'm using btime on my function, I see that it actually took about 37 seconds to do the actual computation. Meanwhile, the TenPy version which I have parallelized a bit so that it's a little bit faster than it was took about 15-16 seconds. So, TenPy still seems to be faster, but not by as much.
AndreasChristophilopoulos commentedon Jul 15, 2026
Upon further testing, I found that after your suggestion, it is approximately twice as slow as the original, un-parallelized TenPy code, which is not as bad as it was at first.
However, another thing I noticed is that increasing the values I sweep over also increases the compilation time, which is confusing to me...
lkdvos commentedon Jul 15, 2026
That does sound like the remainder might be due to the difference in the eigenvalue settings, especially if just the maximal number of iterations is fixed. I think MPSKit is (overly) cautious by default, and something like this might also produce reasonable results:
This is hopefully what #455 will handle better though.
(Note that there is also always the BLAS settings, which are somewhat harder to get to be the same, and don't really reflect on the speed of the libraries. I assume you are not setting the number of BLAS threads manually? If you are on a machine that supports this, also
using MKLtends to be a bit faster.)Regarding the compilation time, I'm not entirely sure why and how that is happening, my first guess would be to check for type stabilities etc, but I can't spot any immediate issues in your script. If you plan on doing quite a bit of these simulations, it might be worth it to invest in a small startup package to precompile a bunch of this, see e.g. https://julialang.github.io/PrecompileTools.jl/stable/#Tutorial:-local-%22Startup%22-packages for some pointers on that
AndreasChristophilopoulos commentedon Jul 15, 2026
Oh I'm not manually setting anything BLAS related. I will try out your recommendations tomorrow.
AndreasChristophilopoulos commentedon Jul 21, 2026
Hey @lkdvos , it took me longer than expected to get back to it, but I did, and... I tried copying your code, but it gives an undefined keyword error, because "keyword argument
trschemenot assigned". I also tried to fix it by giving it a trscheme keyword similar to what I gave the MPO Hamiltonian, like this:alg = DMRG2(; maxiter=10, alg_eigsolve=(; krylovdim=16, maxiter=1, dynamic_tols=true, tol_min=1e-8, tol_max=1e-1, tol_factor=1e-1, trscheme = trunctol(; atol=1e-12) ) )but somehow I still get the same error...
AndreasChristophilopoulos commentedon Jul 21, 2026
Nevermind, I just had to put trscheme outside of alg_eigensolve...
AndreasChristophilopoulos commentedon Jul 21, 2026
Ok, it seems to be worse than before now hahaha, first of all obviously I'm not getting any convergence with 1 iteration, but even with 100 iterations I'm still not getting any convergence. I just get a LOT of warnings about it, and I have to wait a really long time for each DMRG step to finish. I don't have a good handle on how these algorithms work, so I'm not sure what to do to optimize the parameters...