Skip to content

Commit e018fca

Browse files
committed
Added support for La3Ni2O7 and optimized mixing
1 parent bcebc95 commit e018fca

10 files changed

Lines changed: 1128 additions & 147 deletions

moldga/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def __init__(self):
8888
self.previous_sc_path: str = "./"
8989
self.use_lambda_correction: bool = False
9090
self.restrict_chi_phys: bool = False
91+
self.anderson_prev_res: float | None = None
9192

9293

9394
class EliashbergConfig:
@@ -148,6 +149,8 @@ def __init__(self):
148149
self.mu: float = 0.0
149150
self.n: float = 0.0
150151
self.n_bands: int = 1
152+
self.nd_bands: int = 1
153+
self.np_bands: int = 0
151154
self.occ: np.ndarray = np.ndarray(0)
152155
self.occ_k: np.ndarray = np.ndarray(0)
153156
self.occ_dmft: np.ndarray = np.ndarray(0)

moldga/dga_io.py

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,13 @@ def uniquify_path(path: str = None):
3131
return path
3232

3333

34-
def load_from_w2dyn_file_and_update_config() -> tuple[GreensFunction, SelfEnergy, LocalFourPoint, LocalFourPoint]:
34+
def load_from_w2dyn_file_and_update_config(
35+
combine_two_atoms_to_one_obj: bool = False,
36+
) -> tuple[GreensFunction, SelfEnergy, LocalFourPoint, LocalFourPoint]:
3537
"""
3638
Loads data from the w2dyn file and updates the config file.
39+
If combine_atoms_to_one_obj is True, we are doubling the orbital space and putting the data from the
40+
inequivalent atoms into the diagonal blocks after we average over them.
3741
"""
3842
file = w2dyn_aux.W2dynFile(fname=str(os.path.join(config.dmft.input_path, config.dmft.fname_1p)))
3943

