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