Coverage for /builds/kinetik161/ase/ase/calculators/octopus.py: 70.37%

54 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-12-10 11:04 +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 (CalculatorTemplate, 

12 GenericFileIOCalculator, 

13 BaseProfile) 

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

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

16 

17 

18class OctopusIOError(IOError): 

19 pass 

20 

21 

22class OctopusProfile(BaseProfile): 

23 def __init__(self, binary, **kwargs): 

24 super().__init__(**kwargs) 

25 self.binary = binary 

26 

27 def get_calculator_command(self, inputfile): 

28 return [self.binary] 

29 

30 def version(self): 

31 import re 

32 from subprocess import check_output 

33 txt = check_output(self.argv + ['--version']).decode('ascii') 

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

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

36 # the first line. 

37 return match.group(1) 

38 

39 

40class OctopusTemplate(CalculatorTemplate): 

41 def __init__(self): 

42 super().__init__( 

43 name='octopus', 

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

45 ) 

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, inputfile=None, outputfile='octopus.out') 

70 

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

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

73 inp = directory / 'inp' 

74 inp.write_text(txt) 

75 

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

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

78 

79 

80class Octopus(GenericFileIOCalculator): 

81 """Octopus calculator. 

82 

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

84 

85 def __init__(self, 

86 profile=None, 

87 directory='.', 

88 parallel_info=None, 

89 parallel=True, 

90 **kwargs): 

91 """Create Octopus calculator. 

92 

93 Label is always taken as a subdirectory. 

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

95 

96 super().__init__(profile=profile, 

97 template=OctopusTemplate(), 

98 directory=directory, 

99 parameters=kwargs, 

100 parallel_info=parallel_info, 

101 parallel=parallel) 

102 

103 @classmethod 

104 def recipe(cls, **kwargs): 

105 from ase import Atoms 

106 system = Atoms() 

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

108 system.calc = calc 

109 try: 

110 system.get_potential_energy() 

111 except OctopusIOError: 

112 pass 

113 else: 

114 raise OctopusIOError('Expected recipe, but found ' 

115 'useful physical output!')