Coverage for python/lsst/daf/butler/dimensions/_skypix.py: 48%

98 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-16 10:44 +0000

1# This file is part of daf_butler. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This software is dual licensed under the GNU General Public License and also 

10# under a 3-clause BSD license. Recipients may choose which of these licenses 

11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, 

12# respectively. If you choose the GPL option then the following text applies 

13# (but note that there is still no warranty even if you opt for BSD instead): 

14# 

15# This program is free software: you can redistribute it and/or modify 

16# it under the terms of the GNU General Public License as published by 

17# the Free Software Foundation, either version 3 of the License, or 

18# (at your option) any later version. 

19# 

20# This program is distributed in the hope that it will be useful, 

21# but WITHOUT ANY WARRANTY; without even the implied warranty of 

22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

23# GNU General Public License for more details. 

24# 

25# You should have received a copy of the GNU General Public License 

26# along with this program. If not, see <http://www.gnu.org/licenses/>. 

27 

28from __future__ import annotations 

29 

30from lsst.daf.butler.column_spec import IntColumnSpec 

31 

32__all__ = ( 

33 "SkyPixDimension", 

34 "SkyPixSystem", 

35) 

36 

37from collections.abc import Iterator, Mapping, Set 

38from types import MappingProxyType 

39from typing import TYPE_CHECKING 

40 

41from lsst.sphgeom import PixelizationABC 

42from lsst.utils import doImportType 

43 

44from .._named import NamedValueAbstractSet, NamedValueSet 

45from .._topology import TopologicalFamily, TopologicalSpace 

46from ._elements import Dimension, KeyColumnSpec, MetadataColumnSpec 

47from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor 

48 

49if TYPE_CHECKING: 

50 from ._universe import DimensionUniverse 

51 

52 

53class SkyPixSystem(TopologicalFamily): 

54 """Class for hierarchical pixelization of the sky. 

55 

56 A `TopologicalFamily` that represents a hierarchical pixelization of the 

57 sky. 

58 

59 Parameters 

60 ---------- 

61 name : `str` 

62 Name of the system. 

63 maxLevel : `int` 

64 Maximum level (inclusive) of the hierarchy. 

65 PixelizationClass : `type` (`lsst.sphgeom.PixelizationABC` subclass) 

66 Class whose instances represent a particular level of this 

67 pixelization. 

68 """ 

69 

70 def __init__( 

71 self, 

72 name: str, 

73 *, 

74 maxLevel: int, 

75 PixelizationClass: type[PixelizationABC], 

76 ): 

77 super().__init__(name, TopologicalSpace.SPATIAL) 

78 self.maxLevel = maxLevel 

79 self.PixelizationClass = PixelizationClass 

80 self._members: dict[int, SkyPixDimension] = {} 

81 for level in range(maxLevel + 1): 

82 self._members[level] = SkyPixDimension(self, level) 

83 

84 def choose(self, endpoints: Set[str], universe: DimensionUniverse) -> SkyPixDimension: 

85 # Docstring inherited from TopologicalFamily. 

86 best: SkyPixDimension | None = None 

87 for endpoint_name in endpoints: 

88 endpoint = universe[endpoint_name] 

89 if endpoint not in self: 

90 continue 

91 assert isinstance(endpoint, SkyPixDimension) 

92 if best is None or best.level < endpoint.level: 

93 best = endpoint 

94 if best is None: 

95 raise RuntimeError(f"No recognized endpoints for {self.name} in {endpoints}.") 

96 return best 

97 

98 def __getitem__(self, level: int) -> SkyPixDimension: 

99 return self._members[level] 

100 

101 def __iter__(self) -> Iterator[SkyPixDimension]: 

102 return iter(self._members.values()) 

103 

104 def __len__(self) -> int: 

105 return len(self._members) 

106 

107 

108class SkyPixDimension(Dimension): 

109 """Special dimension for sky pixelizations. 

110 

111 A special `Dimension` subclass for hierarchical pixelizations of the 

112 sky at a particular level. 

113 

114 Unlike most other dimensions, skypix dimension records are not stored in 

115 the database, as these records only contain an integer pixel ID and a 

116 region on the sky, and each of these can be computed directly from the 

117 other. 

118 

119 Parameters 

120 ---------- 

121 system : `SkyPixSystem` 

122 Pixelization system this dimension belongs to. 

123 level : `int` 

124 Integer level of this pixelization (smaller numbers are coarser grids). 

125 """ 

126 

127 def __init__(self, system: SkyPixSystem, level: int): 

128 self.system = system 

129 self.level = level 

130 self.pixelization = system.PixelizationClass(level) 

131 

132 @property 

133 def name(self) -> str: 

134 return f"{self.system.name}{self.level}" 

135 

136 @property 

