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

91 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-21 09:55 +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 program is free software: you can redistribute it and/or modify 

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

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

12# (at your option) any later version. 

13# 

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

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

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

17# GNU General Public License for more details. 

18# 

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

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

21 

22from __future__ import annotations 

23 

24__all__ = ( 

25 "SkyPixDimension", 

26 "SkyPixSystem", 

27) 

28 

29from collections.abc import Mapping, Set 

30from types import MappingProxyType 

31from typing import TYPE_CHECKING 

32 

33import sqlalchemy 

34from lsst.sphgeom import PixelizationABC 

35from lsst.utils import doImportType 

36 

37from .. import ddl 

38from .._topology import TopologicalFamily, TopologicalRelationshipEndpoint, TopologicalSpace 

39from ..named import NamedValueAbstractSet, NamedValueSet 

40from ._elements import Dimension 

41from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor 

42 

43if TYPE_CHECKING: 

44 from ...registry.interfaces import SkyPixDimensionRecordStorage 

45 

46 

47class SkyPixSystem(TopologicalFamily): 

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

49 

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

51 sky. 

52 

53 Parameters 

54 ---------- 

55 name : `str` 

56 Name of the system. 

57 maxLevel : `int` 

58 Maximum level (inclusive) of the hierarchy. 

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

60 Class whose instances represent a particular level of this 

61 pixelization. 

62 """ 

63 

64 def __init__( 

65 self, 

66 name: str, 

67 *, 

68 maxLevel: int, 

69 PixelizationClass: type[PixelizationABC], 

70 ): 

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

72 self.maxLevel = maxLevel 

73 self.PixelizationClass = PixelizationClass 

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

75 for level in range(maxLevel + 1): 

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

77 

78 def choose(self, endpoints: NamedValueAbstractSet[TopologicalRelationshipEndpoint]) -> SkyPixDimension: 

79 # Docstring inherited from TopologicalFamily. 

80 best: SkyPixDimension | None = None 

81 for endpoint in endpoints: 

82 if endpoint not in self: 

83 continue 

84 assert isinstance(endpoint, SkyPixDimension) 

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

86 best = endpoint 

87 if best is None: 

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

89 return best 

90 

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

92 return self._members[level] 

93 

94 

95class SkyPixDimension(Dimension): 

96 """Special dimension for sky pixelizations. 

97 

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

99 sky at a particular level. 

100 

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

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

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

104 other. 

105 

106 Parameters 

107 ---------- 

108 system : `SkyPixSystem` 

109 Pixelization system this dimension belongs to. 

110 level : `int` 

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

112 """ 

113 

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

115 self.system = system 

116 self.level = level 

117 self.pixelization = system.PixelizationClass(level) 

118 

119 @property 

120 def name(self) -> str: 

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

122 

123 @property 

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

125 # Docstring inherited from DimensionElement. 

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

127 

128 @property 

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

130 # Docstring inherited from DimensionElement. 

131 return NamedValueSet().freeze() 

132 

133 @property 

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

135 # Docstring inherited from TopologicalRelationshipEndpoint 

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

137 

138 @property 

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

140 # Docstring inherited from DimensionElement. 

141 return NamedValueSet().freeze() 

142 

143 def hasTable(self) -> bool: 

144 # Docstring inherited from DimensionElement.hasTable. 

145 return False 

146 

147 def makeStorage(self) -> SkyPixDimensionRecordStorage: 

148 """Make the storage record. 

149 

150 Constructs the `DimensionRecordStorage` instance that should 

151 be used to back this element in a registry. 

152 

153 Returns 

154 ------- 

155 storage : `SkyPixDimensionRecordStorage` 

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

157 """ 

158 from ...registry.dimensions.skypix import BasicSkyPixDimensionRecordStorage 

159 

160 return BasicSkyPixDimensionRecordStorage(self) 

161 

162 @property 

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

164 # Docstring inherited from DimensionElement. 

165 return NamedValueSet( 

166 { 

167 ddl.FieldSpec( 

168 name="id", 

169 dtype=sqlalchemy.BigInteger, 

170 primaryKey=True, 

171 nullable=False, 

172 ) 

173 } 

174 ).freeze() 

175 

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

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

178 

179 system: SkyPixSystem 

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

181 """ 

182 

183 level: int 

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

185 """ 

186 

187 pixelization: PixelizationABC 

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

189 points (`sphgeom.PixelizationABC`). 

190 """ 

191 

192 

193class SkyPixConstructionVisitor(DimensionConstructionVisitor): 

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

195 

196 Parameters 

197 ---------- 

198 name : `str` 

199 Name of the `SkyPixSystem` to be constructed. 

200 pixelizationClassName : `str` 

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

202 particular level of this pixelization. 

203 maxLevel : `int`, optional 

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

205 an attempt will be made to obtain it from a ``MAX_LEVEL`` attribute 

206 of the pixelization class. 

207 

208 Notes 

209 ----- 

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

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

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

213 by the `DimensionUniverse`; this depends on `DimensionGraph.encode` going 

214 away or otherwise eliminating assumptions about the set of dimensions in a 

215 universe being static. 

216 """ 

217 

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

219 super().__init__(name) 

220 self._pixelizationClassName = pixelizationClassName 

221 self._maxLevel = maxLevel 

222 

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

224 # Docstring inherited from DimensionConstructionVisitor. 

225 return False 

226 

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

228 # Docstring inherited from DimensionConstructionVisitor. 

229 PixelizationClass = doImportType(self._pixelizationClassName) 

230 assert issubclass(PixelizationClass, PixelizationABC) 

231 if self._maxLevel is not None: 

232 maxLevel = self._maxLevel 

233 else: 

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

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

236 if max_level is None: 

237 raise TypeError( 

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

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

240 ) 

241 assert isinstance(max_level, int) 

242 maxLevel = max_level 

243 system = SkyPixSystem( 

244 self.name, 

245 maxLevel=maxLevel, 

246 PixelizationClass=PixelizationClass, 

247 ) 

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

249 for level in range(maxLevel + 1): 

250 dimension = system[level] 

251 builder.dimensions.add(dimension) 

252 builder.elements.add(dimension)