Skip to content

Commit 178605f

Browse files
committed
break/REFACT(NET): screm on execute (not compute) ...
so as caompute() calmly calculates needs/provides, based on given IO.
1 parent 00a5a93 commit 178605f

File tree

3 files changed

+50
-30
lines changed

3 files changed

+50
-30
lines changed

graphtik/netop.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,10 @@ def compute(self, named_inputs, outputs=None, recompile=None) -> dict:
207207
)
208208

209209
solution = plan.execute(
210-
named_inputs, self.overwrites_collector, self.execution_method
210+
named_inputs,
211+
outputs,
212+
overwrites=self.overwrites_collector,
213+
method=self.execution_method,
211214
)
212215

213216
return solution

graphtik/network.py

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -397,25 +397,37 @@ def _execute_sequential_method(self, solution, overwrites, executed):
397397
else:
398398
raise AssertionError("Unrecognized instruction.%r" % step)
399399

400-
def execute(self, named_inputs, overwrites=None, method=None):
400+
def execute(self, named_inputs, outputs=None, *, overwrites=None, method=None):
401401
"""
402402
:param named_inputs:
403403
A maping of names --> values that must contain at least
404404
the compulsory inputs that were specified when the plan was built
405405
(but cannot enforce that!).
406406
Cloned, not modified.
407-
407+
:param outputs:
408+
If not None, they are just checked if possible, based on :attr:`provides`,
409+
and scream if not.
408410
:param overwrites:
409411
(optional) a mutable dict to collect calculated-but-discarded values
410412
because they were "pinned" by input vaules.
411413
If missing, the overwrites values are simply discarded.
412414
413415
:raises ValueError:
414-
If given `inputs` mismatched `plan.needs`, with msg:
416+
- If given `inputs` mismatched `plan.needs`, with msg:
415417
416418
*Plan needs more inputs...*
419+
- If `outputs` asked cannot be produced by the `graph`, with msg:
420+
421+
*Impossible outputs...*
422+
423+
- If cannot produce any `outputs` from the given `inputs`, with msg:
424+
425+
*Unsolvable graph: ...*
417426
"""
418427
try:
428+
if not self.dag:
429+
raise ValueError(f"Unsolvable graph:\n {self}")
430+
419431
# Check plan<-->inputs mismatch.
420432
#
421433
missing = iset(self.needs) - iset(named_inputs)
@@ -425,6 +437,17 @@ def execute(self, named_inputs, overwrites=None, method=None):
425437
f"\n given inputs: {list(named_inputs)}\n {self}"
426438
)
427439

