def matsubara_frequencies(n_points, beta) -> xr.DataArray: iws = xr.DataArray(gt.matsubara_frequencies(n_points, beta=beta), dims=[Dim.iws], coords=[n_points]) iws.name = 'iω_n' iws.attrs['description'] = 'fermionic Matsubara frequencies' iws.attrs['temperature'] = 1/beta return iws
def curr_acorr_n1_iv(prm: Hubbard_Parameters, self_iw, occ, N_iv: int): """Calculate bosonic Matsubara current-current correlation function. Parameters ---------- prm : Hubbard_Parameters The model. self_iw : (N_sp, N_l=1, N_iw) complex np.ndarray The local self-energy, for non-negative Matsubaras `iws.imag > 0`. occ : (N_sp, N_l=1) float np.ndarray Occupation number, needed to perform Matsubara sum corrections. N_iv : int Number of bosonic Matsubaras to calculate. Must be non-negative, only non-negative Matsubaras will be calculated. Returns ------- curr_acorr_n1_iv : (N_sp, 1, N_iv) complex np.ndarray The current-current correlation function. """ assert prm.N_l == 1 assert occ.shape == self_iw.shape[: -1], "Shape mismatch of occupation and self-energy." N_iw = self_iw.shape[-1] # we need sum over positive and negative Matsubaras self_iw = np.concatenate((self_iw[..., ::-1].conj(), self_iw), axis=-1) iws = gt.matsubara_frequencies(range(-N_iw, N_iw), beta=prm.beta) assert model.rev_dict_hilbert_transfrom[prm.hilbert_transform] == 'bethe', \ "Only Bethe lattice derivative implemented." gf_iw = prm.gf_dmft_s(iws, self_iw) xi_iw = (iws + prm.onsite_energy() - self_iw) # we assume as symmetric density of states, thus that ϵ^{(0)} = 0 xi0 = prm.onsite_energy(hartree=occ[::-1]) xi0_iw_inv = 1. / (iws + xi0) gf_d1_iw = gt.bethe_gf_d1_omega(xi_iw, half_bandwidth=prm.D) def p_iv(n_b): # there is no `-0` so we have to treat that separately lmsk = Ellipsis, slice(None, -n_b if n_b != 0 else None) rmsk = Ellipsis, slice(n_b, None) d_xi_iw = xi_iw[lmsk] - xi_iw[rmsk] small_limit = abs(d_xi_iw) < 1e-8 # only very few elements should be `small_limit` so we don't care about overhead res = gf_iw[lmsk] - gf_iw[rmsk] res[~small_limit] /= d_xi_iw[~small_limit] # symmetric, just in case res[small_limit] = .5 * (gf_d1_iw[lmsk][small_limit] + gf_d1_iw[rmsk][small_limit]) # Matsubara sum corrections delta_sum = prm.T * np.sum(res + xi0_iw_inv[lmsk] * xi0_iw_inv[rmsk], axis=-1) if n_b == 0: # only relevant near half filling to have corrections return delta_sum - gt.fermi_fct_d1(xi0, beta=prm.beta) return delta_sum # for N_l == 1 there are only corrections to iν_0 return np.moveaxis(np.array([p_iv(n_b) for n_b in range(N_iv)]), 0, -1)
def test_2x2_matrix(): """Compare with analytic inversion of (2, 2) matrix. Done for the 1D chain of sites. """ prm = model.Hubbard_Parameters(2, lattice='chain') def gf_2x2(omega, t_mat, onsite_energys): assert np.all(np.diag(t_mat) == 0.), \ "No diagonal elements for t_mat allowed" assert t_mat.shape == (2, 2) assert onsite_energys.shape == (2, ) diag = omega + onsite_energys norm = 1. / (np.prod(diag) - t_mat[0, 1] * t_mat[1, 0]) gf = np.zeros_like(t_mat, dtype=np.complex) gf[0, 0] = diag[1] gf[1, 1] = diag[0] gf[0, 1] = -t_mat[0, 1] gf[1, 0] = -t_mat[1, 0] return norm * gf prm.T = 0.0137 prm.t_mat = np.zeros((2, 2)) prm.t_mat[0, 1] = prm.t_mat[1, 0] = 1.3 prm.mu = np.array([0, 1.73]) prm.h = np.array([0, -0.3]) prm.D = None prm.assert_valid() omegas = gt.matsubara_frequencies(np.arange(100), prm.beta) gf_prm = prm.gf0(omegas, diagonal=False) e_onsite = prm.onsite_energy() gf_2x2_up = np.array([ gf_2x2(iw, prm.t_mat, e_onsite.sel({ Dim.sp: 'up' }).values) for iw in omegas ]) gf_2x2_up = gf_2x2_up.transpose(1, 2, 0) # adjuste axis order (2, 2, omegas) assert np.allclose(gf_2x2_up, gf_prm.sel({Dim.sp: 'up'})) gf_2x2_dn = np.array([ gf_2x2(iw, prm.t_mat, e_onsite.sel({ Dim.sp: 'dn' }).values) for iw in omegas ]) gf_2x2_dn = gf_2x2_dn.transpose(1, 2, 0) # adjuste axis order (2, 2, omegas) assert np.allclose(gf_2x2_dn, gf_prm.sel({Dim.sp: 'dn'}))
def fit_iw_tail(gf_iw, beta, order) -> FourierFct: """Fit the tail of `gf_iw` with function behaving as (iw)^{-`order`}. Parameters ---------- gf_iw : (..., N_iw) complex np.ndarray The function at **fermionic** Matsubara frequencies. beta : float The inverse temperature `beta` = 1/T. order : int Leading order of the high-frequency behavior of the tail. Returns ------- fit_iw_tail.iw : (..., N_iw) complex np.ndarray The tail fit for the same frequencies as `gf_iw`. fit_iw_tail.tau : (..., 2*N_iw + 1) float np.ndarray The Fourier transform of the tail for τ ∈ [0, β]. Raises ------ RuntimeError If the Fourier transform of the tail contains `np.nan`. This should be fixed and never occur. It indicates a overflow in `get_gf_from_moments()`. """ N_iw = gf_iw.shape[-1] odd = order % 2 # CC = 2.*order # shift to make the function small for low frequencies CC = 0. iws = gt.matsubara_frequencies(np.arange(N_iw), beta=beta) tau = np.linspace(0, beta, num=2*N_iw + 1, endpoint=True) def to_float(number): # scipy only handles np.float64 return (number.imag if odd else number.real).astype(np.float64) norm_tail_iw = .5 * ((iws + CC)**-order + (iws - CC)**-order) # tail with amplitude 1 sigma = to_float(iws**(-order-2)) # next order correction that is odd/even moment, err = _fitting(iws.imag, fit_iw=to_float(norm_tail_iw), gf_iw=to_float(gf_iw), sigma=sigma) LOGGER.info('Amplitude of fit (order %s): %s ± %s', order, moment, err) moment = moment[..., np.newaxis] gf_tau_fct = get_order_n_pole(order) tail_tau = moment*.5*(gf_tau_fct(tau, CC, beta) + gf_tau_fct(tau, -CC, beta)) if _has_nan(tail_tau): raise RuntimeError("Calculation of tail-fit failed. Most likely a overflow occurred.") return FourierFct(iw=moment*norm_tail_iw, tau=tail_tau)
def get_gf_from_moments(moments, beta, N_iw) -> FourierFct: """Green's function from `moments` on Matsubara axis and imaginary time. Parameters ---------- moment : (n, ...) float array_like or float High frequency moment of `gf_iw`. beta : float The inverse temperature. N_iw : int Number of Matsubara frequencies Returns ------- moments.iw : (..., N_iw) complex np.ndarray Matsubara Green's function calculated from the moments for positive frequencies. moments.tau : (..., 2*N_iw + 1) float np.ndarray Imaginary time Green's function calculated from the moments for τ in [0, β]. """ moments = np.asarray(moments) iws = gt.matsubara_frequencies(np.arange(N_iw), beta=beta) if moments.shape[-1] == 1: # last dimension can be used for iws/tau return FourierFct(iw=moments/iws, tau=-.5*moments) if moments.shape[-1] == 2: m1, m2 = moments[..., 0], moments[..., 1] if np.any(m1 == 0.): if np.all(m2 == 0.) and np.all(m1 == 0.): return FourierFct(iw=0, tau=0) # TODO: TEST THIS! tau = np.linspace(0, beta, num=2*N_iw + 1, endpoint=True) mom_iw = np.zeros((*m1.shape, N_iw), dtype=iws.dtype) mom_tau = np.zeros((*m1.shape, tau.size), dtype=tau.dtype) # cases where mom[0] == 0: mom_is0 = m1 == 0 mom_iw[mom_is0] = m2[mom_is0, np.newaxis]/iws**2 mom_tau[mom_is0] = m2[mom_is0, np.newaxis]*(.5*tau + .25*beta) # cases where mom[0] != 0: pole = (m2[~mom_is0]/m1[~mom_is0])[..., np.newaxis] mom_iw[~mom_is0] = m1[~mom_is0, np.newaxis]/(iws - pole) mom_tau[~mom_is0] = m1[~mom_is0, np.newaxis] * ft_pole2tau(tau, pole=pole, beta=beta) return FourierFct(iw=mom_iw, tau=mom_tau) tau = np.linspace(0, beta, num=2*N_iw + 1, endpoint=True) pole = (m2/m1)[..., np.newaxis] mom_iw = m1[..., np.newaxis]/(iws - pole) mom_tau = m1[..., np.newaxis] * ft_pole2tau(tau, pole=pole, beta=beta) return FourierFct(iw=mom_iw, tau=mom_tau) raise NotImplementedError()
def test_compare_greensfunction(): """Non-interacting and DMFT should give the same for self-energy=0.""" N = 13 prm = model.Hubbard_Parameters(N, lattice='bethe') prm.T = 0.137 prm.D = 1. # half-bandwidth prm.mu[N // 2] = 0.45 prm.h[N // 2] = 0.9 t = 0.2 prm.t_mat = model.hopping_matrix(N, nearest_neighbor=t) iw = gt.matsubara_frequencies(np.arange(100), beta=prm.beta) gf0 = prm.gf0(iw) gf_dmft = prm.gf_dmft_s(iw, np.zeros_like(gf0)) assert np.allclose(gf0, gf_dmft)
def curr_acorr0_n1_iv0(prm: Hubbard_Parameters, *, occ=None, n_iw=2**16): """Calculate non-interacting bosonic Matsubara current-current correlation function. For the non-interacting case, only the zeroth Matsubara frequency :math:`iν_0 = 0` is finite, all other frequencies vanish. Thus only the zeroth frequency is calculated. Parameters ---------- prm : Hubbard_Parameters The model. occ : (N_sp, N_l=1) float np.ndarray Used to include Hartree contribution if `prm.U ≠ 0` n_iw : int How many fermionic Matsubaras will be used for the summation. Returns ------- curr_acorr_n1_iv : (N_sp, 1) complex np.ndarray The current-current correlation function. Notes ----- For the non-interacting case it is in fact not a good idea, to use Matsubara summation. It would probably be more efficient to perform the Matsubara sum analytic and numerically integrate over the DOS. Furthermore, using Pade instead of Matsubara frequencies would be way more efficient. """ # TODO: dynamically calculate the number of Matsubaras to use from temperature iws = gt.matsubara_frequencies(range(n_iw), beta=prm.beta) if occ is not None: occ = occ[::-1] xi = prm.onsite_energy(iws, hartree=occ) assert model.rev_dict_hilbert_transfrom[prm.hilbert_transform] == 'bethe' gf_d1 = gt.bethe_gf_d1_omega(np.add.outer(xi, iws), half_bandwidth=prm.D) delta_sum = 2 * prm.T * np.sum(gf_d1 + 1. / (iws + xi)**2, axis=-1).real return delta_sum - gt.fermi_fct_d1(xi, beta=prm.beta)
def test_Hartree_curr_acorr_n1(): """Compare the results of `curr_acorr_n1_iv` with the Hartree case.""" prm = model.Hubbard_Parameters(N_l=1, lattice='bethe') prm.D = 1.37 prm.mu[:] = .136 prm.U[:] = 2.37 prm.T = 0.01 prm.assert_valid() iws = gt.matsubara_frequencies(range(1024), beta=prm.beta) hartree = layer_dmft.hartree_solution(prm, iws) self_hartree = hartree.self_iw occ = hartree.occ K_iv = conduct.curr_acorr_n1_iv(prm, self_iw=self_hartree, occ=occ, N_iv=10) assert np.allclose(K_iv.imag, 0), "Correlation is real quantity." # accuracy depends on temperature! # Finite values are due to truncation of Matsubara sums assert np.allclose( K_iv[..., 1:], 0, atol=1e-4), "For U=0, only zeroth frequency contributes." assert np.allclose(K_iv[..., 0], conduct.curr_acorr0_n1_iv0(prm, occ=occ))
def plot_spectral(it: Union[int, str] = -1): # # load data # lay_obj = dataio.LayerData() lay_data, iteration = lay_obj.iter(it, return_iternum=True) logging.debug("Load iteration %s of layer data", iteration) # FIXME: imp_data gets collected upon calling `iter` and discarding the object! imp_obj = dataio.ImpurityData() imp_data = imp_obj.iter(iteration) assert lay_data["temperature"] == prm.T layers = list(imp_data.keys()) gf_lay_iw: np.ndarray = lay_data['gf_iw'] gf_lay_iw = gf_lay_iw[:, layers] gf_imp_iw = np.array([imp_lay['gf_iw'] for imp_lay in imp_data.values() ]).transpose(1, 0, 2) # gf_imp_iw = np.array([imp_data[lay]['gf_iw'] for lay in sorted(imp_data.keys())]).transpose(1, 0, 2) # print(*sorted(imp_data.keys())) N_iw = gf_lay_iw.shape[-1] iws = gt.matsubara_frequencies(np.arange(N_iw), beta=prm.beta) # # options # SPLIT_PLOTS = False PARAMAGNETIC = False if np.count_nonzero( prm.h) or not layer_dmft.FORCE_PARAMAGNET else True SHIFT: complex = 0.2 * iws[0] # SHIFT *= 4 # FIXME THRESHOLD: float = SHIFT.imag / 2 THRESHOLD = np.infty # FIXME if PARAMAGNETIC: # average over spins gf_lay_iw = gf_lay_iw.mean(axis=0, keepdims=True) # should already be averaged gf_imp_iw = gf_imp_iw.mean(axis=0, keepdims=True) omega = np.linspace(-4 * prm.D, 4 * prm.D, num=1000, dtype=complex) omega += SHIFT # shift into imaginary plane N_w = omega.size # prepare Pade kind_gf = pade.KindGf(N_iw // 10, 8 * N_iw // 10) no_neg_imag = pade.FilterNegImag(THRESHOLD) pade_gf = partial(pade.avg_no_neg_imag, z_in=iws, z_out=omega, kind=kind_gf, threshold=THRESHOLD) # perform Pade gf_lay_w = pade_gf(fct_z=gf_lay_iw) gf_imp_w = pade_gf(fct_z=gf_imp_iw) # # IMPURITY GREEN'S FUNCTION FROM SELF-ENERGY # # FIXME: save hybridization function with data lay_data_prev = lay_obj.iter(iteration - 1) siams = prm.get_impurity_models(z=iws, self_z=lay_data_prev['self_iw'], gf_z=lay_data_prev['gf_iw'], occ=lay_data_prev['occ']) # # create figure # rows, cols = gf_lay_iw.shape[:-1] if SPLIT_PLOTS: axes = np.array([ plt.subplots(nrows=rows, sharex=True, squeeze=False)[1][:, 0] for __ in range(cols) ]).T print(axes.shape) else: __, axes = plt.subplots(nrows=rows, ncols=cols, sharex=True, sharey="row", squeeze=False) for axis in axes.flatten(): axis.grid(True) axis.axhline(0, color="black", linewidth=1) axis.axvline(0, color="black", linewidth=1) data_max = max(abs(dat.x.imag).max() for dat in (gf_lay_w, gf_imp_w)) ax: plt.Axes = axes[-1, 0] ax.set_ylim(bottom=-1.05 * data_max, top=0.05 * data_max) ax = axes[0, 0] ax.set_ylim(bottom=0.05 * data_max, top=-1.05 * data_max) # gf_lay_w.x[0] *= -1 # check ifspins are correct # # plot data # for ax, gf_lay_ll, gf_lay_ll_err in zip(axes.reshape((-1)), gf_lay_w.x.reshape((-1, N_w)), gf_lay_w.err.reshape((-1, N_w))): plot.err_plot(x=omega.real, y=gf_lay_ll.imag, yerr=gf_lay_ll_err.imag, axis=ax, fmt='--', label='Gf_lay') for ax, gf_imp_ll, gf_imp_ll_err in zip(axes.reshape((-1)), gf_imp_w.x.reshape((-1, N_w)), gf_imp_w.err.reshape((-1, N_w))): plot.err_plot(x=omega.real, y=gf_imp_ll.imag, yerr=gf_imp_ll_err.imag, axis=ax, fmt='--', label='Gf_imp') for lay, siam_ll in enumerate(siams): siam_ll: model.SIAM if lay not in layers: continue self_iw = lay_data['self_iw'][:, lay] coeff = pade.coefficients(iws, fct_z=siam_ll.hybrid_fct + self_iw) kind_self = pade.KindSelf(N_iw // 10, 8 * N_iw // 10) valid = no_neg_imag( omega, kind_self.islice(pade.calc_iterator(omega, z_in=iws, coeff=coeff))) def _mod(z, pade_z): return 1 / (z - pade_z + siam_ll.e_onsite[..., np.newaxis]) gf_hyb = pade.Mod_Averager(z_in=iws, coeff=coeff, mod_fct=_mod, valid_pades=valid, kind=kind_self)(omega) for ax, sp in zip(axes[:, layers.index(lay)], model.Spins): plot.err_plot(x=omega.real, y=gf_hyb.x[sp].imag, yerr=gf_hyb.err[sp].imag, axis=ax, fmt='--', label=r'Gf_hyb[$\Sigma$]') spin_strs = ('↑', '↓') for sp, ax in zip(spin_strs, axes[:, 0]): ax.set_ylabel(rf"$\Im G_{{{sp}l}}(i\omega_n)$") for lay, ax in zip(imp_data.keys(), axes[0]): ax.set_title(f"l = {lay}") for ax in axes[-1]: ax.set_xlabel("ω") axes[-1, -1].legend() plt.tight_layout() plt.show()
def charge_self_consistency_int(prm: Hubbard_Parameters, self_iw, occ0, tol): """Charge self-consistency using root-finding algorithm. Parameters ---------- parameters : model.Hubbard_Parameters `model.Hubbard_Parameters` object with the parameters set, determining Hamiltonian. tol : float Target tol of the self-consistency. Iteration stops if it is achieved. occ : ndarray, optional Starting value for the occupation. Returns ------- sol : OptimizedResult The solution of the optimization routine, see `optimize.root`. `x` contains the actual values, and `success` states if convergence was achieved. occ : SpinResolvedArray The final occupation of the converged result. V : ndarray The final potential of the converged result. """ # TODO: optimize n_points from error guess # TODO: check against given `n` if sum gives right result # TODO: give back self-energy? # optimization: allow mask assert self_iw.shape[-2] == occ0.shape[-1] == prm.N_l iws = gt.matsubara_frequencies(np.arange(self_iw.shape[-1]), beta=prm.beta) # subtract Hartree part self_iw_bare = self_iw - hfm.self_m0(prm.U, occ0[::-1])[..., np.newaxis] output = {} # TODO: check tol of density for target tol optimizer = partial(update_occupation, i_omega=iws, params=prm, out_dict=output, self_iw_bare=self_iw_bare) solve = partial(_root, fun=optimizer, x0=occ0, tol=tol) LOGGER.progress("Search self-consistent occupation number") try: sol = solve() except KeyboardInterrupt as key_err: print('Optimization canceled -- trying to continue') try: hartree_occ = output['occ'][::-1] except KeyError: print('Failed! No occupation so far.') raise key_err sol = optimize.OptimizeResult() sol.x = output['occ'] sol.success = False sol.message = 'Optimization interrupted by user, not terminated.' else: hartree_occ = output['occ'][::-1] # finalize gf_iw = prm.gf_dmft_s(iws, self_z=self_iw_bare + hfm.self_m0(prm.U, hartree_occ)[..., np.newaxis]) occ = prm.occ0(gf_iw, hartree=hartree_occ, return_err=True) LOGGER.info("Success of finding self-consistent occupation: %s", sol.success) LOGGER.info("%s", sol.message) return ChargeSelfconsistency(sol=sol, occ=occ, V=prm.V)
def charge_self_consistency(parameters, tol, V0=None, occ0=None, kind='auto', n_points=2**11): """Charge self-consistency using root-finding algorithm. Parameters ---------- parameters : model.Hubbard_Parameters `model.Hubbard_Parameters` object with the parameters set, determining Hamiltonian. tol : float Target tol of the self-consistency. Iteration stops if it is achieved. V0 : ndarray, optional Starting value for the electrical potential. occ : ndarray, optional Starting value for the occupation. kind : {'auto', 'occ', 'occ_lsq' 'V'}, optional Weather self-consistency is determined according to the charge 'occ' or the electrical potential 'V'. In the magnetic case 'V' seems to convergence better, there are half as many degrees of freedom. 'occ' on the other hand can readily incorporate the Hartree term of the self-energy. Per default ('auto') 'occ' is used in the interacting and 'V' in the non-interacting case. Additionally a constrained (:math:`0 ≤ n_{lσ} ≤ 1`) least-square algorithm can be used. If the root-finding algorithms do not converge, this `kind` might help. n_points : int, optional Number of Matsubara frequencies taken into account to calculate the charge. Returns ------- sol : OptimizedResult The solution of the optimization routine, see `optimize.root`. `x` contains the actual values, and `success` states if convergence was achieved. occ : SpinResolvedArray The final occupation of the converged result. V : ndarray The final potential of the converged result. """ # TODO: optimize n_points from error guess # TODO: check against given `n` if sum gives right result assert kind in set( ('auto', 'occ', 'occ_lsq', 'V')), f"Unknown kind: {kind}" assert V0 is None or occ0 is None params = parameters iw_array = xr.Variable(data=gt.matsubara_frequencies(np.arange(n_points), beta=params.beta), dims=Dim.iws) if kind == 'auto': if np.any(params.U != 0): # interacting case kind = 'occ' # use 'occ', it can incorporate Hartree term else: # non-interacting case kind = 'V' # use 'V', has half as many parameters to optimize if V0 is not None: params.V[:] = V0 output = {} # TODO: check tol of density for target tol if kind.startswith('occ'): if occ0 is None: gf_iw = params.gf0(iw_array) occ0 = params.occ0(gf_iw, return_err=False) optimizer = partial(update_occupation, i_omega=iw_array, params=params, out_dict=output) root_finder = _occ_least_square if kind == 'occ_lsq' else _root solve = partial(root_finder, fun=optimizer, x0=occ0, tol=tol) elif kind == 'V': optimizer = partial(update_potential, i_omega=iw_array, params=params, out_dict=output) solve = partial(_root, fun=optimizer, x0=params.V[:], tol=tol) LOGGER.progress("Search self-consistent occupation number") try: sol = solve() except KeyboardInterrupt as key_err: print('Optimization canceled -- trying to continue') try: hartree_occ = output['occ'][::-1] except KeyError: print('Failed! No occupation so far.') raise key_err sol = optimize.OptimizeResult() sol.x = output['occ' if kind in ('occ', 'occ_lsq') else 'V'] sol.success = False sol.message = 'Optimization interrupted by user, not terminated.' else: hartree_occ = output['occ'].roll({Dim.sp: 1}, roll_coords=False) # finalize gf_iw = params.gf0(iw_array, hartree=hartree_occ) occ = params.occ0(gf_iw, hartree=hartree_occ, return_err=True) LOGGER.info("Success of finding self-consistent occupation: %s", sol.success) LOGGER.info("%s", sol.message) return ChargeSelfconsistency(sol=sol, occ=occ, V=params.V)
def read_iw(dir_='.'): """Return the Matsubara frequencies.""" prm = load_param(dir_) out_dir = output_dir(dir_) iw_output = np.loadtxt(out_dir / GF_FILE, unpack=True) return gt.matsubara_frequencies(iw_output[0], beta=prm.beta)