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 Any, Optional, Tuple, Type, TYPE_CHECKING, Union
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,
96 **kwargs: Any) -> Union[Tuple[int, int], int]:
97 """Pack the given data ID into a single integer.
99 Parameters
100 ----------
101 dataId : `DataId`
102 Data ID to pack. Values for any keys also present in the "fixed"
103 data ID passed at construction must be the same as the values
104 passed at construction.
105 returnMaxBits : `bool`
106 If `True`, return a tuple of ``(packed, self.maxBits)``.
107 **kwargs
108 Additional keyword arguments forwarded to
109 `DataCoordinate.standardize`.
111 Returns
112 -------
113 packed : `int`
114 Packed integer ID.
115 maxBits : `int`, optional
116 Maximum number of nonzero bits in ``packed``. Not returned unless
117 ``returnMaxBits`` is `True`.
119 Notes
120 -----
121 Should not be overridden by derived class
122 (`~DimensionPacker._pack` should be overridden instead).
123 """
124 dataId = DataCoordinate.standardize(dataId, **kwargs)
125 packed = self._pack(dataId)
126 if returnMaxBits:
127 return packed, self.maxBits
128 else:
129 return packed
131 @abstractmethod
132 def unpack(self, packedId: int) -> DataCoordinate:
133 """Unpack an ID produced by `pack` into a full `DataCoordinate`.
135 Must be implemented by all concrete derived classes.
137 Parameters
138 ----------
139 packedId : `int`
140 The result of a call to `~DimensionPacker.pack` on either
141 ``self`` or an identically-constructed packer instance.
143 Returns
144 -------
145 dataId : `DataCoordinate`
146 Dictionary-like ID that uniquely identifies all covered
147 dimensions.
148 """
149 raise NotImplementedError()
151 # Class attributes below are shadowed by instance attributes, and are
152 # present just to hold the docstrings for those instance attributes.
154 fixed: ExpandedDataCoordinate
155 """The dimensions provided to the packer at construction
156 (`ExpandedDataCoordinate`)
158 The packed ID values are only unique and reversible with these
159 dimensions held fixed.
160 """
162 dimensions: DimensionGraph
163 """The dimensions of data IDs packed by this instance (`DimensionGraph`).
164 """
167class DimensionPackerFactory:
168 """A factory class for `DimensionPacker` instances that can be constructed
169 from configuration.
171 This class is primarily intended for internal use by `DimensionUniverse`.
172 """
174 def __init__(self, fixed: DimensionGraph, dimensions: DimensionGraph, clsName: str):
175 self.fixed = fixed
176 self.dimensions = dimensions
177 self._clsName = clsName
178 self._cls: Optional[Type[DimensionPacker]] = None
180 @classmethod
181 def fromConfig(cls, universe: DimensionUniverse, config: Config) -> DimensionPackerFactory:
182 """Construct a `DimensionPackerFactory` from a piece of dimension
183 configuration.
185 Parameters
186 ----------
187 universe : `DimensionGraph`
188 All dimension objects known to the `Registry`.
189 config : `Config`
190 A dict-like `Config` node corresponding to a single entry
191 in the ``packers`` section of a `DimensionConfig`.
192 """
193 fixed = DimensionGraph(universe=universe, names=config["fixed"])
194 dimensions = DimensionGraph(universe=universe, names=config["dimensions"])
195 clsName = config["cls"]
196 return cls(fixed=fixed, dimensions=dimensions, clsName=clsName)
198 def __call__(self, fixed: ExpandedDataCoordinate) -> DimensionPacker:
199 """Construct a `DimensionPacker` instance for the given fixed data ID.
201 Parameters
202 ----------
203 fixed : `ExpandedDataCoordinate`
204 Data ID that provides values for the "fixed" dimensions of the
205 packer. Must be expanded with all metadata known to the
206 `Registry`.
207 """
208 assert fixed.graph.issuperset(self.fixed)
209 if self._cls is None:
210 self._cls = doImport(self._clsName)
211 return self._cls(fixed, self.dimensions)
213 # Class attributes below are shadowed by instance attributes, and are
214 # present just to hold the docstrings for those instance attributes.
216 fixed: DimensionGraph
217 """The dimensions provided to new packers at construction
218 (`DimensionGraph`)
220 The packed ID values are only unique and reversible with these
221 dimensions held fixed.
222 """
224 dimensions: DimensionGraph
225 """The dimensions of data IDs packed by the instances constructed by this
226 factory (`DimensionGraph`).
227 """