Coverage for sparc/sparc_parsers/pseudopotential.py: 100%

92 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-12 01:13 +0000

1"""Provide a simple parser for pseudo potentials 

2 

3The psp8 format is defined in abinit manual 

4https://docs.abinit.org/developers/psp8_info/ 

5 

6The first 

7 

8""" 

9 

10import os 

11import re 

12import shutil 

13from pathlib import Path 

14from warnings import warn 

15 

16import numpy as np 

17from ase.data import atomic_names, chemical_symbols 

18 

19 

20class NotPSP8Format(Exception): 

21 def __init__(self, message): 

22 self.message = message 

23 

24 

25class NoMatchingPseudopotential(FileNotFoundError): 

26 def __init__(self, message): 

27 self.message = message 

28 

29 

30class MultiplePseudoPotentialFiles(Exception): 

31 def __init__(self, message): 

32 self.message = message 

33 

34 

35def parse_psp8_header(text): 

36 """Parse the first 4 lines of psp8 text 

37 if parsing failed, raise exception 

38 """ 

39 header = text.split("\n")[:4] 

40 # Line 1 

41 

42 psp8_magic = r"^\s*(?P<symbol>[\w]+)\s+ONCVPSP-(?P<psp8ver>[\d.\w]+)\s+r\_core=(?P<r_core>.*?)$" 

43 psp8_data = {} 

44 match = re.search(psp8_magic, header[0]) 

45 if match is None: 

46 raise NotPSP8Format(f"The pseudopotential file is not in PSP8 format!") 

47 mgroup = match.groupdict() 

48 psp8_data["symbol"] = mgroup["symbol"].strip() 

49 psp8_data["psp8ver"] = mgroup["psp8ver"].strip() 

50 psp8_data["r_core"] = np.fromstring(mgroup["r_core"].strip(), sep=" ", dtype=float) 

51 # Line 2 

52 zatom, zion, pspd, *_ = header[1].split() 

53 psp8_data["zatom"] = float(zatom) 

54 psp8_data["zion"] = float(zion) 

55 # TODO: should we make date in datetime object? 

56 psp8_data["pspd"] = str(pspd).strip() 

57 

58 # Line 3 

59 pspcod, pspxc, lmax, lloc, mmax, r2well, *_ = header[2].split() 

60 psp8_data["pspcod"] = int(pspcod) 

61 psp8_data["pspxc"] = int(pspxc) 

62 psp8_data["lmax"] = int(lmax) 

63 psp8_data["lloc"] = int(lloc) 

64 psp8_data["mmax"] = int(mmax) 

65 psp8_data["r2well"] = int(r2well) 

66 

67 # Line 4 

68 rchrg, fchrg, qchrg, *_ = header[3].split() 

69 psp8_data["rchrg"] = float(rchrg) 

70 psp8_data["fchrg"] = float(fchrg) 

71 psp8_data["qchrg"] = float(qchrg) 

72 

73 # Sanity check the symbol and zatom 

74 int_zatom = int(psp8_data["zatom"]) 

75 if chemical_symbols[int_zatom] != psp8_data["symbol"]: 

76 raise NotPSP8Format( 

77 ( 

78 f"The symbol defined in pseudo potential {psp8_data['symbol']} does not match " 

79 f"the Z={int_zatom}!" 

80 ) 

81 ) 

82 return psp8_data 

83 

84 

85def infer_pseudo_path(symbol, search_path): 

86 """Given an element symbol like 'Na', get the file name 

87 of the search_path (resolved) that search through the search path 

88 

89 TODO: shall we support multiple directories? 

90 TODO: add a `setup` option like VASP? 

91 """ 

92 search_path = Path(search_path).resolve() 

93 potfiles = ( 

94 list(search_path.glob("*.psp8")) 

95 + list(search_path.glob("*.psp")) 

96 + list(search_path.glob("*.pot")) 

97 ) 

98 candidates = [] 

99 for pf in potfiles: 

100 try: 

101 psp8_data = parse_psp8_header(open(pf, "r").read()) 

102 except Exception as e: 

103 print(e) 

104 psp8_data = None 

105 

106 if psp8_data: 

107 if psp8_data["symbol"] == symbol: 

108 candidates.append(pf) 

109 if len(candidates) == 0: 

110 raise NoMatchingPseudopotential( 

111 ( 

112 f"No pseudopotential file for {symbol} found " 

113 "under the search path {search_path}!" 

114 ) 

115 ) 

116 elif len(candidates) > 1: 

117 msg = ( 

118 f"There are multiple psp8 files for {symbol}:\n" 

119 f"{candidates}. Please select the desired pseudopotential file!" 

120 ) 

121 raise MultiplePseudoPotentialFiles(msg) 

122 

123 else: 

124 return candidates[0] 

125 

126 

127def copy_psp_file(source_pot, target_dir, use_symbol=False): 

128 """Copy the pseudo potential file `source_pot` under `target_dir` 

129 

130 if use_symbol is True, rename the potential to '{symbol}.psp8' 

131 

132 the function returns the name of the pseudo potential file 

133 """ 

134 

135 source_pot = Path(source_pot) 

136 target_dir = Path(target_dir) 

137 psp8_data = parse_psp8_header(open(source_pot, "r").read()) 

138 symbol = psp8_data["symbol"] 

139 if use_symbol: 

140 potname = f"{symbol}.psp8" 

141 else: 

142 potname = source_pot.name 

143 

144 target_pot = target_dir / potname 

145 # shutil will copy 

146 shutil.copy(source_pot, target_pot) 

147 return potname 

148 

149 

150def find_pseudo_path(symbol, search_path=None, pseudopotential_mapping={}): 

151 """Get the pseudo potential file at best effort 

152 

153 Searching priorities 

154 1) if pseudopotential_mapping has symbol as key, use the file name 

155 There are two possibilities 

156 i) filename does not contain directory information: i.e. Na-pbe.pot 

157 use search_path / filename for the mapping 

158 ii) filename contains directory information, directly use the file name 

159 2) No pseudopotential_mapping is given, get the psp from search_path 

160 """ 

161 mapping_psp = pseudopotential_mapping.get(symbol, None) 

162 if mapping_psp is None: 

163 if search_path is None: 

164 raise NoMatchingPseudopotential( 

165 ( 

166 f"No psudopotentials found for {symbol} " 

167 "because neither search_path nor psp name is provided." 

168 ) 

169 ) 

170 return infer_pseudo_path(symbol, search_path) 

171 else: 

172 str_psp = str(mapping_psp) 

173 mapping_psp = Path(mapping_psp) 

174 # if psp contains any path information (/, \\), treat is as a direct file 

175 is_node_file_name = (mapping_psp.name == str_psp) and (os.sep not in str_psp) 

176 if is_node_file_name: 

177 if search_path is None: 

178 raise NoMatchingPseudopotential( 

179 ( 

180 f"You provide the pseudopotential name {mapping_psp} but no search path is defined. I cannot locate the pseudopotential file." 

181 ) 

182 ) 

183 mapping_psp = Path(search_path) / str_psp 

184 

185 if not mapping_psp.is_file(): 

186 warn( 

187 ( 

188 f"Pseudopotential file {mapping_psp} is defined by user input but cannot be found!\n" 

189 "Please check your setup. I'll write the .ion file anyway." 

190 ) 

191 ) 

192 return mapping_psp