Coverage for ase / _4 / calculators / results.py: 84.48%

58 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-21 15:52 +0000

1"""ASEv4 `CalculationResults` class.""" 

2 

3from copy import deepcopy 

4from types import MappingProxyType 

5from typing import Any 

6 

7import numpy as np 

8 

9from ase.outputs import Properties, all_outputs 

10 

11 

12class CalculationResults: 

13 """Container for the results of all Calculator.evaluate() methods. 

14 

15 Currently only supports properties defined in `ase.outputs.all_outputs` 

16 and assumes that Atoms.store(CalculationResults) will write the 

17 properties to Atoms.info and Atoms.arrays. 

18 

19 Stored data is immutable - setter functions don't allow overwriting 

20 non-empty attributes and getter functions return a read-only proxy. 

21 

22 Parameters 

23 ---------- 

24 metadata : dict, optional 

25 Arbitrary information about the calculation. 

26 properties : ase.outputs.Properties 

27 Calculated properties, compliant with `ase.outputs.all_outputs`. 

28 

29 Attributes 

30 ---------- 

31 metadata : dict 

32 properties : ase.outputs.Properties 

33 

34 Methods 

35 ------- 

36 add_metadata(key, val) 

37 add_property(key, val) 

38 

39 """ 

40 

41 _metadata: dict[str, Any] 

42 _properties: Properties 

43 recognised_properties = all_outputs 

44 

45 def __init__( 

46 self, 

47 metadata: dict | None = None, 

48 properties: dict | Properties | None = None, 

49 ) -> None: 

50 self._metadata = {} 

51 if metadata is not None: 

52 self.metadata = metadata 

53 

54 if properties is None: 

55 properties = Properties({}) 

56 elif isinstance(properties, dict): 

57 properties = Properties(properties) 

58 self._properties = properties 

59 

60 @property 

61 def metadata(self) -> MappingProxyType: 

62 return MappingProxyType(self._metadata) 

63 

64 @metadata.setter 

65 def metadata(self, data: dict[str, Any]) -> None: 

66 """Metadata of the results.""" 

67 if not isinstance(data, dict): 

68 raise TypeError(f'`metadata` has to be a dict, not {type(data)}.') 

69 if len(self.metadata) > 0: 

70 raise AttributeError( 

71 f'Not overwriting already set metadata: {self.metadata}.' 

72 ) 

73 self._metadata = deepcopy(data) 

74 

75 @property 

76 def properties(self) -> Properties: 

77 """Stored properties.""" 

78 return self._properties 

79 

80 @properties.setter 

81 def properties(self, data: dict | Properties) -> None: 

82 if not isinstance(data, dict) and not isinstance(data, Properties): 

83 raise TypeError( 

84 '`properties` has to be an instance of ' 

85 f'`ase.outputs.Properties` or `dict`, not {type(data)}.' 

86 ) 

87 if len(self.properties) > 0: 

88 raise AttributeError( 

89 f'Not overwriting already set properties: {self.properties}.' 

90 ) 

91 

92 if isinstance(data, dict): 

93 data = Properties(data) 

94 self._properties = data 

95 

96 def add_metadata( 

97 self, 

98 key: str, 

99 val: int | float | np.ndarray | str, 

100 ) -> None: 

101 """Add metadata.""" 

102 if key in self._metadata: 

103 raise AttributeError( 

104 f'{key} is already present in the metadata ' 

105 f'with {key}={self.metadata[key]}.' 

106 ) 

107 self._metadata[key] = val 

108 

109 def add_property( 

110 self, 

111 key: str, 

112 val: int | float | np.ndarray | str, 

113 ) -> None: 

114 """Add property.""" 

115 if key not in self.recognised_properties: 

116 raise KeyError( 

117 f'{key} is not one of the recognised ' 

118 f'properties {self.recognised_properties.keys()}.' 

119 ) 

120 

121 self._properties._setvalue(key, val) 

122 

123 def __repr__(self) -> str: 

124 clsname = type(self).__name__ 

125 info = f'{clsname}\n' 

126 

127 # metadata 

128 info += ' Metadata:\n' 

129 for key, val in self.metadata.items(): 

130 info += f' {key}={val}\n' 

131 

132 # properties 

133 info += ' Properties:\n' 

134 for key, val in self.properties.items(): 

135 info += f' {key}={val}\n' 

136 

137 return info