computation-graph 67__tar.gz → 69__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-67 → computation_graph-69}/PKG-INFO +1 -1
- {computation_graph-67 → computation_graph-69}/computation_graph/composers/__init__.py +29 -37
- computation_graph-69/computation_graph/composers/duplication.py +154 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/graph.py +70 -39
- {computation_graph-67 → computation_graph-69}/computation_graph/graph_runners.py +2 -2
- {computation_graph-67 → computation_graph-69}/computation_graph/graph_test.py +27 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/run.py +6 -4
- {computation_graph-67 → computation_graph-69}/computation_graph/signature.py +36 -1
- {computation_graph-67 → computation_graph-69}/computation_graph.egg-info/PKG-INFO +1 -1
- {computation_graph-67 → computation_graph-69}/setup.py +1 -1
- computation_graph-67/computation_graph/composers/duplication.py +0 -124
- {computation_graph-67 → computation_graph-69}/LICENSE.md +0 -0
- {computation_graph-67 → computation_graph-69}/README.md +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/__init__.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/base_types.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/composers/composers_test.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/composers/condition.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/composers/condition_test.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/composers/debug.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/composers/lift.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/composers/lift_test.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/composers/logic.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/composers/memory.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/composers/memory_test.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/infer_sink.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/infer_sink_test.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/legacy.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/trace/__init__.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/trace/ascii.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/trace/graphviz.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/trace/graphviz_test.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/trace/mermaid.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph/trace/trace_utils.py +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph.egg-info/SOURCES.txt +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph.egg-info/dependency_links.txt +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph.egg-info/requires.txt +0 -0
- {computation_graph-67 → computation_graph-69}/computation_graph.egg-info/top_level.txt +0 -0
- {computation_graph-67 → computation_graph-69}/pyproject.toml +0 -0
- {computation_graph-67 → computation_graph-69}/setup.cfg +0 -0
|
@@ -286,44 +286,36 @@ def _infer_composition_edges(
|
|
|
286
286
|
unbound_signature = graph.unbound_signature(
|
|
287
287
|
graph.get_incoming_edges_for_node(destination.edges)
|
|
288
288
|
)
|
|
289
|
+
source_nodes = (
|
|
290
|
+
graph.get_all_nodes(source.edges)
|
|
291
|
+
if isinstance(source, GraphType)
|
|
292
|
+
else frozenset()
|
|
293
|
+
)
|
|
294
|
+
skip_cycle_check = isinstance(source, base_types.ComputationNode)
|
|
295
|
+
new_edges = set()
|
|
296
|
+
# The connectable-node set is order-independent (result is a frozenset), so we
|
|
297
|
+
# can iterate the cached node set instead of rescanning the edges.
|
|
298
|
+
for node in graph.get_all_nodes(destination.edges):
|
|
299
|
+
node_signature = unbound_signature(node)
|
|
300
|
+
if not (
|
|
301
|
+
key in node_signature.kwargs
|
|
302
|
+
or (key is None and signature.is_unary(node_signature))
|
|
303
|
+
):
|
|
304
|
+
continue
|
|
305
|
+
# Do not add edges to nodes from source that are already present in destination (cycle).
|
|
306
|
+
if not skip_cycle_check and node in source_nodes:
|
|
307
|
+
continue
|
|
308
|
+
new_edges.add(
|
|
309
|
+
try_connect(destination=node, unbound_destination_signature=node_signature)
|
|
310
|
+
)
|
|
311
|
+
if not new_edges:
|
|
312
|
+
raise AssertionError(
|
|
313
|
+
f"Cannot compose, destination signature does not contain key '{key}'"
|
|
314
|
+
)
|
|
289
315
|
return graph.merge_graphs(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
gamla.sync.mapcat(graph.get_edge_nodes),
|
|
294
|
-
gamla.unique,
|
|
295
|
-
gamla.sync.filter(
|
|
296
|
-
gamla.sync.compose_left(
|
|
297
|
-
unbound_signature,
|
|
298
|
-
lambda sig: key in sig.kwargs
|
|
299
|
-
or (key is None and signature.is_unary(sig)),
|
|
300
|
-
)
|
|
301
|
-
),
|
|
302
|
-
# Do not add edges to nodes from source that are already present in destination (cycle).
|
|
303
|
-
gamla.sync.filter(
|
|
304
|
-
lambda node: isinstance(source, base_types.ComputationNode)
|
|
305
|
-
or node
|
|
306
|
-
not in graph.get_all_nodes(
|
|
307
|
-
source.edges if isinstance(source, GraphType) else source
|
|
308
|
-
)
|
|
309
|
-
),
|
|
310
|
-
gamla.sync.map(
|
|
311
|
-
lambda destination_node: try_connect(
|
|
312
|
-
destination=destination_node,
|
|
313
|
-
unbound_destination_signature=unbound_signature(destination_node),
|
|
314
|
-
)
|
|
315
|
-
),
|
|
316
|
-
frozenset,
|
|
317
|
-
gamla.sync.check(
|
|
318
|
-
gamla.identity,
|
|
319
|
-
AssertionError(
|
|
320
|
-
f"Cannot compose, destination signature does not contain key '{key}'"
|
|
321
|
-
),
|
|
322
|
-
),
|
|
323
|
-
# Sink is a placeholder here; `merge_graphs` below sets the real sink
|
|
324
|
-
# via `sink_node_or_graph`.
|
|
325
|
-
lambda edges: GraphType(edges, None), # type: ignore[arg-type]
|
|
326
|
-
),
|
|
316
|
+
# Sink is a placeholder here; `merge_graphs` below sets the real sink
|
|
317
|
+
# via `sink_node_or_graph`.
|
|
318
|
+
GraphType(frozenset(new_edges), None), # type: ignore[arg-type]
|
|
327
319
|
_get_edges_from_node_or_graph(source),
|
|
328
320
|
destination,
|
|
329
321
|
sink_node_or_graph=_determine_sink(source, destination, is_future),
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import functools
|
|
3
|
+
import inspect
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
import gamla
|
|
7
|
+
|
|
8
|
+
from computation_graph import base_types, graph
|
|
9
|
+
from computation_graph.composers import debug
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def duplicate_function(func):
|
|
13
|
+
if inspect.iscoroutinefunction(func):
|
|
14
|
+
|
|
15
|
+
@functools.wraps(func)
|
|
16
|
+
async def inner(*args, **kwargs):
|
|
17
|
+
return await func(*args, **kwargs)
|
|
18
|
+
|
|
19
|
+
else:
|
|
20
|
+
|
|
21
|
+
@functools.wraps(func)
|
|
22
|
+
def inner(*args, **kwargs):
|
|
23
|
+
return func(*args, **kwargs)
|
|
24
|
+
|
|
25
|
+
return debug.name_callable(inner, f"duplicate of {func.__name__}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _duplicate_computation_edge(get_duplicated_node):
|
|
29
|
+
return gamla.compose_left(
|
|
30
|
+
gamla.dataclass_transform("source", get_duplicated_node),
|
|
31
|
+
gamla.dataclass_transform("destination", get_duplicated_node),
|
|
32
|
+
gamla.dataclass_transform(
|
|
33
|
+
"args", gamla.compose_left(gamla.map(get_duplicated_node), tuple)
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _signature_is_empty(signature: base_types.NodeSignature) -> bool:
|
|
39
|
+
return not (
|
|
40
|
+
signature.kwargs
|
|
41
|
+
or signature.optional_kwargs
|
|
42
|
+
or signature.is_args
|
|
43
|
+
or signature.is_kwargs
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _duplicate_node(node: base_types.ComputationNode) -> base_types.ComputationNode:
|
|
48
|
+
if _signature_is_empty(node.signature):
|
|
49
|
+
return node
|
|
50
|
+
inner = duplicate_function(node.func)
|
|
51
|
+
return dataclasses.replace(node, name=inner.__name__, func=inner)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _node_to_duplicated_node(nodes):
|
|
55
|
+
return {node: _duplicate_node(node) for node in nodes if not node.is_terminal}.get
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def duplicate_graph(original: base_types.GraphType) -> base_types.GraphType:
|
|
59
|
+
|
|
60
|
+
_node_to_duplicated_node_index = gamla.pipe(
|
|
61
|
+
original.edges, graph.get_all_nodes, _node_to_duplicated_node
|
|
62
|
+
)
|
|
63
|
+
get_duplicated_node = gamla.when(
|
|
64
|
+
_node_to_duplicated_node_index, _node_to_duplicated_node_index
|
|
65
|
+
)
|
|
66
|
+
return base_types.GraphType(
|
|
67
|
+
edges=gamla.pipe(
|
|
68
|
+
original.edges,
|
|
69
|
+
gamla.map(
|
|
70
|
+
_duplicate_computation_edge(
|
|
71
|
+
gamla.when(get_duplicated_node, get_duplicated_node)
|
|
72
|
+
)
|
|
73
|
+
),
|
|
74
|
+
frozenset,
|
|
75
|
+
),
|
|
76
|
+
sink=get_duplicated_node(original.sink),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
duplicate_function_or_graph = gamla.ternary(
|
|
81
|
+
gamla.is_instance(base_types.GraphType), duplicate_graph, duplicate_function
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def safe_replace_sources(
|
|
86
|
+
source_to_replacement_dict: Dict[
|
|
87
|
+
base_types.CallableOrNode, base_types.CallableOrNodeOrGraph
|
|
88
|
+
],
|
|
89
|
+
cg: base_types.GraphType,
|
|
90
|
+
) -> base_types.GraphType:
|
|
91
|
+
if not len(source_to_replacement_dict):
|
|
92
|
+
return cg
|
|
93
|
+
|
|
94
|
+
source_to_replacement_dict = gamla.pipe(
|
|
95
|
+
source_to_replacement_dict,
|
|
96
|
+
gamla.keymap(graph.make_computation_node),
|
|
97
|
+
gamla.valmap(
|
|
98
|
+
gamla.unless(
|
|
99
|
+
gamla.is_instance(base_types.GraphType), graph.make_computation_node
|
|
100
|
+
)
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
get_duplicate = gamla.pipe(
|
|
104
|
+
gamla.graph_traverse_many(
|
|
105
|
+
source_to_replacement_dict.keys(), graph.traverse_forward(cg.edges)
|
|
106
|
+
),
|
|
107
|
+
gamla.remove(gamla.contains(source_to_replacement_dict.keys())),
|
|
108
|
+
_node_to_duplicated_node,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def node_replacement(node):
|
|
112
|
+
replacement = source_to_replacement_dict.get(node)
|
|
113
|
+
if replacement is not None:
|
|
114
|
+
return replacement
|
|
115
|
+
duplicate = get_duplicate(node)
|
|
116
|
+
return duplicate if duplicate is not None else node
|
|
117
|
+
|
|
118
|
+
replacement_graphs_edges = {}
|
|
119
|
+
|
|
120
|
+
def source_replacement(node):
|
|
121
|
+
replacement = node_replacement(node)
|
|
122
|
+
if isinstance(replacement, base_types.GraphType):
|
|
123
|
+
replacement_graphs_edges[id(replacement)] = replacement.edges
|
|
124
|
+
return replacement.sink
|
|
125
|
+
return replacement
|
|
126
|
+
|
|
127
|
+
new_edges = []
|
|
128
|
+
for edge in cg.edges:
|
|
129
|
+
destination = node_replacement(edge.destination)
|
|
130
|
+
if edge.args:
|
|
131
|
+
args = tuple(source_replacement(arg) for arg in edge.args)
|
|
132
|
+
if destination is edge.destination and all(
|
|
133
|
+
new is old for new, old in zip(args, edge.args)
|
|
134
|
+
):
|
|
135
|
+
new_edges.append(edge)
|
|
136
|
+
else:
|
|
137
|
+
new_edges.append(
|
|
138
|
+
dataclasses.replace(edge, args=args, destination=destination)
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
source = source_replacement(edge.source)
|
|
142
|
+
if source is edge.source and destination is edge.destination:
|
|
143
|
+
new_edges.append(edge)
|
|
144
|
+
else:
|
|
145
|
+
new_edges.append(
|
|
146
|
+
dataclasses.replace(edge, source=source, destination=destination)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return base_types.GraphType(
|
|
150
|
+
edges=base_types.merge_edges(
|
|
151
|
+
frozenset(new_edges), *replacement_graphs_edges.values()
|
|
152
|
+
),
|
|
153
|
+
sink=node_replacement(cg.sink),
|
|
154
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import dataclasses
|
|
2
2
|
import functools
|
|
3
|
-
from typing import Callable, FrozenSet, Optional, Tuple
|
|
3
|
+
from typing import Callable, Dict, FrozenSet, Iterable, Optional, Tuple
|
|
4
4
|
|
|
5
5
|
import gamla
|
|
6
6
|
from gamla.optimized import sync as opt_gamla
|
|
@@ -64,11 +64,17 @@ def make_computation_node(
|
|
|
64
64
|
)
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
_EMPTY_EDGE_SET: FrozenSet[base_types.ComputationEdge] = frozenset()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_incoming_edges_for_node(
|
|
71
|
+
edges: Iterable[base_types.ComputationEdge],
|
|
72
|
+
) -> Callable[[base_types.ComputationNode], FrozenSet[base_types.ComputationEdge]]:
|
|
73
|
+
grouped: Dict[base_types.ComputationNode, list] = {}
|
|
74
|
+
for edge in edges:
|
|
75
|
+
grouped.setdefault(edge.destination, []).append(edge)
|
|
76
|
+
node_to_edges = {node: frozenset(group) for node, group in grouped.items()}
|
|
77
|
+
return lambda node: node_to_edges.get(node, _EMPTY_EDGE_SET)
|
|
72
78
|
|
|
73
79
|
|
|
74
80
|
get_terminals = opt_gamla.compose_left(
|
|
@@ -95,30 +101,24 @@ def make_terminal(name: str, func: Callable) -> base_types.ComputationNode:
|
|
|
95
101
|
)
|
|
96
102
|
|
|
97
103
|
|
|
98
|
-
_keep_not_in_bound_kwargs = opt_gamla.compose_left(
|
|
99
|
-
opt_gamla.map(base_types.edge_key),
|
|
100
|
-
gamla.filter(gamla.identity),
|
|
101
|
-
frozenset,
|
|
102
|
-
gamla.contains,
|
|
103
|
-
gamla.remove,
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
|
|
107
104
|
@gamla.curry
|
|
108
105
|
def unbound_signature(
|
|
109
106
|
node_to_incoming_edges, node: base_types.ComputationNode
|
|
110
107
|
) -> base_types.NodeSignature:
|
|
111
108
|
"""Computes the new signature of unbound variables after considering internal edges."""
|
|
112
109
|
incoming_edges = node_to_incoming_edges(node)
|
|
113
|
-
|
|
110
|
+
bound_keys = {edge.key for edge in incoming_edges if edge.key}
|
|
111
|
+
node_signature = node.signature
|
|
114
112
|
return base_types.NodeSignature(
|
|
115
|
-
is_kwargs=
|
|
116
|
-
and "**kwargs"
|
|
117
|
-
and signature.is_unary(
|
|
118
|
-
is_args=
|
|
113
|
+
is_kwargs=node_signature.is_kwargs
|
|
114
|
+
and all(edge.key != "**kwargs" for edge in incoming_edges)
|
|
115
|
+
and signature.is_unary(node_signature),
|
|
116
|
+
is_args=node_signature.is_args
|
|
119
117
|
and not any(edge.args for edge in incoming_edges),
|
|
120
|
-
kwargs=tuple(
|
|
121
|
-
optional_kwargs=tuple(
|
|
118
|
+
kwargs=tuple(k for k in node_signature.kwargs if k not in bound_keys),
|
|
119
|
+
optional_kwargs=tuple(
|
|
120
|
+
k for k in node_signature.optional_kwargs if k not in bound_keys
|
|
121
|
+
),
|
|
122
122
|
)
|
|
123
123
|
|
|
124
124
|
|
|
@@ -231,6 +231,45 @@ def replace_node(
|
|
|
231
231
|
)
|
|
232
232
|
|
|
233
233
|
|
|
234
|
+
def replace_nodes(
|
|
235
|
+
node_map: Dict[base_types.ComputationNode, base_types.ComputationNode]
|
|
236
|
+
) -> Callable[[base_types.GraphType], base_types.GraphType]:
|
|
237
|
+
"""Replace many nodes at once (sources, destinations and sink) in a single pass."""
|
|
238
|
+
|
|
239
|
+
def replace_nodes(g: base_types.GraphType) -> base_types.GraphType:
|
|
240
|
+
if not node_map:
|
|
241
|
+
return g
|
|
242
|
+
new_edges = []
|
|
243
|
+
for edge in g.edges:
|
|
244
|
+
destination = node_map.get(edge.destination, edge.destination)
|
|
245
|
+
if edge.args:
|
|
246
|
+
args = tuple(node_map.get(arg, arg) for arg in edge.args)
|
|
247
|
+
if destination is edge.destination and all(
|
|
248
|
+
new is old for new, old in zip(args, edge.args)
|
|
249
|
+
):
|
|
250
|
+
new_edges.append(edge)
|
|
251
|
+
else:
|
|
252
|
+
new_edges.append(
|
|
253
|
+
dataclasses.replace(edge, args=args, destination=destination)
|
|
254
|
+
)
|
|
255
|
+
else:
|
|
256
|
+
assert edge.source is not None
|
|
257
|
+
source = node_map.get(edge.source, edge.source)
|
|
258
|
+
if source is edge.source and destination is edge.destination:
|
|
259
|
+
new_edges.append(edge)
|
|
260
|
+
else:
|
|
261
|
+
new_edges.append(
|
|
262
|
+
dataclasses.replace(
|
|
263
|
+
edge, source=source, destination=destination
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
return base_types.GraphType(
|
|
267
|
+
edges=frozenset(new_edges), sink=node_map.get(g.sink, g.sink)
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return replace_nodes
|
|
271
|
+
|
|
272
|
+
|
|
234
273
|
def transform_edges(
|
|
235
274
|
query: Callable[[base_types.ComputationEdge], bool],
|
|
236
275
|
edge_mapper: Callable[[base_types.ComputationEdge], base_types.ComputationEdge],
|
|
@@ -322,28 +361,20 @@ def merge_graphs(
|
|
|
322
361
|
isinstance(x, base_types.GraphType) for x in graphs
|
|
323
362
|
), f"{graphs} not a graph"
|
|
324
363
|
assert (
|
|
325
|
-
not base_types.
|
|
364
|
+
not isinstance(sink_node_or_graph, base_types.GraphType)
|
|
365
|
+
or not sink_node_or_graph.edges
|
|
326
366
|
or sink_node_or_graph in graphs
|
|
327
367
|
), "sink graph must be one of the graphs being merged"
|
|
328
368
|
|
|
329
|
-
new_g =
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
tuple,
|
|
333
|
-
gamla.pair_right(
|
|
334
|
-
gamla.just(
|
|
335
|
-
sink_node_or_graph.sink
|
|
336
|
-
if isinstance(sink_node_or_graph, base_types.GraphType)
|
|
337
|
-
else sink_node_or_graph
|
|
338
|
-
)
|
|
369
|
+
new_g = base_types.GraphType(
|
|
370
|
+
base_types.merge_edges(
|
|
371
|
+
*(g.edges for g in graphs if g != base_types.EMPTY_GRAPH)
|
|
339
372
|
),
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
gamla.identity,
|
|
373
|
+
(
|
|
374
|
+
sink_node_or_graph.sink
|
|
375
|
+
if isinstance(sink_node_or_graph, base_types.GraphType)
|
|
376
|
+
else sink_node_or_graph
|
|
345
377
|
),
|
|
346
|
-
gamla.star(base_types.GraphType),
|
|
347
378
|
)
|
|
348
379
|
|
|
349
380
|
if base_types.is_debug_mode():
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import inspect
|
|
2
2
|
from typing import Callable, Union
|
|
3
3
|
|
|
4
4
|
import gamla
|
|
@@ -77,7 +77,7 @@ def unary_with_state_and_expectations(
|
|
|
77
77
|
def variadic_with_state_and_expectations(g, sink):
|
|
78
78
|
f = run.to_callable_strict(g)
|
|
79
79
|
|
|
80
|
-
if
|
|
80
|
+
if inspect.iscoroutinefunction(f):
|
|
81
81
|
|
|
82
82
|
async def inner(turns):
|
|
83
83
|
prev = {}
|
|
@@ -1000,6 +1000,33 @@ def test_replace_node():
|
|
|
1000
1000
|
)
|
|
1001
1001
|
|
|
1002
1002
|
|
|
1003
|
+
@pytest.mark.parametrize(
|
|
1004
|
+
"node_map",
|
|
1005
|
+
[
|
|
1006
|
+
pytest.param({}, id="empty map is a no-op"),
|
|
1007
|
+
pytest.param({_node1: _node1_async}, id="single node"),
|
|
1008
|
+
pytest.param({_node1: _node1_async, _node2: _next_int}, id="multiple nodes"),
|
|
1009
|
+
pytest.param({(lambda x: x): _node1_async}, id="node not in graph is a no-op"),
|
|
1010
|
+
],
|
|
1011
|
+
)
|
|
1012
|
+
def test_replace_nodes(node_map: Dict):
|
|
1013
|
+
a = graph.make_source()
|
|
1014
|
+
g = graph.merge_graphs(
|
|
1015
|
+
composers.compose_source_unary(_node1, a),
|
|
1016
|
+
composers.compose_left_unary(_node1, _node2),
|
|
1017
|
+
sink_node_or_graph=graph.make_computation_node(_node2),
|
|
1018
|
+
)
|
|
1019
|
+
node_map = gamla.pipe(
|
|
1020
|
+
node_map,
|
|
1021
|
+
gamla.keymap(graph.make_computation_node),
|
|
1022
|
+
gamla.valmap(graph.make_computation_node),
|
|
1023
|
+
)
|
|
1024
|
+
expected = g
|
|
1025
|
+
for original, replacement in node_map.items():
|
|
1026
|
+
expected = graph.replace_node(original, replacement)(expected)
|
|
1027
|
+
assert graph.replace_nodes(node_map)(g) == expected
|
|
1028
|
+
|
|
1029
|
+
|
|
1003
1030
|
def test_ambig_edges_assertion_in_merge_graphs_active_only_when_env_var_is_active(
|
|
1004
1031
|
monkeypatch,
|
|
1005
1032
|
):
|
|
@@ -109,7 +109,7 @@ _is_graph_async = opt_gamla.compose_left(
|
|
|
109
109
|
opt_gamla.mapcat(lambda edge: (edge.source, *edge.args)),
|
|
110
110
|
opt_gamla.remove(gamla.equals(None)),
|
|
111
111
|
opt_gamla.map(base_types.node_implementation),
|
|
112
|
-
gamla.anymap(
|
|
112
|
+
gamla.anymap(inspect.iscoroutinefunction),
|
|
113
113
|
)
|
|
114
114
|
|
|
115
115
|
|
|
@@ -452,7 +452,7 @@ def _make_get_node_executor(
|
|
|
452
452
|
return result
|
|
453
453
|
|
|
454
454
|
all_nodes = graph.get_all_nodes(edges)
|
|
455
|
-
async_nodes = {n for n in all_nodes if
|
|
455
|
+
async_nodes = {n for n in all_nodes if inspect.iscoroutinefunction(n.func)}
|
|
456
456
|
sync = all_nodes - async_nodes
|
|
457
457
|
tf = graph.traverse_forward(edges)
|
|
458
458
|
downstream_from_async = set(gamla.graph_traverse_many(async_nodes, tf))
|
|
@@ -572,7 +572,9 @@ to_callable = to_callable_with_side_effect(gamla.just(gamla.just(None)))
|
|
|
572
572
|
|
|
573
573
|
|
|
574
574
|
def _node_is_properly_composed(
|
|
575
|
-
node_to_incoming_edges:
|
|
575
|
+
node_to_incoming_edges: Callable[
|
|
576
|
+
[base_types.ComputationNode], FrozenSet[base_types.ComputationEdge]
|
|
577
|
+
]
|
|
576
578
|
) -> Callable[[base_types.ComputationNode], bool]:
|
|
577
579
|
return gamla.compose_left(
|
|
578
580
|
graph.unbound_signature(node_to_incoming_edges),
|
|
@@ -581,7 +583,7 @@ def _node_is_properly_composed(
|
|
|
581
583
|
)
|
|
582
584
|
|
|
583
585
|
|
|
584
|
-
def _assert_composition_is_valid(g: base_types.
|
|
586
|
+
def _assert_composition_is_valid(g: typing.Iterable[base_types.ComputationEdge]):
|
|
585
587
|
return opt_gamla.pipe(
|
|
586
588
|
g,
|
|
587
589
|
graph.get_all_nodes,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
import inspect
|
|
3
|
+
import types
|
|
3
4
|
from types import MappingProxyType
|
|
4
5
|
from typing import Callable, FrozenSet
|
|
5
6
|
|
|
@@ -39,7 +40,7 @@ _func_parameters = gamla.compose_left(
|
|
|
39
40
|
)
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
def
|
|
43
|
+
def _from_callable_via_inspect(func: Callable) -> base_types.NodeSignature:
|
|
43
44
|
function_parameters = _func_parameters(func)
|
|
44
45
|
return base_types.NodeSignature(
|
|
45
46
|
is_args=gamla.anymap(parameter_is_star)(function_parameters),
|
|
@@ -60,6 +61,40 @@ def from_callable(func: Callable) -> base_types.NodeSignature:
|
|
|
60
61
|
)
|
|
61
62
|
|
|
62
63
|
|
|
64
|
+
def _as_plain_function(func: Callable):
|
|
65
|
+
"""The `inspect.signature`-equivalent target when it is a plain function whose
|
|
66
|
+
signature is fully described by its code object, else None (wrappers with
|
|
67
|
+
`__signature__`, partials, methods, builtins and classes need `inspect`)."""
|
|
68
|
+
unwrapped = inspect.unwrap(func, stop=lambda f: hasattr(f, "__signature__"))
|
|
69
|
+
if not isinstance(unwrapped, types.FunctionType) or hasattr(
|
|
70
|
+
unwrapped, "__signature__"
|
|
71
|
+
):
|
|
72
|
+
return None
|
|
73
|
+
return unwrapped
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def from_callable(func: Callable) -> base_types.NodeSignature:
|
|
77
|
+
plain_function = _as_plain_function(func)
|
|
78
|
+
if plain_function is None:
|
|
79
|
+
return _from_callable_via_inspect(func)
|
|
80
|
+
code = plain_function.__code__
|
|
81
|
+
positional = code.co_varnames[: code.co_argcount]
|
|
82
|
+
keyword_only = code.co_varnames[
|
|
83
|
+
code.co_argcount : code.co_argcount + code.co_kwonlyargcount
|
|
84
|
+
]
|
|
85
|
+
defaults = plain_function.__defaults__ or ()
|
|
86
|
+
keyword_defaults = plain_function.__kwdefaults__ or {}
|
|
87
|
+
return base_types.NodeSignature(
|
|
88
|
+
is_args=bool(code.co_flags & inspect.CO_VARARGS),
|
|
89
|
+
is_kwargs=bool(code.co_flags & inspect.CO_VARKEYWORDS),
|
|
90
|
+
kwargs=positional + keyword_only,
|
|
91
|
+
optional_kwargs=(
|
|
92
|
+
positional[len(positional) - len(defaults) :]
|
|
93
|
+
+ tuple(name for name in keyword_only if name in keyword_defaults)
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
63
98
|
def parameters(signature: base_types.NodeSignature) -> FrozenSet[str]:
|
|
64
99
|
return frozenset(
|
|
65
100
|
{
|
|
@@ -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="69",
|
|
11
11
|
long_description=_LONG_DESCRIPTION,
|
|
12
12
|
long_description_content_type="text/markdown",
|
|
13
13
|
packages=setuptools.find_namespace_packages(),
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import functools
|
|
3
|
-
import inspect
|
|
4
|
-
from typing import Dict
|
|
5
|
-
|
|
6
|
-
import gamla
|
|
7
|
-
|
|
8
|
-
from computation_graph import base_types, graph
|
|
9
|
-
from computation_graph.composers import debug
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def duplicate_function(func):
|
|
13
|
-
if asyncio.iscoroutinefunction(func):
|
|
14
|
-
|
|
15
|
-
@functools.wraps(func)
|
|
16
|
-
async def inner(*args, **kwargs):
|
|
17
|
-
return await func(*args, **kwargs)
|
|
18
|
-
|
|
19
|
-
else:
|
|
20
|
-
|
|
21
|
-
@functools.wraps(func)
|
|
22
|
-
def inner(*args, **kwargs):
|
|
23
|
-
return func(*args, **kwargs)
|
|
24
|
-
|
|
25
|
-
return debug.name_callable(inner, f"duplicate of {func.__name__}")
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _duplicate_computation_edge(get_duplicated_node):
|
|
29
|
-
return gamla.compose_left(
|
|
30
|
-
gamla.dataclass_transform("source", get_duplicated_node),
|
|
31
|
-
gamla.dataclass_transform("destination", get_duplicated_node),
|
|
32
|
-
gamla.dataclass_transform(
|
|
33
|
-
"args", gamla.compose_left(gamla.map(get_duplicated_node), tuple)
|
|
34
|
-
),
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
_duplicate_node = gamla.compose_left(
|
|
39
|
-
base_types.node_implementation,
|
|
40
|
-
gamla.when(
|
|
41
|
-
gamla.compose_left(inspect.signature, gamla.attrgetter("parameters"), len),
|
|
42
|
-
duplicate_function,
|
|
43
|
-
),
|
|
44
|
-
graph.make_computation_node,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
duplicate_node = _duplicate_node
|
|
48
|
-
|
|
49
|
-
_node_to_duplicated_node = gamla.compose_left(
|
|
50
|
-
gamla.remove(base_types.node_is_terminal),
|
|
51
|
-
gamla.map(gamla.pair_right(_duplicate_node)),
|
|
52
|
-
dict,
|
|
53
|
-
gamla.dict_to_getter_with_default(None),
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def duplicate_graph(original: base_types.GraphType) -> base_types.GraphType:
|
|
58
|
-
|
|
59
|
-
_node_to_duplicated_node_index = gamla.pipe(
|
|
60
|
-
original.edges, graph.get_all_nodes, _node_to_duplicated_node
|
|
61
|
-
)
|
|
62
|
-
get_duplicated_node = gamla.when(
|
|
63
|
-
_node_to_duplicated_node_index, _node_to_duplicated_node_index
|
|
64
|
-
)
|
|
65
|
-
return base_types.GraphType(
|
|
66
|
-
edges=gamla.pipe(
|
|
67
|
-
original.edges,
|
|
68
|
-
gamla.map(
|
|
69
|
-
_duplicate_computation_edge(
|
|
70
|
-
gamla.when(get_duplicated_node, get_duplicated_node)
|
|
71
|
-
)
|
|
72
|
-
),
|
|
73
|
-
frozenset,
|
|
74
|
-
),
|
|
75
|
-
sink=get_duplicated_node(original.sink),
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
duplicate_function_or_graph = gamla.ternary(
|
|
80
|
-
gamla.is_instance(base_types.GraphType), duplicate_graph, duplicate_function
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def safe_replace_sources(
|
|
85
|
-
source_to_replacement_dict: Dict[
|
|
86
|
-
base_types.CallableOrNode, base_types.CallableOrNodeOrGraph
|
|
87
|
-
],
|
|
88
|
-
cg: base_types.GraphType,
|
|
89
|
-
) -> base_types.GraphType:
|
|
90
|
-
if not len(source_to_replacement_dict):
|
|
91
|
-
return cg
|
|
92
|
-
|
|
93
|
-
source_to_replacement_dict = gamla.keymap(graph.make_computation_node)(
|
|
94
|
-
source_to_replacement_dict
|
|
95
|
-
)
|
|
96
|
-
reachable_to_duplicate_map = gamla.pipe(
|
|
97
|
-
gamla.graph_traverse_many(
|
|
98
|
-
source_to_replacement_dict.keys(), graph.traverse_forward(cg.edges)
|
|
99
|
-
),
|
|
100
|
-
gamla.remove(gamla.contains(source_to_replacement_dict.keys())),
|
|
101
|
-
_node_to_duplicated_node,
|
|
102
|
-
)
|
|
103
|
-
get_node_replacement = gamla.compose_left(
|
|
104
|
-
gamla.lazyjuxt(
|
|
105
|
-
gamla.dict_to_getter_with_default(None)(source_to_replacement_dict),
|
|
106
|
-
reachable_to_duplicate_map,
|
|
107
|
-
gamla.identity,
|
|
108
|
-
),
|
|
109
|
-
gamla.find(gamla.identity),
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
def update_edge(e: base_types.ComputationEdge) -> base_types.GraphType:
|
|
113
|
-
dest = base_types.edge_destination(e)
|
|
114
|
-
g = graph.replace_node(dest, get_node_replacement(dest))(
|
|
115
|
-
base_types.GraphType(edges=frozenset((e,)), sink=dest)
|
|
116
|
-
)
|
|
117
|
-
for source in base_types.edge_sources(e):
|
|
118
|
-
g = graph.replace_source(source, get_node_replacement(source), g)
|
|
119
|
-
return g
|
|
120
|
-
|
|
121
|
-
return base_types.GraphType(
|
|
122
|
-
edges=frozenset(edge for e in cg.edges for edge in update_edge(e).edges),
|
|
123
|
-
sink=get_node_replacement(cg.sink),
|
|
124
|
-
)
|
|
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
|
|
File without changes
|
{computation_graph-67 → computation_graph-69}/computation_graph.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|