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

54 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-08-02 00:12 +0000

1# fmt: off 

2 

3"""ASE-interface to Octopus. 

4 

5Ask Hjorth Larsen <asklarsen@gmail.com> 

6Carlos de Armas 

7 

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

9""" 

10 

11import numpy as np 

12 

13from ase.calculators.genericfileio import ( 

14 BaseProfile, 

15 CalculatorTemplate, 

16 GenericFileIOCalculator, 

17) 

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

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

20 

21 

22class OctopusIOError(IOError): 

23 pass 

24 

25 

26class OctopusProfile(BaseProfile): 

27 def get_calculator_command(self, inputfile): 

28 return [] 

29 

30 def version(self): 

31 import re 

32 from subprocess import check_output 

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

34 encoding='ascii') 

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

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

37 # the first line. 

38 return match.group(1) 

39 

40 

41class OctopusTemplate(CalculatorTemplate): 

42 _label = 'octopus' 

43 

44 def __init__(self): 

45 super().__init__( 

46 'octopus', 

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

48 ) 

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

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

51 

52 def read_results(self, directory): 

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

54 results = {} 

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

56 results.update(read_static_info(fd)) 

57 

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

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

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

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

62 eigpath = directory / 'static/eigenvalues' 

63 if eigpath.is_file(): 

64 with open(eigpath) as fd: 

65 kpts, eigs, occs = read_eigenvalues_file(fd) 

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

67 # XXX New Octopus probably has symmetry reduction !! 

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

69 ibz_kpoints=kpts, 

70 kpoint_weights=kpt_weights) 

71 return results 

72 

73 def execute(self, directory, profile): 

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

75 errorfile=self.errorname) 

76 

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

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

79 inp = directory / 'inp' 

80 inp.write_text(txt) 

81 

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

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

84 

85 

86class Octopus(GenericFileIOCalculator): 

87 """Octopus calculator. 

88 

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

90 

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

92 """Create Octopus calculator. 

93 

94 Label is always taken as a subdirectory. 

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

96 

97 super().__init__(profile=profile, 

98 template=OctopusTemplate(), 

99 directory=directory, 

100 parameters=kwargs) 

101 

102 @classmethod 

103 def recipe(cls, **kwargs): 

104 from ase import Atoms 

105 system = Atoms() 

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

107 system.calc = calc 

108 try: 

109 system.get_potential_energy() 

110 except OctopusIOError: 

111 pass 

112 else: 

113 raise OctopusIOError('Expected recipe, but found ' 

114 'useful physical output!')