Skip to content

Commit 1c18b1c

Browse files
amcadmusHan Wang
andauthored
Support relative force model deviation by normalizing the RMS force magnitude (#496)
* support relative force model deviation normalized by avg f of MD trajectories * update readme Co-authored-by: Han Wang <wang_han@iapcm.ac.cn>
1 parent 11ce228 commit 1c18b1c

File tree

5 files changed

+106
-8
lines changed

5 files changed

+106
-8
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ The bold notation of key (such aas **type_map**) means that it's a necessary key
556556
| model_devi_numb_candi_v | Int | 0 | See `model_devi_adapt_trust_lo`.|
557557
| model_devi_perc_candi_f | Float | 0.0 | See `model_devi_adapt_trust_lo`.|
558558
| model_devi_perc_candi_v | Float | 0.0 | See `model_devi_adapt_trust_lo`.|
559+
| model_devi_f_avg_relative | Boolean | False | Normalized the force model deviations by the RMS force magnitude along the trajectory. This key should not be used with `use_relative`. |
559560
| **model_devi_clean_traj** | Boolean | true | Deciding whether to clean traj folders in MD since they are too large. |
560561
| **model_devi_nopbc** | Boolean | False | Assume open boundary condition in MD simulations. |
561562
| model_devi_activation_func | List of list of string | [["tanh","tanh"],["tanh","gelu"],["gelu","tanh"],["gelu","gelu"]] | Set activation functions for models, length of the List should be the same as `numb_models`, and two elements in the list of string respectively assign activation functions to the embedding and fitting nets within each model. *Backward compatibility*: the orginal "List of String" format is still supported, where embedding and fitting nets of one model use the same activation function, and the length of the List should be the same as `numb_models`|

dpgen/generator/lib/lammps.py

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def make_lammps_input(ensemble,
8989
ret+= "\n"
9090
ret+= "thermo_style custom step temp pe ke etotal press vol lx ly lz xy xz yz\n"
9191
ret+= "thermo ${THERMO_FREQ}\n"
92-
ret+= "dump 1 all custom ${DUMP_FREQ} traj/*.lammpstrj id type x y z\n"
92+
ret+= "dump 1 all custom ${DUMP_FREQ} traj/*.lammpstrj id type x y z fx fy fz\n"
9393
ret+= "restart 10000 dpgen.restart\n"
9494
ret+= "\n"
9595
if pka_e is None :
@@ -137,5 +137,37 @@ def make_lammps_input(ensemble,
137137
# cvt_lammps_conf('POSCAR', 'tmp.lmp')
138138

139139

140-
141-
140+
def get_dumped_forces(
141+
file_name):
142+
with open(file_name) as fp:
143+
lines = fp.read().split('\n')
144+
natoms = None
145+
for idx,ii in enumerate(lines):
146+
if 'ITEM: NUMBER OF ATOMS' in ii:
147+
natoms = int(lines[idx+1])
148+
break
149+
if natoms is None:
150+
raise RuntimeError('wrong dump file format, cannot find number of atoms', file_name)
151+
idfx = None
152+
for idx,ii in enumerate(lines):
153+
if 'ITEM: ATOMS' in ii:
154+
keys = ii
155+
keys = keys.replace('ITEM: ATOMS', '')
156+
keys = keys.split()
157+
idfx = keys.index('fx')
158+
idfy = keys.index('fy')
159+
idfz = keys.index('fz')
160+
break
161+
if idfx is None:
162+
raise RuntimeError('wrong dump file format, cannot find dump keys', file_name)
163+
ret = []
164+
for ii in range(idx+1, idx+natoms+1):
165+
words = lines[ii].split()
166+
ret.append([ float(words[ii]) for ii in [idfx, idfy, idfz] ])
167+
ret = np.array(ret)
168+
return ret
169+
170+
171+
if __name__ == '__main__':
172+
ret = get_dumped_forces('40.lammpstrj')
173+
print(ret)

dpgen/generator/run.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
from dpgen.generator.lib.utils import record_iter
4141
from dpgen.generator.lib.utils import log_task
4242
from dpgen.generator.lib.utils import symlink_user_forward_files
43-
from dpgen.generator.lib.lammps import make_lammps_input
43+
from dpgen.generator.lib.lammps import make_lammps_input, get_dumped_forces
4444
from dpgen.generator.lib.vasp import write_incar_dict
4545
from dpgen.generator.lib.vasp import make_vasp_incar_user_dict
4646
from dpgen.generator.lib.vasp import incar_upper
@@ -1323,6 +1323,24 @@ def check_bad_box(conf_name,
13231323
return is_bad
13241324

13251325

1326+
def _read_model_devi_file(
1327+
task_path : str,
1328+
model_devi_f_avg_relative : bool = False
1329+
):
1330+
model_devi = np.loadtxt(os.path.join(task_path, 'model_devi.out'))
1331+
if model_devi_f_avg_relative :
1332+
trajs = glob.glob(os.path.join(task_path, 'traj', '*.lammpstrj'))
1333+
all_f = []
1334+
for ii in trajs:
1335+
all_f.append(get_dumped_forces(ii))
1336+
all_f = np.array(all_f)
1337+
all_f = all_f.reshape([-1,3])
1338+
avg_f = np.sqrt(np.average(np.sum(np.square(all_f), axis = 1)))
1339+
model_devi[:,4:7] = model_devi[:,4:7] / avg_f
1340+
np.savetxt(os.path.join(task_path, 'model_devi_avgf.out'), model_devi, fmt='%16.6e')
1341+
return model_devi
1342+
1343+
13261344
def _select_by_model_devi_standard(
13271345
modd_system_task: List[str],
13281346
f_trust_lo : float,
@@ -1331,6 +1349,7 @@ def _select_by_model_devi_standard(
13311349
v_trust_hi : float,
13321350
cluster_cutoff : float,
13331351
model_devi_skip : int = 0,
1352+
model_devi_f_avg_relative : bool = False,
13341353
detailed_report_make_fp : bool = True,
13351354
):
13361355
fp_candidate = []
@@ -1345,7 +1364,7 @@ def _select_by_model_devi_standard(
13451364
for tt in modd_system_task :
13461365
with warnings.catch_warnings():
13471366
warnings.simplefilter("ignore")
1348-
all_conf = np.loadtxt(os.path.join(tt, 'model_devi.out'))
1367+
all_conf = _read_model_devi_file(tt, model_devi_f_avg_relative)
13491368
for ii in range(all_conf.shape[0]) :
13501369
if all_conf[ii][0] < model_devi_skip :
13511370
continue
@@ -1393,7 +1412,8 @@ def _select_by_model_devi_adaptive_trust_low(
13931412
v_trust_hi : float,
13941413
numb_candi_v : int,
13951414
perc_candi_v : float,
1396-
model_devi_skip : int = 0
1415+
model_devi_skip : int = 0,
1416+
model_devi_f_avg_relative : bool = False,
13971417
):
13981418
"""
13991419
modd_system_task model deviation tasks belonging to one system
@@ -1424,6 +1444,7 @@ def _select_by_model_devi_adaptive_trust_low(
14241444
with warnings.catch_warnings():
14251445
warnings.simplefilter("ignore")
14261446
model_devi = np.loadtxt(os.path.join(tt, 'model_devi.out'))
1447+
model_devi = _read_model_devi_file(tt, model_devi_f_avg_relative)
14271448
for ii in range(model_devi.shape[0]) :
14281449
if model_devi[ii][0] < model_devi_skip :
14291450
continue
@@ -1512,6 +1533,7 @@ def _make_fp_vasp_inner (modd_path,
15121533
fp_tasks = []
15131534
cluster_cutoff = jdata['cluster_cutoff'] if jdata.get('use_clusters', False) else None
15141535
model_devi_adapt_trust_lo = jdata.get('model_devi_adapt_trust_lo', False)
1536+
model_devi_f_avg_relative = jdata.get('model_devi_f_avg_relative', False)
15151537
# skip save *.out if detailed_report_make_fp is False, default is True
15161538
detailed_report_make_fp = jdata.get("detailed_report_make_fp", True)
15171539
# skip bad box criteria
@@ -1532,7 +1554,9 @@ def _make_fp_vasp_inner (modd_path,
15321554
v_trust_lo, v_trust_hi,
15331555
cluster_cutoff,
15341556
model_devi_skip,
1535-
detailed_report_make_fp = detailed_report_make_fp)
1557+
model_devi_f_avg_relative = model_devi_f_avg_relative,
1558+
detailed_report_make_fp = detailed_report_make_fp,
1559+
)
15361560
else:
15371561
numb_candi_f = jdata.get('model_devi_numb_candi_f', 10)
15381562
numb_candi_v = jdata.get('model_devi_numb_candi_v', 0)
@@ -1543,7 +1567,9 @@ def _make_fp_vasp_inner (modd_path,
15431567
modd_system_task,
15441568
f_trust_hi, numb_candi_f, perc_candi_f,
15451569
v_trust_hi, numb_candi_v, perc_candi_v,
1546-
model_devi_skip = model_devi_skip)
1570+
model_devi_skip = model_devi_skip,
1571+
model_devi_f_avg_relative = model_devi_f_avg_relative,
1572+
)
15471573
dlog.info("system {0:s} {1:9s} : f_trust_lo {2:6.3f} v_trust_lo {3:6.3f}".format(ss, 'adapted', f_trust_lo_ad, v_trust_lo_ad))
15481574

15491575
# print a report

tests/generator/context.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from dpgen.generator.run import *
55
from dpgen.generator.lib.gaussian import detect_multiplicity
66
from dpgen.generator.lib.ele_temp import NBandsEsti
7+
from dpgen.generator.lib.lammps import get_dumped_forces
78

89
param_file = 'param-mg-vasp.json'
910
param_file_v1 = 'param-mg-vasp-v1.json'

tests/generator/test_lammps.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import os,sys,json,glob,shutil,textwrap
2+
import dpdata
3+
import numpy as np
4+
import unittest
5+
6+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
7+
__package__ = 'generator'
8+
from .context import get_dumped_forces
9+
10+
class TestGetDumpForce(unittest.TestCase):
11+
def setUp(self):
12+
file_content = textwrap.dedent("""\
13+
ITEM: TIMESTEP
14+
40
15+
ITEM: NUMBER OF ATOMS
16+
2
17+
ITEM: BOX BOUNDS xy xz yz pp pp pp
18+
-2.9180686220264818e-04 8.0855380329747089e+00 1.4011011277606830e-07
19+
-2.9198257591541018e-04 8.0855378881632269e+00 3.3202396460852749e-08
20+
-2.9180686326490957e-04 8.0855378891632768e+00 -1.7571268247505500e-07
21+
ITEM: ATOMS id type x y z fx fy fz
22+
1 1 2.09532 8.19528 2.00538 -0.00569269 -0.0200373 -0.0342394
23+
2 1 -0.0727384 4.01773 4.05582 -0.0297083 0.0817184 0.0722508
24+
""")
25+
with open('tmp.dump', 'w') as fp:
26+
fp.write(file_content)
27+
self.expected_f = [ -0.00569269, -0.0200373, -0.0342394, -0.0297083, 0.0817184, 0.0722508]
28+
29+
def tearDown(self):
30+
if os.path.isfile('tmp.dump'):
31+
os.remove('tmp.dump')
32+
33+
def test_read_dump(self):
34+
ff = get_dumped_forces('tmp.dump')
35+
self.assertEqual(ff.shape, (2, 3))
36+
ff = ff.reshape([-1])
37+
for ii in range(6):
38+
self.assertAlmostEqual(ff[ii], self.expected_f[ii])

0 commit comments

Comments
 (0)