Skip to content

Commit d1c9536

Browse files
authored
Fix: implement special treatment in abacuslite I/O on the SCF unconverged case (#7195)
1 parent f814048 commit d1c9536

2 files changed

Lines changed: 121 additions & 8 deletions

File tree

interfaces/ASE_interface/abacuslite/io/latestio.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import shutil
44
import unittest
55
from pathlib import Path
6-
from typing import Dict, List, Optional
6+
from typing import Dict, List, Optional, Tuple
77

88
import numpy as np
99
from ase.atoms import Atoms
@@ -22,6 +22,7 @@
2222
read_traj_from_md_dump,
2323
read_magmom_from_running_log,
2424
is_invalid_arr,
25+
find_final_info_with_iter_header
2526
)
2627

2728
def read_esolver_type_from_running_log(src: str | Path | List[str]) \
@@ -359,6 +360,30 @@ def read_stress_from_running_log(src: str | Path | List[str]) \
359360

360361
return stresses
361362

363+
def read_iter_header_from_running_log(src: str | Path | List[str]) \
364+
-> List[Tuple[int, int]]:
365+
'''
366+
read the "iteration header" from the running log, useful for determining
367+
which is the final iteration. The "iteration header" is defined as:
368+
```
369+
--> #ION MOVE# 1 #ELEC ITER# 3
370+
```
371+
'''
372+
if isinstance(src, (str, Path)):
373+
with open(src) as f:
374+
raw = f.readlines()
375+
else: # assume the src is the return of the readlines()
376+
raw = src
377+
# with open(fn) as f:
378+
# raw = f.readlines()
379+
raw = [l.strip() for l in raw]
380+
raw = [l for l in raw if l] # remove empty lines
381+
382+
HEADER_PAT = r'-->\s+#ION MOVE#\s+(\d+)\s+#ELEC ITER#\s+(\d+)'
383+
res = [re.findall(HEADER_PAT, l) for l in raw]
384+
385+
return [(int(pack[0][0]), int(pack[0][1])) for pack in res if pack]
386+
362387
def read_abacus_out(fileobj,
363388
index=slice(None),
364389
results_required=True,
@@ -439,9 +464,11 @@ def read_abacus_out(fileobj,
439464
# the simulation, which is, not exactly to be true for the NPT-MD
440465
# runs.
441466

442-
_, energies = read_energies_from_running_log(abacus_lines)
443-
# only keep the SCF converged energies
444-
energies = [edct for edct in energies if 'E_KS(sigma->0)' in edct]
467+
# only keep the energies of the final iteration for each ion step
468+
energies = find_final_info_with_iter_header(
469+
read_energies_from_running_log(abacus_lines)[1],
470+
read_iter_header_from_running_log(abacus_lines)
471+
)
445472

446473
# read the magmom
447474
magmom = read_magmom_from_running_log(abacus_lines)
@@ -639,5 +666,12 @@ def test_read_pw_symm0_nspin4_gamma_md(self):
639666
# (self.testfiles / 'eig_occ.txt').unlink()
640667
(self.testfiles / 'MD_dump').unlink()
641668

669+
def test_read_iter_header_from_running_log(self):
670+
header = read_iter_header_from_running_log(
671+
self.testfiles / 'lcao-symm0-nspin2-multik-cellrelax')
672+
self.assertTupleEqual(header[0], (1, 1))
673+
self.assertTupleEqual(header[1], (1, 2))
674+
self.assertTupleEqual(header[2], (2, 1))
675+
642676
if __name__ == '__main__':
643677
unittest.main()

interfaces/ASE_interface/abacuslite/io/legacyio.py

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import unittest
44
from io import TextIOWrapper
55
from pathlib import Path
6-
from typing import Dict, List, Tuple, Optional
6+
from typing import Dict, List, Tuple, Optional, Any
77

88
import numpy as np
99
from ase.atoms import Atoms
@@ -706,6 +706,63 @@ def read_magmom_from_running_log(src: str | Path | List[str]) \
706706

707707
return magmom
708708

709+
def read_iter_header_from_running_log(src: str | Path | List[str]) \
710+
-> List[Tuple[int, int]]:
711+
'''
712+
read the "iteration header" from the running log, useful for determining
713+
which is the final iteration. The "iteration header" is defined as:
714+
```
715+
LCAO ALGORITHM --------------- ION= 1 ELEC= 100---------------------
716+
```
717+
or for PW:
718+
```
719+
PW ALGORITHM --------------- ION=2 ELEC=1 -----------------------
720+
```
721+
'''
722+
if isinstance(src, (str, Path)):
723+
with open(src) as f:
724+
raw = f.readlines()
725+
else: # assume the src is the return of the readlines()
726+
raw = src
727+
# with open(fn) as f:
728+
# raw = f.readlines()
729+
raw = [l.strip() for l in raw]
730+
raw = [l for l in raw if l] # remove empty lines
731+
732+
HEADER_PAT = r'(LCAO|PW)\s+ALGORITHM\s+-+\s+ION=\s+([0-9]+)\s+ELEC=\s+([0-9]+)'
733+
res = [re.findall(HEADER_PAT, l) for l in raw]
734+
735+
return [(int(pack[0][1]), int(pack[0][2])) for pack in res if pack]
736+
737+
def find_final_info_with_iter_header(
738+
info: List[Any],
739+
headers: List[Tuple[int, int]]
740+
) -> List[Any]:
741+
'''find the energies of the final iteration
742+
743+
Parameters
744+
----------
745+
info: List[Dict[str, float]]
746+
The information that is printed after each iteration.
747+
headers: List[Tuple[int, int]]
748+
The "iteration header" returned from the function
749+
`read_iter_header_from_running_log`
750+
751+
Returns
752+
-------
753+
List[Any]
754+
The information of the final iteration.
755+
'''
756+
headers = np.array(headers) # because indices should start from 0
757+
assert headers.ndim == 2
758+
assert headers.shape[1] == 2
759+
760+
ion = headers[:, 0].flatten()
761+
ion, nelec = np.unique(ion, return_counts=True)
762+
ielec = np.cumsum(nelec) - 1
763+
764+
return [info[i] for i in ielec]
765+
709766
def is_invalid_arr(arr) -> bool:
710767
'''Check if the array is invalid, including the cases of None,
711768
empty array, and array with NaN values.
@@ -797,9 +854,11 @@ def read_abacus_out(fileobj,
797854
# the simulation, which is, not exactly to be true for the NPT-MD
798855
# runs.
799856

800-
_, energies = read_energies_from_running_log(abacus_lines)
801-
# only keep the SCF converged energies
802-
energies = [edct for edct in energies if 'E_KS(sigma->0)' in edct]
857+
# only keep the energies of the final iteration for each ion step
858+
energies = find_final_info_with_iter_header(
859+
read_energies_from_running_log(abacus_lines)[1],
860+
read_iter_header_from_running_log(abacus_lines)
861+
)
803862

804863
# read the magmom
805864
magmom = read_magmom_from_running_log(abacus_lines)
@@ -1107,5 +1166,25 @@ def test_read_magmom_from_running_log(self):
11071166
np.array([[0. , 0. , 3.62032142],
11081167
[0. , 0. , 3.62032142]])))
11091168

1169+
def test_read_iter_header_from_running_log(self):
1170+
fn = self.testfiles / 'lcao-symm0-nspin2-multik-cellrelax_'
1171+
header = read_iter_header_from_running_log(fn)
1172+
self.assertEqual(header[0], (1,1))
1173+
self.assertEqual(header[1], (1,2))
1174+
self.assertEqual(header[2], (2,1))
1175+
fn = self.testfiles / 'pw-symm0-nspin4-gamma-md_'
1176+
header = read_iter_header_from_running_log(fn)
1177+
self.assertEqual(header[0], (1,1))
1178+
self.assertEqual(header[1], (1,2))
1179+
self.assertEqual(header[2], (1,3))
1180+
self.assertEqual(header[3], (3,2))
1181+
self.assertEqual(header[4], (3,3))
1182+
1183+
def test_find_final_info_with_iter_header(self):
1184+
info = [1, 2, 3, 4, 5, 6]
1185+
header = [[1,1], [1,2], [2,1], [3,1], [3,2], [3,3]]
1186+
final_info = find_final_info_with_iter_header(info, header)
1187+
self.assertListEqual(final_info, [info[1], info[2], info[5]])
1188+
11101189
if __name__ == '__main__':
11111190
unittest.main()

0 commit comments

Comments
 (0)