Coverage for local_installation_linux/mumott/methods/residual_calculators/base_residual_calculator.py: 88%

58 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2024-08-11 23:08 +0000

1from abc import ABC, abstractmethod 

2 

3import logging 

4import numpy as np 

5from numpy.typing import NDArray 

6 

7from mumott import DataContainer 

8from mumott.methods.basis_sets.base_basis_set import BasisSet 

9from mumott.methods.projectors.base_projector import Projector 

10from mumott.methods.utilities.preconditioning import get_largest_eigenvalue 

11 

12logger = logging.getLogger(__name__) 

13 

14 

15class ResidualCalculator(ABC): 

16 

17 """This is the base class from which specific residual calculators are being derived. 

18 """ 

19 

20 def __init__(self, 

21 data_container: DataContainer, 

22 basis_set: BasisSet, 

23 projector: Projector, 

24 use_scalar_projections: bool = False, 

25 scalar_projections: NDArray[float] = None): 

26 self._data_container = data_container 

27 self._geometry_hash = hash(data_container.geometry) 

28 self._basis_set = basis_set 

29 self._basis_set_hash = hash(self._basis_set) 

30 self._projector = projector 

31 # GPU-based projectors need float32 

32 self._coefficients = np.zeros((*self._data_container.geometry.volume_shape, 

33 len(self._basis_set)), dtype=self.dtype) 

34 self._use_scalar_projections = use_scalar_projections 

35 self._scalar_projections = scalar_projections 

36 

37 self._basis_set.probed_coordinates = self.probed_coordinates 

38 

39 @property 

40 def dtype(self) -> np.dtype: 

41 """ dtype used by the projector object attached to this instance. """ 

42 return self._projector.dtype 

43 

44 @abstractmethod 

45 def get_residuals(self) -> dict: 

46 pass 

47 

48 @property 

49 def probed_coordinates(self) -> NDArray: 

50 """ An array of 3-vectors with the (x, y, z)-coordinates 

51 on the reciprocal space map probed by the method. 

52 Structured as ``(N, K, I, 3)``, where ``N`` 

53 is the number of projections, ``K`` is the number of 

54 detector segments, ``I`` is the number of points to be 

55 integrated over, and the last axis contains the 

56 (x, y, z)-coordinates. 

57 

58 Notes 

59 ----- 

60 The region of the reciprocal space map spanned by 

61 each detector segment is represented as a parametric curve 

62 between each segment. This is intended to simulate the effect 

63 of summing up pixels on a detector screen. For other methods of 

64 generating data (e.g., by fitting measurements to a curve), 

65 it may be more appropriate to only include a single point, which 

66 will then have the same coordinates as the center of a detector 

67 segments. This can be achieved by setting the property 

68 :attr:`integration_samples`. 

69 

70 The number of detector segments is 

71 `len(geometry.detecor_angles)*len(geometry.two_theta)` 

72 i.e. the product of the number of two_theta bins times the number of 

73 azimuthal bins. As a default, only on two theta bin is used. 

74 When several two_theta bins are used, the second index corresponds 

75 to a raveled array, where the azimuthal is the fast index and 

76 two theta is the slow index. 

77 """ 

78 return self._data_container.geometry.probed_coordinates 

79 

80 def get_estimate_of_matrix_norm(self): 

81 """Calculate the matrix norm of the matrix implied by the basis set and 

82 projector associated with this residual calculator. 

83 

84 Returns 

85 ------- 

86 An estimate of the matrix norm (largest singular value) 

87 """ 

88 return get_largest_eigenvalue(self._basis_set, self._projector,) 

89 

90 @property 

91 def coefficients(self) -> NDArray: 

92 """Optimization coefficients for this method.""" 

93 return self._coefficients 

94 

95 @coefficients.setter 

96 def coefficients(self, val: NDArray) -> None: 

97 self._coefficients = val.reshape(self._coefficients.shape).astype(self.dtype) 

98 

99 @property 

100 def _data(self) -> NDArray: 

101 """ Internal method for choosing between scalar_projections and data. """ 

102 if self._use_scalar_projections: 

103 return self._scalar_projections 

104 else: 

105 return self._data_container.data 

106 

107 @property 

108 def _weights(self) -> NDArray: 

109 """ Internal method for choosing between weights for the 

110 scalar_projections or weights for the data. """ 

111 if self._use_scalar_projections: 

112 return np.mean(self._data_container.projections.weights, axis=-1)[..., None] 

113 else: 

114 return self._data_container.projections.weights 

115 

116 @property 

117 def _detector_angles(self) -> NDArray: 

118 """ Internal method for choosing between detector angles for the data 

119 or detector angles for the scalar_projections. """ 

120 if self._use_scalar_projections: 

121 return np.array((0.,)) 

122 else: 

123 return self._data_container.geometry.detector_angles 

124 

125 @abstractmethod 

126 def __str__(self) -> str: 

127 pass 

128 

129 @abstractmethod 

130 def _repr_html_(self) -> str: 

131 pass