Coverage for cosmolayer/store/grid.py: 100%

23 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-26 00:09 +0000

1"""The symmetric, evenly spaced grid a sigma profile is binned onto. 

2 

3A sigma profile is a histogram of surface area over charge density 

4(sigma, in e/Ų). Profiles are always binned onto the ``SigmaGrid`` 

5passed in. 

6 

7Two operations are named separately because both are sometimes called 

8"shifting": 

9 

10- *centering* subtracts a row's mean charge density from its segments 

11 **before** binning, so the profile has zero first moment. It changes 

12 the data, not the grid (``SigmaProfileTable.centered``). 

13- *translating* moves an already-binned row along a grid, used when 

14 un-centering atom profiles while summing them to molecule level. 

15""" 

16 

17from dataclasses import dataclass 

18from functools import cached_property 

19 

20import numpy as np 

21from numpy.typing import NDArray 

22 

23DEFAULT_MAX_ABS_SIGMA = 0.025 

24DEFAULT_NUM_POINTS = 51 

25 

26 

27@dataclass(frozen=True) 

28class SigmaGrid: 

29 """A symmetric, evenly spaced grid of sigma-profile points. 

30 

31 Parameters 

32 ---------- 

33 max_abs_sigma : float, optional 

34 Bounded sigma-profile value at each end of the grid, in e/Ų, by 

35 default ``DEFAULT_MAX_ABS_SIGMA``. 

36 num_points : int, optional 

37 Number of grid points, by default ``DEFAULT_NUM_POINTS``. An odd 

38 count puts a point at sigma = 0; an even count straddles it. 

39 

40 Examples 

41 -------- 

42 >>> grid = SigmaGrid(max_abs_sigma=0.025, num_points=51) 

43 >>> round(grid.bin_width, 6) 

44 0.001 

45 >>> len(grid) 

46 51 

47 >>> grid.values[0], grid.values[-1] 

48 (np.float64(-0.025), np.float64(0.025)) 

49 """ 

50 

51 max_abs_sigma: float = DEFAULT_MAX_ABS_SIGMA 

52 num_points: int = DEFAULT_NUM_POINTS 

53 

54 @property 

55 def bin_width(self) -> float: 

56 """Width of one bin, in e/Ų. 

57 

58 Returns 

59 ------- 

60 float 

61 ``2 * max_abs_sigma / (num_points - 1)``. 

62 """ 

63 return (2.0 * self.max_abs_sigma) / (self.num_points - 1) 

64 

65 @cached_property 

66 def values(self) -> NDArray[np.float64]: 

67 """Sigma value at every grid point, in e/Ų. 

68 

69 Returns 

70 ------- 

71 np.ndarray, shape (num_points,) 

72 Evenly spaced values from ``-max_abs_sigma`` to 

73 ``max_abs_sigma``. 

74 """ 

75 return np.linspace(-self.max_abs_sigma, self.max_abs_sigma, self.num_points) 

76 

77 @classmethod 

78 def from_values(cls, values: NDArray[np.float64]) -> "SigmaGrid": 

79 """Reconstruct a :class:`SigmaGrid` from an existing array of grid 

80 values. 

81 

82 Parameters 

83 ---------- 

84 values : np.ndarray 

85 Evenly spaced, symmetric grid values, as returned by 

86 ``self.values``. 

87 

88 Returns 

89 ------- 

90 SigmaGrid 

91 A grid whose ``.values`` reproduce ``values``. 

92 

93 Examples 

94 -------- 

95 >>> grid = SigmaGrid(0.025, 51) 

96 >>> SigmaGrid.from_values(grid.values) == grid 

97 True 

98 """ 

99 return cls(float(values[-1]), len(values)) 

100 

101 def __len__(self) -> int: 

102 return self.num_points 

103 

104 

105DEFAULT_SIGMA_GRID = SigmaGrid() 

106"""Default 51-point grid from -0.025 to 0.025 e/Ų."""