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

92 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-01 02:05 -0800

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 types import MappingProxyType 

30from typing import TYPE_CHECKING, AbstractSet, Dict, Mapping, Optional, Type 

31 

32import sqlalchemy 

33from lsst.sphgeom import PixelizationABC 

34from lsst.utils import doImportType 

35 

36from .. import ddl 

37from .._topology import TopologicalFamily, TopologicalRelationshipEndpoint, TopologicalSpace 

38from ..named import NamedValueAbstractSet, NamedValueSet 

39from ._elements import Dimension 

40from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor 

41 

42if TYPE_CHECKING: 42 ↛ 43line 42 didn't jump to line 43, because the condition on line 42 was never true

43 from ...registry.interfaces import SkyPixDimensionRecordStorage 

44 

45 

46class SkyPixSystem(TopologicalFamily): 

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

48 

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

50 sky. 

51 

52 Parameters 

53 ---------- 

54 name : `str` 

55 Name of the system. 

56 maxLevel : `int` 

57 Maximum level (inclusive) of the hierarchy. 

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

59 Class whose instances represent a particular level of this 

60 pixelization. 

61 """ 

62 

63 def __init__( 

64 self, 

65 name: str, 

66 *, 

67 maxLevel: int, 

68 PixelizationClass: Type[PixelizationABC], 

69 ): 

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

71 self.maxLevel = maxLevel 

72 self.PixelizationClass = PixelizationClass 

73 self._members: Dict[int, SkyPixDimension] = {} 

74 for level in range(maxLevel + 1): 

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

76 

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

78 # Docstring inherited from TopologicalFamily. 

79 best: Optional[SkyPixDimension] = None 

80 for endpoint in endpoints: 

81 if endpoint not in self: 

82 continue 

83 assert isinstance(endpoint, SkyPixDimension) 

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

85 best = endpoint 

86 if best is None: 

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

88 return best 

89 

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

91 return self._members[level] 

92 

93 

94class SkyPixDimension(Dimension): 

95 """Special dimension for sky pixelizations. 

96 

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

98 sky at a particular level. 

99 

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

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

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

103 other. 

104 

105 Parameters 

106 ---------- 

107 system : `SkyPixSystem` 

108 Pixelization system this dimension belongs to. 

109 level : `int` 

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

111 """ 

112 

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

114 self.system = system 

115 self.level = level 

116 self.pixelization = system.PixelizationClass(level) 

117 

118 @property 

119 def name(self) -> str: 

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

121 

122 @property 

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

124 # Docstring inherited from DimensionElement. 

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

126 

127 @property 

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

129 # Docstring inherited from DimensionElement. 

130 return NamedValueSet().freeze() 

131 

132 @property 

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

134 # Docstring inherited from TopologicalRelationshipEndpoint 

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

136 

137 @property 

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

139 # Docstring inherited from DimensionElement. 

140 return NamedValueSet().freeze() 

141 

142 def hasTable(self) -> bool: 

143 # Docstring inherited from DimensionElement.hasTable. 

144 return False 

145 

146 def makeStorage(self) -> SkyPixDimensionRecordStorage: 

147 """Make the storage record. 

148 

149 Constructs the `DimensionRecordStorage` instance that should 

150 be used to back this element in a registry. 

151 

152 Returns 

153 ------- 

154 storage : `SkyPixDimensionRecordStorage` 

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

156 """ 

157 from ...registry.dimensions.skypix import BasicSkyPixDimensionRecordStorage 

158 

159 return BasicSkyPixDimensionRecordStorage(self) 

160 

161 @property 

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

163 # Docstring inherited from DimensionElement. 

164 return NamedValueSet( 

165 { 

166 ddl.FieldSpec( 

167 name="id", 

168 dtype=sqlalchemy.BigInteger, 

169 primaryKey=True, 

170 nullable=False, 

171 ) 

172 } 

173 ).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, 

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

205 of the 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 

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

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

214 universe being static. 

215 """ 

216 

217 def __init__(self, name: str, pixelizationClassName: str, maxLevel: Optional[int] = None): 

218 super().__init__(name) 

219 self._pixelizationClassName = pixelizationClassName 

220 self._maxLevel = maxLevel 

221 

222 def hasDependenciesIn(self, others: AbstractSet[str]) -> bool: 

223 # Docstring inherited from DimensionConstructionVisitor. 

224 return False 

225 

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

227 # Docstring inherited from DimensionConstructionVisitor. 

228 PixelizationClass = doImportType(self._pixelizationClassName) 

229 assert issubclass(PixelizationClass, PixelizationABC) 

230 if self._maxLevel is not None: 

231 maxLevel = self._maxLevel 

232 else: 

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

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

235 if max_level is None: 

236 raise TypeError( 

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

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

239 ) 

240 assert isinstance(max_level, int) 

241 maxLevel = max_level 

242 system = SkyPixSystem( 

243 self.name, 

244 maxLevel=maxLevel, 

245 PixelizationClass=PixelizationClass, 

246 ) 

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

248 for level in range(maxLevel + 1): 

249 dimension = system[level] 

250 builder.dimensions.add(dimension) 

251 builder.elements.add(dimension)