Coverage for python/lsst/daf/butler/dimensions/_packer.py: 71%

33 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-27 03:00 -0700

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__ = ("DimensionPacker",) 

31 

32from abc import ABCMeta, abstractmethod 

33from typing import TYPE_CHECKING, Any 

34 

35from ._coordinate import DataCoordinate, DataId 

36from ._graph import DimensionGraph, DimensionGroup 

37 

38if TYPE_CHECKING: # Imports needed only for type annotations; may be circular. 

39 from ._universe import DimensionUniverse 

40 

41 

42class DimensionPacker(metaclass=ABCMeta): 

43 """Class for going from `DataCoordinate` to packed integer ID and back. 

44 

45 An abstract base class for bidirectional mappings between a 

46 `DataCoordinate` and a packed integer ID. 

47 

48 Parameters 

49 ---------- 

50 fixed : `DataCoordinate` 

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

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

53 of calls to `unpack`. Subclasses may ignore particular dimensions, and 

54 are permitted to require that ``fixed.hasRecords()`` return `True`. 

55 dimensions : `DimensionGroup` or `DimensionGraph` 

56 The dimensions of data IDs packed by this instance. Only 

57 `DimensionGroup` will be supported after v27. 

58 """ 

59 

60 def __init__(self, fixed: DataCoordinate, dimensions: DimensionGroup | DimensionGraph): 

61 self.fixed = fixed 

62 self._dimensions = self.fixed.universe.conform(dimensions) 

63 

64 @property 

65 def universe(self) -> DimensionUniverse: 

66 """Graph containing all known dimensions (`DimensionUniverse`).""" 

67 return self.fixed.universe 

68 

69 @property 

70 def dimensions(self) -> DimensionGraph: 

71 """The dimensions of data IDs packed by this instance 

72 (`DimensionGraph`). 

73 

74 After v27 this will be a `DimensionGroup`. 

75 """ 

76 return self._dimensions._as_graph() 

77 

78 @property 

79 @abstractmethod 

80 def maxBits(self) -> int: 

81 """Return The maximum number of nonzero bits in the packed ID. 

82 

83 This packed ID will be returned by 

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

85 

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

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

88 """ 

89 raise NotImplementedError() 

90 

91 @abstractmethod 

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

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

94 

95 Must be implemented by all concrete derived classes. 

96 

97 Parameters 

98 ---------- 

99 dataId : `DataCoordinate` 

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

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

102 `DataCoordinate`, not an informal data ID 

103 

104 Returns 

105 ------- 

106 packed : `int` 

107 Packed integer ID. 

108 """ 

109 raise NotImplementedError() 

110 

111 def pack( 

112 self, dataId: DataId | None = None, *, returnMaxBits: bool = False, **kwargs: Any 

113 ) -> tuple[int, int] | int: 

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

115 

116 Parameters 

117 ---------- 

118 dataId : `DataId` 

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

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

121 passed at construction, but in general you must still specify 

122 those keys. 

123 returnMaxBits : `bool` 

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

125 **kwargs 

126 Additional keyword arguments are treated like additional key-value 

127 pairs in ``dataId``. 

128 

129 Returns 

130 ------- 

131 packed : `int` 

132 Packed integer ID. 

133 maxBits : `int`, optional 

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

135 ``returnMaxBits`` is `True`. 

136 

137 Notes 

138 ----- 

139 Should not be overridden by derived class 

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

141 """ 

142 dataId = DataCoordinate.standardize( 

143 dataId, **kwargs, universe=self.fixed.universe, defaults=self.fixed 

144 ) 

145 if dataId.subset(self.fixed.dimensions) != self.fixed: 

146 raise ValueError(f"Data ID packer expected a data ID consistent with {self.fixed}, got {dataId}.") 

147 packed = self._pack(dataId) 

148 if returnMaxBits: 

149 return packed, self.maxBits 

150 else: 

151 return packed 

152 

153 @abstractmethod 

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

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

156 

157 Must be implemented by all concrete derived classes. 

158 

159 Parameters 

160 ---------- 

161 packedId : `int` 

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

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

164 

165 Returns 

166 ------- 

167 dataId : `DataCoordinate` 

168 Dictionary-like ID that uniquely identifies all covered 

169 dimensions. 

170 """ 

171 raise NotImplementedError() 

172 

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

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

175 

176 fixed: DataCoordinate 

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

178 (`DataCoordinate`) 

179 

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

181 dimensions held fixed. 

182 """