Coverage for /builds/ase/ase/ase/calculators/nwchem.py: 71.74%

46 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-08-02 00:12 +0000

1# fmt: off 

2 

3"""This module defines an ASE interface to NWchem 

4 

5https://nwchemgit.github.io 

6""" 

7import os 

8from pathlib import Path 

9 

10import numpy as np 

11 

12from ase import io 

13from ase.calculators.calculator import FileIOCalculator 

14from ase.spectrum.band_structure import BandStructure 

15from ase.units import Hartree 

16 

17 

18class NWChem(FileIOCalculator): 

19 implemented_properties = ['energy', 'free_energy', 

20 'forces', 'stress', 'dipole'] 

21 _legacy_default_command = 'nwchem PREFIX.nwi > PREFIX.nwo' 

22 accepts_bandpath_keyword = True 

23 discard_results_on_any_change = True 

24 

25 fileio_rules = FileIOCalculator.ruleset( 

26 extend_argv=['{prefix}.nwi'], 

27 stdout_name='{prefix}.nwo') 

28 

29 def __init__(self, restart=None, 

30 ignore_bad_restart_file=FileIOCalculator._deprecated, 

31 label='nwchem', atoms=None, command=None, **kwargs): 

32 """ 

33 NWChem keywords are specified using (potentially nested) 

34 dictionaries. Consider the following input file block:: 

35 

36 dft 

37 odft 

38 mult 2 

39 convergence energy 1e-9 density 1e-7 gradient 5e-6 

40 end 

41 

42 This can be generated by the NWChem calculator by using the 

43 following settings: 

44 >>> from ase.calculators.nwchem import NWChem 

45 >>> calc = NWChem(dft={'odft': None, 

46 ... 'mult': 2, 

47 ... 'convergence': {'energy': 1e-9, 

48 ... 'density': 1e-7, 

49 ... 'gradient': 5e-6, 

50 ... }, 

51 ... }, 

52 ... ) 

53 

54 In addition, the calculator supports several special keywords: 

55 

56 theory: str 

57 Which NWChem module should be used to calculate the 

58 energies and forces. Supported values are ``'dft'``, 

59 ``'scf'``, ``'mp2'``, ``'ccsd'``, ``'tce'``, ``'tddft'``, 

60 ``'pspw'``, ``'band'``, and ``'paw'``. If not provided, the 

61 calculator will attempt to guess which theory to use based 

62 on the keywords provided by the user. 

63 xc: str 

64 The exchange-correlation functional to use. Only relevant 

65 for DFT calculations. 

66 task: str 

67 What type of calculation is to be performed, e.g. 

68 ``'energy'``, ``'gradient'``, ``'optimize'``, etc. When 

69 using ``'SocketIOCalculator'``, ``task`` should be set 

70 to ``'optimize'``. In most other circumstances, ``task`` 

71 should not be set manually. 

72 basis: str or dict 

73 Which basis set to use for gaussian-type orbital 

74 calculations. Set to a string to use the same basis for all 

75 elements. To use a different basis for different elements, 

76 provide a dict of the form: 

77 

78 >>> calc = NWChem(..., 

79 ... basis={'O': '3-21G', 

80 ... 'Si': '6-31g'}) 

81 

82 basispar: str 

83 Additional keywords to put in the NWChem ``basis`` block, 

84 e.g. ``'rel'`` for relativistic bases. 

85 symmetry: int or str 

86 The point group (for gaussian-type orbital calculations) or 

87 space group (for plane-wave calculations) of the system. 

88 Supports both group names (e.g. ``'c2v'``, ``'Fm3m'``) and 

89 numbers (e.g. ``225``). 

90 autosym: bool 

91 Whether NWChem should automatically determine the symmetry 

92 of the structure (defaults to ``False``). 

93 center: bool 

94 Whether NWChem should automatically center the structure 

95 (defaults to ``False``). Enable at your own risk. 

96 autoz: bool 

97 Whether NWChem should automatically construct a Z-matrix 

98 for your molecular system (defaults to ``False``). 

99 geompar: str 

100 Additional keywords to put in the NWChem `geometry` block, 

101 e.g. ``'nucleus finite'`` for gaussian-shaped nuclear 

102 charges. Do not set ``'autosym'``, ``'center'``, or 

103 ``'autoz'`` in this way; instead, use the appropriate 

104 keyword described above for these settings. 

105 set: dict 

106 Used to manually create or modify entries in the NWChem 

107 rtdb. For example, the following settings enable 

108 pseudopotential filtering for plane-wave calculations:: 

109 

110 set nwpw:kbpp_ray .true. 

111 set nwpw:kbpp_filter .true. 

112 

113 These settings are generated by the NWChem calculator by 

114 passing the arguments: 

115 

116 >>> calc = NWChem(..., 

117 >>> set={'nwpw:kbpp_ray': True, 

118 >>> 'nwpw:kbpp_filter': True}) 

119 

120 kpts: (int, int, int), or dict 

121 Indicates which k-point mesh to use. Supported syntax is 

122 similar to that of GPAW. Implies ``theory='band'``. 

123 bandpath: BandPath object 

124 The band path to use for a band structure calculation. 

125 Implies ``theory='band'``. 

126 pretasks: list of dict 

127 Tasks used to produce a better initial guess 

128 for the wavefunction. 

129 These task typically use a cheaper level of theory 

130 or smaller basis set (but not both). 

131 The output energy and forces should remain unchanged 

132 regardless of the number of tasks or their parameters, 

133 but the runtime may be significantly improved. 

134 

135 For example, a MP2 calculation preceded by guesses at the 

136 DFT and HF levels would be 

137 

138 >>> calc = NWChem(theory='mp2', basis='aug-cc-pvdz', 

139 >>> pretasks=[ 

140 >>> {'dft': {'xc': 'hfexch'}, 

141 >>> 'set': {'lindep:n_dep': 0}}, 

142 >>> {'theory': 'scf', 'set': {'lindep:n_dep': 0}}, 

143 >>> ]) 

144 

145 Each dictionary could contain any of the other parameters, 

146 except those which pertain to global configurations 

147 (e.g., geometry details, scratch dir). 

148 

149 The default basis set is that of the final step in the calculation, 

150 or that of the previous step that which defines a basis set. 

151 For example, all steps in the example will use aug-cc-pvdz 

152 because the last step is the only one which defines a basis. 

153 

154 Steps which change basis set must use the same theory. 

155 The following specification would perform SCF using the 3-21G 

156 basis set first, then B3LYP//3-21g, and then B3LYP//6-31G(2df,p) 

157 

158 >>> calc = NWChem(theory='dft', xc='b3lyp', basis='6-31g(2df,p)', 

159 >>> pretasks=[ 

160 >>> {'theory': 'scf', 'basis': '3-21g', 

161 >>> 'set': {'lindep:n_dep': 0}}, 

162 >>> {'dft': {'xc': 'b3lyp'}}, 

163 >>> ]) 

164 

165 The :code:`'set': {'lindep:n_dep': 0}` option is highly suggested 

166 as a way to avoid errors relating to symmetry changes between tasks. 

167 

168 The calculator will configure appropriate options for saving 

169 and loading intermediate wavefunctions, and 

170 place an "ignore" task directive between each step so that 

171 convergence errors in intermediate steps do not halt execution. 

172 """ 

