def setup_impurity_solver(self, mesh_param={}, sp={}, nrgp={}, verbose_nrg=False): self.cp.update( mesh_param ) # dictionary cp is set up by model-specific constructor of the derived class, but we need to add mesh_parameters self.S = Solver(**self.cp) self.S.set_verbosity(verbose_nrg) self.sp.update(sp) # add remaining solve parameters self.nrgp.update(nrgp) # optionally add low-level NRG parameters self.S.set_nrg_params(**self.nrgp) # Now we initialize the physical quantities Sigma, mu = self.initial_Sigma_mu( ) # Get initial self-energy and chemical potential Gloc, Delta = self_consistency( Sigma, mu, self.ht ) # Initialize local GF Gloc and hybridization function Delta Gloc, Delta, mu = self.adjust_mu( Delta, Sigma, mu ) # Adjust mu toward satisfying occupancy goal (and return updated Glocal and Delta as well) self.Gloc = Gloc.copy() # Initial approximation for local lattice GF self.Delta = Delta.copy( ) # Initial approximation for hybridization function self.set_mu(mu) # Sets mu and updates the impurity level
class test_2orb(unittest.TestCase): # Construct Parameters cp = {} cp["model"] = "2orb-UJ" cp["symtype"] = "QS" cp["mesh_max"] = 1.0 cp["mesh_min"] = 1e-1 cp["mesh_ratio"] = 1.1 # Set up the Solver S = Solver(**cp) # Solve Parameters sp = {} sp["T"] = 1e-1 sp["Lambda"] = 4.0 sp["Nz"] = 1 sp["Tmin"] = 0.5 sp["keep"] = 2000 sp["keepenergy"] = 10.0 # Model Parameters mp = {} mp["U1"] = 1.0 mp["U2"] = 0.9 mp["eps1"] = -0.5 mp["eps2"] = -0.4 mp["U12"] = 0.1 mp["J12"] = 0.05 sp["model_parameters"] = mp # Low-level NRG Parameters np = {} np["bins"] = 50 S.set_nrg_params(**np) # # Initialize hybridization function S.Delta_w['imp'][0,0] << 0.5 * SemiCircular(1.0) S.Delta_w['imp'][1,1] << 0.4 * SemiCircular(1.0) # Out-of-diagonal Delta is zero # Solve the impurity model S.solve(**sp) # # Store the Result with HDFArchive("3_2orb-UJ_QS.out.h5", 'w') as arch: arch["A_w"] = S.A_w arch["G_w"] = S.G_w arch["F_w"] = S.F_w arch["Sigma_w"] = S.Sigma_w # Compare against reference result h5diff("3_2orb-UJ_QS.out.h5", "3_2orb-UJ_QS.ref.h5")
class test_SIAM(unittest.TestCase): # Construct Parameters cp = {} cp["model"] = "SIAM" cp["symtype"] = "QS" cp["mesh_max"] = 1.0 cp["mesh_min"] = 1e-3 cp["mesh_ratio"] = 1.1 # Set up the Solver S = Solver(**cp) # Solve Parameters sp = {} sp["T"] = 1e-3 sp["Lambda"] = 4.0 sp["Nz"] = 4 sp["Tmin"] = 1e-4 sp["keep"] = 50 sp["keepenergy"] = 6.0 # Model Parameters mp = {} mp["U1"] = 0.5 mp["eps1"] = -0.24 sp["model_parameters"] = mp # Low-level NRG Parameters np = {} np["bins"] = 50 S.set_nrg_params(**np) # # Initialize hybridization function S.Delta_w['imp'] << 0.1 * SemiCircular(1.0) # Solve the impurity model S.solve(**sp) if mpi.is_master_node(): # Store the Result fnout = "MPI_SIAM_QS_np%i.out.h5" % mpi.size with HDFArchive(fnout, 'w') as arch: arch["A_w"] = S.A_w arch["G_w"] = S.G_w arch["F_w"] = S.F_w arch["Sigma_w"] = S.Sigma_w # Compare against reference result fnref = "MPI_SIAM_QS.ref.h5" # the same for all cases! h5diff(fnout, fnref)
class test_SIAM(unittest.TestCase): # Construct Parameters cp = {} cp["model"] = "SIAM" cp["symtype"] = "QSZ" cp["mesh_max"] = 1.0 cp["mesh_min"] = 1e-3 cp["mesh_ratio"] = 1.1 # Set up the Solver S = Solver(**cp) # Solve Parameters sp = {} sp["T"] = 1e-3 sp["Lambda"] = 4.0 sp["Nz"] = 2 sp["Tmin"] = 1e-4 sp["keep"] = 50 sp["keepenergy"] = 6.0 # Model Parameters mp = {} mp["U1"] = 0.5 mp["eps1"] = -0.24 mp["B1"] = -0.1 sp["model_parameters"] = mp # Low-level NRG Parameters np = {} np["bins"] = 50 S.set_nrg_params(**np) # # Initialize hybridization function S.Delta_w['up'] << 0.05 * SemiCircular(1.0) S.Delta_w['dn'] << 0.1 * SemiCircular(1.0) # Solve the impurity model S.solve(**sp) # # Store the Result with HDFArchive("2_SIAM_QSZ.out.h5", 'w') as arch: arch["A_w"] = S.A_w arch["G_w"] = S.G_w arch["F_w"] = S.F_w arch["Sigma_w"] = S.Sigma_w # Compare against reference result h5diff("2_SIAM_QSZ.out.h5", "2_SIAM_QSZ.ref.h5")
class test_SIAM(unittest.TestCase): # Construct Parameters cp = {} cp["model"] = "SIAM" cp["symtype"] = "QS" cp["mesh_max"] = 1.0 cp["mesh_min"] = 1e-3 cp["mesh_ratio"] = 1.1 # Set up the Solver S = Solver(**cp) # Solve Parameters sp = {} sp["T"] = 1e-3 sp["Lambda"] = 4.0 sp["Nz"] = 2 sp["Tmin"] = 1e-4 sp["keep"] = 50 sp["keepenergy"] = 6.0 # Model Parameters mp = {} mp["U1"] = 0.5 mp["eps1"] = -0.24 sp["model_parameters"] = mp # Low-level NRG Parameters np = {} np["bins"] = 50 S.set_nrg_params(**np) # # Initialize hybridization function S.Delta_w['imp'] << 0.1 * SemiCircular(1.0) # Solve the impurity model S.solve(**sp) if mpi.is_master_node(): HDFArchive('Solver.h5', 'w')['S'] = S # Rerun the Solver and Compare S_old = HDFArchive('Solver.h5', 'r')['S'] S_old.solve(**S_old.last_solve_params) assert_block_gfs_are_close(S_old.G_w, S.G_w, 1e-12)
class test_SIAM(unittest.TestCase): # Construct Parameters cp = {} cp["model"] = "SIAM" cp["symtype"] = "QS" cp["mesh_max"] = 1.0 cp["mesh_min"] = 1e-3 cp["mesh_ratio"] = 1.1 # Set up the Solver S = Solver(**cp) # Solve Parameters sp = {} sp["T"] = 1e-3 sp["Lambda"] = 4.0 sp["Nz"] = 2 sp["Tmin"] = 1e-4 sp["keep"] = 50 sp["keepenergy"] = 6.0 # Model Parameters mp = {} mp["U1"] = 0.5 mp["eps1"] = -0.24 sp["model_parameters"] = mp # Low-level NRG Parameters np = {} np["bins"] = 50 S.set_nrg_params(**np) # # Initialize hybridization function S.Delta_w['imp'] << 0.1 * SemiCircular(1.0) # Solve the impurity model S.solve(**sp) G_w_1 = S.G_w.copy() S.solve(**sp) G_w_2 = S.G_w.copy() assert_block_gfs_are_close(G_w_1, G_w_2, 1e-12)
from nrgljubljana_interface import Solver, Flat from h5 import * # Parameters D, V, U = 1.0, 0.25, 1.0 e_f = -U / 2.0 # particle-hole symmetric case T = 1e-5 # Set up the Solver S = Solver(model="SIAM", symtype="QS", mesh_max=2.0, mesh_min=1e-5, mesh_ratio=1.01) # Solve Parameters sp = { "T": T, "Lambda": 2.0, "Nz": 4, "Tmin": 1e-6, "keep": 2000, "keepenergy": 10.0, "bandrescale": 1.0 } # Model Parameters mp = {"U1": U, "eps1": e_f} sp["model_parameters"] = mp # Initialize hybridization function
from pytriqs.archive import * import math # Parameters D = 1.0 # bandwidth U = 1.0 Gamma = 0.1 e_f = -0.4 U12 = 1.0 J12 = 0.0 T = 1e-3 # Set up the Solver S = Solver(model="2orb-UJ", symtype="QS", mesh_max=2.00676, mesh_min=1e-5, mesh_ratio=1.01) # Solve Parameters sp = { "T": T, "Lambda": 4.0, "Nz": 4, "Tmin": 1e-5, "keep": 4000, "keepenergy": 10.0, "bandrescale": 1.0 } # Model Parameters
from nrgljubljana_interface import Solver, Flat from pytriqs.archive import * # Parameters D, V, U = 1.0, 0.2, 0 # Zero e-e interaction e_f = -U / 2.0 # particle-hole symmetric case T = 1e-5 omega = 0.2 # phonon frequency g1 = 0.2 # electron-phonon coupling strength n1 = 1 # n1 in g1*(a+a^dag)*(n-n1) # Set up the Solver S = Solver(model="Holstein/Nph=10", symtype="QS", mesh_max=2.0, mesh_min=1e-5, mesh_ratio=1.01) # Solve Parameters sp = { "T": T, "Lambda": 2.0, "Nz": 4, "Tmin": 1e-6, "keep": 1000, "keepenergy": 10.0, "bandrescale": 1.0 } # Model Parameters mp = {"U1": U, "eps1": e_f, "omega": omega, "g1": g1, "n1": n1}
class DMFT_solver(object): itern = 0 # iteration counter okn = 0 # number of iterations in which convergent criteria were satisfied solution_filename = "solution.h5" # converged solution (all convergence criteria satisfied) checkpoint_filename = "checkpoint.h5" # current solution (for checkpoint/restart functionality) stats_filename = "stats.dat" # some statistics about the DMFT iteration stop_flag_file = "STOP" # the calculation can be aborted by creating this file converged_flag_file = "CONVERGED" # the script touches this file upon reaching convergence # Provides the initial Sigma and mu def initial_Sigma_mu(self): if os.path.isfile(self.checkpoint_filename ): # continue DMFT iteration after interruption return restart_calculation(self.checkpoint_filename) elif os.path.isfile( self.solution_filename ): # continue DMFT iteration after failed convergence return restart_calculation(self.solution_filename) else: # start from scratch return self.new_calculation() def __init__(self, param, dmft_param): self.param = param self.dmft_param = dmft_param self.occupancy_goal = param["occupancy"] self.T = param["T"] self.observables = [] # List of expectation values to compute self.cp = {} # Dictinary with creator parameters self.sp = { "T": self.T, "model_parameters": {} } # Dictionary with solver parameters self.mp = {} # Dictionary with model parameters self.nrgp = { } # Dictionary with low-level NRG Parameters (optional tweaks) # Open file with basic information (convergence criteria, occupancy, expectation values of operators) for monitoring the iteration process if mpi.is_master_node(): self.stats_file = open(self.stats_filename, "w", buffering=1) # line buffered # Initialize a function ht0 for calculating the Hilbert transform of the DOS if (param["dos"] == "Bethe"): self.ht1 = lambda z: 2 * ( z - 1j * np.sign(z.imag) * np.sqrt(1 - z**2) ) # Analytical expression for Hilbert transform of Bethe lattice DOS self.ht0 = lambda z: self.ht1(z / param["Bethe_unit"]) else: table = np.loadtxt(param["dos"]) self.dosA = Gf(mesh=MeshReFreqPts(table[:, 0]), target_shape=[]) for i, w in enumerate(self.dosA.mesh): self.dosA[w] = np.array([[table[i, 1]]]) self.ht0 = lambda z: hilbert_transform_refreq(self.dosA, z) # Performs the Hilbert transform with argument z, ensuring that the imaginary part is positive (to produce retarded GFs) def ht(self, z, EPS=1e-20): return self.ht0(z.real + 1j * (z.imag if z.imag > 0.0 else EPS) ) # Fix problems due to causality violation # Model specific changes to the impurity model parameters need to be defined in derived classes def set_mu(self, mu): self.mu = mu # This may be called after all constructors (base and all derived classes) have done their job def setup_impurity_solver(self, mesh_param={}, sp={}, nrgp={}, verbose_nrg=False): self.cp.update( mesh_param ) # dictionary cp is set up by model-specific constructor of the derived class, but we need to add mesh_parameters self.S = Solver(**self.cp) self.S.set_verbosity(verbose_nrg) self.sp.update(sp) # add remaining solve parameters self.nrgp.update(nrgp) # optionally add low-level NRG parameters self.S.set_nrg_params(**self.nrgp) # Now we initialize the physical quantities Sigma, mu = self.initial_Sigma_mu( ) # Get initial self-energy and chemical potential Gloc, Delta = self_consistency( Sigma, mu, self.ht ) # Initialize local GF Gloc and hybridization function Delta Gloc, Delta, mu = self.adjust_mu( Delta, Sigma, mu ) # Adjust mu toward satisfying occupancy goal (and return updated Glocal and Delta as well) self.Gloc = Gloc.copy() # Initial approximation for local lattice GF self.Delta = Delta.copy( ) # Initial approximation for hybridization function self.set_mu(mu) # Sets mu and updates the impurity level # Iteratively adjust mu, taking into account the self-consistency equation. # Returns an improved estimate for the hybridisation function. def adjust_mu(self, Delta_in, Sigma, old_mu): Delta = Delta_in.copy() mu = old_mu max_mu_adjust = self.dmft_param.get( "max_mu_adjust", 10 ) # number of cycles for adjusting the value of the chemical potential for _ in range(max_mu_adjust): mu = update_mu(Delta, Sigma, self.occupancy_goal, self.T, mu) Gloc, Delta = self_consistency(Sigma, mu, self.ht) new_mu = mu max_mu_diff = self.dmft_param.get( "max_mu_diff", None) # maximum change in mu that is allowed if max_mu_diff is not None: new_mu = clamp(old_mu - max_mu_diff, new_mu, old_mu + max_mu_diff) mu_alpha = self.dmft_param.get( "mu_alpha", 1.0) # mixing parameter for the chemical potential adjustment new_mu = mu_alpha * new_mu + (1.0 - mu_alpha) * old_mu if verbose(): print("Adjusted mu from %.10f to %.10f" % (old_mu, new_mu)) return Gloc, Delta, new_mu def set_model_parameters(self, mp): for k, v in mp.items(): if v is None: del mp[k] # Remove undefined parameters from the dictionary self.sp["model_parameters"].update(mp) # Update, not an assignment! # Store the complete set of results def store_result(self, fn): with HDFArchive(fn, 'w') as arch: arch["S"] = self.S arch["Gloc"] = self.Gloc arch["Delta"] = self.Delta arch["mu"] = self.mu arch["Gself"] = self.Gself arch["occupancy"] = self.occupancy # Perform a DMFT step. Input is the hybridization function for solving the effective impurity model, # output is a new hybridization function resulting from the application of the DMFT self-consistency equation. def dmft_step(self, Delta_in): self.itern += 1 if verbose(): print("\nIteration %i min_iter=%i max_iter=%i" % (self.itern, self.dmft_param["min_iter"], self.dmft_param["max_iter"])) Delta_in_fixed = fix_hyb_function(Delta_in, self.dmft_param["Delta_min"]) self.S.Delta_w << Delta_in_fixed t0 = timeit.default_timer() self.S.solve(**self.sp) # Solve the impurity model t1 = timeit.default_timer() dt = int(t1 - t0) solver_time = '{:02}:{:02}:{:02}'.format(dt // 3600, dt % 3600 // 60, dt % 60) # hh:mm:ss format self.Gself = calc_G( Delta_in_fixed, self.S.Sigma_w, self.mu) # impurity GF ("self-energy-trick" improved) Gloc_prev = self.Gloc.copy( ) # store copy of previous Gloc for checking convergence self.Gloc, self.Delta = self_consistency( self.S.Sigma_w, self.mu, self.ht) # apply the DMFT self-consistency equation self.occupancy = calc_occupancy( self.Delta, self.S.Sigma_w, self.mu, self.T) # occupancy calculated from the local lattice GF diff_loc_imp = gf_diff( self.Gself, self.Gloc) # difference between impurity and local lattice GF diff_prev = gf_diff( self.Gloc, Gloc_prev ) # difference between two consecutively computed local latice GFs diff_occupancy = abs( self.occupancy - self.occupancy_goal ) # this difference is used as the measure of deviation stats = OrderedDict([("itern", self.itern), ("time", solver_time), ("mu", self.mu), ("diff_loc_imp", diff_loc_imp), ("diff_prev", diff_prev), ("diff_occupancy", diff_occupancy), ("occupancy", self.occupancy)]) for i in self.observables: stats[i] = self.S.expv[i] header_string = fmt_str_header( len(stats)).format(*[i for i in stats.keys()]) stats_string = fmt_str(len(stats)).format(*[i for i in stats.values()]) if mpi.is_master_node(): if self.itern == 1: print(header_string, file=self.stats_file) print(stats_string, file=self.stats_file) if verbose(): print(header_string) print(stats_string) if self.dmft_param["store_steps"] and mpi.is_master_node(): os.mkdir(str(self.itern)) # one subdirectory per iteration save_BlockGf(str(self.itern) + "/Delta", Delta_in_fixed) save_BlockGf(str(self.itern) + "/Sigma", self.S.Sigma_w) # self-energy save_BlockGf(str(self.itern) + "/G", self.Gloc) # local lattice Green's function save_BlockA(str(self.itern) + "/A", self.Gloc) # spectral function of local lattice GF self.store_result(str(self.itern) + "/" + self.solution_filename) if mpi.is_master_node(): self.store_result(self.checkpoint_filename ) # for checkpoint/restart functionality # Check for convergence if (diff_loc_imp < self.dmft_param["eps_loc_imp"] and diff_prev < self.dmft_param["eps_prev"] and diff_occupancy < self.dmft_param["eps_occupancy"]): self.okn += 1 else: self.okn = 0 # The only way to exit the DMFT loop is by generating exceptions. if (self.okn >= self.dmft_param.get("conv_iter", 1) and self.itern >= self.dmft_param["min_iter"]): if mpi.is_master_node(): self.store_result(self.solution_filename ) # full converged results as an HDF5 file try: os.remove(self.checkpoint_filename ) # checkpoint file is no longer needed except OSError: pass if self.converged_flag_file: touch(self.converged_flag_file ) # signal convergence through a file raise Converged("%s\n%s" % (header_string, stats_string)) if (self.itern == self.dmft_param["max_iter"]): raise FailedToConverge("%s\n%s" % (header_string, stats_string)) if self.stop_flag_file and os.path.isfile(self.stop_flag_file): raise ForcedStop() if self.dmft_param["occup_method"] == "adjust": self.Gloc, self.Delta, new_mu = self.adjust_mu( self.Delta, self.S.Sigma_w, self.mu) # here we update mu to get closer to target occupancy self.set_mu(new_mu) return self.Delta # DMFT driver routine with linear mixing def solve_with_linear_mixing(self, alpha, mixer_param): Delta_in = self.Delta.copy() while True: Delta_out = self.dmft_step(Delta_in) newDelta = alpha * Delta_out + (1 - alpha) * Delta_in Delta_in << newDelta # DMFT driver routine with Broyden mixing: the goal is to find a root of the function F(Delta)=dmft_step(Delta)-Delta. def solve_with_broyden_mixing(self, alpha, broyden_param): if self.dmft_param["occup_method"] == "broyden": def F(Delta_in, mu): if verbose(): print("(Broyden) Setting mu to %.18f" % mu) self.set_mu(mu) Delta_out = self.dmft_step(Delta_in) occup_factor = self.dmft_param.get("occup_factor", 1.0) return Delta_out - Delta_in, occup_factor * ( self.occupancy_goal - self.occupancy) npF = lambda x: wrap(*F(*unwrap(x, self.S, scalar=True)) ) # unpack the tuples xin = wrap(self.Delta, self.mu) else: def F(Delta): return self.dmft_step(Delta) - Delta npF = lambda x: bgf_to_nparray(F(nparray_to_bgf(x, self.S))) xin = bgf_to_nparray(self.Delta) bp = { "reduction_method": "svd", "max_rank": 20, "f_tol": 1e-99 } # Loop forever (small f_tol!) bp.update(broyden_param) optimize.broyden1(npF, xin, alpha=alpha, verbose=verbose(), **bp) raise Converged("broyden1() converged") def solve(self): alpha = self.dmft_param["alpha"] if (self.dmft_param["mixing_method"] == "linear"): self.solve_with_linear_mixing(alpha) if (self.dmft_param["mixing_method"] == "broyden"): self.solve_with_broyden_mixing( alpha, self.dmft_param.get("broyden_param", {}))
# Additional quantities of interest observables = ["n_d", "n_d^2"] if B is not None: observables.extend(["SZd"]) if "Holstein" in model: observables.extend(["nph", "displ", "displ^2"]) # Run-time global variables itern = 0 # iteration counter verbose = verbose and mpi.is_master_node() # output is only produced by the master node store_steps = store_steps and mpi.is_master_node() # output is only produced by the master node symtype = ("QS" if B is None else "QSZ") # Set up the Solver S = Solver(model=model, symtype=symtype, mesh_max=mesh_max, mesh_min=mesh_min, mesh_ratio=mesh_ratio) S.set_verbosity(verbose_nrg) newG = lambda : S.G_w.copy() # Creates a BlockGf object of appropriate structure nr_blocks = lambda bgf : len([bl for bl in bgf.indices]) # Returns the number of blocks in a BlockGf object block_size = lambda bl : len(S.G_w[bl].indices[0]) # Matrix size of Green's functions in block 'bl' identity = lambda bl : np.identity(block_size(bl)) # Returns the identity matrix in block 'bl' # Solve Parameters sp = { "T": T, "Lambda": 2.0, "Nz": 4, "Tmin": 1e-5, "keep": 10000, "keepenergy": 10.0 } # Model Parameters mp = { "U1": U, "B1": B, "omega": omega, "g1": g1, "n1": n1 } for k,v in mp.items(): if v is None: del mp[k] # Remove undefined parameters from the dictionary sp["model_parameters"] = mp