File: abinit.py

package info (click to toggle)
python-ase 3.24.0-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 15,448 kB
  • sloc: python: 144,945; xml: 2,728; makefile: 113; javascript: 47
file content (142 lines) | stat: -rw-r--r-- 4,138 bytes parent folder | download
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
"""This module defines an ASE interface to ABINIT.

http://www.abinit.org/
"""

from pathlib import Path
from subprocess import check_output

import ase.io.abinit as io
from ase.calculators.genericfileio import (
    BaseProfile,
    CalculatorTemplate,
    GenericFileIOCalculator,
)


class AbinitProfile(BaseProfile):
    configvars = {'pp_paths'}

    def __init__(self, command, *, pp_paths=None, **kwargs):
        super().__init__(command, **kwargs)
        # XXX pp_paths is a raw configstring when it gets here.
        # All the config stuff should have been loaded somehow by now,
        # so this should be refactored.
        if isinstance(pp_paths, str):
            pp_paths = [path for path in pp_paths.splitlines() if path]
        if pp_paths is None:
            pp_paths = []
        self.pp_paths = pp_paths

    def version(self):
        argv = [*self._split_command, '--version']
        return check_output(argv, encoding='ascii').strip()

    def get_calculator_command(self, inputfile):
        return [str(inputfile)]

    def socketio_argv_unix(self, socket):
        # XXX clean up the passing of the inputfile
        inputfile = AbinitTemplate().input_file
        return [inputfile, '--ipi', f'{socket}:UNIX']


class AbinitTemplate(CalculatorTemplate):
    _label = 'abinit'  # Controls naming of files within calculation directory

    def __init__(self):
        super().__init__(
            name='abinit',
            implemented_properties=[
                'energy',
                'free_energy',
                'forces',
                'stress',
                'magmom',
            ],
        )

        # XXX superclass should require inputname and outputname

        self.inputname = f'{self._label}.in'
        self.outputname = f'{self._label}.log'
        self.errorname = f'{self._label}.err'

    def execute(self, directory, profile) -> None:
        profile.run(directory, self.inputname, self.outputname,
                    errorfile=self.errorname)

    def write_input(self, profile, directory, atoms, parameters, properties):
        directory = Path(directory)
        parameters = dict(parameters)
        pp_paths = parameters.pop('pp_paths', profile.pp_paths)
        assert pp_paths is not None

        kw = dict(xc='LDA', smearing=None, kpts=None, raw=None, pps='fhi')
        kw.update(parameters)

        io.prepare_abinit_input(
            directory=directory,
            atoms=atoms,
            properties=properties,
            parameters=kw,
            pp_paths=pp_paths,
        )

    def read_results(self, directory):
        return io.read_abinit_outputs(directory, self._label)

    def load_profile(self, cfg, **kwargs):
        return AbinitProfile.from_config(cfg, self.name, **kwargs)

    def socketio_argv(self, profile, unixsocket, port):
        # XXX This handling of --ipi argument is used by at least two
        # calculators, should refactor if needed yet again
        if unixsocket:
            ipi_arg = f'{unixsocket}:UNIX'
        else:
            ipi_arg = f'localhost:{port:d}'

        return profile.get_calculator_command(self.inputname) + [
            '--ipi',
            ipi_arg,
        ]

    def socketio_parameters(self, unixsocket, port):
        return dict(ionmov=28, expert_user=1, optcell=2)


class Abinit(GenericFileIOCalculator):
    """Class for doing ABINIT calculations.

    The default parameters are very close to those that the ABINIT
    Fortran code would use.  These are the exceptions::

      calc = Abinit(xc='LDA', ecut=400, toldfe=1e-5)
    """

    def __init__(
        self,
        *,
        profile=None,
        directory='.',
        **kwargs,
    ):
        """Construct ABINIT-calculator object.

        Examples
        ========
        Use default values:

        >>> h = Atoms('H', calculator=Abinit(ecut=200, toldfe=0.001))
        >>> h.center(vacuum=3.0)
        >>> e = h.get_potential_energy()

        """

        super().__init__(
            template=AbinitTemplate(),
            profile=profile,
            directory=directory,
            parameters=kwargs,
        )