Coverage for /builds/ase/ase/ase/calculators/octopus.py: 75.93%
54 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
3"""ASE-interface to Octopus.
5Ask Hjorth Larsen <asklarsen@gmail.com>
6Carlos de Armas
8http://tddft.org/programs/octopus/
9"""
11import numpy as np
13from ase.calculators.genericfileio import (
14 BaseProfile,
15 CalculatorTemplate,
16 GenericFileIOCalculator,
17)
18from ase.io.octopus.input import generate_input, process_special_kwargs
19from ase.io.octopus.output import read_eigenvalues_file, read_static_info
22class OctopusIOError(IOError):
23 pass
26class OctopusProfile(BaseProfile):
27 def get_calculator_command(self, inputfile):
28 return []
30 def version(self):
31 import re
32 from subprocess import check_output
33 txt = check_output([*self._split_command, '--version'],
34 encoding='ascii')
35 match = re.match(r'octopus\s*(.+)', txt)
36 # With MPI it prints the line for each rank, but we just match
37 # the first line.
38 return match.group(1)
41class OctopusTemplate(CalculatorTemplate):
42 _label = 'octopus'
44 def __init__(self):
45 super().__init__(
46 'octopus',
47 implemented_properties=['energy', 'forces', 'dipole', 'stress'],
48 )
49 self.outputname = f'{self._label}.out'
50 self.errorname = f'{self._label}.err'
52 def read_results(self, directory):
53 """Read octopus output files and extract data."""
54 results = {}
55 with open(directory / 'static/info') as fd:
56 results.update(read_static_info(fd))
58 # If the eigenvalues file exists, we get the eigs/occs from that one.
59 # This probably means someone ran Octopus in 'unocc' mode to
60 # get eigenvalues (e.g. for band structures), and the values in
61 # static/info will be the old (selfconsistent) ones.
62 eigpath = directory / 'static/eigenvalues'
63 if eigpath.is_file():
64 with open(eigpath) as fd:
65 kpts, eigs, occs = read_eigenvalues_file(fd)
66 kpt_weights = np.ones(len(kpts)) # XXX ? Or 1 / len(kpts) ?
67 # XXX New Octopus probably has symmetry reduction !!
68 results.update(eigenvalues=eigs, occupations=occs,
69 ibz_kpoints=kpts,
70 kpoint_weights=kpt_weights)
71 return results
73 def execute(self, directory, profile):
74 profile.run(directory, None, self.outputname,
75 errorfile=self.errorname)
77 def write_input(self, profile, directory, atoms, parameters, properties):
78 txt = generate_input(atoms, process_special_kwargs(atoms, parameters))
79 inp = directory / 'inp'
80 inp.write_text(txt)
82 def load_profile(self, cfg, **kwargs):
83 return OctopusProfile.from_config(cfg, self.name, **kwargs)
86class Octopus(GenericFileIOCalculator):
87 """Octopus calculator.
89 The label is always assumed to be a directory."""
91 def __init__(self, profile=None, directory='.', **kwargs):
92 """Create Octopus calculator.
94 Label is always taken as a subdirectory.
95 Restart is taken to be a label."""
97 super().__init__(profile=profile,
98 template=OctopusTemplate(),
99 directory=directory,
100 parameters=kwargs)
102 @classmethod
103 def recipe(cls, **kwargs):
104 from ase import Atoms
105 system = Atoms()
106 calc = Octopus(CalculationMode='recipe', **kwargs)
107 system.calc = calc
108 try:
109 system.get_potential_energy()
110 except OctopusIOError:
111 pass
112 else:
113 raise OctopusIOError('Expected recipe, but found '
114 'useful physical output!')