Hide keyboard shortcuts

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

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

# This file is part of ctrl_mpexec. 

# 

# 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/>. 

 

"""Module defining few methods to generate GraphViz diagrams from pipelines 

or quantum graphs. 

""" 

 

__all__ = ["graph2dot", "pipeline2dot"] 

 

# ------------------------------- 

# Imports of standard modules -- 

# ------------------------------- 

 

# ----------------------------- 

# Imports for other modules -- 

# ----------------------------- 

from lsst.daf.butler import DimensionUniverse 

from lsst.pipe.base import iterConnections, Pipeline 

 

# ---------------------------------- 

# Local non-exported definitions -- 

# ---------------------------------- 

 

 

def _renderTaskNode(nodeName, taskDef, file, idx=None): 

"""Render GV node for a task""" 

label = [taskDef.taskName.rpartition('.')[-1]] 

if idx is not None: 

label += ["index: {}".format(idx)] 

if taskDef.label: 

label += ["label: {}".format(taskDef.label)] 

label = r'\n'.join(label) 

attrib = dict(shape="box", 

style="filled,bold", 

fillcolor="gray70", 

label=label) 

attrib = ['{}="{}"'.format(key, val) for key, val in attrib.items()] 

print("{} [{}];".format(nodeName, ", ".join(attrib)), file=file) 

 

 

def _renderDSTypeNode(dsType, file): 

"""Render GV node for a dataset type""" 

label = [dsType.name] 

if dsType.dimensions: 

label += ["Dimensions: " + ", ".join(dsType.dimensions.names)] 

label = r'\n'.join(label) 

attrib = dict(shape="box", 

style="rounded,filled", 

fillcolor="gray90", 

label=label) 

attrib = ['{}="{}"'.format(key, val) for key, val in attrib.items()] 

print("{} [{}];".format(dsType.name, ", ".join(attrib)), file=file) 

 

 

def _renderDSNode(nodeName, dsRef, file): 

"""Render GV node for a dataset""" 

label = [dsRef.datasetType.name] 

for key in sorted(dsRef.dataId.keys()): 

label += [str(key) + "=" + str(dsRef.dataId[key])] 

label = r'\n'.join(label) 

attrib = dict(shape="box", 

style="rounded,filled", 

fillcolor="gray90", 

label=label) 

attrib = ['{}="{}"'.format(key, val) for key, val in attrib.items()] 

print("{} [{}];".format(nodeName, ", ".join(attrib)), file=file) 

 

 

def _datasetRefId(dsRef): 

"""Make an idetifying string for given ref""" 

idStr = str(dsRef.datasetType.name) 

for key in sorted(dsRef.dataId.keys()): 

idStr += ":" + str(key) + "=" + str(dsRef.dataId[key]) 

return idStr 

 

 

def _makeDSNode(dsRef, allDatasetRefs, file): 

"""Make new node for dataset if it does not exist. 

 

Returns node name. 

""" 

dsRefId = _datasetRefId(dsRef) 

nodeName = allDatasetRefs.get(dsRefId) 

if nodeName is None: 

idx = len(allDatasetRefs) 

nodeName = "dsref_{}".format(idx) 

allDatasetRefs[dsRefId] = nodeName 

_renderDSNode(nodeName, dsRef, file) 

return nodeName 

 

# ------------------------ 

# Exported definitions -- 

# ------------------------ 

 

 

def graph2dot(qgraph, file): 

"""Convert QuantumGraph into GraphViz digraph. 

 

This method is mostly for documentation/presentation purposes. 

 

Parameters 

---------- 

qgraph: `pipe.base.QuantumGraph` 

QuantumGraph instance. 

file : str or file object 

File where GraphViz graph (DOT language) is written, can be a file name 

or file object. 

 

Raises 

------ 

`OSError` is raised when output file cannot be open. 

`ImportError` is raised when task class cannot be imported. 

""" 

# open a file if needed 

close = False 

if not hasattr(file, "write"): 

file = open(file, "w") 

close = True 

 

print("digraph QuantumGraph {", file=file) 

 

allDatasetRefs = {} 

for taskId, nodes in enumerate(qgraph): 

 

taskDef = nodes.taskDef 

 

for qId, quantum in enumerate(nodes.quanta): 

 

# node for a task 

taskNodeName = "task_{}_{}".format(taskId, qId) 

_renderTaskNode(taskNodeName, taskDef, file) 

 

# quantum inputs 

for dsRefs in quantum.predictedInputs.values(): 

for dsRef in dsRefs: 

nodeName = _makeDSNode(dsRef, allDatasetRefs, file) 

print("{} -> {};".format(nodeName, taskNodeName), file=file) 

 

# quantum outputs 

for dsRefs in quantum.outputs.values(): 

for dsRef in dsRefs: 

nodeName = _makeDSNode(dsRef, allDatasetRefs, file) 

print("{} -> {};".format(taskNodeName, nodeName), file=file) 

 

print("}", file=file) 

if close: 

file.close() 

 

 

def pipeline2dot(pipeline, file): 

"""Convert Pipeline into GraphViz digraph. 

 

This method is mostly for documentation/presentation purposes. 

Unlike other methods this method does not validate graph consistency. 

 

Parameters 

---------- 

pipeline : `pipe.base.Pipeline` 

Pipeline description. 

file : str or file object 

File where GraphViz graph (DOT language) is written, can be a file name 

or file object. 

 

Raises 

------ 

`OSError` is raised when output file cannot be open. 

`ImportError` is raised when task class cannot be imported. 

`MissingTaskFactoryError` is raised when TaskFactory is needed but not 

provided. 

""" 

universe = DimensionUniverse() 

 

# open a file if needed 

close = False 

if not hasattr(file, "write"): 

file = open(file, "w") 

close = True 

 

print("digraph Pipeline {", file=file) 

 

allDatasets = set() 

if isinstance(pipeline, Pipeline): 

pipeline = pipeline.toExpandedPipeline() 

for idx, taskDef in enumerate(pipeline): 

 

# node for a task 

taskNodeName = "task{}".format(idx) 

_renderTaskNode(taskNodeName, taskDef, file, idx) 

 

for attr in iterConnections(taskDef.connections, 'inputs'): 

dsType = attr.makeDatasetType(universe) 

if dsType.name not in allDatasets: 

_renderDSTypeNode(dsType, file) 

allDatasets.add(dsType.name) 

print("{} -> {};".format(dsType.name, taskNodeName), file=file) 

 

for name in taskDef.connections.outputs: 

attr = getattr(taskDef.connections, name) 

dsType = attr.makeDatasetType(universe) 

if dsType.name not in allDatasets: 

_renderDSTypeNode(dsType, file) 

allDatasets.add(dsType.name) 

print("{} -> {};".format(taskNodeName, dsType.name), file=file) 

 

print("}", file=file) 

if close: 

file.close()