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

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/>.
22from __future__ import annotations
25__all__ = ("DimensionPacker",)
27from abc import ABCMeta, abstractmethod
28from typing import (
29 AbstractSet,
30 Any,
31 Iterable,
32 Optional,
33 Tuple,
34 Type,
35 TYPE_CHECKING,
36 Union,
37)
39from lsst.utils import doImport
41from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor
42from ._coordinate import DataCoordinate, DataId
43from ._graph import DimensionGraph
45if TYPE_CHECKING: # Imports needed only for type annotations; may be circular. 45 ↛ 46line 45 didn't jump to line 46, because the condition on line 45 was never true
46 from ._universe import DimensionUniverse
49class DimensionPacker(metaclass=ABCMeta):
50 """An abstract base class for bidirectional mappings between a
51 `DataCoordinate` and a packed integer ID.
53 Parameters
54 ----------
55 fixed : `DataCoordinate`
56 Expanded data ID for the dimensions whose values must remain fixed
57 (to these values) in all calls to `pack`, and are used in the results
58 of calls to `unpack`. ``fixed.hasRecords()`` must return `True`.
59 dimensions : `DimensionGraph`
60 The dimensions of data IDs packed by this instance.
61 """
63 def __init__(self, fixed: DataCoordinate, dimensions: DimensionGraph):
64 self.fixed = fixed
65 self.dimensions = dimensions
67 @property
68 def universe(self) -> DimensionUniverse:
69 """A graph containing all known dimensions (`DimensionUniverse`).
70 """
71 return self.fixed.universe
73 @property
74 @abstractmethod
75 def maxBits(self) -> int:
76 """The maximum number of nonzero bits in the packed ID returned by
77 `~DimensionPacker.pack` (`int`).
79 Must be implemented by all concrete derived classes. May return
80 `None` to indicate that there is no maximum.
81 """
82 raise NotImplementedError()
84 @abstractmethod
85 def _pack(self, dataId: DataCoordinate) -> int:
86 """Abstract implementation for `~DimensionPacker.pack`.
88 Must be implemented by all concrete derived classes.
90 Parameters
91 ----------
92 dataId : `DataCoordinate`
93 Dictionary-like object identifying (at least) all packed
94 dimensions associated with this packer. Guaranteed to be a true
95 `DataCoordinate`, not an informal data ID
97 Returns
98 -------
99 packed : `int`
100 Packed integer ID.
101 """
102 raise NotImplementedError()
104 def pack(self, dataId: DataId, *, returnMaxBits: bool = False,
105 **kwargs: Any) -> Union[Tuple[int, int], int]:
106 """Pack the given data ID into a single integer.
108 Parameters
109 ----------
110 dataId : `DataId`
111 Data ID to pack. Values for any keys also present in the "fixed"
112 data ID passed at construction must be the same as the values
113 passed at construction.
114 returnMaxBits : `bool`
115 If `True`, return a tuple of ``(packed, self.maxBits)``.
116 **kwargs
117 Additional keyword arguments forwarded to
118 `DataCoordinate.standardize`.
120 Returns
121 -------
122 packed : `int`
123 Packed integer ID.
124 maxBits : `int`, optional
125 Maximum number of nonzero bits in ``packed``. Not returned unless
126 ``returnMaxBits`` is `True`.
128 Notes
129 -----
130 Should not be overridden by derived class
131 (`~DimensionPacker._pack` should be overridden instead).
132 """
133 dataId = DataCoordinate.standardize(dataId, **kwargs)
134 packed = self._pack(dataId)
135 if returnMaxBits:
136 return packed, self.maxBits
137 else:
138 return packed
140 @abstractmethod
141 def unpack(self, packedId: int) -> DataCoordinate:
142 """Unpack an ID produced by `pack` into a full `DataCoordinate`.
144 Must be implemented by all concrete derived classes.
146 Parameters
147 ----------
148 packedId : `int`
149 The result of a call to `~DimensionPacker.pack` on either
150 ``self`` or an identically-constructed packer instance.
152 Returns
153 -------
154 dataId : `DataCoordinate`
155 Dictionary-like ID that uniquely identifies all covered
156 dimensions.
157 """
158 raise NotImplementedError()
160 # Class attributes below are shadowed by instance attributes, and are
161 # present just to hold the docstrings for those instance attributes.
163 fixed: DataCoordinate
164 """The dimensions provided to the packer at construction
165 (`DataCoordinate`)
167 The packed ID values are only unique and reversible with these
168 dimensions held fixed. ``fixed.hasRecords() is True`` is guaranteed.
169 """
171 dimensions: DimensionGraph
172 """The dimensions of data IDs packed by this instance (`DimensionGraph`).
173 """
176class DimensionPackerFactory:
177 """A factory class for `DimensionPacker` instances that can be constructed
178 from configuration.
180 This class is primarily intended for internal use by `DimensionUniverse`.
182 Parameters
183 ----------
184 clsName : `str`
185 Fully-qualified name of the packer class this factory constructs.
186 fixed : `AbstractSet` [ `str` ]
187 Names of dimensions whose values must be provided to the packer when it
188 is constructed. This will be expanded lazily into a `DimensionGraph`
189 prior to `DimensionPacker` construction.
190 dimensions : `AbstractSet` [ `str` ]
191 Names of dimensions whose values are passed to `DimensionPacker.pack`.
192 This will be expanded lazily into a `DimensionGraph` prior to
193 `DimensionPacker` construction.
194 """
195 def __init__(
196 self,
197 clsName: str,
198 fixed: AbstractSet[str],
199 dimensions: AbstractSet[str],
200 ):
201 # We defer turning these into DimensionGraph objects until first use
202 # because __init__ is called before a DimensionUniverse exists, and
203 # DimensionGraph instances can only be constructed afterwards.
204 self._fixed: Union[AbstractSet[str], DimensionGraph] = fixed
205 self._dimensions: Union[AbstractSet[str], DimensionGraph] = dimensions
206 self._clsName = clsName
207 self._cls: Optional[Type[DimensionPacker]] = None
209 def __call__(self, universe: DimensionUniverse, fixed: DataCoordinate) -> DimensionPacker:
210 """Construct a `DimensionPacker` instance for the given fixed data ID.
212 Parameters
213 ----------
214 fixed : `DataCoordinate`
215 Data ID that provides values for the "fixed" dimensions of the
216 packer. Must be expanded with all metadata known to the
217 `Registry`. ``fixed.hasRecords()`` must return `True`.
218 """
219 # Construct DimensionGraph instances if necessary on first use.
220 # See related comment in __init__.
221 if not isinstance(self._fixed, DimensionGraph):
222 self._fixed = universe.extract(self._fixed)
223 if not isinstance(self._dimensions, DimensionGraph):
224 self._dimensions = universe.extract(self._dimensions)
225 assert fixed.graph.issuperset(self._fixed)
226 if self._cls is None:
227 self._cls = doImport(self._clsName)
228 return self._cls(fixed, self._dimensions)
231class DimensionPackerConstructionVisitor(DimensionConstructionVisitor):
232 """Builder visitor for a single `DimensionPacker`.
234 A single `DimensionPackerConstructionVisitor` should be added to a
235 `DimensionConstructionBuilder` for each `DimensionPackerFactory` that
236 should be added to a universe.
238 Parameters
239 ----------
240 name : `str`
241 Name used to identify this configuration of the packer in a
242 `DimensionUniverse`.
243 clsName : `str`
244 Fully-qualified name of a `DimensionPacker` subclass.
245 fixed : `Iterable` [ `str` ]
246 Names of dimensions whose values must be provided to the packer when it
247 is constructed. This will be expanded lazily into a `DimensionGraph`
248 prior to `DimensionPacker` construction.
249 dimensions : `Iterable` [ `str` ]
250 Names of dimensions whose values are passed to `DimensionPacker.pack`.
251 This will be expanded lazily into a `DimensionGraph` prior to
252 `DimensionPacker` construction.
253 """
254 def __init__(self, name: str, clsName: str, fixed: Iterable[str], dimensions: Iterable[str]):
255 super().__init__(name)
256 self._fixed = set(fixed)
257 self._dimensions = set(dimensions)
258 self._clsName = clsName
260 def hasDependenciesIn(self, others: AbstractSet[str]) -> bool:
261 # Docstring inherited from DimensionConstructionVisitor.
262 return False
264 def visit(self, builder: DimensionConstructionBuilder) -> None:
265 # Docstring inherited from DimensionConstructionVisitor.
266 builder.packers[self.name] = DimensionPackerFactory(
267 clsName=self._clsName,
268 fixed=self._fixed,
269 dimensions=self._dimensions,
270 )