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
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-21 15:52 +0000
1"""ASEv4 `CalculationResults` class."""
3from copy import deepcopy
4from types import MappingProxyType
5from typing import Any
7import numpy as np
9from ase.outputs import Properties, all_outputs
12class CalculationResults:
13 """Container for the results of all Calculator.evaluate() methods.
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.
19 Stored data is immutable - setter functions don't allow overwriting
20 non-empty attributes and getter functions return a read-only proxy.
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`.
29 Attributes
30 ----------
31 metadata : dict
32 properties : ase.outputs.Properties
34 Methods
35 -------
36 add_metadata(key, val)
37 add_property(key, val)
39 """
41 _metadata: dict[str, Any]
42 _properties: Properties
43 recognised_properties = all_outputs
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
54 if properties is None:
55 properties = Properties({})
56 elif isinstance(properties, dict):
57 properties = Properties(properties)
58 self._properties = properties
60 @property
61 def metadata(self) -> MappingProxyType:
62 return MappingProxyType(self._metadata)
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)
75 @property
76 def properties(self) -> Properties:
77 """Stored properties."""
78 return self._properties
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 )
92 if isinstance(data, dict):
93 data = Properties(data)
94 self._properties = data
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
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 )
121 self._properties._setvalue(key, val)
123 def __repr__(self) -> str:
124 clsname = type(self).__name__
125 info = f'{clsname}\n'
127 # metadata
128 info += ' Metadata:\n'
129 for key, val in self.metadata.items():
130 info += f' {key}={val}\n'
132 # properties
133 info += ' Properties:\n'
134 for key, val in self.properties.items():
135 info += f' {key}={val}\n'
137 return info