Coverage for python/lsst/pipe/base/graph/quantumNode.py: 64%

56 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-15 02:49 -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 

22 

23__all__ = ("QuantumNode", "NodeId", "BuildId") 

24 

25import uuid 

26from dataclasses import dataclass 

27from typing import Any, NewType 

28 

29from lsst.daf.butler import ( 

30 DatasetRef, 

31 DimensionRecord, 

32 DimensionRecordsAccumulator, 

33 DimensionUniverse, 

34 Quantum, 

35 SerializedQuantum, 

36) 

37from pydantic import BaseModel 

38 

39from ..pipeline import TaskDef 

40 

41BuildId = NewType("BuildId", str) 

42 

43 

44def _hashDsRef(ref: DatasetRef) -> int: 

45 return hash((ref.datasetType, ref.dataId)) 

46 

47 

48@dataclass(frozen=True, eq=True) 

49class NodeId: 

50 """Deprecated, this class is used with QuantumGraph save formats of 

51 1 and 2 when unpicking objects and must be retained until those formats 

52 are considered unloadable. 

53 

54 This represents an unique identifier of a node within an individual 

55 construction of a `QuantumGraph`. This identifier will stay constant 

56 through a pickle, and any `QuantumGraph` methods that return a new 

57 `QuantumGraph`. 

58 

59 A `NodeId` will not be the same if a new graph is built containing the same 

60 information in a `QuantumNode`, or even built from exactly the same inputs. 

61 

62 `NodeId`s do not play any role in deciding the equality or identity (hash) 

63 of a `QuantumNode`, and are mainly useful in debugging or working with 

64 various subsets of the same graph. 

65 

66 This interface is a convenance only, and no guarantees on long term 

67 stability are made. New implementations might change the `NodeId`, or 

68 provide more or less guarantees. 

69 """ 

70 

71 number: int 

72 """The unique position of the node within the graph assigned at graph 

73 creation. 

74 """ 

75 buildId: BuildId 

76 """Unique identifier created at the time the originating graph was created 

77 """ 

78 

79 

80@dataclass(frozen=True) 

81class QuantumNode: 

82 """Class representing a node in the quantum graph. 

83 

84 The ``quantum`` attribute represents the data that is to be processed at 

85 this node. 

86 """ 

87 

88 quantum: Quantum 

89 """The unit of data that is to be processed by this graph node""" 

90 taskDef: TaskDef 

91 """Definition of the task that will process the `Quantum` associated with 

92 this node. 

93 """ 

94 nodeId: uuid.UUID 

95 """The unique position of the node within the graph assigned at graph 

96 creation. 

97 """ 

98 

99 def __post_init__(self) -> None: 

100 # use setattr here to preserve the frozenness of the QuantumNode 

101 self._precomputedHash: int 

102 object.__setattr__(self, "_precomputedHash", hash((self.taskDef.label, self.quantum))) 

103 

104 def __eq__(self, other: object) -> bool: 

105 if not isinstance(other, QuantumNode): 

106 return False 

107 if self.quantum != other.quantum: 

108 return False 

109 return self.taskDef == other.taskDef 

110 

111 def __hash__(self) -> int: 

112 """For graphs it is useful to have a more robust hash than provided 

113 by the default quantum id based hashing 

114 """ 

115 return self._precomputedHash 

116 

117 def __repr__(self) -> str: 

118 """Make more human readable string representation.""" 

119 return ( 

120 f"{self.__class__.__name__}(quantum={self.quantum}, taskDef={self.taskDef}, nodeId={self.nodeId})" 

121 ) 

122 

123 def to_simple(self, accumulator: DimensionRecordsAccumulator | None = None) -> SerializedQuantumNode: 

124 return SerializedQuantumNode( 

125 quantum=self.quantum.to_simple(accumulator=accumulator), 

126 taskLabel=self.taskDef.label, 

127 nodeId=self.nodeId, 

128 ) 

129 

130 @classmethod 

131 def from_simple( 

132 cls, 

133 simple: SerializedQuantumNode, 

134 taskDefMap: dict[str, TaskDef], 

135 universe: DimensionUniverse, 

136 recontitutedDimensions: dict[int, tuple[str, DimensionRecord]] | None = None, 

137 ) -> QuantumNode: 

138 return QuantumNode( 

139 quantum=Quantum.from_simple( 

140 simple.quantum, universe, reconstitutedDimensions=recontitutedDimensions 

141 ), 

142 taskDef=taskDefMap[simple.taskLabel], 

143 nodeId=simple.nodeId, 

144 ) 

145 

146 

147class SerializedQuantumNode(BaseModel): 

148 quantum: SerializedQuantum 

149 taskLabel: str 

150 nodeId: uuid.UUID 

151 

152 @classmethod 

153 def direct(cls, *, quantum: dict[str, Any], taskLabel: str, nodeId: str) -> SerializedQuantumNode: 

154 node = SerializedQuantumNode.__new__(cls) 

155 setter = object.__setattr__ 

156 setter(node, "quantum", SerializedQuantum.direct(**quantum)) 

157 setter(node, "taskLabel", taskLabel) 

158 setter(node, "nodeId", uuid.UUID(nodeId)) 

159 setter(node, "__fields_set__", {"quantum", "taskLabel", "nodeId"}) 

160 return node