173 FileIOCalculator.__init__(self, restart, ignore_bad_restart_file, 

174 label, atoms, command, **kwargs) 

175 if self.prefix is None: 

176 self.prefix = 'nwchem' 

177 self.calc = None 

178 

179 def input_filename(self): 

180 return f'{self.prefix}.nwi' 

181 

182 def output_filename(self): 

183 return f'{self.prefix}.nwo' 

184 

185 def write_input(self, atoms, properties=None, system_changes=None): 

186 FileIOCalculator.write_input(self, atoms, properties, system_changes) 

187 

188 # Prepare perm and scratch directories 

189 perm = os.path.abspath(self.parameters.get('perm', self.label)) 

190 scratch = os.path.abspath(self.parameters.get('scratch', self.label)) 

191 os.makedirs(perm, exist_ok=True) 

192 os.makedirs(scratch, exist_ok=True) 

193 

194 io.write(Path(self.directory) / self.input_filename(), 

195 atoms, properties=properties, 

196 label=self.label, **self.parameters) 

197 

198 def read_results(self): 

199 output = io.read(Path(self.directory) / self.output_filename()) 

200 self.calc = output.calc 

201 self.results = output.calc.results 

202 

203 def band_structure(self): 

204 self.calculate() 

205 perm = self.parameters.get('perm', self.label) 

206 if self.calc.get_spin_polarized(): 

207 alpha = np.loadtxt(os.path.join(perm, self.label + '.alpha_band')) 

208 beta = np.loadtxt(os.path.join(perm, self.label + '.beta_band')) 

209 energies = np.array([alpha[:, 1:], beta[:, 1:]]) * Hartree 

210 else: 

211 data = np.loadtxt(os.path.join(perm, 

212 self.label + '.restricted_band')) 

213 energies = data[np.newaxis, :, 1:] * Hartree 

214 eref = self.calc.get_fermi_level() 

215 if eref is None: 

216 eref = 0. 

217 return BandStructure(self.parameters.bandpath, energies, eref)