Skip to content

Commit 9497859

Browse files
committed
refactor(core/ml): migrate Evaluator to core eval layer
1 parent ec72093 commit 9497859

4 files changed

Lines changed: 329 additions & 324 deletions

File tree

deepks/core/ml/eval/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
"""Scaffold package for refactor architecture."""
1+
"""Evaluation components for DeepKS core ML layer."""
2+
3+
from .evaluator import * # noqa: F401,F403

deepks/core/ml/eval/evaluator.py

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
import os
2+
import sys
3+
import numpy as np
4+
import torch
5+
from time import time
6+
try:
7+
import deepks
8+
except ImportError as e:
9+
sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../../")
10+
from deepks.io.readers.group_reader import generalized_eigh
11+
from deepks.model.utils import get_density_matrix, cal_phi_loss, cal_v_delta, get_occ_func, make_loss, get_gedm, cal_vdr, loss_hr
12+
13+
class Evaluator:
14+
def __init__(self,
15+
energy_factor=1., force_factor=0.,
16+
stress_factor=0., orbital_factor=0.,
17+
v_delta_factor=0., v_delta_r_factor=0.,
18+
phi_factor=0., phi_occ=0,
19+
band_factor=0.,band_occ=0,
20+
density_m_factor=0.,density_m_occ=0,
21+
density_factor=0., grad_penalty=0.,
22+
energy_lossfn=None, force_lossfn=None,
23+
stress_lossfn=None, orbital_lossfn=None,
24+
v_delta_lossfn=None, v_delta_r_lossfn=None,
25+
phi_lossfn=None, band_lossfn=None,
26+
density_m_lossfn=None,
27+
energy_per_atom=0,vd_divide_by_nlocal=False):
28+
# energy term
29+
if energy_lossfn is None:
30+
energy_lossfn = {}
31+
if isinstance(energy_lossfn, dict):
32+
energy_lossfn = make_loss(**energy_lossfn)
33+
self.e_factor = energy_factor
34+
self.e_lossfn = energy_lossfn
35+
# force term
36+
if force_lossfn is None:
37+
force_lossfn = {}
38+
if isinstance(force_lossfn, dict):
39+
force_lossfn = make_loss(**force_lossfn)
40+
self.f_factor = force_factor
41+
self.f_lossfn = force_lossfn
42+
# stress term
43+
if stress_lossfn is None:
44+
stress_lossfn = {}
45+
if isinstance(stress_lossfn, dict):
46+
stress_lossfn = make_loss(**stress_lossfn)
47+
self.s_factor = stress_factor
48+
self.s_lossfn = stress_lossfn
49+
# orbital(bandgap) term
50+
if orbital_lossfn is None:
51+
orbital_lossfn = {}
52+
if isinstance(orbital_lossfn, dict):
53+
orbital_lossfn = make_loss(**orbital_lossfn)
54+
self.o_factor = orbital_factor
55+
self.o_lossfn = orbital_lossfn
56+
# v_delta term
57+
if v_delta_lossfn is None:
58+
v_delta_lossfn = {}
59+
if isinstance(v_delta_lossfn, dict):
60+
v_delta_lossfn = make_loss(**v_delta_lossfn)
61+
self.vd_factor = v_delta_factor
62+
self.vd_lossfn = v_delta_lossfn
63+
self.vd_divide_by_nlocal = vd_divide_by_nlocal
64+
# v_delta_r term
65+
if v_delta_r_lossfn is None:
66+
v_delta_r_lossfn = {}
67+
if isinstance(v_delta_r_lossfn, dict):
68+
# v_delta_r_lossfn = make_loss(**v_delta_r_lossfn)
69+
v_delta_r_lossfn = loss_hr
70+
self.vdr_factor = v_delta_r_factor
71+
self.vdr_lossfn = v_delta_r_lossfn
72+
# phi term
73+
if phi_lossfn is None:
74+
phi_lossfn = {}
75+
if isinstance(phi_lossfn, dict):
76+
phi_lossfn = make_loss(**phi_lossfn)
77+
self.phi_factor = phi_factor
78+
self.phi_lossfn = phi_lossfn
79+
self.get_phi_occ = get_occ_func(phi_occ)
80+
# band energy term
81+
if band_lossfn is None:
82+
band_lossfn = {}
83+
if isinstance(band_lossfn, dict):
84+
band_lossfn = make_loss(**band_lossfn)
85+
self.band_factor = band_factor
86+
self.band_lossfn = band_lossfn
87+
self.get_band_occ = get_occ_func(band_occ)
88+
#density matrix term
89+
if density_m_lossfn is None:
90+
density_m_lossfn = {}
91+
if isinstance(density_m_lossfn, dict):
92+
density_m_lossfn = make_loss(**density_m_lossfn)
93+
self.density_m_factor = density_m_factor
94+
self.density_m_lossfn = density_m_lossfn
95+
self.get_density_m_occ = get_occ_func(density_m_occ)
96+
# coulomb term of dm; requires head gradient
97+
self.d_factor = density_factor
98+
# gradient penalty, not very useful
99+
self.g_penalty = grad_penalty
100+
# energy loss divide by 1/natom/natom^2
101+
self.energy_per_atom=energy_per_atom
102+
103+
def __call__(self, model, sample):
104+
_dref = next(model.parameters()).device
105+
#print("_dref:")
106+
#print(_dref)
107+
tot_loss = 0.
108+
loss=[]
109+
# keep only phialpha in cpu, move all other data to _dref, set complex dtype to complex128
110+
for k, v in sample.items():
111+
if k == "data_shape":
112+
sample[k] = v
113+
elif isinstance(v, list):
114+
sample[k] = [vv.to(_dref, non_blocking=True) for vv in v]
115+
elif not torch.is_complex(v):
116+
sample[k] = v.to(_dref, non_blocking=True)
117+
else:
118+
if k == "phialpha":
119+
sample[k] = v.to("cpu", dtype=torch.complex128, non_blocking=True)
120+
else:
121+
sample[k] = v.to(_dref, dtype=torch.complex128, non_blocking=True)
122+
e_label, eig = sample["lb_e"], sample["eig"]
123+
nframe = e_label.shape[0]
124+
requires_grad = ( (self.f_factor > 0 and "lb_f" in sample)
125+
or (self.s_factor > 0 and "lb_s" in sample)
126+
or (self.o_factor > 0 and "lb_o" in sample)
127+
or (self.vd_factor > 0 and "lb_vd" in sample)
128+
or (self.vdr_factor > 0 and "lb_vdr" in sample)
129+
or (self.phi_factor > 0 and "lb_phi" in sample)
130+
or (self.band_factor > 0 and "lb_band" in sample)
131+
or (self.density_m_factor > 0)
132+
or (self.d_factor > 0 and "gldv" in sample)
133+
or self.g_penalty > 0)
134+
eig.requires_grad_(requires_grad)
135+
# begin the calculation
136+
e_pred = model(eig)
137+
# may divide e_loss by 1 or natom or natom**2: this way energy loss will not increase when number of atom increase
138+
natom = eig.shape[1]
139+
e_loss = self.e_factor * self.e_lossfn(e_pred, e_label) / (natom**self.energy_per_atom)
140+
tot_loss = tot_loss + e_loss
141+
loss.append(e_loss)
142+
if requires_grad:
143+
[gev] = torch.autograd.grad(e_pred, eig,
144+
grad_outputs=torch.ones_like(e_pred),
145+
retain_graph=True, create_graph=True, only_inputs=True)
146+
# for now always use pure l2 loss for gradient penalty
147+
if self.g_penalty > 0 and "eg0" in sample:
148+
eg_base, gveg = sample["eg0"], sample["gveg"]
149+
eg_tot = torch.einsum('...apg,...ap->...g', gveg, gev) + eg_base
150+
tot_loss = tot_loss + self.g_penalty * eg_tot.pow(2).mean(0).sum()
151+
loss.append(self.g_penalty * eg_tot.pow(2).mean(0).sum())
152+
# optional force calculation
153+
if self.f_factor > 0 and "lb_f" in sample:
154+
f_label, gvx = sample["lb_f"], sample["gvx"]
155+
f_pred = - torch.einsum("...bxap,...ap->...bx", gvx, gev)
156+
tot_loss = tot_loss + self.f_factor * self.f_lossfn(f_pred, f_label)
157+
loss.append(self.f_factor * self.f_lossfn(f_pred, f_label))
158+
# optional stress calculation
159+
if self.s_factor > 0 and "lb_s" in sample:
160+
s_label, gvepsl = sample["lb_s"], sample["gvepsl"]
161+
s_pred = torch.einsum("...iap,...ap->...i", gvepsl, gev)
162+
tot_loss = tot_loss + self.s_factor * self.s_lossfn(s_pred, s_label)
163+
loss.append(self.s_factor * self.s_lossfn(s_pred, s_label))
164+
# optional orbital(bandgap) calculation
165+
if self.o_factor > 0 and "lb_o" in sample:
166+
o_label, op = sample["lb_o"], sample["op"]
167+
op = op.contiguous().view(op.shape[0], o_label.shape[1], o_label.shape[2], op.shape[-2], op.shape[-1])
168+
o_pred = torch.einsum("...kiap,...ap->...ki", op, gev)
169+
# print(o_label.shape, op.shape, o_pred.shape, gev.shape)
170+
tot_loss = tot_loss + self.o_factor * self.o_lossfn(o_pred, o_label)
171+
loss.append(self.o_factor * self.o_lossfn(o_pred, o_label))
172+
# optional v_delta/phi/band_energy/density_matrix calculation
173+
if (self.vd_factor > 0 and "lb_vd" in sample) or (self.phi_factor > 0 and "lb_phi" in sample) \
174+
or (self.band_factor > 0 and "lb_band" in sample) or (self.density_m_factor > 0 and "lb_phi" in sample):
175+
# cal v_delta
176+
if "vdp" in sample:
177+
vdp = sample["vdp"] # can be complex
178+
vd_pred = torch.einsum("...kxyap,...ap->...kxy", vdp, gev)
179+
elif "phialpha" in sample and "gevdm" in sample:
180+
# start=time()
181+
vd_pred = cal_v_delta(gev,sample["gevdm"],sample["phialpha"])
182+
# end=time()
183+
# print("cal vdp time in batch:",end-start)
184+
nlocal = vd_pred.shape[-1]
185+
186+
# optional v_delta calculation
187+
if self.vd_factor > 0 and "lb_vd" in sample:
188+
vd_label = sample["lb_vd"]
189+
vd_loss = self.vd_factor * self.vd_lossfn(vd_pred, vd_label)
190+
# original: mean method,divide by nlocal**2. vd_divide_by_nlocal:divide by nlocal
191+
if self.vd_divide_by_nlocal:
192+
vd_loss = vd_loss * nlocal
193+
tot_loss = tot_loss + vd_loss
194+
loss.append(vd_loss)
195+
196+
if (self.phi_factor > 0 and "lb_phi" in sample) or (self.band_factor > 0 and "lb_band" in sample) or (self.density_m_factor > 0 and "lb_phi" in sample):
197+
h_base = sample["h_base"]
198+
if "L_inv" in sample:
199+
L_inv=sample["L_inv"]
200+
band_pred,phi_pred=generalized_eigh(h_base+vd_pred,L_inv)
201+
else:
202+
band_pred,phi_pred= torch.linalg.eigh(h_base+vd_pred,UPLO='U')
203+
# optional phi calculation
204+
if self.phi_factor > 0 and "lb_phi" in sample:
205+
phi_label = sample["lb_phi"]
206+
phi_loss = self.phi_factor * cal_phi_loss(phi_pred,phi_label,self.get_phi_occ(natom))
207+
tot_loss = tot_loss + phi_loss
208+
loss.append(phi_loss)
209+
# optional band energy calculation
210+
if self.band_factor > 0 and "lb_band" in sample:
211+
band_label = sample["lb_band"]
212+
band_occ=self.get_band_occ(natom)
213+
band_loss = self.band_factor * self.band_lossfn(band_pred[...,:band_occ], band_label[...,:band_occ])
214+
tot_loss = tot_loss + band_loss
215+
# print("occ_band",band_pred[...,:band_occ],band_label[...,:band_occ])
216+
loss.append(band_loss)
217+
# optional density matrix calculation
218+
if self.density_m_factor > 0 and "lb_phi" in sample:
219+
# calculate density_m_label every time, kind of waste of time
220+
phi_label = sample["lb_phi"]
221+
density_m_occ=self.get_density_m_occ(natom)
222+
density_m_label = get_density_matrix(phi_label,density_m_occ)
223+
density_m_pred = get_density_matrix(phi_pred,density_m_occ)
224+
#need to multiply nlocal, reason is the same as v_delta
225+
density_m_loss = self.density_m_factor * self.density_m_lossfn(density_m_pred, density_m_label) * nlocal
226+
tot_loss = tot_loss + density_m_loss
227+
loss.append(density_m_loss)
228+
# optional v_delta_r calculation
229+
if self.vdr_factor > 0 and "lb_vdr" in sample:
230+
vdr_label = sample["lb_vdr"] * 0.5 # Ry2Hartree
231+
if "vdrp" in sample:
232+
vdrp = sample["vdrp"]
233+
vdr_pred = torch.einsum("...bcdxyap,...ap->...bcdxy", vdrp, gev)
234+
elif "gevdm" in sample and "iR_mat" in sample and "overlap" in sample and "data_shape" in sample:
235+
gevdm = sample["gevdm"]
236+
overlap = sample["overlap"]
237+
iR_mat = sample["iR_mat"]
238+
data_shape = sample["data_shape"]
239+
gedm = get_gedm(gev, gevdm, data_shape[0], data_shape[1])
240+
vdr_pred = cal_vdr(gedm, overlap, iR_mat, vdr_label)
241+
vdr_loss = self.vdr_factor * self.vdr_lossfn(vdr_pred, vdr_label)
242+
tot_loss = tot_loss + vdr_loss
243+
loss.append(vdr_loss)
244+
# density loss with fix head grad
245+
if self.d_factor > 0 and "gldv" in sample:
246+
gldv = sample["gldv"]
247+
d_loss = self.d_factor * torch.abs((gldv * gev).mean(0).sum())
248+
tot_loss = tot_loss + d_loss
249+
loss.append(d_loss)
250+
loss.append(tot_loss)
251+
return loss
252+
253+
def print_head(self,name,data_keys,align_len=20):
254+
info=f"{name}_energy".rjust(align_len)
255+
if self.g_penalty > 0 and "eg0" in data_keys:
256+
info+=f"{name}_grad".rjust(align_len)
257+
# optional force calculation
258+
if self.f_factor > 0 and "lb_f" in data_keys:
259+
info+=f"{name}_force".rjust(align_len)
260+
# optional stress calculation
261+
if self.s_factor > 0 and "lb_s" in data_keys:
262+
info+=f"{name}_stress".rjust(align_len)
263+
# optional orbital(bandgap) calculation
264+
if self.o_factor > 0 and "lb_o" in data_keys:
265+
info+=f"{name}_bandgap".rjust(align_len)
266+
# optional v_delta calculation
267+
if self.vd_factor > 0 and "lb_vd" in data_keys:
268+
info+=f"{name}_v_delta".rjust(align_len)
269+
# optional v_delta_r calculation
270+
if self.vdr_factor > 0 and "lb_vdr" in data_keys:
271+
info+=f"{name}_v_delta_r".rjust(align_len)
272+
# optional phi calculation
273+
if self.phi_factor > 0 and "lb_phi" in data_keys:
274+
info+=f"{name}_phi".rjust(align_len)
275+
# optional band energy calculation
276+
if self.band_factor > 0 and "lb_band" in data_keys:
277+
info+=f"{name}_band".rjust(align_len)
278+
# optional density matrix calculation
279+
if self.density_m_factor > 0 and "lb_phi" in data_keys:
280+
info+=f"{name}_dm".rjust(align_len)
281+
# density loss with fix head grad
282+
if self.d_factor > 0 and "gldv" in data_keys:
283+
info+=f"{name}_density".rjust(align_len)
284+
print(info,end='')
285+
286+
class NatomLossList:
287+
def __init__(self):
288+
self.natom_loss_list=dict()
289+
self.n_loss_term=0
290+
291+
def clear_loss(self):
292+
if not self.n_loss_term:
293+
self.n_loss_term=len(self.natom_loss_list[list(self.natom_loss_list.keys())[0]][0])
294+
#don't clear natom, just sample_all_batch in the beginning gives all data
295+
for natom in self.natom_loss_list.keys():
296+
self.natom_loss_list[natom]=[[0. for _ in range(self.n_loss_term)]]
297+
298+
def add_loss(self,natom,loss):
299+
assert len(loss) > 0, "loss should not be empty"
300+
if not self.n_loss_term:
301+
self.n_loss_term=len(loss)
302+
assert len(loss) == self.n_loss_term, \
303+
f"loss length are different for newly added natom {natom}, expected {self.n_loss_term}, got {len(loss)}"
304+
if natom not in self.natom_loss_list.keys():
305+
self.natom_loss_list[natom]=[]
306+
self.natom_loss_list[natom].append([loss_term.item() for loss_term in loss])
307+
308+
def natoms(self):
309+
return sorted(self.natom_loss_list.keys())
310+
311+
def avg_atom_loss(self):
312+
# avg upon data
313+
return {natom:np.mean(losses,axis=0) for (natom,losses) in self.natom_loss_list.items()}
314+
315+
def print_avg_atom_loss(self,align_len=20):
316+
avg_atom_loss = sorted(self.avg_atom_loss().items(), key=lambda x: x[0])
317+
for (atom,aal) in avg_atom_loss:
318+
for avg_atom_loss_term in aal[:-1]:
319+
print(f"{avg_atom_loss_term:>{align_len}.4e}",end='')
320+
321+
def avg_loss(self):
322+
# avg upon data and natom
323+
return np.mean([loss for losses in self.natom_loss_list.values() for loss in losses ],axis=0)

0 commit comments

Comments
 (0)