440+
if outputs is not None:
441+
unknown = (
442+
iset(astuple(outputs, "outputs", allowed_types=abc.Sequence))
443+
- self.provides
444+
)
445+
if unknown:
446+
raise ValueError(
447+
f"Impossible outputs: {list(unknown)}\n for graph: {self}"
448+
f"\n {self}"
449+
)
450+
428451
# choose a method of execution
429452
executor = (
430453
self._execute_thread_pool_barrier_method
@@ -614,10 +637,6 @@ def prune(
614637
- if `outputs` asked do not exist in network, with msg:
615638
616639
*Unknown output nodes: ...*
617-
618-
- if `outputs` asked cannot be produced by the `graph`, with msg:
619-
620-
*Impossible outputs...*
621640
"""
622641
# TODO: break cycles here.
623642
dag = self.graph
@@ -678,13 +697,6 @@ def prune(
678697
outputs = iset(
679698
n for n in self.provides if n not in inputs and n in pruned_dag
680699
)
681-
else:
682-
unknown = iset(outputs) - pruned_dag
683-
if unknown:
684-
raise ValueError(
685-
f"Impossible outputs: {list(unknown)}\n for graph: {pruned_dag.nodes}"
686-
f"\n {self}"
687-
)
688700

689701
assert all(_yield_datanodes(pruned_dag)), pruned_dag
690702
assert inputs is not None and not isinstance(inputs, str)
@@ -847,11 +859,6 @@ def compile(
847859
plan = self._cached_plans[cache_key]
848860
else:
849861
pruned_dag, broken_edges, needs, provides = self.prune(inputs, outputs)
850-
if not pruned_dag:
851-
raise ValueError(
852-
f"Unsolvable graph:\n needs: {inputs}\n provides: {outputs}\n {self}"
853-
)
854-
855862
steps = self._build_execution_steps(pruned_dag, needs, outputs or ())
856863
plan = ExecutionPlan(
857864
self,

test/test_graphtik.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -181,23 +181,32 @@ def powers_in_trange(a, exponent):
181181
}
182182

183183
inputs = {"a": 1, "b": 2, "exponent": 3}
184-
plan = net.compile(outputs=None, inputs=inputs.keys())
185-
sol = plan.execute(named_inputs=inputs)
184+
outputs = None
185+
plan = net.compile(outputs=outputs, inputs=inputs.keys())
186+
sol = plan.execute(inputs)
187+
assert sol == exp
188+
sol = plan.execute(inputs, outputs)
186189
assert sol == exp
187190

188191
# get specific outputs
189192
exp = {"sum_ab_times_b": 6}
190-
plan = net.compile(outputs=["sum_ab_times_b"], inputs=list(inputs))
191-
sol = plan.execute(named_inputs=inputs)
193+
outputs=["sum_ab_times_b"]
194+
plan = net.compile(outputs=outputs, inputs=list(inputs))
195+
sol = plan.execute(inputs)
196+
assert sol == exp
197+
sol = plan.execute(inputs, outputs)
192198
assert sol == exp
193199

194200
# start with inputs already computed
195201
inputs = {"sum_ab": 1, "b": 2, "exponent": 3}
196202
exp = {"sum_ab_times_b": 2}
197-
plan = net.compile(outputs=["sum_ab_times_b"], inputs=inputs)
203+
outputs=["sum_ab_times_b"]
204+
plan = net.compile(outputs=outputs, inputs=inputs)
198205
with pytest.raises(ValueError, match=r"Plan needs more inputs:"):
199206
sol = plan.execute(named_inputs={"sum_ab": 1})
200-
sol = plan.execute(named_inputs=inputs)
207+
sol = plan.execute(inputs)
208+
assert sol == exp
209+
sol = plan.execute(inputs, outputs)
201210
assert sol == exp
202211

203212

@@ -790,8 +799,9 @@ def add(a=0, b=0):
790799
== "NetworkOperation('t', needs=[optional('a')], provides=['sum1'])"
791800
)
792801

793-
with pytest.raises(ValueError, match="Impossible outputs:"):
794-
compose("t", *ops, needs="bb", provides=["sum2"])
802+
netop = compose("t", *ops, needs="bb", provides=["sum2"])
803+
with pytest.raises(ValueError, match="Unsolvable graph:"):
804+
netop.compute({'bb': 11})
795805

796806
## Narrow by unknown needs
797807
#
@@ -845,7 +855,7 @@ def test_sideffect_no_real_data(exemethod, netop_sideffect1: NetworkOperation):
845855
with pytest.raises(ValueError, match="Unsolvable graph"):
846856
assert graph.compute(inp, recompile=True)
847857

848-
with pytest.raises(ValueError, match="Impossible output"):
858+
with pytest.raises(ValueError, match="Unsolvable graph"):
849859
graph.compute(inp, ["box", sideffect("b")])
850860

851861
with pytest.raises(ValueError, match="Plan needs more inputs"):
@@ -859,7 +869,7 @@ def test_sideffect_no_real_data(exemethod, netop_sideffect1: NetworkOperation):
859869
assert sol == {"box": [1, 2, 3], sideffect("a"): True}
860870
#
861871
# bad, not asked the out-sideffect
862-
with pytest.raises(ValueError, match="Impossible output"):
872+
with pytest.raises(ValueError, match="Unsolvable graph"):
863873
sol = graph.compute({"box": [0], sideffect("a"): True}, "box")
864874
#
865875
# ok, asked the 1st out-sideffect

0 commit comments

Comments
 (0)