computation-graph 54__tar.gz → 57__tar.gz
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.
- {computation-graph-54 → computation_graph-57}/PKG-INFO +1 -1
- {computation-graph-54 → computation_graph-57}/computation_graph/base_types.py +5 -1
- {computation-graph-54 → computation_graph-57}/computation_graph/composers/duplication.py +2 -1
- {computation-graph-54 → computation_graph-57}/computation_graph/graph.py +1 -1
- {computation-graph-54 → computation_graph-57}/computation_graph/graph_test.py +49 -0
- computation_graph-57/computation_graph/run.py +577 -0
- {computation-graph-54 → computation_graph-57}/computation_graph.egg-info/PKG-INFO +1 -1
- {computation-graph-54 → computation_graph-57}/computation_graph.egg-info/top_level.txt +1 -0
- {computation-graph-54 → computation_graph-57}/setup.py +1 -1
- computation-graph-54/computation_graph/run.py +0 -427
- {computation-graph-54 → computation_graph-57}/LICENSE.md +0 -0
- {computation-graph-54 → computation_graph-57}/README.md +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/__init__.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/composers/__init__.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/composers/composers_test.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/composers/condition.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/composers/condition_test.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/composers/debug.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/composers/lift.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/composers/logic.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/composers/memory.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/composers/memory_test.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/graph_runners.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/legacy.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/signature.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/trace/__init__.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/trace/ascii.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/trace/graphviz.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/trace/graphviz_test.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/trace/mermaid.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph/trace/trace_utils.py +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph.egg-info/SOURCES.txt +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph.egg-info/dependency_links.txt +0 -0
- {computation-graph-54 → computation_graph-57}/computation_graph.egg-info/requires.txt +0 -0
- {computation-graph-54 → computation_graph-57}/pyproject.toml +0 -0
- {computation-graph-54 → computation_graph-57}/setup.cfg +0 -0
|
@@ -96,9 +96,13 @@ class ComputationNode:
|
|
|
96
96
|
func: Callable
|
|
97
97
|
signature: NodeSignature
|
|
98
98
|
is_terminal: bool
|
|
99
|
+
computed_hash: int = dataclasses.field(init=False)
|
|
100
|
+
|
|
101
|
+
def __post_init__(self):
|
|
102
|
+
object.__setattr__(self, "computed_hash", hash(self.func))
|
|
99
103
|
|
|
100
104
|
def __hash__(self):
|
|
101
|
-
return
|
|
105
|
+
return self.computed_hash
|
|
102
106
|
|
|
103
107
|
def __repr__(self):
|
|
104
108
|
return self.name
|
|
@@ -44,6 +44,7 @@ _duplicate_node = gamla.compose_left(
|
|
|
44
44
|
graph.make_computation_node,
|
|
45
45
|
)
|
|
46
46
|
|
|
47
|
+
duplicate_node = _duplicate_node
|
|
47
48
|
|
|
48
49
|
_node_to_duplicated_node = gamla.compose_left(
|
|
49
50
|
gamla.remove(base_types.node_is_terminal),
|
|
@@ -104,4 +105,4 @@ def safe_replace_sources(
|
|
|
104
105
|
g = graph.replace_source(source, get_node_replacement(source), g)
|
|
105
106
|
return g
|
|
106
107
|
|
|
107
|
-
return
|
|
108
|
+
return base_types.merge_graphs(*(update_edge(e) for e in cg))
|
|
@@ -162,7 +162,7 @@ traverse_forward: Callable[
|
|
|
162
162
|
),
|
|
163
163
|
gamla.groupby_many_reduce(
|
|
164
164
|
opt_gamla.compose_left(gamla.head, gamla.wrap_tuple),
|
|
165
|
-
lambda destinations, e:
|
|
165
|
+
lambda destinations, e: {*(destinations if destinations else ()), e[1]},
|
|
166
166
|
),
|
|
167
167
|
gamla.dict_to_getter_with_default(()),
|
|
168
168
|
gamla.before(make_computation_node),
|
|
@@ -63,6 +63,55 @@ def test_simple():
|
|
|
63
63
|
)
|
|
64
64
|
|
|
65
65
|
|
|
66
|
+
async def test_async_run_as_soon_as_possible(capsys):
|
|
67
|
+
# This test is to make sure that the async node runs as soon as possible.
|
|
68
|
+
# nodes in different topological layers should run concurrently if not dependent on each other.
|
|
69
|
+
async def concurrent1(x):
|
|
70
|
+
print("start concurrent1") # noqa
|
|
71
|
+
await asyncio.sleep(0.1)
|
|
72
|
+
print("end concurrent1") # noqa
|
|
73
|
+
return "concurrent1"
|
|
74
|
+
|
|
75
|
+
async def concurrent2(x):
|
|
76
|
+
print("start concurrent2") # noqa
|
|
77
|
+
await asyncio.sleep(0.1)
|
|
78
|
+
print("end concurrent2") # noqa
|
|
79
|
+
return "concurrent2"
|
|
80
|
+
|
|
81
|
+
def sink(x, y):
|
|
82
|
+
return f"x={x}, y={y}"
|
|
83
|
+
|
|
84
|
+
g = base_types.merge_graphs(
|
|
85
|
+
composers.compose_unary(concurrent1, lambda: "x"),
|
|
86
|
+
composers.compose_unary(concurrent2, lambda y: y, lambda: "y"),
|
|
87
|
+
composers.compose_left(concurrent1, sink, key="x"),
|
|
88
|
+
composers.compose_left(concurrent2, sink, key="y"),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
await graph_runners.nullary(g, sink)
|
|
92
|
+
|
|
93
|
+
# Capture the output
|
|
94
|
+
captured = capsys.readouterr()
|
|
95
|
+
|
|
96
|
+
# Assertions
|
|
97
|
+
assert "start concurrent1" in captured.out
|
|
98
|
+
assert "start concurrent2" in captured.out
|
|
99
|
+
assert "end concurrent1" in captured.out
|
|
100
|
+
assert "end concurrent2" in captured.out
|
|
101
|
+
assert captured.out.index("start concurrent1") < captured.out.index(
|
|
102
|
+
"end concurrent1"
|
|
103
|
+
)
|
|
104
|
+
assert captured.out.index("start concurrent2") < captured.out.index(
|
|
105
|
+
"end concurrent2"
|
|
106
|
+
)
|
|
107
|
+
assert captured.out.index("start concurrent1") < captured.out.index(
|
|
108
|
+
"end concurrent2"
|
|
109
|
+
)
|
|
110
|
+
assert captured.out.index("start concurrent2") < captured.out.index(
|
|
111
|
+
"end concurrent1"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
66
115
|
async def test_simple_async():
|
|
67
116
|
assert (
|
|
68
117
|
await graph_runners.unary(
|
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import dataclasses
|
|
3
|
+
import functools
|
|
4
|
+
import inspect
|
|
5
|
+
import itertools
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import time
|
|
9
|
+
import typing
|
|
10
|
+
from typing import (
|
|
11
|
+
Any,
|
|
12
|
+
Awaitable,
|
|
13
|
+
Callable,
|
|
14
|
+
Dict,
|
|
15
|
+
FrozenSet,
|
|
16
|
+
Iterable,
|
|
17
|
+
Mapping,
|
|
18
|
+
Optional,
|
|
19
|
+
Set,
|
|
20
|
+
Tuple,
|
|
21
|
+
Type,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
import gamla
|
|
25
|
+
import immutables
|
|
26
|
+
import termcolor
|
|
27
|
+
import toposort
|
|
28
|
+
import typeguard
|
|
29
|
+
from gamla.optimized import sync as opt_gamla
|
|
30
|
+
|
|
31
|
+
from computation_graph import base_types, composers, graph, signature
|
|
32
|
+
|
|
33
|
+
CG_NO_RESULT = "CG_NO_RESULT"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class _DepNotFoundError(Exception):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
_NodeToResults = Dict[base_types.ComputationNode, base_types.Result]
|
|
41
|
+
_ComputationInput = Tuple[Tuple[base_types.Result, ...], Dict[str, base_types.Result]]
|
|
42
|
+
_SingleNodeSideEffect = Callable[[base_types.ComputationNode, Any], None]
|
|
43
|
+
_ComputationInputSpec = Tuple[
|
|
44
|
+
Tuple[base_types.ComputationNode, ...], Dict[str, base_types.ComputationNode]
|
|
45
|
+
]
|
|
46
|
+
_NodeExecutor = Callable[
|
|
47
|
+
[
|
|
48
|
+
Mapping[
|
|
49
|
+
base_types.ComputationNode,
|
|
50
|
+
base_types.Result | Awaitable[base_types.Result],
|
|
51
|
+
],
|
|
52
|
+
base_types.ComputationNode,
|
|
53
|
+
],
|
|
54
|
+
base_types.Result,
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _transpose_graph(
|
|
59
|
+
graph: Dict[base_types.ComputationNode, Set[base_types.ComputationNode]]
|
|
60
|
+
) -> Dict[base_types.ComputationNode, Set[base_types.ComputationNode]]:
|
|
61
|
+
return opt_gamla.pipe(
|
|
62
|
+
graph, dict.keys, opt_gamla.groupby_many(graph.get), opt_gamla.valmap(set)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
_toposort_nodes: Callable[
|
|
67
|
+
[base_types.GraphType], Tuple[FrozenSet[base_types.ComputationNode], ...]
|
|
68
|
+
] = opt_gamla.compose_left(
|
|
69
|
+
opt_gamla.groupby_many(base_types.edge_sources),
|
|
70
|
+
opt_gamla.valmap(
|
|
71
|
+
opt_gamla.compose_left(opt_gamla.map(base_types.edge_destination), set)
|
|
72
|
+
),
|
|
73
|
+
_transpose_graph,
|
|
74
|
+
lambda g: toposort.toposort_flatten(g, sort=False),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _type_check(node: base_types.ComputationNode, result):
|
|
79
|
+
return_typing = typing.get_type_hints(node.func).get("return", None)
|
|
80
|
+
if return_typing:
|
|
81
|
+
try:
|
|
82
|
+
typeguard.check_type(str(node), result, return_typing)
|
|
83
|
+
except TypeError as e:
|
|
84
|
+
logging.error([node.func.__code__, e])
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _profile(node, time_started: float):
|
|
88
|
+
elapsed = time.perf_counter() - time_started
|
|
89
|
+
if elapsed <= 0.1:
|
|
90
|
+
return
|
|
91
|
+
logging.warning(
|
|
92
|
+
termcolor.colored(
|
|
93
|
+
f"function took {elapsed:.6f} seconds: {base_types.pretty_print_function_name(node.func)}",
|
|
94
|
+
color="red",
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
_group_by_is_async_result = opt_gamla.groupby(lambda k_v: inspect.isawaitable(k_v[1]))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
_is_graph_async = opt_gamla.compose_left(
|
|
103
|
+
opt_gamla.mapcat(lambda edge: (edge.source, *edge.args)),
|
|
104
|
+
opt_gamla.remove(gamla.equals(None)),
|
|
105
|
+
opt_gamla.map(base_types.node_implementation),
|
|
106
|
+
gamla.anymap(asyncio.iscoroutinefunction),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _future_edge_to_regular_edge_with_placeholder(
|
|
111
|
+
source_to_placeholder: dict[base_types.ComputationNode, base_types.ComputationNode]
|
|
112
|
+
) -> Callable[[base_types.ComputationEdge], base_types.ComputationEdge]:
|
|
113
|
+
def replace_source(edge):
|
|
114
|
+
assert edge.source, "only supports singular edges for now"
|
|
115
|
+
|
|
116
|
+
return dataclasses.replace(
|
|
117
|
+
edge, is_future=False, source=source_to_placeholder[edge.source]
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return replace_source
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
_map_future_source_to_placeholder = opt_gamla.compose_left(
|
|
124
|
+
opt_gamla.map(
|
|
125
|
+
opt_gamla.pair_right(
|
|
126
|
+
opt_gamla.compose_left(
|
|
127
|
+
gamla.attrgetter("name"), graph.make_source_with_name
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
),
|
|
131
|
+
gamla.frozendict,
|
|
132
|
+
)
|
|
133
|
+
_graph_to_future_sources = opt_gamla.compose_left(
|
|
134
|
+
opt_gamla.filter(base_types.edge_is_future),
|
|
135
|
+
# We assume that future edges cannot be with multiple sources
|
|
136
|
+
opt_gamla.map(base_types.edge_source),
|
|
137
|
+
frozenset,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
"""Replace multiple edges pointing to a terminal with one edge that has multiple args"""
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _merge_edges_pointing_to_terminals(g: base_types.GraphType) -> base_types.GraphType:
|
|
145
|
+
return gamla.compose_left(
|
|
146
|
+
gamla.groupby(gamla.attrgetter("destination")),
|
|
147
|
+
gamla.itemmap(
|
|
148
|
+
gamla.star(
|
|
149
|
+
lambda dest, edges_for_dest: (
|
|
150
|
+
dest,
|
|
151
|
+
base_types.merge_graphs(
|
|
152
|
+
composers.make_or(
|
|
153
|
+
opt_gamla.maptuple(base_types.edge_source)(edges_for_dest),
|
|
154
|
+
merge_fn=(aggregate := lambda args: args),
|
|
155
|
+
),
|
|
156
|
+
composers.compose_left_unary(aggregate, dest),
|
|
157
|
+
)
|
|
158
|
+
if dest.is_terminal
|
|
159
|
+
else edges_for_dest,
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
),
|
|
163
|
+
dict.values,
|
|
164
|
+
gamla.concat,
|
|
165
|
+
tuple,
|
|
166
|
+
)(g)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _to_callable_with_side_effect_for_single_and_multiple(
|
|
170
|
+
single_node_side_effect: _SingleNodeSideEffect,
|
|
171
|
+
all_nodes_side_effect: Callable,
|
|
172
|
+
edges: base_types.GraphType,
|
|
173
|
+
handled_exceptions: Tuple[Type[Exception], ...],
|
|
174
|
+
) -> Callable[[_NodeToResults, _NodeToResults], _NodeToResults]:
|
|
175
|
+
edges = _merge_edges_pointing_to_terminals(edges)
|
|
176
|
+
single_node_side_effect = (
|
|
177
|
+
(lambda node, result: result)
|
|
178
|
+
if os.getenv(base_types.COMPUTATION_GRAPH_DEBUG_ENV_KEY) is None
|
|
179
|
+
else single_node_side_effect
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
future_sources = _graph_to_future_sources(edges)
|
|
183
|
+
future_source_to_placeholder = _map_future_source_to_placeholder(future_sources)
|
|
184
|
+
edges = gamla.pipe(
|
|
185
|
+
edges,
|
|
186
|
+
gamla.unique,
|
|
187
|
+
opt_gamla.map(
|
|
188
|
+
opt_gamla.when(
|
|
189
|
+
base_types.edge_is_future,
|
|
190
|
+
_future_edge_to_regular_edge_with_placeholder(
|
|
191
|
+
future_source_to_placeholder
|
|
192
|
+
),
|
|
193
|
+
)
|
|
194
|
+
),
|
|
195
|
+
tuple,
|
|
196
|
+
gamla.side_effect(_assert_composition_is_valid),
|
|
197
|
+
gamla.side_effect(base_types.assert_no_unwanted_ambiguity),
|
|
198
|
+
)
|
|
199
|
+
is_async = _is_graph_async(edges)
|
|
200
|
+
placeholder_to_future_source = opt_gamla.pipe(
|
|
201
|
+
future_source_to_placeholder, gamla.itemmap(lambda k_v: (k_v[1], k_v[0]))
|
|
202
|
+
)
|
|
203
|
+
get_node_executor = _make_get_node_executor(
|
|
204
|
+
edges, handled_exceptions, single_node_side_effect
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
topological_layers = opt_gamla.pipe(
|
|
208
|
+
edges,
|
|
209
|
+
_toposort_nodes,
|
|
210
|
+
gamla.remove(gamla.contains(placeholder_to_future_source)),
|
|
211
|
+
opt_gamla.maptuple(opt_gamla.pair_right(get_node_executor)),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
translate_source_to_placeholder = opt_gamla.compose_left(
|
|
215
|
+
opt_gamla.keyfilter(gamla.contains(future_sources)),
|
|
216
|
+
opt_gamla.keymap(future_source_to_placeholder.__getitem__),
|
|
217
|
+
)
|
|
218
|
+
all_node_side_effects_on_edges = gamla.side_effect(all_nodes_side_effect(edges))
|
|
219
|
+
|
|
220
|
+
if is_async:
|
|
221
|
+
|
|
222
|
+
async def final_runner(sources_to_values):
|
|
223
|
+
d = gamla.pipe(
|
|
224
|
+
topological_layers,
|
|
225
|
+
_run_graph(
|
|
226
|
+
translate_source_to_placeholder(sources_to_values),
|
|
227
|
+
handled_exceptions,
|
|
228
|
+
),
|
|
229
|
+
dict,
|
|
230
|
+
)
|
|
231
|
+
results_by_is_async = _group_by_is_async_result(d.items())
|
|
232
|
+
async_results = tuple(zip(*results_by_is_async.get(True, ())))
|
|
233
|
+
sync_results = dict(results_by_is_async.get(False, ()))
|
|
234
|
+
return all_node_side_effects_on_edges(
|
|
235
|
+
sync_results
|
|
236
|
+
| (
|
|
237
|
+
{
|
|
238
|
+
k: v
|
|
239
|
+
for (k, v) in zip(
|
|
240
|
+
async_results[0],
|
|
241
|
+
await asyncio.gather(
|
|
242
|
+
*async_results[1], return_exceptions=True
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
if not isinstance(v, Exception)
|
|
246
|
+
}
|
|
247
|
+
if async_results
|
|
248
|
+
else {}
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
else:
|
|
253
|
+
|
|
254
|
+
def final_runner(sources_to_values):
|
|
255
|
+
return gamla.pipe(
|
|
256
|
+
topological_layers,
|
|
257
|
+
_run_graph(
|
|
258
|
+
translate_source_to_placeholder(sources_to_values),
|
|
259
|
+
handled_exceptions,
|
|
260
|
+
),
|
|
261
|
+
dict,
|
|
262
|
+
all_node_side_effects_on_edges,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
return (_async_graph_reducer if is_async else _graph_reducer)(final_runner)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
_get_args_nodes: Callable[
|
|
269
|
+
[Tuple[base_types.ComputationEdge, ...]], Tuple[base_types.ComputationNode, ...]
|
|
270
|
+
] = gamla.compose_left(
|
|
271
|
+
opt_gamla.filter(base_types.edge_args), gamla.head, base_types.edge_args
|
|
272
|
+
)
|
|
273
|
+
_get_kwargs_nodes = opt_gamla.compose_left(
|
|
274
|
+
opt_gamla.filter(
|
|
275
|
+
gamla.compose_left(base_types.edge_key, gamla.not_equals("*args"))
|
|
276
|
+
),
|
|
277
|
+
gamla.map(gamla.juxt(base_types.edge_key, base_types.edge_source)),
|
|
278
|
+
gamla.frozendict,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _node_incoming_edges_to_input_spec(
|
|
283
|
+
node_incoming_edges: Tuple[base_types.ComputationEdge],
|
|
284
|
+
) -> _ComputationInputSpec:
|
|
285
|
+
if not len(node_incoming_edges):
|
|
286
|
+
return (), {}
|
|
287
|
+
first_incoming_edge = gamla.head(node_incoming_edges)
|
|
288
|
+
node = base_types.edge_destination(first_incoming_edge)
|
|
289
|
+
if node.signature.is_kwargs:
|
|
290
|
+
return (base_types.edge_source(first_incoming_edge),), {}
|
|
291
|
+
return (
|
|
292
|
+
_get_args_nodes(node_incoming_edges) if node.signature.is_args else (),
|
|
293
|
+
_get_kwargs_nodes(node_incoming_edges),
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _to_awaitable(v) -> Awaitable:
|
|
298
|
+
if inspect.isawaitable(v):
|
|
299
|
+
return v
|
|
300
|
+
f = asyncio.get_event_loop().create_future()
|
|
301
|
+
f.set_result(v)
|
|
302
|
+
return f
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _make_get_node_executor(
|
|
306
|
+
edges, handled_exceptions, single_node_side_effect: _SingleNodeSideEffect
|
|
307
|
+
):
|
|
308
|
+
node_to_incoming_edges = functools.cache(graph.get_incoming_edges_for_node(edges))
|
|
309
|
+
node_to_computation_input_spec_options: Callable[
|
|
310
|
+
[base_types.ComputationNode], Tuple[_ComputationInputSpec]
|
|
311
|
+
] = functools.cache(
|
|
312
|
+
gamla.compose_left(
|
|
313
|
+
node_to_incoming_edges,
|
|
314
|
+
opt_gamla.groupby(base_types.edge_key),
|
|
315
|
+
opt_gamla.valmap(gamla.sort_by(base_types.edge_priority)),
|
|
316
|
+
dict.values,
|
|
317
|
+
opt_gamla.star(itertools.product),
|
|
318
|
+
opt_gamla.maptuple(_node_incoming_edges_to_input_spec),
|
|
319
|
+
)
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
async def gather(
|
|
323
|
+
args: Tuple[Awaitable, ...], kwargs: Mapping[str, Awaitable]
|
|
324
|
+
) -> tuple[tuple[Any, ...], dict[str, Any]]:
|
|
325
|
+
if args or kwargs:
|
|
326
|
+
try:
|
|
327
|
+
gathered = await asyncio.gather(*args, *kwargs.values())
|
|
328
|
+
return gathered[: len(args)], dict(
|
|
329
|
+
zip(kwargs.keys(), gathered[len(args) :])
|
|
330
|
+
)
|
|
331
|
+
except (
|
|
332
|
+
_DepNotFoundError,
|
|
333
|
+
base_types.SkipComputationError,
|
|
334
|
+
*handled_exceptions,
|
|
335
|
+
):
|
|
336
|
+
# We delete the references to the upstream tasks to avoid circular reference (task->exception->traceback->task) and improve memory performance
|
|
337
|
+
del args, kwargs
|
|
338
|
+
raise _DepNotFoundError() from None
|
|
339
|
+
return (), {}
|
|
340
|
+
|
|
341
|
+
def node_to_input_sync(
|
|
342
|
+
accumulated_results: Mapping[base_types.ComputationNode, base_types.Result],
|
|
343
|
+
input_options: Iterable[_ComputationInputSpec],
|
|
344
|
+
) -> Optional[_ComputationInput]:
|
|
345
|
+
for input_spec in input_options:
|
|
346
|
+
args, kwargs = input_spec
|
|
347
|
+
try:
|
|
348
|
+
return tuple(accumulated_results[arg] for arg in args), {
|
|
349
|
+
k: accumulated_results[v] for k, v in kwargs.items()
|
|
350
|
+
}
|
|
351
|
+
except KeyError:
|
|
352
|
+
...
|
|
353
|
+
return None
|
|
354
|
+
|
|
355
|
+
async def node_to_input_async(
|
|
356
|
+
accumulated_results: Mapping[
|
|
357
|
+
base_types.ComputationNode, base_types.Result | Awaitable[base_types.Result]
|
|
358
|
+
],
|
|
359
|
+
input_options: Iterable[_ComputationInputSpec],
|
|
360
|
+
) -> Optional[_ComputationInput]:
|
|
361
|
+
for input_spec in input_options:
|
|
362
|
+
args_spec, kwargs_spec = input_spec
|
|
363
|
+
if all(
|
|
364
|
+
accumulated_results.get(arg, CG_NO_RESULT) is not CG_NO_RESULT
|
|
365
|
+
for arg in args_spec
|
|
366
|
+
) and all(
|
|
367
|
+
accumulated_results.get(kwarg, CG_NO_RESULT) is not CG_NO_RESULT
|
|
368
|
+
for kwarg in kwargs_spec.values()
|
|
369
|
+
):
|
|
370
|
+
try:
|
|
371
|
+
return await gather(
|
|
372
|
+
tuple(_to_awaitable(accumulated_results[a]) for a in args_spec),
|
|
373
|
+
{
|
|
374
|
+
k: _to_awaitable(accumulated_results[v])
|
|
375
|
+
for k, v in kwargs_spec.items()
|
|
376
|
+
},
|
|
377
|
+
)
|
|
378
|
+
except (
|
|
379
|
+
_DepNotFoundError,
|
|
380
|
+
base_types.SkipComputationError,
|
|
381
|
+
*handled_exceptions,
|
|
382
|
+
):
|
|
383
|
+
...
|
|
384
|
+
return None
|
|
385
|
+
|
|
386
|
+
@opt_gamla.after(asyncio.create_task)
|
|
387
|
+
async def await_deps_and_apply(
|
|
388
|
+
accumulated_results: Mapping[
|
|
389
|
+
base_types.ComputationNode, base_types.Result | Awaitable[base_types.Result]
|
|
390
|
+
],
|
|
391
|
+
node: base_types.ComputationNode,
|
|
392
|
+
) -> base_types.Result:
|
|
393
|
+
args_kwargs = await node_to_input_async(
|
|
394
|
+
accumulated_results, node_to_computation_input_spec_options(node)
|
|
395
|
+
)
|
|
396
|
+
# We delete the references to the upstream tasks to avoid circular reference (task->exception->traceback->task) and improve memory performance
|
|
397
|
+
del accumulated_results
|
|
398
|
+
if args_kwargs is None:
|
|
399
|
+
raise _DepNotFoundError()
|
|
400
|
+
|
|
401
|
+
args, kwargs = args_kwargs
|
|
402
|
+
before = time.perf_counter()
|
|
403
|
+
result = node.func(*args, **kwargs)
|
|
404
|
+
single_node_side_effect(node, result)
|
|
405
|
+
if inspect.isawaitable(result):
|
|
406
|
+
raise Exception(
|
|
407
|
+
f"{node} returned an awaitable result but is not an async function"
|
|
408
|
+
)
|
|
409
|
+
_profile(node, before)
|
|
410
|
+
return result
|
|
411
|
+
|
|
412
|
+
@opt_gamla.after(asyncio.create_task)
|
|
413
|
+
async def await_deps_and_await(
|
|
414
|
+
accumulated_results: Mapping[
|
|
415
|
+
base_types.ComputationNode, base_types.Result | Awaitable[base_types.Result]
|
|
416
|
+
],
|
|
417
|
+
node: base_types.ComputationNode,
|
|
418
|
+
) -> base_types.Result:
|
|
419
|
+
args_kwargs = await node_to_input_async(
|
|
420
|
+
accumulated_results, node_to_computation_input_spec_options(node)
|
|
421
|
+
)
|
|
422
|
+
# We delete the references to the upstream tasks to avoid circular reference (task->exception->traceback->task) and improve memory performance
|
|
423
|
+
del accumulated_results
|
|
424
|
+
if args_kwargs is None:
|
|
425
|
+
raise _DepNotFoundError()
|
|
426
|
+
|
|
427
|
+
args, kwargs = args_kwargs
|
|
428
|
+
before = time.perf_counter()
|
|
429
|
+
result = await node.func(*args, **kwargs)
|
|
430
|
+
single_node_side_effect(node, result)
|
|
431
|
+
_profile(node, before)
|
|
432
|
+
return result
|
|
433
|
+
|
|
434
|
+
@opt_gamla.after(asyncio.create_task)
|
|
435
|
+
async def get_deps_and_await(
|
|
436
|
+
accumulated_results: Mapping[base_types.ComputationNode, base_types.Result],
|
|
437
|
+
node: base_types.ComputationNode,
|
|
438
|
+
) -> base_types.Result:
|
|
439
|
+
args_kwargs = node_to_input_sync(
|
|
440
|
+
accumulated_results, node_to_computation_input_spec_options(node)
|
|
441
|
+
)
|
|
442
|
+
# We delete the references to the upstream tasks to avoid circular reference (task->exception->traceback->task) and improve memory performance
|
|
443
|
+
del accumulated_results
|
|
444
|
+
if args_kwargs is None:
|
|
445
|
+
raise _DepNotFoundError()
|
|
446
|
+
|
|
447
|
+
args, kwargs = args_kwargs
|
|
448
|
+
before = time.perf_counter()
|
|
449
|
+
result = await node.func(*args, **kwargs)
|
|
450
|
+
single_node_side_effect(node, result)
|
|
451
|
+
_profile(node, before)
|
|
452
|
+
return result
|
|
453
|
+
|
|
454
|
+
def get_deps_and_apply(
|
|
455
|
+
accumulated_results: Mapping[base_types.ComputationNode, base_types.Result],
|
|
456
|
+
node: base_types.ComputationNode,
|
|
457
|
+
) -> base_types.Result:
|
|
458
|
+
args_kwargs = node_to_input_sync(
|
|
459
|
+
accumulated_results, node_to_computation_input_spec_options(node)
|
|
460
|
+
)
|
|
461
|
+
# We delete the references to the upstream tasks to avoid circular reference (task->exception->traceback->task) and improve memory performance
|
|
462
|
+
del accumulated_results
|
|
463
|
+
if args_kwargs is None:
|
|
464
|
+
raise _DepNotFoundError()
|
|
465
|
+
|
|
466
|
+
args, kwargs = args_kwargs
|
|
467
|
+
before = time.perf_counter()
|
|
468
|
+
result = node.func(*args, **kwargs)
|
|
469
|
+
single_node_side_effect(node, result)
|
|
470
|
+
if inspect.isawaitable(result):
|
|
471
|
+
raise Exception(
|
|
472
|
+
f"{node} returned an awaitable result but is not an async function"
|
|
473
|
+
)
|
|
474
|
+
_profile(node, before)
|
|
475
|
+
return result
|
|
476
|
+
|
|
477
|
+
all_nodes = graph.get_all_nodes(edges)
|
|
478
|
+
async_nodes = {n for n in all_nodes if asyncio.iscoroutinefunction(n.func)}
|
|
479
|
+
sync = all_nodes - async_nodes
|
|
480
|
+
tf = graph.traverse_forward(edges)
|
|
481
|
+
downstream_from_async = set(gamla.graph_traverse_many(async_nodes, tf))
|
|
482
|
+
|
|
483
|
+
async_and_downstream = async_nodes & downstream_from_async
|
|
484
|
+
async_not_downstream = async_nodes - downstream_from_async
|
|
485
|
+
sync_and_downstream = sync & downstream_from_async
|
|
486
|
+
sync_not_downstream = sync - downstream_from_async
|
|
487
|
+
|
|
488
|
+
def get_executor(node: base_types.ComputationNode) -> _NodeExecutor:
|
|
489
|
+
if node in async_and_downstream:
|
|
490
|
+
return await_deps_and_await
|
|
491
|
+
if node in async_not_downstream:
|
|
492
|
+
return get_deps_and_await
|
|
493
|
+
if node in sync_and_downstream:
|
|
494
|
+
return await_deps_and_apply
|
|
495
|
+
if node in sync_not_downstream:
|
|
496
|
+
# This is fully sync so it only uses sync results from the mapping, its typing says the whole mapping is sync.
|
|
497
|
+
return get_deps_and_apply # type: ignore
|
|
498
|
+
raise Exception("no executor found")
|
|
499
|
+
|
|
500
|
+
return get_executor
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _run_graph(inputs: dict, handled_exceptions):
|
|
504
|
+
def run_graph(
|
|
505
|
+
nodes: tuple[tuple[base_types.ComputationNode, _NodeExecutor]]
|
|
506
|
+
) -> _NodeToResults:
|
|
507
|
+
accumulated_results = inputs.copy()
|
|
508
|
+
for node_executor in nodes:
|
|
509
|
+
try:
|
|
510
|
+
accumulated_results[node_executor[0]] = node_executor[1](
|
|
511
|
+
accumulated_results, node_executor[0]
|
|
512
|
+
)
|
|
513
|
+
except (
|
|
514
|
+
_DepNotFoundError,
|
|
515
|
+
base_types.SkipComputationError,
|
|
516
|
+
*handled_exceptions,
|
|
517
|
+
):
|
|
518
|
+
pass
|
|
519
|
+
return accumulated_results
|
|
520
|
+
|
|
521
|
+
return run_graph
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def _graph_reducer(graph_callable):
|
|
525
|
+
def reducer(prev: _NodeToResults, sources: _NodeToResults) -> _NodeToResults:
|
|
526
|
+
return {**prev, **(graph_callable({**prev, **sources}))}
|
|
527
|
+
|
|
528
|
+
return reducer
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _async_graph_reducer(graph_callable):
|
|
532
|
+
async def reducer(prev: _NodeToResults, sources: _NodeToResults) -> _NodeToResults:
|
|
533
|
+
return {**prev, **(await graph_callable({**prev, **sources}))}
|
|
534
|
+
|
|
535
|
+
return reducer
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
to_callable_with_side_effect = gamla.curry(
|
|
539
|
+
_to_callable_with_side_effect_for_single_and_multiple
|
|
540
|
+
)(_type_check)
|
|
541
|
+
|
|
542
|
+
# Use the second line if you want to see the winning path in the computation graph (a little slower).
|
|
543
|
+
to_callable = to_callable_with_side_effect(gamla.just(gamla.just(None)))
|
|
544
|
+
# to_callable = to_callable_with_side_effect(graphviz.computation_trace('utterance_computation.dot'))
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def _node_is_properly_composed(
|
|
548
|
+
node_to_incoming_edges: base_types.GraphType,
|
|
549
|
+
) -> Callable[[base_types.ComputationNode], bool]:
|
|
550
|
+
return gamla.compose_left(
|
|
551
|
+
graph.unbound_signature(node_to_incoming_edges),
|
|
552
|
+
signature.parameters,
|
|
553
|
+
gamla.len_equals(0),
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def _assert_composition_is_valid(g: base_types.GraphType):
|
|
558
|
+
return opt_gamla.pipe(
|
|
559
|
+
g,
|
|
560
|
+
graph.get_all_nodes,
|
|
561
|
+
opt_gamla.remove(
|
|
562
|
+
_node_is_properly_composed(graph.get_incoming_edges_for_node(g))
|
|
563
|
+
),
|
|
564
|
+
opt_gamla.map(gamla.wrap_str("{0} at {0.func.__code__}")),
|
|
565
|
+
tuple,
|
|
566
|
+
gamla.assert_that_with_message(
|
|
567
|
+
gamla.wrap_str("Bad composition for: {}"), gamla.len_equals(0)
|
|
568
|
+
),
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def to_callable_strict(
|
|
573
|
+
g: base_types.GraphType,
|
|
574
|
+
) -> Callable[[_NodeToResults, _NodeToResults], _NodeToResults]:
|
|
575
|
+
return gamla.compose(
|
|
576
|
+
gamla.star(to_callable(g, frozenset())), gamla.map(immutables.Map), gamla.pack
|
|
577
|
+
)
|
|
@@ -7,7 +7,7 @@ with open("README.md", "r") as fh:
|
|
|
7
7
|
setuptools.setup(
|
|
8
8
|
name="computation-graph",
|
|
9
9
|
python_requires=">=3",
|
|
10
|
-
version="
|
|
10
|
+
version="57",
|
|
11
11
|
long_description=_LONG_DESCRIPTION,
|
|
12
12
|
long_description_content_type="text/markdown",
|
|
13
13
|
packages=setuptools.find_namespace_packages(),
|
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import dataclasses
|
|
3
|
-
import functools
|
|
4
|
-
import itertools
|
|
5
|
-
import logging
|
|
6
|
-
import os
|
|
7
|
-
import time
|
|
8
|
-
import typing
|
|
9
|
-
from typing import Any, Callable, Dict, FrozenSet, Set, Tuple, Type
|
|
10
|
-
|
|
11
|
-
import gamla
|
|
12
|
-
import immutables
|
|
13
|
-
import termcolor
|
|
14
|
-
import toposort
|
|
15
|
-
import typeguard
|
|
16
|
-
from gamla.optimized import async_functions as opt_async_gamla
|
|
17
|
-
from gamla.optimized import sync as opt_gamla
|
|
18
|
-
|
|
19
|
-
from computation_graph import base_types, composers, graph, signature
|
|
20
|
-
from computation_graph.composers import debug
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class _DepNotFoundError(Exception):
|
|
24
|
-
pass
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
_NodeToResults = Dict[base_types.ComputationNode, base_types.Result]
|
|
28
|
-
_ComputationInput = Tuple[Tuple[base_types.Result, ...], Dict[str, base_types.Result]]
|
|
29
|
-
_SingleNodeSideEffect = Callable[[base_types.ComputationNode, Any], None]
|
|
30
|
-
_NodeToComputationInput = Callable[[base_types.ComputationNode], _ComputationInput]
|
|
31
|
-
_ComputationInputSpec = Tuple[
|
|
32
|
-
Tuple[base_types.ComputationNode, ...], Dict[str, base_types.ComputationNode]
|
|
33
|
-
]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def _transpose_graph(
|
|
37
|
-
graph: Dict[base_types.ComputationNode, Set[base_types.ComputationNode]]
|
|
38
|
-
) -> Dict[base_types.ComputationNode, Set[base_types.ComputationNode]]:
|
|
39
|
-
return opt_gamla.pipe(
|
|
40
|
-
graph, dict.keys, opt_gamla.groupby_many(graph.get), opt_gamla.valmap(set)
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
_toposort_nodes: Callable[
|
|
45
|
-
[base_types.GraphType], Tuple[FrozenSet[base_types.ComputationNode], ...]
|
|
46
|
-
] = opt_gamla.compose_left(
|
|
47
|
-
opt_gamla.groupby_many(base_types.edge_sources),
|
|
48
|
-
opt_gamla.valmap(
|
|
49
|
-
opt_gamla.compose_left(opt_gamla.map(base_types.edge_destination), set)
|
|
50
|
-
),
|
|
51
|
-
_transpose_graph,
|
|
52
|
-
toposort.toposort,
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _type_check(node: base_types.ComputationNode, result):
|
|
57
|
-
return_typing = typing.get_type_hints(node.func).get("return", None)
|
|
58
|
-
if return_typing:
|
|
59
|
-
try:
|
|
60
|
-
typeguard.check_type(str(node), result, return_typing)
|
|
61
|
-
except TypeError as e:
|
|
62
|
-
logging.error([node.func.__code__, e])
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def _profile(node, time_started: float):
|
|
66
|
-
elapsed = time.perf_counter() - time_started
|
|
67
|
-
if elapsed <= 0.1:
|
|
68
|
-
return
|
|
69
|
-
logging.warning(
|
|
70
|
-
termcolor.colored(
|
|
71
|
-
f"function took {elapsed:.6f} seconds: {base_types.pretty_print_function_name(node.func)}",
|
|
72
|
-
color="red",
|
|
73
|
-
)
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def _make_get_node_input_and_apply(
|
|
78
|
-
is_async: bool, side_effect: _SingleNodeSideEffect
|
|
79
|
-
) -> Callable[..., base_types.Result]:
|
|
80
|
-
if is_async:
|
|
81
|
-
|
|
82
|
-
@opt_async_gamla.star
|
|
83
|
-
async def run_node(
|
|
84
|
-
node_to_input: _NodeToComputationInput, node: base_types.ComputationNode
|
|
85
|
-
) -> base_types.Result:
|
|
86
|
-
args, kwargs = node_to_input(node)
|
|
87
|
-
before = time.perf_counter()
|
|
88
|
-
result = await gamla.to_awaitable(node.func(*args, **kwargs))
|
|
89
|
-
side_effect(node, result)
|
|
90
|
-
_profile(node, before)
|
|
91
|
-
return node, result
|
|
92
|
-
|
|
93
|
-
else:
|
|
94
|
-
|
|
95
|
-
@opt_gamla.star
|
|
96
|
-
def run_node(node_to_input: _NodeToComputationInput, node: base_types.ComputationNode) -> base_types.Result: # type: ignore
|
|
97
|
-
args, kwargs = node_to_input(node)
|
|
98
|
-
before = time.perf_counter()
|
|
99
|
-
result = node.func(*args, **kwargs)
|
|
100
|
-
side_effect(node, result)
|
|
101
|
-
_profile(node, before)
|
|
102
|
-
return node, result
|
|
103
|
-
|
|
104
|
-
return run_node
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
_is_graph_async = opt_gamla.compose_left(
|
|
108
|
-
opt_gamla.mapcat(lambda edge: (edge.source, *edge.args)),
|
|
109
|
-
opt_gamla.remove(gamla.equals(None)),
|
|
110
|
-
opt_gamla.map(base_types.node_implementation),
|
|
111
|
-
gamla.anymap(asyncio.iscoroutinefunction),
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _future_edge_to_regular_edge_with_placeholder(
|
|
116
|
-
source_to_placeholder: dict[base_types.ComputationNode, base_types.ComputationNode]
|
|
117
|
-
) -> Callable[[base_types.ComputationEdge], base_types.ComputationEdge]:
|
|
118
|
-
def replace_source(edge):
|
|
119
|
-
assert edge.source, "only supports singular edges for now"
|
|
120
|
-
|
|
121
|
-
return dataclasses.replace(
|
|
122
|
-
edge, is_future=False, source=source_to_placeholder[edge.source]
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
return replace_source
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
_map_future_source_to_placeholder = opt_gamla.compose_left(
|
|
129
|
-
opt_gamla.map(
|
|
130
|
-
opt_gamla.pair_right(
|
|
131
|
-
opt_gamla.compose_left(
|
|
132
|
-
gamla.attrgetter("name"), graph.make_source_with_name
|
|
133
|
-
)
|
|
134
|
-
)
|
|
135
|
-
),
|
|
136
|
-
gamla.frozendict,
|
|
137
|
-
)
|
|
138
|
-
_graph_to_future_sources = opt_gamla.compose_left(
|
|
139
|
-
opt_gamla.filter(base_types.edge_is_future),
|
|
140
|
-
# We assume that future edges cannot be with multiple sources
|
|
141
|
-
opt_gamla.map(base_types.edge_source),
|
|
142
|
-
frozenset,
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
"""Replace multiple edges pointing to a terminal with one edge that has multiple args"""
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def _merge_edges_pointing_to_terminals(g: base_types.GraphType) -> base_types.GraphType:
|
|
150
|
-
return gamla.compose_left(
|
|
151
|
-
gamla.groupby(gamla.attrgetter("destination")),
|
|
152
|
-
gamla.itemmap(
|
|
153
|
-
gamla.star(
|
|
154
|
-
lambda dest, edges_for_dest: (
|
|
155
|
-
dest,
|
|
156
|
-
base_types.merge_graphs(
|
|
157
|
-
composers.make_or(
|
|
158
|
-
opt_gamla.maptuple(base_types.edge_source)(edges_for_dest),
|
|
159
|
-
merge_fn=(aggregate := lambda args: args),
|
|
160
|
-
),
|
|
161
|
-
composers.compose_left_unary(aggregate, dest),
|
|
162
|
-
)
|
|
163
|
-
if dest.is_terminal
|
|
164
|
-
else edges_for_dest,
|
|
165
|
-
)
|
|
166
|
-
)
|
|
167
|
-
),
|
|
168
|
-
dict.values,
|
|
169
|
-
gamla.concat,
|
|
170
|
-
tuple,
|
|
171
|
-
)(g)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def _to_callable_with_side_effect_for_single_and_multiple(
|
|
175
|
-
single_node_side_effect: _SingleNodeSideEffect,
|
|
176
|
-
all_nodes_side_effect: Callable,
|
|
177
|
-
edges: base_types.GraphType,
|
|
178
|
-
handled_exceptions: Tuple[Type[Exception], ...],
|
|
179
|
-
) -> Callable[[_NodeToResults, _NodeToResults], _NodeToResults]:
|
|
180
|
-
edges = _merge_edges_pointing_to_terminals(edges)
|
|
181
|
-
single_node_side_effect = (
|
|
182
|
-
(lambda node, result: result)
|
|
183
|
-
if os.getenv(base_types.COMPUTATION_GRAPH_DEBUG_ENV_KEY) is None
|
|
184
|
-
else single_node_side_effect
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
future_sources = _graph_to_future_sources(edges)
|
|
188
|
-
future_source_to_placeholder = _map_future_source_to_placeholder(future_sources)
|
|
189
|
-
edges = gamla.pipe(
|
|
190
|
-
edges,
|
|
191
|
-
gamla.unique,
|
|
192
|
-
opt_gamla.map(
|
|
193
|
-
opt_gamla.when(
|
|
194
|
-
base_types.edge_is_future,
|
|
195
|
-
_future_edge_to_regular_edge_with_placeholder(
|
|
196
|
-
future_source_to_placeholder
|
|
197
|
-
),
|
|
198
|
-
)
|
|
199
|
-
),
|
|
200
|
-
tuple,
|
|
201
|
-
gamla.side_effect(_assert_composition_is_valid),
|
|
202
|
-
gamla.side_effect(base_types.assert_no_unwanted_ambiguity),
|
|
203
|
-
)
|
|
204
|
-
is_async = _is_graph_async(edges)
|
|
205
|
-
placeholder_to_future_source = opt_gamla.pipe(
|
|
206
|
-
future_source_to_placeholder, gamla.itemmap(lambda k_v: (k_v[1], k_v[0]))
|
|
207
|
-
)
|
|
208
|
-
topological_layers = opt_gamla.pipe(
|
|
209
|
-
edges,
|
|
210
|
-
_toposort_nodes,
|
|
211
|
-
gamla.map_filter_empty(
|
|
212
|
-
gamla.compose_left(
|
|
213
|
-
gamla.remove(gamla.contains(placeholder_to_future_source)), frozenset
|
|
214
|
-
)
|
|
215
|
-
),
|
|
216
|
-
tuple,
|
|
217
|
-
)
|
|
218
|
-
translate_source_to_placeholder = opt_gamla.compose_left(
|
|
219
|
-
opt_gamla.keyfilter(gamla.contains(future_sources)),
|
|
220
|
-
opt_gamla.keymap(future_source_to_placeholder.__getitem__),
|
|
221
|
-
)
|
|
222
|
-
all_node_side_effects_on_edges = all_nodes_side_effect(edges)
|
|
223
|
-
reduce_layers = _make_reduce_layers(
|
|
224
|
-
edges, handled_exceptions, is_async, single_node_side_effect
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
if is_async:
|
|
228
|
-
|
|
229
|
-
async def final_runner(sources_to_values):
|
|
230
|
-
return await gamla.pipe(
|
|
231
|
-
topological_layers,
|
|
232
|
-
reduce_layers(
|
|
233
|
-
immutables.Map(translate_source_to_placeholder(sources_to_values))
|
|
234
|
-
),
|
|
235
|
-
dict,
|
|
236
|
-
gamla.side_effect(all_node_side_effects_on_edges),
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
else:
|
|
240
|
-
|
|
241
|
-
def final_runner(sources_to_values):
|
|
242
|
-
return gamla.pipe(
|
|
243
|
-
topological_layers,
|
|
244
|
-
reduce_layers(
|
|
245
|
-
immutables.Map(translate_source_to_placeholder(sources_to_values))
|
|
246
|
-
),
|
|
247
|
-
dict,
|
|
248
|
-
gamla.side_effect(all_node_side_effects_on_edges),
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
return (_async_graph_reducer if is_async else _graph_reducer)(final_runner)
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
_get_args_nodes: Callable[
|
|
255
|
-
[Tuple[base_types.ComputationEdge, ...]], Tuple[base_types.ComputationNode, ...]
|
|
256
|
-
] = gamla.compose_left(
|
|
257
|
-
opt_gamla.filter(base_types.edge_args), gamla.head, base_types.edge_args
|
|
258
|
-
)
|
|
259
|
-
_get_kwargs_nodes = opt_gamla.compose_left(
|
|
260
|
-
opt_gamla.filter(
|
|
261
|
-
gamla.compose_left(base_types.edge_key, gamla.not_equals("*args"))
|
|
262
|
-
),
|
|
263
|
-
gamla.map(gamla.juxt(base_types.edge_key, base_types.edge_source)),
|
|
264
|
-
gamla.frozendict,
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
def _node_incoming_edges_to_input_spec(
|
|
269
|
-
node_incoming_edges: Tuple[base_types.ComputationEdge],
|
|
270
|
-
) -> _ComputationInputSpec:
|
|
271
|
-
if not len(node_incoming_edges):
|
|
272
|
-
return (), {}
|
|
273
|
-
first_incoming_edge = gamla.head(node_incoming_edges)
|
|
274
|
-
node = base_types.edge_destination(first_incoming_edge)
|
|
275
|
-
if node.signature.is_kwargs:
|
|
276
|
-
return (base_types.edge_source(first_incoming_edge),), {}
|
|
277
|
-
return (
|
|
278
|
-
_get_args_nodes(node_incoming_edges) if node.signature.is_args else (),
|
|
279
|
-
_get_kwargs_nodes(node_incoming_edges),
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
def _edges_to_accumulated_results_to_node_to_first_possible_input(
|
|
284
|
-
edges,
|
|
285
|
-
) -> Callable[[_NodeToResults], _NodeToComputationInput]:
|
|
286
|
-
node_to_incoming_edges = graph.get_incoming_edges_for_node(edges)
|
|
287
|
-
node_to_computation_input_spec_options: Callable[
|
|
288
|
-
[base_types.ComputationNode], Tuple[_ComputationInputSpec]
|
|
289
|
-
] = functools.cache(
|
|
290
|
-
gamla.compose_left(
|
|
291
|
-
node_to_incoming_edges,
|
|
292
|
-
opt_gamla.groupby(base_types.edge_key),
|
|
293
|
-
opt_gamla.valmap(gamla.sort_by(base_types.edge_priority)),
|
|
294
|
-
dict.values,
|
|
295
|
-
opt_gamla.star(itertools.product),
|
|
296
|
-
opt_gamla.maptuple(_node_incoming_edges_to_input_spec),
|
|
297
|
-
)
|
|
298
|
-
)
|
|
299
|
-
# TODO(eli): Add sync.translate_exception/except that avoids inspect.iscoroutinefunction so this can be inlined (ENG-5212)
|
|
300
|
-
head_or_dep_not_found = gamla.translate_exception(
|
|
301
|
-
gamla.head, StopIteration, _DepNotFoundError
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
def make_node_to_first_possible_input(
|
|
305
|
-
accumulated_results: _NodeToResults,
|
|
306
|
-
) -> _NodeToComputationInput:
|
|
307
|
-
return opt_gamla.compose_left(
|
|
308
|
-
node_to_computation_input_spec_options,
|
|
309
|
-
opt_gamla.map(
|
|
310
|
-
gamla.excepts(
|
|
311
|
-
KeyError,
|
|
312
|
-
lambda _: None,
|
|
313
|
-
opt_gamla.packstack(
|
|
314
|
-
opt_gamla.maptuple(accumulated_results.__getitem__),
|
|
315
|
-
opt_gamla.valmap(accumulated_results.__getitem__),
|
|
316
|
-
),
|
|
317
|
-
)
|
|
318
|
-
),
|
|
319
|
-
opt_gamla.remove(gamla.equals(None)),
|
|
320
|
-
head_or_dep_not_found,
|
|
321
|
-
tuple,
|
|
322
|
-
)
|
|
323
|
-
|
|
324
|
-
return make_node_to_first_possible_input
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
def _make_reduce_layers(
|
|
328
|
-
edges: base_types.GraphType,
|
|
329
|
-
handled_exceptions: Tuple[Type[Exception], ...],
|
|
330
|
-
is_async: bool,
|
|
331
|
-
single_node_side_effect: _SingleNodeSideEffect,
|
|
332
|
-
) -> Callable[[immutables.Map], Callable[[Tuple[FrozenSet, ...]], immutables.Map]]:
|
|
333
|
-
get_node_input_and_apply = _make_get_node_input_and_apply(
|
|
334
|
-
is_async, single_node_side_effect
|
|
335
|
-
)
|
|
336
|
-
accumulated_results_to_node_to_input = (
|
|
337
|
-
_edges_to_accumulated_results_to_node_to_first_possible_input(edges)
|
|
338
|
-
)
|
|
339
|
-
single_layer_reducer = debug.name_callable(
|
|
340
|
-
gamla.compose_left(
|
|
341
|
-
gamla.pack,
|
|
342
|
-
gamla.juxt(
|
|
343
|
-
gamla.head,
|
|
344
|
-
gamla.compose_left(
|
|
345
|
-
opt_gamla.packstack(
|
|
346
|
-
accumulated_results_to_node_to_input, gamla.identity
|
|
347
|
-
),
|
|
348
|
-
gamla.explode(1),
|
|
349
|
-
gamla.map_filter_empty(
|
|
350
|
-
gamla.first(
|
|
351
|
-
get_node_input_and_apply,
|
|
352
|
-
gamla.just(None),
|
|
353
|
-
exception_type=(
|
|
354
|
-
*handled_exceptions,
|
|
355
|
-
base_types.SkipComputationError,
|
|
356
|
-
_DepNotFoundError,
|
|
357
|
-
),
|
|
358
|
-
)
|
|
359
|
-
),
|
|
360
|
-
tuple,
|
|
361
|
-
),
|
|
362
|
-
),
|
|
363
|
-
opt_gamla.star(
|
|
364
|
-
lambda prev_results, current_results: prev_results.update(
|
|
365
|
-
current_results
|
|
366
|
-
)
|
|
367
|
-
),
|
|
368
|
-
),
|
|
369
|
-
"single_layer_reducer",
|
|
370
|
-
)
|
|
371
|
-
return gamla.curry(gamla.reduce_curried)(single_layer_reducer)
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
def _graph_reducer(graph_callable):
|
|
375
|
-
def reducer(prev: _NodeToResults, sources: _NodeToResults) -> _NodeToResults:
|
|
376
|
-
return {**prev, **(graph_callable({**prev, **sources}))}
|
|
377
|
-
|
|
378
|
-
return reducer
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
def _async_graph_reducer(graph_callable):
|
|
382
|
-
async def reducer(prev: _NodeToResults, sources: _NodeToResults) -> _NodeToResults:
|
|
383
|
-
return {**prev, **(await graph_callable({**prev, **sources}))}
|
|
384
|
-
|
|
385
|
-
return reducer
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
to_callable_with_side_effect = gamla.curry(
|
|
389
|
-
_to_callable_with_side_effect_for_single_and_multiple
|
|
390
|
-
)(_type_check)
|
|
391
|
-
|
|
392
|
-
# Use the second line if you want to see the winning path in the computation graph (a little slower).
|
|
393
|
-
to_callable = to_callable_with_side_effect(gamla.just(gamla.just(None)))
|
|
394
|
-
# to_callable = to_callable_with_side_effect(graphviz.computation_trace('utterance_computation.dot'))
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
def _node_is_properly_composed(
|
|
398
|
-
node_to_incoming_edges: base_types.GraphType,
|
|
399
|
-
) -> Callable[[base_types.ComputationNode], bool]:
|
|
400
|
-
return gamla.compose_left(
|
|
401
|
-
graph.unbound_signature(node_to_incoming_edges),
|
|
402
|
-
signature.parameters,
|
|
403
|
-
gamla.len_equals(0),
|
|
404
|
-
)
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
def _assert_composition_is_valid(g: base_types.GraphType):
|
|
408
|
-
return opt_gamla.pipe(
|
|
409
|
-
g,
|
|
410
|
-
graph.get_all_nodes,
|
|
411
|
-
opt_gamla.remove(
|
|
412
|
-
_node_is_properly_composed(graph.get_incoming_edges_for_node(g))
|
|
413
|
-
),
|
|
414
|
-
opt_gamla.map(gamla.wrap_str("{0} at {0.func.__code__}")),
|
|
415
|
-
tuple,
|
|
416
|
-
gamla.assert_that_with_message(
|
|
417
|
-
gamla.wrap_str("Bad composition for: {}"), gamla.len_equals(0)
|
|
418
|
-
),
|
|
419
|
-
)
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
def to_callable_strict(
|
|
423
|
-
g: base_types.GraphType,
|
|
424
|
-
) -> Callable[[_NodeToResults, _NodeToResults], _NodeToResults]:
|
|
425
|
-
return gamla.compose(
|
|
426
|
-
gamla.star(to_callable(g, frozenset())), gamla.map(immutables.Map), gamla.pack
|
|
427
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{computation-graph-54 → computation_graph-57}/computation_graph.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|