Coverage for /builds/ase/ase/ase/codes.py: 76.09%
138 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3from dataclasses import dataclass
5# Note: There could be more than one "calculator" for any given code;
6# for example Espresso can work both as GenericFileIOCalculator and
7# SocketIOCalculator, or as part of some DFTD3 combination.
8#
9# Also, DFTD3 is one external code but can be invoked alone (as PureDFTD3)
10# as well as together with a DFT code (the main DFTD3 calculator).
11#
12# The current CodeMetadata object only specifies a single calculator class.
13# We should be wary of these invisible "one-to-one" restrictions.
16@dataclass
17class CodeMetadata:
18 name: str
19 longname: str
20 modulename: str
21 classname: str
23 def calculator_class(self):
24 from importlib import import_module
25 module = import_module(self.modulename)
26 cls = getattr(module, self.classname)
27 return cls
29 @classmethod
30 def define_code(cls, name, longname, importpath):
31 modulename, classname = importpath.rsplit('.', 1)
32 return cls(name, longname, modulename, classname)
34 def _description(self):
35 yield f'Name: {self.longname}'
36 yield f'Import: {self.modulename}.{self.classname}'
37 yield f'Type: {self.calculator_type()}'
38 yield ''
39 yield from self._config_description()
41 def description(self, indent=''):
42 return '\n'.join(indent + line for line in self._description())
44 def is_legacy_fileio(self):
45 from ase.calculators.calculator import FileIOCalculator
46 return issubclass(self.calculator_class(), FileIOCalculator)
48 def is_generic_fileio(self):
49 from ase.calculators.genericfileio import CalculatorTemplate
51 # It is nicer to check for the template class, since it has the name,
52 # but then calculator_class() should be renamed.
53 return issubclass(self.calculator_class(), CalculatorTemplate)
55 def is_calculator_oldbase(self):
56 from ase.calculators.calculator import Calculator
57 return issubclass(self.calculator_class(), Calculator)
59 def is_base_calculator(self):
60 from ase.calculators.calculator import BaseCalculator
61 return issubclass(self.calculator_class(), BaseCalculator)
63 def calculator_type(self):
64 cls = self.calculator_class()
66 if self.is_generic_fileio():
67 return 'GenericFileIOCalculator'
69 if self.is_legacy_fileio():
70 return 'FileIOCalculator (legacy)'
72 if self.is_calculator_oldbase():
73 return 'Calculator (legacy base class)'
75 if self.is_base_calculator():
76 return 'Base calculator'
78 return f'BAD: Not a proper calculator (superclasses: {cls.__mro__})'
80 def profile(self):
81 from ase.calculators.calculator import FileIOCalculator
82 from ase.calculators.genericfileio import CalculatorTemplate
83 from ase.config import cfg
84 cls = self.calculator_class()
85 if issubclass(cls, CalculatorTemplate):
86 return cls().load_profile(cfg)
87 elif hasattr(cls, 'fileio_rules'):
88 assert issubclass(cls, FileIOCalculator)
89 return cls.load_argv_profile(cfg, self.name)
90 else:
91 raise NotImplementedError('profile() not implemented')
93 def _config_description(self):
94 from ase.calculators.genericfileio import BadConfiguration
95 from ase.config import cfg
97 parser = cfg.parser
98 if self.name not in parser:
99 yield f'Not configured: No [{self.name}] section in configuration'
100 return
102 try:
103 profile = self.profile()
104 except BadConfiguration as ex:
105 yield f'Error in configuration section [{self.name}]'
106 yield 'Missing or bad parameters:'
107 yield f' {ex}'
108 return
109 except NotImplementedError as ex:
110 yield f'N/A: {ex}'
111 return
113 yield f'Configured by section [{self.name}]:'
114 configvars = vars(profile)
115 for name in sorted(configvars):
116 yield f' {name} = {configvars[name]}'
118 return
121def register_codes():
123 codes = {}
125 def reg(name, *args):
126 code = CodeMetadata.define_code(name, *args)
127 codes[name] = code
129 reg('abinit', 'Abinit', 'ase.calculators.abinit.AbinitTemplate')
130 reg('ace', 'ACE molecule', 'ase.calculators.acemolecule.ACE')
131 # internal: reg('acn', 'ACN force field', 'ase.calculators.acn.ACN')
132 reg('aims', 'FHI-Aims', 'ase.calculators.aims.AimsTemplate')
133 reg('amber', 'Amber', 'ase.calculators.amber.Amber')
134 reg('castep', 'Castep', 'ase.calculators.castep.Castep')
135 # internal: combine_mm
136 # internal: counterions
137 reg('cp2k', 'CP2K', 'ase.calculators.cp2k.CP2K')
138 reg('crystal', 'CRYSTAL', 'ase.calculators.crystal.CRYSTAL')
139 reg('demon', 'deMon', 'ase.calculators.demon.Demon')
140 reg('demonnano', 'deMon-nano', 'ase.calculators.demonnano.DemonNano')
141 reg('dftb', 'DFTB+', 'ase.calculators.dftb.Dftb')
142 reg('dftd3', 'DFT-D3', 'ase.calculators.dftd3.DFTD3')
143 # reg('dftd3-pure', 'DFT-D3 (pure)', 'ase.calculators.dftd3.puredftd3')
144 reg('dmol', 'DMol3', 'ase.calculators.dmol.DMol3')
145 # internal: reg('eam', 'EAM', 'ase.calculators.eam.EAM')
146 reg('elk', 'ELK', 'ase.calculators.elk.ELK')
147 # internal: reg('emt', 'EMT potential', 'ase.calculators.emt.EMT')
148 reg('espresso', 'Quantum Espresso',
149 'ase.calculators.espresso.EspressoTemplate')
150 reg('exciting', 'Exciting',
151 'ase.calculators.exciting.exciting.ExcitingGroundStateTemplate')
152 # internal: reg('ff', 'FF', 'ase.calculators.ff.ForceField')
153 # fleur <- external nowadays
154 reg('gamess_us', 'GAMESS-US', 'ase.calculators.gamess_us.GAMESSUS')
155 reg('gaussian', 'Gaussian', 'ase.calculators.gaussian.Gaussian')
156 reg('gromacs', 'Gromacs', 'ase.calculators.gromacs.Gromacs')
157 reg('gulp', 'GULP', 'ase.calculators.gulp.GULP')
158 # h2morse.py do we need a specific H2 morse calculator when we have morse??
159 # internal: reg('harmonic', 'Harmonic potential',
160 # 'ase.calculators.harmonic.HarmonicCalculator')
161 # internal: reg('idealgas', 'Ideal gas (dummy)',
162 # 'ase.calculators.idealgas.IdealGas')
163 # XXX cannot import without kimpy installed, fixme:
164 # reg('kim', 'OpenKIM', 'ase.calculators.kim.kim.KIM')
165 reg('lammpslib', 'Lammps (python library)',
166 'ase.calculators.lammpslib.LAMMPSlib')
167 reg('lammpsrun', 'Lammps (external)', 'ase.calculators.lammpsrun.LAMMPS')
168 # internal: reg('lj', 'Lennard–Jones potential',
169 # 'ase.calculators.lj.LennardJones')
170 # internal: loggingcalc.py
171 # internal: mixing.py
172 reg('mopac', 'MOPAC', 'ase.calculators.mopac.MOPAC')
173 # internal: reg('morse', 'Morse potential',
174 # 'ase.calculators.morse.MorsePotential')
175 reg('nwchem', 'NWChem', 'ase.calculators.nwchem.NWChem')
176 reg('octopus', 'Octopus', 'ase.calculators.octopus.OctopusTemplate')
177 reg('onetep', 'Onetep', 'ase.calculators.onetep.OnetepTemplate')
178 reg('openmx', 'OpenMX', 'ase.calculators.openmx.OpenMX')
179 reg('orca', 'ORCA', 'ase.calculators.orca.OrcaTemplate')
180 reg('plumed', 'Plumed', 'ase.calculators.plumed.Plumed')
181 reg('psi4', 'Psi4', 'ase.calculators.psi4.Psi4')
182 reg('qchem', 'QChem', 'ase.calculators.qchem.QChem')
183 # internal: qmmm.py
184 reg('siesta', 'SIESTA', 'ase.calculators.siesta.Siesta')
185 # internal: test.py
186 # internal: reg('tip3p', 'TIP3P', 'ase.calculators.tip3p.TIP3P')
187 # internal: reg('tip4p', 'TIP4P', 'ase.calculators.tip4p.TIP4P')
188 reg('turbomole', 'Turbomole', 'ase.calculators.turbomole.Turbomole')
189 reg('vasp', 'VASP', 'ase.calculators.vasp.Vasp')
190 # internal: vdwcorrection
191 return codes
194codes = register_codes()
197def list_codes(names):
198 from ase.config import cfg
199 cfg.print_header()
200 print()
202 for name in names:
203 code = codes[name]
204 print(code.name)
205 try:
206 print(code.description(indent=' '))
207 except Exception as ex:
208 print(f'Bad configuration of {name}: {ex!r}')
209 print()
212if __name__ == '__main__':
213 import sys
214 names = sys.argv[1:]
215 if not names:
216 names = [*codes]
217 list_codes(names)