Coverage for /builds/hweiske/ase/ase/calculators/octopus.py: 77.19%
57 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-22 11:22 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-22 11:22 +0000
1"""ASE-interface to Octopus.
3Ask Hjorth Larsen <asklarsen@gmail.com>
4Carlos de Armas
6http://tddft.org/programs/octopus/
7"""
9import numpy as np
11from ase.calculators.genericfileio import (BaseProfile, CalculatorTemplate,
12 GenericFileIOCalculator)
13from ase.io.octopus.input import generate_input, process_special_kwargs
14from ase.io.octopus.output import read_eigenvalues_file, read_static_info
17class OctopusIOError(IOError):
18 pass
21class OctopusProfile(BaseProfile):
22 def __init__(self, binary, **kwargs):
23 super().__init__(**kwargs)
24 self.binary = binary
26 def get_calculator_command(self, inputfile):
27 return [self.binary]
29 def version(self):
30 import re
31 from subprocess import check_output
32 txt = check_output([self.binary, '--version'], encoding='ascii')
33 match = re.match(r'octopus\s*(.+)', txt)
34 # With MPI it prints the line for each rank, but we just match
35 # the first line.
36 return match.group(1)
39class OctopusTemplate(CalculatorTemplate):
40 _label = 'octopus'
42 def __init__(self):
43 super().__init__(
44 'octopus',
45 implemented_properties=['energy', 'forces', 'dipole', 'stress'],
46 )
47 self.outputname = f'{self._label}.out'
48 self.errorname = f'{self._label}.err'
50 def read_results(self, directory):
51 """Read octopus output files and extract data."""
52 results = {}
53 with open(directory / 'static/info') as fd:
54 results.update(read_static_info(fd))
56 # If the eigenvalues file exists, we get the eigs/occs from that one.
57 # This probably means someone ran Octopus in 'unocc' mode to
58 # get eigenvalues (e.g. for band structures), and the values in
59 # static/info will be the old (selfconsistent) ones.
60 eigpath = directory / 'static/eigenvalues'
61 if eigpath.is_file():
62 with open(eigpath) as fd:
63 kpts, eigs, occs = read_eigenvalues_file(fd)
64 kpt_weights = np.ones(len(kpts)) # XXX ? Or 1 / len(kpts) ?
65 # XXX New Octopus probably has symmetry reduction !!
66 results.update(eigenvalues=eigs, occupations=occs,
67 ibz_k_points=kpts,
68 k_point_weights=kpt_weights)
69 return results
71 def execute(self, directory, profile):
72 profile.run(directory, None, self.outputname,
73 errorfile=self.errorname)
75 def write_input(self, profile, directory, atoms, parameters, properties):
76 txt = generate_input(atoms, process_special_kwargs(atoms, parameters))
77 inp = directory / 'inp'
78 inp.write_text(txt)
80 def load_profile(self, cfg, **kwargs):
81 return OctopusProfile.from_config(cfg, self.name, **kwargs)
84class Octopus(GenericFileIOCalculator):
85 """Octopus calculator.
87 The label is always assumed to be a directory."""
89 def __init__(self,
90 profile=None,
91 directory='.',
92 parallel_info=None,
93 parallel=True,
94 **kwargs):
95 """Create Octopus calculator.
97 Label is always taken as a subdirectory.
98 Restart is taken to be a label."""
100 super().__init__(profile=profile,
101 template=OctopusTemplate(),
102 directory=directory,
103 parameters=kwargs,
104 parallel_info=parallel_info,
105 parallel=parallel)
107 @classmethod
108 def recipe(cls, **kwargs):
109 from ase import Atoms
110 system = Atoms()
111 calc = Octopus(CalculationMode='recipe', **kwargs)
112 system.calc = calc
113 try:
114 system.get_potential_energy()
115 except OctopusIOError:
116 pass
117 else:
118 raise OctopusIOError('Expected recipe, but found '
119 'useful physical output!')