torch-flowgraph 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,50 @@
1
+ # SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF)
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+
5
+ """torch-flowgraph: PyTorch computation graph tracing and visualization.
6
+
7
+ Public API
8
+ ----------
9
+ :class:`GraphBase`
10
+ Base class for all computation graph representations. JSON-deserialised
11
+ graphs are returned as :class:`GraphBase` instances.
12
+
13
+ :class:`Clustering`
14
+ Abstract base class for reclustering strategies.
15
+
16
+ :class:`ClanClustering`
17
+ Re-cluster using the McCreary clan-based graph-parsing algorithm.
18
+
19
+ :class:`LevelClustering`
20
+ Keep only cluster boxes at a given depth; all nodes remain visible.
21
+
22
+ :class:`NameClustering`
23
+ Keep only cluster boxes with specific names; all nodes remain visible.
24
+
25
+ :func:`apply_clan_clustering`
26
+ Convenience wrapper around ``graph.recluster(ClanClustering())``.
27
+ """
28
+
29
+ from . import patches as _patches # noqa: F401 — apply torch meta-device patches
30
+ from ._post_import import on_import
31
+ from .clustering import ClanClustering
32
+ from .clustering import Clustering
33
+ from .clustering import LevelClustering
34
+ from .clustering import NameClustering
35
+ from .clustering import NoClustering
36
+ from .clustering import apply_clan_clustering
37
+ from .graph import GraphBase
38
+ from .validation import validate_graph
39
+
40
+ __all__ = [
41
+ "GraphBase",
42
+ "Clustering",
43
+ "ClanClustering",
44
+ "LevelClustering",
45
+ "NameClustering",
46
+ "NoClustering",
47
+ "apply_clan_clustering",
48
+ "validate_graph",
49
+ "on_import",
50
+ ]
@@ -0,0 +1,8 @@
1
+ # SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF)
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ import sys
5
+
6
+ from .cli import main
7
+
8
+ sys.exit(main())
@@ -0,0 +1,78 @@
1
+ # SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF)
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Post-import hooks.
5
+
6
+ Provides :func:`on_import` to register a callback that fires the first time
7
+ a given module is imported (or immediately, if already loaded). This lets a
8
+ package register patches that apply only when a third-party dependency is
9
+ actually pulled in by some other code path.
10
+ """
11
+
12
+ import builtins
13
+ import sys
14
+
15
+ _pending: dict = {}
16
+ _done: set = set()
17
+ _orig_import = None
18
+ _depth = 0
19
+
20
+
21
+ def on_import(module_name, callback):
22
+ """Run *callback(module)* the first time *module_name* is imported.
23
+
24
+ If the module is already in :data:`sys.modules`, *callback* is invoked
25
+ immediately. Otherwise an import hook is installed that fires the
26
+ callback exactly once, the first time the module appears in
27
+ :data:`sys.modules` *after* the outermost ``import`` statement that
28
+ caused it to load has fully completed. (Submodule imports during a
29
+ package's ``__init__.py`` execution don't fire callbacks for the parent
30
+ package, which would otherwise see a half-initialised module.)
31
+
32
+ Parameters
33
+ ----------
34
+ module_name:
35
+ Fully qualified module name (e.g. ``"torch_geometric.utils._subgraph"``).
36
+ callback:
37
+ Callable taking the imported module as its single argument.
38
+ """
39
+ if module_name in sys.modules and module_name not in _pending:
40
+ _done.add(module_name)
41
+ callback(sys.modules[module_name])
42
+ return
43
+
44
+ _pending.setdefault(module_name, []).append(callback)
45
+ _install_hook()
46
+
47
+
48
+ def _install_hook():
49
+ global _orig_import
50
+ if _orig_import is not None:
51
+ return
52
+ _orig_import = builtins.__import__
53
+
54
+ def _wrapped_import(name, globals=None, locals=None, fromlist=(), level=0):
55
+ global _depth
56
+ _depth += 1
57
+ try:
58
+ result = _orig_import(name, globals, locals, fromlist, level)
59
+ finally:
60
+ _depth -= 1
61
+
62
+ # Only fire callbacks once we have returned to the outermost
63
+ # __import__ call. Otherwise modules in sys.modules may still be
64
+ # mid-execution (a package whose __init__.py is partially run).
65
+ if _depth == 0 and _pending:
66
+ for pending_name in list(_pending):
67
+ if pending_name in sys.modules and pending_name not in _done:
68
+ _done.add(pending_name)
69
+ callbacks = _pending.pop(pending_name)
70
+ mod = sys.modules[pending_name]
71
+ for cb in callbacks:
72
+ try:
73
+ cb(mod)
74
+ except Exception:
75
+ pass
76
+ return result
77
+
78
+ builtins.__import__ = _wrapped_import
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.0'
22
+ __version_tuple__ = version_tuple = (0, 1, 0)
23
+
24
+ __commit_id__ = commit_id = None
@@ -0,0 +1,262 @@
1
+ # SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF)
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Graphviz DOT attribute name lists.
5
+
6
+ This module enumerates the valid attribute names for different element types
7
+ in Graphviz DOT files. The lists are used by the plotting functions to
8
+ filter out unknown attributes when building DOT output, ensuring that only
9
+ recognised attributes are emitted.
10
+
11
+ Attribute hierarchy
12
+ -------------------
13
+ ``COMMON_ATTRIBUTES``
14
+ Attributes valid for all element types (nodes, edges, graphs, clusters).
15
+ ``NODE_EDGE_ATTRIBUTES``
16
+ Attributes valid for both nodes and edges (extends ``COMMON_ATTRIBUTES``).
17
+ ``NODE_ATTRIBUTES``
18
+ Node-specific attributes (extends ``NODE_EDGE_ATTRIBUTES``).
19
+ ``EDGE_ATTRIBUTES``
20
+ Edge-specific attributes (extends ``NODE_EDGE_ATTRIBUTES``).
21
+ ``CLUSTER_GRAPH_ATTRIBUTES``
22
+ Attributes valid for both clusters and graphs (extends
23
+ ``COMMON_ATTRIBUTES``).
24
+ ``CLUSTER_ATTRIBUTES``
25
+ Cluster-specific attributes (extends ``CLUSTER_GRAPH_ATTRIBUTES``).
26
+ ``GRAPH_ATTRIBUTES``
27
+ Graph-specific attributes (extends ``CLUSTER_GRAPH_ATTRIBUTES``).
28
+
29
+ Reference: https://graphviz.org/doc/info/attrs.html
30
+ """
31
+
32
+ # Attributes shared across all Graphviz element types.
33
+ COMMON_ATTRIBUTES = [
34
+ "URL",
35
+ "class",
36
+ "colorscheme",
37
+ "fontcolor",
38
+ "fontname",
39
+ "fontsize",
40
+ "href",
41
+ "id",
42
+ "label",
43
+ "nojustify",
44
+ "style",
45
+ "target",
46
+ "tooltip",
47
+ ]
48
+
49
+ # Attributes valid for both nodes and edges (but not graphs/clusters).
50
+ NODE_EDGE_ATTRIBUTES = COMMON_ATTRIBUTES + [
51
+ "color",
52
+ "comment",
53
+ "fillcolor",
54
+ "layer",
55
+ "penwidth",
56
+ "pos",
57
+ "showboxes",
58
+ "xlabel",
59
+ "xlp",
60
+ ]
61
+
62
+ # Attributes valid only for nodes.
63
+ NODE_ATTRIBUTES = NODE_EDGE_ATTRIBUTES + [
64
+ "area",
65
+ "distortion",
66
+ "fixedsize",
67
+ "gradientangle",
68
+ "group",
69
+ "height",
70
+ "image",
71
+ "imagepos",
72
+ "imagescale",
73
+ "labelloc",
74
+ "margin",
75
+ "ordering",
76
+ "orientation",
77
+ "peripheries",
78
+ "pin",
79
+ "rects",
80
+ "regular",
81
+ "root",
82
+ "samplepoints",
83
+ "shape",
84
+ "shapefile",
85
+ "sides",
86
+ "skew",
87
+ "sortv",
88
+ "vertices",
89
+ "width",
90
+ "z",
91
+ ]
92
+
93
+ # Attributes valid only for edges.
94
+ EDGE_ATTRIBUTES = NODE_EDGE_ATTRIBUTES + [
95
+ "arrowhead",
96
+ "arrowsize",
97
+ "arrowtail",
98
+ "constraint",
99
+ "decorate",
100
+ "dir",
101
+ "edgeURL",
102
+ "edgehref",
103
+ "edgetarget",
104
+ "edgetooltip",
105
+ "headURL",
106
+ "head_lp",
107
+ "headclip",
108
+ "headhref",
109
+ "headlabel",
110
+ "headport",
111
+ "headtarget",
112
+ "headtooltip",
113
+ "labelURL",
114
+ "labelangle",
115
+ "labeldistance",
116
+ "labelfloat",
117
+ "labelfontcolor",
118
+ "labelfontname",
119
+ "labelfontsize",
120
+ "labelhref",
121
+ "labeltarget",
122
+ "labeltooltip",
123
+ "len",
124
+ "lhead",
125
+ "lp",
126
+ "ltail",
127
+ "minlen",
128
+ "samehead",
129
+ "sametail",
130
+ "tailURL",
131
+ "tail_lp",
132
+ "tailclip",
133
+ "tailhref",
134
+ "taillabel",
135
+ "tailport",
136
+ "tailtarget",
137
+ "tailtooltip",
138
+ "weight",
139
+ ]
140
+
141
+ # Attributes valid for both clusters and top-level graphs.
142
+ CLUSTER_GRAPH_ATTRIBUTES = COMMON_ATTRIBUTES + [
143
+ "K",
144
+ "bb",
145
+ "bgcolor",
146
+ "gradientangle",
147
+ "labeljust",
148
+ "labelloc",
149
+ "lheight",
150
+ "lp",
151
+ "lwidth",
152
+ "margin",
153
+ "sortv",
154
+ ]
155
+
156
+ # Attributes valid only for clusters (subgraphs whose name begins with
157
+ # ``cluster_``).
158
+ CLUSTER_ATTRIBUTES = CLUSTER_GRAPH_ATTRIBUTES + [
159
+ "area",
160
+ "cluster",
161
+ "color",
162
+ "fillcolor",
163
+ "layer",
164
+ "pencolor",
165
+ "penwidth",
166
+ "peripheries",
167
+ ]
168
+
169
+ # Attributes valid only for the top-level graph.
170
+ GRAPH_ATTRIBUTES = CLUSTER_GRAPH_ATTRIBUTES + [
171
+ "Damping",
172
+ "TBbalance",
173
+ "_background",
174
+ "beautify",
175
+ "center",
176
+ "charset",
177
+ "clusterrank",
178
+ "comment",
179
+ "compound",
180
+ "concentrate",
181
+ "defaultdist",
182
+ "dim",
183
+ "dimen",
184
+ "diredgeconstraints",
185
+ "dpi",
186
+ "epsilon",
187
+ "esep",
188
+ "fontnames",
189
+ "fontpath",
190
+ "forcelabels",
191
+ "imagepath",
192
+ "inputscale",
193
+ "label_scheme",
194
+ "landscape",
195
+ "layerlistsep",
196
+ "layers",
197
+ "layerselect",
198
+ "layersep",
199
+ "layout",
200
+ "levels",
201
+ "levelsgap",
202
+ "linelength",
203
+ "maxiter",
204
+ "mclimit",
205
+ "mindist",
206
+ "mode",
207
+ "model",
208
+ "newrank",
209
+ "nodesep",
210
+ "normalize",
211
+ "notranslate",
212
+ "nslimit",
213
+ "nslimit1",
214
+ "oneblock",
215
+ "ordering",
216
+ "orientation",
217
+ "outputorder",
218
+ "overlap",
219
+ "overlap_scaling",
220
+ "overlap_shrink",
221
+ "pack",
222
+ "packmode",
223
+ "pad",
224
+ "page",
225
+ "pagedir",
226
+ "quadtree",
227
+ "quantum",
228
+ "rankdir",
229
+ "ranksep",
230
+ "ratio",
231
+ "remincross",
232
+ "repulsiveforce",
233
+ "resolution",
234
+ "root",
235
+ "rotate",
236
+ "rotation",
237
+ "scale",
238
+ "searchsize",
239
+ "sep",
240
+ "showboxes",
241
+ "size",
242
+ "smoothing",
243
+ "splines",
244
+ "start",
245
+ "stylesheet",
246
+ "truecolor",
247
+ "viewport",
248
+ "voro_margin",
249
+ "xdotversion",
250
+ ]
251
+
252
+
253
+ if __name__ == "__main__":
254
+ # Helper script to verify that the attribute group boundaries are correct.
255
+ common = (set(CLUSTER_ATTRIBUTES).intersection(GRAPH_ATTRIBUTES)) - set(COMMON_ATTRIBUTES)
256
+ print(f"CLUSTER_GRAPH_ATTRIBUTES = COMMON_ATTRIBUTES + {repr(sorted(common- set(COMMON_ATTRIBUTES)))}")
257
+ print(
258
+ f"CLUSTER_ATTRIBUTES = CLUSTER_GRAPH_ATTRIBUTES+{repr(sorted(set(CLUSTER_ATTRIBUTES)-common- set(COMMON_ATTRIBUTES)))}"
259
+ )
260
+ print(
261
+ f"GRAPH_ATTRIBUTES = CLUSTER_GRAPH_ATTRIBUTES + {repr(sorted(set(GRAPH_ATTRIBUTES)-common- set(COMMON_ATTRIBUTES)))}"
262
+ )
torch_flowgraph/cli.py ADDED
@@ -0,0 +1,44 @@
1
+ # SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF)
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Command-line interface for torch-flowgraph.
5
+
6
+ Usage::
7
+
8
+ torch-flowgraph validate path/to/graph.json
9
+ python -m torch_flowgraph validate path/to/graph.json
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import sys
16
+
17
+ from .graph import GraphBase
18
+ from .validation import validate_graph
19
+
20
+
21
+ def _cmd_validate(args) -> int:
22
+ graph = GraphBase.from_json_file(args.path)
23
+ ok = validate_graph(graph, verbose=not args.quiet)
24
+ return 0 if ok else 1
25
+
26
+
27
+ def main(argv=None) -> int:
28
+ parser = argparse.ArgumentParser(
29
+ prog="torch-flowgraph",
30
+ description="Inspect and validate torch-flowgraph JSON graphs.",
31
+ )
32
+ sub = parser.add_subparsers(dest="command", required=True)
33
+
34
+ validate = sub.add_parser("validate", help="Check a graph JSON file for inconsistencies.")
35
+ validate.add_argument("path", help="Path to a graph JSON file.")
36
+ validate.add_argument("-q", "--quiet", action="store_true", help="Suppress the report; only set the exit code.")
37
+ validate.set_defaults(func=_cmd_validate)
38
+
39
+ args = parser.parse_args(argv)
40
+ return args.func(args)
41
+
42
+
43
+ if __name__ == "__main__":
44
+ sys.exit(main())