@@ -51,36 +55,106 @@ def load_from_w2dyn_file_and_update_config() -> tuple[GreensFunction, SelfEnergy
5155
config.lattice.interaction.vpp = file.get_vpp()
5256

5357
config.sys.mu = file.get_mu()
54-
config.sys.n_bands = file.get_nd() + file.get_np()
58+
config.sys.nd_bands = file.get_nd()
59+
config.sys.np_bands = file.get_np()
60+
config.sys.n_bands = config.sys.nd_bands + config.sys.np_bands
5561
config.sys.n = file.get_totdens()
5662
config.sys.occ_dmft = 2 * np.mean(file.get_rho1(), axis=(1, 3))
5763

64+
if combine_two_atoms_to_one_obj:
65+
config.sys.n_bands *= 2
66+
config.sys.nd_bands *= 2
67+
config.sys.np_bands *= 2
68+
config.sys.n *= 2
69+
70+
config.sys.occ_dmft = np.zeros((config.sys.n_bands, config.sys.n_bands))
71+
rho_1_mean = 0.5 * (np.mean(file.get_rho1(), axis=(1, 3)) + np.mean(file.get_rho1(ineq=2), axis=(1, 3)))
72+
config.sys.occ_dmft[0:2, 0:2] = 2 * rho_1_mean
73+
config.sys.occ_dmft[2:4, 2:4] = 2 * rho_1_mean
74+
5875
if config.sys.n == 0:
5976
config.sys.n = 2 * np.sum(config.sys.occ_dmft)
6077

6178
file2 = w2dyn_aux.W2dynG4iwFile(fname=str(os.path.join(config.dmft.input_path, config.dmft.fname_2p)))
62-
g2_dens = LocalFourPoint(file2.read_g2_full_multiband(config.sys.n_bands, name="dens"), channel=SpinChannel.DENS)
63-
g2_magn = LocalFourPoint(file2.read_g2_full_multiband(config.sys.n_bands, name="magn"), channel=SpinChannel.MAGN)
79+
g2_dens = LocalFourPoint(
80+
file2.read_g2_full_multiband(file.get_nd() + file.get_np(), name="dens"), channel=SpinChannel.DENS
81+
)
82+
g2_magn = LocalFourPoint(
83+
file2.read_g2_full_multiband(file.get_nd() + file.get_np(), name="magn"), channel=SpinChannel.MAGN
84+
)
85+
86+
if combine_two_atoms_to_one_obj:
87+
g2_dens_2 = LocalFourPoint(
88+
file2.read_g2_full_multiband(file.get_nd() + file.get_np(), ineq=2, name="dens"), channel=SpinChannel.DENS
89+
)
90+
g2_magn_2 = LocalFourPoint(
91+
file2.read_g2_full_multiband(file.get_nd() + file.get_np(), ineq=2, name="magn"), channel=SpinChannel.MAGN
92+
)
93+
94+
def construct_large_g2(g2_1: LocalFourPoint, g2_2: LocalFourPoint) -> LocalFourPoint:
95+
g2_mean = 0.5 * (g2_1.mat + g2_2.mat)
96+
del g2_2
97+
g2_1.mat = np.zeros((4, 4, 4, 4, *g2_mean.shape[4:]))
98+
g2_1.mat[0:2, 0:2, 0:2, 0:2] = g2_mean
99+
g2_1.mat[2:4, 2:4, 2:4, 2:4] = g2_mean
100+
del g2_mean
101+
return g2_1
102+
103+
g2_dens = construct_large_g2(g2_dens, g2_dens_2)
104+
g2_magn = construct_large_g2(g2_magn, g2_magn_2)
105+
64106
file2.close()
65107

66108
update_frequency_boxes(g2_dens.niw, g2_dens.niv)
67109

68110
def extend_orbital(arr: np.ndarray) -> np.ndarray:
69-
return np.einsum("i...,ij->ij...", arr, np.eye(config.sys.n_bands))
111+
return np.einsum("i...,ij->ij...", arr, np.eye(arr.shape[0]))
70112

71113
giw_spin_mean = np.mean(file.get_giw(), axis=1) # [band,spin,niv]
72114
niv_dmft = giw_spin_mean.shape[-1] // 2
73115
niv_cut = config.box.niw_core + config.box.niv_full + 10
74116
giw_spin_mean = giw_spin_mean[..., niv_dmft - niv_cut : niv_dmft + niv_cut]
75117
g_dmft = GreensFunction(extend_orbital(giw_spin_mean))
76118

119+
if combine_two_atoms_to_one_obj:
120+
giw_spin_mean_2 = np.mean(file.get_giw(ineq=2), axis=1)[..., niv_dmft - niv_cut : niv_dmft + niv_cut]
121+
giw_spin_mean = 0.5 * (giw_spin_mean + giw_spin_mean_2)
122+
del giw_spin_mean_2
123+
giw_spin_mean_large = np.zeros((2 * giw_spin_mean.shape[0], *giw_spin_mean.shape[1:]))
124+
giw_spin_mean_large[0:2] = giw_spin_mean
125+
giw_spin_mean_large[2:4] = giw_spin_mean
126+
g_dmft = GreensFunction(extend_orbital(giw_spin_mean_large))
127+
del giw_spin_mean_large
128+
77129
siw_spin_mean = np.mean(file.get_siw(), axis=1) # [band,spin,niv]
78130
siw_spin_mean = extend_orbital(siw_spin_mean)[None, None, None, ...]
79131
siw_dc_spin_mean = np.mean(file.get_dc(), axis=-1) # [band,spin]
80132
siw_dc_spin_mean = extend_orbital(siw_dc_spin_mean)[None, None, None, ..., None]
81133
siw_spin_mean = siw_spin_mean[..., niv_dmft - niv_cut : niv_dmft + niv_cut]
82134
sigma_dmft = SelfEnergy(siw_spin_mean, estimate_niv_core=True) + siw_dc_spin_mean
83135

136+
if combine_two_atoms_to_one_obj:
137+
siw_spin_mean_2 = np.mean(file.get_siw(ineq=2), axis=1)
138+
siw_spin_mean_2 = extend_orbital(siw_spin_mean_2)[None, None, None, ...]
139+
siw_dc_spin_mean_2 = np.mean(file.get_dc(ineq=2), axis=-1)
140+
siw_dc_spin_mean_2 = extend_orbital(siw_dc_spin_mean_2)[None, None, None, ..., None]
141+
siw_spin_mean_2 = siw_spin_mean_2[..., niv_dmft - niv_cut : niv_dmft + niv_cut]
142+
siw_spin_mean = 0.5 * (siw_spin_mean + siw_spin_mean_2)
143+
del siw_spin_mean_2
144+
siw_dc_spin_mean = 0.5 * (siw_dc_spin_mean + siw_dc_spin_mean_2)
145+
del siw_dc_spin_mean_2
146+
147+
siw_spin_mean_large = np.zeros(
148+
(1, 1, 1, 2 * siw_spin_mean.shape[3], 2 * siw_spin_mean.shape[3], siw_spin_mean.shape[-1])
149+
)
150+
siw_spin_mean_large[:, :, :, 0:2, 0:2, ...] = siw_spin_mean
151+
siw_spin_mean_large[:, :, :, 2:4, 2:4, ...] = siw_spin_mean
152+
siw_dc_spin_mean_large = np.zeros_like(siw_spin_mean_large)
153+
siw_dc_spin_mean_large[:, :, :, 0:2, 0:2, ...] = siw_dc_spin_mean
154+
siw_dc_spin_mean_large[:, :, :, 2:4, 2:4, ...] = siw_dc_spin_mean
155+
sigma_dmft = SelfEnergy(siw_spin_mean_large, estimate_niv_core=True) + siw_dc_spin_mean_large
156+
del siw_spin_mean_large, siw_dc_spin_mean_large
157+
84158
del giw_spin_mean, siw_spin_mean, siw_dc_spin_mean
85159

86160
file.close()
@@ -198,11 +272,17 @@ def set_hamiltonian(er_type: str, er_input: str | list, int_type: str, int_input
198272
if int_type == "one_band_from_dmft" or int_type == "" or int_type is None:
199273
return ham.single_band_interaction(config.lattice.interaction.udd)
200274
elif int_type == "kanamori_from_dmft":
201-
return ham.kanamori_interaction(
202-
config.sys.n_bands,
275+
return ham.kanamori_interaction_dp(
276+
config.sys.nd_bands,
277+
config.sys.np_bands,
203278
config.lattice.interaction.udd,
279+
config.lattice.interaction.upp,
280+
config.lattice.interaction.udp,
204281
config.lattice.interaction.jdd,
282+
config.lattice.interaction.jpp,
283+
config.lattice.interaction.jdp,
205284
config.lattice.interaction.vdd,
285+
config.lattice.interaction.vpp,
206286
)
207287
elif int_type == "custom":
208288
if not isinstance(int_input, str):

moldga/hamiltonian.py

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,26 +107,81 @@ def interaction_orbital_diagonal(self, u: float, n_bands: int = 1) -> "Hamiltoni
107107

108108
return self._add_interaction_term(interaction_elements)
109109

110-
def kanamori_interaction(self, n_bands: int, udd: float, jdd: float, vdd: float = None) -> "Hamiltonian":
110+
def kanamori_interaction_d(self, n_bands: int, udd: float, jdd: float, vdd: float = None) -> "Hamiltonian":
111111
"""
112-
Adds the Kanamori interaction terms to the Hamiltonian. The interaction terms are defined by the Hubbard 'udd' (U),
113-
the exchange interaction 'jdd' (J) and the pair hopping 'vdd' (V or sometimes U'). 'vdd' is an optional parameter, if left empty,
114-
it is set to U-2J.
112+
Adds the Kanamori interaction terms ONLY for d orbitals to the Hamiltonian.
113+
The interaction terms are defined by the Hubbard 'udd' (U),
114+
the exchange interaction 'jdd' (J) and the pair hopping 'vdd' (V or sometimes U').
115+
'vdd' is an optional parameter, if left empty, it is set to V=U-2J.
115116
"""
117+
return self.kanamori_interaction_dp(nd_bands=n_bands, udd=udd, jdd=jdd, vdd=vdd)
118+
119+
def kanamori_interaction_p(self, n_bands: int, upp: float, jpp: float, vpp: float = None) -> "Hamiltonian":
120+
"""
121+
Adds the Kanamori interaction terms ONLY for p orbitals to the Hamiltonian.
122+
The interaction terms are defined by the Hubbard 'udd' (U),
123+
the exchange interaction 'jpp' (J) and the pair hopping 'vpp' (V or sometimes U').
124+
'vpp' is an optional parameter, if left empty, it is set to V=U-2J.
125+
"""
126+
return self.kanamori_interaction_dp(np_bands=n_bands, upp=upp, jpp=jpp, vpp=vpp)
127+
128+
def kanamori_interaction_dp(
129+
self,
130+
nd_bands: int = 0,
131+
np_bands: int = 0,
132+
udd: float = 0.0,
133+
upp: float = 0.0,
134+
udp: float = 0.0,
135+
jdd: float = 0.0,
136+
jpp: float = 0.0,
137+
jdp: float = 0.0,
138+
vdd: float = None,
139+
vpp: float = None,
140+
) -> "Hamiltonian":
141+
"""
142+
Adds the full Kanamori interaction terms for d and p orbitals to the Hamiltonian.
143+
The interaction terms are defined by the local interaction Hubbard U,
144+
the exchange interaction J and the pair hopping V or sometimes U'.
145+
vdd (vpp) (vdp) are optional parameters, if left empty, they are set to V=U-2J.
146+
"""
147+
148+
# Default V=U-2J
116149
if vdd is None:
117150
vdd = udd - 2 * jdd
151+
if vpp is None:
152+
vpp = upp - 2 * jpp
153+
154+
def orb_type(i: int) -> str:
155+
return "d" if i < nd_bands else "p" # d bands come first
156+
157+
def get_params(o1: int, o2: int) -> tuple[float, float, float]:
158+
# Return correct (U, J, V) depending on orbital types of o1, o2
159+
t1, t2 = orb_type(o1), orb_type(o2)
160+
161+
if t1 == "d" and t2 == "d":
162+
return udd, jdd, vdd
163+
elif t1 == "p" and t2 == "p":
164+
return upp, jpp, vpp
165+
else:
166+
return 0, jdp, udp # udp is used as "Vdp", since there is no Udp possible that fits U_llll
118167

119168
r_loc = [0, 0, 0]
169+
n_tot = nd_bands + np_bands
120170

121171
interaction_elements = []
122-
for a, b, c, d in it.product(range(n_bands), repeat=4):
172+
173+
for a, b, c, d in it.product(range(n_tot), repeat=4):
123174
bands = [a + 1, b + 1, c + 1, d + 1]
175+
176+
# choose parameters based on (a,b) pair (equivalently (c,d))
177+
u, j, v = get_params(a, b)
178+
124179
if a == b == c == d: # U_{llll}
125-
interaction_elements.append(InteractionElement(r_loc, bands, udd))
126-
elif (a == d and b == c) or (a == b and c == d): # U_{lmml} or U_{llmm}
127-
interaction_elements.append(InteractionElement(r_loc, bands, jdd))
180+
interaction_elements.append(InteractionElement(r_loc, bands, u))
181+
elif (a == d and b == c) or (a == b and c == d): # U_{lmml}, U_{llmm}
182+
interaction_elements.append(InteractionElement(r_loc, bands, j))
128183
elif a == c and b == d: # U_{lmlm}
129-
interaction_elements.append(InteractionElement(r_loc, bands, vdd))
184+
interaction_elements.append(InteractionElement(r_loc, bands, v))
130185

131186
return self._add_interaction_term(interaction_elements)
132187

moldga/nonlocal_sde.py

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -496,9 +496,7 @@ def read_last_n_sigmas_from_files(n: int, output_path: str = "./", previous_sc_p
496496
last_iterations = sorted(
497497
[(int(match.group(1)), f) for f in files if (match := re.search(r"sigma_dga_iteration_(\d+)\.npy$", f))],
498498
key=lambda x: x[0],
499-
reverse=True,
500-
)
501-
last_iterations = last_iterations[:n] if len(last_iterations) >= n else last_iterations
499+
)[-n:]
502500
return [np.load(file) for _, file in last_iterations]
503501

504502

@@ -536,7 +534,7 @@ def calculate_self_energy_q(
536534
sigma_old, starting_iter = get_starting_sigma(config.self_consistency.previous_sc_path, sigma_dmft)
537535
if starting_iter > 0:
538536
logger.info(
539-
f"Using previous calculation and starting the self-consistency loop at iteration {starting_iter+1}."
537+
f"Using previous calculation and starting the self-consistency loop at iteration {starting_iter + 1}."
540538
)
541539

542540
sigma_old = sigma_old.cut_niv(config.box.niw_core + config.box.niv_full + 10)
@@ -637,7 +635,7 @@ def calculate_self_energy_q(
637635
config.sys.mu = update_mu(
638636
old_mu, config.sys.n, giwk_full.ek, sigma_new.mat, config.sys.beta, sigma_new.fit_smom()[0]
639637
)
640-
# linear mixing of mu to ensure more stable mu convergence
638+
641639
config.sys.mu = (
642640
config.self_consistency.mixing * config.sys.mu + (1 - config.self_consistency.mixing) * old_mu
643641
)
@@ -771,9 +769,96 @@ def get_result(idx: int):
771769
sigma_new.mat[..., niv - niv_core : niv + niv_core] = get_proposal(-1).reshape(shape) + update.reshape(shape)
772770

773771
logger.info(
774-
f"Pulay mixing applied with {n_hist} previous iterations and a mixing parameter of {config.self_consistency.mixing}."
772+
f"Pulay mixing applied with {n_hist} previous iterations and "
773+
f"a mixing parameter of {config.self_consistency.mixing}."
775774
)
776775

776+
return sigma_new
777+
if (
778+
config.self_consistency.mixing_strategy == "anderson"
779+
and current_iter > n_hist
780+
and config.self_consistency.save_iter
781+
and config.output.save_quantities
782+
):
783+
last_sigmas = read_last_n_sigmas_from_files(
784+
n_hist, config.output.output_path, config.self_consistency.previous_sc_path
785+
)
786+
787+
niv = sigma_new.current_shape[-1] // 2
788+
niv_core = config.box.niv_core
789+
sl = slice(niv - niv_core, niv + niv_core)
790+
791+
sigma_dmft_stacked = np.tile(sigma_dmft.mat, (config.lattice.k_grid.nk_tot, 1, 1, 1))
792+
793+
last_proposals = [sigma_dmft_stacked] + last_sigmas # [dmft, s1, ..., s_{n-1}]
794+
last_results = last_sigmas + [sigma_new.mat] # [s1, s2, ..., s_new]
795+
last_proposals = [s[..., sl] for s in last_proposals]
796+
last_results = [s[..., sl] for s in last_results]
797+
798+
shape = last_results[-1].shape
799+
n_total = int(np.prod(shape))
800+
flat = lambda x: x.reshape(-1)
801+
802+
# Current residual f_n = F(x_n) - x_n
803+
f_curr = flat(last_results[-1]) - flat(last_proposals[-1])
804+
f_vec = np.concatenate([f_curr.real, f_curr.imag])
805+
norm_f = np.linalg.norm(f_vec)
806+
807+
# Build dX and dF matrices (n_hist columns)
808+
# dX[:,i] = x_{n-i} - x_{n-i-1} (proposal differences)
809+
# dF[:,i] = f_{n-i} - f_{n-i-1} (residual differences)
810+
dx_cols = []
811+
df_cols = []
812+
for i in range(n_hist):
813+
dx = flat(last_proposals[-1 - i]) - flat(last_proposals[-2 - i])
814+
dx_cols.append(np.concatenate([dx.real, dx.imag]))
815+
816+
df_i = flat(last_results[-1 - i]) - flat(last_proposals[-1 - i])
817+
df_im1 = flat(last_results[-2 - i]) - flat(last_proposals[-2 - i])
818+
df = df_i - df_im1
819+
df_cols.append(np.concatenate([df.real, df.imag]))
820+
821+
dx_matrix = np.column_stack(dx_cols) # (2*n_total, n_hist)
822+
df_matrix = np.column_stack(df_cols) # (2*n_total, n_hist)
823+
824+
# Anderson: solve min ||f_curr - dF @ c||
825+
try:
826+
u, s, vh = np.linalg.svd(df_matrix, full_matrices=False)
827+
828+
s_max = s[0] if len(s) > 0 else 1.0
829+
cutoff = 1e-5 * s_max
830+
mask = s > cutoff
831+
832+
if not np.any(mask):
833+
raise np.linalg.LinAlgError("All singular values below threshold.")
834+
835+
s_reg = s[mask] / (s[mask] ** 2 + cutoff**2)
836+
coeffs = vh[mask].T @ (s_reg * (u[:, mask].T @ f_vec))
837+
838+
except np.linalg.LinAlgError:
839+
logger.warning("Anderson SVD failed — falling back to linear mixing.")
840+
return alpha * sigma_new + (1 - alpha) * sigma_old
841+
842+
# Undamped Anderson proposal: x_n + f_n - (dX + dF) @ c
843+
x_n = flat(last_proposals[-1])
844+
x_anderson = np.concatenate([x_n.real, x_n.imag]) + f_vec - (dx_matrix + df_matrix) @ coeffs
845+
x_anderson = x_anderson[:n_total] + 1j * x_anderson[n_total:]
846+
847+
# Damp between old proposal and Anderson proposal
848+
x_n_complex = x_n
849+
candidate = (1 - alpha) * x_n_complex + alpha * x_anderson.reshape(-1)
850+
851+
# Safety clamp
852+
update = candidate - x_n_complex
853+
norm_u = np.linalg.norm(update)
854+
if norm_f > 0 and norm_u > 3.0 * norm_f:
855+
candidate = x_n_complex + update * (3.0 * norm_f / norm_u)
856+
logger.warning(f"Anderson step clamped (norm_u={norm_u:.3e}, norm_f={norm_f:.3e}).")
857+
858+
sigma_new.mat[..., sl] = candidate.reshape(shape)
859+
860+
logger.info(f"Anderson acceleration applied (m={n_hist}, alpha={alpha:.3f}, norm_f={norm_f:.3e}).")
861+
777862
return sigma_new
778863

779864
sigma_new = config.self_consistency.mixing * sigma_new + (1 - config.self_consistency.mixing) * sigma_old

0 commit comments

Comments
 (0)