Step-by-step guide
Below is a step-by-step guide to setting up a CTQMC calculation with the CTSEG solver.
Step 1 - Choose the construction parameters
We first need to specify the parameters that will define the structure of the solver object. They can be conveniently supplied as a Python dictionary:
constr_params = {
"gf_struct": gf_struct,
"beta": 100,
"n_tau": 1001,
"n_tau_bosonic": 1001
}
gf_struct
specifies the block structure of the impurity Green’s function (see below).beta
is the inverse temperature.n_tau
is the number of points of the imaginary time grid on which the input hybridization \(\Delta(\tau)\) is sampled.It is also the default number of samples for the measured fermionic two-point functions.
n_tau_bosonic
is the number of points of the imaginary time grid on which the bosonic two-point function inputs (\(D_0(\tau)\) and \(\mathcal{J}_{\perp}(\tau)\)) are sampled.It is also the default number of samples for the measured bosonic two-point functions.
Green’s function structure
The parameter gf_struct
is a list of pairs of the form (block_name, block_size)
.
The choice of the blocks is not unique, but choosing the smallest possible blocks
ensures the best performance. The impurity problems solved by CTSEG are always spin-conserving,
it is therfore always possible to define at least two blocks up
and down
. For example, for a
single orbital:
gf_struct = [("up", 1), ("down", 1)]
It is suboptimal, but equivalent, to define a single-block structure for the same single-orbital problem:
gf_struct = [("orb", 2)]
Note
The choice of the block structure affects the Markov chain, unless move_move
is disabled.
Warning
For a problem with \(\mathcal{J}_{\perp}\) interactions, the structure must have two blocks of size 1.
Step 2 - Construct the solver
With the parameters set, we are ready to construct the solver object:
from triqs_ctseg import Solver
S = Solver(**constr_params)
Step 3 - Supply the solver inputs
We now need to provide the solver with the parameters of the action that define the impurity problem.
Here, we will give examples for a single-orbital impurity (gf_struct = [("up", 1), ("down", 1)]
).
The CTSEG input structure is similar to the CTHYB Delta interface.
The time-independent part of the action (i.e., the local Hamiltonian) is supplied as a TRIQS many-body operator, and the time-dependent parts
are supplied as the appropriate type of TRIQS Green’s functions.
Local Hamiltonian
The impurity problem must be supplied to CTSEG in the basis where the quadratic part of the local Hamiltonian is diagonal.
Quadratic part
In its eigenbasis, the quadratic part of the local Hamiltonian h_loc0
is
proportional to the density operator. Density operators are represented by n(block_name, orbital_number)
.
For our single orbital problem:
from triqs.operators import *
h_loc0 = (eps - mu) * (n("up", 0) + n("down", 0))
Here, eps
is the orbital energy and mu
the chemical potential.
The interaction Hamiltonian is passed to the solver as part of the solve parameters (see below).
Quartic part
In the impurity problems handled by CTSEG, the quartic part of the local Hamiltonian is always a density-density interaction. For our single orbital problem:
from triqs.operators import *
h_int = U * n("up", 0) * n("down", 0)
For more complex Hamiltonians (in multi-orbital problems), it is possible to use the h_int_density
function.
For example, for a two-orbital system (gf_struct = [("up", 2), ("down", 2)]
), one defines the interaction matrix for electrons of the same spin:
Umat = matrix([[0, V],
[V, 0]])
And the interaction matrix for electrons of opposite spin:
Upmat = matrix([[U, V],
[V, U]])
Then, the Hamiltonian is constructed as:
from triqs.operators.util import h_int_density
block_names = ["up", "down"]
n_orb = 2
h_int = h_int_density(block_names, n_orb, Umat, Upmat, off_diag = True)
The interaction Hamiltonian is passed to the solver as part of the solve parameters (see below).
Hybridization function
The CTSEG solver takes as an input the hybridization function \(\Delta(\tau)\) that appears in the impurity action (see CTSEG algorithm). It is initialized as:
from triqs.gf import *
from triqs.gf.tools import *
tau_mesh = MeshImTime(beta, 'Fermion', n_tau)
Delta_tau = BlockGf(mesh = tau_mesh, gf_struct = gf_struct)
The data in Delta_tau
can be specified manually (Delta_tau["block_name"].data = ...
), or with one
of the TRIQS built-in functions. For example:
iw_mesh = MeshImFreq(beta, 'Fermion', n_tau//2)
Delta_iw = BlockGf(mesh = iw_mesh, gf_struct = gf_struct)
Delta_iw << Fourier(Delta_tau)
Delta_iw << t**2 * SemiCircular(2 * t, chem_potential = mu)
Delta_tau << Fourier(Delta_iw)
The hybridization function is supplied to the solver via:
S.Delta_tau << Delta_tau
The value of n_tau
supplied in the constr_params
and the number of points in the \(\tau\) grid of
the \(\Delta(\tau)\) input must match.
This is different from the default interface of the CTHYB solver, which takes as input the non-interacting impurity Green’s function \(G_0(i\omega_n)\). It is defined as
where \(\hat h_0\) is the quadratic part of the local Hamiltonian. Note that \(\Delta(i\omega_n)\) vanishes in the high frequency limit.
The following code extracts from a BlockGf
G0_iw
a BlockGf
Delta_iw
and a matrix h0
for each block:
def get_h0_Delta(G0_iw):
h0_lst, Delta_iw = [], G0_iw.copy()
for bl in G0_iw.indices:
Delta_iw[bl] << iOmega_n - inverse(G0_iw[bl])
tail, err = fit_hermitian_tail(Delta_iw[bl])
Delta_iw[bl] << Delta_iw[bl] - tail[0]
h0_lst.append(tail[0])
return h0_lst, Delta_iw
h0_lst, Delta_iw = get_h0_Delta(G0_iw)
If the h0
are already expressed in the local eigenbasis, the obtained hybridization function can be
directly used as the solver input. In the general case, they need to be rotated to the eigenbasis:
rot_lst = [np.matrix(linalg.eig(h0_bl)[1]) for h0_bl in h0]
for (bl, g0_bl), rot_bl in zip(Delta_iw, rot_lst):
g0_bl << rot_bl.H * g0_bl * rot_bl
If a rotation is required, then the interactions should also be rotated accordingly. The non-interacting Hamiltonian operator in its eigenbasis is obtained as:
import numpy as np
from numpy import linalg
for i, (bl_name, bl_size) in enumerate(gf_struct):
for j in range bl_size:
if (i == 0 and j == 0):
hloc_0 = linalg.eig(h0_lst[i])[0][j].real * n(bl_name, j)
else:
hloc_0 += linalg.eig(h0_lst[i])[0][j].real * n(bl_name, j)
Dynamical density-density interaction
The dynamical density-density interaction \(D(\tau)\) (see CTSEG algorithm) is initialized as:
tau_mesh = MeshImTime(beta, 'Boson', n_tau_bosonic)
D_tau = Block2Gf(mesh = tau_mesh, gf_struct = gf_struct)
It is a two-block-index Green’s function, since the dynamical interaction does not necessarily have the same block diagonal structure as the hybridization function.
The data in D_tau
can be specified manually (D_tau["block1", "block2"].data = ...
) or by using an analytical expression.
For example:
from triqs.gf.descriptors import Function
wp = 1
D_iw = GfImFreq(indices = [0], beta = beta, statistic = "Boson", n_points = n_tau_bosonic//2)
d_tau = GfImTime(indices = [0], beta = beta, statistic = "Boson", n_points = n_tau_bosonic)
D_iw << Function(lambda w: wp**2 / (w**2 - wp**2))
d_tau << Fourier(D_iw)
D_tau["up", "up"] << d_tau
D_tau["up", "down"] << d_tau
D_tau["down", "up"] << d_tau
D_tau["down", "down"] << d_tau
The dynamical interaction is supplied to the solver via:
S.D0_tau << D_tau
Spin-spin interaction
The perpendicular spin-spin interaction \(J_{\perp}(\tau)\) (see CTSEG algorithm) is initialized as:
Jperp_tau = GfImTime(indices = [0], beta = beta, statistic = "Boson", n_points = n_tau_bosonic)
It is a \(1 \times 1\) matrix Green’s function, since perpendicular spin-spin interactions are only implemented for a single orbital. It is supplied to the solver via:
S.Jperp_tau << Jperp_tau
If the impurity action contains a spin-spin interaction term of the form \((1/2) \cdot Q(\tau - \tau') \sum_i s_i(\tau) s_i(\tau')\), it can be split into a density-density and a perpendicular spin-spin term. Indeed, making use of symmetry properties, we may replace in the action
The solver is then accordingly set up as:
S.Jperp_tau << Q_tau
S.D0_tau["up", "up"] << (1/4) * Q_tau
S.D0_tau["up", "down"] << - (1/4) * Q_tau
S.D0_tau["down", "up"] << - (1/4) * Q_tau
S.D0_tau["down", "down"] << (1/4) * Q_tau
The value of n_tau_bosonic
supplied in the constr_params
and the number of points in the \(\tau\) grids of
the \(D(\tau)\) and \(J_{\perp}(\tau)\) inputs must match.
Solve parameters
The parameters required to perform a CTQMC run are conveniently supplied as a Python dictionary. The following parameters need to be specified for every run. For example:
solve_params = {
"h_int": h_int,
"h_loc0": h_loc0,
"length_cycle": 50,
"n_warmup_cycles": 20000,
"n_cycles": 200000
}
h_int
is the local interaction Hamiltonian (see above).h_loc0
is quadratic part of the local Hamiltonian (see above).length_cycle
is the length of a Monte Carlo cycle. Observables are sampled everylength_cycle
Monte Carlo moves (either accepted or rejected).n_warmup_cycles
is the number of cycles to do before any observables are samples, so as to “forget” the initial configuration.n_cycles
is the number of cycles used for the production run.
Other parameters include:
Measure control. All the measurements can be switched on and off. Some of the measurements (self-energy improved estimator, density correlation functions) can be time-consuming, and they are off by default. For example, to turn the improved estimator measurement on, one should set
solve_params["measure_F_tau"] = True
.Move control. All the Monte Carlo moves can be switched on and off. This functionality exists to facilitate testing for developers. The solver chooses the relevant moves depending on its inputs, and regular users should not need move control.
Optional sample numbers for the measured two-point functions:
n_tau_G
(defaults ton_tau
) for fermionic functions andn_tau_chi2
(defaults ton_tau_bosonic
) for bosonic functions.
The complete list of parameters is available here.
Step 4 - Run the solver
The CTQMC run is triggered by:
S.solve(**solve_params)
After it is done accumulating, the solver prints the average acceptance rates. Very low acceptance rates for all moves (below 0.01)
are generally a sign that something went wrong. However, some of the moves (split_spin_segment
, regroup_spin_segment
)
often have low acceptance rates, even if the calculation runs as it should.
Step 5 - Access the results
The results of the accumulation are stored in S.results
. For example, the impurity Green’s function is accessed with:
G_tau = S.results.G_tau
The results can be analyzed using the TRIQS plotting tools (oplot
) or by directly extracting the data:
G_up_data = G_tau["up"].data
G_down_data = G_tau["down"].data
times = np.array([tau.value for tau in G_tau.mesh])
For a rotationally-invariant impurity, the spin-spin correlation function \(\chi(\tau) = (1/3) \sum_{i = x, y, z} \langle s_i(\tau) s_i(0) \rangle\) can be obtained from the density-density correlation function:
nn_tau = S.results.nn_tau
chi_tau = 0.25 * (nn_tau["up", "up"] + nn_tau["down", "down"] - nn_tau["up", "down"] - nn_tau["down", "up"])
If rotational invariance is broken (for instance, in the presence of a Zeeman field), one needs to measure separately the perpendicular spin-spin correlation function:
nn_tau = S.results.nn_tau
sperp_tau = S.results.sperp_tau
chi_tau = (0.25 * (nn_tau["up", "up"] + nn_tau["down", "down"] - nn_tau["up", "down"] - nn_tau["down", "up"]) + 2*sperp_tau) / 3
Step 6 - Save the results
The TRIQS h5
module is convenient for saving the results to an hdf5 file. It is possible to save all the data at once
by saving the solver object:
from h5 import *
with HDFArchive("results.h5", "a") as A:
A["Solver"] = S
Running the solver in parallel
The CTSEG solver supports MPI parallelism. If the solver run is set up in a file script.py
, a parallel run
is typically achieved with the command:
mpirun -np <n_cores> python script.py
Each core then runs its own Markov chain of length n_cycles
(starting from a different random number generator seed)
and at the end the results from the different cores are averaged together.