1414# Collection of functions related to machine learning and data analysis
1515# Also helper tools for Torch and MLatom interfaces
1616
17+ # Helper that writes a training-data file in the MACE extended-XYZ format.
18+ # atom_energies/energies are expected in Hartree and gradients in Hartree/Bohr;
19+ # all are converted to MACE units (eV and eV/Å) on the fly.
20+ def write_mace_xyz_file (filename , atom_energies , energies , gradients , fragments , Grad = True ):
21+ #TODO: Nmols, comp, molindex ?
22+ Nmols = "1"
23+ comp = "xxx"
24+ molindex = 0
25+ with open (filename , "w" ) as mace_file :
26+ print (f"Writing isolated atom reference energies to { filename } ...." )
27+ for el , an_at in atom_energies .items ():
28+ en_ev = an_at * 27.211386245988
29+ mace_file .write ("1\n " )
30+ if Grad :
31+ mace_file .write (f"Properties=species:S:1:pos:R:3:forces_REF:R:3 config_type=IsolatedAtom energy_REF={ en_ev } pbc='F F F'\n " )
32+ mace_file .write (f"{ el :2s} { 0.0 :17.8f} { 0.0 :17.8f} { 0.0 :17.8f} "
33+ f"{ - 0.0 :17.8f} { - 0.0 :17.8f} { - 0.0 :17.8f} \n " )
34+ else :
35+ mace_file .write (f"Properties=species:S:1:pos:R:3 config_type=IsolatedAtom energy_REF={ en_ev } pbc='F F F'\n " )
36+ mace_file .write (f"{ el :2s} { 0.0 :17.8f} { 0.0 :17.8f} { 0.0 :17.8f} \n " )
37+
38+ for i in range (len (energies )):
39+ # Converting energy to eV
40+ frag = fragments [i ]
41+ energy_ev = energies [i ]* 27.211386245988
42+ mace_file .write (f"{ frag .numatoms } \n " )
43+ if Grad :
44+ force = - 1 * np .array (gradients [i ]) * 51.42206747
45+ mace_file .write (f"Properties=species:S:1:pos:R:3:molID:I:1:forces_REF:R:3 Nmols={ Nmols } Comp={ comp } energy_REF={ energy_ev } pbc='F F F'\n " )
46+ for j in range (frag .numatoms ): # Bug fix: inner loop variable was i, now j
47+ mace_file .write (f"{ frag .elems [j ]:2s} { frag .coords [j ][0 ]:17.8f} { frag .coords [j ][1 ]:17.8f} { frag .coords [j ][2 ]:17.8f} "
48+ f"{ molindex :9d} { force [j ][0 ]:17.8f} { force [j ][1 ]:17.8f} { force [j ][2 ]:17.8f} \n " )
49+ else :
50+ # Write without forces
51+ mace_file .write (f"Properties=species:S:1:pos:R:3:molID:I:1 Nmols={ Nmols } Comp={ comp } energy_REF={ energy_ev } pbc='F F F'\n " )
52+ for j in range (frag .numatoms ): # Bug fix: inner loop variable was i, now j
53+ mace_file .write (f"{ frag .elems [j ]:2s} { frag .coords [j ][0 ]:17.8f} { frag .coords [j ][1 ]:17.8f} { frag .coords [j ][2 ]:17.8f} "
54+ f"{ molindex :9d} \n " )
55+
1756# Function to create ML training data given XYZ-files and 2 ASH theories
1857def create_ML_training_data (xyz_dir = None , dcd_trajectory = None , xyz_trajectory = None , xyz_files = None , num_snapshots = None , random_snapshots = True ,
1958 dcd_pdb_topology = None , nth_frame_in_traj = 1 , printlevel = 2 ,
@@ -196,7 +235,8 @@ def create_ML_training_data(xyz_dir=None, dcd_trajectory=None, xyz_trajectory=No
196235 # Remove old files if present
197236 for f in ["train_data.xyz" , "train_data.energies" , "train_data.gradients" , "train_data_mace.xyz" ,
198237 "train_data_theory1.energies" , "train_data_theory2.energies" ,
199- "train_data_theory1.gradients" , "train_data_theory2.gradients" ]:
238+ "train_data_theory1.gradients" , "train_data_theory2.gradients" ,
239+ "train_data_mace_theory1.xyz" , "train_data_mace_theory2.xyz" ]:
200240 try :
201241 os .remove (f )
202242 except :
@@ -213,9 +253,10 @@ def create_ML_training_data(xyz_dir=None, dcd_trajectory=None, xyz_trajectory=No
213253 gradients_theory1 = []
214254 gradients_theory2 = []
215255
216- # Removing
256+ # Removing
217257 theory_1 .cleanup ()
218- theory_2 .cleanup ()
258+ if theory_2 is not None :
259+ theory_2 .cleanup ()
219260
220261 if runmode == "serial" :
221262 print ("Runmode is serial!" )
@@ -325,6 +366,9 @@ def create_ML_training_data(xyz_dir=None, dcd_trajectory=None, xyz_trajectory=No
325366 gradients .append (gradient )
326367
327368 # Calculate energies for atoms
369+ # Per-theory isolated-atom reference energies, needed to write per-theory MACE files
370+ energies_atoms_dict_theory1 = {}
371+ energies_atoms_dict_theory2 = {}
328372 if energies_atoms_dict is None :
329373 print ("\n Now calculating isolated atom reference energies for each element in the training set" )
330374 energies_atoms_dict = {}
@@ -340,13 +384,15 @@ def create_ML_training_data(xyz_dir=None, dcd_trajectory=None, xyz_trajectory=No
340384 theory_1 .cleanup ()
341385 result_1 = Singlepoint (theory = theory_1 , fragment = atomfrag , printlevel = 0 ,
342386 result_write_to_disk = False )
387+ energies_atoms_dict_theory1 [uniq_el ] = result_1 .energy
343388 if delta is True :
344389 theory_2 .printlevel = 0
345390 # Running theory 2
346391 print ("Now running Theory 2 for atom:" , uniq_el )
347392 theory_2 .cleanup ()
348393 result_2 = Singlepoint (theory = theory_2 , fragment = atomfrag , printlevel = 0 ,
349394 result_write_to_disk = False )
395+ energies_atoms_dict_theory2 [uniq_el ] = result_2 .energy
350396 # Delta energy
351397 atomenergy = result_2 .energy - result_1 .energy
352398 else :
@@ -406,42 +452,23 @@ def create_ML_training_data(xyz_dir=None, dcd_trajectory=None, xyz_trajectory=No
406452 print ("\n Now writing data in MACE-format with energies in units of eV and forces in eV/Å" )
407453 print ("Fragments labels:" ,[frag .label for frag in fragments ])
408454 print ("energies:" , energies )
409- # Write data file that MACE uses
455+ # Write combined data file that MACE uses (delta data when two theories are used,
456+ # otherwise the single-theory data)
457+ write_mace_xyz_file ("train_data_mace.xyz" , energies_atoms_dict , energies , gradients ,
458+ fragments , Grad = Grad )
410459
411- with open ("train_data_mace.xyz" , "w" ) as mace_file :
412- print ("Writing isolated atom reference energies...." )
413- for el , an_at in energies_atoms_dict .items ():
414- en_ev = an_at * 27.211386245988
415- mace_file .write ("1\n " )
416- if Grad :
417- mace_file .write (f"Properties=species:S:1:pos:R:3:forces_REF:R:3 config_type=IsolatedAtom energy_REF={ en_ev } pbc='F F F'\n " )
418- mace_file .write (f"{ el :2s} { 0.0 :17.8f} { 0.0 :17.8f} { 0.0 :17.8f} "
419- f"{ - 0.0 :17.8f} { - 0.0 :17.8f} { - 0.0 :17.8f} \n " )
420- else :
421- mace_file .write (f"Properties=species:S:1:pos:R:3 config_type=IsolatedAtom energy_REF={ en_ev } pbc='F F F'\n " )
422- mace_file .write (f"{ el :2s} { 0.0 :17.8f} { 0.0 :17.8f} { 0.0 :17.8f} \n " )
423- #TODO: Nmols, comp, molindex ?
424- Nmols = "1"
425- comp = "xxx"
426- molindex = 0
427-
428- for i in range (len (energies )):
429- # Converting energy to eV
430- frag = fragments [i ]
431- energy_ev = energies [i ]* 27.211386245988
432- mace_file .write (f"{ frag .numatoms } \n " )
433- if Grad :
434- force = - 1 * np .array (gradients [i ]) * 51.42206747
435- mace_file .write (f"Properties=species:S:1:pos:R:3:molID:I:1:forces_REF:R:3 Nmols={ Nmols } Comp={ comp } energy_REF={ energy_ev } pbc='F F F'\n " )
436- for j in range (frag .numatoms ): # Bug fix: inner loop variable was i, now j
437- mace_file .write (f"{ frag .elems [j ]:2s} { frag .coords [j ][0 ]:17.8f} { frag .coords [j ][1 ]:17.8f} { frag .coords [j ][2 ]:17.8f} "
438- f"{ molindex :9d} { force [j ][0 ]:17.8f} { force [j ][1 ]:17.8f} { force [j ][2 ]:17.8f} \n " )
439- else :
440- # Write without forces
441- mace_file .write (f"Properties=species:S:1:pos:R:3:molID:I:1 Nmols={ Nmols } Comp={ comp } energy_REF={ energy_ev } pbc='F F F'\n " )
442- for j in range (frag .numatoms ): # Bug fix: inner loop variable was i, now j
443- mace_file .write (f"{ frag .elems [j ]:2s} { frag .coords [j ][0 ]:17.8f} { frag .coords [j ][1 ]:17.8f} { frag .coords [j ][2 ]:17.8f} "
444- f"{ molindex :9d} \n " )
460+ # When delta-learning (two theories), also write a MACE-format file for each
461+ # individual theory level, using that theory's own atomic reference energies.
462+ if delta is True :
463+ if energies_atoms_dict_theory1 and energies_atoms_dict_theory2 :
464+ write_mace_xyz_file ("train_data_mace_theory1.xyz" , energies_atoms_dict_theory1 ,
465+ energies_theory1 , gradients_theory1 , fragments , Grad = Grad )
466+ write_mace_xyz_file ("train_data_mace_theory2.xyz" , energies_atoms_dict_theory2 ,
467+ energies_theory2 , gradients_theory2 , fragments , Grad = Grad )
468+ else :
469+ print ("Warning: per-theory isolated-atom reference energies are not available "
470+ "(user-provided energies_atoms_dict only holds the combined values)." )
471+ print ("Skipping per-theory MACE files train_data_mace_theory1.xyz / train_data_mace_theory2.xyz" )
445472
446473 print ("All done! Files created:\n train_data.xyz\n train_data.energies\n train_data_mace.xyz" )
447474 if Grad :
@@ -450,6 +477,8 @@ def create_ML_training_data(xyz_dir=None, dcd_trajectory=None, xyz_trajectory=No
450477 print ("train_data_theory1.energies\n train_data_theory2.energies" )
451478 if Grad :
452479 print ("train_data_theory1.gradients\n train_data_theory2.gradients" )
480+ if energies_atoms_dict_theory1 and energies_atoms_dict_theory2 :
481+ print ("train_data_mace_theory1.xyz\n train_data_mace_theory2.xyz" )
453482 print ("Number of user-chosen snapshots:" , num_snapshots )
454483 print ("Number of successfully generated datapoints:" , len (energies ))
455484
@@ -842,4 +871,4 @@ def coulomb_matrix(fragment):
842871 else :
843872 # avoid divide-by-zero
844873 C [i , j ] = Z [i ] * Z [j ] / D [i , j ]
845- return C
874+ return C
0 commit comments