Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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 

25__all__ = ("DimensionPacker",) 

26 

27from abc import ABCMeta, abstractmethod 

28from typing import TYPE_CHECKING 

29 

30from lsst.utils import doImport 

31 

32from ..config import Config 

33from .graph import DimensionGraph 

34from .coordinate import DataCoordinate, DataId, ExpandedDataCoordinate 

35 

36if TYPE_CHECKING: # Imports needed only for type annotations; may be circular. 36 ↛ 37line 36 didn't jump to line 37, because the condition on line 36 was never true

37 from .universe import DimensionUniverse 

38 

39 

40class DimensionPacker(metaclass=ABCMeta): 

41 """An abstract base class for bidirectional mappings between a 

42 `DataCoordinate` and a packed integer ID. 

43 

44 Parameters 

45 ---------- 

46 fixed : `ExpandedDataCoordinate` 

47 Expanded data ID for the dimensions whose values must remain fixed 

48 (to these values) in all calls to `pack`, and are used in the results 

49 of calls to `unpack`. 

50 dimensions : `DimensionGraph` 

51 The dimensions of data IDs packed by this instance. 

52 """ 

53 

54 def __init__(self, fixed: ExpandedDataCoordinate, dimensions: DimensionGraph): 

55 self.fixed = fixed 

56 self.dimensions = dimensions 

57 

58 @property 

59 def universe(self) -> DimensionUniverse: 

60 """A graph containing all known dimensions (`DimensionUniverse`). 

61 """ 

62 return self.fixed.universe 

63 

64 @property 

65 @abstractmethod 

66 def maxBits(self) -> int: 

67 """The maximum number of nonzero bits in the packed ID returned by 

68 `~DimensionPacker.pack` (`int`). 

69 

70 Must be implemented by all concrete derived classes. May return 

71 `None` to indicate that there is no maximum. 

72 """ 

73 raise NotImplementedError() 

74 

75 @abstractmethod 

76 def _pack(self, dataId: DataCoordinate) -> int: 

77 """Abstract implementation for `~DimensionPacker.pack`. 

78 

79 Must be implemented by all concrete derived classes. 

80 

81 Parameters 

82 ---------- 

83 dataId : `DataCoordinate` 

84 Dictionary-like object identifying (at least) all packed 

85 dimensions associated with this packer. Guaranteed to be a true 

86 `DataCoordinate`, not an informal data ID 

87 

88 Returns 

89 ------- 

90 packed : `int` 

91 Packed integer ID. 

92 """ 

93 raise NotImplementedError() 

94 

95 def pack(self, dataId: DataId, *, returnMaxBits: bool = False, **kwds) -> int: 

96 """Pack the given data ID into a single integer. 

97 

98 Parameters 

99 ---------- 

100 dataId : `DataId` 

101 Data ID to pack. Values for any keys also present in the "fixed" 

102 data ID passed at construction must be the same as the values 

103 passed at construction. 

104 returnMaxBits : `bool` 

105 If `True`, return a tuple of ``(packed, self.maxBits)``. 

106 kwds 

107 Additional keyword arguments forwarded to 

108 `DataCoordinate.standardize`. 

109 

110 Returns 

111 ------- 

112 packed : `int` 

113 Packed integer ID. 

114 maxBits : `int`, optional 

115 Maximum number of nonzero bits in ``packed``. Not returned unless 

116 ``returnMaxBits`` is `True`. 

117 

118 Notes 

119 ----- 

120 Should not be overridden by derived class 

121 (`~DimensionPacker._pack` should be overridden instead). 

122 """ 

123 dataId = DataCoordinate.standardize(dataId, **kwds) 

124 packed = self._pack(dataId) 

125 if returnMaxBits: 

126 return packed, self.maxBits 

127 else: 

128 return packed 

129 

130 @abstractmethod 

131 def unpack(self, packedId: int) -> DataCoordinate: 

132 """Unpack an ID produced by `pack` into a full `DataCoordinate`. 

133 

134 Must be implemented by all concrete derived classes. 

135 

136 Parameters 

137 ---------- 

138 packedId : `int` 

139 The result of a call to `~DimensionPacker.pack` on either 

140 ``self`` or an identically-constructed packer instance. 

141 

142 Returns 

143 ------- 

144 dataId : `DataCoordinate` 

145 Dictionary-like ID that uniquely identifies all covered 

146 dimensions. 

147 """ 

148 raise NotImplementedError() 

149 

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

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

152 

153 fixed: ExpandedDataCoordinate 

154 """The dimensions provided to the packer at construction 

155 (`ExpandedDataCoordinate`) 

156 

157 The packed ID values are only unique and reversible with these 

158 dimensions held fixed. 

159 """ 

160 

161 dimensions: DimensionGraph 

162 """The dimensions of data IDs packed by this instance (`DimensionGraph`). 

163 """ 

164 

165 

166class DimensionPackerFactory: 

167 """A factory class for `DimensionPacker` instances that can be constructed 

168 from configuration. 

169 

170 This class is primarily intended for internal use by `DimensionUniverse`. 

171 """ 

172 

173 def __init__(self, fixed: DimensionGraph, dimensions: DimensionGraph, clsName: str): 

174 self.fixed = fixed 

175 self.dimensions = dimensions 

176 self._clsName = clsName 

177 self._cls = None 

178 

179 @classmethod 

180 def fromConfig(cls, universe: DimensionUniverse, config: Config): 

181 """Construct a `DimensionPackerFactory` from a piece of dimension 

182 configuration. 

183 

184 Parameters 

185 ---------- 

186 universe : `DimensionGraph` 

187 All dimension objects known to the `Registry`. 

188 config : `Config` 

189 A dict-like `Config` node corresponding to a single entry 

190 in the ``packers`` section of a `DimensionConfig`. 

191 """ 

192 fixed = DimensionGraph(universe=universe, names=config["fixed"]) 

193 dimensions = DimensionGraph(universe=universe, names=config["dimensions"]) 

194 clsName = config["cls"] 

195 return cls(fixed=fixed, dimensions=dimensions, clsName=clsName) 

196 

197 def __call__(self, fixed: ExpandedDataCoordinate) -> DimensionPacker: 

198 """Construct a `DimensionPacker` instance for the given fixed data ID. 

199 

200 Parameters 

201 ---------- 

202 fixed : `ExpandedDataCoordinate` 

203 Data ID that provides values for the "fixed" dimensions of the 

204 packer. Must be expanded with all metadata known to the 

205 `Registry`. 

206 """ 

207 assert fixed.graph.issuperset(self.fixed) 

208 if self._cls is None: 

209 self._cls = doImport(self._clsName) 

210 return self._cls(fixed, self.dimensions) 

211 

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

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

214 

215 fixed: ExpandedDataCoordinate 

216 """The dimensions provided to new packers at construction 

217 (`ExpandedDataCoordinate`) 

218 

219 The packed ID values are only unique and reversible with these 

220 dimensions held fixed. 

221 """ 

222 

223 dimensions: DimensionGraph 

224 """The dimensions of data IDs packed by the instances constructed by this 

225 factory (`DimensionGraph`). 

226 """