computation-graph 66__tar.gz → 68__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-66 → computation_graph-68}/PKG-INFO +1 -1
- {computation_graph-66 → computation_graph-68}/computation_graph/base_types.py +42 -20
- {computation_graph-66 → computation_graph-68}/computation_graph/composers/__init__.py +177 -87
- {computation_graph-66 → computation_graph-68}/computation_graph/composers/composers_test.py +24 -2
- computation_graph-68/computation_graph/composers/duplication.py +154 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/graph.py +148 -41
- {computation_graph-66 → computation_graph-68}/computation_graph/graph_runners.py +17 -19
- {computation_graph-66 → computation_graph-68}/computation_graph/graph_test.py +230 -126
- computation_graph-68/computation_graph/infer_sink.py +48 -0
- computation_graph-68/computation_graph/infer_sink_test.py +403 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/legacy.py +6 -2
- {computation_graph-66 → computation_graph-68}/computation_graph/run.py +30 -18
- {computation_graph-66 → computation_graph-68}/computation_graph/trace/graphviz.py +5 -3
- {computation_graph-66 → computation_graph-68}/computation_graph.egg-info/PKG-INFO +1 -1
- {computation_graph-66 → computation_graph-68}/computation_graph.egg-info/SOURCES.txt +2 -0
- {computation_graph-66 → computation_graph-68}/setup.py +1 -1
- computation_graph-66/computation_graph/composers/duplication.py +0 -108
- {computation_graph-66 → computation_graph-68}/LICENSE.md +0 -0
- {computation_graph-66 → computation_graph-68}/README.md +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/__init__.py +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/composers/condition.py +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/composers/condition_test.py +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/composers/debug.py +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/composers/lift.py +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/composers/lift_test.py +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/composers/logic.py +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/composers/memory.py +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/composers/memory_test.py +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/signature.py +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/trace/__init__.py +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/trace/ascii.py +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/trace/graphviz_test.py +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/trace/mermaid.py +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph/trace/trace_utils.py +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph.egg-info/dependency_links.txt +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph.egg-info/requires.txt +0 -0
- {computation_graph-66 → computation_graph-68}/computation_graph.egg-info/top_level.txt +0 -0
- {computation_graph-66 → computation_graph-68}/pyproject.toml +0 -0
- {computation_graph-66 → computation_graph-68}/setup.cfg +0 -0
|
@@ -63,9 +63,17 @@ class ComputationEdge:
|
|
|
63
63
|
assert bool(self.args) != bool(
|
|
64
64
|
self.source
|
|
65
65
|
), f"Edge must have a source or args, not both: {self}"
|
|
66
|
+
assert bool(self.args) or isinstance(
|
|
67
|
+
self.source, ComputationNode
|
|
68
|
+
), f"source must be a ComputationNode {self.source}"
|
|
69
|
+
assert isinstance(
|
|
70
|
+
self.destination, ComputationNode
|
|
71
|
+
), f"destination must be a ComputationNode {self.destination}"
|
|
72
|
+
assert all(
|
|
73
|
+
isinstance(x, ComputationNode) for x in self.args
|
|
74
|
+
), f"all args must be ComputationNodes {self.args}"
|
|
66
75
|
if (
|
|
67
76
|
not self.args
|
|
68
|
-
# TODO(uri): doesn't support `functools.partial`, suggested to drop support for it entirely.
|
|
69
77
|
and not isinstance(self.source.func, functools.partial)
|
|
70
78
|
and not isinstance(self.destination.func, functools.partial)
|
|
71
79
|
):
|
|
@@ -76,7 +84,9 @@ class ComputationEdge:
|
|
|
76
84
|
|
|
77
85
|
def __repr__(self):
|
|
78
86
|
source_str = (
|
|
79
|
-
"
|
|
87
|
+
f"[{''.join(map(str, self.args))}]"
|
|
88
|
+
if self.source is None
|
|
89
|
+
else str(self.source)
|
|
80
90
|
)
|
|
81
91
|
line = "...." if self.is_future else "----"
|
|
82
92
|
return source_str + line + self.key + line + ">" + str(self.destination)
|
|
@@ -105,7 +115,16 @@ class ComputationNode:
|
|
|
105
115
|
return self.computed_hash
|
|
106
116
|
|
|
107
117
|
def __repr__(self):
|
|
108
|
-
return self.name
|
|
118
|
+
return f"<CompuationNode {self.name.replace('<', '').replace('>', '')}({','.join(self.signature.kwargs)}) >"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclasses.dataclass(frozen=True)
|
|
122
|
+
class GraphType:
|
|
123
|
+
edges: FrozenSet[ComputationEdge]
|
|
124
|
+
sink: ComputationNode
|
|
125
|
+
|
|
126
|
+
def __post_init__(self):
|
|
127
|
+
assert isinstance(self.edges, frozenset), "edges must be a frozenset"
|
|
109
128
|
|
|
110
129
|
|
|
111
130
|
node_implementation = gamla.attrgetter("func")
|
|
@@ -125,13 +144,15 @@ def edge_sources(edge: ComputationEdge) -> Tuple[ComputationNode, ...]:
|
|
|
125
144
|
|
|
126
145
|
is_computation_graph = gamla.alljuxt(
|
|
127
146
|
# Note that this cannot be set to `GraphType` (due to `is_instance` limitation).
|
|
128
|
-
gamla.is_instance(
|
|
129
|
-
gamla.len_greater(0),
|
|
130
|
-
gamla.
|
|
147
|
+
gamla.is_instance(GraphType),
|
|
148
|
+
gamla.compose_left(gamla.attrgetter("edges"), gamla.len_greater(0)),
|
|
149
|
+
gamla.compose_left(
|
|
150
|
+
gamla.attrgetter("edges"), gamla.allmap(gamla.is_instance(ComputationEdge))
|
|
151
|
+
),
|
|
131
152
|
)
|
|
132
153
|
|
|
133
154
|
|
|
134
|
-
|
|
155
|
+
_ambiguity_groups_on_edges = gamla.compose(
|
|
135
156
|
frozenset,
|
|
136
157
|
gamla.filter(gamla.len_greater(1)),
|
|
137
158
|
dict.values,
|
|
@@ -139,9 +160,9 @@ ambiguity_groups = gamla.compose(
|
|
|
139
160
|
gamla.groupby(gamla.juxt(edge_destination, edge_key, edge_priority)),
|
|
140
161
|
)
|
|
141
162
|
|
|
142
|
-
|
|
163
|
+
assert_no_unwanted_ambiguity_on_edges = gamla.side_effect(
|
|
143
164
|
gamla.compose_left(
|
|
144
|
-
|
|
165
|
+
_ambiguity_groups_on_edges,
|
|
145
166
|
gamla.assert_that_with_message(
|
|
146
167
|
gamla.wrap_str(
|
|
147
168
|
"There are multiple edges with the same destination, key and priority in the computation graph!: {}"
|
|
@@ -153,23 +174,24 @@ assert_no_unwanted_ambiguity = gamla.side_effect(
|
|
|
153
174
|
)
|
|
154
175
|
|
|
155
176
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if os.getenv(COMPUTATION_GRAPH_DEBUG_ENV_KEY) is not None:
|
|
159
|
-
assert_no_unwanted_ambiguity(graph)
|
|
177
|
+
def is_debug_mode() -> bool:
|
|
178
|
+
return os.getenv(COMPUTATION_GRAPH_DEBUG_ENV_KEY) is not None
|
|
160
179
|
|
|
161
180
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
181
|
+
@gamla.side_effect
|
|
182
|
+
def assert_no_unwanted_ambiguity_when_debug_set(graph: GraphType):
|
|
183
|
+
if is_debug_mode():
|
|
184
|
+
assert_no_unwanted_ambiguity_on_edges(graph.edges)
|
|
185
|
+
|
|
165
186
|
|
|
166
|
-
|
|
187
|
+
# TODO(nitzo): Is this used ? Check if we can re-enable the assertion when in debug / move to merge.
|
|
188
|
+
def merge_edges(*edges: FrozenSet[ComputationEdge]) -> FrozenSet[ComputationEdge]:
|
|
189
|
+
result: FrozenSet[ComputationEdge] = frozenset()
|
|
190
|
+
return result.union(*edges)
|
|
167
191
|
|
|
168
192
|
|
|
169
|
-
# We use a tuple to generate a unique id for each node based on the order of edges.
|
|
170
|
-
GraphType = FrozenSet[ComputationEdge]
|
|
171
193
|
GraphOrCallable = Union[Callable, GraphType]
|
|
172
194
|
CallableOrNode = Union[Callable, ComputationNode]
|
|
173
195
|
CallableOrNodeOrGraph = Union[CallableOrNode, GraphType]
|
|
174
196
|
NodeOrGraph = Union[ComputationNode, GraphType]
|
|
175
|
-
EMPTY_GRAPH: GraphType = frozenset()
|
|
197
|
+
EMPTY_GRAPH: GraphType = GraphType(edges=frozenset(), sink=None) # type: ignore[arg-type]
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any, Callable, Dict, Iterable, Optional, Sequence
|
|
3
|
+
from typing import Any, Callable, Dict, Iterable, Optional, Sequence, Tuple
|
|
4
4
|
|
|
5
5
|
import gamla
|
|
6
6
|
|
|
7
7
|
from computation_graph import base_types, graph, signature
|
|
8
|
+
from computation_graph.base_types import GraphType
|
|
9
|
+
from computation_graph.graph import make_computation_node
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class _ComputationError:
|
|
11
13
|
pass
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
gamla.is_instance(
|
|
16
|
+
_to_computation_node_if_callable = gamla.unless(
|
|
17
|
+
gamla.is_instance(base_types.GraphType), graph.make_computation_node
|
|
16
18
|
)
|
|
17
19
|
|
|
18
20
|
|
|
@@ -47,37 +49,52 @@ def make_and(
|
|
|
47
49
|
return args
|
|
48
50
|
|
|
49
51
|
merge_node = graph.make_computation_node(args_to_tuple)
|
|
52
|
+
# `merge_fn` may be a callable/node or a whole graph; `make_compose` handles
|
|
53
|
+
# both, connecting `merge_node`'s output into `merge_fn` under key "args".
|
|
54
|
+
merge_fn_graph = make_compose(merge_fn, merge_node, key="args")
|
|
50
55
|
|
|
51
56
|
return gamla.sync.pipe(
|
|
52
57
|
funcs,
|
|
53
|
-
gamla.sync.map(
|
|
58
|
+
gamla.sync.map(_to_computation_node_if_callable),
|
|
54
59
|
tuple,
|
|
55
60
|
gamla.sync.juxtcat(
|
|
56
|
-
gamla.sync.
|
|
61
|
+
gamla.sync.filter(gamla.is_instance(base_types.GraphType)),
|
|
57
62
|
gamla.sync.compose_left(
|
|
58
|
-
gamla.sync.map(
|
|
63
|
+
gamla.sync.map(
|
|
64
|
+
lambda node_or_graph: (
|
|
65
|
+
node_or_graph.sink
|
|
66
|
+
if isinstance(node_or_graph, base_types.GraphType)
|
|
67
|
+
else node_or_graph
|
|
68
|
+
)
|
|
69
|
+
),
|
|
59
70
|
tuple,
|
|
60
|
-
lambda nodes: (
|
|
61
|
-
(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
lambda nodes: base_types.GraphType(
|
|
72
|
+
frozenset(
|
|
73
|
+
[
|
|
74
|
+
base_types.ComputationEdge(
|
|
75
|
+
is_future=False,
|
|
76
|
+
priority=0,
|
|
77
|
+
source=None,
|
|
78
|
+
args=nodes,
|
|
79
|
+
destination=merge_node,
|
|
80
|
+
key="*args",
|
|
81
|
+
)
|
|
82
|
+
]
|
|
70
83
|
),
|
|
71
|
-
|
|
84
|
+
merge_node,
|
|
72
85
|
),
|
|
86
|
+
gamla.wrap_tuple,
|
|
73
87
|
),
|
|
74
88
|
),
|
|
75
|
-
|
|
89
|
+
tuple,
|
|
90
|
+
lambda graphs: graph.merge_graphs(
|
|
91
|
+
*graphs, merge_fn_graph, sink_node_or_graph=merge_fn_graph
|
|
92
|
+
),
|
|
76
93
|
)
|
|
77
94
|
|
|
78
95
|
|
|
79
96
|
def make_or(
|
|
80
|
-
|
|
97
|
+
nodes_or_graphs: Sequence[base_types.CallableOrNodeOrGraph],
|
|
81
98
|
merge_fn: base_types.CallableOrNodeOrGraph,
|
|
82
99
|
) -> base_types.GraphType:
|
|
83
100
|
"""Aggregate funcs' output into `merge_fn`.
|
|
@@ -91,63 +108,44 @@ def make_or(
|
|
|
91
108
|
)
|
|
92
109
|
|
|
93
110
|
filter_node = graph.make_computation_node(filter_computation_errors)
|
|
111
|
+
# `merge_fn` may be a callable/node or a whole graph; `make_compose` handles
|
|
112
|
+
# both, connecting `filter_node`'s output into `merge_fn` under key "args".
|
|
113
|
+
merge_fn_graph = make_compose(merge_fn, filter_node, key="args")
|
|
94
114
|
|
|
95
115
|
return gamla.sync.pipe(
|
|
96
|
-
|
|
116
|
+
nodes_or_graphs,
|
|
97
117
|
gamla.sync.map(make_optional(default_value=_ComputationError())),
|
|
98
118
|
tuple,
|
|
99
|
-
gamla.
|
|
119
|
+
gamla.juxtcat(
|
|
120
|
+
gamla.identity,
|
|
100
121
|
gamla.sync.compose_left(
|
|
101
|
-
gamla.sync.map(
|
|
122
|
+
gamla.sync.map(gamla.attrgetter("sink")),
|
|
102
123
|
tuple,
|
|
103
|
-
lambda sinks: (
|
|
104
|
-
(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
124
|
+
lambda sinks: GraphType(
|
|
125
|
+
frozenset(
|
|
126
|
+
[
|
|
127
|
+
base_types.ComputationEdge(
|
|
128
|
+
is_future=False,
|
|
129
|
+
priority=0,
|
|
130
|
+
source=None,
|
|
131
|
+
args=sinks,
|
|
132
|
+
destination=filter_node,
|
|
133
|
+
key="*args",
|
|
134
|
+
)
|
|
135
|
+
]
|
|
113
136
|
),
|
|
114
|
-
|
|
137
|
+
filter_node,
|
|
115
138
|
),
|
|
116
|
-
|
|
139
|
+
gamla.wrap_tuple,
|
|
140
|
+
),
|
|
141
|
+
),
|
|
142
|
+
tuple,
|
|
143
|
+
lambda graphs: graph.merge_graphs(
|
|
144
|
+
*graphs, merge_fn_graph, sink_node_or_graph=merge_fn_graph
|
|
117
145
|
),
|
|
118
|
-
gamla.concat,
|
|
119
|
-
gamla.sync.star(base_types.merge_graphs),
|
|
120
146
|
)
|
|
121
147
|
|
|
122
148
|
|
|
123
|
-
_destinations = gamla.compose(set, gamla.map(base_types.edge_destination))
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def _infer_sink(graph_or_node: base_types.NodeOrGraph) -> base_types.ComputationNode:
|
|
127
|
-
if isinstance(graph_or_node, base_types.ComputationNode):
|
|
128
|
-
return graph_or_node
|
|
129
|
-
graph_without_future_edges = graph.remove_future_edges(graph_or_node)
|
|
130
|
-
if graph_without_future_edges:
|
|
131
|
-
try:
|
|
132
|
-
return graph.sink_excluding_terminals(graph_without_future_edges)
|
|
133
|
-
except AssertionError:
|
|
134
|
-
# If we reached here we can try again without sources of future edges.
|
|
135
|
-
sources_of_future_edges = gamla.sync.pipe(
|
|
136
|
-
graph_or_node,
|
|
137
|
-
gamla.sync.filter(base_types.edge_is_future),
|
|
138
|
-
gamla.sync.map(base_types.edge_source),
|
|
139
|
-
frozenset,
|
|
140
|
-
)
|
|
141
|
-
result = (
|
|
142
|
-
graph.get_leaves(graph_without_future_edges) - sources_of_future_edges
|
|
143
|
-
)
|
|
144
|
-
assert len(result) == 1
|
|
145
|
-
return gamla.head(result)
|
|
146
|
-
|
|
147
|
-
assert len(_destinations(graph_or_node)) == 1, graph_or_node
|
|
148
|
-
return base_types.edge_destination(gamla.head(graph_or_node))
|
|
149
|
-
|
|
150
|
-
|
|
151
149
|
def make_first(*graphs: base_types.CallableOrNodeOrGraph) -> base_types.GraphType:
|
|
152
150
|
"""Returns a graph that when run, returns the first value that doesn't raise an exception.
|
|
153
151
|
>>> def raise_some_exception():
|
|
@@ -156,22 +154,34 @@ def make_first(*graphs: base_types.CallableOrNodeOrGraph) -> base_types.GraphTyp
|
|
|
156
154
|
(raise_exception----constituent_of_first---->first_sink, just----constituent_of_first---->first_sink)
|
|
157
155
|
Will return 1.
|
|
158
156
|
"""
|
|
159
|
-
graph_or_nodes = tuple(map(
|
|
157
|
+
graph_or_nodes = tuple(map(_to_computation_node_if_callable, graphs))
|
|
160
158
|
|
|
161
159
|
def first_sink(constituent_of_first):
|
|
162
160
|
return constituent_of_first
|
|
163
161
|
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
sink = graph.make_computation_node(first_sink)
|
|
163
|
+
|
|
164
|
+
return graph.merge_graphs(
|
|
165
|
+
*gamla.pipe(
|
|
166
|
+
graph_or_nodes,
|
|
167
|
+
gamla.map(_get_edges_from_node_or_graph),
|
|
168
|
+
gamla.filter(gamla.is_instance(GraphType)),
|
|
169
|
+
),
|
|
166
170
|
gamla.pipe(
|
|
167
171
|
graph_or_nodes,
|
|
168
|
-
gamla.map(
|
|
172
|
+
gamla.map(
|
|
173
|
+
lambda graph_or_node: (
|
|
174
|
+
graph_or_node.sink
|
|
175
|
+
if isinstance(graph_or_node, GraphType)
|
|
176
|
+
else graph_or_node
|
|
177
|
+
)
|
|
178
|
+
),
|
|
169
179
|
enumerate,
|
|
170
180
|
gamla.map(
|
|
171
181
|
gamla.star(
|
|
172
182
|
lambda i, g: base_types.ComputationEdge(
|
|
173
183
|
source=g,
|
|
174
|
-
destination=
|
|
184
|
+
destination=sink,
|
|
175
185
|
key="constituent_of_first",
|
|
176
186
|
args=(),
|
|
177
187
|
priority=i,
|
|
@@ -179,8 +189,10 @@ def make_first(*graphs: base_types.CallableOrNodeOrGraph) -> base_types.GraphTyp
|
|
|
179
189
|
)
|
|
180
190
|
)
|
|
181
191
|
),
|
|
182
|
-
|
|
192
|
+
frozenset,
|
|
193
|
+
lambda edges: GraphType(edges, sink),
|
|
183
194
|
),
|
|
195
|
+
sink_node_or_graph=sink,
|
|
184
196
|
)
|
|
185
197
|
|
|
186
198
|
|
|
@@ -188,6 +200,39 @@ def last(*args) -> base_types.GraphType:
|
|
|
188
200
|
return make_first(*reversed(args))
|
|
189
201
|
|
|
190
202
|
|
|
203
|
+
def _determine_sink(
|
|
204
|
+
source: base_types.NodeOrGraph, destination: base_types.NodeOrGraph, is_future: bool
|
|
205
|
+
) -> base_types.ComputationNode:
|
|
206
|
+
if is_future:
|
|
207
|
+
if isinstance(source, base_types.GraphType):
|
|
208
|
+
return source.sink
|
|
209
|
+
return (
|
|
210
|
+
destination.sink
|
|
211
|
+
if isinstance(destination, base_types.GraphType)
|
|
212
|
+
else destination
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
if isinstance(destination, base_types.GraphType):
|
|
216
|
+
return destination.sink
|
|
217
|
+
|
|
218
|
+
if destination.is_terminal:
|
|
219
|
+
return source.sink if isinstance(source, base_types.GraphType) else source
|
|
220
|
+
return destination
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _determine_composition_sink(
|
|
224
|
+
graphs: Tuple[base_types.NodeOrGraph, ...]
|
|
225
|
+
) -> base_types.NodeOrGraph:
|
|
226
|
+
for graph_or_node in reversed(graphs):
|
|
227
|
+
if isinstance(graph_or_node, base_types.GraphType):
|
|
228
|
+
return graph_or_node.sink
|
|
229
|
+
|
|
230
|
+
if graph_or_node.is_terminal:
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
raise AssertionError("Could not compose, failed to. find a sink")
|
|
234
|
+
|
|
235
|
+
|
|
191
236
|
@gamla.curry
|
|
192
237
|
def _try_connect(
|
|
193
238
|
source: base_types.ComputationNode,
|
|
@@ -220,20 +265,31 @@ def _infer_composition_edges(
|
|
|
220
265
|
source: base_types.NodeOrGraph,
|
|
221
266
|
destination: base_types.NodeOrGraph,
|
|
222
267
|
) -> base_types.GraphType:
|
|
223
|
-
|
|
268
|
+
|
|
269
|
+
try_connect = _try_connect(
|
|
270
|
+
source.sink if isinstance(source, GraphType) else source,
|
|
271
|
+
key,
|
|
272
|
+
priority,
|
|
273
|
+
is_future,
|
|
274
|
+
)
|
|
224
275
|
|
|
225
276
|
if isinstance(destination, base_types.ComputationNode):
|
|
226
|
-
|
|
227
|
-
|
|
277
|
+
sink = _determine_sink(source, destination, is_future)
|
|
278
|
+
return graph.merge_graphs(
|
|
279
|
+
base_types.GraphType(
|
|
280
|
+
edges=frozenset([try_connect(destination, destination.signature)]),
|
|
281
|
+
sink=sink,
|
|
282
|
+
),
|
|
228
283
|
_get_edges_from_node_or_graph(source),
|
|
284
|
+
sink_node_or_graph=sink,
|
|
229
285
|
)
|
|
230
|
-
|
|
231
286
|
unbound_signature = graph.unbound_signature(
|
|
232
|
-
graph.get_incoming_edges_for_node(destination)
|
|
287
|
+
graph.get_incoming_edges_for_node(destination.edges)
|
|
233
288
|
)
|
|
234
|
-
return
|
|
289
|
+
return graph.merge_graphs(
|
|
235
290
|
gamla.sync.pipe(
|
|
236
291
|
destination,
|
|
292
|
+
gamla.attrgetter("edges"),
|
|
237
293
|
gamla.sync.mapcat(graph.get_edge_nodes),
|
|
238
294
|
gamla.unique,
|
|
239
295
|
gamla.sync.filter(
|
|
@@ -246,24 +302,31 @@ def _infer_composition_edges(
|
|
|
246
302
|
# Do not add edges to nodes from source that are already present in destination (cycle).
|
|
247
303
|
gamla.sync.filter(
|
|
248
304
|
lambda node: isinstance(source, base_types.ComputationNode)
|
|
249
|
-
or node
|
|
305
|
+
or node
|
|
306
|
+
not in graph.get_all_nodes(
|
|
307
|
+
source.edges if isinstance(source, GraphType) else source
|
|
308
|
+
)
|
|
250
309
|
),
|
|
251
310
|
gamla.sync.map(
|
|
252
|
-
lambda
|
|
253
|
-
destination=
|
|
254
|
-
unbound_destination_signature=unbound_signature(
|
|
311
|
+
lambda destination_node: try_connect(
|
|
312
|
+
destination=destination_node,
|
|
313
|
+
unbound_destination_signature=unbound_signature(destination_node),
|
|
255
314
|
)
|
|
256
315
|
),
|
|
257
|
-
|
|
316
|
+
frozenset,
|
|
258
317
|
gamla.sync.check(
|
|
259
318
|
gamla.identity,
|
|
260
319
|
AssertionError(
|
|
261
320
|
f"Cannot compose, destination signature does not contain key '{key}'"
|
|
262
321
|
),
|
|
263
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]
|
|
264
326
|
),
|
|
265
|
-
destination,
|
|
266
327
|
_get_edges_from_node_or_graph(source),
|
|
328
|
+
destination,
|
|
329
|
+
sink_node_or_graph=_determine_sink(source, destination, is_future),
|
|
267
330
|
)
|
|
268
331
|
|
|
269
332
|
|
|
@@ -279,10 +342,13 @@ def _make_compose_inner(
|
|
|
279
342
|
return gamla.sync.pipe(
|
|
280
343
|
funcs,
|
|
281
344
|
reversed,
|
|
282
|
-
gamla.sync.map(
|
|
345
|
+
gamla.sync.map(_to_computation_node_if_callable),
|
|
283
346
|
gamla.sliding_window(2),
|
|
284
347
|
gamla.sync.map(gamla.star(_infer_composition_edges(priority, key, is_future))),
|
|
285
|
-
|
|
348
|
+
tuple,
|
|
349
|
+
lambda graphs: graph.merge_graphs(
|
|
350
|
+
*graphs, sink_node_or_graph=_determine_composition_sink(graphs)
|
|
351
|
+
),
|
|
286
352
|
)
|
|
287
353
|
|
|
288
354
|
|
|
@@ -309,11 +375,24 @@ def make_compose_future(
|
|
|
309
375
|
def when_memory_unavailable():
|
|
310
376
|
return default
|
|
311
377
|
|
|
312
|
-
|
|
378
|
+
if not isinstance(destination, base_types.GraphType):
|
|
379
|
+
destination = make_computation_node(destination)
|
|
380
|
+
|
|
381
|
+
if not isinstance(source, base_types.GraphType):
|
|
382
|
+
source = make_computation_node(source)
|
|
383
|
+
|
|
384
|
+
if isinstance(source, base_types.GraphType):
|
|
385
|
+
sink_node_or_graph = source.sink
|
|
386
|
+
elif isinstance(destination, base_types.GraphType):
|
|
387
|
+
sink_node_or_graph = destination.sink
|
|
388
|
+
else:
|
|
389
|
+
sink_node_or_graph = destination
|
|
390
|
+
return graph.merge_graphs(
|
|
313
391
|
_make_compose_inner(destination, source, key=key, is_future=True, priority=0),
|
|
314
392
|
_make_compose_inner(
|
|
315
393
|
destination, when_memory_unavailable, key=key, is_future=False, priority=1
|
|
316
394
|
),
|
|
395
|
+
sink_node_or_graph=sink_node_or_graph,
|
|
317
396
|
)
|
|
318
397
|
|
|
319
398
|
|
|
@@ -384,11 +463,22 @@ def compose_dict(
|
|
|
384
463
|
>>> compose_dict(gamla.between, {"low": gamla.just(0), "high": gamla.just(10)})
|
|
385
464
|
(just----low---->between, just----high---->between)
|
|
386
465
|
"""
|
|
466
|
+
|
|
467
|
+
destination = f if base_types.is_computation_graph(f) else make_computation_node(f) # type: ignore
|
|
468
|
+
sink = (
|
|
469
|
+
destination.sink # type: ignore
|
|
470
|
+
if base_types.is_computation_graph(destination)
|
|
471
|
+
else destination
|
|
472
|
+
)
|
|
473
|
+
|
|
387
474
|
return gamla.pipe(
|
|
388
475
|
d,
|
|
389
476
|
dict.items,
|
|
390
|
-
gamla.sync.map(
|
|
391
|
-
|
|
477
|
+
gamla.sync.map(
|
|
478
|
+
gamla.star(lambda key, fn: make_compose(destination, fn, key=key))
|
|
479
|
+
),
|
|
480
|
+
tuple,
|
|
481
|
+
lambda graphs: graph.merge_graphs(*graphs, sink_node_or_graph=sink), # type: ignore
|
|
392
482
|
) or compose_left_unary(f, lambda x: x)
|
|
393
483
|
|
|
394
484
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import computation_graph.graph
|
|
2
|
-
from computation_graph import
|
|
2
|
+
from computation_graph import composers, graph, graph_runners
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def test_unary_composition_with_graph_destination():
|
|
@@ -30,14 +30,36 @@ def test_infer_sink_edge_case_all_future_edges_with_single_destination():
|
|
|
30
30
|
def c_b(c, b):
|
|
31
31
|
return b
|
|
32
32
|
|
|
33
|
-
two_future_edges_single_dest =
|
|
33
|
+
two_future_edges_single_dest = graph.merge_graphs(
|
|
34
34
|
composers.compose_left_source(s, "c", c_b),
|
|
35
35
|
composers.compose_left_source(s, "b", c_b),
|
|
36
|
+
sink_node_or_graph=graph.make_computation_node(c_b),
|
|
36
37
|
)
|
|
37
38
|
|
|
38
39
|
composers.compose_left_unary(two_future_edges_single_dest, lambda x: x)
|
|
39
40
|
|
|
40
41
|
|
|
42
|
+
def test_compose_dict():
|
|
43
|
+
def add(a, b):
|
|
44
|
+
return a + b
|
|
45
|
+
|
|
46
|
+
result = graph_runners.nullary_infer_sink(
|
|
47
|
+
composers.compose_dict(add, {"a": lambda: 3, "b": lambda: 4})
|
|
48
|
+
)
|
|
49
|
+
assert result == 7
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_compose_dict_with_graph_destination():
|
|
53
|
+
def add(a, b):
|
|
54
|
+
return a + b
|
|
55
|
+
|
|
56
|
+
destination_graph = composers.compose_left(lambda: 3, add, key="a")
|
|
57
|
+
result = graph_runners.nullary_infer_sink(
|
|
58
|
+
composers.compose_dict(destination_graph, {"b": lambda: 4})
|
|
59
|
+
)
|
|
60
|
+
assert result == 7
|
|
61
|
+
|
|
62
|
+
|
|
41
63
|
def test_ambiguity_does_not_blow_up():
|
|
42
64
|
counter = 0
|
|
43
65
|
|