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

96 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-05 11:07 +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 

30__all__ = ( 

31 "SkyPixDimension", 

32 "SkyPixSystem", 

33) 

34 

35from collections.abc import Iterator, Mapping, Set 

36from types import MappingProxyType 

37from typing import TYPE_CHECKING 

38 

39import sqlalchemy 

40from lsst.sphgeom import PixelizationABC 

41from lsst.utils import doImportType 

42 

43from .. import ddl 

44from .._named import NamedValueAbstractSet, NamedValueSet 

45from .._topology import TopologicalFamily, TopologicalSpace 

46from ._elements import Dimension 

47from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor 

48 

49if TYPE_CHECKING: 

50 from ..registry.interfaces import SkyPixDimensionRecordStorage 

51 from ._universe import DimensionUniverse 

52 

53 

54class SkyPixSystem(TopologicalFamily): 

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

56 

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

58 sky. 

59 

60 Parameters 

61 ---------- 

62 name : `str` 

63 Name of the system. 

64 maxLevel : `int` 

65 Maximum level (inclusive) of the hierarchy. 

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

67 Class whose instances represent a particular level of this 

68 pixelization. 

69 """ 

70 

71 def __init__( 

72 self, 

73 name: str, 

74 *, 

75 maxLevel: int, 

76 PixelizationClass: type[PixelizationABC], 

77 ): 

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

79 self.maxLevel = maxLevel 

80 self.PixelizationClass = PixelizationClass 

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

82 for level in range(maxLevel + 1): 

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

84 

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

86 # Docstring inherited from TopologicalFamily. 

87 best: SkyPixDimension | None = None 

88 for endpoint_name in endpoints: 

89 endpoint = universe[endpoint_name] 

90 if endpoint not in self: 

91 continue 

92 assert isinstance(endpoint, SkyPixDimension) 

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

94 best = endpoint 

95 if best is None: 

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

97 return best 

98 

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

100 return self._members[level] 

101 

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

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

104 

105 def __len__(self) -> int: 

106 return len(self._members) 

107 

108 

109class SkyPixDimension(Dimension): 

110 """Special dimension for sky pixelizations. 

111 

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

113 sky at a particular level. 

114 

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

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

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

118 other. 

119 

120 Parameters 

121 ---------- 

122 system : `SkyPixSystem` 

123 Pixelization system this dimension belongs to. 

124 level : `int` 

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

126 """ 

127 

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

129 self.system = system 

130 self.level = level 

131 self.pixelization = system.PixelizationClass(level) 

132 

133 @property 

134 def name(self) -> str: 

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

136 

137 @property 

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

139 # Docstring inherited from DimensionElement. 

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

141 

142 @property 

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

144 # Docstring inherited from DimensionElement. 

145 return NamedValueSet().freeze() 

146 

147 @property 

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

149 # Docstring inherited from TopologicalRelationshipEndpoint 

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

151 

152 @property 

153 def metadata(self) -> NamedValueAbstractSet[ddl.FieldSpec]: 

154 # Docstring inherited from DimensionElement. 

155 return NamedValueSet().freeze() 

156 

157 def hasTable(self) -> bool: 

158 # Docstring inherited from DimensionElement.hasTable. 

159 return False 

160 

161 def makeStorage(self) -> SkyPixDimensionRecordStorage: 

162 """Make the storage record. 

163 

164 Constructs the `DimensionRecordStorage` instance that should 

165 be used to back this element in a registry. 

166 

167 Returns 

168 ------- 

169 storage : `SkyPixDimensionRecordStorage` 

170 Storage object that should back this element in a registry. 

171 """ 

172 from ..registry.dimensions.skypix import BasicSkyPixDimensionRecordStorage 

173 

174 return BasicSkyPixDimensionRecordStorage(self) 

175 

176 @property 

177 def uniqueKeys(self) -> NamedValueAbstractSet[ddl.FieldSpec]: 

178 # Docstring inherited from DimensionElement. 

179 return NamedValueSet( 

180 { 

181 ddl.FieldSpec( 

182 name="id", 

183 dtype=sqlalchemy.BigInteger, 

184 primaryKey=True, 

185 nullable=False, 

186 ) 

187 } 

188 ).freeze() 

189 

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

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

192 

193 system: SkyPixSystem 

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

195 """ 

196 

197 level: int 

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

199 """ 

200 

201 pixelization: PixelizationABC 

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

203 points (`sphgeom.PixelizationABC`). 

204 """ 

205 

206 

207class SkyPixConstructionVisitor(DimensionConstructionVisitor): 

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

209 

210 Parameters 

211 ---------- 

212 name : `str` 

213 Name of the `SkyPixSystem` to be constructed. 

214 pixelizationClassName : `str` 

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

216 particular level of this pixelization. 

217 maxLevel : `int`, optional 

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

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

220 pixelization class. 

221 

222 Notes 

223 ----- 

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

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

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

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

228 set of dimensions in a universe being static. 

229 """ 

230 

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

232 super().__init__(name) 

233 self._pixelizationClassName = pixelizationClassName 

234 self._maxLevel = maxLevel 

235 

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

237 # Docstring inherited from DimensionConstructionVisitor. 

238 return False 

239 

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

241 # Docstring inherited from DimensionConstructionVisitor. 

242 PixelizationClass = doImportType(self._pixelizationClassName) 

243 assert issubclass(PixelizationClass, PixelizationABC) 

244 if self._maxLevel is not None: 

245 maxLevel = self._maxLevel 

246 else: 

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

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

249 if max_level is None: 

250 raise TypeError( 

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

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

253 ) 

254 assert isinstance(max_level, int) 

255 maxLevel = max_level 

256 system = SkyPixSystem( 

257 self.name, 

258 maxLevel=maxLevel, 

259 PixelizationClass=PixelizationClass, 

260 ) 

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

262 for level in range(maxLevel + 1): 

263 dimension = system[level] 

264 builder.dimensions.add(dimension) 

265 builder.elements.add(dimension)