Coverage for /builds/alexhroom/ase/ase/calculators/octopus.py: 75.93%

54 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2024-08-05 14:37 +0000

1"""ASE-interface to Octopus. 

2 

3Ask Hjorth Larsen <asklarsen@gmail.com> 

4Carlos de Armas 

5 

6http://tddft.org/programs/octopus/ 

7""" 

8 

9import numpy as np 

10 

11from ase.calculators.genericfileio import (BaseProfile, CalculatorTemplate, 

12 GenericFileIOCalculator) 

13from ase.io.octopus.input import generate_input, process_special_kwargs 

14from ase.io.octopus.output import read_eigenvalues_file, read_static_info 

15 

16 

17class OctopusIOError(IOError): 

18 pass 

19 

20 

21class OctopusProfile(BaseProfile): 

22 def get_calculator_command(self, inputfile): 

23 return [] 

24 

25 def version(self): 

26 import re 

27 from subprocess import check_output 

28 txt = check_output([*self._split_command, '--version'], 

29 encoding='ascii') 

30 match = re.match(r'octopus\s*(.+)', txt) 

31 # With MPI it prints the line for each rank, but we just match 

32 # the first line. 

33 return match.group(1) 

34 

35 

36class OctopusTemplate(CalculatorTemplate): 

37 _label = 'octopus' 

38 

39 def __init__(self): 

40 super().__init__( 

41 'octopus', 

42 implemented_properties=['energy', 'forces', 'dipole', 'stress'], 

43 ) 

44 self.outputname = f'{self._label}.out' 

45 self.errorname = f'{self._label}.err' 

46 

47 def read_results(self, directory): 

48 """Read octopus output files and extract data.""" 

49 results = {} 

50 with open(directory / 'static/info') as fd: 

51 results.update(read_static_info(fd)) 

52 

53 # If the eigenvalues file exists, we get the eigs/occs from that one. 

54 # This probably means someone ran Octopus in 'unocc' mode to 

55 # get eigenvalues (e.g. for band structures), and the values in 

56 # static/info will be the old (selfconsistent) ones. 

57 eigpath = directory / 'static/eigenvalues' 

58 if eigpath.is_file(): 

59 with open(eigpath) as fd: 

60 kpts, eigs, occs = read_eigenvalues_file(fd) 

61 kpt_weights = np.ones(len(kpts)) # XXX ? Or 1 / len(kpts) ? 

62 # XXX New Octopus probably has symmetry reduction !! 

63 results.update(eigenvalues=eigs, occupations=occs, 

64 ibz_k_points=kpts, 

65 k_point_weights=kpt_weights) 

66 return results 

67 

68 def execute(self, directory, profile): 

69 profile.run(directory, None, self.outputname, 

70 errorfile=self.errorname) 

71 

72 def write_input(self, profile, directory, atoms, parameters, properties): 

73 txt = generate_input(atoms, process_special_kwargs(atoms, parameters)) 

74 inp = directory / 'inp' 

75 inp.write_text(txt) 

76 

77 def load_profile(self, cfg, **kwargs): 

78 return OctopusProfile.from_config(cfg, self.name, **kwargs) 

79 

80 

81class Octopus(GenericFileIOCalculator): 

82 """Octopus calculator. 

83 

84 The label is always assumed to be a directory.""" 

85 

86 def __init__(self, profile=None, directory='.', **kwargs): 

87 """Create Octopus calculator. 

88 

89 Label is always taken as a subdirectory. 

90 Restart is taken to be a label.""" 

91 

92 super().__init__(profile=profile, 

93 template=OctopusTemplate(), 

94 directory=directory, 

95 parameters=kwargs) 

96 

97 @classmethod 

98 def recipe(cls, **kwargs): 

99 from ase import Atoms 

100 system = Atoms() 

101 calc = Octopus(CalculationMode='recipe', **kwargs) 

102 system.calc = calc 

103 try: 

104 system.get_potential_energy() 

105 except OctopusIOError: 

106 pass 

107 else: 

108 raise OctopusIOError('Expected recipe, but found ' 

109 'useful physical output!')