137 def required(self) -> NamedValueAbstractSet[Dimension]: 

138 # Docstring inherited from DimensionElement. 

139 return NamedValueSet({self}).freeze() 

140 

141 @property 

142 def implied(self) -> NamedValueAbstractSet[Dimension]: 

143 # Docstring inherited from DimensionElement. 

144 return NamedValueSet().freeze() 

145 

146 @property 

147 def topology(self) -> Mapping[TopologicalSpace, TopologicalFamily]: 

148 # Docstring inherited from TopologicalRelationshipEndpoint 

149 return MappingProxyType({TopologicalSpace.SPATIAL: self.system}) 

150 

151 @property 

152 def metadata_columns(self) -> NamedValueAbstractSet[MetadataColumnSpec]: 

153 # Docstring inherited from DimensionElement. 

154 return NamedValueSet().freeze() 

155 

156 @property 

157 def documentation(self) -> str: 

158 # Docstring inherited from DimensionElement. 

159 return f"Level {self.level} of the {self.system.name!r} sky pixelization system." 

160 

161 def hasTable(self) -> bool: 

162 # Docstring inherited from DimensionElement.hasTable. 

163 return False 

164 

165 @property 

166 def has_own_table(self) -> bool: 

167 # Docstring inherited from DimensionElement. 

168 return False 

169 

170 @property 

171 def unique_keys(self) -> NamedValueAbstractSet[KeyColumnSpec]: 

172 # Docstring inherited from DimensionElement. 

173 return NamedValueSet([IntColumnSpec(name="id", nullable=False)]).freeze() 

174 

175 # Class attributes below are shadowed by instance attributes, and are 

176 # present just to hold the docstrings for those instance attributes. 

177 

178 system: SkyPixSystem 

179 """Pixelization system this dimension belongs to (`SkyPixSystem`). 

180 """ 

181 

182 level: int 

183 """Integer level of this pixelization (smaller numbers are coarser grids). 

184 """ 

185 

186 pixelization: PixelizationABC 

187 """Pixelization instance that can compute regions from IDs and IDs from 

188 points (`sphgeom.PixelizationABC`). 

189 """ 

190 

191 

192class SkyPixConstructionVisitor(DimensionConstructionVisitor): 

193 """Builder visitor for a single `SkyPixSystem` and its dimensions. 

194 

195 Parameters 

196 ---------- 

197 name : `str` 

198 Name of the `SkyPixSystem` to be constructed. 

199 pixelizationClassName : `str` 

200 Fully-qualified name of the class whose instances represent a 

201 particular level of this pixelization. 

202 maxLevel : `int`, optional 

203 Maximum level (inclusive) of the hierarchy. If not provided, an 

204 attempt will be made to obtain it from a ``MAX_LEVEL`` attribute of the 

205 pixelization class. 

206 

207 Notes 

208 ----- 

209 At present, this class adds both a new `SkyPixSystem` instance all possible 

210 `SkyPixDimension` to the builder that invokes it. In the future, it may 

211 add only the `SkyPixSystem`, with dimension instances created on-the-fly by 

212 the `DimensionUniverse`; this depends on eliminating assumptions about the 

213 set of dimensions in a universe being static. 

214 """ 

215 

216 def __init__(self, name: str, pixelizationClassName: str, maxLevel: int | None = None): 

217 super().__init__(name) 

218 self._pixelizationClassName = pixelizationClassName 

219 self._maxLevel = maxLevel 

220 

221 def hasDependenciesIn(self, others: Set[str]) -> bool: 

222 # Docstring inherited from DimensionConstructionVisitor. 

223 return False 

224 

225 def visit(self, builder: DimensionConstructionBuilder) -> None: 

226 # Docstring inherited from DimensionConstructionVisitor. 

227 PixelizationClass = doImportType(self._pixelizationClassName) 

228 assert issubclass(PixelizationClass, PixelizationABC) 

229 if self._maxLevel is not None: 

230 maxLevel = self._maxLevel 

231 else: 

232 # MyPy does not know the return type of getattr. 

233 max_level = getattr(PixelizationClass, "MAX_LEVEL", None) 

234 if max_level is None: 

235 raise TypeError( 

236 f"Skypix pixelization class {self._pixelizationClassName} does" 

237 " not have MAX_LEVEL but no max level has been set explicitly." 

238 ) 

239 assert isinstance(max_level, int) 

240 maxLevel = max_level 

241 system = SkyPixSystem( 

242 self.name, 

243 maxLevel=maxLevel, 

244 PixelizationClass=PixelizationClass, 

245 ) 

246 builder.topology[TopologicalSpace.SPATIAL].add(system) 

247 for level in range(maxLevel + 1): 

248 dimension = system[level] 

249 builder.dimensions.add(dimension) 

250 builder.elements.add(dimension)