Coverage for python/lsst/daf/butler/core/dimensions/packer.py : 49%

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 TYPE_CHECKING
30from lsst.utils import doImport
32from ..config import Config
33from .graph import DimensionGraph
34from .coordinate import DataCoordinate, DataId, ExpandedDataCoordinate
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
40class DimensionPacker(metaclass=ABCMeta):
41 """An abstract base class for bidirectional mappings between a
42 `DataCoordinate` and a packed integer ID.
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 """
54 def __init__(self, fixed: ExpandedDataCoordinate, dimensions: DimensionGraph):
55 self.fixed = fixed
56 self.dimensions = dimensions
58 @property
59 def universe(self) -> DimensionUniverse:
60 """A graph containing all known dimensions (`DimensionUniverse`).
61 """
62 return self.fixed.universe
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`).
70 Must be implemented by all concrete derived classes. May return
71 `None` to indicate that there is no maximum.
72 """
73 raise NotImplementedError()
75 @abstractmethod
76 def _pack(self, dataId: DataCoordinate) -> int:
77 """Abstract implementation for `~DimensionPacker.pack`.
79 Must be implemented by all concrete derived classes.
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
88 Returns
89 -------
90 packed : `int`
91 Packed integer ID.
92 """
93 raise NotImplementedError()
95 def pack(self, dataId: DataId, *, returnMaxBits: bool = False, **kwds) -> int:
96 """Pack the given data ID into a single integer.
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`.
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`.
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
130 @abstractmethod
131 def unpack(self, packedId: int) -> DataCoordinate:
132 """Unpack an ID produced by `pack` into a full `DataCoordinate`.
134 Must be implemented by all concrete derived classes.
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.
142 Returns
143 -------
144 dataId : `DataCoordinate`
145 Dictionary-like ID that uniquely identifies all covered
146 dimensions.
147 """
148 raise NotImplementedError()
150 # Class attributes below are shadowed by instance attributes, and are
151 # present just to hold the docstrings for those instance attributes.
153 fixed: ExpandedDataCoordinate
154 """The dimensions provided to the packer at construction
155 (`ExpandedDataCoordinate`)
157 The packed ID values are only unique and reversible with these
158 dimensions held fixed.
159 """
161 dimensions: DimensionGraph
162 """The dimensions of data IDs packed by this instance (`DimensionGraph`).
163 """
166class DimensionPackerFactory:
167 """A factory class for `DimensionPacker` instances that can be constructed
168 from configuration.
170 This class is primarily intended for internal use by `DimensionUniverse`.
171 """
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
179 @classmethod
180 def fromConfig(cls, universe: DimensionUniverse, config: Config):
181 """Construct a `DimensionPackerFactory` from a piece of dimension
182 configuration.
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)
197 def __call__(self, fixed: ExpandedDataCoordinate) -> DimensionPacker:
198 """Construct a `DimensionPacker` instance for the given fixed data ID.
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)
212 # Class attributes below are shadowed by instance attributes, and are
213 # present just to hold the docstrings for those instance attributes.
215 fixed: ExpandedDataCoordinate
216 """The dimensions provided to new packers at construction
217 (`ExpandedDataCoordinate`)
219 The packed ID values are only unique and reversible with these
220 dimensions held fixed.
221 """
223 dimensions: DimensionGraph
224 """The dimensions of data IDs packed by the instances constructed by this
225 factory (`DimensionGraph`).
226 """