From cd78ea7556ca014301db8979fc49c13592779850 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 18 Jul 2023 11:17:04 -0700 Subject: [PATCH 01/15] Refactor: Rename function --- tests/unit/workflow/test_workflow.py | 44 ++++++++++++++-------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index b0bf3751c..99e467b07 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -8,7 +8,7 @@ from pyiron_contrib.workflow.workflow import Workflow -def fnc(x=0): +def plus_one(x=0): y = x + 1 return y @@ -20,10 +20,10 @@ def test_node_addition(self): wf = Workflow("my_workflow") # Validate the four ways to add a node - wf.add(Function(fnc, label="foo")) - wf.add.Function(fnc, label="bar") - wf.baz = Function(fnc, label="whatever_baz_gets_used") - Function(fnc, label="qux", parent=wf) + wf.add(Function(plus_one, label="foo")) + wf.add.Function(plus_one, label="bar") + wf.baz = Function(plus_one, label="whatever_baz_gets_used") + Function(plus_one, label="qux", parent=wf) self.assertListEqual(list(wf.nodes.keys()), ["foo", "bar", "baz", "qux"]) wf.boa = wf.qux self.assertListEqual( @@ -34,13 +34,13 @@ def test_node_addition(self): wf.strict_naming = False # Validate name incrementation - wf.add(Function(fnc, label="foo")) - wf.add.Function(fnc, label="bar") + wf.add(Function(plus_one, label="foo")) + wf.add.Function(plus_one, label="bar") wf.baz = Function( - fnc, + plus_one, label="without_strict_you_can_override_by_assignment" ) - Function(fnc, label="boa", parent=wf) + Function(plus_one, label="boa", parent=wf) self.assertListEqual( list(wf.nodes.keys()), [ @@ -52,16 +52,16 @@ def test_node_addition(self): wf.strict_naming = True # Validate name preservation with self.assertRaises(AttributeError): - wf.add(Function(fnc, label="foo")) + wf.add(Function(plus_one, label="foo")) with self.assertRaises(AttributeError): - wf.add.Function(fnc, label="bar") + wf.add.Function(plus_one, label="bar") with self.assertRaises(AttributeError): - wf.baz = Function(fnc, label="whatever_baz_gets_used") + wf.baz = Function(plus_one, label="whatever_baz_gets_used") with self.assertRaises(AttributeError): - Function(fnc, label="boa", parent=wf) + Function(plus_one, label="boa", parent=wf) def test_node_packages(self): wf = Workflow("my_workflow") @@ -80,8 +80,8 @@ def test_node_packages(self): def test_double_workfloage_and_node_removal(self): wf1 = Workflow("one") - wf1.add.Function(fnc, label="node1") - node2 = Function(fnc, label="node2", parent=wf1, x=wf1.node1.outputs.y) + wf1.add.Function(plus_one, label="node1") + node2 = Function(plus_one, label="node2", parent=wf1, x=wf1.node1.outputs.y) self.assertTrue(node2.connected) wf2 = Workflow("two") @@ -95,9 +95,9 @@ def test_double_workfloage_and_node_removal(self): def test_workflow_io(self): wf = Workflow("wf") - wf.add.Function(fnc, label="n1") - wf.add.Function(fnc, label="n2") - wf.add.Function(fnc, label="n3") + wf.add.Function(plus_one, label="n1") + wf.add.Function(plus_one, label="n2") + wf.add.Function(plus_one, label="n3") with self.subTest("Workflow IO should be drawn from its nodes"): self.assertEqual(len(wf.inputs), 3) @@ -122,7 +122,7 @@ def test_working_directory(self): self.assertTrue(wf._working_directory is None) self.assertIsInstance(wf.working_directory, DirectoryObject) self.assertTrue(str(wf.working_directory.path).endswith(wf.label)) - wf.add.Function(fnc) + wf.add.Function(plus_one) self.assertTrue(str(wf.fnc.working_directory.path).endswith(wf.fnc.label)) wf.working_directory.delete() @@ -192,8 +192,8 @@ def sum(a, b): def test_call(self): wf = Workflow("wf") - wf.a = wf.add.SingleValue(fnc) - wf.b = wf.add.SingleValue(fnc) + wf.a = wf.add.SingleValue(plus_one) + wf.b = wf.add.SingleValue(plus_one) @Workflow.wrap_as.single_value_node(output_labels="sum") def sum_(a, b): @@ -207,7 +207,7 @@ def sum_(a, b): ) wf(a_x=42, b_x=42) self.assertEqual( - fnc(42) + fnc(42), + plus_one(42) + plus_one(42), wf.sum.outputs.sum.value, msg="Workflow should accept input channel kwargs and update inputs " "accordingly" From 5d896a5d9963f8a53c5fb8aa36c87d5565673921 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 18 Jul 2023 11:42:22 -0700 Subject: [PATCH 02/15] Make Composite conform to abstract Node spec Namely, on_run should be a property returning a callable --- pyiron_contrib/workflow/composite.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index bacc934e8..3834513bd 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -115,12 +115,22 @@ def upstream_nodes(self) -> list[Node]: if node.outputs.connected and not node.inputs.connected ] + @property def on_run(self): + return self.run_graph + + @staticmethod + def run_graph(self): starting_nodes = ( self.upstream_nodes if self.starting_nodes is None else self.starting_nodes ) for node in starting_nodes: node.run() + return DotDict(self.outputs.to_value_dict()) + + @property + def run_args(self) -> dict: + return {"self": self} def add_node(self, node: Node, label: Optional[str] = None) -> None: """ From 23fea7795fcec61e4386f0e9db9ca55cfbc909cb Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 18 Jul 2023 12:02:56 -0700 Subject: [PATCH 03/15] Disallow executors for composite nodes They are not implemented and working yet, so at least fail cleanly! --- pyiron_contrib/workflow/composite.py | 11 +++++++++++ tests/unit/workflow/test_workflow.py | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 3834513bd..623817068 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -101,6 +101,17 @@ def __init__( self.add: NodeAdder = NodeAdder(self) self.starting_nodes: None | list[Node] = None + @property + def executor(self) -> None: + return None + + @executor.setter + def executor(self, new_executor): + if new_executor is not None: + raise NotImplementedError( + "Running composite nodes with an executor is not yet supported" + ) + def to_dict(self): return { "label": self.label, diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 99e467b07..668d7b6cc 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -143,6 +143,11 @@ def test_no_parents(self): # In both cases, we satisfy the spec that workflow's can't have parents wf2.parent = wf + def test_executor(self): + wf = Workflow("wf") + with self.assertRaises(NotImplementedError): + wf.executor = "literally anything other than None should raise the error" + def test_parallel_execution(self): wf = Workflow("wf") From 9b7e23456b99bd8911a0de6ff6d7bd84c17d358d Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 18 Jul 2023 12:15:42 -0700 Subject: [PATCH 04/15] Fail cleanly with function nodes that use self too --- pyiron_contrib/workflow/function.py | 6 ++++++ tests/unit/workflow/test_function.py | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 883bae4bf..90c6844b8 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -535,6 +535,12 @@ def on_run(self): def run_args(self) -> dict: kwargs = self.inputs.to_value_dict() if "self" in self._input_args: + if self.executor is not None: + raise NotImplementedError( + f"The node {self.label} cannot be run on an executor because it " + f"uses the `self` argument and this functionality is not yet " + f"implemented" + ) kwargs["self"] = self return kwargs diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index d0251eae4..34dd0bfe8 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -3,6 +3,7 @@ from typing import Optional, Union import warnings +from pyiron_contrib.executors import CloudpickleProcessPoolExecutor from pyiron_contrib.workflow.channels import NotData from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.function import ( @@ -279,6 +280,13 @@ def with_self(self, x: float) -> float: msg="Function functions should be able to modify attributes on the node object." ) + node.executor = CloudpickleProcessPoolExecutor + with self.assertRaises(NotImplementedError): + # Submitting node_functions that use self is still raising + # TypeError: cannot pickle '_thread.lock' object + # For now we just fail cleanly + node.run() + def with_messed_self(x: float, self) -> float: return x + 0.1 From 2c4b30031f108554bf99340370ca4c2c829f1b53 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 18 Jul 2023 12:16:21 -0700 Subject: [PATCH 05/15] Add explanatory comment for devs --- tests/unit/workflow/test_workflow.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 668d7b6cc..af320a26f 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -146,6 +146,9 @@ def test_no_parents(self): def test_executor(self): wf = Workflow("wf") with self.assertRaises(NotImplementedError): + # Submitting callables that use self is still raising + # TypeError: cannot pickle '_thread.lock' object + # For now we just fail cleanly wf.executor = "literally anything other than None should raise the error" def test_parallel_execution(self): From 5b3835d52af38498be89976d22699330f11d3f88 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 18 Jul 2023 12:22:32 -0700 Subject: [PATCH 06/15] :bug: finish renaming the function used in the test suite --- tests/unit/workflow/test_workflow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index af320a26f..18ea73ecd 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -123,7 +123,9 @@ def test_working_directory(self): self.assertIsInstance(wf.working_directory, DirectoryObject) self.assertTrue(str(wf.working_directory.path).endswith(wf.label)) wf.add.Function(plus_one) - self.assertTrue(str(wf.fnc.working_directory.path).endswith(wf.fnc.label)) + self.assertTrue( + str(wf.plus_one.working_directory.path).endswith(wf.plus_one.label) + ) wf.working_directory.delete() def test_no_parents(self): From 74adeeef4ab75604158ee5e0b1dcbf621488c431 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 18 Jul 2023 12:28:22 -0700 Subject: [PATCH 07/15] Return output when calling `run` And downstream stuff like `update` and thus `__call__`. This was requested by Joerg and now makes things really start to feel like regular python --- pyiron_contrib/workflow/node.py | 20 ++++---- tests/unit/workflow/test_function.py | 72 +++++++++++++++++++++++++++- tests/unit/workflow/test_workflow.py | 45 +++++++++++++++++ 3 files changed, 127 insertions(+), 10 deletions(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 0e46dbce7..a1da62b1f 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -8,7 +8,7 @@ import warnings from abc import ABC, abstractmethod from concurrent.futures import Future -from typing import Optional, TYPE_CHECKING +from typing import Any, Optional, TYPE_CHECKING from pyiron_contrib.executors import CloudpickleProcessPoolExecutor from pyiron_contrib.workflow.files import DirectoryObject @@ -154,7 +154,7 @@ def outputs(self) -> Outputs: @property @abstractmethod - def on_run(self) -> callable[..., tuple]: + def on_run(self) -> callable[..., Any | tuple]: """ What the node actually does! """ @@ -167,7 +167,7 @@ def run_args(self) -> dict: """ return {} - def process_run_result(self, run_output: tuple) -> None: + def process_run_result(self, run_output: Any | tuple) -> None: """ What to _do_ with the results of `on_run` once you have them. @@ -176,7 +176,7 @@ def process_run_result(self, run_output: tuple) -> None: """ pass - def run(self) -> None: + def run(self) -> Any | tuple | Future: """ Executes the functionality of the node defined in `on_run`. Handles the status of the node, and communicating with any remote @@ -195,10 +195,11 @@ def run(self) -> None: self.running = False self.failed = True raise e - self.finish_run(run_output) + return self.finish_run(run_output) elif isinstance(self.executor, CloudpickleProcessPoolExecutor): self.future = self.executor.submit(self.on_run, **self.run_args) self.future.add_done_callback(self.finish_run) + return self.future else: raise NotImplementedError( "We currently only support executing the node functionality right on " @@ -206,7 +207,7 @@ def run(self) -> None: "pyiron_contrib.workflow.util.CloudpickleProcessPoolExecutor." ) - def finish_run(self, run_output: tuple | Future): + def finish_run(self, run_output: tuple | Future) -> Any | tuple: """ Switch the node status, process the run result, then fire the ran signal. @@ -224,6 +225,7 @@ def finish_run(self, run_output: tuple | Future): try: self.process_run_result(run_output) self.signals.output.ran() + return run_output except Exception as e: self.failed = True raise e @@ -234,9 +236,9 @@ def _build_signal_channels(self) -> Signals: signals.output.ran = OutputSignal("ran", self) return signals - def update(self) -> None: + def update(self) -> Any | tuple | Future | None: if self.run_on_updates and self.ready: - self.run() + return self.run() @property def working_directory(self): @@ -300,4 +302,4 @@ def _batch_update_input(self, **kwargs): def __call__(self, **kwargs) -> None: self._batch_update_input(**kwargs) - self.update() + return self.update() diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 34dd0bfe8..4d40382ad 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -1,6 +1,7 @@ -import unittest +from concurrent.futures import Future from sys import version_info from typing import Optional, Union +import unittest import warnings from pyiron_contrib.executors import CloudpickleProcessPoolExecutor @@ -342,6 +343,75 @@ def test_call(self): # there should just be a warning that the data didn't get updated node(some_randome_kwaaaaarg="foo") + def test_return_value(self): + node = Function(plus_one) + + with self.subTest("Run on main process"): + return_on_call = node(1) + self.assertEqual( + return_on_call, + plus_one(1), + msg="Run output should be returned on call" + ) + + return_on_update = node.update() + self.assertEqual( + return_on_update, + plus_one(1), + msg="Run output should be returned on update" + ) + + node.run_on_updates = False + return_on_update_without_run = node.update() + self.assertIsNone( + return_on_update_without_run, + msg="When not running on updates, the update should not return anything" + ) + return_on_call_without_run = node(2) + self.assertIsNone( + return_on_call_without_run, + msg="When not running on updates, the call should not return anything" + ) + return_on_explicit_run = node.run() + self.assertEqual( + return_on_explicit_run, + plus_one(2), + msg="On explicit run, the most recent input data should be used and the " + "result should be returned" + ) + + with self.subTest("Run on executor"): + node.executor = CloudpickleProcessPoolExecutor() + node.run_on_updates = False + + return_on_update_without_run = node.update() + self.assertIsNone( + return_on_update_without_run, + msg="When not running on updates, the update should not return " + "anything whether there is an executor or not" + ) + return_on_explicit_run = node.run() + self.assertIsInstance( + return_on_explicit_run, + Future, + msg="Running with an executor should return the future" + ) + with self.assertRaises(RuntimeError): + # The executor run should take a second + # So we can double check that attempting to run while already running + # raises an error + node.run() + node.future.result() # Wait for the remote execution to finish + + node.run_on_updates = True + return_on_update_with_run = node.update() + self.assertIsInstance( + return_on_update_with_run, + Future, + msg="Updating should return the same as run when we get a run from the " + "update, obviously..." + ) + node.future.result() # Wait for the remote execution to finish @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestSlow(unittest.TestCase): diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 18ea73ecd..7a8efac73 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -5,6 +5,7 @@ from pyiron_contrib.workflow.channels import NotData from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.function import Function +from pyiron_contrib.workflow.util import DotDict from pyiron_contrib.workflow.workflow import Workflow @@ -229,6 +230,50 @@ def sum_(a, b): # We _must_ use kwargs wf(42, 42) + def test_return_value(self): + wf = Workflow("wf") + wf.run_on_updates = True + wf.a = wf.add.SingleValue(plus_one) + wf.b = wf.add.SingleValue(plus_one, x=wf.a) + + with self.subTest("Run on main process"): + return_on_call = wf(a_x=1) + self.assertEqual( + return_on_call, + DotDict({"b_y": 1 + 2}), + msg="Run output should be returned on call. Expecting a DotDict of " + "output values" + ) + + return_on_update = wf.update() + self.assertEqual( + return_on_update.b_y, + 1 + 2, + msg="Run output should be returned on update" + ) + + wf.run_on_updates = False + return_on_update_without_run = wf.update() + self.assertIsNone( + return_on_update_without_run, + msg="When not running on updates, the update should not return anything" + ) + return_on_call_without_run = wf(a_x=2) + self.assertIsNone( + return_on_call_without_run, + msg="When not running on updates, the call should not return anything" + ) + return_on_explicit_run = wf.run() + self.assertEqual( + return_on_explicit_run["b_y"], + 2 + 2, + msg="On explicit run, the most recent input data should be used and the " + "result should be returned" + ) + + # Note: We don't need to test running on an executor, because Workflows can't + # do that yet + if __name__ == '__main__': unittest.main() From 712afb90b5ab3fbf5a161996feef2f2b7daa29bc Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 18 Jul 2023 12:42:52 -0700 Subject: [PATCH 08/15] Consistently pass the Node.run_on_updates kwarg through in children --- pyiron_contrib/workflow/composite.py | 9 ++++++++- pyiron_contrib/workflow/function.py | 2 +- pyiron_contrib/workflow/workflow.py | 15 +++++++++++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 623817068..4c6401d3d 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -92,10 +92,17 @@ def __init__( label: str, *args, parent: Optional[Composite] = None, + run_on_updates: bool = False, strict_naming: bool = True, **kwargs, ): - super().__init__(*args, label=label, parent=parent, **kwargs) + super().__init__( + *args, + label=label, + parent=parent, + run_on_updates=run_on_updates, + **kwargs + ) self.strict_naming: bool = strict_naming self.nodes: DotDict[str:Node] = DotDict() self.add: NodeAdder = NodeAdder(self) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 90c6844b8..0aa82779a 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -360,6 +360,7 @@ def __init__( super().__init__( label=label if label is not None else node_function.__name__, parent=parent, + run_on_updates=run_on_updates, # **kwargs, ) @@ -379,7 +380,6 @@ def __init__( ) self._verify_that_channels_requiring_update_all_exist() - self.run_on_updates = run_on_updates self._batch_update_input(*args, **kwargs) if update_on_instantiation: diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 73d18f648..4550c3e94 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -125,8 +125,19 @@ class Workflow(Composite): integrity of workflows when they're used somewhere else? """ - def __init__(self, label: str, *nodes: Node, strict_naming=True): - super().__init__(label=label, parent=None, strict_naming=strict_naming) + def __init__( + self, + label: str, + *nodes: Node, + run_on_updates: bool = False, + strict_naming=True + ): + super().__init__( + label=label, + parent=None, + run_on_updates=run_on_updates, + strict_naming=strict_naming, + ) for node in nodes: self.add_node(node) From 0ce3d35db325652e1db6403dafbc4ed46716bb8f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 19 Jul 2023 09:27:51 -0700 Subject: [PATCH 09/15] Update Node docs --- pyiron_contrib/workflow/node.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index a1da62b1f..e5f2ec3d7 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -45,6 +45,16 @@ class Node(HasToDict, ABC): By default, nodes' signals input comes with `run` and `ran` IO ports which force the `run()` method and which emit after `finish_run()` is completed, respectfully. + The `run()` method returns a representation of the node output (possible a futures + object, if the node is running on an executor), and consequently `update()` also + returns this output if the node is `ready` and has `run_on_updates = True`. + + Calling an already instantiated node allows its input channels to be updated using + keyword arguments corresponding to the channel labels, performing a batch-update of + all supplied input and then calling `update()`. + As such, calling the node _also_ returns a representation of the output (or `None` + if the node is not set to run on updates, or is otherwise unready to run). + Nodes have a status, which is currently represented by the `running` and `failed` boolean flags. Their value is controlled automatically in the defined `run` and `finish_run` From 1de973f8a3b16961f44e45d052f080457655281e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 19 Jul 2023 09:33:58 -0700 Subject: [PATCH 10/15] Update Function docs --- pyiron_contrib/workflow/function.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 0aa82779a..decc53a59 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -70,6 +70,10 @@ class Function(Node): on call. This invokes an `update()` call, which can in turn invoke `run()` if `run_on_updates` is set to `True`. + `run()` returns the output of the executed function, or a futures object if the + node is set to use an executor. + Calling the node or executing an `update()` returns the same thing as running, if + the node is run, or `None` if it is not set to run on updates or not ready to run. Args: node_function (callable): The function determining the behaviour of the node. @@ -163,10 +167,12 @@ class Function(Node): {'p1': 2, 'm1': 1} Input data can be provided to both initialization and on call as ordered args - or keyword kwargs, e.g.: + or keyword kwargs. + When running, updating, or calling the node, the output of the wrapped function + (if it winds up getting run in the conditional cases of updating and calling) is + returned: >>> plus_minus_1(2, y=3) - >>> plus_minus_1.outputs.to_value_dict() - {'p1': 3, 'm1': 2} + (3, 2) Finally, we might stop these updates from happening automatically, even when all the input data is present and available: @@ -180,8 +186,7 @@ class Function(Node): With these flags set, the node requires us to manually call a run: >>> plus_minus_1.run() - >>> plus_minus_1.outputs.to_value_dict() - {'p1': 1, 'm1': -1} + (-1, 1) So function nodes have the most basic level of protection that they won't run if they haven't seen any input data. From c6ee0cae60a72011dc6c5f6d9fe63f7cc1963aae Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 19 Jul 2023 09:41:25 -0700 Subject: [PATCH 11/15] Update Compositedocs --- pyiron_contrib/workflow/composite.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 4c6401d3d..c07092d24 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -57,6 +57,9 @@ class Composite(Node, ABC): By default, `run()` will be called on all owned nodes have output connections but no input connections (i.e. the upstream-most nodes), but this can be overridden to specify particular nodes to use instead. + The `run()` method (and `update()`, and calling the workflow, when these result in + a run), return a new dot-accessible dictionary of keys and values created from the + composite output IO panel. Does not specify `input` and `output` as demanded by the parent class; this requirement is still passed on to children. From c20937b8fd474724ebc20966fbbc83a1bb72f3ec Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 19 Jul 2023 09:43:49 -0700 Subject: [PATCH 12/15] Have Composite and Workflow run on update by default We may wish to later make Macro's slow, but for Workflows, since the IO is just routing through to the owned nodes, input updates are _anyhow_ most of the time re-running things, so it's a sensible default IMO --- pyiron_contrib/workflow/composite.py | 2 +- pyiron_contrib/workflow/workflow.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index c07092d24..c27b3210f 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -95,7 +95,7 @@ def __init__( label: str, *args, parent: Optional[Composite] = None, - run_on_updates: bool = False, + run_on_updates: bool = True, strict_naming: bool = True, **kwargs, ): diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 4550c3e94..f03ce1ab6 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -129,7 +129,7 @@ def __init__( self, label: str, *nodes: Node, - run_on_updates: bool = False, + run_on_updates: bool = True, strict_naming=True ): super().__init__( From b796753a438c5a4ced07820f727db1619bcbc178 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 19 Jul 2023 09:45:40 -0700 Subject: [PATCH 13/15] Update Workflow docs --- pyiron_contrib/workflow/workflow.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index f03ce1ab6..e2b543e80 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -88,8 +88,13 @@ class Workflow(Composite): These input keys can be used when calling the workflow to update the input. In our example, the nodes update automatically when their input gets updated, so all we need to do to see updated workflow output is update the input: - >>> wf(first_x=10) - >>> wf.outputs.second_y.value + >>> out = wf(first_x=10) + >>> out + {'second_y': 12} + + Note: this _looks_ like a dictionary, but has some extra convenience that we + can dot-access data: + >>> out.second_y 12 Workflows also give access to packages of pre-built nodes under different From b674f36a71e9c8b3924c3ebce3e25e4ee5bd820f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 19 Jul 2023 09:46:49 -0700 Subject: [PATCH 14/15] Notebook: update topic order --- notebooks/workflow_example.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 9ed57c66e..6c76b4db4 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -36,9 +36,9 @@ "- How to instantiate a node\n", "- How to make reusable node classes\n", "- How to connect node inputs and outputs together\n", + "- Flow control (i.e. signal channels vs data channels)\n", "- Defining new nodes from special node classes (Fast and SingleValue)\n", "- The five ways of adding nodes to a workflow\n", - "- Flow control (i.e. signal channels vs data channels)\n", "- Using pre-defined nodes " ] }, From 52028ebc40031a24d0229f00b25f981380114a6a Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 19 Jul 2023 09:53:54 -0700 Subject: [PATCH 15/15] Notebook: show how run returns values --- notebooks/workflow_example.ipynb | 139 ++++++++++++++++++++++--------- 1 file changed, 100 insertions(+), 39 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 6c76b4db4..e273a9812 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -11,7 +11,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "88c66e527673496c8f5b7ea75538baa0", + "model_id": "00c1cb12911741a18f9c06ba09e74ae6", "version_major": 2, "version_minor": 0 }, @@ -357,6 +357,43 @@ "adder_node.outputs.sum_.value" ] }, + { + "cell_type": "markdown", + "id": "c0997630-c053-42bb-8c0d-332f8bc26216", + "metadata": {}, + "source": [ + "Finally, when running (or updating or calling when those result in a run -- i.e. the node is set to run on updates and is ready) a function node returns the wrapped function output directly:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "69b59737-9e09-4b4b-a0e2-76a09de02c08", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "31" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "adder_node(15, 16)" + ] + }, + { + "cell_type": "markdown", + "id": "f233f3f7-9576-4400-8e92-a1f6109d7f9b", + "metadata": {}, + "source": [ + "Note for advanced users: when the node has an executor set, running returns a futures object for the calculation, whose `.result()` will eventually be the function output." + ] + }, { "cell_type": "markdown", "id": "07a22cee-e340-4551-bb81-07d8be1d152b", @@ -373,7 +410,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "id": "61b43a9b-8dad-48b7-9194-2045e465793b", "metadata": {}, "outputs": [], @@ -383,7 +420,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "id": "647360a9-c971-4272-995c-aa01e5f5bb83", "metadata": {}, "outputs": [ @@ -421,7 +458,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "8fb0671b-045a-4d71-9d35-f0beadc9cf3a", "metadata": {}, "outputs": [ @@ -431,7 +468,7 @@ "-10" ] }, - "execution_count": 15, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -452,7 +489,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "5ce91f42-7aec-492c-94fb-2320c971cd79", "metadata": {}, "outputs": [ @@ -488,7 +525,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "20360fe7-b422-4d78-9bd1-de233f28c8df", "metadata": {}, "outputs": [ @@ -521,7 +558,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "1a4e9693-0980-4435-aecc-3331d8b608dd", "metadata": {}, "outputs": [], @@ -533,7 +570,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "7c4d314b-33bb-4a67-bfb9-ed77fba3949c", "metadata": {}, "outputs": [ @@ -580,7 +617,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "id": "2e418abf-7059-4e1e-9b9f-b3dc0a4b5e35", "metadata": {}, "outputs": [ @@ -621,7 +658,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "id": "3310eac4-04f6-421b-9824-19bb2d680be6", "metadata": {}, "outputs": [ @@ -663,7 +700,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "id": "7a6f2bce-6b5e-4321-9457-0a6790d2202a", "metadata": {}, "outputs": [], @@ -673,13 +710,13 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 24, "id": "6569014a-815b-46dd-8b47-4e1cd4584b3b", "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiMAAAGdCAYAAADAAnMpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAgdElEQVR4nO3db0yd9f3/8dc5h8KpHRxDK3Dasopdq0WiDhoqdI2Zs4xq8NsbSzGuVl1NpOpq7XRp00WkMSG6aKabZf6rxrQ6olMjCUO5sSltt7FSmojHRGPZaO1BQomH4x/a9ZzP70YDP88OUM4pnA/nnOcjOTe4+Jz2zZVu5+l1cT7HYYwxAgAAsMRpewAAAJDeiBEAAGAVMQIAAKwiRgAAgFXECAAAsIoYAQAAVhEjAADAKmIEAABYlWF7gKkIh8M6efKksrOz5XA4bI8DAACmwBijYDCohQsXyumc+PpHUsTIyZMnVVhYaHsMAAAQh+PHj2vx4sUTfj8pYiQ7O1vSuR8mJyfH8jQAAGAqhoeHVVhYOPY6PpGkiJHRWzM5OTnECAAASeZ8v2LBL7ACAACriBEAAGAVMQIAAKwiRgAAgFXECAAAsIoYAQAAVhEjAADAKmIEAABYlRSbngHAbBAKG3X2DmkgOKK8bLfKi3LlcvJ5WcCFIkYAYAraevxqaPHJHxgZO+b1uFVfU6zqEq/FyYDkx20aADiPth6/tuw7EhEiktQfGNGWfUfU1uO3NBmQGogRAJhEKGzU0OKTGed7o8caWnwKhcdbAWAqiBEAmERn71DUFZHvMpL8gRF19g4lbiggxRAjADCJgeDEIRLPOgDRiBEAmERetnta1wGIRowAwCTKi3Ll9bg10Rt4HTr3rpryotxEjgWkFGIEACbhcjpUX1MsSVFBMvp1fU0x+40AF4AYAYDzqC7xqmljqQo8kbdiCjxuNW0sZZ8R4AKx6RkATEF1iVdriwvYgRWYAcQIAEyRy+lQxdL5tscAUg63aQAAgFXECAAAsIoYAQAAVhEjAADAKmIEAABYRYwAAACriBEAAGAVMQIAAKwiRgAAgFXECAAAsIoYAQAAVhEjAADAKmIEAABYRYwAAACriBEAAGAVMQIAAKwiRgAAgFXECAAAsIoYAQAAVhEjAADAKmIEAABYRYwAAACriBEAAGAVMQIAAKwiRgAAgFXECAAAsIoYAQAAVhEjAADAKmIEAABYRYwAAACriBEAAGAVMQIAAKwiRgAAgFXECAAAsIoYAQAAVmXYHgAAgOkUCht19g5pIDiivGy3yoty5XI6bI+FSRAjAICU0dbjV0OLT/7AyNgxr8et+ppiVZd4LU6GyXCbBgCQEtp6/Nqy70hEiEhSf2BEW/YdUVuP39JkOB9iBACQ9EJho4YWn8w43xs91tDiUyg83grYRowAAJJeZ+9Q1BWR7zKS/IERdfYOJW4oTBkxAgBIegPBiUMknnVILGIEAJD08rLd07oOiUWMAACSXnlRrrwetyZ6A69D595VU16Um8ixMEXECAAg6bmcDtXXFEtSVJCMfl1fU8x+I7MUMQIASAnVJV41bSxVgSfyVkyBx62mjaXsMzKLsekZACBlVJd4tba4gB1Yk0xcV0b27NmjoqIiud1ulZWVqaOjY9L1+/fv19VXX62LLrpIXq9Xd955p06dOhXXwAAATMbldKhi6Xz93zWLVLF0PiGSBGKOkebmZm3btk27du1Sd3e31qxZo3Xr1qmvr2/c9QcOHNCmTZu0efNmffTRR3r99df1r3/9S3fdddcFDw8AAJJfzDHy5JNPavPmzbrrrru0YsUK/e53v1NhYaGamprGXf+Pf/xDl156qbZu3aqioiL96Ec/0t13363Dhw9f8PAAACD5xRQjZ86cUVdXl6qqqiKOV1VV6dChQ+M+p7KyUidOnFBra6uMMfriiy/0xhtv6Kabbprw7zl9+rSGh4cjHgAAIDXFFCODg4MKhULKz8+POJ6fn6/+/v5xn1NZWan9+/ertrZWmZmZKigo0MUXX6zf//73E/49jY2N8ng8Y4/CwsJYxgQAAEkkrl9gdTgifxnIGBN1bJTP59PWrVv18MMPq6urS21tbert7VVdXd2Ef/7OnTsVCATGHsePH49nTAAAkARiemvvggUL5HK5oq6CDAwMRF0tGdXY2KjVq1froYcekiRdddVVmjdvntasWaNHH31UXm/0+76zsrKUlZUVy2gAACBJxXRlJDMzU2VlZWpvb4843t7ersrKynGf880338jpjPxrXC6XpHNXVAAAQHqL+TbN9u3b9cILL2jv3r36+OOP9cADD6ivr2/stsvOnTu1adOmsfU1NTV688031dTUpGPHjungwYPaunWrysvLtXDhwun7SQAAQFKKeQfW2tpa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hUurGMnLdk/rOgAAcOHSKkbKi3Ll9bg10Rt4HTr3rpryotxEjgUAQFpLqxhxOR2qrymWpKggGf26vqaY/UYAAEigtIoRSaou8appY6kKPJG3Ygo8bjVtLGWfEQAAEiwtNz2rLvFqbXEBO7ACADALpGWMSOdu2VQsnW97DAAA0l7a3aYBAACzCzECAACsIkYAAIBVxAgAALCKGAEAAFYRIwAAwCpiBAAAWEWMAAAAq4gRAABgVVLswGqMkSQNDw9bngQAAEzV6Ov26Ov4RJIiRoLBoCSpsLDQ8iQAACBWwWBQHo9nwu87zPlyZRYIh8M6efKksrOz5XBEfpjd8PCwCgsLdfz4ceXk5FiaML1wzhOPc24H5z3xOOd2zNR5N8YoGAxq4cKFcjon/s2QpLgy4nQ6tXjx4knX5OTk8A83wTjnicc5t4Pznniccztm4rxPdkVkFL/ACgAArCJGAACAVUkfI1lZWaqvr1dWVpbtUdIG5zzxOOd2cN4Tj3Nuh+3znhS/wAoAAFJX0l8ZAQAAyY0YAQAAVhEjAADAKmIEAABYNetjZM+ePSoqKpLb7VZZWZk6OjomXf/++++rrKxMbrdbl112mf74xz8maNLUEst5f/PNN7V27VpdcsklysnJUUVFhd59990ETpsaYv23PurgwYPKyMjQNddcM7MDpqhYz/vp06e1a9cuLVmyRFlZWVq6dKn27t2boGlTQ6znfP/+/br66qt10UUXyev16s4779SpU6cSNG3y++CDD1RTU6OFCxfK4XDo7bffPu9zEv5aamaxP/3pT2bOnDnm+eefNz6fz9x///1m3rx55j//+c+4648dO2Yuuugic//99xufz2eef/55M2fOHPPGG28kePLkFut5v//++81jjz1mOjs7zSeffGJ27txp5syZY44cOZLgyZNXrOd81Jdffmkuu+wyU1VVZa6++urEDJtC4jnvN998s1m1apVpb283vb295p///Kc5ePBgAqdObrGe846ODuN0Os1TTz1ljh07Zjo6OsyVV15p1q9fn+DJk1dra6vZtWuX+fOf/2wkmbfeemvS9TZeS2d1jJSXl5u6urqIY1dccYXZsWPHuOt//etfmyuuuCLi2N13322uvfbaGZsxFcV63sdTXFxsGhoapnu0lBXvOa+trTW/+c1vTH19PTESh1jP+1/+8hfj8XjMqVOnEjFeSor1nP/2t781l112WcSxp59+2ixevHjGZkxlU4kRG6+ls/Y2zZkzZ9TV1aWqqqqI41VVVTp06NC4z/n73/8etf6nP/2pDh8+rP/+978zNmsqiee8/69wOKxgMKjc3NyZGDHlxHvOX3rpJX322Weqr6+f6RFTUjzn/Z133tHKlSv1+OOPa9GiRVq+fLkefPBBffvtt4kYOenFc84rKyt14sQJtba2yhijL774Qm+88YZuuummRIyclmy8ls7aD8obHBxUKBRSfn5+xPH8/Hz19/eP+5z+/v5x1589e1aDg4Pyer0zNm+qiOe8/68nnnhCX3/9tTZs2DATI6aceM75p59+qh07dqijo0MZGbP2f8azWjzn/dixYzpw4IDcbrfeeustDQ4O6p577tHQ0BC/NzIF8ZzzyspK7d+/X7W1tRoZGdHZs2d188036/e//30iRk5LNl5LZ+2VkVEOhyPia2NM1LHzrR/vOCYX63kf9dprr+mRRx5Rc3Oz8vLyZmq8lDTVcx4KhXTrrbeqoaFBy5cvT9R4KSuWf+vhcFgOh0P79+9XeXm5brzxRj355JN6+eWXuToSg1jOuc/n09atW/Xwww+rq6tLbW1t6u3tVV1dXSJGTVuJfi2dtf9JtWDBArlcrqhaHhgYiCq2UQUFBeOuz8jI0Pz582ds1lQSz3kf1dzcrM2bN+v111/XDTfcMJNjppRYz3kwGNThw4fV3d2t++67T9K5F0ljjDIyMvTee+/p+uuvT8jsySyef+ter1eLFi2K+Ej0FStWyBijEydOaNmyZTM6c7KL55w3NjZq9erVeuihhyRJV111lebNm6c1a9bo0Ucf5Yr3DLDxWjprr4xkZmaqrKxM7e3tEcfb29tVWVk57nMqKiqi1r/33ntauXKl5syZM2OzppJ4zrt07orIHXfcoVdffZV7uTGK9Zzn5OToww8/1NGjR8cedXV1uvzyy3X06FGtWrUqUaMntXj+ra9evVonT57UV199NXbsk08+kdPp1OLFi2d03lQQzzn/5ptv5HRGvlS5XC5J//+/1jG9rLyWztivxk6D0beAvfjii8bn85lt27aZefPmmX//+9/GGGN27NhhbrvttrH1o29HeuCBB4zP5zMvvvgib+2NQ6zn/dVXXzUZGRnmmWeeMX6/f+zx5Zdf2voRkk6s5/x/8W6a+MR63oPBoFm8eLH52c9+Zj766CPz/vvvm2XLlpm77rrL1o+QdGI95y+99JLJyMgwe/bsMZ999pk5cOCAWblypSkvL7f1IySdYDBouru7TXd3t5FknnzySdPd3T32durZ8Fo6q2PEGGOeeeYZs2TJEpOZmWlKS0vN+++/P/a922+/3Vx33XUR6//2t7+ZH/7whyYzM9NceumlpqmpKcETp4ZYzvt1111nJEU9br/99sQPnsRi/bf+XcRI/GI97x9//LG54YYbzNy5c83ixYvN9u3bzTfffJPgqZNbrOf86aefNsXFxWbu3LnG6/Wan//85+bEiRMJnjp5/fWvf530/6Nnw2upwxiucwEAAHtm7e+MAACA9ECMAAAAq4gRAABgFTECAACsIkYAAIBVxAgAALCKGAEAAFYRIwAAwCpiBAAAWEWMAAAAq4gRAABgFTECAACs+n8+Ow+kNMUfdwAAAABJRU5ErkJggg==", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiMAAAGdCAYAAADAAnMpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAhk0lEQVR4nO3de0zd9R3/8dcBCqd2cAytwGmLlXbtLBJrgVCha8ycxVaD67KlGFerri5SdbV2ul9ZlyKdCdFN4xW8tRrT2hE79ScJQ0mMlV42VtouVkx0lo3WHiRAPOAFauHz+6OBX48HlHPKOR/OOc9Hcv44Hz5fzvvkU8559Xt5fx3GGCMAAABL4mwXAAAAYhthBAAAWEUYAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABWEUYAAIBVCbYLGI+hoSGdOnVKycnJcjgctssBAADjYIxRX1+fZs6cqbi4sfd/REQYOXXqlDIzM22XAQAAgnDixAnNnj17zJ9HRBhJTk6WdPbNpKSkWK4GAACMR29vrzIzM0e+x8cSEWFk+NBMSkoKYQQAgAjzfadYcAIrAACwKuAw8t5776mkpEQzZ86Uw+HQG2+88b3b7N27V3l5eXI6nZo7d66eeeaZYGoFAABRKOAw8uWXX2rRokV66qmnxjW/ra1N1113nZYtW6YjR47oD3/4gzZs2KC//e1vARcLAACiT8DnjKxcuVIrV64c9/xnnnlGF198sR577DFJ0sKFC3Xo0CH95S9/0S9+8YtAXx4AAESZkJ8zcvDgQRUXF/uMXXvttTp06JC++eabUbcZGBhQb2+vzwMAAESnkIeRjo4Opaen+4ylp6frzJkz6urqGnWbqqoquVyukQc9RgAAiF5huZrm25f0GGNGHR9WXl4ur9c78jhx4kTIawQAAHaEvM9IRkaGOjo6fMY6OzuVkJCg6dOnj7pNUlKSkpKSQl0aAACYBEIeRgoLC1VXV+cz9vbbbys/P19TpkwJ9cuPaXDIqLmtR519/UpLdqogK1Xxcdz3BgCAcAs4jHzxxRf6z3/+M/K8ra1NR48eVWpqqi6++GKVl5fr008/1csvvyxJKisr01NPPaVNmzbpN7/5jQ4ePKjt27dr9+7dE/cuAtRwzKPKulZ5vP0jY26XUxUl2VqR47ZWFwAAsSjgc0YOHTqkxYsXa/HixZKkTZs2afHixdq6daskyePxqL29fWR+VlaW6uvr9e677+qKK67Qn/70Jz3xxBPWLuttOObR+p2HfYKIJHV4+7V+52E1HPNYqQsAgFjlMMNnk05ivb29crlc8nq953VvmsEhox8/9I5fEBnmkJThcmrf/7maQzYAAJyn8X5/x9S9aZrbesYMIpJkJHm8/Wpu6wlfUQAAxLiYCiOdfWMHkWDmAQCA8xdTYSQt2Tmh8wAAwPmLqTBSkJUqt8upsc4GcejsVTUFWanhLAsAgJgWU2EkPs6hipJsSfILJMPPK0qyOXkViEKDQ0YHP+nW/z36qQ5+0q3BoUl/7j4QM0Le9GyyWZHjVs2aXL8+Ixn0GQGiFr2FgMktpi7tPRcdWIHYMNxb6NsfdMN/7TVrcgkkQIiM9/s75vaMDIuPc6hw3uj3xgEQHQaHjCrrWv2CiHT2Un6HpMq6Vi3PzuA/I4BFMXXOCIDYQm8hIDIQRgBELXoLAZGBMAIgatFbCIgMhBEAUYveQkBkIIwAiFr0FgIiA2EEQFQb7i2U4fI9FJPhcnJZLzBJxOylvQBix4oct5ZnZ9BbCJikCCMAYgK9hYDJi8M0AADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwKsF2AUA0Ghwyam7rUWdfv9KSnSrISlV8nMN2WQAwKRFGgAnWcMyjyrpWebz9I2Nul1MVJdlakeO2WBkATE4cpgEmUMMxj9bvPOwTRCSpw9uv9TsPq+GYx1JlADB5EUaACTI4ZFRZ1yozys+GxyrrWjU4NNoMAIhdhBFggjS39fjtETmXkeTx9qu5rSd8RQFABCCMABOks2/sIBLMPACIFYQRYIKkJTsndB4AxArCCDBBCrJS5XY5NdYFvA6dvaqmICs1nGUBwKRHGAEmSHycQxUl2ZLkF0iGn1eUZNNvBAC+hTACTKAVOW7VrMlVhsv3UEyGy6maNbn0GQGAUdD0DJhgK3LcWp6dQQdWABgnwggQAvFxDhXOm267DACICBymAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFVBhZHq6mplZWXJ6XQqLy9PTU1N3zl/165dWrRokS644AK53W7ddttt6u7uDqpgAAAQXQIOI7W1tdq4caO2bNmiI0eOaNmyZVq5cqXa29tHnb9v3z6tXbtW69at0wcffKBXX31V//rXv3T77befd/EAACDyBRxGHn30Ua1bt0633367Fi5cqMcee0yZmZmqqakZdf4//vEPXXLJJdqwYYOysrL04x//WHfccYcOHTp03sUDAIDIF1AYOX36tFpaWlRcXOwzXlxcrAMHDoy6TVFRkU6ePKn6+noZY/TZZ59pz549uv7668d8nYGBAfX29vo8AABAdAoojHR1dWlwcFDp6ek+4+np6ero6Bh1m6KiIu3atUulpaVKTExURkaGLrzwQj355JNjvk5VVZVcLtfIIzMzM5AyAQBABAnqBFaHw+Hz3BjjNzastbVVGzZs0NatW9XS0qKGhga1tbWprKxszN9fXl4ur9c78jhx4kQwZQIAgAiQEMjkGTNmKD4+3m8vSGdnp9/ekmFVVVVaunSp7r//fknS5ZdfrmnTpmnZsmV68MEH5Xa7/bZJSkpSUlJSIKUBAIAIFdCekcTEROXl5amxsdFnvLGxUUVFRaNu89VXXykuzvdl4uPjJZ3dowIAAGJbwIdpNm3apBdeeEE7duzQhx9+qHvvvVft7e0jh13Ky8u1du3akfklJSV67bXXVFNTo+PHj2v//v3asGGDCgoKNHPmzIl7JwAAICIFdJhGkkpLS9Xd3a1t27bJ4/EoJydH9fX1mjNnjiTJ4/H49By59dZb1dfXp6eeekq/+93vdOGFF+rqq6/WQw89NHHvAgAARCyHiYBjJb29vXK5XPJ6vUpJSbFdDgAAGIfxfn9zbxoAAGAVYQQAAFhFGAEAAFYRRgAAgFWEEQAAYFXAl/YCAIDoMDhk1NzWo86+fqUlO1WQlar4uNFv7xJKhBEAAGJQwzGPKuta5fH2j4y5XU5VlGRrRY7/rVpCicM0AADEmIZjHq3fedgniEhSh7df63ceVsMxT1jrIYwAABBDBoeMKutaNVrH0+GxyrpWDQ6FrycqYQQAgBjS3Nbjt0fkXEaSx9uv5raesNVEGAEAIIZ09o0dRIKZNxEIIwAAxJC0ZOeEzpsIhBEAAGJIQVaq3C6nxrqA16GzV9UUZKWGrSbCCAAAMSQ+zqGKkmxJ8gskw88rSrLD2m+EMAIAQIxZkeNWzZpcZbh8D8VkuJyqWZMb9j4jND0DACAGrchxa3l2Bh1YAQCAPfFxDhXOm267DA7TAAAAuwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsSbBcAAOdjcMioua1HnX39Skt2qiArVfFxDttlAQgAYQRAxGo45lFlXas83v6RMbfLqYqSbK3IcVusDEAgOEwDICI1HPNo/c7DPkFEkjq8/Vq/87AajnksVQYgUIQRABFncMiosq5VZpSfDY9V1rVqcGi0GQAmG8IIgIjT3Nbjt0fkXEaSx9uv5rae8BUFIGiEEQARp7Nv7CASzDwAdhFGAESctGTnhM4DYBdhBEDEKchKldvl1FgX8Dp09qqagqzUcJYFIEiEEQARJz7OoYqSbEnyCyTDzytKsuk3AkQIwgiAiLQix62aNbnKcPkeislwOVWzJpc+I0AEoekZgIi1Iset5dkZdGAFIhxhBEBEi49zqHDedNtlADgPHKYBAABWBRVGqqurlZWVJafTqby8PDU1NX3n/IGBAW3ZskVz5sxRUlKS5s2bpx07dgRVMAAAiC4BH6apra3Vxo0bVV1draVLl+rZZ5/VypUr1draqosvvnjUbVavXq3PPvtM27dv1w9/+EN1dnbqzJkz5108AACIfA5jTEA3b1iyZIlyc3NVU1MzMrZw4UKtWrVKVVVVfvMbGhp044036vjx40pNDe6a/97eXrlcLnm9XqWkpAT1OwAAQHiN9/s7oMM0p0+fVktLi4qLi33Gi4uLdeDAgVG3efPNN5Wfn6+HH35Ys2bN0oIFC3Tffffp66+/HvN1BgYG1Nvb6/MAAADRKaDDNF1dXRocHFR6errPeHp6ujo6Okbd5vjx49q3b5+cTqdef/11dXV16c4771RPT8+Y541UVVWpsrIykNIAAECECuoEVofD9xp+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39bVV18dltqjVTB/I263W7NmzfK51frChQtljNHJkyc1f/78kNYczYJZj6qqKi1dulT333+/JOnyyy/XtGnTtGzZMj344IPsXQ+BSb9nJDExUXl5eWpsbPQZb2xsVFFR0ajbFBYW+s1/++23lZ+frylTpoSs1lgQzHpIZ/eI3HrrrXrllVc47jqBAl2PlJQUvf/++zp69OjIo6ysTD/60Y909OhRLVmyJFylR61g/kaWLl2qU6dO6YsvvhgZ++ijjxQXF6fZs2eHtN5oF8x6fPXVV4qL8/16jI+Pl/T/97Jjgtk6czYQw5dlbd++3bS2tpqNGzeaadOmmf/+97/GGGM2b95sbr755pH5w5f23nvvvaa1tdVs376dS3snUKDr8corr5iEhATz9NNPG4/HM/L4/PPPbb2FqBLoenwbV9NMvEDXpK+vz8yePdv88pe/NB988IHZu3evmT9/vrn99tttvYWoEuh6vPjiiyYhIcFUV1ebTz75xOzbt8/k5+ebgoICW28h6kVEGDHGmKefftrMmTPHJCYmmtzcXLN3796Rn91yyy3mqquu8pn/7rvvmsWLF5vExERzySWXmJqamjBXHN0CWY+rrrrKSPJ73HLLLeEvPEoF+vdxLsJIaAS6Jh9++KG55pprzNSpU83s2bPNpk2bzFdffRXmqqNXoOvxxBNPmOzsbDN16lTjdrvNr371K3Py5MkwVx07HMawzwkAANgz6c8ZAQAA0Y0wAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwKr/Bwq4+PKt2NvcAAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -719,7 +756,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "id": "25f0495a-e85f-43b7-8a70-a2c9cbd51ebb", "metadata": {}, "outputs": [ @@ -729,7 +766,7 @@ "(False, False)" ] }, - "execution_count": 24, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -740,7 +777,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "id": "449ce797-be62-4211-b483-c717a3d70583", "metadata": {}, "outputs": [ @@ -750,7 +787,7 @@ "(True, False)" ] }, - "execution_count": 25, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -762,13 +799,13 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "id": "7008b0fc-3644-401c-b49f-9c40f9d89ac4", "metadata": {}, "outputs": [ { "data": { - "image/png": "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nk0DAABSIiX7jAAAACw2wggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsCrXdgEAsBgiM0Z9gxManZpWcYFPNeVF8uZ4bJcFYAEIIwAyXvdASC2dQYXC03PnAn6fmhsqtKUyYLEyAAvBMA2AjNY9ENLOI2digogkjYSntfPIGXUPhCxVBmChCCMAMlZkxqilMyiT4GfRcy2dQUVmErUAsFQQRgBkrL7Bibg7IlczkkLhafUNTqSvKACOEUYAZKzRqfmDyNWO//f/5e4IsIQRRgBkrOIC34Laffdn5/WZJ/+d+SPAEkUYAZCxasqLFPD7tJAFvExoBZYuwgiAjOXN8ai5oUKSrhtImNAKLF2EEQAZbUtlQO3bq1Tqv/6QDRNagaWJMAIg422pDOjYN+7UI//nowtqv9CJrwDSgzACICt4czza+NGVC2q70ImvANKDMAIga1xvQqtHs9vE15QXpbMsANdBGAGQNa41oTX6urmhggfoAUsMYQRAVplvQmup36f27VU8OA9YgnhqL4Css6UyoM0VpeobnNDo1LSKC2aHZrgjAixNhBEAWcmb41Ht2hW2ywCwAAzTAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsSiqMtLW1qby8XD6fT9XV1ert7V3Q+44fP67c3FzdcccdyfxaAACQhRyHkY6ODu3evVsHDhxQf3+/Nm3apK1bt2poaOia7wuHw9qxY4f+/M//POliAQBA9vEYY4yTN2zYsEFVVVVqb2+fO7du3Tpt27ZNra2t877vi1/8om699VZ5vV79+Mc/1tmzZxf8OycnJ+X3+xUOh1VYWOikXAAAYMlCr9+O7oxcvnxZp0+fVn19fcz5+vp6nThxYt73vfDCCzp//ryam5sX9HsuXbqkycnJmAMAAGQnR2FkbGxMkUhEJSUlMedL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6/Tp0+ru7tbg4KCamprSUaprpes6mhFfo1auXCmv1xuXmEdHR+NSW1RpaWnC9rm5uVqxYkXKas0myfR7VEdHhx566CG9/PLLuuuuu1JZZlZx2udTU1M6deqU+vv79cgjj0iavVAaY5Sbm6s33nhDd955Z1pqz1TJ/DsPBAK6+eab5ff7586tW7dOxhhdvHhRt956a0prznTJ9Hlra6s2btyoxx57TJL0yU9+UsuXL9emTZv0xBNPcLc7BdJ5Hc2IOyN5eXmqrq5WT09PzPmenh7V1dUlfE9tbW1c+zfeeEPr16/XsmXLUlZrNkmm36XZOyIPPPCAXnzxRcZzHXLa54WFhfrlL3+ps2fPzh1NTU362Mc+prNnz2rDhg3pKj1jJfPvfOPGjXrnnXf0+9//fu7c22+/rZycHK1evTql9WaDZPr8vffeU05O7CXL6/VK+v/f1rG40nodXfQpsSkSXQZ26NAhEwwGze7du83y5cvNb37zG2OMMfv27TP333//XPvokqQ9e/aYYDBoDh06xNLeJDjt9xdffNHk5uaaZ5991oRCobnjd7/7na0/IeM47fP3YzWNc077fGpqyqxevdr81V/9lfnVr35l3nzzTXPrrbeahx9+2NafkHGc9vkLL7xgcnNzTVtbmzl//rw5duyYWb9+vampqbH1J2Scqakp09/fb/r7+40k8/TTT5v+/v655dQ2r6MZE0aMMebZZ581a9asMXl5eaaqqsq8+eabcz/78pe/bD772c/GtP+P//gP86lPfcrk5eWZj3zkI6a9vT3NFWcHJ/3+2c9+1kiKO7785S+nv/AM5vTf+tUII8lx2ufnzp0zd911l7nhhhvM6tWrzd69e817772X5qozm9M+f+aZZ0xFRYW54YYbTCAQMF/60pfMxYsX01x15vrZz352zf8/27yOeozh/hYAALAnI+aMAACA7EUYAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFWEEQAAYNX/A3IrpT43izeiAAAAAElFTkSuQmCC", + "image/png": "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dOTIEe3du1cHDhyI7zsBAMxZZoZHFauij04DieY4jFRXV2t4eFg7d+5UMBhUcXGx2tratGLFCklSMBiM2HMkEAiora1Njz32mJ577jktW7ZMzzzzjL72ta/F710AAADX8pip1aRJbHR0VD6fT6FQiPUjAAC4xFyv3zybBgAAWEUYAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABWEUYAAIBVjjc9s2FqK5TR0VHLlQAAgLmaum5faUszV4SRsbExSVJBQYHlSgAAgFNjY2Py+Xyzft8VO7BOTk7q3LlzWrRokTye6A9yGh0dVUFBgU6fPs0urQnGubeHc28P594ezr0dsZx3Y4zGxsa0bNkyZWTMvjLEFSMjGRkZWr58+Zz65uTk8MtpCefeHs69PZx7ezj3djg975cbEZnCAlYAAGAVYQQAAFiVMmEkOztb9fX1ys7Otl1K2uHc28O5t4dzbw/n3o75PO+uWMAKAABSV8qMjAAAAHcijAAAAKsIIwAAwCrCCAAAsMpVYaSpqUmBQEBer1elpaXq7Oy8bP+DBw+qtLRUXq9XK1eu1J49exJUaepxcu5ff/11rVu3Tp/5zGeUk5OjiooK/frXv05gtanD6e/8lHfeeUdZWVm65ZZb5rfAFOb03E9MTGjHjh1asWKFsrOztWrVKr3wwgsJqja1OD33+/fv180336xrr71Wfr9fDz/8sIaHhxNUbep4++23VVVVpWXLlsnj8egXv/jFFY+J23XWuMS//Mu/mAULFpif/vSnpre31zz66KPmuuuuM//3f/8Xtf+pU6fMtddeax599FHT29trfvrTn5oFCxaYV199NcGVu5/Tc//oo4+ap59+2nR1dZn33nvP1NXVmQULFphjx44luHJ3c3rep/zhD38wK1euNJWVlebmm29OTLEpJpZzf88995jbbrvNdHR0mL6+PvOf//mf5p133klg1anB6bnv7Ow0GRkZ5kc/+pE5deqU6ezsNJ///OfNvffem+DK3a+trc3s2LHDvPbaa0aSeeONNy7bP57XWdeEkfLyclNTUxPR9rnPfc488cQTUfv//d//vfnc5z4X0fbII4+YL37xi/NWY6pyeu6jKSoqMg0NDfEuLaXFet6rq6vNP/7jP5r6+nrCSIycnvt/+7d/Mz6fzwwPDyeivJTm9Nz/8z//s1m5cmVE2zPPPGOWL18+bzWmg7mEkXheZ10xTXP+/Hl1d3ersrIyor2yslKHDx+OesyRI0dm9L/zzjt19OhRffzxx/NWa6qJ5dx/2uTkpMbGxpSbmzsfJaakWM/7iy++qJMnT6q+vn6+S0xZsZz7X/3qVyorK9P3v/99XX/99brpppv0ne98R3/84x8TUXLKiOXcr169WmfOnFFbW5uMMXr//ff16quv6u67705EyWktntdZVzwob2hoSOFwWHl5eRHteXl5GhgYiHrMwMBA1P4XLlzQ0NCQ/H7/vNWbSmI595/2wx/+UB9++KHuu++++SgxJcVy3n//+9/riSeeUGdnp7KyXPFPOynFcu5PnTqlQ4cOyev16o033tDQ0JC++c1vamRkhHUjDsRy7levXq39+/erurpa4+PjunDhgu655x79+Mc/TkTJaS2e11lXjIxM8Xg8EV8bY2a0Xal/tHZcmdNzP+XAgQP63ve+p5aWFi1dunS+yktZcz3v4XBY999/vxoaGnTTTTclqryU5uR3fnJyUh6PR/v371d5ebnuuusu7dq1Sy+99BKjIzFwcu57e3u1bds2Pfnkk+ru7lZ7e7v6+vpUU1OTiFLTXryus674+LRkyRJlZmbOSMaDg4MzUtmU/Pz8qP2zsrK0ePHieas11cRy7qe0tLRo8+bN+vnPf6477rhjPstMOU7P+9jYmI4ePaqenh59+9vflnTxAmmMUVZWlt58803dfvvtCand7WL5nff7/br++usjHpVeWFgoY4zOnDmjG2+8cV5rThWxnPvGxkatWbNGjz/+uCTpC1/4gq677jqtXbtWTz31FKPg8yie11lXjIwsXLhQpaWl6ujoiGjv6OjQ6tWrox5TUVExo/+bb76psrIyLViwYN5qTTWxnHvp4ojIQw89pFdeeYW52xg4Pe85OTn67W9/q+PHj0+/ampq9Od//uc6fvy4brvttkSV7nqx/M6vWbNG586d0wcffDDd9t577ykjI0PLly+f13pTSSzn/qOPPlJGRuSlLDMzU9Inn9IxP+J6nXW85NWSqdu99u7da3p7e8327dvNddddZ/73f//XGGPME088YR544IHp/lO3HD322GOmt7fX7N27l1t7Y+T03L/yyismKyvLPPfccyYYDE6//vCHP9h6C67k9Lx/GnfTxM7puR8bGzPLly83f/VXf2V+97vfmYMHD5obb7zRbNmyxdZbcC2n5/7FF180WVlZpqmpyZw8edIcOnTIlJWVmfLycltvwbXGxsZMT0+P6enpMZLMrl27TE9Pz/Rt1fN5nXVNGDHGmOeee86sWLHCLFy40JSUlJiDBw9Of+/BBx80X/7ylyP6/+Y3vzG33nqrWbhwofnsZz9rmpubE1xx6nBy7r/85S8bSTNeDz74YOILdzmnv/OXIoxcHafn/t133zV33HGHueaaa8zy5ctNbW2t+eijjxJcdWpweu6feeYZU1RUZK655hrj9/vN3/zN35gzZ84kuGr3+/d///fL/t89n9dZjzGMYwEAAHtcsWYEAACkLsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAq/4foA0IgyVTI5cAAAAASUVORK5CYII=", "text/plain": [ "
" ] @@ -806,7 +843,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 28, "id": "1cd000bd-9b24-4c39-9cac-70a3291d0660", "metadata": {}, "outputs": [], @@ -831,7 +868,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "id": "7964df3c-55af-4c25-afc5-9e07accb606a", "metadata": {}, "outputs": [ @@ -872,7 +909,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 30, "id": "809178a5-2e6b-471d-89ef-0797db47c5ad", "metadata": {}, "outputs": [ @@ -913,22 +950,15 @@ "id": "18ba07ca-f1f9-4f05-98db-d5612f9acbb6", "metadata": {}, "source": [ - "Unlike function nodes, workflow input has no intrinsic order. We can still update it by calling the workflow, but we _need_ to use keyword and not positional arguments:" + "Unlike function nodes, workflow input has no intrinsic order. We can still update it by calling the workflow, but we _need_ to use keyword and not positional arguments. Runs of the workflow (which typically happen when the workflow is updated or called) return a dot-accessible dictionary based on the output channels:" ] }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "id": "52c48d19-10a2-4c48-ae81-eceea4129a60", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "7\n" - ] - }, { "name": "stderr", "output_type": "stream", @@ -938,11 +968,42 @@ "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label x to the io key b_x\n", " warn(\n" ] + }, + { + "data": { + "text/plain": [ + "{'sum_sum_': 7}" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "wf(a_x=2, b_x=3)\n", - "print(wf.outputs.sum_sum_.value)" + "out = wf(a_x=2, b_x=3)\n", + "out" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "bb35ba3e-602d-4c9c-b046-32da9401dd1c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "7" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.sum_sum_" ] }, { @@ -969,7 +1030,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 33, "id": "ae500d5e-e55b-432c-8b5f-d5892193cdf5", "metadata": {}, "outputs": [ @@ -977,9 +1038,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:193: UserWarning: Reassigning the node bulk_structure to the label structure when adding it to the parent with_prebuilt.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:224: UserWarning: Reassigning the node bulk_structure to the label structure when adding it to the parent with_prebuilt.\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:193: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent with_prebuilt.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:224: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent with_prebuilt.\n", " warn(\n" ] }, @@ -994,9 +1055,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:193: UserWarning: Reassigning the node calc_md to the label calc when adding it to the parent with_prebuilt.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:224: UserWarning: Reassigning the node calc_md to the label calc when adding it to the parent with_prebuilt.\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:193: UserWarning: Reassigning the node scatter to the label plot when adding it to the parent with_prebuilt.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:224: UserWarning: Reassigning the node scatter to the label plot when adding it to the parent with_prebuilt.\n", " warn(\n" ] },