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
« prev ^ index » next coverage.py v7.5.3, created at 2025-03-06 04:00 +0000
1from dataclasses import dataclass
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.
14@dataclass
15class CodeMetadata:
16 name: str
17 longname: str
18 modulename: str
19 classname: str
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
27 @classmethod
28 def define_code(cls, name, longname, importpath):
29 modulename, classname = importpath.rsplit('.', 1)
30 return cls(name, longname, modulename, classname)
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()
39 def description(self, indent=''):
40 return '\n'.join(indent + line for line in self._description())
42 def is_legacy_fileio(self):
43 from ase.calculators.calculator import FileIOCalculator
44 return issubclass(self.calculator_class(), FileIOCalculator)
46 def is_generic_fileio(self):
47 from ase.calculators.genericfileio import CalculatorTemplate
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)
53 def is_calculator_oldbase(self):
54 from ase.calculators.calculator import Calculator
55 return issubclass(self.calculator_class(), Calculator)
57 def is_base_calculator(self):
58 from ase.calculators.calculator import BaseCalculator
59 return issubclass(self.calculator_class(), BaseCalculator)
61 def calculator_type(self):
62 cls = self.calculator_class()
64 if self.is_generic_fileio():
65 return 'GenericFileIOCalculator'
67 if self.is_legacy_fileio():
68 return 'FileIOCalculator (legacy)'
70 if self.is_calculator_oldbase():
71 return 'Calculator (legacy base class)'
73 if self.is_base_calculator():
74 return 'Base calculator'
76 return f'BAD: Not a proper calculator (superclasses: {cls.__mro__})'
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')
91 def _config_description(self):
92 from ase.calculators.genericfileio import BadConfiguration
93 from ase.config import cfg
95 parser = cfg.parser
96 if self.name not in parser:
97 yield f'Not configured: No [{self.name}] section in configuration'
98 return
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
111 yield f'Configured by section [{self.name}]:'
112 configvars = vars(profile)
113 for name in sorted(configvars):
114 yield f' {name} = {configvars[name]}'
116 return
119def register_codes():
121 codes = {}
123 def reg(name, *args):
124 code = CodeMetadata.define_code(name, *args)
125 codes[name] = code
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
192codes = register_codes()
195def list_codes(names):
196 from ase.config import cfg
197 cfg.print_header()
198 print()
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()
210if __name__ == '__main__':
211 import sys
212 names = sys.argv[1:]
213 if not names:
214 names = [*codes]
215 list_codes(names)