Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""ASE-interface to Octopus.
3Ask Hjorth Larsen <asklarsen@gmail.com>
4Carlos de Armas
6http://tddft.org/programs/octopus/
7"""
9import os
10import numpy as np
11from ase.io.octopus.input import (
12 process_special_kwargs, kwargs2atoms,
13 generate_input, parse_input_file,
14 normalize_keywords)
15from ase.io.octopus.output import read_eigenvalues_file, read_static_info
16from ase.calculators.calculator import (
17 FileIOCalculator, EigenvalOccupationMixin, PropertyNotImplementedError)
20class OctopusIOError(IOError):
21 pass
24class Octopus(FileIOCalculator, EigenvalOccupationMixin):
25 """Octopus calculator.
27 The label is always assumed to be a directory."""
29 implemented_properties = ['energy', 'forces', 'dipole', 'stress']
30 command = 'octopus'
32 def __init__(self,
33 restart=None,
34 label=None,
35 directory=None,
36 atoms=None,
37 command=None,
38 **kwargs):
39 """Create Octopus calculator.
41 Label is always taken as a subdirectory.
42 Restart is taken to be a label."""
44 kwargs.pop('check_keywords', None) # Ignore old keywords
45 kwargs.pop('troublesome_keywords', None)
47 if label is not None:
48 # restart mechanism in Calculator tends to set the label.
49 #import warnings
50 #warnings.warn('Please use directory=... instead of label')
51 directory = label.rstrip('/')
53 if directory is None:
54 directory = 'ink-pool'
56 self.kwargs = {}
58 FileIOCalculator.__init__(self, restart=restart,
59 directory=directory,
60 atoms=atoms,
61 command=command, **kwargs)
62 # The above call triggers set() so we can update self.kwargs.
64 def set(self, **kwargs):
65 """Set octopus input file parameters."""
66 kwargs = normalize_keywords(kwargs)
67 changes = FileIOCalculator.set(self, **kwargs)
68 if changes:
69 self.results.clear()
70 self.kwargs.update(kwargs)
71 # XXX should use 'Parameters' but don't know how
73 def get_xc_functional(self):
74 """Return the XC-functional identifier.
75 'LDA', 'PBE', ..."""
76 return self.kwargs.get('xcfunctional', 'LDA')
78 def get_bz_k_points(self):
79 """Return all the k-points in the 1. Brillouin zone.
80 The coordinates are relative to reciprocal latice vectors."""
81 # Have not found nice way of extracting this information
82 # from Octopus. Thus unimplemented. -askhl
83 raise NotImplementedError
85 def get_charges(self, atoms=None):
86 raise PropertyNotImplementedError
88 def get_fermi_level(self):
89 return self.results['efermi']
91 def get_potential_energies(self):
92 raise PropertyNotImplementedError
94 def get_dipole_moment(self, atoms=None):
95 if 'dipole' not in self.results:
96 msg = ('Dipole moment not calculated.\n'
97 'You may wish to use SCFCalculateDipole=True')
98 raise OctopusIOError(msg)
99 return self.results['dipole']
101 def get_stresses(self):
102 raise PropertyNotImplementedError
104 def get_number_of_spins(self):
105 """Return the number of spins in the calculation.
106 Spin-paired calculations: 1, spin-polarized calculation: 2."""
107 return 2 if self.get_spin_polarized() else 1
109 def get_spin_polarized(self):
110 """Is it a spin-polarized calculation?"""
112 sc = self.kwargs.get('spincomponents')
113 if sc is None or sc == 'unpolarized':
114 return False
115 elif sc == 'spin_polarized' or sc == 'polarized':
116 return True
117 else:
118 raise NotImplementedError('SpinComponents keyword %s' % sc)
120 def get_ibz_k_points(self):
121 """Return k-points in the irreducible part of the Brillouin zone.
122 The coordinates are relative to reciprocal latice vectors."""
123 return self.results['ibz_k_points']
125 def get_k_point_weights(self):
126 return self.results['k_point_weights']
128 def get_number_of_bands(self):
129 return self.results['nbands']
131 #def get_magnetic_moments(self, atoms=None):
132 # if self.results['nspins'] == 1:
133 # return np.zeros(len(self.atoms))
134 # return self.results['magmoms'].copy()
136 #def get_magnetic_moment(self, atoms=None):
137 # if self.results['nspins'] == 1:
138 # return 0.0
139 # return self.results['magmom']
141 def get_occupation_numbers(self, kpt=0, spin=0):
142 return self.results['occupations'][kpt, spin].copy()
144 def get_eigenvalues(self, kpt=0, spin=0):
145 return self.results['eigenvalues'][kpt, spin].copy()
147 def _getpath(self, path, check=False):
148 path = os.path.join(self.directory, path)
149 if check:
150 if not os.path.exists(path):
151 raise OctopusIOError('No such file or directory: %s' % path)
152 return path
154 def get_atoms(self):
155 return FileIOCalculator.get_atoms(self)
157 def read_results(self):
158 """Read octopus output files and extract data."""
159 with open(self._getpath('static/info', check=True)) as fd:
160 self.results.update(read_static_info(fd))
162 # If the eigenvalues file exists, we get the eigs/occs from that one.
163 # This probably means someone ran Octopus in 'unocc' mode to
164 # get eigenvalues (e.g. for band structures), and the values in
165 # static/info will be the old (selfconsistent) ones.
166 try:
167 eigpath = self._getpath('static/eigenvalues', check=True)
168 except OctopusIOError:
169 pass
170 else:
171 with open(eigpath) as fd:
172 kpts, eigs, occs = read_eigenvalues_file(fd)
173 kpt_weights = np.ones(len(kpts)) # XXX ? Or 1 / len(kpts) ?
174 self.results.update(eigenvalues=eigs, occupations=occs,
175 ibz_k_points=kpts,
176 k_point_weights=kpt_weights)
178 def write_input(self, atoms, properties=None, system_changes=None):
179 FileIOCalculator.write_input(self, atoms, properties=properties,
180 system_changes=system_changes)
181 txt = generate_input(atoms, process_special_kwargs(atoms, self.kwargs))
182 with open(self._getpath('inp'), 'w') as fd:
183 fd.write(txt)
185 def read(self, directory):
186 # XXX label of restart file may not be the same as actual label!
187 # This makes things rather tricky. We first set the label to
188 # that of the restart file and arbitrarily expect the remaining code
189 # to rectify any consequent inconsistencies.
190 self.directory = directory
192 inp_path = self._getpath('inp')
193 with open(inp_path) as fd:
194 kwargs = parse_input_file(fd)
196 self.atoms, kwargs = kwargs2atoms(kwargs)
197 self.kwargs.update(kwargs)
199 self.read_results()
201 @classmethod
202 def recipe(cls, **kwargs):
203 from ase import Atoms
204 system = Atoms()
205 calc = Octopus(CalculationMode='recipe', **kwargs)
206 system.calc = calc
207 try:
208 system.get_potential_energy()
209 except OctopusIOError:
210 pass
211 else:
212 raise OctopusIOError('Expected recipe, but found '
213 'useful physical output!')