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 143 144 145 146 147 148 149 150 151 152 153 154
|
"""
`Elk <https://elk.sourceforge.io>`_ is an all-electron full-potential linearised
augmented-plane wave (LAPW) code.
.. versionchanged:: 3.26.0
:class:`ELK` is now a subclass of :class:`GenericFileIOCalculator`.
.. |config| replace:: ``config.ini``
.. _config: calculators.html#calculator-configuration
:class:`ELK` can be configured with |config|_.
.. code-block:: ini
[elk]
command = /path/to/elk
sppath = /path/to/species
If you need to override it for programmatic control of the ``elk`` command,
use :class:`ElkProfile`.
.. code-block:: python
from ase.calculators.elk import ELK, ElkProfile
profile = ElkProfile(command='/path/to/elk')
calc = ELK(profile=profile)
"""
import os
import re
import warnings
from pathlib import Path
from typing import Optional
from ase.calculators.genericfileio import (
BaseProfile,
CalculatorTemplate,
GenericFileIOCalculator,
read_stdout,
)
from ase.io.elk import ElkReader, write_elk_in
COMPATIBILITY_MSG = (
'`ELK` has been restructured. '
'Please use `ELK(profile=ElkProfile(command))` instead.'
)
class ElkProfile(BaseProfile):
"""Profile for :class:`ELK`."""
configvars = {'sppath'}
def __init__(self, command, sppath: Optional[str] = None, **kwargs) -> None:
super().__init__(command, **kwargs)
self.sppath = sppath
def get_calculator_command(self, inputfile):
return []
def version(self):
output = read_stdout(self._split_command)
match = re.search(r'Elk code version (\S+)', output, re.M)
return match.group(1)
class ElkTemplate(CalculatorTemplate):
"""Template for :class:`ELK`."""
def __init__(self):
super().__init__('elk', ['energy', 'forces'])
self.inputname = 'elk.in'
self.outputname = 'elk.out'
def write_input(
self,
profile: ElkProfile,
directory,
atoms,
parameters,
properties,
):
directory = Path(directory)
parameters = dict(parameters)
if 'forces' in properties:
parameters['tforce'] = True
if 'sppath' not in parameters and profile.sppath:
parameters['sppath'] = profile.sppath
write_elk_in(directory / self.inputname, atoms, parameters=parameters)
def execute(self, directory, profile: ElkProfile) -> None:
profile.run(directory, self.inputname, self.outputname)
def read_results(self, directory):
from ase.outputs import Properties
reader = ElkReader(directory)
dct = dict(reader.read_everything())
converged = dct.pop('converged')
if not converged:
raise RuntimeError('Did not converge')
# (Filter results thorugh Properties for error detection)
props = Properties(dct)
return dict(props)
def load_profile(self, cfg, **kwargs):
return ElkProfile.from_config(cfg, self.name, **kwargs)
class ELK(GenericFileIOCalculator):
"""Elk calculator."""
def __init__(
self,
*,
profile=None,
command=GenericFileIOCalculator._deprecated,
label=GenericFileIOCalculator._deprecated,
directory='.',
**kwargs,
) -> None:
"""
Parameters
----------
**kwargs : dict, optional
ASE standard keywords like ``xc``, ``kpts`` and ``smearing`` or any
Elk-native keywords.
Examples
--------
>>> calc = ELK(tasks=0, ngridk=(3, 3, 3))
"""
if command is not self._deprecated:
raise RuntimeError(COMPATIBILITY_MSG)
if label is not self._deprecated:
msg = 'Ignoring label, please use directory instead'
warnings.warn(msg, FutureWarning)
if 'ASE_ELK_COMMAND' in os.environ and profile is None:
warnings.warn(COMPATIBILITY_MSG, FutureWarning)
super().__init__(
template=ElkTemplate(),
profile=profile,
directory=directory,
parameters=kwargs,
)
|