Coverage for python/lsst/daf/butler/core/dimensions/universe.py : 28%

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
# This file is part of daf_butler. # # Developed for the LSST Data Management System. # This product includes software developed by the LSST Project # (http://www.lsst.org). # See the COPYRIGHT file at the top-level directory of this distribution # for details of code ownership. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
from .coordinate import ExpandedDataCoordinate from .packer import DimensionPacker
"""A special `DimensionGraph` that constructs and manages a complete set of compatible dimensions.
`DimensionUniverse` is not a class-level singleton, but all instances are tracked in a singleton map keyed by the version number in the configuration they were loaded from. Because these universes are solely responsible for constructing `DimensionElement` instances, these are also indirectly tracked by that singleton as well.
Parameters ---------- config : `Config`, optional Configuration describing the dimensions and their relationships. If not provided, default configuration (from ``daf_butler/config/dimensions.yaml``) wil be loaded. """
"""Singleton dictionary of all instances, keyed by version.
For internal use only. """
# Normalize the config and apply defaults. config = DimensionConfig(config)
# First see if an equivalent instance already exists. version = config["version"] self = cls._instances.get(version) if self is not None: return self
# Create the universe instance and add core attributes. # We don't want any of what DimensionGraph.__new__ does, so we just go # straight to object.__new__. The C++ side of my brain is offended by # this, but I think it's the right approach in Python, where we don't # have the option of having multiple constructors with different roles. self = object.__new__(cls) self.universe = self self._cache = {} self.dimensions = NamedValueSet() self.elements = NamedValueSet()
# Read the skypix dimensions from config. skyPixDimensions, self.commonSkyPix = processSkyPixConfig(config["skypix"]) # Add the skypix dimensions to the universe after sorting # lexicographically (no topological sort because skypix dimensions # never have any dependencies). for name in sorted(skyPixDimensions): skyPixDimensions[name]._finish(self)
# Read the other dimension elements from config. elementsToDo = processElementsConfig(config["elements"]) # Add elements to the universe in topological order by identifying at # each outer iteration which elements have already had all of their # dependencies added. while elementsToDo: unblocked = [name for name, element in elementsToDo.items() if element._directDependencyNames.isdisjoint(elementsToDo.keys())] unblocked.sort() # Break ties lexicographically. if not unblocked: raise RuntimeError(f"Cycle detected in dimension elements: {elementsToDo.keys()}.") for name in unblocked: # Finish initialization of the element with steps that # depend on those steps already having been run for all # dependencies. # This includes adding the element to self.elements and # (if appropriate) self.dimensions. elementsToDo.pop(name)._finish(self)
# Add attributes for special subsets of the graph. self.empty = DimensionGraph(self, (), conform=False) self._finish()
# Set up factories for dataId packers as defined by config. self._packers = {} for name, subconfig in config.get("packers", {}).items(): self._packers[name] = DimensionPackerFactory.fromConfig(universe=self, config=subconfig)
# Use the version number from the config as a key in the singleton # dict containing all instances; that will let us transfer dimension # objects between processes using pickle without actually going # through real initialization, as long as a universe with the same # version has already been constructed in the receiving process. self._version = version cls._instances[self._version] = self return self
return f"DimensionUniverse({self})"
"""Construct a `DimensionGraph` from a possibly-heterogenous iterable of `Dimension` instances and string names thereof.
Constructing `DimensionGraph` directly from names or dimension instances is slightly more efficient when it is known in advance that the iterable is not heterogenous.
Parameters ---------- iterable: iterable of `Dimension` or `str` Dimensions that must be included in the returned graph (their dependencies will be as well).
Returns ------- graph : `DimensionGraph` A `DimensionGraph` instance containing all given dimensions. """ names = set() for item in iterable: try: names.add(item.name) except AttributeError: names.add(item) return DimensionGraph(universe=self, names=names)
"""Return a sorted version of the given iterable of dimension elements.
The universe's sort order is topological (an element's dependencies precede it), starting with skypix dimensions (which never have dependencies) and then sorting lexicographically to break ties.
Parameters ---------- elements : iterable of `DimensionElement`. Elements to be sorted. reverse : `bool`, optional If `True`, sort in the opposite order.
Returns ------- sorted : `list` of `DimensionElement` A sorted list containing the same elements that were given. """ s = set(elements) result = [element for element in self.elements if element in s or element.name in s] if reverse: result.reverse() return result
"""Construct a `DimensionPacker` that can pack data ID dictionaries into unique integers.
Parameters ---------- name : `str` Name of the packer, matching a key in the "packers" section of the dimension configuration. dataId : `ExpandedDataCoordinate` Fully-expanded data ID that identfies the at least the "fixed" dimensions of the packer (i.e. those that are assumed/given, setting the space over which packed integer IDs are unique). """ return self._packers[name](dataId)
def _unpickle(cls, version: bytes) -> DimensionUniverse: """Callable used for unpickling.
For internal use only. """ try: return cls._instances[version] except KeyError as err: raise pickle.UnpicklingError( f"DimensionUniverse with version '{version}' " f"not found. Note that DimensionUniverse objects are not " f"truly serialized; when using pickle to transfer them " f"between processes, an equivalent instance with the same " f"version must already exist in the receiving process." ) from err
return (self._unpickle, (self._version,))
# Class attributes below are shadowed by instance attributes, and are # present just to hold the docstrings for those instance attributes.
"""The `DimensionGraph` that contains no dimensions (`DimensionGraph`). """
dimensions in the `Registry` database (`SkyPixDimension`). """ |