Coverage for /builds/hweiske/ase/ase/calculators/octopus.py: 77.19%

57 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-22 11:22 +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 __init__(self, binary, **kwargs): 

23 super().__init__(**kwargs) 

24 self.binary = binary 

25 

26 def get_calculator_command(self, inputfile): 

27 return [self.binary] 

28 

29 def version(self): 

30 import re 

31 from subprocess import check_output 

32 txt = check_output([self.binary, '--version'], encoding='ascii') 

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

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

35 # the first line. 

36 return match.group(1) 

37 

38 

39class OctopusTemplate(CalculatorTemplate): 

40 _label = 'octopus' 

41 

42 def __init__(self): 

43 super().__init__( 

44 'octopus', 

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

46 ) 

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

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

49 

50 def read_results(self, directory): 

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

52 results = {} 

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

54 results.update(read_static_info(fd)) 

55 

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

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

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

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

60 eigpath = directory / 'static/eigenvalues' 

61 if eigpath.is_file(): 

62 with open(eigpath) as fd: 

63 kpts, eigs, occs = read_eigenvalues_file(fd) 

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

65 # XXX New Octopus probably has symmetry reduction !! 

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

67 ibz_k_points=kpts, 

68 k_point_weights=kpt_weights) 

69 return results 

70 

71 def execute(self, directory, profile): 

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

73 errorfile=self.errorname) 

74 

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

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

77 inp = directory / 'inp' 

78 inp.write_text(txt) 

79 

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

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

82 

83 

84class Octopus(GenericFileIOCalculator): 

85 """Octopus calculator. 

86 

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

88 

89 def __init__(self, 

90 profile=None, 

91 directory='.', 

92 parallel_info=None, 

93 parallel=True, 

94 **kwargs): 

95 """Create Octopus calculator. 

96 

97 Label is always taken as a subdirectory. 

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

99 

100 super().__init__(profile=profile, 

101 template=OctopusTemplate(), 

102 directory=directory, 

103 parameters=kwargs, 

104 parallel_info=parallel_info, 

105 parallel=parallel) 

106 

107 @classmethod 

108 def recipe(cls, **kwargs): 

109 from ase import Atoms 

110 system = Atoms() 

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

112 system.calc = calc 

113 try: 

114 system.get_potential_energy() 

115 except OctopusIOError: 

116 pass 

117 else: 

118 raise OctopusIOError('Expected recipe, but found ' 

119 'useful physical output!')