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 2025-05-05 21:21 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2025-05-05 21:21 +0000
1from abc import ABC, abstractmethod
3import logging
4import numpy as np
5from numpy.typing import NDArray
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
12logger = logging.getLogger(__name__)
15class ResidualCalculator(ABC):
17 """This is the base class from which specific residual calculators are being derived.
18 """
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
37 self._basis_set.probed_coordinates = self.probed_coordinates
39 @property
40 def dtype(self) -> np.dtype:
41 """ dtype used by the projector object attached to this instance. """
42 return self._projector.dtype
44 @abstractmethod
45 def get_residuals(self) -> dict:
46 pass
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.
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`.
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
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.
84 Returns
85 -------
86 An estimate of the matrix norm (largest singular value)
87 """
88 return get_largest_eigenvalue(self._basis_set, self._projector,)
90 @property
91 def coefficients(self) -> NDArray:
92 """Optimization coefficients for this method."""
93 return self._coefficients
95 @coefficients.setter
96 def coefficients(self, val: NDArray) -> None:
97 self._coefficients = val.reshape(self._coefficients.shape).astype(self.dtype)
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
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
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
125 @abstractmethod
126 def __str__(self) -> str:
127 pass
129 @abstractmethod
130 def _repr_html_(self) -> str:
131 pass