Coverage for /builds/debichem-team/python-ase/ase/codes.py: 76.09%

138 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-03-06 04:00 +0000

1from dataclasses import dataclass 

2 

3# Note: There could be more than one "calculator" for any given code; 

4# for example Espresso can work both as GenericFileIOCalculator and 

5# SocketIOCalculator, or as part of some DFTD3 combination. 

6# 

7# Also, DFTD3 is one external code but can be invoked alone (as PureDFTD3) 

8# as well as together with a DFT code (the main DFTD3 calculator). 

9# 

10# The current CodeMetadata object only specifies a single calculator class. 

11# We should be wary of these invisible "one-to-one" restrictions. 

12 

13 

14@dataclass 

15class CodeMetadata: 

16 name: str 

17 longname: str 

18 modulename: str 

19 classname: str 

20 

21 def calculator_class(self): 

22 from importlib import import_module 

23 module = import_module(self.modulename) 

24 cls = getattr(module, self.classname) 

25 return cls 

26 

27 @classmethod 

28 def define_code(cls, name, longname, importpath): 

29 modulename, classname = importpath.rsplit('.', 1) 

30 return cls(name, longname, modulename, classname) 

31 

32 def _description(self): 

33 yield f'Name: {self.longname}' 

34 yield f'Import: {self.modulename}.{self.classname}' 

35 yield f'Type: {self.calculator_type()}' 

36 yield '' 

37 yield from self._config_description() 

38 

39 def description(self, indent=''): 

40 return '\n'.join(indent + line for line in self._description()) 

41 

42 def is_legacy_fileio(self): 

43 from ase.calculators.calculator import FileIOCalculator 

44 return issubclass(self.calculator_class(), FileIOCalculator) 

45 

46 def is_generic_fileio(self): 

47 from ase.calculators.genericfileio import CalculatorTemplate 

48 

49 # It is nicer to check for the template class, since it has the name, 

50 # but then calculator_class() should be renamed. 

51 return issubclass(self.calculator_class(), CalculatorTemplate) 

52 

53 def is_calculator_oldbase(self): 

54 from ase.calculators.calculator import Calculator 

55 return issubclass(self.calculator_class(), Calculator) 

56 

57 def is_base_calculator(self): 

58 from ase.calculators.calculator import BaseCalculator 

59 return issubclass(self.calculator_class(), BaseCalculator) 

60 

61 def calculator_type(self): 

62 cls = self.calculator_class() 

63 

64 if self.is_generic_fileio(): 

65 return 'GenericFileIOCalculator' 

66 

67 if self.is_legacy_fileio(): 

68 return 'FileIOCalculator (legacy)' 

69 

70 if self.is_calculator_oldbase(): 

71 return 'Calculator (legacy base class)' 

72 

73 if self.is_base_calculator(): 

74 return 'Base calculator' 

75 

76 return f'BAD: Not a proper calculator (superclasses: {cls.__mro__})' 

77 

78 def profile(self): 

79 from ase.calculators.calculator import FileIOCalculator 

80 from ase.calculators.genericfileio import CalculatorTemplate 

81 from ase.config import cfg 

82 cls = self.calculator_class() 

83 if issubclass(cls, CalculatorTemplate): 

84 return cls().load_profile(cfg) 

85 elif hasattr(cls, 'fileio_rules'): 

86 assert issubclass(cls, FileIOCalculator) 

87 return cls.load_argv_profile(cfg, self.name) 

88 else: 

89 raise NotImplementedError('profile() not implemented') 

90 

91 def _config_description(self): 

92 from ase.calculators.genericfileio import BadConfiguration 

93 from ase.config import cfg 

94 

95 parser = cfg.parser 

96 if self.name not in parser: 

97 yield f'Not configured: No [{self.name}] section in configuration' 

98 return 

99 

100 try: 

101 profile = self.profile() 

102 except BadConfiguration as ex: 

103 yield f'Error in configuration section [{self.name}]' 

104 yield 'Missing or bad parameters:' 

105 yield f' {ex}' 

106 return 

107 except NotImplementedError as ex: 

108 yield f'N/A: {ex}' 

109 return 

110 

111 yield f'Configured by section [{self.name}]:' 

112 configvars = vars(profile) 

113 for name in sorted(configvars): 

114 yield f' {name} = {configvars[name]}' 

115 

116 return 

117 

118 

119def register_codes(): 

120 

121 codes = {} 

122 

123 def reg(name, *args): 

124 code = CodeMetadata.define_code(name, *args) 

125 codes[name] = code 

126 

127 reg('abinit', 'Abinit', 'ase.calculators.abinit.AbinitTemplate') 

128 reg('ace', 'ACE molecule', 'ase.calculators.acemolecule.ACE') 

129 # internal: reg('acn', 'ACN force field', 'ase.calculators.acn.ACN') 

130 reg('aims', 'FHI-Aims', 'ase.calculators.aims.AimsTemplate') 

131 reg('amber', 'Amber', 'ase.calculators.amber.Amber') 

132 reg('castep', 'Castep', 'ase.calculators.castep.Castep') 

