Coverage for python/lsst/pipe/base/graph/quantumNode.py: 65%
56 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-07 04:06 -0700
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-07 04:06 -0700
1# This file is part of pipe_base.
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/>.
21from __future__ import annotations
23import uuid
25__all__ = ("QuantumNode", "NodeId", "BuildId")
27from dataclasses import dataclass
28from typing import Any, Dict, NewType, Optional, Tuple
30from lsst.daf.butler import (
31 DatasetRef,
32 DimensionRecord,
33 DimensionRecordsAccumulator,
34 DimensionUniverse,
35 Quantum,
36 SerializedQuantum,
37)
38from pydantic import BaseModel
40from ..pipeline import TaskDef
42BuildId = NewType("BuildId", str)
45def _hashDsRef(ref: DatasetRef) -> int:
46 return hash((ref.datasetType, ref.dataId))
49@dataclass(frozen=True, eq=True)
50class NodeId:
51 """Deprecated, this class is used with QuantumGraph save formats of
52 1 and 2 when unpicking objects and must be retained until those formats
53 are considered unloadable.
55 This represents an unique identifier of a node within an individual
56 construction of a `QuantumGraph`. This identifier will stay constant
57 through a pickle, and any `QuantumGraph` methods that return a new
58 `QuantumGraph`.
60 A `NodeId` will not be the same if a new graph is built containing the same
61 information in a `QuantumNode`, or even built from exactly the same inputs.
63 `NodeId`s do not play any role in deciding the equality or identity (hash)
64 of a `QuantumNode`, and are mainly useful in debugging or working with
65 various subsets of the same graph.
67 This interface is a convenance only, and no guarantees on long term
68 stability are made. New implementations might change the `NodeId`, or
69 provide more or less guarantees.
70 """
72 number: int
73 """The unique position of the node within the graph assigned at graph
74 creation.
75 """
76 buildId: BuildId
77 """Unique identifier created at the time the originating graph was created
78 """
81@dataclass(frozen=True)
82class QuantumNode:
83 """This class represents a node in the quantum graph.
85 The quantum attribute represents the data that is to be processed at this
86 node.
87 """
89 quantum: Quantum
90 """The unit of data that is to be processed by this graph node"""
91 taskDef: TaskDef
92 """Definition of the task that will process the `Quantum` associated with
93 this node.
94 """
95 nodeId: uuid.UUID
96 """The unique position of the node within the graph assigned at graph
97 creation.
98 """
100 def __post_init__(self) -> None:
101 # use setattr here to preserve the frozenness of the QuantumNode
102 self._precomputedHash: int
103 object.__setattr__(self, "_precomputedHash", hash((self.taskDef.label, self.quantum)))
105 def __eq__(self, other: object) -> bool:
106 if not isinstance(other, QuantumNode):
107 return False
108 if self.quantum != other.quantum:
109 return False
110 return self.taskDef == other.taskDef
112 def __hash__(self) -> int:
113 """For graphs it is useful to have a more robust hash than provided
114 by the default quantum id based hashing
115 """
116 return self._precomputedHash
118 def __repr__(self) -> str:
119 """Make more human readable string representation."""
120 return (
121 f"{self.__class__.__name__}(quantum={self.quantum}, "
122 f"taskDef={self.taskDef}, nodeId={self.nodeId})"
123 )
125 def to_simple(self, accumulator: Optional[DimensionRecordsAccumulator] = None) -> SerializedQuantumNode:
126 return SerializedQuantumNode(
127 quantum=self.quantum.to_simple(accumulator=accumulator),
128 taskLabel=self.taskDef.label,
129 nodeId=self.nodeId,
130 )
132 @classmethod
133 def from_simple(
134 cls,
135 simple: SerializedQuantumNode,
136 taskDefMap: Dict[str, TaskDef],
137 universe: DimensionUniverse,
138 recontitutedDimensions: Optional[Dict[int, Tuple[str, DimensionRecord]]] = None,
139 ) -> QuantumNode:
140 return QuantumNode(
141 quantum=Quantum.from_simple(
142 simple.quantum, universe, reconstitutedDimensions=recontitutedDimensions
143 ),
144 taskDef=taskDefMap[simple.taskLabel],
145 nodeId=simple.nodeId,
146 )
149class SerializedQuantumNode(BaseModel):
150 quantum: SerializedQuantum
151 taskLabel: str
152 nodeId: uuid.UUID
154 @classmethod
155 def direct(cls, *, quantum: Dict[str, Any], taskLabel: str, nodeId: str) -> SerializedQuantumNode:
156 node = SerializedQuantumNode.__new__(cls)
157 setter = object.__setattr__
158 setter(node, "quantum", SerializedQuantum.direct(**quantum))
159 setter(node, "taskLabel", taskLabel)
160 setter(node, "nodeId", uuid.UUID(nodeId))
161 setter(node, "__fields_set__", {"quantum", "taskLabel", "nodeId"})
162 return node