133 # internal: combine_mm 

134 # internal: counterions 

135 reg('cp2k', 'CP2K', 'ase.calculators.cp2k.CP2K') 

136 reg('crystal', 'CRYSTAL', 'ase.calculators.crystal.CRYSTAL') 

137 reg('demon', 'deMon', 'ase.calculators.demon.Demon') 

138 reg('demonnano', 'deMon-nano', 'ase.calculators.demonnano.DemonNano') 

139 reg('dftb', 'DFTB+', 'ase.calculators.dftb.Dftb') 

140 reg('dftd3', 'DFT-D3', 'ase.calculators.dftd3.DFTD3') 

141 # reg('dftd3-pure', 'DFT-D3 (pure)', 'ase.calculators.dftd3.puredftd3') 

142 reg('dmol', 'DMol3', 'ase.calculators.dmol.DMol3') 

143 # internal: reg('eam', 'EAM', 'ase.calculators.eam.EAM') 

144 reg('elk', 'ELK', 'ase.calculators.elk.ELK') 

145 # internal: reg('emt', 'EMT potential', 'ase.calculators.emt.EMT') 

146 reg('espresso', 'Quantum Espresso', 

147 'ase.calculators.espresso.EspressoTemplate') 

148 reg('exciting', 'Exciting', 

149 'ase.calculators.exciting.exciting.ExcitingGroundStateTemplate') 

150 # internal: reg('ff', 'FF', 'ase.calculators.ff.ForceField') 

151 # fleur <- external nowadays 

152 reg('gamess_us', 'GAMESS-US', 'ase.calculators.gamess_us.GAMESSUS') 

153 reg('gaussian', 'Gaussian', 'ase.calculators.gaussian.Gaussian') 

154 reg('gromacs', 'Gromacs', 'ase.calculators.gromacs.Gromacs') 

155 reg('gulp', 'GULP', 'ase.calculators.gulp.GULP') 

156 # h2morse.py do we need a specific H2 morse calculator when we have morse?? 

157 # internal: reg('harmonic', 'Harmonic potential', 

158 # 'ase.calculators.harmonic.HarmonicCalculator') 

159 # internal: reg('idealgas', 'Ideal gas (dummy)', 

160 # 'ase.calculators.idealgas.IdealGas') 

161 # XXX cannot import without kimpy installed, fixme: 

162 # reg('kim', 'OpenKIM', 'ase.calculators.kim.kim.KIM') 

163 reg('lammpslib', 'Lammps (python library)', 

164 'ase.calculators.lammpslib.LAMMPSlib') 

165 reg('lammpsrun', 'Lammps (external)', 'ase.calculators.lammpsrun.LAMMPS') 

166 # internal: reg('lj', 'Lennard–Jones potential', 

167 # 'ase.calculators.lj.LennardJones') 

168 # internal: loggingcalc.py 

169 # internal: mixing.py 

170 reg('mopac', 'MOPAC', 'ase.calculators.mopac.MOPAC') 

171 # internal: reg('morse', 'Morse potential', 

172 # 'ase.calculators.morse.MorsePotential') 

173 reg('nwchem', 'NWChem', 'ase.calculators.nwchem.NWChem') 

174 reg('octopus', 'Octopus', 'ase.calculators.octopus.OctopusTemplate') 

175 reg('onetep', 'Onetep', 'ase.calculators.onetep.OnetepTemplate') 

176 reg('openmx', 'OpenMX', 'ase.calculators.openmx.OpenMX') 

177 reg('orca', 'ORCA', 'ase.calculators.orca.OrcaTemplate') 

178 reg('plumed', 'Plumed', 'ase.calculators.plumed.Plumed') 

179 reg('psi4', 'Psi4', 'ase.calculators.psi4.Psi4') 

180 reg('qchem', 'QChem', 'ase.calculators.qchem.QChem') 

181 # internal: qmmm.py 

182 reg('siesta', 'SIESTA', 'ase.calculators.siesta.Siesta') 

183 # internal: test.py 

184 # internal: reg('tip3p', 'TIP3P', 'ase.calculators.tip3p.TIP3P') 

185 # internal: reg('tip4p', 'TIP4P', 'ase.calculators.tip4p.TIP4P') 

186 reg('turbomole', 'Turbomole', 'ase.calculators.turbomole.Turbomole') 

187 reg('vasp', 'VASP', 'ase.calculators.vasp.Vasp') 

188 # internal: vdwcorrection 

189 return codes 

190 

191 

192codes = register_codes() 

193 

194 

195def list_codes(names): 

196 from ase.config import cfg 

197 cfg.print_header() 

198 print() 

199 

200 for name in names: 

201 code = codes[name] 

202 print(code.name) 

203 try: 

204 print(code.description(indent=' ')) 

205 except Exception as ex: 

206 print(f'Bad configuration of {name}: {ex!r}') 

207 print() 

208 

209 

210if __name__ == '__main__': 

211 import sys 

212 names = sys.argv[1:] 

213 if not names: 

214 names = [*codes] 

215 list_codes(names)