From c0e4c907cdebd816de3c4091f737f049ef0a1fdf Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Thu, 23 Feb 2023 12:39:22 +0100 Subject: [PATCH 001/756] First stab at replacement for base classes --- pyiron_contrib/tinybase/__init__.py | 170 ++++++++++++++++++++++++++++ pyiron_contrib/tinybase/ase.py | 47 ++++++++ pyiron_contrib/tinybase/murn.py | 56 +++++++++ 3 files changed, 273 insertions(+) create mode 100644 pyiron_contrib/tinybase/__init__.py create mode 100644 pyiron_contrib/tinybase/ase.py create mode 100644 pyiron_contrib/tinybase/murn.py diff --git a/pyiron_contrib/tinybase/__init__.py b/pyiron_contrib/tinybase/__init__.py new file mode 100644 index 000000000..7eac3e03b --- /dev/null +++ b/pyiron_contrib/tinybase/__init__.py @@ -0,0 +1,170 @@ +import abc +import enum +from typing import Union + +from pyiron_base.interfaces.object import HasStorage + +class RunMachine: + + class Code(enum.Enum): + INIT = 'init' + RUNNING = 'running' + FINISHED = 'finished' + + def __init__(self, initial_state): + self._state = RunMachine.Code(initial_state) + self._callbacks = {} + self._data = {} # state variables associated with each state + + def on(self, state: Union[str, Code], callback): + if isinstance(state, str): + state = RunMachine.Code(state) + self._callbacks[state] = callback + + def goto(self, state: Union[str, Code], **kwargs): + if isinstance(state, str): + state = RunMachine.Code(state) + self._state = state + self._data = {} + self._data.update(kwargs) + + def step(self, state: Union[str, Code, None] = None, **kwargs): + if state is not None: + self.goto(state, **kwargs) + self._callbacks.get(self._state, lambda: pass)() + +class Executor(abc.ABC): + + def __init__(self): + self._run_machine = RunMachine("init") + + def run(self): + self._run_machine.step() + +class SingleExecutor(Executor, abc.ABC): + + def __init__(self, node): + super().__init__() + self._node = node + + @property + def node(self): + return self._node + +class ForegroundExecutor(SingleExecutor): + + def __init__(self, node): + super().__init__(node=node) + self._run_machine.on("init", self.run_init) + + def run_init(self): + self._run_machine.goto("running") + + try: + ret = self.node.execute() + except Exception as e: + ret = ReturnStatus("aborted", msg=e) + + self._run_machine.goto("finished", status=ret) + +from threading import Thread + +class BackgroundExecutor(SingleExecutor): + + def __init__(self, node): + super().__init__(node=node) + self._run_machine = RunMachine("init") + self._run_machine.on("init", self.run_init) + self._run_machine.on("running", self.run_running) + self._thread = None + + def run_init(self): + self._run_machine.goto("running") + + node = self.node + class NodeThread(Thread): + def run(self): + try: + self.ret = node.execute() + except Exception as e: + self.ret = ReturnStatus("aborted", msg=e) + + + self._thread = NodeThread() + self._thread.start() + + def run_running(self): + self._thread.join(timeout=0) + if not self._thread.is_alive(): + self._run_machine.goto("finished", status=self._thread.ret) + else: + print("Node is still running!") + +class AbstractInput(HasStorage, abc.ABC): + pass + +class StructureInput(AbstractInput): + def __init__(self): + super().__init__() + self.storage.structure = None + + structure = make_storage_mapping('structure') + +class AbstractOutput(HasStorage, abc.ABC): + pass + +class ReturnStatus: + + class Code(enum.Enum): + DONE = "done" + ABORTED = "aborted" + WARNING = "warning" + NOT_CONVERGED = "not_converged" + + def __init__(self, code, msg=None): + self.code = code if not isinstance(code, str) else ReturnStatus.Code(code) + self.msg = msg + + def __repr__(self): + return f"ReturnStatus({self.code}, {self.msg})" + def __str__(self): + return f"{self.code}({self.msg})" + +class AbstractNode(abc.ABC): + + _executors = { + 'foreground': ForegroundExecutor, + 'background': BackgroundExecutor + } + + def __init__(self): + self._input, self._output = None, None + + @abc.abstractmethod + def _get_input(self) -> AbstractInput: + pass + + @property + def input(self) -> AbstractInput: + if self._input is None: + self._input = self._get_input() + return self._input + + @abc.abstractmethod + def _get_output(self) -> AbstractOutput: + pass + + @property + def output(self) -> AbstractOutput: + if self._output is None: + self._output = self._get_output() + return self._output + + @abc.abstractmethod + def execute(self) -> ReturnStatus: + pass + + def run(self, how='foreground'): + exe = self._executors[how](node=self) + exe.run() + return exe diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py new file mode 100644 index 000000000..0a4ddd877 --- /dev/null +++ b/pyiron_contrib/tinybase/ase.py @@ -0,0 +1,47 @@ +from . import AbstractInput +from . import StructureInput +from . import AbstractOutput +from . import AbstractNode +from . import ReturnStatus + +import numpy as np +import matplotlib.pyplot as plt + +def make_storage_mapping(name): + def fget(self): + return self.storage[name] + + def fset(self, value): + self.storage[name] = value + + return property(fget=fget, fset=fset) + + +class AseInput(StructureInput): + def __init__(self): + super().__init__() + self.storage.calculator = None + + calculator = make_storage_mapping('calculator') + +class EnergyOutput(AbstractOutput): + def __init__(self): + super().__init__() + self.storage.energy_pot = None + + energy_pot = make_storage_mapping('energy_pot') + +class AseNode(AbstractNode): + + def _get_input(self): + return AseInput() + + def _get_output(self): + return EnergyOutput() + + def execute(self): + structure = self.input.structure + structure.calc = self.input.calculator + self.output.energy_pot = structure.get_potential_energy() + return ReturnStatus("done") + diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py new file mode 100644 index 000000000..18156bf01 --- /dev/null +++ b/pyiron_contrib/tinybase/murn.py @@ -0,0 +1,56 @@ +from . import AbstractInput +from . import StructureInput +from . import AbstractOutput +from . import AbstractNode +from . import ReturnStatus + +import numpy as np +import matplotlib.pyplot as plt + +class MurnaghanInput(StructureInput): + def __init__(self): + super().__init__() + self.storage.strains = None + self.storage.node = None + + strains = make_storage_mapping('strains') + node = make_storage_mapping('node') + + def set_strain_range(self, range, steps): + self.strains = (1 + np.linspace(-range, range, steps))**(1/3) + +class MurnaghanOutput(AbstractOutput): + def __init__(self): + super().__init__() + self.storage.volumes = None + self.storage.energies = None + + volumes = make_storage_mapping('volumes') + energies = make_storage_mapping('energies') + + def plot(self): + plt.plot(self.volumes, self.energies) + +class MurnaghanNode(AbstractNode): + + def _get_input(self): + return MurnaghanInput() + + def _get_output(self): + return MurnaghanOutput() + + def execute(self): + cell = self.input.structure.get_cell() + node = self.input.node + energy = [] + volume = [] + for s in self.input.strains: + structure = self.input.structure.copy() + structure.set_cell(cell * s, scale_atoms=True) + node.input.structure = structure + node.execute() + energy.append(node.output.energy_pot) + volume.append(structure.get_volume()/len(structure)) + self.output.energies = np.array(energy) + self.output.volumes = np.array(volume) + return ReturnStatus('done') From 78c2c3a54ed9b5ba8e96e76c4b6f865d84f71259 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Wed, 1 Mar 2023 15:58:34 +0100 Subject: [PATCH 002/756] Move Executor to separate module --- pyiron_contrib/tinybase/__init__.py | 118 ++++++---------------------- pyiron_contrib/tinybase/ase.py | 14 +--- pyiron_contrib/tinybase/executor.py | 117 +++++++++++++++++++++++++++ pyiron_contrib/tinybase/murn.py | 18 +++-- 4 files changed, 156 insertions(+), 111 deletions(-) create mode 100644 pyiron_contrib/tinybase/executor.py diff --git a/pyiron_contrib/tinybase/__init__.py b/pyiron_contrib/tinybase/__init__.py index 7eac3e03b..5860ec9fe 100644 --- a/pyiron_contrib/tinybase/__init__.py +++ b/pyiron_contrib/tinybase/__init__.py @@ -1,104 +1,22 @@ import abc import enum -from typing import Union +from typing import Optional from pyiron_base.interfaces.object import HasStorage -class RunMachine: - - class Code(enum.Enum): - INIT = 'init' - RUNNING = 'running' - FINISHED = 'finished' - - def __init__(self, initial_state): - self._state = RunMachine.Code(initial_state) - self._callbacks = {} - self._data = {} # state variables associated with each state - - def on(self, state: Union[str, Code], callback): - if isinstance(state, str): - state = RunMachine.Code(state) - self._callbacks[state] = callback - - def goto(self, state: Union[str, Code], **kwargs): - if isinstance(state, str): - state = RunMachine.Code(state) - self._state = state - self._data = {} - self._data.update(kwargs) - - def step(self, state: Union[str, Code, None] = None, **kwargs): - if state is not None: - self.goto(state, **kwargs) - self._callbacks.get(self._state, lambda: pass)() - -class Executor(abc.ABC): - - def __init__(self): - self._run_machine = RunMachine("init") - - def run(self): - self._run_machine.step() - -class SingleExecutor(Executor, abc.ABC): - - def __init__(self, node): - super().__init__() - self._node = node - - @property - def node(self): - return self._node - -class ForegroundExecutor(SingleExecutor): - - def __init__(self, node): - super().__init__(node=node) - self._run_machine.on("init", self.run_init) - - def run_init(self): - self._run_machine.goto("running") - - try: - ret = self.node.execute() - except Exception as e: - ret = ReturnStatus("aborted", msg=e) - - self._run_machine.goto("finished", status=ret) - -from threading import Thread - -class BackgroundExecutor(SingleExecutor): - - def __init__(self, node): - super().__init__(node=node) - self._run_machine = RunMachine("init") - self._run_machine.on("init", self.run_init) - self._run_machine.on("running", self.run_running) - self._thread = None - - def run_init(self): - self._run_machine.goto("running") - - node = self.node - class NodeThread(Thread): - def run(self): - try: - self.ret = node.execute() - except Exception as e: - self.ret = ReturnStatus("aborted", msg=e) +from .executor import ( + ForegroundExecutor, + BackgroundExecutor +) +def make_storage_mapping(name): + def fget(self): + return self.storage[name] - self._thread = NodeThread() - self._thread.start() + def fset(self, value): + self.storage[name] = value - def run_running(self): - self._thread.join(timeout=0) - if not self._thread.is_alive(): - self._run_machine.goto("finished", status=self._thread.ret) - else: - print("Node is still running!") + return property(fget=fget, fset=fset) class AbstractInput(HasStorage, abc.ABC): pass @@ -130,6 +48,9 @@ def __repr__(self): def __str__(self): return f"{self.code}({self.msg})" + def is_done(self): + return self.code == self.Code.DONE + class AbstractNode(abc.ABC): _executors = { @@ -161,9 +82,18 @@ def output(self) -> AbstractOutput: return self._output @abc.abstractmethod - def execute(self) -> ReturnStatus: + def _execute(self) -> Optional[ReturnStatus]: pass + def execute(self) -> ReturnStatus: + try: + ret = self._execute() + if ret is None: + ret = ReturnStatus("done") + except Exception as e: + ret = ReturnStatus("aborted", msg=e) + return ret + def run(self, how='foreground'): exe = self._executors[how](node=self) exe.run() diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index 0a4ddd877..7ba708f47 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -3,19 +3,11 @@ from . import AbstractOutput from . import AbstractNode from . import ReturnStatus +from . import make_storage_mapping import numpy as np import matplotlib.pyplot as plt -def make_storage_mapping(name): - def fget(self): - return self.storage[name] - - def fset(self, value): - self.storage[name] = value - - return property(fget=fget, fset=fset) - class AseInput(StructureInput): def __init__(self): @@ -39,9 +31,7 @@ def _get_input(self): def _get_output(self): return EnergyOutput() - def execute(self): + def _execute(self): structure = self.input.structure structure.calc = self.input.calculator self.output.energy_pot = structure.get_potential_energy() - return ReturnStatus("done") - diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py new file mode 100644 index 000000000..ae1622c33 --- /dev/null +++ b/pyiron_contrib/tinybase/executor.py @@ -0,0 +1,117 @@ +import abc +import enum +from typing import Union + +class RunMachine: + + class Code(enum.Enum): + INIT = 'init' + RUNNING = 'running' + FINISHED = 'finished' + + def __init__(self, initial_state): + self._state = RunMachine.Code(initial_state) + self._callbacks = {} + self._data = {} # state variables associated with each state + + def on(self, state: Union[str, Code], callback): + if isinstance(state, str): + state = RunMachine.Code(state) + self._callbacks[state] = callback + + def goto(self, state: Union[str, Code], **kwargs): + if isinstance(state, str): + state = RunMachine.Code(state) + self._state = state + self._data = {} + self._data.update(kwargs) + + def step(self, state: Union[str, Code, None] = None, **kwargs): + if state is not None: + self.goto(state, **kwargs) + self._callbacks.get(self._state, lambda: None)() + +class Executor(abc.ABC): + + def __init__(self): + self._run_machine = RunMachine("init") + + def run(self): + self._run_machine.step() + +class SingleExecutor(Executor, abc.ABC): + + def __init__(self, node): + super().__init__() + self._node = node + + @property + def node(self): + return self._node + +class ForegroundExecutor(SingleExecutor): + + def __init__(self, node): + super().__init__(node=node) + self._run_machine.on("init", self.run_init) + + def run_init(self): + self._run_machine.goto("running") + + ret = self.node.execute() + + self._run_machine.goto("finished", status=ret) + +from threading import Thread + +class BackgroundExecutor(SingleExecutor): + + def __init__(self, node): + super().__init__(node=node) + self._run_machine = RunMachine("init") + self._run_machine.on("init", self.run_init) + self._run_machine.on("running", self.run_running) + self._thread = None + + def run_init(self): + self._run_machine.goto("running") + + node = self.node + class NodeThread(Thread): + def run(self): + self.ret = node.execute() + + self._thread = NodeThread() + self._thread.start() + + def run_running(self): + self._thread.join(timeout=0) + if not self._thread.is_alive(): + self._run_machine.goto("finished", status=self._thread.ret) + else: + print("Node is still running!") + +class ListExecutor(Executor, abc.ABC): + + def __init__(self, nodes): + super().__init__() + self._nodes = nodes + + @property + def nodes(self): + return self._nodes + +class SerialListExecutor(ListExecutor): + + def __init__(self, nodes): + super().__init__(nodes=nodes) + self._run_machine.on("init", self.run_init) + + def run_init(self): + self._run_machine.goto("running") + + returns = [] + for node in self.nodes: + returns.append(node.execute()) + + self._run_machine.goto("finished", status=returns) diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index 18156bf01..4c59cfa9f 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -3,6 +3,7 @@ from . import AbstractOutput from . import AbstractNode from . import ReturnStatus +from . import make_storage_mapping import numpy as np import matplotlib.pyplot as plt @@ -39,18 +40,25 @@ def _get_input(self): def _get_output(self): return MurnaghanOutput() - def execute(self): + def _execute(self): cell = self.input.structure.get_cell() node = self.input.node energy = [] volume = [] + returns = [] for s in self.input.strains: structure = self.input.structure.copy() structure.set_cell(cell * s, scale_atoms=True) node.input.structure = structure - node.execute() - energy.append(node.output.energy_pot) - volume.append(structure.get_volume()/len(structure)) + ret = node.execute() + returns.append(ret) + if ret.is_done(): + energy.append(node.output.energy_pot) + volume.append(structure.get_volume()/len(structure)) self.output.energies = np.array(energy) self.output.volumes = np.array(volume) - return ReturnStatus('done') + errors = [ret for ret in returns if not ret.is_done()] + if len(errors) == 0: + return ReturnStatus('done') + else: + return ReturnStatus('aborted', msg=errors) From 357cd8706cdaa9892873291af4b4bc5a9b9c0ce9 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Wed, 1 Mar 2023 18:06:38 +0100 Subject: [PATCH 003/756] Add MD via ASE and compose ASE inputs --- pyiron_contrib/tinybase/ase.py | 87 +++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index 7ba708f47..c112487ac 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -8,14 +8,18 @@ import numpy as np import matplotlib.pyplot as plt +from pyiron_atomistics.atomistics.structure.has_structure import HasStructure -class AseInput(StructureInput): +class AseInput(AbstractInput): def __init__(self): super().__init__() self.storage.calculator = None calculator = make_storage_mapping('calculator') +class AseStaticInput(AseInput, StructureInput): + pass + class EnergyOutput(AbstractOutput): def __init__(self): super().__init__() @@ -23,7 +27,7 @@ def __init__(self): energy_pot = make_storage_mapping('energy_pot') -class AseNode(AbstractNode): +class AseStaticNode(AbstractNode): def _get_input(self): return AseInput() @@ -35,3 +39,82 @@ def _execute(self): structure = self.input.structure structure.calc = self.input.calculator self.output.energy_pot = structure.get_potential_energy() + + +class MDInput(StructureInput): + def __init__(self): + super().__init__() + self.storage.steps = None + self.storage.timestep = None + self.storage.temperature = None + self.storage.output_steps = None + + steps = make_storage_mapping('steps') + timestep = make_storage_mapping('timestep') + temperature = make_storage_mapping('temperature') + output_steps = make_storage_mapping('output_steps') + +class MDOutput(AbstractOutput, HasStructure): + def __init__(self): + super().__init__() + + self.storage.pot_energies = [] + self.storage.kin_energies = [] + self.storage.forces = [] + self.storage.structures = [] + + pot_energies = make_storage_mapping("pot_energies") + kin_energies = make_storage_mapping("kin_energies") + forces = make_storage_mapping("forces") + structures = make_storage_mapping("structures") + + def plot_energies(self): + plt.plot(self.pot_energies - np.min(self.pot_energies), label='pot') + plt.plot(self.kin_energies, label='kin') + plt.legend() + + def _number_of_structures(self): + return len(self.structures) + + def _get_structure(self, frame, wrap_atoms=True): + return self.structures[frame] + + +class AseMDInput(AseInput, MDInput): + pass + +from ase.md.langevin import Langevin +from ase.md.velocitydistribution import MaxwellBoltzmannDistribution +from ase import units + +class AseMDNode(AbstractNode): + + def _get_input(self): + return AseMDInput() + + def _get_output(self): + return MDOutput() + + def _execute(self): + structure = self.input.structure.copy() + structure.calc = self.input.calculator + + MaxwellBoltzmannDistribution(structure, temperature_K=self.input.temperature * 2) + + dyn = Langevin( + structure, + timestep=self.input.timestep * units.fs, + temperature_K=self.input.temperature, + friction=1e-3, + append_trajectory=True + ) + + def parse(): + self.output.structures.append(structure.copy()) + self.output.pot_energies.append(structure.get_potential_energy()) + self.output.kin_energies.append(structure.get_kinetic_energy()) + self.output.forces.append(structure.get_forces()) + + parse() + dyn.attach(parse, interval=self.input.steps // self.input.output_steps) + dyn.run(self.input.steps) From bde3c7d13c7b20680fa86545a53bb2ee3ccd80a0 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Thu, 2 Mar 2023 14:15:59 +0100 Subject: [PATCH 004/756] Prototype a ListNode and executor Not yet working fully. Somehow the executor has to callback into the node to collect output --- pyiron_contrib/tinybase/__init__.py | 49 ++++++++++++++++++++++++++++- pyiron_contrib/tinybase/executor.py | 33 +++++++++++++++++++ pyiron_contrib/tinybase/murn.py | 33 ++++++++++++++++--- 3 files changed, 110 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/tinybase/__init__.py b/pyiron_contrib/tinybase/__init__.py index 5860ec9fe..a3f00e6b3 100644 --- a/pyiron_contrib/tinybase/__init__.py +++ b/pyiron_contrib/tinybase/__init__.py @@ -6,7 +6,10 @@ from .executor import ( ForegroundExecutor, - BackgroundExecutor + BackgroundExecutor, + ListExecutor, + SerialListExecutor, + BackgroundListExecutor ) def make_storage_mapping(name): @@ -98,3 +101,47 @@ def run(self, how='foreground'): exe = self._executors[how](node=self) exe.run() return exe + +class ListNode(AbstractNode, abc.ABC): + + _executors = { + 'foreground': SerialListExecutor, + 'background': BackgroundListExecutor + } + + def __init__(self): + super().__init__() + self._nodes = None + + @abc.abstractmethod + def _create_nodes(self): + pass + + @property + def nodes(self): + if self._nodes is None: + self._nodes = self._create_nodes() + return self._nodes + + @abc.abstractmethod + def _extract_output(self, step, node, ret): + pass + + # If our own execute is called we act like a normal node, executing child nodes and then process their output + def _execute(self): + for i, node in enumerate(self.nodes): + ret = node.execute() + self._extract_output(i, node, ret) + + # If called via run by the user directly we can also dispatch to a list executor + def run(self, how='foreground'): + Exe = self._executors[how] + if issubclass(Exe, ListExecutor): + exe = Exe(self.nodes) + exe.run() + # for i, (node, ret) in enumerate(zip(exe.nodes, exe._run_machine._data['status'])): + # self._extract_output(i, node, ret) + else: + exe = Exe(self) + exe.run() + return exe diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index ae1622c33..b034b3a9f 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -14,6 +14,10 @@ def __init__(self, initial_state): self._callbacks = {} self._data = {} # state variables associated with each state + @property + def state(self): + return self._state + def on(self, state: Union[str, Code], callback): if isinstance(state, str): state = RunMachine.Code(state) @@ -115,3 +119,32 @@ def run_init(self): returns.append(node.execute()) self._run_machine.goto("finished", status=returns) + +class BackgroundListExecutor(ListExecutor): + + def __init__(self, nodes): + super().__init__(nodes=nodes) + self._run_machine.on("init", self.run_init) + self._run_machine.on("running", self.run_running) + self._exes = [] + + def run_init(self): + self._run_machine.goto("running") + + for node in self.nodes: + exe = BackgroundExecutor(node) + exe.run() + self._exes.append(exe) + + def run_running(self): + + still_running = False + for exe in self._exes: + exe.run() + still_running |= exe._run_machine.state != RunMachine.Code.FINISHED + + if not still_running: + self._run_machine.goto( + "finished", + status=[exe._run_machine._data["status"] for exe in self._exes] + ) diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index 4c59cfa9f..8ea36cf5c 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -1,10 +1,12 @@ from . import AbstractInput from . import StructureInput from . import AbstractOutput -from . import AbstractNode +from . import AbstractNode, ListNode from . import ReturnStatus from . import make_storage_mapping +from copy import deepcopy + import numpy as np import matplotlib.pyplot as plt @@ -23,8 +25,8 @@ def set_strain_range(self, range, steps): class MurnaghanOutput(AbstractOutput): def __init__(self): super().__init__() - self.storage.volumes = None - self.storage.energies = None + self.storage.volumes = [] + self.storage.energies = [] volumes = make_storage_mapping('volumes') energies = make_storage_mapping('energies') @@ -54,7 +56,7 @@ def _execute(self): returns.append(ret) if ret.is_done(): energy.append(node.output.energy_pot) - volume.append(structure.get_volume()/len(structure)) + volume.append(structure.get_volume()) self.output.energies = np.array(energy) self.output.volumes = np.array(volume) errors = [ret for ret in returns if not ret.is_done()] @@ -62,3 +64,26 @@ def _execute(self): return ReturnStatus('done') else: return ReturnStatus('aborted', msg=errors) + +class ListMurnaghanNode(ListNode): + + def _get_input(self): + return MurnaghanInput() + + def _get_output(self): + return MurnaghanOutput() + + def _create_nodes(self): + cell = self.input.structure.get_cell() + nodes = [] + for s in self.input.strains: + n = deepcopy(self.input.node) + n.input.structure = self.input.structure.copy() + n.input.structure.set_cell(cell * s, scale_atoms=True) + nodes.append(n) + return nodes + + def _extract_output(self, i, node, ret): + if ret.is_done(): + self.output.energies.append(node.output.energy_pot) + self.output.volumes.append(node.input.structure.get_volume()) From 5bfa5babc47014a39de9c8380701bed5862ebf08 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Thu, 2 Mar 2023 14:18:04 +0100 Subject: [PATCH 005/756] Rename AseInput everywhere --- pyiron_contrib/tinybase/ase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index c112487ac..0876783b2 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -30,7 +30,7 @@ def __init__(self): class AseStaticNode(AbstractNode): def _get_input(self): - return AseInput() + return AseStaticInput() def _get_output(self): return EnergyOutput() From e35edf7d003d53ed7fc4d399fb35a5c4d1d88f0f Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Thu, 2 Mar 2023 17:07:15 +0100 Subject: [PATCH 006/756] Move Input/Output classes to separate module Also adds a method to the abstract classes to quickly make subclasses. --- pyiron_contrib/tinybase/__init__.py | 23 +------- pyiron_contrib/tinybase/ase.py | 73 +++++-------------------- pyiron_contrib/tinybase/container.py | 82 ++++++++++++++++++++++++++++ pyiron_contrib/tinybase/murn.py | 37 ++++++------- 4 files changed, 113 insertions(+), 102 deletions(-) create mode 100644 pyiron_contrib/tinybase/container.py diff --git a/pyiron_contrib/tinybase/__init__.py b/pyiron_contrib/tinybase/__init__.py index a3f00e6b3..483f69101 100644 --- a/pyiron_contrib/tinybase/__init__.py +++ b/pyiron_contrib/tinybase/__init__.py @@ -4,6 +4,7 @@ from pyiron_base.interfaces.object import HasStorage +from .container import AbstractInput, AbstractOutput from .executor import ( ForegroundExecutor, BackgroundExecutor, @@ -12,28 +13,6 @@ BackgroundListExecutor ) -def make_storage_mapping(name): - def fget(self): - return self.storage[name] - - def fset(self, value): - self.storage[name] = value - - return property(fget=fget, fset=fset) - -class AbstractInput(HasStorage, abc.ABC): - pass - -class StructureInput(AbstractInput): - def __init__(self): - super().__init__() - self.storage.structure = None - - structure = make_storage_mapping('structure') - -class AbstractOutput(HasStorage, abc.ABC): - pass - class ReturnStatus: class Code(enum.Enum): diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index 0876783b2..bc7bfc8b3 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -1,31 +1,27 @@ -from . import AbstractInput -from . import StructureInput -from . import AbstractOutput +from .container import ( + AbstractInput, + StructureInput, + MDInput, + make_storage_mapping, + EnergyOutput, + MDOutput +) from . import AbstractNode -from . import ReturnStatus -from . import make_storage_mapping import numpy as np import matplotlib.pyplot as plt -from pyiron_atomistics.atomistics.structure.has_structure import HasStructure +from ase.md.langevin import Langevin +from ase.md.velocitydistribution import MaxwellBoltzmannDistribution +from ase import units -class AseInput(AbstractInput): - def __init__(self): - super().__init__() - self.storage.calculator = None - calculator = make_storage_mapping('calculator') +AseInput = AbstractInput.from_attributes('AseInput', 'calculator') + class AseStaticInput(AseInput, StructureInput): pass -class EnergyOutput(AbstractOutput): - def __init__(self): - super().__init__() - self.storage.energy_pot = None - - energy_pot = make_storage_mapping('energy_pot') class AseStaticNode(AbstractNode): @@ -41,52 +37,9 @@ def _execute(self): self.output.energy_pot = structure.get_potential_energy() -class MDInput(StructureInput): - def __init__(self): - super().__init__() - self.storage.steps = None - self.storage.timestep = None - self.storage.temperature = None - self.storage.output_steps = None - - steps = make_storage_mapping('steps') - timestep = make_storage_mapping('timestep') - temperature = make_storage_mapping('temperature') - output_steps = make_storage_mapping('output_steps') - -class MDOutput(AbstractOutput, HasStructure): - def __init__(self): - super().__init__() - - self.storage.pot_energies = [] - self.storage.kin_energies = [] - self.storage.forces = [] - self.storage.structures = [] - - pot_energies = make_storage_mapping("pot_energies") - kin_energies = make_storage_mapping("kin_energies") - forces = make_storage_mapping("forces") - structures = make_storage_mapping("structures") - - def plot_energies(self): - plt.plot(self.pot_energies - np.min(self.pot_energies), label='pot') - plt.plot(self.kin_energies, label='kin') - plt.legend() - - def _number_of_structures(self): - return len(self.structures) - - def _get_structure(self, frame, wrap_atoms=True): - return self.structures[frame] - - class AseMDInput(AseInput, MDInput): pass -from ase.md.langevin import Langevin -from ase.md.velocitydistribution import MaxwellBoltzmannDistribution -from ase import units - class AseMDNode(AbstractNode): def _get_input(self): diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py new file mode 100644 index 000000000..1816556a3 --- /dev/null +++ b/pyiron_contrib/tinybase/container.py @@ -0,0 +1,82 @@ +"""Generic Input Base Clases""" + +import abc + +from pyiron_base.interfaces.object import HasStorage +from pyiron_atomistics.atomistics.structure.has_structure import HasStructure + +import numpy as np +import matplotlib.pyplot as plt + +def make_storage_mapping(name, default=None): + def fget(self): + try: + return self.storage[name] + except KeyError: + self.storage[name] = default + return default + + def fset(self, value): + self.storage[name] = value + + return property(fget=fget, fset=fset) + +class AbstractContainer(HasStorage, abc.ABC): + # TODO: this should go into HasStorage, exists here only to give one location to define from_ methods + @classmethod + def from_attributes(cls, name, *attrs, **default_attrs): + """ + Create a new sub class with given attributes. + + Args: + name (str): name of the new class + *attrs (str): names of the new attributes + **default_attrs (str): names and defaults of new attributes + """ + body = {a: make_storage_mapping(a) for a in attrs} + body.update({a: make_storage_mapping(a, d) for a, d in default_attrs.items()}) + return type(name, (cls,), body) + + +class AbstractInput(AbstractContainer, abc.ABC): + pass + +StructureInput = AbstractInput.from_attributes("StructureInput", "structure") + +MDInput = AbstractInput.from_attributes( + "MDInput", + "steps", + "timestep", + "temperature", + "output_steps" +) + + +class AbstractOutput(AbstractContainer, abc.ABC): + pass + +EnergyOutput = AbstractOutput.from_attributes( + "EnergyOutput", + "energy_pot" +) + +MDOutputBase = AbstractOutput.from_attributes( + "MDOutputBase", + pot_energies=[], + kin_energies=[], + forces=[], + structures=[], +) + +class MDOutput(HasStructure, MDOutputBase): + + def plot_energies(self): + plt.plot(self.pot_energies - np.min(self.pot_energies), label='pot') + plt.plot(self.kin_energies, label='kin') + plt.legend() + + def _number_of_structures(self): + return len(self.structures) + + def _get_structure(self, frame, wrap_atoms=True): + return self.structures[frame] diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index 8ea36cf5c..320b55b02 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -1,36 +1,33 @@ -from . import AbstractInput -from . import StructureInput -from . import AbstractOutput +from .container import ( + StructureInput, + make_storage_mapping, + AbstractOutput +) from . import AbstractNode, ListNode from . import ReturnStatus -from . import make_storage_mapping from copy import deepcopy import numpy as np import matplotlib.pyplot as plt -class MurnaghanInput(StructureInput): - def __init__(self): - super().__init__() - self.storage.strains = None - self.storage.node = None - - strains = make_storage_mapping('strains') - node = make_storage_mapping('node') +MurnaghanInputBase = StructureInput.from_attributes( + "MurnaghanInputBase", + "strains", + "node" +) +class MurnaghanInput(MurnaghanInputBase): def set_strain_range(self, range, steps): self.strains = (1 + np.linspace(-range, range, steps))**(1/3) -class MurnaghanOutput(AbstractOutput): - def __init__(self): - super().__init__() - self.storage.volumes = [] - self.storage.energies = [] - - volumes = make_storage_mapping('volumes') - energies = make_storage_mapping('energies') +MurnaghanOutputBase = AbstractOutput.from_attributes( + "MurnaghanInputBase", + volumes=[], + energies=[] +) +class MurnaghanOutput(MurnaghanInputBase): def plot(self): plt.plot(self.volumes, self.energies) From 8c00b11614ca3e60a51140bcabc249c9282bc604 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 3 Mar 2023 11:27:46 +0100 Subject: [PATCH 007/756] Use functions for defaults to allow mutable types --- pyiron_contrib/tinybase/container.py | 14 +++++++------- pyiron_contrib/tinybase/murn.py | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index 1816556a3..8067abe19 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -8,13 +8,13 @@ import numpy as np import matplotlib.pyplot as plt -def make_storage_mapping(name, default=None): +def make_storage_mapping(name, default=lambda: None): def fget(self): try: return self.storage[name] except KeyError: - self.storage[name] = default - return default + self.storage[name] = default() + return self.storage[name] def fset(self, value): self.storage[name] = value @@ -62,10 +62,10 @@ class AbstractOutput(AbstractContainer, abc.ABC): MDOutputBase = AbstractOutput.from_attributes( "MDOutputBase", - pot_energies=[], - kin_energies=[], - forces=[], - structures=[], + pot_energies=list, + kin_energies=list, + forces=list, + structures=list, ) class MDOutput(HasStructure, MDOutputBase): diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index 320b55b02..ec268238b 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -22,12 +22,12 @@ def set_strain_range(self, range, steps): self.strains = (1 + np.linspace(-range, range, steps))**(1/3) MurnaghanOutputBase = AbstractOutput.from_attributes( - "MurnaghanInputBase", - volumes=[], - energies=[] + "MurnaghanOutputBase", + volumes=list, + energies=list ) -class MurnaghanOutput(MurnaghanInputBase): +class MurnaghanOutput(MurnaghanOutputBase): def plot(self): plt.plot(self.volumes, self.energies) From 53568b46549fed8a973bd966f8581f06048e1c68 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 3 Mar 2023 11:29:20 +0100 Subject: [PATCH 008/756] Add a method to observe executor state changes --- pyiron_contrib/tinybase/executor.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index b034b3a9f..22eab3e45 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -1,5 +1,6 @@ import abc import enum +from collections import defaultdict from typing import Union class RunMachine: @@ -12,6 +13,7 @@ class Code(enum.Enum): def __init__(self, initial_state): self._state = RunMachine.Code(initial_state) self._callbacks = {} + self._observers = defaultdict(list) self._data = {} # state variables associated with each state @property @@ -23,12 +25,19 @@ def on(self, state: Union[str, Code], callback): state = RunMachine.Code(state) self._callbacks[state] = callback + def observe(self, state: Union[str, Code], callback): + if isinstance(state, str): + state = RunMachine.Code(state) + self._observers[state].append(callback) + def goto(self, state: Union[str, Code], **kwargs): if isinstance(state, str): state = RunMachine.Code(state) self._state = state self._data = {} self._data.update(kwargs) + for obs in self._observers[state]: + obs(self._data) def step(self, state: Union[str, Code, None] = None, **kwargs): if state is not None: From 522795fd6bd5cf2162749ca4eb1f3726f9f77ec3 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 3 Mar 2023 11:30:28 +0100 Subject: [PATCH 009/756] Use observe to list nodes --- pyiron_contrib/tinybase/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/tinybase/__init__.py b/pyiron_contrib/tinybase/__init__.py index 483f69101..385432eaa 100644 --- a/pyiron_contrib/tinybase/__init__.py +++ b/pyiron_contrib/tinybase/__init__.py @@ -117,9 +117,11 @@ def run(self, how='foreground'): Exe = self._executors[how] if issubclass(Exe, ListExecutor): exe = Exe(self.nodes) + exe._run_machine.observe("finished", + lambda data: [self._extract_output(i, n, r) + for i, (n, r) in enumerate(zip(self.nodes, data['status']))] + ) exe.run() - # for i, (node, ret) in enumerate(zip(exe.nodes, exe._run_machine._data['status'])): - # self._extract_output(i, node, ret) else: exe = Exe(self) exe.run() From 33956adb388f3daa7793c76cc428d980c2716ab6 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 3 Mar 2023 11:30:46 +0100 Subject: [PATCH 010/756] Add a node for arbitrary functions --- pyiron_contrib/tinybase/__init__.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pyiron_contrib/tinybase/__init__.py b/pyiron_contrib/tinybase/__init__.py index 385432eaa..af3256610 100644 --- a/pyiron_contrib/tinybase/__init__.py +++ b/pyiron_contrib/tinybase/__init__.py @@ -81,6 +81,23 @@ def run(self, how='foreground'): exe.run() return exe +FunctionInput = AbstractInput.from_attributes("FunctionInput", args=list, kwargs=dict) +FunctionOutput = AbstractOutput.from_attributes("FunctionOutput", "result") +class FunctionNode(AbstractNode): + + def __init__(self, function): + super().__init__() + self._function = function + + def _get_input(self): + return FunctionInput() + + def _get_output(self): + return FunctionOutput() + + def _execute(self): + self.output.result = self._function(*self.input.args, **self.input.kwargs) + class ListNode(AbstractNode, abc.ABC): _executors = { From 9b8160d67837aaa0314e735228ffdd95954c24a7 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 3 Mar 2023 13:41:23 +0100 Subject: [PATCH 011/756] Vastly simplify Executors All executors can now process lists of nodes. Normal nodes are simply singleton lists. ListNodes can process the output of their children by observing the collect state of the executor run state machine. --- pyiron_contrib/tinybase/__init__.py | 33 +++---- pyiron_contrib/tinybase/executor.py | 145 +++++++++++----------------- 2 files changed, 68 insertions(+), 110 deletions(-) diff --git a/pyiron_contrib/tinybase/__init__.py b/pyiron_contrib/tinybase/__init__.py index af3256610..3342bc625 100644 --- a/pyiron_contrib/tinybase/__init__.py +++ b/pyiron_contrib/tinybase/__init__.py @@ -6,11 +6,8 @@ from .container import AbstractInput, AbstractOutput from .executor import ( - ForegroundExecutor, + Executor, BackgroundExecutor, - ListExecutor, - SerialListExecutor, - BackgroundListExecutor ) class ReturnStatus: @@ -36,7 +33,7 @@ def is_done(self): class AbstractNode(abc.ABC): _executors = { - 'foreground': ForegroundExecutor, + 'foreground': Executor, 'background': BackgroundExecutor } @@ -63,6 +60,9 @@ def output(self) -> AbstractOutput: self._output = self._get_output() return self._output + def check_ready(self): + return True + @abc.abstractmethod def _execute(self) -> Optional[ReturnStatus]: pass @@ -77,7 +77,7 @@ def execute(self) -> ReturnStatus: return ret def run(self, how='foreground'): - exe = self._executors[how](node=self) + exe = self._executors[how](nodes=[self]) exe.run() return exe @@ -101,8 +101,8 @@ def _execute(self): class ListNode(AbstractNode, abc.ABC): _executors = { - 'foreground': SerialListExecutor, - 'background': BackgroundListExecutor + 'foreground': Executor, + 'background': BackgroundExecutor } def __init__(self): @@ -131,15 +131,10 @@ def _execute(self): # If called via run by the user directly we can also dispatch to a list executor def run(self, how='foreground'): - Exe = self._executors[how] - if issubclass(Exe, ListExecutor): - exe = Exe(self.nodes) - exe._run_machine.observe("finished", - lambda data: [self._extract_output(i, n, r) - for i, (n, r) in enumerate(zip(self.nodes, data['status']))] - ) - exe.run() - else: - exe = Exe(self) - exe.run() + exe = self._executors[how](self.nodes) + exe._run_machine.observe("collect", + lambda data: [self._extract_output(i, n, r) + for i, (n, r) in enumerate(zip(self.nodes, data['status']))] + ) + exe.run() return exe diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index 22eab3e45..f5cb0e80e 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -3,11 +3,15 @@ from collections import defaultdict from typing import Union +import logging + class RunMachine: class Code(enum.Enum): INIT = 'init' + READY = 'ready' RUNNING = 'running' + COLLECT = 'collect' FINISHED = 'finished' def __init__(self, initial_state): @@ -46,114 +50,73 @@ def step(self, state: Union[str, Code, None] = None, **kwargs): class Executor(abc.ABC): - def __init__(self): + def __init__(self, nodes): + self._nodes = nodes self._run_machine = RunMachine("init") - - def run(self): - self._run_machine.step() - -class SingleExecutor(Executor, abc.ABC): - - def __init__(self, node): - super().__init__() - self._node = node + self._run_machine.on("init", self._run_init) + # exists mostly to let downstream code hook into it, e.g. to write input files and such + self._run_machine.on("ready", self._run_ready) + self._run_machine.on("running", self._run_running) + # exists mostly to let downstream code hook into it, e.g. to read output files and such + self._run_machine.on("collect", self._run_collect) + self._run_machine.on("finished", self._run_finished) @property - def node(self): - return self._node - -class ForegroundExecutor(SingleExecutor): - - def __init__(self, node): - super().__init__(node=node) - self._run_machine.on("init", self.run_init) - - def run_init(self): - self._run_machine.goto("running") - - ret = self.node.execute() - - self._run_machine.goto("finished", status=ret) - -from threading import Thread - -class BackgroundExecutor(SingleExecutor): - - def __init__(self, node): - super().__init__(node=node) - self._run_machine = RunMachine("init") - self._run_machine.on("init", self.run_init) - self._run_machine.on("running", self.run_running) - self._thread = None - - def run_init(self): - self._run_machine.goto("running") - - node = self.node - class NodeThread(Thread): - def run(self): - self.ret = node.execute() - - self._thread = NodeThread() - self._thread.start() + def nodes(self): + return self._nodes - def run_running(self): - self._thread.join(timeout=0) - if not self._thread.is_alive(): - self._run_machine.goto("finished", status=self._thread.ret) + def _run_init(self): + if all(node.check_ready() for node in self.nodes): + self._run_machine.step("ready") else: - print("Node is still running!") + logging.info("Node is not ready yet!") -class ListExecutor(Executor, abc.ABC): + def _run_ready(self): + self._run_machine.step("running") - def __init__(self, nodes): - super().__init__() - self._nodes = nodes + def _run_running(self): + ret = [node.execute() for node in self.nodes] + self._run_machine.step("collect", status=ret) - @property - def nodes(self): - return self._nodes + def _run_collect(self): + self._run_machine.step("finished", + status=self._run_machine._data["status"], + output=[node.output for node in self.nodes] + ) -class SerialListExecutor(ListExecutor): + def _run_finished(self): + pass - def __init__(self, nodes): - super().__init__(nodes=nodes) - self._run_machine.on("init", self.run_init) + def run(self): + self._run_machine.step() - def run_init(self): - self._run_machine.goto("running") - returns = [] - for node in self.nodes: - returns.append(node.execute()) - - self._run_machine.goto("finished", status=returns) +from threading import Thread -class BackgroundListExecutor(ListExecutor): +class BackgroundExecutor(Executor): def __init__(self, nodes): super().__init__(nodes=nodes) - self._run_machine.on("init", self.run_init) - self._run_machine.on("running", self.run_running) - self._exes = [] + self._threads = {} + self._returns = {} - def run_init(self): - self._run_machine.goto("running") + def _run_running(self): - for node in self.nodes: - exe = BackgroundExecutor(node) - exe.run() - self._exes.append(exe) - - def run_running(self): + def exec_node(node): + self._returns[node] = node.execute() still_running = False - for exe in self._exes: - exe.run() - still_running |= exe._run_machine.state != RunMachine.Code.FINISHED - - if not still_running: - self._run_machine.goto( - "finished", - status=[exe._run_machine._data["status"] for exe in self._exes] - ) + for node in self.nodes: + if node not in self._threads: + thread = self._threads[node] = Thread(target=exec_node, args=(node,)) + thread.start() + still_running = True + else: + thread = self._threads[node] + thread.join(timeout=0) + still_running |= thread.is_alive() + if still_running: + logging.info("Some nodes are still executing!") + else: + # how to ensure ordering? dict remembers insertion order, so maybe ok for now + self._run_machine.step("collect", status=list(self._returns.values())) From 6ca106c26f9eaab316fcb60d3fc62bcb68358f6a Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 3 Mar 2023 16:11:55 +0100 Subject: [PATCH 012/756] Allow to manually override __module__ of dynamical input/output classes Otherwise the pickling won't work, because it thinks it's defined in abc. --- pyiron_contrib/tinybase/container.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index 8067abe19..530317de5 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -24,31 +24,36 @@ def fset(self, value): class AbstractContainer(HasStorage, abc.ABC): # TODO: this should go into HasStorage, exists here only to give one location to define from_ methods @classmethod - def from_attributes(cls, name, *attrs, **default_attrs): + def from_attributes(cls, name, *attrs, module=None, **default_attrs): """ Create a new sub class with given attributes. Args: name (str): name of the new class *attrs (str): names of the new attributes + module (str, optional): the module path where this class is defined; you cannot pickle instances of classes + defined by this function, if you do not provide this as __name__ **default_attrs (str): names and defaults of new attributes """ body = {a: make_storage_mapping(a) for a in attrs} body.update({a: make_storage_mapping(a, d) for a, d in default_attrs.items()}) - return type(name, (cls,), body) + T = type(name, (cls,), body) + T.__module__ = module + return T class AbstractInput(AbstractContainer, abc.ABC): pass -StructureInput = AbstractInput.from_attributes("StructureInput", "structure") +StructureInput = AbstractInput.from_attributes("StructureInput", "structure", module=__name__) MDInput = AbstractInput.from_attributes( "MDInput", "steps", "timestep", "temperature", - "output_steps" + "output_steps", + module=__name__ ) @@ -57,7 +62,8 @@ class AbstractOutput(AbstractContainer, abc.ABC): EnergyOutput = AbstractOutput.from_attributes( "EnergyOutput", - "energy_pot" + "energy_pot", + module=__name__ ) MDOutputBase = AbstractOutput.from_attributes( From a0620591f9dfee0b8920086bd024811fc54cb0c4 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 3 Mar 2023 16:13:21 +0100 Subject: [PATCH 013/756] Add a process pool executor --- pyiron_contrib/tinybase/__init__.py | 4 +- pyiron_contrib/tinybase/executor.py | 58 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/__init__.py b/pyiron_contrib/tinybase/__init__.py index 3342bc625..824be985f 100644 --- a/pyiron_contrib/tinybase/__init__.py +++ b/pyiron_contrib/tinybase/__init__.py @@ -8,6 +8,7 @@ from .executor import ( Executor, BackgroundExecutor, + ProcessExecutor ) class ReturnStatus: @@ -102,7 +103,8 @@ class ListNode(AbstractNode, abc.ABC): _executors = { 'foreground': Executor, - 'background': BackgroundExecutor + 'background': BackgroundExecutor, + 'process': ProcessExecutor } def __init__(self): diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index f5cb0e80e..40079a48c 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -120,3 +120,61 @@ def exec_node(node): else: # how to ensure ordering? dict remembers insertion order, so maybe ok for now self._run_machine.step("collect", status=list(self._returns.values())) + +from concurrent.futures import ProcessPoolExecutor + +def run_node(node): + ret = node.execute() + return ret, node.output + +class ProcessExecutor(Executor): + + def __init__(self, nodes, processes=None): + super().__init__(nodes=nodes) + self._processes = processes if processes is not None else 4 + self._pool = None + self._futures = {} + self._returns = {} + + def _run_running(self): + + if self._pool is None: + self._pool = ProcessPoolExecutor(max_workers=self._processes) + pool = self._pool + + still_running = False + for node in self.nodes: + if node not in self._futures: + self._futures[node] = pool.submit(run_node, node) + still_running = True + else: + future = self._futures[node] + if future.done(): + # TODO breaks API + ret, output = future.result(timeout=0) + node._output = output + self._returns[node] = ret + else: + still_running = True + + if still_running: + logging.info("Some nodes are still executing!") + else: + pool.shutdown() + # how to ensure ordering? dict remembers insertion order, so maybe ok for now + self._run_machine.step("collect", status=list(self._returns.values())) + + + + +class HdfExecutor(Executor): + + def __init__(self): + self._run_machine.observe("running", self._save_input) + self._run_machine.observe("finished", self._save_output) + + def _save_input(self, data): + self.node.input.to_hdf(self._hdf) # where's that coming from? + + def _save_output(self, data): + self.node.output.to_hdf(self._hdf) # where's that coming from? From 66b5d35fd0da8e2a6a0837d5563a958cc1b6f096 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Sat, 4 Mar 2023 12:08:57 +0100 Subject: [PATCH 014/756] Rework ProcessExecutor and unify with BackgroundExecutor add time keeping to all Executors --- pyiron_contrib/tinybase/__init__.py | 3 +- pyiron_contrib/tinybase/executor.py | 143 ++++++++++++++-------------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/pyiron_contrib/tinybase/__init__.py b/pyiron_contrib/tinybase/__init__.py index 824be985f..3d18582e0 100644 --- a/pyiron_contrib/tinybase/__init__.py +++ b/pyiron_contrib/tinybase/__init__.py @@ -35,7 +35,8 @@ class AbstractNode(abc.ABC): _executors = { 'foreground': Executor, - 'background': BackgroundExecutor + 'background': BackgroundExecutor, + 'process': ProcessExecutor } def __init__(self): diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index 40079a48c..34f53eefc 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -2,6 +2,7 @@ import enum from collections import defaultdict from typing import Union +import time import logging @@ -53,6 +54,8 @@ class Executor(abc.ABC): def __init__(self, nodes): self._nodes = nodes self._run_machine = RunMachine("init") + + # Set up basic flow self._run_machine.on("init", self._run_init) # exists mostly to let downstream code hook into it, e.g. to write input files and such self._run_machine.on("ready", self._run_ready) @@ -61,6 +64,25 @@ def __init__(self, nodes): self._run_machine.on("collect", self._run_collect) self._run_machine.on("finished", self._run_finished) + # keeping track of run times + self._running_start = None + self._collect_start = None + self._finished_start = None + self._run_machine.observe("running", self._time_running) + self._run_machine.observe("collect", self._time_collect) + self._run_machine.observe("finished", self._time_finished) + + def _time_running(self, _data): + self._running_start = time.monotonic() + + def _time_collect(self, _data): + self._collect_start = time.monotonic() + self._run_time = self._collect_start - self._running_start + + def _time_finished(self, _data): + self._finished_start = time.monotonic() + self._collect_time = self._finished_start - self._collect_start + @property def nodes(self): return self._nodes @@ -90,91 +112,68 @@ def _run_finished(self): def run(self): self._run_machine.step() - -from threading import Thread - -class BackgroundExecutor(Executor): - - def __init__(self, nodes): - super().__init__(nodes=nodes) - self._threads = {} - self._returns = {} - - def _run_running(self): - - def exec_node(node): - self._returns[node] = node.execute() - - still_running = False - for node in self.nodes: - if node not in self._threads: - thread = self._threads[node] = Thread(target=exec_node, args=(node,)) - thread.start() - still_running = True - else: - thread = self._threads[node] - thread.join(timeout=0) - still_running |= thread.is_alive() - if still_running: - logging.info("Some nodes are still executing!") - else: - # how to ensure ordering? dict remembers insertion order, so maybe ok for now - self._run_machine.step("collect", status=list(self._returns.values())) - -from concurrent.futures import ProcessPoolExecutor +from concurrent.futures import ( + ThreadPoolExecutor, + ProcessPoolExecutor, + Executor as FExecutor +) +from threading import Lock def run_node(node): ret = node.execute() return ret, node.output -class ProcessExecutor(Executor): +class FuturesExecutor(Executor, abc.ABC): + + _FuturePoolExecutor: FExecutor = None - def __init__(self, nodes, processes=None): + # poor programmer's abstract attribute check + def __init_subclass__(cls): + if cls._FuturePoolExecutor is None: + raise TypeError(f"Subclass {cls} of FuturesExecutor does not define 'FuturePoolExecutor'!") + + def __init__(self, nodes, max_tasks=None): super().__init__(nodes=nodes) - self._processes = processes if processes is not None else 4 - self._pool = None + self._max_tasks = max_tasks if max_tasks is not None else 4 + self._done = 0 self._futures = {} self._returns = {} + self._lock = Lock() - def _run_running(self): - - if self._pool is None: - self._pool = ProcessPoolExecutor(max_workers=self._processes) - pool = self._pool - - still_running = False - for node in self.nodes: - if node not in self._futures: - self._futures[node] = pool.submit(run_node, node) - still_running = True - else: - future = self._futures[node] - if future.done(): - # TODO breaks API - ret, output = future.result(timeout=0) - node._output = output - self._returns[node] = ret - else: - still_running = True - - if still_running: - logging.info("Some nodes are still executing!") + def _process_future(self, future=None): + for node, _f in self._futures.items(): + if _f == future: break else: - pool.shutdown() - # how to ensure ordering? dict remembers insertion order, so maybe ok for now - self._run_machine.step("collect", status=list(self._returns.values())) - - + assert False, "This should never happen!" + ret, output = future.result(timeout=0) + node._output = output + self._returns[node] = ret + with self._lock: + self._done += 1 + self._check_finish() -class HdfExecutor(Executor): + def _check_finish(self, log=False): + with self._lock: + if self._done == len(self.nodes): + # how to ensure ordering? dict remembers insertion order, so maybe ok for now + self._run_machine.step("collect", status=list(self._returns.values())) - def __init__(self): - self._run_machine.observe("running", self._save_input) - self._run_machine.observe("finished", self._save_output) + def _run_running(self): + if len(self._futures) < len(self.nodes): + pool = self._FuturePoolExecutor(max_workers=self._max_tasks) + try: + for node in self.nodes: + future = self._futures[node] = pool.submit(run_node, node) + future.add_done_callback(self._process_future) + finally: + # with statement doesn't allow me to put wait=False, so I gotta do it here with try/finally. + pool.shutdown(wait=False) + else: + logging.info("Some nodes are still executing!") - def _save_input(self, data): - self.node.input.to_hdf(self._hdf) # where's that coming from? +class BackgroundExecutor(FuturesExecutor): + _FuturePoolExecutor = ThreadPoolExecutor - def _save_output(self, data): - self.node.output.to_hdf(self._hdf) # where's that coming from? +class ProcessExecutor(FuturesExecutor): + _FuturePoolExecutor = ProcessPoolExecutor From 227f61c75e6d8d0df603a597abaafee08a14f658 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Sat, 4 Mar 2023 17:14:43 +0100 Subject: [PATCH 015/756] Replace make_storage_mapping with class; move check_ready AbstractInput --- pyiron_contrib/tinybase/ase.py | 1 - pyiron_contrib/tinybase/container.py | 93 ++++++++++++++++++++++++---- pyiron_contrib/tinybase/executor.py | 2 +- pyiron_contrib/tinybase/murn.py | 8 ++- 4 files changed, 90 insertions(+), 14 deletions(-) diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index bc7bfc8b3..c77f2f8c7 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -2,7 +2,6 @@ AbstractInput, StructureInput, MDInput, - make_storage_mapping, EnergyOutput, MDOutput ) diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index 530317de5..404ca3581 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -8,18 +8,88 @@ import numpy as np import matplotlib.pyplot as plt -def make_storage_mapping(name, default=lambda: None): - def fget(self): +class StorageAttribute: + """ + Create an attribute that is synced to a storage attribute. + + It must be created on a class that derives from :class:`.HasStorage`. It essentially behaves like a property that + writes and reads values to the underlying :attr:`.HasStorage.storage`. DataContainer of the class. When accessing + this property before setting it, `None` is returned. + + It's possible to modify the default value and the accepted type of values by using the builder-style :meth:`.type` + and :meth:`.default` methods. + + >>> class MyType(HasStorage): + ... a = StorageAttribute() + ... b = StorageAttribute().type(int) + ... c = StorageAttribute().default(list) + ... d = StorageAttribute().default(lambda: 42).type(int) + + >>> t = MyType() + >>> t.a # return None + >>> t.b = 3 + >>> t.b + 3 + >>> t.b = 'asdf' + TypeError(f"{value} is not of type {self.value_type}") + >>> t.c + [] + >>> t.d + 42 + """ + + def __init__(self): + self.name = None + self.value_type = object + self.default_constructor = None + + def __set_name__(self, obj, name): + self.name = name + + def __get__(self, obj, objtype=None): try: - return self.storage[name] + return obj.storage[self.name] except KeyError: - self.storage[name] = default() - return self.storage[name] + if self.default_constructor is not None: + ret = obj.storage[self.name] = self.default_constructor() + return ret + else: + return None + + def __set__(self, obj, value): + if isinstance(value, self.value_type): + obj.storage[self.name] = value + else: + raise TypeError(f"{value} is not of type {self.value_type}") + + def type(self, value_type): + """ + Set a type to type check values set on the attribute. + + Args: + value_type (type, tuple of type): a class or list of classes that are acceptable for the attribute - def fset(self, value): - self.storage[name] = value + Returns: + self: the object it is called on + """ + self.value_type = value_type + return self - return property(fget=fget, fset=fset) + def default(self, default_constructor): + """ + Set a function to create a default value, if the attribute is accessed without being set. + + Args: + default_constructor (function, type): Either a class or a function that takes no arguments + + Returns: + self: the object it is called on + """ + self.default_constructor = default_constructor + return self + + def doc(self, text): + self.__doc__ = text class AbstractContainer(HasStorage, abc.ABC): # TODO: this should go into HasStorage, exists here only to give one location to define from_ methods @@ -35,15 +105,16 @@ def from_attributes(cls, name, *attrs, module=None, **default_attrs): defined by this function, if you do not provide this as __name__ **default_attrs (str): names and defaults of new attributes """ - body = {a: make_storage_mapping(a) for a in attrs} - body.update({a: make_storage_mapping(a, d) for a, d in default_attrs.items()}) + body = {a: StorageAttribute() for a in attrs} + body.update({a: StorageAttribute().default(d) for a, d in default_attrs.items()}) T = type(name, (cls,), body) T.__module__ = module return T class AbstractInput(AbstractContainer, abc.ABC): - pass + def check_ready(self): + return True StructureInput = AbstractInput.from_attributes("StructureInput", "structure", module=__name__) diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index 34f53eefc..4505f315a 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -88,7 +88,7 @@ def nodes(self): return self._nodes def _run_init(self): - if all(node.check_ready() for node in self.nodes): + if all(node.input.check_ready() for node in self.nodes): self._run_machine.step("ready") else: logging.info("Node is not ready yet!") diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index ec268238b..7fe85f936 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -1,6 +1,5 @@ from .container import ( StructureInput, - make_storage_mapping, AbstractOutput ) from . import AbstractNode, ListNode @@ -18,6 +17,13 @@ ) class MurnaghanInput(MurnaghanInputBase): + def check_ready(self): + structure_ready = self.structure is not None + strain_ready = len(self.strains) > 0 + node = self.node + node.input.structure = self.structure + return structure_ready and strain_ready and node.input.check_ready() + def set_strain_range(self, range, steps): self.strains = (1 + np.linspace(-range, range, steps))**(1/3) From 10df39f4d5b94d50eabfa525b8224226125fd4ac Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Sat, 4 Mar 2023 17:29:42 +0100 Subject: [PATCH 016/756] Remove old Murnaghan node for List node --- pyiron_contrib/tinybase/murn.py | 33 +-------------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index 7fe85f936..a981383b5 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -37,38 +37,7 @@ class MurnaghanOutput(MurnaghanOutputBase): def plot(self): plt.plot(self.volumes, self.energies) -class MurnaghanNode(AbstractNode): - - def _get_input(self): - return MurnaghanInput() - - def _get_output(self): - return MurnaghanOutput() - - def _execute(self): - cell = self.input.structure.get_cell() - node = self.input.node - energy = [] - volume = [] - returns = [] - for s in self.input.strains: - structure = self.input.structure.copy() - structure.set_cell(cell * s, scale_atoms=True) - node.input.structure = structure - ret = node.execute() - returns.append(ret) - if ret.is_done(): - energy.append(node.output.energy_pot) - volume.append(structure.get_volume()) - self.output.energies = np.array(energy) - self.output.volumes = np.array(volume) - errors = [ret for ret in returns if not ret.is_done()] - if len(errors) == 0: - return ReturnStatus('done') - else: - return ReturnStatus('aborted', msg=errors) - -class ListMurnaghanNode(ListNode): +class MurnaghanNode(ListNode): def _get_input(self): return MurnaghanInput() From 1a2b581a005e4b6485cc9f1e9d9d067b8b1f1d4a Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 6 Mar 2023 13:22:49 +0100 Subject: [PATCH 017/756] Add minimizer node for ASE --- pyiron_contrib/tinybase/ase.py | 72 +++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index c77f2f8c7..7decb4f4b 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -1,11 +1,12 @@ from .container import ( AbstractInput, + StorageAttribute, StructureInput, MDInput, EnergyOutput, MDOutput ) -from . import AbstractNode +from . import AbstractNode, ReturnStatus import numpy as np import matplotlib.pyplot as plt @@ -13,6 +14,9 @@ from ase.md.langevin import Langevin from ase.md.velocitydistribution import MaxwellBoltzmannDistribution from ase import units +from ase.optimize.lbfgs import LBFGS +from ase.optimize.fire import FIRE +from ase.optimize.gpmin.gpmin import GPMin AseInput = AbstractInput.from_attributes('AseInput', 'calculator') @@ -70,3 +74,69 @@ def parse(): parse() dyn.attach(parse, interval=self.input.steps // self.input.output_steps) dyn.run(self.input.steps) + +MinimizeInput = AbstractInput.from_attributes( + 'MinimizeInput', + ionic_force_tolerance=float, + max_steps=int, + output_steps=int, +) + +class AseMinimizeInput(AseInput, StructureInput, MinimizeInput): + + """My first experimental docstring""" + algo = StorageAttribute().type(str).default('LBFGS') + """My second experimental docstring""" + minimizer_kwargs = StorageAttribute().type(dict).default(dict) + + def lbfgs(self, damping=None, alpha=None): + self.algo = 'LBFGS' + if damping is not None: + self.minimizer_kwargs['damping'] = damping + if alpha is not None: + self.minimizer_kwargs['alpha'] = alpha + + def fire(self): + self.algo = 'FIRE' + + def gpmin(self): + self.algo = 'GPMIN' + + def get_ase_optimizer(self, structure): + return { + 'LBFGS': LBFGS, + 'FIRE': FIRE, + 'GPMIN': GPMin + }[self.algo](structure, **self.minimizer_kwargs) + + +class AseMinimizeNode(AbstractNode): + + def _get_input(self): + return AseMinimizeInput() + + def _get_output(self): + return MDOutput() + + def _execute(self): + structure = self.input.structure.copy() + structure.calc = self.input.calculator + + opt = self.input.get_ase_optimizer(structure) + + def parse(): + self.output.structures.append(structure.copy()) + self.output.pot_energies.append(structure.get_potential_energy()) + self.output.kin_energies.append(structure.get_kinetic_energy()) + self.output.forces.append(structure.get_forces()) + + opt.attach(parse, interval=self.input.output_steps) + opt.run(fmax=self.input.ionic_force_tolerance, steps=self.input.max_steps) + parse() + + max_force = abs(self.output.forces[-1]).max() + if max_force > self.input.ionic_force_tolerance: + return ReturnStatus( + "not_converged", + f"force in last step ({max_force}) is larger than tolerance ({self.input.ionic_force_tolerance})!" + ) From c69aad07ddee2c449c1f7164a5d89f97fa687d35 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 6 Mar 2023 13:34:58 +0100 Subject: [PATCH 018/756] Remove module name hack by sys._getframe hack --- pyiron_contrib/tinybase/container.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index 404ca3581..1c405dda0 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -1,6 +1,7 @@ """Generic Input Base Clases""" import abc +import sys from pyiron_base.interfaces.object import HasStorage from pyiron_atomistics.atomistics.structure.has_structure import HasStructure @@ -101,13 +102,16 @@ def from_attributes(cls, name, *attrs, module=None, **default_attrs): Args: name (str): name of the new class *attrs (str): names of the new attributes - module (str, optional): the module path where this class is defined; you cannot pickle instances of classes - defined by this function, if you do not provide this as __name__ + module (str, optional): the module path where this class is defined; on CPython this is automagically filled + in, in other python implementations you need to manually provide this value as __name__ when you + call this method for the resulting class to be picklable. **default_attrs (str): names and defaults of new attributes """ body = {a: StorageAttribute() for a in attrs} body.update({a: StorageAttribute().default(d) for a, d in default_attrs.items()}) T = type(name, (cls,), body) + if module is None: + module = sys._getframe(1).f_globals['__name__'] T.__module__ = module return T @@ -116,7 +120,7 @@ class AbstractInput(AbstractContainer, abc.ABC): def check_ready(self): return True -StructureInput = AbstractInput.from_attributes("StructureInput", "structure", module=__name__) +StructureInput = AbstractInput.from_attributes("StructureInput", "structure") MDInput = AbstractInput.from_attributes( "MDInput", @@ -124,7 +128,6 @@ def check_ready(self): "timestep", "temperature", "output_steps", - module=__name__ ) @@ -134,7 +137,6 @@ class AbstractOutput(AbstractContainer, abc.ABC): EnergyOutput = AbstractOutput.from_attributes( "EnergyOutput", "energy_pot", - module=__name__ ) MDOutputBase = AbstractOutput.from_attributes( From 8e159560d0e6ba4df8eae9b5f4cbe4589b7235eb Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 6 Mar 2023 13:41:00 +0100 Subject: [PATCH 019/756] Add wait method to executor wait() blocks until the desired run state is reached --- pyiron_contrib/tinybase/executor.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index 4505f315a..a2a74d913 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -49,7 +49,8 @@ def step(self, state: Union[str, Code, None] = None, **kwargs): self.goto(state, **kwargs) self._callbacks.get(self._state, lambda: None)() -class Executor(abc.ABC): + +class Executor: def __init__(self, nodes): self._nodes = nodes @@ -112,6 +113,15 @@ def _run_finished(self): def run(self): self._run_machine.step() + def wait(self, until="finished", sleep=0.1): + """ + Sleep until specified state of the run state machine is reached. + """ + until = RunMachine.Code(until) + while until != self._run_machine.state: + time.sleep(sleep) + + from concurrent.futures import ( ThreadPoolExecutor, ProcessPoolExecutor, From bf2821080ca527cfbebcb4ca36ec5834b5dab6ed Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 6 Mar 2023 13:42:09 +0100 Subject: [PATCH 020/756] Rewrite ListNode and add SeriesNode Both now take an additional argument, the executor class to use for their children. This now allows complete freedom how nodes are run and still allows master nodes (ListNode and SeriesNode) to decide themselves how they want their children to be run, without depending on executor details. --- pyiron_contrib/tinybase/__init__.py | 134 +++++++++++++++++++++++----- pyiron_contrib/tinybase/murn.py | 48 +++++++--- 2 files changed, 146 insertions(+), 36 deletions(-) diff --git a/pyiron_contrib/tinybase/__init__.py b/pyiron_contrib/tinybase/__init__.py index 3d18582e0..1c6b66e99 100644 --- a/pyiron_contrib/tinybase/__init__.py +++ b/pyiron_contrib/tinybase/__init__.py @@ -100,44 +100,132 @@ def _get_output(self): def _execute(self): self.output.result = self._function(*self.input.args, **self.input.kwargs) +MasterInput = AbstractInput.from_attributes( + "MasterInput", + child_executor=lambda: Executor +) + +class ListInput(MasterInput, abc.ABC): + + @abc.abstractmethod + def _create_nodes(self): + pass + class ListNode(AbstractNode, abc.ABC): + def __init__(self): + super().__init__() + + @abc.abstractmethod + def _extract_output(self, step, node, ret): + pass + + def _execute(self): + nodes = self.input._create_nodes() + exe = self.input.child_executor(nodes) + exe.run() + exe.wait() + + for i, (node, ret) in enumerate(zip(nodes, exe._run_machine._data["status"])): + self._extract_output(i, node, ret) + +SeriesInputBase = AbstractInput.from_attributes( + "SeriesInputBase", + nodes=list, + connections=list +) + +class SeriesInput(SeriesInputBase, MasterInput): + def check_ready(self): + return len(self.nodes) == len(connections) + 1 + + def first(self, node): + """ + Set initial node. + + Resets whole input + + Args: + node (AbstractNode): the first node to execute + + Returns: + self: the input object + """ + self.nodes = [node] + self.connections = [] + return self + + def then(self, next_node, connection): + """ + Add a new node and how to connect it to the previous node. + + Args: + next_node (:class:`~.AbstractNode`): next node to execute + connection (function): takes the input of next_node and the output of the previous node + + Returns: + self: the input object + """ + self.nodes.append(next_node) + self.connections.append(connection) + return self + +class SeriesNode(AbstractNode): + + def _get_input(self): + return SeriesInput() + + def _get_output(self): + return self.input.nodes[-1]._get_output() + + def _execute(self): + Exe = self.input.child_executor + + first = self.input.nodes[0] + exe = Exe([first]) + exe.run() + exe.wait() + + prev = first + for node, connection in zip(self.input.nodes[1:], self.input.connections): + connection(node.input, prev.output) + exe = Exe([node]) + exe.run() + exe.wait() + ret = exe._run_machine._data["status"][0] + if ret is not None and not ret.is_done(): + return ReturnStatus("aborted", ret) + + + +class TinyJob(abc.ABC): + _executors = { 'foreground': Executor, 'background': BackgroundExecutor, 'process': ProcessExecutor } - def __init__(self): - super().__init__() - self._nodes = None + def __init__(self, project, job_name): + self._project = project + self._name = job_name @abc.abstractmethod - def _create_nodes(self): + def _get_node(self): pass @property - def nodes(self): - if self._nodes is None: - self._nodes = self._create_nodes() - return self._nodes + def input(self): + return self._node.input - @abc.abstractmethod - def _extract_output(self, step, node, ret): - pass - - # If our own execute is called we act like a normal node, executing child nodes and then process their output - def _execute(self): - for i, node in enumerate(self.nodes): - ret = node.execute() - self._extract_output(i, node, ret) + @property + def output(self): + return self._node.output - # If called via run by the user directly we can also dispatch to a list executor def run(self, how='foreground'): - exe = self._executors[how](self.nodes) - exe._run_machine.observe("collect", - lambda data: [self._extract_output(i, n, r) - for i, (n, r) in enumerate(zip(self.nodes, data['status']))] - ) + exe = self._executor = self._executors[how](nodes=[self._node]) + exe._run_machine.observe("ready", self.save_input) + exe._run_machine.observe("collect", self.save_output) exe.run() return exe + diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index a981383b5..9fb403e8e 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -1,14 +1,18 @@ from .container import ( + AbstractOutput, StructureInput, - AbstractOutput ) -from . import AbstractNode, ListNode +from . import AbstractNode, ListNode, ListInput from . import ReturnStatus from copy import deepcopy import numpy as np import matplotlib.pyplot as plt +import scipy.interpolate as si +import scipy.optimize as so + +from pyiron_atomistics.atomistics.structure.has_structure import HasStructure MurnaghanInputBase = StructureInput.from_attributes( "MurnaghanInputBase", @@ -16,7 +20,7 @@ "node" ) -class MurnaghanInput(MurnaghanInputBase): +class MurnaghanInput(MurnaghanInputBase, ListInput): def check_ready(self): structure_ready = self.structure is not None strain_ready = len(self.strains) > 0 @@ -27,16 +31,40 @@ def check_ready(self): def set_strain_range(self, range, steps): self.strains = (1 + np.linspace(-range, range, steps))**(1/3) + def _create_nodes(self): + cell = self.structure.get_cell() + nodes = [] + for s in self.strains: + n = deepcopy(self.node) + n.input.structure = self.structure.copy() + n.input.structure.set_cell(cell * s, scale_atoms=True) + nodes.append(n) + return nodes + MurnaghanOutputBase = AbstractOutput.from_attributes( "MurnaghanOutputBase", + 'base_structure', volumes=list, energies=list ) -class MurnaghanOutput(MurnaghanOutputBase): +class MurnaghanOutput(MurnaghanOutputBase, HasStructure): def plot(self): plt.plot(self.volumes, self.energies) + @property + def equilibrium_volume(self): + inter = si.interp1d(self.volumes, self.energies) + return so.minimize_scalar(inter, bounds=(np.min(self.volumes), np.max(self.volumes))).x + + def _number_of_structures(self): + return 1 + + def _get_structure(self, frame, wrap_atoms=True): + s = self.base_structure + s.set_cell(s.get_cell() * (self.equilibrium_volume/s.get_volume())**(1/3)) + return s + class MurnaghanNode(ListNode): def _get_input(self): @@ -45,15 +73,9 @@ def _get_input(self): def _get_output(self): return MurnaghanOutput() - def _create_nodes(self): - cell = self.input.structure.get_cell() - nodes = [] - for s in self.input.strains: - n = deepcopy(self.input.node) - n.input.structure = self.input.structure.copy() - n.input.structure.set_cell(cell * s, scale_atoms=True) - nodes.append(n) - return nodes + def _execute(self): + self.output.base_structure = self.input.structure + return super()._execute() def _extract_output(self, i, node, ret): if ret.is_done(): From 9c043a32a97c09ef6744a64e0eb87863f4cd45da Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 6 Mar 2023 18:01:58 +0100 Subject: [PATCH 021/756] Remove output from node itself Only the executor now knows about the output. --- pyiron_contrib/tinybase/__init__.py | 46 +++++++++++-------------- pyiron_contrib/tinybase/ase.py | 31 ++++++++--------- pyiron_contrib/tinybase/container.py | 10 ++++++ pyiron_contrib/tinybase/executor.py | 50 +++++++++++++++++----------- pyiron_contrib/tinybase/murn.py | 21 ++++++++---- 5 files changed, 91 insertions(+), 67 deletions(-) diff --git a/pyiron_contrib/tinybase/__init__.py b/pyiron_contrib/tinybase/__init__.py index 1c6b66e99..bd130f52d 100644 --- a/pyiron_contrib/tinybase/__init__.py +++ b/pyiron_contrib/tinybase/__init__.py @@ -40,7 +40,7 @@ class AbstractNode(abc.ABC): } def __init__(self): - self._input, self._output = None, None + self._input = None @abc.abstractmethod def _get_input(self) -> AbstractInput: @@ -56,27 +56,19 @@ def input(self) -> AbstractInput: def _get_output(self) -> AbstractOutput: pass - @property - def output(self) -> AbstractOutput: - if self._output is None: - self._output = self._get_output() - return self._output - - def check_ready(self): - return True - @abc.abstractmethod - def _execute(self) -> Optional[ReturnStatus]: + def _execute(self, output) -> Optional[ReturnStatus]: pass def execute(self) -> ReturnStatus: + output = self._get_output() try: - ret = self._execute() + ret = self._execute(output) if ret is None: ret = ReturnStatus("done") except Exception as e: ret = ReturnStatus("aborted", msg=e) - return ret + return ret, output def run(self, how='foreground'): exe = self._executors[how](nodes=[self]) @@ -97,8 +89,8 @@ def _get_input(self): def _get_output(self): return FunctionOutput() - def _execute(self): - self.output.result = self._function(*self.input.args, **self.input.kwargs) + def _execute(self, output): + output.result = self._function(*self.input.args, **self.input.kwargs) MasterInput = AbstractInput.from_attributes( "MasterInput", @@ -117,17 +109,17 @@ def __init__(self): super().__init__() @abc.abstractmethod - def _extract_output(self, step, node, ret): + def _extract_output(self, output, step, node, ret, node_output): pass - def _execute(self): + def _execute(self, output): nodes = self.input._create_nodes() exe = self.input.child_executor(nodes) exe.run() exe.wait() - for i, (node, ret) in enumerate(zip(nodes, exe._run_machine._data["status"])): - self._extract_output(i, node, ret) + for i, (node, ret, node_output) in enumerate(zip(nodes, exe.status, exe.output)): + self._extract_output(output, i, node, ret, node_output) SeriesInputBase = AbstractInput.from_attributes( "SeriesInputBase", @@ -178,24 +170,26 @@ def _get_input(self): def _get_output(self): return self.input.nodes[-1]._get_output() - def _execute(self): + def _execute(self, output): Exe = self.input.child_executor - first = self.input.nodes[0] - exe = Exe([first]) + exe = Exe(self.input.nodes[:1]) exe.run() exe.wait() + ret = exe.status[0] + if not ret.is_done(): + return ReturnStatus("aborted", ret) - prev = first for node, connection in zip(self.input.nodes[1:], self.input.connections): - connection(node.input, prev.output) + connection(node.input, exe.output[0]) exe = Exe([node]) exe.run() exe.wait() - ret = exe._run_machine._data["status"][0] - if ret is not None and not ret.is_done(): + ret = exe.status[0] + if not ret.is_done(): return ReturnStatus("aborted", ret) + output.transfer(exe.output[0]) class TinyJob(abc.ABC): diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index 7decb4f4b..d4e9e426f 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -34,10 +34,10 @@ def _get_input(self): def _get_output(self): return EnergyOutput() - def _execute(self): + def _execute(self, output): structure = self.input.structure structure.calc = self.input.calculator - self.output.energy_pot = structure.get_potential_energy() + output.energy_pot = structure.get_potential_energy() class AseMDInput(AseInput, MDInput): @@ -51,7 +51,7 @@ def _get_input(self): def _get_output(self): return MDOutput() - def _execute(self): + def _execute(self, output): structure = self.input.structure.copy() structure.calc = self.input.calculator @@ -66,10 +66,10 @@ def _execute(self): ) def parse(): - self.output.structures.append(structure.copy()) - self.output.pot_energies.append(structure.get_potential_energy()) - self.output.kin_energies.append(structure.get_kinetic_energy()) - self.output.forces.append(structure.get_forces()) + output.structures.append(structure.copy()) + output.pot_energies.append(structure.get_potential_energy()) + output.kin_energies.append(structure.get_kinetic_energy()) + output.forces.append(structure.get_forces()) parse() dyn.attach(parse, interval=self.input.steps // self.input.output_steps) @@ -118,25 +118,26 @@ def _get_input(self): def _get_output(self): return MDOutput() - def _execute(self): + def _execute(self, output): structure = self.input.structure.copy() structure.calc = self.input.calculator opt = self.input.get_ase_optimizer(structure) def parse(): - self.output.structures.append(structure.copy()) - self.output.pot_energies.append(structure.get_potential_energy()) - self.output.kin_energies.append(structure.get_kinetic_energy()) - self.output.forces.append(structure.get_forces()) + output.structures.append(structure.copy()) + output.pot_energies.append(structure.get_potential_energy()) + output.kin_energies.append(structure.get_kinetic_energy()) + output.forces.append(structure.get_forces()) opt.attach(parse, interval=self.input.output_steps) opt.run(fmax=self.input.ionic_force_tolerance, steps=self.input.max_steps) parse() - max_force = abs(self.output.forces[-1]).max() - if max_force > self.input.ionic_force_tolerance: + max_force = abs(output.forces[-1]).max() + force_tolerance = self.input.ionic_force_tolerance + if max_force > force_tolerance: return ReturnStatus( "not_converged", - f"force in last step ({max_force}) is larger than tolerance ({self.input.ionic_force_tolerance})!" + f"force in last step ({max_force}) is larger than tolerance ({force_tolerance})!" ) diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index 1c405dda0..82fb26ebf 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -111,10 +111,20 @@ def from_attributes(cls, name, *attrs, module=None, **default_attrs): body.update({a: StorageAttribute().default(d) for a, d in default_attrs.items()}) T = type(name, (cls,), body) if module is None: + # this is also how cpython does it for namedtuple module = sys._getframe(1).f_globals['__name__'] T.__module__ = module return T + def transfer(self, other): + """ + Copy the contents of another + """ + if isinstance(self, other.__class__): + self.storage.update(other.storage) + else: + raise TypeError("Must pass a superclass to transfer from!") + class AbstractInput(AbstractContainer, abc.ABC): def check_ready(self): diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index a2a74d913..efb7cdc07 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -98,13 +98,13 @@ def _run_ready(self): self._run_machine.step("running") def _run_running(self): - ret = [node.execute() for node in self.nodes] - self._run_machine.step("collect", status=ret) + status, output = zip(*[node.execute() for node in self.nodes]) + self._run_machine.step("collect", status=status, output=output) def _run_collect(self): self._run_machine.step("finished", - status=self._run_machine._data["status"], - output=[node.output for node in self.nodes] + status=self.status, + output=self.output, ) def _run_finished(self): @@ -121,6 +121,14 @@ def wait(self, until="finished", sleep=0.1): while until != self._run_machine.state: time.sleep(sleep) + @property + def status(self): + return self._run_machine._data["status"] + + @property + def output(self): + return self._run_machine._data["output"] + from concurrent.futures import ( ThreadPoolExecutor, @@ -130,8 +138,7 @@ def wait(self, until="finished", sleep=0.1): from threading import Lock def run_node(node): - ret = node.execute() - return ret, node.output + return node.execute() class FuturesExecutor(Executor, abc.ABC): @@ -147,18 +154,17 @@ def __init__(self, nodes, max_tasks=None): self._max_tasks = max_tasks if max_tasks is not None else 4 self._done = 0 self._futures = {} - self._returns = {} + self._status = {} + self._output = {} + self._index = {} self._lock = Lock() - def _process_future(self, future=None): - for node, _f in self._futures.items(): - if _f == future: break - else: - assert False, "This should never happen!" + def _process_future(self, future): + node = self._futures[future] - ret, output = future.result(timeout=0) - node._output = output - self._returns[node] = ret + status, output = future.result(timeout=0) + self._status[node] = status + self._output[node] = output with self._lock: self._done += 1 self._check_finish() @@ -166,15 +172,21 @@ def _process_future(self, future=None): def _check_finish(self, log=False): with self._lock: if self._done == len(self.nodes): - # how to ensure ordering? dict remembers insertion order, so maybe ok for now - self._run_machine.step("collect", status=list(self._returns.values())) + status = [self._status[n] for n in sorted(self.nodes, key=lambda n: self._index[n])] + output = [self._output[n] for n in sorted(self.nodes, key=lambda n: self._index[n])] + self._run_machine.step("collect", + status=status, + output=output, + ) def _run_running(self): if len(self._futures) < len(self.nodes): pool = self._FuturePoolExecutor(max_workers=self._max_tasks) try: - for node in self.nodes: - future = self._futures[node] = pool.submit(run_node, node) + for i, node in enumerate(self.nodes): + future = pool.submit(run_node, node) + self._futures[future] = node + self._index[node] = i future.add_done_callback(self._process_future) finally: # with statement doesn't allow me to put wait=False, so I gotta do it here with try/finally. diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index 9fb403e8e..899cebbda 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -73,11 +73,18 @@ def _get_input(self): def _get_output(self): return MurnaghanOutput() - def _execute(self): - self.output.base_structure = self.input.structure - return super()._execute() - - def _extract_output(self, i, node, ret): + def _execute(self, output): + output.base_structure = self.input.structure + ret = super()._execute(output) + I = np.argsort(output.volumes) + output.volumes = output.volumes[I] + output.energies = output.energies[I] + + def _extract_output(self, output, i, node, ret, node_output): + if len(output.energies) == 0: + output.energies = np.zeros(len(self.input.strains)) + if len(output.volumes) == 0: + output.volumes = np.zeros(len(self.input.strains)) if ret.is_done(): - self.output.energies.append(node.output.energy_pot) - self.output.volumes.append(node.input.structure.get_volume()) + output.energies[i] = node_output.energy_pot + output.volumes[i] = node.input.structure.get_volume() From 48d2c0fd0ba3144742f51096556236a0b39ecea8 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 6 Mar 2023 18:02:35 +0100 Subject: [PATCH 022/756] Allow MDOutput to be used as a single point energy calculation --- pyiron_contrib/tinybase/container.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index 82fb26ebf..dd3d7cc64 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -157,7 +157,7 @@ class AbstractOutput(AbstractContainer, abc.ABC): structures=list, ) -class MDOutput(HasStructure, MDOutputBase): +class MDOutput(HasStructure, MDOutputBase, EnergyOutput): def plot_energies(self): plt.plot(self.pot_energies - np.min(self.pot_energies), label='pot') @@ -169,3 +169,8 @@ def _number_of_structures(self): def _get_structure(self, frame, wrap_atoms=True): return self.structures[frame] + + # TODO: how to make sure this is generally conforming? + @property + def energy_pot(self): + return self.pot_energies[-1] From 0c70e351368494b8142eb2a11cad3e50745e591c Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 6 Mar 2023 18:03:24 +0100 Subject: [PATCH 023/756] Set a default for minimizer algo --- pyiron_contrib/tinybase/ase.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index d4e9e426f..c61685b24 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -84,9 +84,7 @@ def parse(): class AseMinimizeInput(AseInput, StructureInput, MinimizeInput): - """My first experimental docstring""" algo = StorageAttribute().type(str).default('LBFGS') - """My second experimental docstring""" minimizer_kwargs = StorageAttribute().type(dict).default(dict) def lbfgs(self, damping=None, alpha=None): @@ -107,7 +105,7 @@ def get_ase_optimizer(self, structure): 'LBFGS': LBFGS, 'FIRE': FIRE, 'GPMIN': GPMin - }[self.algo](structure, **self.minimizer_kwargs) + }.get(self.algo, default='LBFGS')(structure, **self.minimizer_kwargs) class AseMinimizeNode(AbstractNode): From 5f8dd865f61f76ead68df5df9b3981e4124770d8 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Tue, 7 Mar 2023 13:14:08 +0100 Subject: [PATCH 024/756] Reset ASE minimizer kwargs and dont use wrong default syntax --- pyiron_contrib/tinybase/ase.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index c61685b24..7839045e0 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -96,16 +96,18 @@ def lbfgs(self, damping=None, alpha=None): def fire(self): self.algo = 'FIRE' + self.minimizer_kwargs = {} def gpmin(self): self.algo = 'GPMIN' + self.minimizer_kwargs = {} def get_ase_optimizer(self, structure): return { 'LBFGS': LBFGS, 'FIRE': FIRE, 'GPMIN': GPMin - }.get(self.algo, default='LBFGS')(structure, **self.minimizer_kwargs) + }.get(self.algo)(structure, **self.minimizer_kwargs) class AseMinimizeNode(AbstractNode): From 24b0f14c57d3c0f237b2d7485fb8a47015a57f51 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Tue, 7 Mar 2023 13:14:47 +0100 Subject: [PATCH 025/756] Allow to specify additional base classes in from_attributes --- pyiron_contrib/tinybase/container.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index dd3d7cc64..2f59aff75 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -95,7 +95,7 @@ def doc(self, text): class AbstractContainer(HasStorage, abc.ABC): # TODO: this should go into HasStorage, exists here only to give one location to define from_ methods @classmethod - def from_attributes(cls, name, *attrs, module=None, **default_attrs): + def from_attributes(cls, name, *attrs, module=None, bases=(), **default_attrs): """ Create a new sub class with given attributes. @@ -105,11 +105,12 @@ def from_attributes(cls, name, *attrs, module=None, **default_attrs): module (str, optional): the module path where this class is defined; on CPython this is automagically filled in, in other python implementations you need to manually provide this value as __name__ when you call this method for the resulting class to be picklable. + bases (list of type): additional base classes **default_attrs (str): names and defaults of new attributes """ body = {a: StorageAttribute() for a in attrs} body.update({a: StorageAttribute().default(d) for a, d in default_attrs.items()}) - T = type(name, (cls,), body) + T = type(name, bases + (cls,), body) if module is None: # this is also how cpython does it for namedtuple module = sys._getframe(1).f_globals['__name__'] From 5c3c7bda8c5e18e928c6b319871c2e5b358369cb Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Tue, 7 Mar 2023 13:15:19 +0100 Subject: [PATCH 026/756] Add a loop node --- pyiron_contrib/tinybase/__init__.py | 115 +++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/tinybase/__init__.py b/pyiron_contrib/tinybase/__init__.py index bd130f52d..fe118d0aa 100644 --- a/pyiron_contrib/tinybase/__init__.py +++ b/pyiron_contrib/tinybase/__init__.py @@ -1,10 +1,11 @@ import abc +from copy import deepcopy import enum -from typing import Optional +from typing import Optional, Callable from pyiron_base.interfaces.object import HasStorage -from .container import AbstractInput, AbstractOutput +from .container import AbstractInput, AbstractOutput, StorageAttribute from .executor import ( Executor, BackgroundExecutor, @@ -191,6 +192,116 @@ def _execute(self, output): output.transfer(exe.output[0]) +LoopInputBase = AbstractInput.from_attributes( + "LoopInput", + "control", + trace=bool, +) + +class LoopControl(HasStorage): + def __init__(self, condition, restart): + super().__init__() + self._condition = condition + self._restart = restart + + scratch = StorageAttribute().default(dict) + + def condition(self, node: AbstractNode, output: AbstractNode): + """ + Whether to terminate the loop or not. + + Args: + node (AbstractNode): the loop body + output (AbstractOutput): output of the loop body + + Args: + bool: True to terminate the loop; False to keep it running + """ + return self._condition(node, output, self.scratch) + + def restart(self, output: AbstractOutput, input: AbstractInput): + """ + Setup the input of the next loop body. + + Args: + output (AbstractOutput): output of the previous loop body + input (AbstractInput): input of the next loop body + """ + self._restart(output, input, self.scratch) + +class RepeatLoopControl(LoopControl): + def __init__(self, steps, restart=lambda *_: None): + super().__init__(condition=self._count_steps, restart=restart) + self._steps = steps + + def _count_steps(self, scratch={}): + c = scratch.get('counter', 0) + c += 1 + scratch['counter'] = c + return c >= steps + + +class LoopInput(LoopInputBase, MasterInput): + """ + Input for :class:`~.LoopNode`. + + Attributes: + node (:class:`~.AbstractNode`): the loop body + control (:class:`.LoopControl`): encapsulates control flow of the loop + """ + + def repeat(self, steps: int, restart: Optional[Callable[[AbstractOutput, AbstractInput, dict], None]] = None): + """ + Set up a loop control that loops for steps and calls restart in between. + + If restart is not given don't do anything to input of the loop body. + """ + if restart is not None: + self.control = RepeatLoopControl(steps, restart) + else: + self.control = RepeatLoopControl(steps) + + def control_with( + self, + condition: Callable[[AbstractNode, AbstractOutput, dict], bool], + restart: Callable[[AbstractOutput, AbstractInput, dict], None] + ): + """ + Set up a loop control that uses the callables for control flow. + + Args: + condition (function): takes the loop body, its output and a persistant dict + restart (function): takes the output of the last loop body, the input of the next one and a persistant dict + """ + self.control = LoopControl(condition, restart) + +class LoopNode(AbstractNode): + """ + Generic node to loop over a given input node. + """ + + def _get_input(self): + return LoopInput() + + def _get_output(self): + return self.input.node._get_output() + + def _execute(self, output): + node = deepcopy(self.input.node) + control = deepcopy(self.input.control) + scratch = {} + while True: + exe = self.input.child_executor([node]) + exe.run() + ret = exe.status[-1] + out = exe.output[-1] + if not ret.is_done(): + return ReturnStatus("aborted", ret) + if control.condition(node, out): + break + control.restart(out, node.input) + output.transfer(out) + class TinyJob(abc.ABC): From bf785d90eb07a6524ff39b0aa9d5b67ac2cf990f Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Tue, 7 Mar 2023 13:23:39 +0100 Subject: [PATCH 027/756] Move node definitions into seperate module --- pyiron_contrib/tinybase/__init__.py | 336 --------------------------- pyiron_contrib/tinybase/ase.py | 4 +- pyiron_contrib/tinybase/murn.py | 10 +- pyiron_contrib/tinybase/node.py | 337 ++++++++++++++++++++++++++++ 4 files changed, 346 insertions(+), 341 deletions(-) create mode 100644 pyiron_contrib/tinybase/node.py diff --git a/pyiron_contrib/tinybase/__init__.py b/pyiron_contrib/tinybase/__init__.py index fe118d0aa..e69de29bb 100644 --- a/pyiron_contrib/tinybase/__init__.py +++ b/pyiron_contrib/tinybase/__init__.py @@ -1,336 +0,0 @@ -import abc -from copy import deepcopy -import enum -from typing import Optional, Callable - -from pyiron_base.interfaces.object import HasStorage - -from .container import AbstractInput, AbstractOutput, StorageAttribute -from .executor import ( - Executor, - BackgroundExecutor, - ProcessExecutor -) - -class ReturnStatus: - - class Code(enum.Enum): - DONE = "done" - ABORTED = "aborted" - WARNING = "warning" - NOT_CONVERGED = "not_converged" - - def __init__(self, code, msg=None): - self.code = code if not isinstance(code, str) else ReturnStatus.Code(code) - self.msg = msg - - def __repr__(self): - return f"ReturnStatus({self.code}, {self.msg})" - def __str__(self): - return f"{self.code}({self.msg})" - - def is_done(self): - return self.code == self.Code.DONE - -class AbstractNode(abc.ABC): - - _executors = { - 'foreground': Executor, - 'background': BackgroundExecutor, - 'process': ProcessExecutor - } - - def __init__(self): - self._input = None - - @abc.abstractmethod - def _get_input(self) -> AbstractInput: - pass - - @property - def input(self) -> AbstractInput: - if self._input is None: - self._input = self._get_input() - return self._input - - @abc.abstractmethod - def _get_output(self) -> AbstractOutput: - pass - - @abc.abstractmethod - def _execute(self, output) -> Optional[ReturnStatus]: - pass - - def execute(self) -> ReturnStatus: - output = self._get_output() - try: - ret = self._execute(output) - if ret is None: - ret = ReturnStatus("done") - except Exception as e: - ret = ReturnStatus("aborted", msg=e) - return ret, output - - def run(self, how='foreground'): - exe = self._executors[how](nodes=[self]) - exe.run() - return exe - -FunctionInput = AbstractInput.from_attributes("FunctionInput", args=list, kwargs=dict) -FunctionOutput = AbstractOutput.from_attributes("FunctionOutput", "result") -class FunctionNode(AbstractNode): - - def __init__(self, function): - super().__init__() - self._function = function - - def _get_input(self): - return FunctionInput() - - def _get_output(self): - return FunctionOutput() - - def _execute(self, output): - output.result = self._function(*self.input.args, **self.input.kwargs) - -MasterInput = AbstractInput.from_attributes( - "MasterInput", - child_executor=lambda: Executor -) - -class ListInput(MasterInput, abc.ABC): - - @abc.abstractmethod - def _create_nodes(self): - pass - -class ListNode(AbstractNode, abc.ABC): - - def __init__(self): - super().__init__() - - @abc.abstractmethod - def _extract_output(self, output, step, node, ret, node_output): - pass - - def _execute(self, output): - nodes = self.input._create_nodes() - exe = self.input.child_executor(nodes) - exe.run() - exe.wait() - - for i, (node, ret, node_output) in enumerate(zip(nodes, exe.status, exe.output)): - self._extract_output(output, i, node, ret, node_output) - -SeriesInputBase = AbstractInput.from_attributes( - "SeriesInputBase", - nodes=list, - connections=list -) - -class SeriesInput(SeriesInputBase, MasterInput): - def check_ready(self): - return len(self.nodes) == len(connections) + 1 - - def first(self, node): - """ - Set initial node. - - Resets whole input - - Args: - node (AbstractNode): the first node to execute - - Returns: - self: the input object - """ - self.nodes = [node] - self.connections = [] - return self - - def then(self, next_node, connection): - """ - Add a new node and how to connect it to the previous node. - - Args: - next_node (:class:`~.AbstractNode`): next node to execute - connection (function): takes the input of next_node and the output of the previous node - - Returns: - self: the input object - """ - self.nodes.append(next_node) - self.connections.append(connection) - return self - -class SeriesNode(AbstractNode): - - def _get_input(self): - return SeriesInput() - - def _get_output(self): - return self.input.nodes[-1]._get_output() - - def _execute(self, output): - Exe = self.input.child_executor - - exe = Exe(self.input.nodes[:1]) - exe.run() - exe.wait() - ret = exe.status[0] - if not ret.is_done(): - return ReturnStatus("aborted", ret) - - for node, connection in zip(self.input.nodes[1:], self.input.connections): - connection(node.input, exe.output[0]) - exe = Exe([node]) - exe.run() - exe.wait() - ret = exe.status[0] - if not ret.is_done(): - return ReturnStatus("aborted", ret) - - output.transfer(exe.output[0]) - -LoopInputBase = AbstractInput.from_attributes( - "LoopInput", - "control", - trace=bool, -) - -class LoopControl(HasStorage): - def __init__(self, condition, restart): - super().__init__() - self._condition = condition - self._restart = restart - - scratch = StorageAttribute().default(dict) - - def condition(self, node: AbstractNode, output: AbstractNode): - """ - Whether to terminate the loop or not. - - Args: - node (AbstractNode): the loop body - output (AbstractOutput): output of the loop body - - Args: - bool: True to terminate the loop; False to keep it running - """ - return self._condition(node, output, self.scratch) - - def restart(self, output: AbstractOutput, input: AbstractInput): - """ - Setup the input of the next loop body. - - Args: - output (AbstractOutput): output of the previous loop body - input (AbstractInput): input of the next loop body - """ - self._restart(output, input, self.scratch) - -class RepeatLoopControl(LoopControl): - def __init__(self, steps, restart=lambda *_: None): - super().__init__(condition=self._count_steps, restart=restart) - self._steps = steps - - def _count_steps(self, scratch={}): - c = scratch.get('counter', 0) - c += 1 - scratch['counter'] = c - return c >= steps - - -class LoopInput(LoopInputBase, MasterInput): - """ - Input for :class:`~.LoopNode`. - - Attributes: - node (:class:`~.AbstractNode`): the loop body - control (:class:`.LoopControl`): encapsulates control flow of the loop - """ - - def repeat(self, steps: int, restart: Optional[Callable[[AbstractOutput, AbstractInput, dict], None]] = None): - """ - Set up a loop control that loops for steps and calls restart in between. - - If restart is not given don't do anything to input of the loop body. - """ - if restart is not None: - self.control = RepeatLoopControl(steps, restart) - else: - self.control = RepeatLoopControl(steps) - - def control_with( - self, - condition: Callable[[AbstractNode, AbstractOutput, dict], bool], - restart: Callable[[AbstractOutput, AbstractInput, dict], None] - ): - """ - Set up a loop control that uses the callables for control flow. - - Args: - condition (function): takes the loop body, its output and a persistant dict - restart (function): takes the output of the last loop body, the input of the next one and a persistant dict - """ - self.control = LoopControl(condition, restart) - -class LoopNode(AbstractNode): - """ - Generic node to loop over a given input node. - """ - - def _get_input(self): - return LoopInput() - - def _get_output(self): - return self.input.node._get_output() - - def _execute(self, output): - node = deepcopy(self.input.node) - control = deepcopy(self.input.control) - scratch = {} - while True: - exe = self.input.child_executor([node]) - exe.run() - ret = exe.status[-1] - out = exe.output[-1] - if not ret.is_done(): - return ReturnStatus("aborted", ret) - if control.condition(node, out): - break - control.restart(out, node.input) - output.transfer(out) - - -class TinyJob(abc.ABC): - - _executors = { - 'foreground': Executor, - 'background': BackgroundExecutor, - 'process': ProcessExecutor - } - - def __init__(self, project, job_name): - self._project = project - self._name = job_name - - @abc.abstractmethod - def _get_node(self): - pass - - @property - def input(self): - return self._node.input - - @property - def output(self): - return self._node.output - - def run(self, how='foreground'): - exe = self._executor = self._executors[how](nodes=[self._node]) - exe._run_machine.observe("ready", self.save_input) - exe._run_machine.observe("collect", self.save_output) - exe.run() - return exe - diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index 7839045e0..91cbe684d 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -1,4 +1,4 @@ -from .container import ( +from pyiron_contrib.tinybase.container import ( AbstractInput, StorageAttribute, StructureInput, @@ -6,7 +6,7 @@ EnergyOutput, MDOutput ) -from . import AbstractNode, ReturnStatus +from pyiron_contrib.tinybase.node import AbstractNode, ReturnStatus import numpy as np import matplotlib.pyplot as plt diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index 899cebbda..a1ff84a3d 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -1,9 +1,13 @@ -from .container import ( +from pyiron_contrib.tinybase.container import ( AbstractOutput, StructureInput, ) -from . import AbstractNode, ListNode, ListInput -from . import ReturnStatus +from pyiron_contrib.tinybase.node import ( + AbstractNode, + ListNode, + ListInput, + ReturnStatus +) from copy import deepcopy diff --git a/pyiron_contrib/tinybase/node.py b/pyiron_contrib/tinybase/node.py new file mode 100644 index 000000000..ddd9248e7 --- /dev/null +++ b/pyiron_contrib/tinybase/node.py @@ -0,0 +1,337 @@ +import abc +from copy import deepcopy +import enum +from typing import Optional, Callable + +from pyiron_base.interfaces.object import HasStorage + +from .container import AbstractInput, AbstractOutput, StorageAttribute +from .executor import ( + Executor, + BackgroundExecutor, + ProcessExecutor +) + +class ReturnStatus: + + class Code(enum.Enum): + DONE = "done" + ABORTED = "aborted" + WARNING = "warning" + NOT_CONVERGED = "not_converged" + + def __init__(self, code, msg=None): + self.code = code if not isinstance(code, str) else ReturnStatus.Code(code) + self.msg = msg + + def __repr__(self): + return f"ReturnStatus({self.code}, {self.msg})" + def __str__(self): + return f"{self.code}({self.msg})" + + def is_done(self): + return self.code == self.Code.DONE + +class AbstractNode(abc.ABC): + + _executors = { + 'foreground': Executor, + 'background': BackgroundExecutor, + 'process': ProcessExecutor + } + + def __init__(self): + self._input = None + + @abc.abstractmethod + def _get_input(self) -> AbstractInput: + pass + + @property + def input(self) -> AbstractInput: + if self._input is None: + self._input = self._get_input() + return self._input + + @abc.abstractmethod + def _get_output(self) -> AbstractOutput: + pass + + @abc.abstractmethod + def _execute(self, output) -> Optional[ReturnStatus]: + pass + + def execute(self) -> ReturnStatus: + output = self._get_output() + try: + ret = self._execute(output) + if ret is None: + ret = ReturnStatus("done") + except Exception as e: + ret = ReturnStatus("aborted", msg=e) + return ret, output + + def run(self, how='foreground'): + exe = self._executors[how](nodes=[self]) + exe.run() + return exe + +FunctionInput = AbstractInput.from_attributes("FunctionInput", args=list, kwargs=dict) +FunctionOutput = AbstractOutput.from_attributes("FunctionOutput", "result") +class FunctionNode(AbstractNode): + + def __init__(self, function): + super().__init__() + self._function = function + + def _get_input(self): + return FunctionInput() + + def _get_output(self): + return FunctionOutput() + + def _execute(self, output): + output.result = self._function(*self.input.args, **self.input.kwargs) + +MasterInput = AbstractInput.from_attributes( + "MasterInput", + child_executor=lambda: Executor +) + +class ListInput(MasterInput, abc.ABC): + + @abc.abstractmethod + def _create_nodes(self): + pass + +class ListNode(AbstractNode, abc.ABC): + + def __init__(self): + super().__init__() + + @abc.abstractmethod + def _extract_output(self, output, step, node, ret, node_output): + pass + + def _execute(self, output): + nodes = self.input._create_nodes() + exe = self.input.child_executor(nodes) + exe.run() + exe.wait() + + for i, (node, ret, node_output) in enumerate(zip(nodes, exe.status, exe.output)): + self._extract_output(output, i, node, ret, node_output) + +SeriesInputBase = AbstractInput.from_attributes( + "SeriesInputBase", + nodes=list, + connections=list +) + +class SeriesInput(SeriesInputBase, MasterInput): + def check_ready(self): + return len(self.nodes) == len(connections) + 1 + + def first(self, node): + """ + Set initial node. + + Resets whole input + + Args: + node (AbstractNode): the first node to execute + + Returns: + self: the input object + """ + self.nodes = [node] + self.connections = [] + return self + + def then(self, next_node, connection): + """ + Add a new node and how to connect it to the previous node. + + Args: + next_node (:class:`~.AbstractNode`): next node to execute + connection (function): takes the input of next_node and the output of the previous node + + Returns: + self: the input object + """ + self.nodes.append(next_node) + self.connections.append(connection) + return self + +class SeriesNode(AbstractNode): + + def _get_input(self): + return SeriesInput() + + def _get_output(self): + return self.input.nodes[-1]._get_output() + + def _execute(self, output): + Exe = self.input.child_executor + + exe = Exe(self.input.nodes[:1]) + exe.run() + exe.wait() + ret = exe.status[0] + if not ret.is_done(): + return ReturnStatus("aborted", ret) + + for node, connection in zip(self.input.nodes[1:], self.input.connections): + connection(node.input, exe.output[0]) + exe = Exe([node]) + exe.run() + exe.wait() + ret = exe.status[0] + if not ret.is_done(): + return ReturnStatus("aborted", ret) + + output.transfer(exe.output[0]) + +LoopInputBase = AbstractInput.from_attributes( + "LoopInput", + "control", + trace=bool, +) + +class LoopControl(HasStorage): + def __init__(self, condition, restart): + super().__init__() + self._condition = condition + self._restart = restart + + scratch = StorageAttribute().default(dict) + + def condition(self, node: AbstractNode, output: AbstractNode): + """ + Whether to terminate the loop or not. + + Args: + node (AbstractNode): the loop body + output (AbstractOutput): output of the loop body + + Args: + bool: True to terminate the loop; False to keep it running + """ + return self._condition(node, output, self.scratch) + + def restart(self, output: AbstractOutput, input: AbstractInput): + """ + Setup the input of the next loop body. + + Args: + output (AbstractOutput): output of the previous loop body + input (AbstractInput): input of the next loop body + """ + self._restart(output, input, self.scratch) + +class RepeatLoopControl(LoopControl): + def __init__(self, steps, restart=lambda *_: None): + super().__init__(condition=self._count_steps, restart=restart) + self._steps = steps + + def _count_steps(self, scratch={}): + c = scratch.get('counter', 0) + c += 1 + scratch['counter'] = c + return c >= steps + + +class LoopInput(LoopInputBase, MasterInput): + """ + Input for :class:`~.LoopNode`. + + Attributes: + node (:class:`~.AbstractNode`): the loop body + control (:class:`.LoopControl`): encapsulates control flow of the loop + """ + + def repeat(self, steps: int, restart: Optional[Callable[[AbstractOutput, AbstractInput, dict], None]] = None): + """ + Set up a loop control that loops for steps and calls restart in between. + + If restart is not given don't do anything to input of the loop body. + """ + if restart is not None: + self.control = RepeatLoopControl(steps, restart) + else: + self.control = RepeatLoopControl(steps) + + def control_with( + self, + condition: Callable[[AbstractNode, AbstractOutput, dict], bool], + restart: Callable[[AbstractOutput, AbstractInput, dict], None] + ): + """ + Set up a loop control that uses the callables for control flow. + + Args: + condition (function): takes the loop body, its output and a persistant dict + restart (function): takes the output of the last loop body, the input of the next one and a persistant dict + """ + self.control = LoopControl(condition, restart) + +class LoopNode(AbstractNode): + """ + Generic node to loop over a given input node. + """ + + def _get_input(self): + return LoopInput() + + def _get_output(self): + return self.input.node._get_output() + + def _execute(self, output): + node = deepcopy(self.input.node) + control = deepcopy(self.input.control) + scratch = {} + while True: + exe = self.input.child_executor([node]) + exe.run() + ret = exe.status[-1] + out = exe.output[-1] + if not ret.is_done(): + return ReturnStatus("aborted", ret) + if control.condition(node, out): + break + control.restart(out, node.input) + output.transfer(out) + + +class TinyJob(abc.ABC): + + _executors = { + 'foreground': Executor, + 'background': BackgroundExecutor, + 'process': ProcessExecutor + } + + def __init__(self, project, job_name): + self._project = project + self._name = job_name + + @abc.abstractmethod + def _get_node(self): + pass + + @property + def input(self): + return self._node.input + + @property + def output(self): + return self._node.output + + def run(self, how='foreground'): + exe = self._executor = self._executors[how](nodes=[self._node]) + exe._run_machine.observe("ready", self.save_input) + exe._run_machine.observe("collect", self.save_output) + exe.run() + return exe + + From a081750c0b0d2ef9da4251ea10b642c284ee7143 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Tue, 7 Mar 2023 14:33:09 +0100 Subject: [PATCH 028/756] Fix RepeatLoopControl --- pyiron_contrib/tinybase/node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/tinybase/node.py b/pyiron_contrib/tinybase/node.py index ddd9248e7..dcbdeb49d 100644 --- a/pyiron_contrib/tinybase/node.py +++ b/pyiron_contrib/tinybase/node.py @@ -234,11 +234,11 @@ def __init__(self, steps, restart=lambda *_: None): super().__init__(condition=self._count_steps, restart=restart) self._steps = steps - def _count_steps(self, scratch={}): + def _count_steps(self, output, input, scratch={}): c = scratch.get('counter', 0) c += 1 scratch['counter'] = c - return c >= steps + return c >= self._steps class LoopInput(LoopInputBase, MasterInput): From c20866b606da77de738a4fd17a3fa577e7cb554b Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Tue, 7 Mar 2023 14:49:36 +0100 Subject: [PATCH 029/756] Add basic notebook --- notebooks/tinybase/Basic.ipynb | 1169 ++++++++++++++++++++++++++++++++ 1 file changed, 1169 insertions(+) create mode 100644 notebooks/tinybase/Basic.ipynb diff --git a/notebooks/tinybase/Basic.ipynb b/notebooks/tinybase/Basic.ipynb new file mode 100644 index 000000000..1f54259e4 --- /dev/null +++ b/notebooks/tinybase/Basic.ipynb @@ -0,0 +1,1169 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ef2b1629-437d-45d8-b9d1-1cf3a6bf9d08", + "metadata": {}, + "source": [ + "# Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "fd11c1fd-6b5b-4739-ad10-9ebe47c0db49", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/ponder/science/phd/dev/pyiron_contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n", + " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d1c3ced555be42948758b8ee70dfe5a8", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from pyiron_contrib.tinybase.node import AbstractNode, FunctionNode, SeriesNode, LoopNode" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "95594ff4-2f77-49c2-b4a2-467268ecac00", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.executor import ProcessExecutor, BackgroundExecutor, Executor" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "88b1b600-28e0-4ad9-82d6-b2bd993efbda", + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "logging.getLogger().setLevel(20)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e3d8cf33-1f39-4ef9-b92c-2dfd43cf4dd3", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "id": "5a1c480f-545b-411b-9cfc-a50af282de29", + "metadata": {}, + "source": [ + "# Function Node" + ] + }, + { + "cell_type": "markdown", + "id": "756bef2c-7a16-40bb-8dee-9e9becf353f3", + "metadata": {}, + "source": [ + "## Basic" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "9f2f3102-d15c-470a-b38c-f8084c9535ec", + "metadata": {}, + "outputs": [], + "source": [ + "def calc_fib(n):\n", + " import time\n", + " n1 = n2 = 1\n", + " for i in range(n):\n", + " time.sleep(.1)\n", + " x = n1 + n2\n", + " n1 = n2\n", + " n2 = x\n", + " return x" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e125f49c-257b-4a24-bc81-83fe345d1dcf", + "metadata": {}, + "outputs": [], + "source": [ + "f = FunctionNode(calc_fib)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "324f3c10-385e-4577-b089-c305f8203ca5", + "metadata": {}, + "outputs": [ + { + "data": { + "application/json": [], + "text/html": [ + "
DataContainer([])
" + ], + "text/plain": [ + "DataContainer([])" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f.input.storage" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "6c1f5af7-f5e9-41d9-a849-bab0ebc7dd9f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f.input.args" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "e0afb76d-d1b7-4b42-925f-fb117d58025e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f.input.kwargs" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6a5c3235-9c6b-481f-b316-db7420d1ad43", + "metadata": {}, + "outputs": [], + "source": [ + "f.input.kwargs['n'] = 10" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "4ade8d6a-6ce2-4f3a-b43d-71e1f87125bf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'n': 10}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f.input.kwargs" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "a39cfb50-4ed6-49ab-8ffb-af236cf61153", + "metadata": {}, + "outputs": [], + "source": [ + "exe = f.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "aa095e57-3d3c-4af5-9a94-eda6af34e2b3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'status': (ReturnStatus(Code.DONE, None),),\n", + " 'output': (,)}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_machine._data" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "82d0ca07-12c2-4cd9-bba3-1f210a907b41", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "144" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].result" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "24994f4a-d5cd-4aad-857f-a33ddd0eaf23", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1.0013468409888446, 2.5904009817168117e-05)" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_time, exe._collect_time" + ] + }, + { + "cell_type": "markdown", + "id": "8a3b6481-3605-44d3-8061-cb00c9fbcd34", + "metadata": {}, + "source": [ + "## Do the same but in the background" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "1e1b986e-9e00-41f2-86c2-945ff7818580", + "metadata": {}, + "outputs": [], + "source": [ + "f = FunctionNode(calc_fib)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "0b612150-f654-4995-8910-e46e766fdce2", + "metadata": {}, + "outputs": [], + "source": [ + "f.input.kwargs['n'] = 100" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "bac98046-09c1-457c-881a-e31f03267788", + "metadata": {}, + "outputs": [], + "source": [ + "exe = f.run(how='background')" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "ba09ae22-d2b4-41ba-8637-cb7f0fb3bfe9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{}" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_machine._data" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "0d2f427a-21e1-449e-a8cc-c2296bff6c10", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_machine.state" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "a9631d5e-d46a-419c-a929-68ddd77487bb", + "metadata": {}, + "outputs": [], + "source": [ + "exe.wait()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "408ffab0-70a1-4d08-9007-4d9f0513935d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "927372692193078999176" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].result" + ] + }, + { + "cell_type": "markdown", + "id": "c255f51a-950f-4e2e-a9b9-feb2f64f3ac5", + "metadata": {}, + "source": [ + "## Do the same but in the background as process" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "1e2178c0-ee70-4ec0-8b4b-2c7c55873b43", + "metadata": {}, + "outputs": [], + "source": [ + "fib_node = FunctionNode(calc_fib)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "998c4724-c7ab-4fc0-8794-8b60da819090", + "metadata": {}, + "outputs": [], + "source": [ + "fib_node.input.kwargs['n'] = 100" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "7b66b215-33cd-425c-bb9b-62e3eaa0451e", + "metadata": {}, + "outputs": [], + "source": [ + "exe = fib_node.run(how='process')" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "fcdcbe0b-f44a-4c37-acd8-10eedf5b3aa2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{}" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_machine._data" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "5354db31-169d-4c7b-a8cc-6ddc3358b4c1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_machine.state" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "282a6ae8-e869-4ae2-bfea-8644ba693866", + "metadata": {}, + "outputs": [], + "source": [ + "exe.wait()" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "fd24c4c8-6b17-433f-ac28-a490911a3628", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "927372692193078999176" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].result" + ] + }, + { + "cell_type": "markdown", + "id": "85ec26e2-db1f-4858-a3ab-b7955e85e572", + "metadata": {}, + "source": [ + "# Executors handle single nodes and lists of them on the same footing" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "e2fed9f1-590b-4ab5-9922-a126444e6169", + "metadata": {}, + "outputs": [], + "source": [ + "nodes = [FunctionNode(calc_fib) for _ in range(10)]" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "fdfc8943-8c0b-4bc6-98f0-71a64b3fae27", + "metadata": {}, + "outputs": [], + "source": [ + "for i, n in enumerate(nodes):\n", + " n.input.kwargs['n'] = 3 + i" + ] + }, + { + "cell_type": "markdown", + "id": "cdb9c07d-a153-4e0c-926e-96702b64cbd3", + "metadata": {}, + "source": [ + "## With the basic executor" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "dd709cfa-775f-41c1-a015-7e0647ec3d27", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "exe = Executor(nodes)\n", + "exe.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "0ac1b35a-b130-4330-bf20-a1222bdc6103", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " )" + ] + }, + "execution_count": 74, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "1ef8d9d6-e5dc-4db1-9e20-7181321f07ce", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "55" + ] + }, + "execution_count": 75, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[5].result" + ] + }, + { + "cell_type": "markdown", + "id": "4106ad2f-ece3-41d6-bed1-b340e434bec1", + "metadata": {}, + "source": [ + "## With the process executor" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "25fe617c-ae8e-4b83-bf58-b790441a1126", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "exe = ProcessExecutor(nodes)\n", + "exe.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "19e5d3e8-6779-4c36-a636-2d8cd549e99c", + "metadata": {}, + "outputs": [], + "source": [ + "exe.wait()" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "66feb98b-3f99-4bfb-9bb5-cccaf26d009b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[ReturnStatus(Code.DONE, None),\n", + " ReturnStatus(Code.DONE, None),\n", + " ReturnStatus(Code.DONE, None),\n", + " ReturnStatus(Code.DONE, None),\n", + " ReturnStatus(Code.DONE, None),\n", + " ReturnStatus(Code.DONE, None),\n", + " ReturnStatus(Code.DONE, None),\n", + " ReturnStatus(Code.DONE, None),\n", + " ReturnStatus(Code.DONE, None),\n", + " ReturnStatus(Code.DONE, None)]" + ] + }, + "execution_count": 90, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.status" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "fbb40611-9f53-479e-854c-82c8c99a8070", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ]" + ] + }, + "execution_count": 91, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output" + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "250f9c2d-5c71-4ddb-a94e-fd42f42cbeff", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "55" + ] + }, + "execution_count": 92, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[5].result" + ] + }, + { + "cell_type": "markdown", + "id": "3fc133cd-e685-499e-b139-82fc2678652a", + "metadata": {}, + "source": [ + "# SeriesNode" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "3dba0814-6a50-41f9-a78f-040014fdc140", + "metadata": {}, + "outputs": [], + "source": [ + "s = SeriesNode()" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "52aae339-ebad-4621-b2e0-c55d4fea3d1b", + "metadata": {}, + "outputs": [], + "source": [ + "f1 = FunctionNode(calc_fib)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "e10f7ee9-98db-48c7-affd-465c2011f7b1", + "metadata": {}, + "outputs": [], + "source": [ + "f2 = FunctionNode(np.sqrt)" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "b7e58b55-b4f5-4e2a-aef5-f4e080e4d50c", + "metadata": {}, + "outputs": [], + "source": [ + "def transfer(input, output):\n", + " input.args = [output.result]" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "b4b2212a-64df-4284-834d-8836c9a59b70", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s.input.first(f1).then(f2, transfer)" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "af337125-c4fe-497d-9374-b2d9301abe08", + "metadata": {}, + "outputs": [], + "source": [ + "s.input.nodes[0].input.kwargs['n'] = 10" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "810a17bb-9f5d-4c50-9665-fa2f93070d60", + "metadata": {}, + "outputs": [], + "source": [ + "status, output = s.execute()" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "4af47287-ab42-4cb4-8e65-c6efb7982ab4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ReturnStatus(Code.DONE, None)" + ] + }, + "execution_count": 66, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "status" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "705637d8-8da7-4429-ae6f-5401fc15cc9e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "12.0" + ] + }, + "execution_count": 67, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "output.result" + ] + }, + { + "cell_type": "markdown", + "id": "fc672a15-6943-410e-91b2-7dfac8326948", + "metadata": {}, + "source": [ + "# Loop Node" + ] + }, + { + "cell_type": "markdown", + "id": "adbced5b-dfa6-408e-ae01-e0a341c217e6", + "metadata": {}, + "source": [ + "## Simple repeat loop" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "b9807c98-6df8-450f-a8dd-1a53cb4ded35", + "metadata": {}, + "outputs": [], + "source": [ + "l = LoopNode()" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "ac2b9aa8-c118-4a1a-bf8b-96d6853b9be6", + "metadata": {}, + "outputs": [], + "source": [ + "l.input.node = FunctionNode(lambda: np.random.rand())" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "ef092015-5756-409a-bd1a-a31793c0b2b8", + "metadata": {}, + "outputs": [], + "source": [ + "l.input.repeat(10, restart=lambda output, input, scratch: print(output.result))" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "91a3d26f-d1fc-44a9-b06d-a9c452dfb3db", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.06831008568469543\n", + "0.4397644993497386\n", + "0.4710237017187203\n", + "0.6351871327200411\n", + "0.09403372149094191\n", + "0.45571674954951835\n", + "0.8693040125694965\n", + "0.03592129945541278\n", + "0.2842080026440601\n" + ] + } + ], + "source": [ + "exe = l.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "dbc8730e-9ebc-403b-9987-0de04e1f77f3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None),)" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.status" + ] + }, + { + "cell_type": "markdown", + "id": "1be0a463-f003-4a43-80a3-3e70df03a0bc", + "metadata": {}, + "source": [ + "## Loop with a termination condition" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "6c251bfa-e8cf-4e1a-990d-451ebb53f713", + "metadata": {}, + "outputs": [], + "source": [ + "l = LoopNode()" + ] + }, + { + "cell_type": "code", + "execution_count": 94, + "id": "563c7fe1-b96f-463c-8903-50f054c831f6", + "metadata": {}, + "outputs": [], + "source": [ + "l.input.node = FunctionNode(lambda: np.random.rand())" + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "id": "10130bfd-636f-4771-b30b-4648a8822f04", + "metadata": {}, + "outputs": [], + "source": [ + "l.input.control_with(\n", + " condition=lambda node, output, scratch: output.result < .05,\n", + " restart=lambda output, input, scratch: print(output.result)\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "f875b6c9-8cd1-4e6b-9ec8-16b93b6e7f64", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.6180549529309188\n", + "0.9433407283326789\n", + "0.6650129766719227\n", + "0.9246096624581522\n", + "0.702924921444492\n", + "0.4932765584360923\n", + "0.10209510690867707\n", + "0.19723819666451714\n", + "0.6319420933414326\n", + "0.9376161926340415\n", + "0.1911762555082791\n", + "0.7812203005244642\n", + "0.36658807729956766\n", + "0.997651587491596\n", + "0.17214861003243775\n", + "0.49700246072622345\n", + "0.8929166329882523\n", + "0.9069634041837235\n", + "0.928329630027329\n", + "0.14530372536131697\n", + "0.4551759858923593\n", + "0.8299354186855429\n", + "0.9971370925238271\n", + "0.3922295916439884\n", + "0.43629137886178726\n", + "0.13481396015844416\n", + "0.06396401175605293\n", + "0.0502648932556814\n", + "0.0919464823724655\n", + "0.2478196375875663\n", + "0.5547919839305524\n", + "0.9950273201349219\n", + "0.7490433592510488\n", + "0.5708404460188841\n", + "0.2800227217981094\n", + "0.452859610657651\n", + "0.5086825431878267\n", + "0.7486390124589416\n", + "0.34312007912192777\n", + "0.771168396478236\n", + "0.4539288607160801\n", + "0.7642828950901653\n", + "0.9944398067831015\n", + "0.8876987515750713\n", + "0.7498600155938839\n", + "0.8124747754930199\n", + "0.9020421405237081\n", + "0.40694715335295206\n", + "0.6880129743298647\n", + "0.8457057679143185\n", + "0.10612064010204925\n", + "0.4658543363818123\n", + "0.35949607240217285\n", + "0.9031175105972618\n", + "0.651652451274804\n", + "0.40381401433722386\n", + "0.6465594430809206\n", + "0.1129458759346127\n", + "0.07455862107161915\n", + "0.7246939877012769\n", + "0.6406247398029579\n", + "0.3875703065028444\n", + "0.6329595311691336\n", + "0.16772887766889388\n", + "0.4353447524968901\n", + "0.8271185273102784\n", + "0.5888175907051821\n", + "0.7213444488699345\n" + ] + } + ], + "source": [ + "exe = l.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "id": "8df83822-0bbd-4157-8bb2-f6e93433eefc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ReturnStatus(Code.DONE, None)" + ] + }, + "execution_count": 101, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.status[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "id": "815ba264-9bdb-4758-ab20-92e3650bdbae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.022068342941342967" + ] + }, + "execution_count": 102, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].result" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.0" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": { + "a0f7f042a295465588d884090818b8a6": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "d1c3ced555be42948758b8ee70dfe5a8": { + "model_module": "nglview-js-widgets", + "model_module_version": "3.0.1", + "model_name": "ColormakerRegistryModel", + "state": { + "_msg_ar": [], + "_msg_q": [], + "_ready": true, + "layout": "IPY_MODEL_a0f7f042a295465588d884090818b8a6" + } + } + }, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From f96c34cc41a0e1e37a7005d0c38aaa1a0eea5104 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Tue, 7 Mar 2023 14:54:16 +0100 Subject: [PATCH 030/756] Add ASE notebook --- notebooks/tinybase/ASE.ipynb | 5115 ++++++++++++++++++++++++++++++++++ 1 file changed, 5115 insertions(+) create mode 100644 notebooks/tinybase/ASE.ipynb diff --git a/notebooks/tinybase/ASE.ipynb b/notebooks/tinybase/ASE.ipynb new file mode 100644 index 000000000..385ba4f3c --- /dev/null +++ b/notebooks/tinybase/ASE.ipynb @@ -0,0 +1,5115 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "32de9950-ae26-488d-92af-9784ab1c7598", + "metadata": {}, + "source": [ + "# Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "f9ecb713-99e6-4f34-b4c2-fe9f3d96c81e", + "metadata": {}, + "outputs": [], + "source": [ + "from ase import Atoms" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "31dc7658-dcf0-41a4-9c62-ec92b02e47ea", + "metadata": {}, + "outputs": [], + "source": [ + "from ase.build import bulk" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "e7cc88a5-8492-482e-8c6b-c17ff967fff5", + "metadata": {}, + "outputs": [], + "source": [ + "from ase.calculators.morse import MorsePotential" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "b3108213-1d94-4354-9537-84982e45683d", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.ase import AseStaticNode, AseMDNode, AseMinimizeNode" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "0029125a-55e6-4181-a59b-09f606a1b4dd", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.murn import MurnaghanNode" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "c7c74920-c6b3-4577-a60f-951a0d3276ec", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.executor import ProcessExecutor, BackgroundExecutor, Executor" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "c6630920-6ab7-4273-883e-999020b1fe5a", + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "logging.getLogger().setLevel(20)" + ] + }, + { + "cell_type": "markdown", + "id": "4d6f4eab-7660-4e66-b85b-8d9969512c00", + "metadata": {}, + "source": [ + "# Simple ASE Static Calculation" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "a6af72cb-989b-46c3-a2b5-4d2b9c5fd1eb", + "metadata": {}, + "outputs": [], + "source": [ + "a = AseStaticNode()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "5b2a9d62-3f74-4acf-acb6-e72dcd984704", + "metadata": {}, + "outputs": [], + "source": [ + "a.input.structure = bulk(\"Fe\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "1af70322-897e-487d-ba18-239ba5bfb7ba", + "metadata": {}, + "outputs": [], + "source": [ + "a.input.calculator = MorsePotential()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "273902ef-03f3-4f68-8668-4e6c6055a302", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None),\n", + " )" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ret, output = a.execute(); ret, output" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "497de0b9-5e11-4d6c-8c19-664d0e759ac4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-0.00013307075712109978" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "output.energy_pot" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "57eced2f-6649-4269-b3fa-6061d518f9ee", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-0.00013307075712109978" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe = a.run()\n", + "exe.output[0].energy_pot" + ] + }, + { + "cell_type": "markdown", + "id": "ca6ebf4d-f7a7-4bc9-8a45-f544df3d7989", + "metadata": { + "tags": [] + }, + "source": [ + "# Murnaghan" + ] + }, + { + "cell_type": "markdown", + "id": "bf7ed983-d810-4193-ac32-b7b50a3f2145", + "metadata": {}, + "source": [ + "## Basic" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "ec7e75f1-56b9-4681-902f-65c107e17721", + "metadata": {}, + "outputs": [], + "source": [ + "m = MurnaghanNode()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "992f6dc3-1a6d-40f3-8429-4b4ce2672fdc", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.node = AseStaticNode()\n", + "m.input.node.input.calculator = MorsePotential()\n", + "m.input.structure = bulk(\"Fe\", a=1.2)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "cdce8eba-afba-4dcd-923f-717568ccfbb3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.input.node.input" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "8faf6257-e776-4257-8abf-2893af06ff2a", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.set_strain_range(.5, 50)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "d66e2c31-8341-4771-8ed1-fb679b0a8040", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0.79370053, 0.8043555 , 0.81473542, 0.82485739, 0.83473686,\n", + " 0.84438786, 0.85382314, 0.86305437, 0.87209225, 0.88094658,\n", + " 0.88962642, 0.89814011, 0.90649538, 0.9146994 , 0.92275884,\n", + " 0.93067991, 0.93846839, 0.94612969, 0.95366889, 0.96109074,\n", + " 0.96839969, 0.97559996, 0.98269548, 0.98968999, 0.996587 ,\n", + " 1.00338986, 1.01010169, 1.0167255 , 1.02326411, 1.0297202 ,\n", + " 1.03609634, 1.04239496, 1.04861836, 1.05476875, 1.06084824,\n", + " 1.06685884, 1.07280247, 1.07868096, 1.08449606, 1.09024946,\n", + " 1.09594278, 1.10157754, 1.10715524, 1.11267731, 1.1181451 ,\n", + " 1.12355993, 1.12892306, 1.13423572, 1.13949907, 1.14471424])" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.input.strains" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "33062219-bbe5-4fa0-88a6-7969e41c30b1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.input.check_ready()" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "297455da-542c-4117-9bde-8605d0cc3e80", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "exe = m.run(how='foreground')" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "d593a930-d19d-45be-9515-e120a71ee588", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None),)" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.status" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "131f8dd3-c1b4-4671-9e0a-84729e174483", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiIAAAGdCAYAAAAvwBgXAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy88F64QAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA5kElEQVR4nO3dd3ic1Z328XvURr13aaziJsvGTS64Y2MbA4FAQkscWkwSL5As4d0kkOxLyWbX+24I2TQIScCQBIzpJqHZFHfc5Sp3SVazeu/tef+QNbZcJVszz0jz/VyXrqBnZjQ/5bGl2+f8zjkWwzAMAQAAmMDD7AIAAID7IogAAADTEEQAAIBpCCIAAMA0BBEAAGAagggAADANQQQAAJiGIAIAAEzjZXYBF9PZ2amioiIFBQXJYrGYXQ4AAOgFwzBUV1en+Ph4eXhcfMzDpYNIUVGRbDab2WUAAIDLkJ+fr8TExIs+x6WDSFBQkKSubyQ4ONjkagAAQG/U1tbKZrPZf49fjEsHke7pmODgYIIIAAADTG/aKmhWBQAApiGIAAAA0xBEAACAaQgiAADANAQRAABgGoIIAAAwDUEEAACYhiACAABMQxABAACmIYgAAADTEEQAAIBpCCIAAMA0Ln3onaMcKanT2zsLFBbgo6VzhppdDgAAbsstR0SKqpv0wvpsvZdZaHYpAAC4NbcMIolh/pKkwqomGYZhcjUAALgvNw0ifpKkupZ21Ta1m1wNAADuyy2DiK+3pyIDfSRJ+VWNJlcDAID7cssgIkkJp6ZnCqqaTK4EAAD35bZBpHt6prCaIAIAgFncPogUMDUDAIBp3DiIMDUDAIDZ3DiIdI+IEEQAADCL+waRUKZmAAAwm9sGkYTuvUSa21XT1GZyNQAAuCe3DSL+Pl6KCOjaS6SQ6RkAAEzhtkFEYuUMAABmc/MgwsoZAADM5OZBhJUzAACYya2DSAJTMwAAmMqtgwgjIgAAmMvNg0hXjwjnzQAAYA63DiIJpzY1q2lqU20ze4kAAOBsbh1EAqxeCmcvEQAATOO0ILJs2TJZLBY98sgjznrLXkkIpU8EAACzOCWIbN++XX/60580duxYZ7xdn7CpGQAA5nF4EKmvr9fixYv15z//WWFhYY5+uz5j5QwAAOZxeBB56KGHdOONN2r+/PmXfG5LS4tqa2t7fDiafeUMQQQAAKfzcuQXf/3117Vr1y5t3769V89ftmyZnn76aUeWdA77iEg1UzMAADibw0ZE8vPz9a//+q/6+9//Ll9f31695vHHH1dNTY39Iz8/31Hl2XHeDAAA5nHYiMjOnTtVWlqqjIwM+7WOjg6tX79ev//979XS0iJPT88er7FarbJarY4q6by6t3mvbmxTXXObgny9nfr+AAC4M4cFkWuvvVb79u3rce3+++9XWlqafvKTn5wTQswSaPVSqL+3qhvbVFjdpLRYgggAAM7isCASFBSkMWPG9LgWEBCgiIiIc66bLTHMT9WNbSqobFJabLDZ5QAA4DbcemfVbomhnDkDAIAZHLpq5mxr16515tv1GpuaAQBgDkZExKZmAACYhSAiKYElvAAAmIIgIqZmAAAwC0FEp/cSqWpsU0NLu8nVAADgPggikoJ9vRXi17V/CCtnAABwHoLIKUzPAADgfASRU1g5AwCA8xFETkkIZeUMAADORhA5hakZAACcjyBySncQKWREBAAApyGInJLIpmYAADgdQeSU7r1EKhpa1djKXiIAADgDQeSUED9vBft2nQHI9AwAAM5BEDkDZ84AAOBcBJEzsHIGAADnIoicwR5E2OYdAACnIIicgZUzAAA4F0HkDGzzDgCAcxFEznB6UzN6RAAAcAaCyBkST503U17fqqbWDpOrAQBg8COInCHYz0tB1lN7iVQzKgIAgKMRRM5gsVjsO6zSJwIAgOMRRM7CyhkAAJyHIHIWVs4AAOA8BJGzsLsqAADOQxA5CyMiAAA4D0HkLPSIAADgPASRs3SPiJTXt6i5jb1EAABwJILIWUL8vBVo30uEUREAAByJIHIWi8VCnwgAAE5CEDkPVs4AAOAcBJHzSAhlRAQAAGcgiJwHK2cAAHAOgsh5dE/NFDI1AwCAQxFEzoMREQAAnIMgch7dIyKldewlAgCAIxFEziPU31sBPp6SpCL2EgEAwGEIIudhsViUwF4iAAA4HEHkAugTAQDA8QgiF2BfOVPNyhkAAByFIHIBbPMOAIDjEUQugKkZAAAcjyByAae3eWdqBgAARyGIXED31ExJLXuJAADgKASRCwgP8FGIn7ckKae8weRqAAAYnAgiF2CxWDQ0KkCSlF1GEAEAwBEIIheRGhUoSTpeVm9yJQAADE4EkYsYShABAMChHBpEli1bpsmTJysoKEjR0dG65ZZbdPjwYUe+Zb/qnpohiAAA4BgODSLr1q3TQw89pC1btmjNmjVqb2/XwoUL1dAwMHouuqdmsssaZBiGydUAADD4eDnyi3/88cc9Pl++fLmio6O1c+dOzZ4925Fv3S+SIvzl5WFRY2uHimubFRfiZ3ZJAAAMKk7tEampqZEkhYeHn/fxlpYW1dbW9vgwk7enh4ZEdO2werx0YIziAAAwkDgtiBiGoUcffVQzZ87UmDFjzvucZcuWKSQkxP5hs9mcVd4FpUaemp4pp08EAID+5rQg8vDDD2vv3r1asWLFBZ/z+OOPq6amxv6Rn5/vrPIuaGj0qYbVUoIIAAD9zaE9It2+//3v6/3339f69euVmJh4wedZrVZZrVZnlNRrp5fwMjUDAEB/c2gQMQxD3//+9/Xuu+9q7dq1SklJceTbOcTp3VUZEQEAoL85NIg89NBDeu2117Rq1SoFBQWpuLhYkhQSEiI/v4GxAqW7R6SoplkNLe0KsDplEAkAALfg0B6R559/XjU1NbrmmmsUFxdn/1i5cqUj37ZfhQX4KCLARxKH3wEA0N8cPjUzGAyNClRFQ6WOl9VrTEKI2eUAADBocNZML6Tat3pnRAQAgP5EEOkFDr8DAMAxCCK9wF4iAAA4BkGkF7pXzuSUN6izc3D0vQAA4AoIIr2QGOYnH08PtbR3qrC6yexyAAAYNAgiveDl6aHkyFOH39EnAgBAvyGI9JL98DtWzgAA0G8IIr1kb1hlRAQAgH5DEOkllvACAND/CCK9lMopvAAA9DuCSC91765aVtei2uY2k6sBAGBwIIj0UrCvt6KDrJJoWAUAoL8QRPrAfuYMO6wCANAvCCJ90N2wml1OEAEAoD8QRPrAvnKmlKkZAAD6A0GkD4ZGs4QXAID+RBDpg9TIrh6RExWNau/oNLkaAAAGPoJIHySE+snq5aHWjk4VVHH4HQAAV4og0gceHpYzNjZjegYAgCtFEOmj7iW87CUCAMCVI4j0EWfOAADQfwgifTQ0ilN4AQDoLwSRPhrK4XcAAPQbgkgfpZxawlvZ0KqqhlaTqwEAYGAjiPRRgNVL8SG+ktjqHQCAK0UQuQypbPUOAEC/IIhcBnvDKiMiAABcEYLIZbCfOcOICAAAV4QgchlSI7uCSDZLeAEAuCIEkcswNLpraiavslFtHH4HAMBlI4hchthgX/n7eKq909CJikazywEAYMAiiFwGi8ViP3OGHVYBALh8BJHLxJkzAABcOYLIZeoOIpzCCwDA5SOIXCZGRAAAuHIEkctk7xEprZdhGCZXAwDAwEQQuUwpkQGyWKTa5nZVcPgdAACXhSBymXy9PZUY5iepa1QEAAD0HUHkCnTvsHqchlUAAC4LQeQKnF45w4gIAACXgyByBbq3emflDAAAl4cgcgWYmgEA4MoQRK5A94hIflWjmts6TK4GAICBhyByBaICrQry9ZJhSLkVjIoAANBXBJErYLFYNDImSJJ08GStydUAADDwEESu0JiEEEnS/kKCCAAAfUUQuULp8cGSpANFNSZXAgDAwEMQuUJj4rtGRA4U1qqzkzNnAADoC6cEkeeee04pKSny9fVVRkaGNmzY4Iy3dYrhMYHy8fRQXUu78qsazS4HAIABxeFBZOXKlXrkkUf0s5/9TJmZmZo1a5auv/565eXlOfqtncLb00MjY7saVg8U0ScCAEBfODyIPPvss1qyZIkeeOABjRo1Sv/7v/8rm82m559/3tFv7TRjErr6RPYX0icCAEBfODSItLa2aufOnVq4cGGP6wsXLtTmzZvPeX5LS4tqa2t7fAwE6d19IoyIAADQJw4NIuXl5ero6FBMTEyP6zExMSouLj7n+cuWLVNISIj9w2azObK8fjPmjJUzhkHDKgAAveWUZlWLxdLjc8MwzrkmSY8//rhqamrsH/n5+c4o74qlxQbLwyKV17eqtK7F7HIAABgwvBz5xSMjI+Xp6XnO6Edpaek5oySSZLVaZbVaHVmSQ/j5eGpYdKCOlNRrf2GNYoJ9zS4JAIABwaEjIj4+PsrIyNCaNWt6XF+zZo2mT5/uyLd2uu79RNhhFQCA3nP41Myjjz6qv/zlL3rppZd08OBB/fCHP1ReXp6WLl3q6Ld2KnZYBQCg7xw6NSNJd955pyoqKvTzn/9cJ0+e1JgxY/Thhx8qKSnJ0W/tVN1nzrByBgCA3nN4EJGkBx98UA8++KAz3so03SMihdVNqmpoVViAj8kVAQDg+jhrpp8E+3orKcJfEqMiAAD0FkGkH42mTwQAgD4hiPSj0d0rZxgRAQCgVwgi/eh0wyojIgAA9AZBpB91T83klDeovqXd5GoAAHB9BJF+FBloVWywrwxDOniS6RkAAC6FINLP7A2rhUzPAABwKQSRfjY6gYZVAAB6iyDSz04v4SWIAABwKQSRfta9cuZoSZ1a2jtMrgYAANdGEOln8SG+CvP3VnunoSPF9WaXAwCASyOI9DOLxXLGxmY0rAIAcDEEEQcYncBW7wAA9AZBxAHsIyKFNKwCAHAxBBEHGHNq5czBk7Vq7+g0uRoAAFwXQcQBkiMCFODjqZb2TmWXN5hdDgAALosg4gAeHhalnxoV2c8OqwAAXBBBxEG6+0TY2AwAgAsjiDjIaEZEAAC4JIKIg3TvsJpVVKvOTsPkagAAcE0EEQcZFh0oHy8P1bW0K7+q0exyAABwSQQRB/H29FBabJAk9hMBAOBCCCIOdLphlT4RAADOhyDiQPaGVVbOAABwXgQRB+puWD1QWCPDoGEVAICzEUQcKC02SJ4eFlU0tKqktsXscgAAcDkEEQfy9fbUsKhASfSJAABwPgQRBxud0L2xGX0iAACcjSDiYKycAQDgwggiDjbm1MoZzpwBAOBcBBEH6z6Ft7C6SVUNrSZXAwCAayGIOFiQr7eSI/wlMSoCAMDZCCJO0L2fyO78KpMrAQDAtRBEnGBycrgkaVsuQQQAgDMRRJxgSkpXENmZW6n2jk6TqwEAwHUQRJxgZEyQgn291NDaQZ8IAABnIIg4gYeHxT4qsi2n0uRqAABwHQQRJ+kOIlsJIgAA2BFEnGRKSoQkaXtupTo7OYkXAACJIOI0o+OD5e/jqZqmNh0prTO7HAAAXAJBxEm8PT2UkRQmiT4RAAC6EUScaCp9IgAA9EAQcaLuPpGt2ZUyDPpEAAAgiDjR2MQQ+Xh5qLy+RTnlDWaXAwCA6QgiTuTr7anxtlBJ9IkAACARRJxuKhubAQBgRxBxMjY2AwDgNIKIk00cEiZPD4sKq5tUUNVodjkAAJjKYUEkNzdXS5YsUUpKivz8/DR06FA9+eSTam1tddRbDggBVi9dlRAiqWuXVQAA3JnDgsihQ4fU2dmpF154QQcOHNCvf/1r/fGPf9RPf/pTR73lgGHfTySbIAIAcG9ejvrCixYt0qJFi+yfp6am6vDhw3r++ef1zDPPOOptB4QpKeF6YX02DasAALfnsCByPjU1NQoPD7/g4y0tLWppabF/Xltb64yynG5SUrgsFim7vEGldc2KDvI1uyQAAEzhtGbV48eP63e/+52WLl16wecsW7ZMISEh9g+bzeas8pwqxN9babHBkqTtOVUmVwMAgHn6HESeeuopWSyWi37s2LGjx2uKioq0aNEi3X777XrggQcu+LUff/xx1dTU2D/y8/P7/h0NEKf3E6kwuRIAAMzT56mZhx9+WHfddddFn5OcnGz/76KiIs2dO1fTpk3Tn/70p4u+zmq1ymq19rWkAWlKSrhe3pzLfiIAALfW5yASGRmpyMjIXj23sLBQc+fOVUZGhpYvXy4PD7Yt6TY5uWtE5HBJnaobWxXq72NyRQAAOJ/DkkFRUZGuueYa2Ww2PfPMMyorK1NxcbGKi4sd9ZYDSlSQVUOjAmQY0vZc+kQAAO7JYatmVq9erWPHjunYsWNKTEzs8ZhhGI562wFlSkqEjpc1aFtOhRakx5hdDgAATuewEZH77rtPhmGc9wNdOAAPAODuaNowUfcBePuLalXf0m5yNQAAOB9BxETxoX5KDPNTR6ehXSfoEwEAuB+CiMmmMD0DAHBjBBGT0ScCAHBnBBGTTUmJkCTtzq9Wc1uHydUAAOBcBBGTJUf4KzrIqtaOTu3Orza7HAAAnIogYjKLxUKfCADAbRFEXAB9IgAAd0UQcQHdfSI7T1SpraPT5GoAAHAegogLGB4dqFB/bzW1dWh/YY3Z5QAA4DQEERfg4WGxn8a7lekZAIAbIYi4iKtTu6ZnNhwtM7kSAACchyDiIualRUuStmZXqqapzeRqAABwDoKIi0iJDNCw6EC1dxpad4RREQCAeyCIuJD5o2IkSZ9mlZhcCQAAzkEQcSEL0rumZ744XMoyXgCAwzS0tOuLw6X6zw+ytHJ7nqm1eJn67uhhvC1MkYE+Kq9v1bacSs0YFml2SQCAQaDt1DEim46Va/OxCmXmV6mtw5DUdQr8nZOHmFYbQcSFeHpYNC8tWm/sKNCarBKCCADgsnR2GjpcUqdNx8q16Vi5tuVUqqG158GqCaF+mjksUrNHRJlUZReCiIuZPypGb+wo0KcHS/TkTemyWCxmlwQAGABKapu14Wi5Nh4t08Zj5Sqvb+3xeHiAj6YNjdCMoZGaMSxCQ8L9XeJ3DEHExcwcHimrl4cKqpp0qLhOo+KCzS4JAOCCGlvbtTW7sit8HCvTkZL6Ho/7+3hqSkr4qeARqbTYIHl4mB88zkYQcTH+Pl6aNTxSnx4s1adZJQQRAICkrumWrJO1Wn+0TOuPlJ06n8ywP26xSGMTQjRreJRmDo/UxCFh8vFy/TUpBBEXNH9UTFcQOVii71873OxyAAAmqahv0cZj5Vp3pEzrj5SrvL6lx+MJoX6aPSJSM4dFafrQCIUF+JhU6eUjiLigeaO6lvHuKahRSW2zYoJ9Ta4IAOAM7adWt3QFjzLtLayRcXrQQ/4+npo+NEKzR0Rp1vAoJUe4Rp/HlSCIuKDoIF+Nt4Vqd361Pj1YosVTk8wuCQDgIGV1LVp3pExfHC7VhiNlqm1u7/F4WmyQ5oyM0pwRUcpICpPVy9OkSh2DIOKiFqTHdAWRLIIIAAwmHZ2G9hZU64vDZVp7uFR7C2p6PB7q762ZwyI1Z0SUZo+IGvSj4gQRF7UgPUa//OSwNh2vUENLuwKs3CoAGKhqGtu09kip1h4u07ojZaps6Lm0dkxCsOaOjNY1I6M13hYqTxdc3eIo/HZzUcOjAzUk3F95lY3acLRci8bEml0SAKCXDMPQ8bIGfX6oRJ8eLNXOE1Xq6Dzd7BFk9dLsEVGaMzJK14yIUvQgH/W4GIKIi7JYLJo/KkYvbcrRmqwSgggAuLjW9k5tz63UZwdL9fmhEuVWNPZ4fERMoOamRWveyGhNTAqTt6frL611BoKIC1uQ3hVEPj9Uoo5Ow62G6gBgIKhubNUXh0v1aVap1h8pU13L6UZTH08PTU0N1/xRMZqXFi1buL+JlbougogLm5QcphA/b1U1tmlXXpUmJ4ebXRIAuL38ykatzirRmqxibc/tOeUSGWjVvLQozUuL0czhkQqkv++S+H/IhXl7emjuyCi9t7tIn2aVEEQAwASdnYb2FdZoTVaJPj1YokPFdT0eT4sN0vxRMZqfHqOxCSEuuY26KyOIuLj56TF6b3eR1mSV6PEbRpldDgC4hbaOTm3JrtAnB4q1JqtEJbWndzT19LBocnKYFqTHasGoGA2JYMrlShBEXNycEVHy9rQou7xBx8vqNTQq0OySAGBQamxt1/ojZfrkQIk+O1jSY2OxAB9PzRkZpQXpMZo7Mlqh/gNvK3VXRRBxcUG+3ro6NUIbjpbr06wSDZ1DEAGA/lLd2KrPDpbq4wPF2nC0TM1tnfbHIgN9tCA9RgtHx2r60IhBt6OpqyCIDAAL0mO6gsjBEn1vzlCzywGAAa2srkWrs4r10b5ifZld0aPZ1Bbup+vSY3XdmFhNHBLGakUnIIgMANeOitETqw5o54kqVdS3KCLQanZJADCgFNc06+P9J/Xh/mJtz63scZBcWmyQrhsdq+tGx2pUXNCAP0RuoCGIDAAJoX5KjwtW1slafX6oVLdPspldEgC4vPzKRn28v1gf7T+pXXnVPR4blxii66+K06LRsUqODDCnQEgiiAwYC9JjlHWyVp8eLCGIAMAF5Fc26sN9J/XBvpPnHCaXkRSm68fEatGYWCWGsdLFVRBEBogF6TH6zWdHtf5IuZrbOuTrTdMUAEhSYXWTPtx7Uv/cd1J78qvt1z0s0pSUcF0/Jk7XjY5VbIj7nufiyggiA8To+GDFBvuquLZZm4+Xa15ajNklAYBpTtY06cN9xfpgb1GPaReLRZqaEq4bx8Zr0ehYRQXRU+fqCCIDhMVi0fz0aP19S57WZJUSRAC4nbK6Fn20/6Te312kHSeq7NctFmlycri+MjZOi8bEKjqIkY+BhCAygCxIj9Xft+TpkwPFevrm0fLx4uRGAINbTVObPjlQrH/sKdKmY+U6Y6WtJieH6car4nT9VXGKCSZ8DFQEkQFkxtAIRQdZVVrXok8PluiGq+LMLgkA+l1ja7s+O1iq9/cUad3hMrV2nN5kbFxiiG4aF68bx8YpLsTPxCrRXwgiA4iXp4duy0jUc2uPa+X2fIIIgEGjraNTG46W6b3MIn16sESNrR32x0bEBOrmcfG6aVy8kiJYajvYEEQGmDsm2fTc2uNaf7RMRdVNig/lXwQABibDMLQrr0rvZRbpg30nVdnQan9sSLi/bhoXp5vHJWhkbJCJVcLRCCIDTHJkgK5ODdeW7Eq9uaNA/zp/uNklAUCfHCut03uZRVq1p1D5lU3265GB1lPhI17jbaHscOomCCID0J2TbV1BZGe+vj9vmDw4CwGAiyutbdaq3UV6b3ehDhTV2q8H+HjqutGxumVCgqYPjZCXJ0347oYgMgBdPyZOT6w6oIKqJm0+XqGZwyPNLgkAztHU2qHVWcV6e1ehNh4ts6948fKwaM6IKH11QoIWjIqRnw8bNLozpwSRlpYWTZ06VXv27FFmZqbGjx/vjLcdtHy9PfXV8fH6+5Y8rdyRTxAB4DI6Ow1tyanQO7sK9dG+k2o4o+l04pBQ3ToxUTdeFafwAB8Tq4QrcUoQ+fGPf6z4+Hjt2bPHGW/nFu6aPKRrT5H9xapqaFUYf6kBmOhYaZ3e2VWo9zILVVTTbL9uC/fT1yYk6tYJCRwuh/NyeBD56KOPtHr1ar399tv66KOPHP12bmNMQoj9RN73dhfq/hkpZpcEwM3UNLbp/b1FemtnQY8zXoJ8vfSVsfH62sQETUoKo+kUF+XQIFJSUqLvfOc7eu+99+Tvf+mTDltaWtTS0mL/vLa29iLPxp2TbXry/QNauT1f901P5i87AIfr6DS04WiZ3txZoDVZJWpt79psrLvv42sTE3XtqGgO5kSvOSyIGIah++67T0uXLtWkSZOUm5t7ydcsW7ZMTz/9tKNKGnRuGZ+g//zwoA4V12lfYY3GJoaaXRKAQepYab3e2lmgdzMLVFJ7+h+MabFBui0jUbdMSFBkIAfMoe/6HESeeuqpS4aF7du3a/PmzaqtrdXjjz/e66/9+OOP69FHH7V/XltbK5vN1tcS3UaIv7euHxOrVbuLtHJ7PkEEQL+qbW7TP/ec1Js785V5xgm3Yf7e+ur4BN2WkajR8cGMxuKKWAzDMC79tNPKy8tVXl5+0eckJyfrrrvu0j/+8Y8ef0A7Ojrk6empxYsX65VXXrnke9XW1iokJEQ1NTUKDg7uS5luY/Oxcn3zL1sVZPXStp/NZxkcgCtiGIa25lTqjR35+nDfSTW3dU29eHpYNHdklG7LSNTctGhZvfhZgwvry+/vPgeR3srLy+vR41FUVKTrrrtOb731lqZOnarExMRLfg2CyKV1dhqa88wXyq9s0q9uH6evZ1z6/1cAOFtJbbPe2lmgN3fkK7ei0X59WHSg7pxk0y0TEhQVxNQLeqcvv78d1iMyZMiQHp8HBgZKkoYOHdqrEILe8fCw6I4Mm3615ohW7sgniADotbaOTn12sFRv7MjX2sOl9g3HAnw8ddO4eN0x2aYJbLUOB2Nn1UHgtkmJ+vWnR7Qtp1I55Q1KYa0+gIvIKW/Q69vz9PbOApXXnz5obnJymO6YZNMNV8UpwMqvBziH0/6kJScny0GzQG4vLsRPc0ZE6YvDZXpjR75+sijN7JIAuJjmtg59cqBYK7blaUt2pf16ZKBVX89I0B2TbBoaFWhihXBXRN5B4s7JNn1xuExv7SzQ/1kwgoOjAEiSjpbUacW2fL2TWaDqxjZJksUiXTMiSndNGaJ5adHy5ucFTEQQGSTmpcUoIsBHZXUt+uJwmRakx5hdEgCTNLV26IN9J7ViW552nqiyX48P8dXtk2y6Y7JNCaF+JlYInEYQGSR8vDz09YxE/Wl9tlZuzyeIAG7oaEmdXt2ap3d2Fai2uV1S17Lba9Oi9Y0pQzR7RJQ8PWg8hWshiAwid0yy6U/rs/XF4VKV1jYrOtjX7JIAOFhzW4c+3l+s17bmaVvu6d4PW7if7po8RLdnJPKzAC6NIDKIDIsOVEZSmHaeqNLbuwr1L9cMNbskAA6SXVavFdvy9NbOAlWd6v3w9LBo/qhofXNqkmYNi5QHox8YAAgig8ydk2zaeaJKb+zI1/dmp/KDCBhE2jo6tSarRH/fckKbj1fYr8eH+OquKUN052SbYhj9wABDEBlkbhwbp//4IEs55Q36+ECxbrgqzuySAFyhkzVNWrEtX69vy1NpXdeBcxaLNHdktBZPHaJrRkbT+4EBiyAyyARYvXT/jBT99rOj+u1nR7VodCyjIsAAZBiGNh2r0N+3nNCagyXqOLXtaWSgVXdNtukbU4ew8gWDAkFkEFoyI0XLN+boUHGdVmcVa9EYRkWAgaKmsU1v7SrQq1tOKLu8wX59akq47p6WpIXpsfLxYt8PDB4EkUEoxN9b981I1u8+P6bffHZMC9MZFQFc3f7CGv3tyxNatafQfuJtoNVLX5+YoMVXJ2lETJDJFQKOQRAZpJbMTNHyTbk6eLJWaw6W6LrRsWaXBOAsre2d+mj/Sf3tyxPaccbGY2mxQbp7WpJuGZ/AmS8Y9PgTPkiF+vvo3ulJ+sMXx/WbT49qYXoMJ2gCLqKktlmvbs3Ta1vzVF7f1Xzq5WHRDVfF6Z5pScpICuPvK9wGQWQQe2Bmql7elKusk7Vak1WihYyKAKYxDEPbcir11y0n9Mn+YrWfaj6NDrJq8dQkfWOKjY3H4JYIIoNYWICP7p2erOfWHtdvPjuqBYyKAE7X1NqhVbsL9fLmXB0qrrNfn5IcrnumJ+m60bEcOge3RhAZ5B6YlaqXN+fqQFGtPjtYqvmcQQM4RUFVo/625YRWbs+3n3rr6+2hWyck6p5pSRoVF2xyhYBrIIgMcuEBPrpnWrL+uK5rVOTaUdGMigAOYhiGtmRX6uXNOVqTVaJTsy9KDPPTvdOSdcckm0L8vc0tEnAxBBE38J1ZKXplc672Fdboi8OlmpfGqAjQn5paO/Te7kK9ctb0y4xhEbpveormpbHzKXAhBBE3EBFo1T3TkvTC+mz95tOjmjuSURGgPxRVN+mvX57Qim15qmnqmn7x8/bU1yYm6N7pyez9AfQCQcRNfGd2qv765QntKajR2iNlmjsy2uySgAHJMAztPFGl5Zty9fGBYvvW67bwrumX2zOYfgH6giDiJiIDrfrW1UP05w05+s2nR3XNiChGRYA+aG3v1D/3Fmn5pq5pzm7TUiN0/4xkXTsqhukX4DIQRNzId2cP1d+2nNDu/GqtO1KmaxgVAS6pvL5Fr27J09+3nlDZqZNvfbw8dOv4BN03I5nVL8AVIoi4kaggq741NUl/2Zij33x2VHMYFQEuKKuoVss35WjV7iK1dnSd/RITbNXdVyfpG1OGKCLQanKFwOBAEHEz352Tqr9tOaHMvGptOFqu2SOizC4JcBmdnYY+P1SqFzfm6MvsCvv1cbZQfXtGsq4fE8fJt0A/I4i4meggXy2emqSXNnWNiswaHsmoCNxeQ0u73tpZoOWbcpRb0ShJ8vSwaNGYWH17RooyksJMrhAYvAgibmjpnFS9uvWEdp6o0icHirVoTJzZJQGmKKxu0iubc7ViW57qmtslSUG+XvrmlCG6Z3qyEkL9TK4QGPwIIm4oOthX352dqt99fkz/d9UBTUuNZLkh3MquvCq9uDFHH+8/vfw2JTJA989I1tcnJirAyo9GwFn42+amHpo7TB/sO6nssgb914cH9f9uG2t2SYBDtXd06pMDJfrLxmxl5lXbr08fGqElM1M0d2S0PFh+CzgdQcRN+Xp76v99faxu/+OXWrkjX18dH6/pwyLNLgvod7XNbVq5LV8vb85VYXWTJMnH00M3j4/Xt2ekKD2e5beAmQgibmxycrjuvjpJf9tyQo+9s0+fPDJbfj6eZpcF9Iu8ikYt35yjN7bnq6G1Q1LXIZDfmjpE35qWpOggX5MrBCARRNzejxeN1KcHS5RX2ahff3pEP71hlNklAZete/v1v2zI0eqsYvvpt8OjA7VkZopumZAgX2/CNuBKCCJuLsjXW/956xh9++Ud+suGbN14VZzG2ULNLgvok7aOTn20v1gvbszRnvxq+/XZI6K0ZGaKZrNMHXBZBBFoXlqMvjo+Xqt2F+knb+/VP74/U96ebNoE11fT1KaV2/P08qZcFdU0S+rafv1rExL07ZkpnH4LDAAEEUiSnvhKutYfKdOh4jq9sO64Hp433OySgAvKq2jUS5ty9OaO0/0fEQE+untakr51dZIi2X4dGDAIIpAkRQRa9eRNo/XIyt367WfHtGhMnIZFB5pdFmBnGIZ2nKjSi2f1f4yICdQDM1N18/h4+j+AAYggAruvjo/Xe7sLtfZwmR5/Z69Wfnca+yrAdG0dnfpw30m9tDFHewpq7Ndnj4jSAzNTOKYAGOAIIrCzWCz6z1uv0sJn12l7bpVe3XpCd09LNrssuKmapja9vi1PL2/O1Un6P4BBiyCCHhJC/fST69P0xKoD+u+PDmneqBjO24BTnaho0PJNuXpjR74aT/V/RAb66O6rk7X46iH0fwCDDEEE5/jW1CS9v7tIO05U6d/f3aeX7pvM0DccyjAMbcup1Isbc7TmYImMU/0fI2OCtGRWim4eR/8HMFgRRHAODw+L/vvrY3XDbzboi8Nlem1bnhZPTTK7LAxCre2d+ufeIr24MUcHimrt168ZGaUHZqZqxrAIQjAwyBFEcF7DogP16MIR+u+PDumJVQeUFB6gmcM5iwb9o6K+Ra9tzdNft5xQWV2LJMnX20Nfm5iob89I1rBo+j8Ad0EQwQV9b3aqDhfX6d3MQv3Lqzv1zr9M13AaBHEFDhfXafmmHL2bWaiW9k5JUkywVfdMS9Y3pwxRWICPyRUCcDaCCC7IYrHov79+lQqqGrU9t0r3v7xd7z44Q1FBNAui9zo7Da09Uqrlm3K14Wi5/frYxBAtmZmi68fEyceLnXwBd2UxjO62MNdTW1urkJAQ1dTUKDiYo7rNUtnQqluf26QTFY0abwvV69+9msZBXFJdc5ve2lmgVzbnKreiUZLkYZGuGx2rb89M0aSkMPo/gEGqL7+/CSLoleyyet363GbVNLXpxqvi9LtvTGCzM5zXiYoGvbw5V2/uKFB9S7skKcjXS3dNtumeacmyhfubXCEAR+vL72+mZtArqVGBeuHuDN394lZ9sO+kkiP99aPr0swuCy7CMAxtOlah5Zty9PnhUvvy29SoAN0/PVlfm5ioACs/bgCci58M6LWrUyO07Gtj9W9v7tEfvjiupIgA3THJZnZZMFFDS7vezSzUX7/M1ZGSevv1uSOjdN+MFM0aFsnIGYCLIoigT27LSFRueYN+/8Ux/fSdfUoM89P0oSzrdTfZZfX625YTemtHgepOTb8E+HjqtoxE3Ts9WalRHJgIoHcIIuizRxeMUG5Fg/6596SW/m2n3nlwBif1uoGOTkNrD5fqlS9PaP2RMvv1lMgAfevqJN0+KVHBvt4mVghgIHJ4EPnggw/085//XHv37lVAQIBmz56td955x9FvCwfy8LDomdvHqai6SbvyqvXtl7fr3QenK4IzQAalqoZWvbEjX3/bckIFVU2SJItFujYtWvdMS9ZMpl8AXAGHBpG3335b3/nOd/Rf//VfmjdvngzD0L59+xz5lnASX29P/fmeSbrluU3Kq2zUnX/aouX3TWZFxCBhGIZ251fr1a15+seeIvvmYyF+3rprsk3fujqJew2gXzhs+W57e7uSk5P19NNPa8mSJZf1NVi+6/qOldbrW3/ZquLaZkUE+OjP907SxCFhZpeFy1Tf0q73Mgv12tY8ZZ08ffZLelyw7puerJvGxcvPhz1kAFycSyzf3bVrlwoLC+Xh4aEJEyaouLhY48eP1zPPPKPRo0ef9zUtLS1qaWmxf15bW3ve58F1DIsO1HsPzdCSV7brQFGtvvGnLXr2jvG6cWyc2aWhD/YX1ujVrXl6f3ehGlo7JEk+Xh76ytg4LZ46RBOHsPkYAMdwWBDJzs6WJD311FN69tlnlZycrF/96leaM2eOjhw5ovDw8HNes2zZMj399NOOKgkOEhviqze+N00/WJGpzw6V6qHXdimvMk1L56Tyy8uFNbV26B97ivTqtjztya+2X0+NCtDiqUn6+sQEhfpz9gsAx+rz1MxTTz11ybCwfft2HTlyRIsXL9YLL7yg7373u5K6RjwSExP1i1/8Qt/73vfOed35RkRsNhtTMwNER6eh//hnll7enCtJunOSTb+4dYy8PTlHxFV09368saNA/9xTZF966+1p0aIxXaMfU1PCCZAArohDp2Yefvhh3XXXXRd9TnJysurq6iRJ6enp9utWq1WpqanKy8s77+usVqusVlZeDFSeHhY9dfNoJUf46+f/zNLKHfkqqG7Uc4szFOLHsk4zldW16N3MAr25o0BHS09vPDYk3F/fnDpEt2UkKpJVTwBM0OcgEhkZqcjIS29glZGRIavVqsOHD2vmzJmSpLa2NuXm5iopKanvlWLAuG9GioZE+Ovh1zK16ViFbnt+s15iRY3TtXV0au3hMr2xI19fHCpVe2fX4Kevt4duGBOn2yfZNDUlnKW3AEzlsB6R4OBgLV26VE8++aRsNpuSkpL0y1/+UpJ0++23O+pt4SLmpcXozaXT9O2Xt+toab1ufW6T/vitDE1KPrc3CP3HMAxlnazVqt1FemdXocrrT091jreF6o5JNn1lXBwbjwFwGQ7dR+SXv/ylvLy8dPfdd6upqUlTp07V559/rrAwlne6g9HxIV0ral7eoayTtbr9hS911+Qh+vF1IxUWQBNkf8qvbNSq3YVatbuox9RLZKCPvjYxUbdnJGp4TJCJFQLA+TlsH5H+wD4ig0NDS7ueWHVAb+8qkCSF+nvrJ4vSdOckG9MCV6C8vkUf7D2pVbsLtSuv2n7dx8tD16ZF65YJCZqXFk2zMACn68vvb4IInGZbTqWeWLVfh4q7GpnH2UL1H18drbGJoeYWNoDUNLXps4MlWrW7SBuPlavjVN+Hh0WaPjRSN4+P13WjY2kOBmAqgghcVntHp/765Qk9u+aI6lvaZbFI35wyRD+6biR7VlxASW2zVmeVaPWBYn15vMLedCpJ4xJDdPP4BN00Nk7Rwb4mVgkApxFE4PJKa5u17KNDejezUJIUHuCjxxal6baMRKZrJB0vq9fqAyX65ECxdp+x2ZjUtZvtV8bG6eZx8UqN4tRjAK6HIIIBY0t2hZ5YtV9HSroaLNPjgrX46iG6eVy8gtxoZUdbR6cy86q19nCpVmeV6NgZDaeSNGFIqK4bHauF6TGEDwAujyCCAaWto1OvbM7Vr9ccsZ9z4uftqa+MjdNdU2yD8pwTwzB0pKReG4+Va9Oxcm3NrrB/71LXTqfThkZqYXqMFqbHMO0CYEAhiGBAqmxo1Tu7CvT69vweIwLDowN152SbvjYxUeEDeNnvyZombTpWoY1Hy7TpeIXK6lp6PB4e4KMZwyI1f1S05qZFs9cHgAGLIIIBzTAM7cqr0opt+frn3iI1t3VKknw8PbRgdIxum5iojOQwl/5FXdfcpv2FtdpbUK29BTXaW1it/MqmHs/x9fbQlJQIzRwWoZnDopQWG0R/DIBBgSCCQaO2uU3v7y7Syu352ldYY79usXSNlEywhWliUqgmDAnTsKhAU36RN7a26+DJOu0tqNa+ghrtKahWdnmDzv6b5WGRxiaGauawSM0YFqmJSaGyenk6vV4AcDSCCAal/YU1XeemHC49Z3RBkoKsXhpnC9WEIaEamxiqmGCrwvx9FB7gI38fz8vuM2nr6NTJ6mblVzUqr7JR+ZWNyq9q6vrfykZVNLSe93UJoX4amxiisYmhGpsYoqsSQ1x6FAcA+gtBBINeWV2LdudXKzOvSrvyqrS3oEaNZzR7ns3Hy0Ph/j4KC/BReIC3wvx9FOrvrY5OQ81tnWpq7VBze4ea2zrU1Naplrau/25s7VB5fYs6L/G3JDLQqnFnhQ5OswXgrggicDvtHZ06UlKvXXlVysyr1qHiWlU1tKqioVUt7Z1X/PV9vDxkC/OTLdxftjB/DQn3ly3cT4lh/rKF+7OTKQCcgSACnGIYhpraOlTZ0KqqhjZVNraqqqFVlQ2tqm5qk5eHRX7envL19pDV2/PUf3d93v3fUUFWRQVaaSQFgF7qy+9vh56+C5jNYrHI38dL/j5eSuTQZwBwORzLCQAATEMQAQAApiGIAAAA0xBEAACAaQgiAADANAQRAABgGoIIAAAwDUEEAACYhiACAABMQxABAACmIYgAAADTEEQAAIBpCCIAAMA0Ln36rmEYkrqOEwYAAAND9+/t7t/jF+PSQaSurk6SZLPZTK4EAAD0VV1dnUJCQi76HIvRm7hiks7OThUVFSkoKEgWi8V+vba2VjabTfn5+QoODjaxQpwP98f1cY9cG/fH9XGPLs4wDNXV1Sk+Pl4eHhfvAnHpEREPDw8lJiZe8PHg4GD+ALgw7o/r4x65Nu6P6+MeXdilRkK60awKAABMQxABAACmGZBBxGq16sknn5TVajW7FJwH98f1cY9cG/fH9XGP+o9LN6sCAIDBbUCOiAAAgMGBIAIAAExDEAEAAKYhiAAAANO4bBB57rnnlJKSIl9fX2VkZGjDhg29et2mTZvk5eWl8ePHO7ZAN9fX+9PS0qKf/exnSkpKktVq1dChQ/XSSy85qVr31Nd79Oqrr2rcuHHy9/dXXFyc7r//flVUVDipWveyfv163XTTTYqPj5fFYtF77713ydesW7dOGRkZ8vX1VWpqqv74xz86vlA31df7884772jBggWKiopScHCwpk2bpk8++cQ5xQ4CLhlEVq5cqUceeUQ/+9nPlJmZqVmzZun6669XXl7eRV9XU1Oje+65R9dee62TKnVPl3N/7rjjDn322Wd68cUXdfjwYa1YsUJpaWlOrNq99PUebdy4Uffcc4+WLFmiAwcO6M0339T27dv1wAMPOLly99DQ0KBx48bp97//fa+en5OToxtuuEGzZs1SZmamfvrTn+oHP/iB3n77bQdX6p76en/Wr1+vBQsW6MMPP9TOnTs1d+5c3XTTTcrMzHRwpYOE4YKmTJliLF26tMe1tLQ047HHHrvo6+68807j3//9340nn3zSGDdunAMrdG99vT8fffSRERISYlRUVDijPBh9v0e//OUvjdTU1B7Xfvvb3xqJiYkOqxFdJBnvvvvuRZ/z4x//2EhLS+tx7Xvf+55x9dVXO7AyGEbv7s/5pKenG08//XT/FzQIudyISGtrq3bu3KmFCxf2uL5w4UJt3rz5gq9bvny5jh8/rieffNLRJbq1y7k/77//viZNmqT/+Z//UUJCgkaMGKF/+7d/U1NTkzNKdjuXc4+mT5+ugoICffjhhzIMQyUlJXrrrbd04403OqNkXMKXX355zv287rrrtGPHDrW1tZlUFS6ks7NTdXV1Cg8PN7uUAcHlDr0rLy9XR0eHYmJielyPiYlRcXHxeV9z9OhRPfbYY9qwYYO8vFzuWxpULuf+ZGdna+PGjfL19dW7776r8vJyPfjgg6qsrKRPxAEu5x5Nnz5dr776qu688041Nzervb1dN998s373u985o2RcQnFx8XnvZ3t7u8rLyxUXF2dSZTifX/3qV2poaNAdd9xhdikDgsuNiHSzWCw9PjcM45xrktTR0aFvfvObevrppzVixAhnlef2ent/pK5/HVgsFr366quaMmWKbrjhBj377LN6+eWXGRVxoL7co6ysLP3gBz/QE088oZ07d+rjjz9WTk6Oli5d6oxS0Qvnu5/nuw5zrVixQk899ZRWrlyp6Ohos8sZEFxu+CAyMlKenp7n/MuttLT0nH8RSFJdXZ127NihzMxMPfzww5K6fvEZhiEvLy+tXr1a8+bNc0rt7qCv90eS4uLilJCQ0ONI6FGjRskwDBUUFGj48OEOrdndXM49WrZsmWbMmKEf/ehHkqSxY8cqICBAs2bN0i9+8Qv+xW2y2NjY895PLy8vRUREmFQVzrZy5UotWbJEb775pubPn292OQOGy42I+Pj4KCMjQ2vWrOlxfc2aNZo+ffo5zw8ODta+ffu0e/du+8fSpUs1cuRI7d69W1OnTnVW6W6hr/dHkmbMmKGioiLV19fbrx05ckQeHh5KTEx0aL3u6HLuUWNjozw8ev448PT0lHT6X94wz7Rp0865n6tXr9akSZPk7e1tUlU404oVK3Tffffptddeo7eqr8zrk72w119/3fD29jZefPFFIysry3jkkUeMgIAAIzc31zAMw3jssceMu++++4KvZ9WMY/X1/tTV1RmJiYnGbbfdZhw4cMBYt26dMXz4cOOBBx4w61sY9Pp6j5YvX254eXkZzz33nHH8+HFj48aNxqRJk4wpU6aY9S0ManV1dUZmZqaRmZlpSDKeffZZIzMz0zhx4oRhGOfen+zsbMPf39/44Q9/aGRlZRkvvvii4e3tbbz11ltmfQuDWl/vz2uvvWZ4eXkZf/jDH4yTJ0/aP6qrq836FgYUlwwihmEYf/jDH4ykpCTDx8fHmDhxorFu3Tr7Y/fee68xZ86cC76WIOJ4fb0/Bw8eNObPn2/4+fkZiYmJxqOPPmo0NjY6uWr30td79Nvf/tZIT083/Pz8jLi4OGPx4sVGQUGBk6t2D1988YUh6ZyPe++91zCM89+ftWvXGhMmTDB8fHyM5ORk4/nnn3d+4W6ir/dnzpw5F30+Ls5iGIy7AgAAc7hcjwgAAHAfBBEAAGAagggAADANQQQAAJiGIAIAAExDEAEAAKYhiAAAANMQRAAAgGkIIgAAwDQEEQAAYBqCCAAAMA1BBAAAmOb/A2YjZONlihRIAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exe.output[0].plot()" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "cee1e3fe-1e6f-4aef-ad75-894f59117cae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.6788586373205143" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].equilibrium_volume" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "90af087c-e926-4465-b237-848c9e382708", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.6788586373205143" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].get_structure().get_volume()" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "d2a84263-1dad-41fc-bb6a-87ecc1de2cc9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Atoms(symbols='Fe', pbc=True, cell=[[-0.5536557129291797, 0.5536557129291797, 0.5536557129291797], [0.5536557129291797, -0.5536557129291797, 0.5536557129291797], [0.5536557129291797, 0.5536557129291797, -0.5536557129291797]])" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].get_structure()" + ] + }, + { + "cell_type": "markdown", + "id": "5a89046e-035b-4f85-9d4d-3d2047d3b963", + "metadata": {}, + "source": [ + "## Again but execute children as background processes, but keep the node itself blocking" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "163db559-574e-4bdd-af56-2538cc38f615", + "metadata": {}, + "outputs": [], + "source": [ + "m = MurnaghanNode()" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "9cb4b0a3-4fdb-478a-8e23-b39370261413", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.node = AseStaticNode()\n", + "m.input.node.input.calculator = MorsePotential()\n", + "m.input.structure = bulk(\"Fe\", a=1.2)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "df71f86e-7764-47ce-a5e2-61df6871bcb3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.input.node.input" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "79a3872f-d9a0-49a7-b94c-a8618b444e16", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.set_strain_range(.6, 1000)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a07ff474-9975-4ff4-a8f2-966b5588168b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "63dfbc8d-9157-4b95-a025-bf854fd6baa9", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.child_executor = ProcessExecutor" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "03d18be4-82fa-4766-84ca-a3558ce6d223", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "exe = m.run(how='foreground')" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "6868c82d-fbf0-4fc8-9b70-586a66610a4e", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_machine.state" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "ab8027b5-a0fd-47f2-8b4a-bf743917330a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "27.62762461800594" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_time" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "7cf03eb5-9ac3-4699-a6d2-cb7fd56c7eff", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.2397998943924904e-05" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._collect_time" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "95a0ddb5-b61a-47e3-8bd4-218202f97d7b", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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uu+++1RcXCyHw6GxY8fqiSeeiFK18WWg1/aZZ57R5MmTlZaWJrfbrX/8x39UTU1NlKqNHxs2bND8+fNVUFAgi8WiV1555bTPWb9+vaZMmaKUlBSNGTNG//Ef/xH5QuPQQK/tSy+9pDlz5ignJ0eZmZmaPn261qxZE51i40wof2+7hfO9jCASglWrVmnRokW67777tHXrVl1yySWaN2+eDh482OfzGhoadP311+trX/talCqNP6Fc22uuuUZ/+9vf9Pjjj2vXrl167rnnNGHChChWHR8Gem3fffddXX/99brxxhu1fft2vfDCC9q0aZNuuummKFce+3w+nyZPnqwVK1b06/FlZWW6/PLLdckll2jr1q2699579eMf/1gvvvhihCuNPwO9ths2bNCcOXO0evVqbdmyRbNnz9b8+fO1devWCFcafwZ6bbuF/b3MwIBdcMEFxq233trj3IQJE4yf//znfT5vwYIFxj//8z8b999/vzF58uQIVhi/BnptX3/9dcPlchk1NTXRKC+uDfTaPvTQQ8aYMWN6nPvNb35jFBYWRqzGoUCS8fLLL/f5mHvuuceYMGFCj3O33HKLceGFF0awsvjXn2t7KhMnTjQeeOCB8Bc0hAzk2ob7vYwRkQFqbW3Vli1bNHfu3B7n586dq/fff7/X5z355JPau3ev7r///kiXGLdCubavvvqqpk6dqgcffFAjR47U+PHjdffdd6u5uTkaJceNUK7tjBkzdOjQIa1evVqGYejo0aP685//rCuuuCIaJQ9pH3zwwUl/Fl//+te1efNmtbW1mVTV0NTZ2Smv16usrCyzSxkSIvFeFtM3vYtF1dXV6ujoUF5eXo/zeXl5qqysPOVzdu/erZ///Od65513ZLNxyXsTyrXdt2+f3n33XaWkpOjll19WdXW1brvtNtXW1tIncoJQru2MGTP0zDPPaMGCBWppaVF7e7u++c1v6re//W00Sh7SKisrT/ln0d7erurqarndbpMqG3p+9atfyefz6ZprrjG7lLgXqfcyRkRCZLFYenxtGMZJ5ySpo6ND1157rR544AGNHz8+WuXFtf5eWynwrx2LxaJnnnlGF1xwgS6//HI9/PDD+uMf/8ioyCkM5Nru2LFDP/7xj/Uv//Iv2rJli9544w2VlZXp1ltvjUapQ96p/ixOdR6he+6557R06VKtWrVKubm5ZpcT1yL5XsY/zwcoOztbSUlJJ/0rsqqq6qR/4UiS1+vV5s2btXXrVt1xxx2SAm+ehmHIZrPpzTff1KWXXhqV2mPdQK+tJLndbo0cObLH7abPPPNMGYahQ4cOqbS0NKI1x4tQru3y5ct10UUX6ac//akk6ZxzzlF6erouueQS/eIXv+Bf7YOQn59/yj8Lm82mESNGmFTV0LJq1SrdeOONeuGFF3TZZZeZXU7ci+R7GSMiA2S32zVlyhStXbu2x/m1a9dqxowZJz0+MzNTn376qbZt2xY8br31Vp1xxhnatm2bpk2bFq3SY95Ar60kXXTRRTpy5IgaGxuD57744gtZrVYVFhZGtN54Esq1bWpqktXa838RSUlJko7/6x2hmT59+kl/Fm+++aamTp2q5ORkk6oaOp577jndcMMNevbZZ+lpCpOIvpcNut01AT3//PNGcnKy8fjjjxs7duwwFi1aZKSnpxv79+83DMMwfv7znxvXXXddr89n1UzvBnptvV6vUVhYaFx99dXG9u3bjfXr1xulpaXGTTfdZNavELMGem2ffPJJw2azGY899pixd+9e49133zWmTp1qXHDBBWb9CjHL6/UaW7duNbZu3WpIMh5++GFj69atxoEDBwzDOPna7tu3z0hLSzPuuusuY8eOHcbjjz9uJCcnG3/+85/N+hVi1kCv7bPPPmvYbDbj0UcfNSoqKoJHfX29Wb9CzBrotf2ycL2XEURC9OijjxrFxcWG3W43zjvvPGP9+vXB7y1cuNCYOXNmr88liPRtoNd2586dxmWXXWakpqYahYWFxuLFi42mpqYoVx0fBnptf/Ob3xgTJ040UlNTDbfbbfzgBz8wDh06FOWqY9/bb79tSDrpWLhwoWEYp76269atM77yla8YdrvdGD16tLFy5croFx4HBnptZ86c2efjcVwof29PFK73MothMMYKAADMQY8IAAAwDUEEAACYhiACAABMQxABAACmIYgAAADTEEQAAIBpCCIAAMA0BBEAAGAagggAADANQQQAAJiGIAIAAExDEAEAAKb5v1E7qt2HJCfYAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exe.output[0].plot()" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "000713cd-9535-4d3e-9df0-3d27447700f4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.6818586500998999" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].get_structure().get_volume()" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "7936847d-ef7e-414a-8cbc-21474519eb84", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.6818586500999" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].equilibrium_volume" + ] + }, + { + "cell_type": "markdown", + "id": "af68c813-e409-4efd-85f8-ee3dfee60201", + "metadata": {}, + "source": [ + "## Again but execute everything in the background." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "d603c588-a0fa-4aa7-ab57-f79fa6224b2b", + "metadata": {}, + "outputs": [], + "source": [ + "m = MurnaghanNode()" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "6b9d7aae-996c-4793-9960-debbf3cd2790", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.node = AseStaticNode()\n", + "m.input.node.input.calculator = MorsePotential()\n", + "m.input.structure = bulk(\"Fe\", a=1.2)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "97fe976c-2f7b-40bc-adf1-e9ee8a2520d7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.input.node.input" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "0f4e2e94-79c6-47f7-ae93-d34a9fe09f08", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.set_strain_range(.7, 100)" + ] + }, + { + "cell_type": "markdown", + "id": "e9fed5d1-757e-43ad-8adb-653bc464dc3b", + "metadata": {}, + "source": [ + "Use the threading backend just to show off." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "07585681-e027-4c42-b5cd-7086c5b9ba39", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.executor import BackgroundExecutor\n", + "\n", + "m.input.child_executor = BackgroundExecutor" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "4ba483ff-4a78-4c24-84db-a42ab518945f", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "exe = m.run(how='background')" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "c597a2dd-f809-4bb6-9935-a0bdc2d35c07", + "metadata": {}, + "outputs": [], + "source": [ + "exe.wait()" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "71a7b3d1-99a8-494e-8649-f7aceada16df", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_machine.state" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "97cf6e6e-c5c4-46eb-bbd0-c36e0cc37589", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "12.170459961984307" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_time" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "21861420-e58c-414b-aed7-0f6d67d12642", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2.338102785870433e-05" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._collect_time" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "11cd873c-a43c-4cb8-ac23-39ec6889c30c", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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zk2aNMm49957e/wzJk+ebNx///2pLs1S+tpON954o/H973/fuO+++4ypU6emsULr6G1b/eEPfzAKCgqMo0ePZqI8y+htO/34xz82Jk6c2OXcQw89ZIwbNy5tNVqRJGP9+vWnvGbx4sXGpEmTupxbuHChcfHFF6exMmvpSTudTDa8n3fXm7YajO/pluxBiUQi2rZtm0pLS7ucLy0t1ebNm3v0M2KxmOrr6zV8+PB0lGgJfW2nJ554Qh9++KHuu+++dJdoGX1pq+eff17Tp0/X8uXLNXbsWJ155pn6zne+o+bm5kyUbIq+tNOsWbO0f/9+vfDCCzIMQ4cOHdJvfvMbXXvttZkoeUB57bXXTmjbq666Slu3blVra6tJVVlfNryf98dgfU+35M0Cjxw5omg0Kr/f3+W83+9XTU1Nj37GT37yEzU2NuqGG25IR4mW0Jd2ev/993Xvvffq1VdfldNpyf/9adGXttqzZ482bdokr9er9evX68iRI1q0aJGOHTs2aOeh9KWdZs2apaefflo33nijWlpa1NbWpuuuu04PP/xwJkoeUGpqak7atm1tbTpy5IjGjBljUmXWlg3v5301mN/TLdmDkmCz2bo8NwzjhHMn88wzz2jZsmVat26dRo8ena7yLKOn7RSNRrVgwQLdf//9OvPMMzNVnqX05m8qFovJZrPp6aef1owZM3TNNddoxYoVevLJJwd1L4rUu3bauXOn7rrrLv3gBz/Qtm3b9Mc//lFVVVW6/fbbM1HqgHOytj3ZecRl2/t5bwz293RLxq2RI0fK4XCc8C+22traE/710d26det066236rnnntOVV16ZzjJN19t2qq+v19atW7V9+3bdeeedkuIfwoZhyOl0asOGDfqHf/iHjNSeaX35mxozZozGjh3b5dbgZ599tgzD0P79+1VSUpLWms3Ql3aqqKjQ7Nmz9d3vfleSdN555ykvL0+XXnqp/u3f/o1egU4CgcBJ29bpdGrEiBEmVWVd2fR+3heD/T3dkj0obrdb06ZN08aNG7uc37hxo2bNmvWJ3/fMM8/olltu0dq1a7Ni/Lu37eTz+fTOO+/ozTffTB633367zjrrLL355puaOXNmpkrPuL78Tc2ePVsHDhxQQ0ND8tzu3btlt9s1bty4tNZrlr60U1NTk+z2rm8lDodDUkfvAOI+85nPnNC2GzZs0PTp0+VyuUyqypqy7f28Lwb9e7pZs3NP59lnnzVcLpfx+OOPGzt37jTKysqMvLw8Y+/evYZhGMa9995rfOUrX0lev3btWsPpdBo//elPjYMHDyaP48ePm/UrZERv26m7wTTj+3R621b19fXGuHHjjM9//vPGjh07jMrKSqOkpMS47bbbzPoVMqK37fTEE08YTqfTeOSRR4wPP/zQ2LRpkzF9+nRjxowZZv0KGVNfX29s377d2L59uyHJWLFihbF9+3bjo48+MgzjxLbas2ePkZuba9x9993Gzp07jccff9xwuVzGb37zG7N+hYzobTtl6/u5YfS+rbobTO/plg0ohmEYP/3pT40zzjjDcLvdxoUXXmhUVlYmX7v55puNOXPmJJ/PmTPHkHTCcfPNN2e+8AzrTTt1N5j+mHuit2313nvvGVdeeaWRk5NjjBs3zrjnnnuMpqamDFedeb1tp4ceesiYPHmykZOTY4wZM8a46aabjP3792e46sx76aWXTvm+c7K2evnll40LLrjAcLvdxoQJE4zVq1dnvvAM6207ZfP7eV/+pjobTO/pNsOgDxYAAFiLJeegAACA7EZAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlvP/AXuzOgpGI5yoAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exe.output[0].plot()" + ] + }, + { + "cell_type": "markdown", + "id": "8a102a81-df04-4527-8739-7fe542f0c1fc", + "metadata": {}, + "source": [ + "# ASE MD" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "02cfe01b-0b24-4723-a79b-d41ffb146bf9", + "metadata": {}, + "outputs": [], + "source": [ + "md = AseMDNode()" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "466d1f9a-b707-4c05-a8af-5414d76bd8eb", + "metadata": {}, + "outputs": [], + "source": [ + "md.input.structure = bulk(\"Fe\", a=1.2, cubic=True).repeat(3)\n", + "md.input.calculator = MorsePotential()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "dfdfc027-1608-43ad-9d15-0c649986eb73", + "metadata": {}, + "outputs": [], + "source": [ + "md.input.steps = 100\n", + "md.input.timestep = 3\n", + "md.input.temperature = 600\n", + "md.input.output_steps = 20" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "db5c7cfe-b075-483e-8b7e-a58cebf1a782", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 2.51 ms, sys: 50.7 ms, total: 53.3 ms\n", + "Wall time: 52.9 ms\n" + ] + } + ], + "source": [ + "%%time\n", + "exe = md.run(how='process')" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "80155255-4dcf-48cb-9825-015da13d6ac0", + "metadata": {}, + "outputs": [], + "source": [ + "exe.wait()" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "6f7aff4e-9e89-459b-843f-46a4d4139bcf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'status': [ReturnStatus(Code.DONE, None)],\n", + " 'output': []}" + ] + }, + "execution_count": 61, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_machine._data" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "62ce8439-bf95-4818-b35c-b4e2ef649bd2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 62, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_machine.state" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "5bcd1b68-6a48-4a08-92d4-143419071618", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "16.012012982013403" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_time" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "d21371e0-fa36-44bd-b7bf-a0092177ba17", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4.1224993765354156e-05" + ] + }, + "execution_count": 64, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._collect_time" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "9e06cd6d-e0f7-40dd-93f2-777f86ffe2eb", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exe.output[0].plot_energies()" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "bb70a653-6231-4f4e-9bbe-279811acc895", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "9acd0738c709420293ca03c9d7cb65a8", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "NGLWidget(max_frame=21)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exe.output[0].animate_structures()" + ] + }, + { + "cell_type": "markdown", + "id": "c7844b5f-1f9b-4770-8608-144c8b84fb87", + "metadata": {}, + "source": [ + "# ASE Minimize" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "f816e2af-0455-4e05-9c39-2e9f615d8f34", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_atomistics import ase_to_pyiron" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "22314020-8f48-487b-a765-229a77d79a2f", + "metadata": {}, + "outputs": [], + "source": [ + "mi = AseMinimizeNode()" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "ff411a05-82e1-4581-b06e-ab2fd7e0be3b", + "metadata": {}, + "outputs": [], + "source": [ + "dimer = Atoms(symbols=['Fe', 'Fe'], positions=[[0,0,0], [0,0, .75]], cell=[10,10,10])" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "5574f0d5-d800-472a-9418-8c6ccc1e555b", + "metadata": {}, + "outputs": [], + "source": [ + "mi.input.structure = dimer\n", + "mi.input.calculator = MorsePotential(rcut1=6,rcut2=10)" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "9e02d6dd-0fa6-4dd6-a7ab-3e648958eb20", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5cff2d0d15044fca9105fa3541411380", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "NGLWidget()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ase_to_pyiron(mi.input.structure).plot3d()" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "663e4435-1cd0-4ce2-9593-85453f4c846a", + "metadata": {}, + "outputs": [], + "source": [ + "mi.input.max_steps = 100\n", + "mi.input.output_steps = 1\n", + "mi.input.ionic_force_tolerance = 1e-6" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "37440e5a-75ff-4601-813a-f5c8df9413ad", + "metadata": {}, + "outputs": [], + "source": [ + "mi.input.gpmin()" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "a448ec7f-53bc-4d72-a8a7-f9392de9f3d5", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Step Time Energy fmax\n", + "GPMin: 0 14:53:00 11.122159 187.2462\n", + "GPMin: 1 14:53:00 -0.278268 1.5338\n", + "GPMin: 2 14:53:00 -0.996055 0.8010\n", + "GPMin: 3 14:53:00 -0.000000 0.0000\n", + "CPU times: user 76.7 ms, sys: 50.9 ms, total: 128 ms\n", + "Wall time: 57.3 ms\n" + ] + } + ], + "source": [ + "%%time\n", + "exe = mi.run(how='foreground')" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "5977dd10-c4cf-40c9-944e-5aa52cfa263d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None),)" + ] + }, + "execution_count": 75, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.status" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "dd164778-634c-4785-903a-08a5243999ce", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2.136147842601888e-07" + ] + }, + "execution_count": 76, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "abs(exe.output[0].forces[-1]).max()" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "515ea06d-9026-4d9e-9df0-b9c249f0758a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 77, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_machine.state" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "52b7231f-8978-46ec-b698-ea8724a6fea3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.057213895983295515" + ] + }, + "execution_count": 78, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_time" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "c845430c-119d-4566-88e1-8465e378fde1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.1272000847384334e-05" + ] + }, + "execution_count": 79, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._collect_time" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "35291d7f-33a9-41ab-9b80-f052c5eb2e55", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAh8AAAGdCAYAAACyzRGfAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy88F64QAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA2gklEQVR4nO3deXRV9b3//9c+ORlIyDkZCBkgQFAMM0QQBCdaFIuKib22vbfWqet3b73FgbK66tDettcOqXbQViq91n69uqzDWtcSaB1TZShFkCFhnoQAIQMJJORkgIz790dyjoYMJOGcs8/wfKx1luZkn+z39uMiL/bnsz9vwzRNUwAAAH5is7oAAAAQXggfAADArwgfAADArwgfAADArwgfAADArwgfAADArwgfAADArwgfAADAr+xWF3Chjo4OlZeXKz4+XoZhWF0OAAAYANM0VV9fr4yMDNls/d/bCLjwUV5erszMTKvLAAAAQ1BaWqrRo0f3e0zAhY/4+HhJncU7HA6LqwEAAAPhcrmUmZnp+T3en4ALH+6pFofDQfgAACDIDGTJBAtOAQCAXxE+AACAXxE+AACAXwXcmg8AAKzW3t6u1tZWq8sIOBEREbLb7Ze8FQbhAwCAz2loaNDJkydlmqbVpQSk2NhYpaenKyoqasg/g/ABAECX9vZ2nTx5UrGxsUpJSWGzy88xTVMtLS2qrq5WSUmJJkyYcNHNxPpC+AAAoEtra6tM01RKSoqGDRtmdTkBZ9iwYYqMjNTx48fV0tKimJiYIf0cFpwCAHAB7nj0bah3O7r9DC/UAQAAMGCEDwAA4FeEDwAAwtz//u//KiEhwW/nI3wAAAC/CpvwUXeuVS/+46ge/b9dVpcCAIBXLViwQA8++KAefPBBJSQkKDk5WT/4wQ88e5XU1tbqnnvuUWJiomJjY7V48WIdPnxYkrRu3Trdf//9qqurk2EYMgxDP/7xj31a76DDx4YNG7RkyRJlZGTIMAwVFBR4vtfa2qpHH31U06ZNU1xcnDIyMnTPPfeovLzcmzUPSXNru37+zn69ua1UJ840WV0OACAImKapppY2S16D3eTs5Zdflt1u15YtW/S73/1OzzzzjF588UVJ0n333adt27ZpzZo1+vjjj2Wapm655Ra1trZq/vz5evbZZ+VwOFRRUaGKigp997vf9cV/To9B7/PR2NioGTNm6P7779e//Mu/dPteU1OTduzYof/6r//SjBkzVFtbq2XLlun222/Xtm3bvFb0UIx0xOiay0foH4dPq6C4TA8vnGBpPQCAwHeutV2Tf/i+Jefe9+TNio0a+K/pzMxMPfPMMzIMQ9nZ2dq9e7eeeeYZLViwQGvWrNE///lPzZ8/X5L05z//WZmZmSooKNBXvvIVOZ1OGYahtLQ0X11ON4MOH4sXL9bixYt7/Z7T6VRhYWG395577jnNmTNHJ06c0JgxY4ZWpZfkzhzlCR8PffFynuMGAISMq6++utvvtXnz5unXv/619u3bJ7vdrrlz53q+l5ycrOzsbO3fv9+KUn2/w6l7DqmvVbTNzc1qbm72fO1yuXxWy81TUvWDApuOVjdqT5lL00Y7fXYuAEDwGxYZoX1P3mzZuX3JNE3L/hLu0/Bx/vx5PfbYY/r6178uh8PR6zH5+fn67//+b1+W4REfE6kbJ6Xqb7sqVFBcRvgAAPTLMIxBTX1YafPmzT2+njBhgiZPnqy2tjZt2bLFM+1y5swZHTp0SJMmTZIkRUVFqb293W+1+uxpl9bWVv3rv/6rOjo69Pzzz/d53OOPP666ujrPq7S01FclSZLyZo6SJK3ZWa72DjoWAgBCQ2lpqZYvX66DBw/q9ddf13PPPadHHnlEEyZMUG5urv793/9dGzdu1M6dO/WNb3xDo0aNUm5uriRp3Lhxamho0IcffqjTp0+rqcm3D2b4JHy0trbqq1/9qkpKSlRYWNjnXQ9Jio6OlsPh6PbypeuvSFFCbKSq65u16chpn54LAAB/ueeee3Tu3DnNmTNHS5cu1UMPPaT/+I//kCS99NJLmjVrlm677TbNmzdPpmnqnXfeUWRkpCRp/vz5euCBB/S1r31NKSkpevrpp31aq9fvJbmDx+HDh7V27VolJyd7+xSXJMpu023T0/Xq5hMqKCrXdRNSrC4JAIBLFhkZqWeffVYrV67s8b3ExES98sor/X5+5cqVvX7WFwZ956OhoUHFxcUqLi6WJJWUlKi4uFgnTpxQW1ub7rzzTm3btk1//vOf1d7ersrKSlVWVqqlpcXbtQ+Ze+rlvT0VOtfivzkuAAAwhPCxbds25eTkKCcnR5K0fPly5eTk6Ic//KFOnjypNWvW6OTJk5o5c6bS09M9r02bNnm9+KGaNTZRoxOHqbGlXX/ff8rqcgAACCuDnnZZsGBBv7uuDXZHNisYhqG8maO0Yu2nWl1cpiUzMqwuCQCAIVu3bp3VJQxK2PR2uVBeTmfgWHewWjWNgTMlBABAqAvb8HH5yHhNyXCorcPU27srrC4HAICwEbbhQ5LuyOlceLq6qMziSgAACB9hHT6WzMiQYUjbjteqtIZOtwAA+ENYh49UR4zmX9a5D8nqYu5+AADgD2EdPqTOTreSVFBcHhRP6gAAEOzCPnx8aWqaou02fVrVoL3lvuuoCwCAryxYsEDLli3r9Xv33Xef8vLy/FrPxQRHqz4fcnR1un17d4UKiso0dRSdbgEAoeO3v/1twN3ZD/s7H5KUO7Nzzw863QIAQo3T6VRCQoLVZXRD+JC0IHukEmIjVVXfrM1Hz1hdDgAAl+S9996T0+nUK6+80mPaZcGCBXr44Yf1ve99T0lJSUpLS9OPf/xjv9ZH+FBnp9tbpqVLkgrY8wMA4GaaUkujNa8hTpW88cYb+upXv6pXXnlF99xzT6/HvPzyy4qLi9OWLVv09NNP68knn1RhYeGl/JcalLBf8+GWN3OUXttyQu/uqdRP8qYqJjLC6pIAAFZrbZJ+blH/ryfKpai4QX3k+eef1xNPPKHVq1frC1/4Qp/HTZ8+XT/60Y8kSRMmTNCKFSv04Ycf6qabbrqkkgeK8NFl9thEjUoYprKz5/Th/irdOj3d6pIAABiwt956S6dOndLGjRs1Z86cfo+dPn16t6/T09NVVVXly/K6IXx0sdkM5c7M0PPrjqiguIzwAQCQImM770BYde5BmDlzpnbs2KGXXnpJV111lQzD6PtHR0Z2+9owDHV0dAypzKEgfHxOXs4oPb/uiNYdrNLZphYlxEZZXRIAwEqGMeipD6tcdtll+vWvf60FCxYoIiJCK1assLqkPrHg9HOuSI3XpHSHWtvpdAsACD5XXHGF1q5dq7feeqvPTccCAXc+LnBHTob2V7i0uqhcd80da3U5AAAMSnZ2tj766CPPHZBARPi4wO0zRin/3QP65FiNTtY2aXTi4ObcAADwt3Xr1nX7etKkSTp16tSAjpWkgoIC7xfVD6ZdLpDmjNHVWe5OtxYtMgIAIIQRPnpxR05np9vVxWUBtx8+AADBjvDRiy9NS1OU3aZDpxq0v6Le6nIAAAgphI9eOGIitXDiSElSQTHbrQMA4E2Ejz7kzuycellTTKdbAAC8ifDRhy9MTJEjxq5K13ltKaHTLQCEE9b79c0b/20IH32Itkd4tlin0y0AhAf3vhgtLS0WVxK4mpqaJPXcon0w2OejH7kzR+n1T0r17u5KPZlLp1sACHV2u12xsbGqrq5WZGSkbDb+ju5mmqaamppUVVWlhISES9rAjPDRjznjkpThjFF53XmtPVClxdNoNgcAocwwDKWnp6ukpETHjx+3upyAlJCQoLS0tEv6GYSPfthshm6fOUp/WH9Eq4rKCB8AEAaioqI0YcIEpl56ERkZ6ZUt2wkfF5GXk6E/rD+idQerVdfUKmfs0Oe4AADBwWazKSYmxuoyQhaTWRcxMc2hiWnxamnv0Dt76HQLAMClInwMQF7Xdus89QIAwKUjfAzA7TMyZBjSlpIalZ09Z3U5AAAENcLHAGQkDNOccUmSOnc8BQAAQ0f4GKDPd7oFAABDR/gYoMXT0hUVYdOBynrtr3BZXQ4AAEGL8DFAzmGR+sLEFEl0ugUA4FIQPgbBPfXy1+JyddDpFgCAISF8DMKC7JGKj7GrvO68PjlWY3U5AAAEJcLHIMRERuiWqXS6BQDgUhA+Bsm94dg7uyvU3NZucTUAAAQfwscgzc1KUrozRq7zbVp7oNrqcgAACDqEj0Gy2QzdPiNDElMvAAAMBeFjCHJndk69fHSgSnXnWi2uBgCA4EL4GIJJ6fHKTu3sdPsenW4BABiUQYePDRs2aMmSJcrIyJBhGCooKOj2fdM09eMf/1gZGRkaNmyYFixYoL1793qr3oBgGIZyczqnXlYx9QIAwKAMOnw0NjZqxowZWrFiRa/ff/rpp/Wb3/xGK1as0NatW5WWlqabbrpJ9fX1l1xsIHGv+9hSUqOKOjrdAgAwUIMOH4sXL9ZPf/pTffnLX+7xPdM09eyzz+r73/++vvzlL2vq1Kl6+eWX1dTUpNdee80rBQeK0YmxmpOVJNOk0y0AAIPh1TUfJSUlqqys1KJFizzvRUdH64YbbtCmTZt6/Uxzc7NcLle3V7DI61p4WkD4AABgwLwaPiorKyVJqamp3d5PTU31fO9C+fn5cjqdnldmZqY3S/KpW6alKTLC0P4Klw5Whta0EgAAvuKTp10Mw+j2tWmaPd5ze/zxx1VXV+d5lZaW+qIkn0iIjdKC7JGS6HQLAMBAeTV8pKWlSVKPuxxVVVU97oa4RUdHy+FwdHsFE3en2zV0ugUAYEC8Gj6ysrKUlpamwsJCz3stLS1av3695s+f781TBYwvThyp+Gi7ys6e01Y63QIAcFGDDh8NDQ0qLi5WcXGxpM5FpsXFxTpx4oQMw9CyZcv085//XKtWrdKePXt03333KTY2Vl//+te9XXtAiImM0Jemdt7xYeEpAAAXN+jwsW3bNuXk5CgnJ0eStHz5cuXk5OiHP/yhJOl73/ueli1bpm9/+9uaPXu2ysrK9MEHHyg+Pt67lQeQO+h0CwDAgBmmaQbUQgWXyyWn06m6urqgWf/R3mFq/i8+1ClXs/7n7lm6eUqa1SUBAOBXg/n9TW8XL4j4XKfb1Tz1AgBAvwgfXpLXNfXy9/1Vcp2n0y0AAH0hfHjJ5HSHJowcrpa2Dr23p/cN1QAAAOHDawzD8Nz9KKDTLQAAfSJ8eJF73cfHR8+osu68xdUAABCYCB9elJkUq6vGJco0pb/uZM8PAAB6Q/jwstyuTrermHoBAKBXhA8vu3Vauuw2Q/sqXDp8ik63AABciPDhZYlxdLoFAKA/hA8fyMtxbzhGp1sAAC5E+PCBGyelani0XSdrz2n7iVqrywEAIKAQPnygW6dbFp4CANAN4cNH8rqeenl7d4Va2josrgYAgMBB+PCReZcla2R8tM42tWr9oWqrywEAIGAQPnwkwmZoSdeOpzz1AgDAZwgfPnSHu9PtvlOqp9MtAACSCB8+NSXDoctS4tRMp1sAADwIHz5kGIZn4enqYnq9AAAgET58zt3rZdOR06py0ekWAADCh4+NSY7VrLGJ6jClNXS6BQCA8OEPeTN56gUAADfChx/cOj1DdpuhPWUufVrVYHU5AABYivDhB0lxUbrhihRJ0mrufgAAwhzhw09yu/b8KCguk2nS6RYAEL4IH35y06RUxUVFqLTmnHbQ6RYAEMYIH34yLCpCN3s63fLUCwAgfBE+/Mi94djfdpWrtZ1OtwCA8ET48KP5lyVrxPBo1Ta1agOdbgEAYYrw4Uf2CJtu93S6ZeoFABCeCB9+lpfTGT4K91WqobnN4moAAPA/woefTRvl1PgRcTrf2qH36XQLAAhDhA8/MwzD02yO7dYBAOGI8GEB99TLPz89rap6Ot0CAMIL4cMCY5PjlDMmQR2m9NedFVaXAwCAXxE+LOLe84NeLwCAcEP4sMht09MVYTO062SdjlbT6RYAED4IHxZJHh6t6yeMkMSeHwCA8EL4sFCeu9NtEZ1uAQDhg/BhoZsmpyo2KkInappUVHrW6nIAAPALwoeFYqPsunlKZ6fb1UUsPAUAhAfCh8VyZ3bu+fHXXRV0ugUAhAXCh8WuvXyERgyPUk1jizYePm11OQAA+Bzhw2L2CJtum+7udMvUCwAg9BE+AoD7qZcP9p5SI51uAQAhzuvho62tTT/4wQ+UlZWlYcOGafz48XryySfV0cF6hr7MGO3UuORYnWtt1wf76HQLAAhtXg8fTz31lP7whz9oxYoV2r9/v55++mn98pe/1HPPPeftU4UMwzA+t+cHG44BAEKb18PHxx9/rNzcXN16660aN26c7rzzTi1atEjbtm3z9qlCirvXy8ZPT6u6vtniagAA8B2vh49rr71WH374oQ4dOiRJ2rlzpzZu3KhbbrnF26cKKeNGxGlGZoLaO0z9bRd3PwAAocvu7R/46KOPqq6uThMnTlRERITa29v1s5/9TP/2b//W6/HNzc1qbv7sb/oul8vbJQWNvJkZ2ll6VgXF5br/miyrywEAwCe8fufjzTff1KuvvqrXXntNO3bs0Msvv6xf/epXevnll3s9Pj8/X06n0/PKzMz0dklB47bpGYqwGdpZelYlpxutLgcAAJ8wTC93NMvMzNRjjz2mpUuXet776U9/qldffVUHDhzocXxvdz4yMzNVV1cnh8PhzdKCwr3/7xOtP1StRxZO0HduusLqcgAAGBCXyyWn0zmg399ev/PR1NQkm637j42IiOjzUdvo6Gg5HI5ur3CWl9O54djqYjrdAgBCk9fXfCxZskQ/+9nPNGbMGE2ZMkVFRUX6zW9+o29+85vePlVIWjQ5TcMi9+jYmSbtPFmnmZkJVpcEAIBXef3Ox3PPPac777xT3/72tzVp0iR997vf1be+9S395Cc/8fapQlJctF2LpqRKkgrodAsACEFeX/NxqQYzZxSq1h6o0v3/u1Ujhkdp8+MLZY9gF3wAQGCzdM0HLt21E0YoOS5KpxtatPFTOt0CAEIL4SMARUbYdNv0dEnS6mI2HAMAhBbCR4DK7er18v7eSjW10OkWABA6CB8BKiczQWOTY9XU0q7CfaesLgcAAK8hfAQowzCUO9Pd6ZanXgAAoYPwEcDyZnZuOLbh8GmdbqDTLQAgNBA+Atj4lOGaPtqp9g5Tb++qsLocAAC8gvAR4PLcUy/FTL0AAEID4SPA3TYjXTZDKjpxVsfP0OkWABD8CB8BbmR8jK65fIQkqaCIPT8AAMGP8BEE3FMvdLoFAIQCwkcQuHlqmmIibTp6ulG7y+qsLgcAgEtC+AgCw6PtumlymiRpFXt+AACCHOEjSLj3/Pjrzgq1tXdYXA0AAENH+AgS11+RosTYSJ1uaNamI2esLgcAgCEjfASJzk63nXc/2G4dABDMCB9BJC+nM3y8v7dS51raLa4GAIChIXwEkSvHJCozaZgaW9pVuJ9OtwCA4ET4CCKGYXy25wdTLwCAIEX4CDK5XeFj/aFq1TS2WFwNAACDR/gIMpePHK6poxxq6zD19i62WwcABB/CRxD6rNMt4QMAEHwIH0Ho9hkZshnS9uO1OnGmyepyAAAYFMJHEBrpiNH8yzo73a4uZuEpACC4ED6CVF6Oe+qFTrcAgOBC+AhSN09JVbTdpiPVjdpT5rK6HAAABozwEaTiYyJ14+RUSZ13PwAACBaEjyB2R9dTL3/dWa72DqZeAADBgfARxK6/IkUJsZGqqm/Wx3S6BQAECcJHEIuy23TrtHRJ0iq2WwcABAnCR5BzP/Xy/t5KnW+l0y0AIPARPoLcrDGJGp04TA3Nbfo7nW4BAEGA8BHkbDZDuTMzJEkFTL0AAIIA4SMEuHu9rDtYrVo63QIAAhzhIwRMSI3XlIyuTre7K6wuBwCAfhE+QoT77ge9XgAAgY7wESKWzMiQYUhbj9WqtIZOtwCAwEX4CBFpzhjNG58sSVqzs9ziagAA6BvhI4S49/xYVUSnWwBA4CJ8hJAvTU1TlN2mT6satLecTrcAgMBE+AghjphI3ThppCQWngIAAhfhI8S4n3pZQ6dbAECAInyEmAXZI+UcFqlTrmZtPkqnWwBA4CF8hJgou023dHW6Zbt1AEAgInyEoDu6nnp5bw+dbgEAgccn4aOsrEzf+MY3lJycrNjYWM2cOVPbt2/3xanQi9ljEzUqYZjqm9v00YEqq8sBAKAbr4eP2tpaXXPNNYqMjNS7776rffv26de//rUSEhK8fSr0wWYzdHtXp9tVTL0AAAKM3ds/8KmnnlJmZqZeeuklz3vjxo3z9mlwEXkzR2nluiNad7BKZ5talBAbZXVJAABI8sGdjzVr1mj27Nn6yle+opEjRyonJ0d//OMf+zy+ublZLper2wuXLjstXpPSHWptN/XO7kqrywEAwMPr4ePo0aNauXKlJkyYoPfff18PPPCAHn74Yb3yyiu9Hp+fny+n0+l5ZWZmeruksJXXNfXCUy8AgEBimF5uAhIVFaXZs2dr06ZNnvcefvhhbd26VR9//HGP45ubm9Xc3Oz52uVyKTMzU3V1dXI4HN4sLexU1J3T/F98JNOUNj76BY1OjLW6JABAiHK5XHI6nQP6/e31Ox/p6emaPHlyt/cmTZqkEydO9Hp8dHS0HA5Htxe8I905TFdn0ekWABBYvB4+rrnmGh08eLDbe4cOHdLYsWO9fSoMQF7OZ1MvdLoFAAQCr4eP73znO9q8ebN+/vOf69NPP9Vrr72mF154QUuXLvX2qTAAX5qarqgImw6datD+inqrywEAwPvh46qrrtKqVav0+uuva+rUqfrJT36iZ599VnfddZe3T4UBcA6L1EI63QIAAojXF5xeqsEsWMHAvLenUg+8ul1pjhhteuyLstkMq0sCAIQYSxecIvB8YWKKHDF2VbrOa3MJnW4BANYifISBaHuEp9Pt6iKeegEAWIvwESbyujrdvrOngk63AABLET7CxJxxScpwxqj+fJvW0ukWAGAhwkeYsNkMLXFvt85TLwAACxE+wsgdXVMvaw9Uq66p1eJqAADhivARRiamOTQxLV4t7R16d0+F1eUAAMIU4SPM5M7svPuxik63AACLED7CzO1d6z62lNSo/Ow5i6sBAIQjwkeYGZUwTHOzkiTR6RYAYA3CRxhy7/lRwNQLAMAChI8wdEtXp9sDlfU6UOmyuhwAQJghfIQhZ2ykvjAxRZJUwHbrAAA/I3yEqbyup17WFJepoyOgGhsDAEIc4SNMfWHiSMXH2FVed16fHKuxuhwAQBghfISpmMgI3TK1q9Mt260DAPyI8BHGcnM69/x4e1eFmtvodAsA8A/CRxi7OitZaY4Yuc63ae2BaqvLAQCECcJHGLPZDM+Op0y9AAD8hfAR5txPvXx4oEp15+h0CwDwPcJHmJuUHq8rUoerpa1D79HpFgDgB4SPMGcYhqfTLRuOAQD8gfAB5Xat+9hcckYVdXS6BQD4FuEDGp0YqznjkmSa0ppi7n4AAHyL8AFJn+35UUD4AAD4GOEDkqRbp6UrMsLQ/gqXDp2qt7ocAEAII3xAkpQQG6UF2SMlSQVF7PkBAPAdwgc83Ht+rC4up9MtAMBnCB/wWDhppIZH21V29py2Ha+1uhwAQIgifMAjJjJCi6emSZIK2G4dAOAjhA90k5fTOfXy9q4KtbR1WFwNACAUET7QzdXjkzUyPlp151q17mCV1eUAAEIQ4QPdRNgMz46nq9nzAwDgA4QP9ODu9fL3/afkOk+nWwCAdxE+0MOUDIcuHzlczW0dem9PpdXlAABCDOEDPRiGoTzP1AtPvQAAvIvwgV65p142HTmjU67zFlcDAAglhA/0KjMpVrPHJtLpFgDgdYQP9Cm3a88PNhwDAHgT4QN9um1auuw2Q3vLXTpMp1sAgJcQPtCnxLgoLchOkcTdDwCA9xA+0K/cz3W6NU063QIALh3hA/26cVKqhkfbdbL2nLbT6RYA4AWED/RrWFSEbp5Cp1sAgPf4PHzk5+fLMAwtW7bM16eCj+TldG449jc63QIAvMCn4WPr1q164YUXNH36dF+eBj42/7IRSomP1tmmVm04VG11OQCAIOez8NHQ0KC77rpLf/zjH5WYmOir08APImyGbp/RefeDqRcAwKXyWfhYunSpbr31Vt14442+OgX8KK/rqZfCfadUT6dbAMAlsPvih77xxhvasWOHtm7detFjm5ub1dzc7Pna5XL5oiRcoqmjHBqfEqej1Y16f+8p3TlrtNUlAQCClNfvfJSWluqRRx7Rq6++qpiYmIsen5+fL6fT6XllZmZ6uyR4gWEYusOz5wdTLwCAoTNML+8cVVBQoDvuuEMRERGe99rb22UYhmw2m5qbm7t9r7c7H5mZmaqrq5PD4fBmabhEJ8406fpfrpXNkDY/vlAjHRcPlwCA8OByueR0Ogf0+9vr0y4LFy7U7t27u713//33a+LEiXr00Ue7BQ9Jio6OVnR0tLfLgA+MSY7VlWMStOPEWa3ZWa7/77rxVpcEAAhCXg8f8fHxmjp1arf34uLilJyc3ON9BJ87ckZpx4mzWl1M+AAADA07nGJQbp2eIbvN0O6yOn1a1WB1OQCAIOSX8LFu3To9++yz/jgVfCwpLkrXX9HZ6ZaFpwCAoeDOBwYtd2bnhmN0ugUADAXhA4O2aHKa4qIidKKmSTtOnLW6HABAkCF8YNC6dbotYuoFADA4hA8MSW5O54Zjb++uUGs7nW4BAANH+MCQXHNZskYMj1ZNY4v+cZhOtwCAgSN8YEjsETYtmZEuSSooKre4GgBAMCF8YMjcnW4/2FephuY2i6sBAAQLwgeGbPpop7JGxOl8a4c+2FtpdTkAgCBB+MCQGYbhuftRUMzUCwBgYAgfuCTuDcc2Hq5WVf15i6sBAAQDwgcuybgRcZqZmaAOU/rbzgqrywEABAHCBy7ZHV17ftDrBQAwEIQPXLJbp6crwmZo58k6Ha2m0y0AoH+ED1yyEcOjdd2EEZJYeAoAuDjCB7zi81MvdLoFAPSH8AGvuGlyqmKjInT8TJOKS89aXQ4AIIARPuAVsVF2LZqcKolOtwCA/hE+4DXuTrd/20WnWwBA3wgf8JrrLh+h5LgonWls0cZPT1tdDgAgQBE+4DWdnW47dzxl6gUA0BfCB7zKvd36B3tPqZFOtwCAXhA+4FUzMxM0LjlW51rbVbjvlNXlAAACEOEDXmUYhnK7Ot2uYuoFANALwge8Lq/rqZeNn57W6YZmi6sBAAQawge8LmtEnGZkJqi9w9TfdrLdOgCgO8IHfCKva+EpvV4AABcifMAnbpueoQiboeLSsyo53Wh1OQCAAEL4gE+kxEfrmss7O92uLmbhKQDgM4QP+MwdOZ1TL6uLy+l0CwDwIHzAZxZNTtOwyAiVnG7UzpN1VpcDAAgQhA/4TFy0XTfR6RYAcAHCB3zqDk+n23K10ekWACDCB3zs2gkjlBQXpdMNLfrnkTNWlwMACACED/hUZIRNt01Pl8TUCwCgE+EDPufu9fL+3ko1tdDpFgDCHeEDPnflmASNSYpVUwudbgEAhA/4gWEYn223ztQLAIQ9wgf8IrfrqZcNh0/rDJ1uASCsET7gF5elDNf00U61d5h6e3eF1eUAACxE+IDfuBeermLqBQDCGuEDfrNkRrpshlR04qyOn6HTLQCEK8IH/GZkfMznOt2WW1wNAMAqhA/4VV7X1EtBcRmdbgEgTBE+4Fc3T01TTKRNR6sbtbuMTrcAEI4IH/Cr4dF23TjJ3emWqRcACEdeDx/5+fm66qqrFB8fr5EjRyovL08HDx709mkQxNydbv9Kp1sACEteDx/r16/X0qVLtXnzZhUWFqqtrU2LFi1SYyNPN6DT9VekKDE2UtX1zdpEp1sACDt2b//A9957r9vXL730kkaOHKnt27fr+uuv9/bpEIQiI2y6dXq6Xt18QgXFZbr+ihSrSwIA+JHP13zU1XUuKkxKSur1+83NzXK5XN1eCH3uqZf391TqXEu7xdUAAPzJp+HDNE0tX75c1157raZOndrrMfn5+XI6nZ5XZmamL0tCgLhyTKIyk4apsaVdhfvpdAsA4cSn4ePBBx/Url279Prrr/d5zOOPP666ujrPq7S01JclIUAYhqHcGZ13P1az3ToAhBWfhY+HHnpIa9as0dq1azV69Og+j4uOjpbD4ej2QnjIy8mQJK0/VK2axhaLqwEA+IvXw4dpmnrwwQf1l7/8RR999JGysrK8fQqEiMtHxmvqKIfa6HQLAGHF6+Fj6dKlevXVV/Xaa68pPj5elZWVqqys1Llz57x9KoQAz3brTL0AQNjwevhYuXKl6urqtGDBAqWnp3teb775prdPhRCwZEaGDEPafrxWpTVNVpcDAPADn0y79Pa67777vH0qhIBUR4yuuczd6Za7HwAQDujtAsvlzuxceLqqiE63ABAOCB+w3JempinabtOR6kbtLWeTOQAIdYQPWC4+JlI3TnZ3umXqBQBCHeEDAcH91MuaneVq72DqBQBCGeEDAeGGK1KUEBupqvpmfUynWwAIaYQPBIQou023TEuXJBXw1AsAhDTCBwKGu9Pte3sqdb6VTrcAEKoIHwgYs8YkalTCMDU0t+nvdLoFgJBF+EDAsNkMz54fBUXlFlcDAPAVwgcCinvqZf2hKtXS6RYAQhLhAwFlQmq8Jqc71NpOp1sACFWEDwScvJzOqRd6vQBAaCJ8IODcPmOUDEPaeqxWJ2vpdAsAoYbwgYCT5ozRvPHJkqTVxSw8BYBQQ/hAQHJvt15Ap1sACDmEDwSkL01LU5TdpsNVDdpXQadbAAglhA8EJEdMpG6cNFISUy8AEGoIHwhYuV1TL6uLy+h0CwAhhPCBgLUgO0WOGLtOuZq15SidbgEgVNitLgDoS7Q9QrdOz9Drn5xQQXGZ5l8+wuqSgLDnOt+qstpzVpeBS2QY0sQ0h2XnJ3wgoOXN7Awf7+6u1JO5UxUTGWF1SUBYqW1s0dZjNdpSUqMtJWe0r9wlZkGDX5TdpkM/XWzZ+QkfCGhXjUtShjNG5XXn9dGBKt0yLd3qkoCQVl3frE9KavRJyRltKanRgcr6HsckxUUpwmZYUB28JSrC2lUXhA8ENJvN0O0zR+kP64+ooKiM8AF4WWXdeW3pChpbjp7RkerGHsdcPnK45mQlaW5WkuZmJSvNGWNBpQglhA8EvDtyOsPHuoPVOtvUooTYKKtLAoJWaU2TtnzuzsbxMz1bGExMi+8MGuOTNScrSSOGR1tQKUIZ4QMBLzstXhPT4nWgsl7v7K7U1+eOsbokICiYpqljZ5q05eiZrsBRo7Kz3ReL2gxpSoZTc7OSNKfrRcCHrxE+EBTyckbpF+8eUEFxGeED6INpmvq0qkGbu6ZQPimpUVV9c7dj7DZD00Y7NTcrWXOzkjRrXKIcMZEWVYxwRfhAULh9Roaeeu+A529uoxKGWV0SYLmODlMHKus712wcrdEnx2pU09jS7ZioCJtmZiZo7vjOuxqzxiYqNoo/+mEt/g9EUMhIGKa5WUnafLRGv1/7qe6ZN1bjRwxXlJ198hA+2to7tLfcpU+6Hnv9pKRGrvNt3Y6JibTpyjGJmpvVuV4jZ0wCj6gj4BA+EDTuyBmlzUdr9NqWE3ptywlFRhi6LGW4stPiPetCstMcynDGyDB4DBDBr6WtQ7vLznY9iVKj7cdr1dDcPWzERUVo1rjOJ1GuHp+kaaMSCOUIeIQPBI28nFE6Ut2oHcdrdbCyXvXNbTpQWd9jH4L4GHtXEOkMI+5/Z14bge58a7uKS892TaGc0fbjtTrf2tHtmPgYu+aMS9Lc8Z2PvU7JcMhu8Z4NwGAZpmkG1F51LpdLTqdTdXV1cjis2/oVgc00TZWdPaeDXeGj858uHa1uVFsf2y+OShjW7S7JxDSHxqfEKZI/uGGRppY27Th+1rPPRnHpWbW0dQ8bibGRXXtsJGvu+CRNTHOwwRcC0mB+fxM+EFJa2jp0pLrBE0oOVLp0sLJeFXXnez3+wqmbSWkOZafFK52pG/hA/flWbTteqy1HO9ds7D5Z1yMsp8RHd23m1bnPxuUpw2UjbCAIDOb3N9MuCClRdpsmpTs0Kb37//h1Ta06eKpeBytd2t91p+RgZb0a+pi6ccTYP3eXpHPq5gqmbjBIdU2t+uRY12Ovx2q0p6yuR1+UDGeMZzOvuVlJyhoRR/BFyOPOB8LWhVM3ndM3A5u6mfi5YMLUDdzONDR3PYlSo81Hz+jgqXpd+CfsmKRYz12NuVlJGp04jLCBkMC0C3AJmtvadbS6UQcqXZ71JAOZupl4wQJXpm5C3ynXeU9PlC0lNfq0qqHHMeNT4jQ3K1lXd+2zke5kjxqEJsIH4AN1Ta2da0hO1XcLJRc++ujmiLFrYtcaEvfdEqZugtvJ2qbOOxtdazaO9dIXJTs13vMkypysJKXE0xcF4YHwAfiJaZo6Wds5dfNZKLn41M1njwIzdROoTNPU8TNNn+v42ntflMkZDs0Z1/kkypxxSUqMoy8KwhPhA7BYc1u7jlQ16uAppm6ChWmaOlLdoM1Hazw7iJ5yde+LEmEzNG2Us2vNRpJmj0viThbQhfABBChvTN1kp8Urnl94l6yjw9TBU/XdOr6e6aUvyoxMp2cKZdbYRMVF85Ag0BvCBxBE+pq6OVLdqPYBTt1MSncoawRTN/1pa+/Q/orOJmybj9Zo67Ea1Z1r7XZMtL2rL0rX4tArxyTSFwUYIMIHEAJ6m7o5UFGvStcAp27SO++UpDnCc+qmtb1Du8vqPItDtx3r2RclNipCs8Ym6uqux16njXYq2k7YAIaC8AGEsLNNLd3ukhyocOnQqYYBTd24A8kVqaE3ddPc1q6dpXWeaZTtx2t1rrW92zHxMXZdNe6z3UOnZDi4WwR4CeEDCDMXTt3sr+jcVv7o6YtP3UxM/2yRazBN3ZxradeOE7WefTaKeumLkhAb2dWErfPOxqR0+qIAvkL4ACDpgqmbis8WufY1dRMVYdP4lLiuUOLwLHINhKmbhuY2bTtW49lBdNfJs2pt7/7H14jh0V17bHTuszFhJH1RAH8JiPDx/PPP65e//KUqKio0ZcoUPfvss7ruuusu+jnCB+B77qmbz28r39/UjXNYpLJT3XdJ/DN1U3euVVu7Hnn9pKRGe8pdPe7ipDtjNDcrSXO6Or6Opy8KYBnLw8ebb76pu+++W88//7yuueYa/c///I9efPFF7du3T2PGjOn3s4QPwBruqRt3GHHfJbnY1M2kdPdTN5c2dVPT2KJPup5E+aSkRvsrXT36omQmDetsLd91ZyMzib4oQKCwPHzMnTtXV155pVauXOl5b9KkScrLy1N+fn6/n/VZ+DBNqbXnVsgA+tfc1q4j1Y06fKpeh0816GDXP0/V9z11k5UyXNmpwzUhNV4TUjv/PTW++9RNVf15bTtWq63HarTtWK0+re7ZFyUrOU6zsxJ11dgkzR6XSF8UwJsiYyUvhvfB/P72+m45LS0t2r59ux577LFu7y9atEibNm3qcXxzc7Oamz/bRdDlcnm7pE6tTdLPM3zzs4EQFi1pcterm5h+PnS263Ww70NGSrql69Xnz2uUtKfrBcC7niiXouIsObXXl7WfPn1a7e3tSk1N7fZ+amqqKisrexyfn58vp9PpeWVmZnq7JAAAEEB8tk/whfOwpmn2Ojf7+OOPa/ny5Z6vXS6XbwJIZGxnygPgd+db23X0dOfUTUXdeWWnxevKMYlyDgutvUaAoBIZa9mpvR4+RowYoYiIiB53OaqqqnrcDZGk6OhoRUf7oeW0YVh2ewkIdzFR0uQ4hyaPTbe6FAABwOvTLlFRUZo1a5YKCwu7vV9YWKj58+d7+3QAACDI+GTaZfny5br77rs1e/ZszZs3Ty+88IJOnDihBx54wBenAwAAQcQn4eNrX/uazpw5oyeffFIVFRWaOnWq3nnnHY0dO9YXpwMAAEGE7dUBAMAlG8zv7+DoIAUAAEIG4QMAAPgV4QMAAPgV4QMAAPgV4QMAAPgV4QMAAPgV4QMAAPgV4QMAAPgV4QMAAPiVT7ZXvxTuDVddLpfFlQAAgIFy/94eyMbpARc+6uvrJUmZmZkWVwIAAAarvr5eTqez32MCrrdLR0eHysvLFR8fL8MwvPqzXS6XMjMzVVpaGpJ9Y0L9+qTQv0auL/iF+jWG+vVJoX+Nvro+0zRVX1+vjIwM2Wz9r+oIuDsfNptNo0eP9uk5HA5HSP4P5Rbq1yeF/jVyfcEv1K8x1K9PCv1r9MX1XeyOhxsLTgEAgF8RPgAAgF+FVfiIjo7Wj370I0VHR1tdik+E+vVJoX+NXF/wC/VrDPXrk0L/GgPh+gJuwSkAAAhtYXXnAwAAWI/wAQAA/IrwAQAA/IrwAQAA/Crkwsfzzz+vrKwsxcTEaNasWfrHP/7R7/Hr16/XrFmzFBMTo/Hjx+sPf/iDnyodmsFc37p162QYRo/XgQMH/FjxwG3YsEFLlixRRkaGDMNQQUHBRT8TbOM32GsMpjHMz8/XVVddpfj4eI0cOVJ5eXk6ePDgRT8XTGM4lGsMpjFcuXKlpk+f7tl8at68eXr33Xf7/UwwjZ80+GsMpvHrTX5+vgzD0LJly/o9zt/jGFLh480339SyZcv0/e9/X0VFRbruuuu0ePFinThxotfjS0pKdMstt+i6665TUVGRnnjiCT388MN66623/Fz5wAz2+twOHjyoiooKz2vChAl+qnhwGhsbNWPGDK1YsWJAxwfb+EmDv0a3YBjD9evXa+nSpdq8ebMKCwvV1tamRYsWqbGxsc/PBNsYDuUa3YJhDEePHq1f/OIX2rZtm7Zt26YvfvGLys3N1d69e3s9PtjGTxr8NboFw/hdaOvWrXrhhRc0ffr0fo+zZBzNEDJnzhzzgQce6PbexIkTzccee6zX47/3ve+ZEydO7Pbet771LfPqq6/2WY2XYrDXt3btWlOSWVtb64fqvEuSuWrVqn6PCbbxu9BArjGYx7CqqsqUZK5fv77PY4J9DAdyjcE8hqZpmomJieaLL77Y6/eCffzc+rvGYB2/+vp6c8KECWZhYaF5ww03mI888kifx1oxjiFz56OlpUXbt2/XokWLur2/aNEibdq0qdfPfPzxxz2Ov/nmm7Vt2za1trb6rNahGMr1ueXk5Cg9PV0LFy7U2rVrfVmmXwXT+F2qYBzDuro6SVJSUlKfxwT7GA7kGt2CbQzb29v1xhtvqLGxUfPmzev1mGAfv4Fco1uwjd/SpUt166236sYbb7zosVaMY8iEj9OnT6u9vV2pqand3k9NTVVlZWWvn6msrOz1+La2Np0+fdpntQ7FUK4vPT1dL7zwgt566y395S9/UXZ2thYuXKgNGzb4o2SfC6bxG6pgHUPTNLV8+XJde+21mjp1ap/HBfMYDvQag20Md+/ereHDhys6OloPPPCAVq1apcmTJ/d6bLCO32CuMdjGT5LeeOMN7dixQ/n5+QM63opxDLiutpfKMIxuX5um2eO9ix3f2/uBYjDXl52drezsbM/X8+bNU2lpqX71q1/p+uuv92md/hJs4zdYwTqGDz74oHbt2qWNGzde9NhgHcOBXmOwjWF2draKi4t19uxZvfXWW7r33nu1fv36Pn85B+P4DeYag238SktL9cgjj+iDDz5QTEzMgD/n73EMmTsfI0aMUERERI+7AFVVVT0SnVtaWlqvx9vtdiUnJ/us1qEYyvX15uqrr9bhw4e9XZ4lgmn8vCnQx/Chhx7SmjVrtHbtWo0ePbrfY4N1DAdzjb0J5DGMiorS5ZdfrtmzZys/P18zZszQb3/7216PDdbxG8w19iaQx2/79u2qqqrSrFmzZLfbZbfbtX79ev3ud7+T3W5Xe3t7j89YMY4hEz6ioqI0a9YsFRYWdnu/sLBQ8+fP7/Uz8+bN63H8Bx98oNmzZysyMtJntQ7FUK6vN0VFRUpPT/d2eZYIpvHzpkAdQ9M09eCDD+ovf/mLPvroI2VlZV30M8E2hkO5xt4E6hj2xjRNNTc39/q9YBu/vvR3jb0J5PFbuHChdu/ereLiYs9r9uzZuuuuu1RcXKyIiIgen7FkHH22lNUCb7zxhhkZGWn+6U9/Mvft22cuW7bMjIuLM48dO2aapmk+9thj5t133+05/ujRo2ZsbKz5ne98x9y3b5/5pz/9yYyMjDT/7//+z6pL6Ndgr++ZZ54xV61aZR46dMjcs2eP+dhjj5mSzLfeesuqS+hXfX29WVRUZBYVFZmSzN/85jdmUVGRefz4cdM0g3/8THPw1xhMY/if//mfptPpNNetW2dWVFR4Xk1NTZ5jgn0Mh3KNwTSGjz/+uLlhwwazpKTE3LVrl/nEE0+YNpvN/OCDD0zTDP7xM83BX2MwjV9fLnzaJRDGMaTCh2ma5u9//3tz7NixZlRUlHnllVd2ewTu3nvvNW+44YZux69bt87Myckxo6KizHHjxpkrV670c8WDM5jre+qpp8zLLrvMjImJMRMTE81rr73WfPvtty2oemDcj7Rd+Lr33ntN0wyN8RvsNQbTGPZ2XZLMl156yXNMsI/hUK4xmMbwm9/8pufPl5SUFHPhwoWeX8qmGfzjZ5qDv8ZgGr++XBg+AmEcDdPsWlUCAADgByGz5gMAAAQHwgcAAPArwgcAAPArwgcAAPArwgcAAPArwgcAAPArwgcAAPArwgcAAPArwgcAAPArwgcAAPArwgcAAPArwgcAAPCr/x815Xy9al4DZQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exe.output[0].plot_energies()" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "1d5b5203-d07f-485b-9553-9150f4a674e7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[11.122158782511535,\n", + " -0.2782678462106827,\n", + " -0.9960554302957411,\n", + " -3.560246436024868e-08,\n", + " -3.560246436024868e-08]" + ] + }, + "execution_count": 81, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].pot_energies" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "d2cc3b3a-5daa-49bb-9d6d-2994ebc74273", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "903d8dc3dee8402b9a481fa1516cc478", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "NGLWidget(max_frame=4)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exe.output[0].animate_structures()" + ] + }, + { + "cell_type": "markdown", + "id": "32c15731-fd88-40d1-9045-beb9c53de501", + "metadata": { + "tags": [] + }, + "source": [ + "# Murnaghan" + ] + }, + { + "cell_type": "markdown", + "id": "aa535698-ddaa-4d60-862c-d6d6d5ecb6c8", + "metadata": {}, + "source": [ + "## Basic" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "4acdeafc-90b5-4b3f-9559-c74b9fa221ab", + "metadata": {}, + "outputs": [], + "source": [ + "m = MurnaghanNode()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "f8cf3136-9b7c-4f1e-b630-962795527946", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.node = AseStaticNode()\n", + "m.input.node.input.calculator = MorsePotential()\n", + "m.input.structure = bulk(\"Fe\", a=1.2)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "fef21aa4-d9f1-4d4a-8761-af1bc3121e5b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.input.node.input" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "41a68b17-c7c4-4a5f-8f04-11bee18fe55a", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.set_strain_range(.5, 50)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "fd107556-99b6-4042-9209-9412b4bbff94", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0.79370053, 0.8043555 , 0.81473542, 0.82485739, 0.83473686,\n", + " 0.84438786, 0.85382314, 0.86305437, 0.87209225, 0.88094658,\n", + " 0.88962642, 0.89814011, 0.90649538, 0.9146994 , 0.92275884,\n", + " 0.93067991, 0.93846839, 0.94612969, 0.95366889, 0.96109074,\n", + " 0.96839969, 0.97559996, 0.98269548, 0.98968999, 0.996587 ,\n", + " 1.00338986, 1.01010169, 1.0167255 , 1.02326411, 1.0297202 ,\n", + " 1.03609634, 1.04239496, 1.04861836, 1.05476875, 1.06084824,\n", + " 1.06685884, 1.07280247, 1.07868096, 1.08449606, 1.09024946,\n", + " 1.09594278, 1.10157754, 1.10715524, 1.11267731, 1.1181451 ,\n", + " 1.12355993, 1.12892306, 1.13423572, 1.13949907, 1.14471424])" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.input.strains" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "0715614a-7284-4388-ac6b-c97bfedf7184", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.input.check_ready()" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "c2aa3093-1ea8-4099-bc14-be0c06e9d34b", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "exe = m.run(how='foreground')" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "be97853a-f182-4b4f-af74-7fd5a4fbd850", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None),)" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.status" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "47f916ef-b140-49c5-adf1-93dca91b4540", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exe.output[0].plot()" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "14162f5b-1318-4595-8c8c-d6346a21721d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.6788586373205143" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].equilibrium_volume" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "0f5ff296-df33-40d2-851b-02d6ded72dd6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.6788586373205143" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].get_structure().get_volume()" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "92b06330-b1fc-41d0-8bd8-bf1b11bf448c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Atoms(symbols='Fe', pbc=True, cell=[[-0.5536557129291797, 0.5536557129291797, 0.5536557129291797], [0.5536557129291797, -0.5536557129291797, 0.5536557129291797], [0.5536557129291797, 0.5536557129291797, -0.5536557129291797]])" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].get_structure()" + ] + }, + { + "cell_type": "markdown", + "id": "26bade63-559f-4c93-be24-5561f5c8190f", + "metadata": {}, + "source": [ + "## Again but execute children as background processes, but keep the node itself blocking" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "89169376-be36-4ceb-9f4e-6e1f3247bc62", + "metadata": {}, + "outputs": [], + "source": [ + "m = MurnaghanNode()" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "2ee9f1d4-5b14-4340-98d4-4bd293af89a4", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.node = AseStaticNode()\n", + "m.input.node.input.calculator = MorsePotential()\n", + "m.input.structure = bulk(\"Fe\", a=1.2)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "3d73a9de-7b4e-476a-b50a-ac6a3957a7ab", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.input.node.input" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "0f075d90-e636-49be-b1a6-741a56363f54", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.set_strain_range(.6, 1000)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b2f703c-745b-49f9-a81f-a780248e9cd3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "58064e52-1c94-49fd-b38f-614cf6f19004", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.child_executor = ProcessExecutor" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "d82a28ab-1a96-4a3a-8f79-5a875ac20788", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "exe = m.run(how='foreground')" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "5472daed-f25c-4aab-b101-90c76a0235a5", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_machine.state" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "a42865c9-8616-4335-8e46-ec4839daab0a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "27.62762461800594" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_time" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "d894a79f-a9e6-4667-9a9b-2bd9a088622f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.2397998943924904e-05" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._collect_time" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "78017969-23fc-46f5-b99f-cd1d2dc74c00", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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uu+++1RcXCyHw6GxY8fqiSeeiFK18WWg1/aZZ57R5MmTlZaWJrfbrX/8x39UTU1NlKqNHxs2bND8+fNVUFAgi8WiV1555bTPWb9+vaZMmaKUlBSNGTNG//Ef/xH5QuPQQK/tSy+9pDlz5ignJ0eZmZmaPn261qxZE51i40wof2+7hfO9jCASglWrVmnRokW67777tHXrVl1yySWaN2+eDh482OfzGhoadP311+trX/talCqNP6Fc22uuuUZ/+9vf9Pjjj2vXrl167rnnNGHChChWHR8Gem3fffddXX/99brxxhu1fft2vfDCC9q0aZNuuummKFce+3w+nyZPnqwVK1b06/FlZWW6/PLLdckll2jr1q2699579eMf/1gvvvhihCuNPwO9ths2bNCcOXO0evVqbdmyRbNnz9b8+fO1devWCFcafwZ6bbuF/b3MwIBdcMEFxq233trj3IQJE4yf//znfT5vwYIFxj//8z8b999/vzF58uQIVhi/BnptX3/9dcPlchk1NTXRKC+uDfTaPvTQQ8aYMWN6nPvNb35jFBYWRqzGoUCS8fLLL/f5mHvuuceYMGFCj3O33HKLceGFF0awsvjXn2t7KhMnTjQeeOCB8Bc0hAzk2ob7vYwRkQFqbW3Vli1bNHfu3B7n586dq/fff7/X5z355JPau3ev7r///kiXGLdCubavvvqqpk6dqgcffFAjR47U+PHjdffdd6u5uTkaJceNUK7tjBkzdOjQIa1evVqGYejo0aP685//rCuuuCIaJQ9pH3zwwUl/Fl//+te1efNmtbW1mVTV0NTZ2Smv16usrCyzSxkSIvFeFtM3vYtF1dXV6ujoUF5eXo/zeXl5qqysPOVzdu/erZ///Od65513ZLNxyXsTyrXdt2+f3n33XaWkpOjll19WdXW1brvtNtXW1tIncoJQru2MGTP0zDPPaMGCBWppaVF7e7u++c1v6re//W00Sh7SKisrT/ln0d7erurqarndbpMqG3p+9atfyefz6ZprrjG7lLgXqfcyRkRCZLFYenxtGMZJ5ySpo6ND1157rR544AGNHz8+WuXFtf5eWynwrx2LxaJnnnlGF1xwgS6//HI9/PDD+uMf/8ioyCkM5Nru2LFDP/7xj/Uv//Iv2rJli9544w2VlZXp1ltvjUapQ96p/ixOdR6he+6557R06VKtWrVKubm5ZpcT1yL5XsY/zwcoOztbSUlJJ/0rsqqq6qR/4UiS1+vV5s2btXXrVt1xxx2SAm+ehmHIZrPpzTff1KWXXhqV2mPdQK+tJLndbo0cObLH7abPPPNMGYahQ4cOqbS0NKI1x4tQru3y5ct10UUX6ac//akk6ZxzzlF6erouueQS/eIXv+Bf7YOQn59/yj8Lm82mESNGmFTV0LJq1SrdeOONeuGFF3TZZZeZXU7ci+R7GSMiA2S32zVlyhStXbu2x/m1a9dqxowZJz0+MzNTn376qbZt2xY8br31Vp1xxhnatm2bpk2bFq3SY95Ar60kXXTRRTpy5IgaGxuD57744gtZrVYVFhZGtN54Esq1bWpqktXa838RSUlJko7/6x2hmT59+kl/Fm+++aamTp2q5ORkk6oaOp577jndcMMNevbZZ+lpCpOIvpcNut01AT3//PNGcnKy8fjjjxs7duwwFi1aZKSnpxv79+83DMMwfv7znxvXXXddr89n1UzvBnptvV6vUVhYaFx99dXG9u3bjfXr1xulpaXGTTfdZNavELMGem2ffPJJw2azGY899pixd+9e49133zWmTp1qXHDBBWb9CjHL6/UaW7duNbZu3WpIMh5++GFj69atxoEDBwzDOPna7tu3z0hLSzPuuusuY8eOHcbjjz9uJCcnG3/+85/N+hVi1kCv7bPPPmvYbDbj0UcfNSoqKoJHfX29Wb9CzBrotf2ycL2XEURC9OijjxrFxcWG3W43zjvvPGP9+vXB7y1cuNCYOXNmr88liPRtoNd2586dxmWXXWakpqYahYWFxuLFi42mpqYoVx0fBnptf/Ob3xgTJ040UlNTDbfbbfzgBz8wDh06FOWqY9/bb79tSDrpWLhwoWEYp76269atM77yla8YdrvdGD16tLFy5croFx4HBnptZ86c2efjcVwof29PFK73MothMMYKAADMQY8IAAAwDUEEAACYhiACAABMQxABAACmIYgAAADTEEQAAIBpCCIAAMA0BBEAAGAagggAADANQQQAAJiGIAIAAExDEAEAAKb5v1E7qt2HJCfYAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exe.output[0].plot()" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "45162eb2-b23d-45c6-8aad-dfe9a6a484d1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.6818586500998999" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].get_structure().get_volume()" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "f0bcfe59-2168-4e74-9d7a-33d900368907", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.6818586500999" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].equilibrium_volume" + ] + }, + { + "cell_type": "markdown", + "id": "36b17048-3941-4d86-bd2a-e131371f4bad", + "metadata": {}, + "source": [ + "## Again but execute everything in the background." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "10f6c113-1e35-48f0-8878-291129bd8a60", + "metadata": {}, + "outputs": [], + "source": [ + "m = MurnaghanNode()" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "70832c31-040e-49be-b0f7-172f930cf31b", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.node = AseStaticNode()\n", + "m.input.node.input.calculator = MorsePotential()\n", + "m.input.structure = bulk(\"Fe\", a=1.2)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "94f4c51d-b69b-4477-a9db-d0ee7627cee6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.input.node.input" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "9a000824-0a9e-4395-8e07-00e484bc7937", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.set_strain_range(.7, 100)" + ] + }, + { + "cell_type": "markdown", + "id": "82c9d3a2-d93c-41aa-a1af-52bfab8cd8df", + "metadata": {}, + "source": [ + "Use the threading backend just to show off." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "ca50857c-1c2f-4fad-a58c-16b399b8721d", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.executor import BackgroundExecutor\n", + "\n", + "m.input.child_executor = BackgroundExecutor" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "12f20f5c-27a3-4533-ad19-8ec06e1c8a90", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "exe = m.run(how='background')" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "2c5d8bea-49ec-4004-8a54-3ded7a3f413d", + "metadata": {}, + "outputs": [], + "source": [ + "exe.wait()" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "ea7bcb58-0890-487e-bef5-bd3cb36143c1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_machine.state" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "0b7c2912-6847-4262-a62d-7233ca398643", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "12.170459961984307" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_time" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "fd5ca921-2062-4f85-b014-382561e9893a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2.338102785870433e-05" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._collect_time" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "4d1223f7-2d72-413e-b20b-cf42781780bb", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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zk2aNMm49957e/wzJk+ebNx///2pLs1S+tpON954o/H973/fuO+++4ypU6emsULr6G1b/eEPfzAKCgqMo0ePZqI8y+htO/34xz82Jk6c2OXcQw89ZIwbNy5tNVqRJGP9+vWnvGbx4sXGpEmTupxbuHChcfHFF6exMmvpSTudTDa8n3fXm7YajO/pluxBiUQi2rZtm0pLS7ucLy0t1ebNm3v0M2KxmOrr6zV8+PB0lGgJfW2nJ554Qh9++KHuu+++dJdoGX1pq+eff17Tp0/X8uXLNXbsWJ155pn6zne+o+bm5kyUbIq+tNOsWbO0f/9+vfDCCzIMQ4cOHdJvfvMbXXvttZkoeUB57bXXTmjbq666Slu3blVra6tJVVlfNryf98dgfU+35M0Cjxw5omg0Kr/f3+W83+9XTU1Nj37GT37yEzU2NuqGG25IR4mW0Jd2ev/993Xvvffq1VdfldNpyf/9adGXttqzZ482bdokr9er9evX68iRI1q0aJGOHTs2aOeh9KWdZs2apaefflo33nijWlpa1NbWpuuuu04PP/xwJkoeUGpqak7atm1tbTpy5IjGjBljUmXWlg3v5301mN/TLdmDkmCz2bo8NwzjhHMn88wzz2jZsmVat26dRo8ena7yLKOn7RSNRrVgwQLdf//9OvPMMzNVnqX05m8qFovJZrPp6aef1owZM3TNNddoxYoVevLJJwd1L4rUu3bauXOn7rrrLv3gBz/Qtm3b9Mc//lFVVVW6/fbbM1HqgHOytj3ZecRl2/t5bwz293RLxq2RI0fK4XCc8C+22traE/710d26det066236rnnntOVV16ZzjJN19t2qq+v19atW7V9+3bdeeedkuIfwoZhyOl0asOGDfqHf/iHjNSeaX35mxozZozGjh3b5dbgZ599tgzD0P79+1VSUpLWms3Ql3aqqKjQ7Nmz9d3vfleSdN555ykvL0+XXnqp/u3f/o1egU4CgcBJ29bpdGrEiBEmVWVd2fR+3heD/T3dkj0obrdb06ZN08aNG7uc37hxo2bNmvWJ3/fMM8/olltu0dq1a7Ni/Lu37eTz+fTOO+/ozTffTB633367zjrrLL355puaOXNmpkrPuL78Tc2ePVsHDhxQQ0ND8tzu3btlt9s1bty4tNZrlr60U1NTk+z2rm8lDodDUkfvAOI+85nPnNC2GzZs0PTp0+VyuUyqypqy7f28Lwb9e7pZs3NP59lnnzVcLpfx+OOPGzt37jTKysqMvLw8Y+/evYZhGMa9995rfOUrX0lev3btWsPpdBo//elPjYMHDyaP48ePm/UrZERv26m7wTTj+3R621b19fXGuHHjjM9//vPGjh07jMrKSqOkpMS47bbbzPoVMqK37fTEE08YTqfTeOSRR4wPP/zQ2LRpkzF9+nRjxowZZv0KGVNfX29s377d2L59uyHJWLFihbF9+3bjo48+MgzjxLbas2ePkZuba9x9993Gzp07jccff9xwuVzGb37zG7N+hYzobTtl6/u5YfS+rbobTO/plg0ohmEYP/3pT40zzjjDcLvdxoUXXmhUVlYmX7v55puNOXPmJJ/PmTPHkHTCcfPNN2e+8AzrTTt1N5j+mHuit2313nvvGVdeeaWRk5NjjBs3zrjnnnuMpqamDFedeb1tp4ceesiYPHmykZOTY4wZM8a46aabjP3792e46sx76aWXTvm+c7K2evnll40LLrjAcLvdxoQJE4zVq1dnvvAM6207ZfP7eV/+pjobTO/pNsOgDxYAAFiLJeegAACA7EZAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlvP/AXuzOgpGI5yoAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exe.output[0].plot()" + ] + }, + { + "cell_type": "markdown", + "id": "e21f6582-e7ec-43be-80ec-e9ad53aabc43", + "metadata": {}, + "source": [ + "# Combine Minimize and Murnaghan" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "149c52b5-a0ce-4e6b-ba55-d94d33aa2f8a", + "metadata": {}, + "outputs": [], + "source": [ + "m = MurnaghanNode()" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "aca24005-ea49-4389-bc26-f292fd0a75a2", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.node = AseMinimizeNode()\n", + "m.input.node.input.calculator = MorsePotential()\n", + "m.input.node.input.max_steps = 100\n", + "m.input.node.input.output_steps = 10\n", + "m.input.node.input.ionic_force_tolerance = 1e-6\n", + "m.input.node.input.lbfgs()\n", + "\n", + "m.input.structure = bulk(\"Fe\", a=1.2)" + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "id": "4ae990bd-af18-4dae-8500-779c9509f3f6", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.set_strain_range(.5, 500)" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "fa62529f-45e6-4e2d-822d-b1cc433a7223", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.child_executor = ProcessExecutor" + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "0925864e-4dd1-4f4e-ace4-aac09c55e787", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:00 4.789242 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:00 4.517693 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:00 4.251945 0.0000\n", + "LBFGS: 0 14:53:00 3.991875 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:01 3.737364 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:01 3.488292 0.0000\n", + "LBFGS: 0 14:53:01 3.006013 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:01 3.244546 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:01 2.320604 0.0000\n", + "LBFGS: 0 14:53:01 2.544148 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:01 2.772582 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:01 2.101849 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:01 1.678307 0.0000\n", + "LBFGS: 0 14:53:01 1.887783 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:01 1.473327 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:01 1.272749 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:01 1.076481 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:01 0.884435 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:01 0.696523 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 0.156747 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 0.332761 0.0000\n", + "LBFGS: 0 14:53:02 0.512659 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 -0.015462 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 -0.183946 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 -0.510037 0.0000\n", + "LBFGS: 0 14:53:02 -0.348779 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 -0.667792 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 -0.973078 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 -1.265189 0.0000\n", + "LBFGS: 0 14:53:02 -1.120747 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 -0.822116 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 -1.406469 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 -1.544652 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 -1.679799 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 -1.941232 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 -1.811973 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 -2.067636 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:02 -2.191241 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:03 -2.312104 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:03 -2.430279 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:03 -2.545820 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:03 -2.658780 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:03 -2.877160 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:03 -2.769210 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:03 -3.085817 0.0000\n", + "LBFGS: 0 14:53:03 -2.982680 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:03 -3.285134 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:03 -3.186620 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:03 -3.475475 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:03 -3.381404 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:03 -3.567390 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:03 -3.657192 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:03 -3.830620 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:03 -3.744922 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -3.914328 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -4.075925 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -3.996084 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -4.153891 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -4.304338 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -4.376892 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -4.230016 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -4.447711 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -4.516830 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -4.584282 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -4.650099 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -4.714313 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -4.776956 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -4.838057 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -4.897647 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -4.955755 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -5.012409 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:04 -5.067638 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.121469 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.173930 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.225046 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.274844 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.323350 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.370587 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.461356 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.416581 0.0000\n", + "LBFGS: 0 14:53:05 -5.504934 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.547340 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.667742 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.628722 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.588595 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.705677 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.742548 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.813177 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.778375 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:05 -5.846976 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -5.879789 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -5.911636 0.0000\n", + "LBFGS: 0 14:53:06 -5.942536 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -5.972506 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.057017 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.083445 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.001565 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.109029 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.029730 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.133786 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.157731 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.180881 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.224853 0.0000\n", + "LBFGS: 0 14:53:06 -6.203250 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.245705 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.265820 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.321888 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.285213 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.303898 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.355836 0.0000\n", + "LBFGS: 0 14:53:06 -6.339196 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.371820 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.387162 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.401872 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.415965 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.429451 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.454650 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.466386 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.477561 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:06 -6.442342 0.0000\n", + "LBFGS: 0 14:53:06 -6.488186 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.507828 0.0000\n", + "LBFGS: 0 14:53:07 -6.498271 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.525395 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.533425 0.0000\n", + "LBFGS: 0 14:53:07 -6.516865 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.540966 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.548028 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.554620 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.566429 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.560750 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.576465 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.571664 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.580840 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.584797 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.588345 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.591492 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.594245 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.596612 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.598601 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.600220 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.601475 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.602374 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.603132 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.602924 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.603005 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.602549 0.0000\n", + "LBFGS: 0 14:53:07 -6.601771 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.600678 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.599275 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.595568 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:07 -6.597570 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.593275 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.590697 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.587840 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.584710 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.581312 0.0000\n", + "LBFGS: 0 14:53:08 -6.577651 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.573734 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.560493 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.569565 0.0000\n", + "LBFGS: 0 14:53:08 -6.565149 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.555599 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.533761 0.0000\n", + "LBFGS: 0 14:53:08 -6.539551 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.550475 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.545124 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.521548 0.0000\n", + "LBFGS: 0 14:53:08 -6.527759 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.515134 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.508521 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.494713 0.0000\n", + "LBFGS: 0 14:53:08 -6.501712 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.487527 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.480158 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.456996 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.464889 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.472611 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.448936 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.415096 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.440712 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.423789 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:08 -6.432329 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.406254 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.378866 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.388136 0.0000\n", + "LBFGS: 0 14:53:09 -6.397266 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.369460 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.359921 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.350251 0.0000\n", + "LBFGS: 0 14:53:09 -6.340455 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.330535 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.320493 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.310333 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.289669 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.300058 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.279171 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.268565 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.247042 0.0000\n", + "LBFGS: 0 14:53:09 -6.257855 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.236130 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.225120 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.180158 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.191532 0.0000\n", + "LBFGS: 0 14:53:09 -6.214016 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.202819 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:09 -6.157154 0.0000\n", + "LBFGS: 0 14:53:09 -6.168698 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -6.145530 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -6.133827 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -6.122047 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -6.110193 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -6.098266 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -6.086268 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -6.062068 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -6.074202 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -6.049871 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -6.025288 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -6.037610 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -6.012907 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -6.000468 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -5.987974 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -5.975426 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -5.950174 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -5.937473 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -5.924725 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -5.962825 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -5.911931 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -5.899093 0.0000\n", + "LBFGS: 0 14:53:10 -5.886212 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -5.873290 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -5.860327 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -5.821215 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -5.847326 0.0000\n", + "LBFGS: 0 14:53:10 -5.834289 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:10 -5.808107 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.794966 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.768591 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.781794 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.755359 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.728813 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.715501 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.702165 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.742099 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.688805 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.675424 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.662022 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.635159 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.648600 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.621701 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.608226 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.594735 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.581230 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.554180 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.567712 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.540637 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.527083 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.513519 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.499947 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.486366 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.472778 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.459183 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.418369 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.431978 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.404757 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:11 -5.445583 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.391143 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.350293 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.377527 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.363910 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.336676 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.323061 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.295837 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.309448 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.268625 0.0000\n", + "LBFGS: 0 14:53:12 -5.255026 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.282229 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.214262 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.241432 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.227844 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.200687 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.160009 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.146468 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.187119 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.173560 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.132936 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.119414 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.105903 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.092403 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.078915 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:12 -5.051977 0.0000\n", + "LBFGS: 0 14:53:12 -5.065440 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -5.038527 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -5.025090 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -5.011668 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.998260 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.984867 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.971490 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.944782 0.0000\n", + "LBFGS: 0 14:53:13 -4.958128 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.931453 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.918140 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.891567 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.904845 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.878308 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.865067 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.838641 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.825456 0.0000\n", + "LBFGS: 0 14:53:13 -4.851844 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.812292 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.799147 0.0000\n", + "LBFGS: 0 14:53:13 -4.772919 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.786023 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.759837 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.733735 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.720716 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.746775 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.681794 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.694745 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.668865 0.0000\n", + "LBFGS: 0 14:53:13 -4.707720 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:13 -4.655959 0.0000\n", + "LBFGS: 0 14:53:13 -4.643076 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.630216 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.617381 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.591781 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.579018 0.0000\n", + "LBFGS: 0 14:53:14 -4.604569 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.566279 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.553565 0.0000\n", + "LBFGS: 0 14:53:14 -4.528212 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.540876 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.490373 0.0000\n", + "LBFGS: 0 14:53:14 -4.515574 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.477811 0.0000\n", + "LBFGS: 0 14:53:14 -4.502960 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.465275 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.452766 0.0000\n", + "LBFGS: 0 14:53:14 -4.427826 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.440283 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.415396 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.402992 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.390616 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.378267 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.353649 0.0000\n", + "LBFGS: 0 14:53:14 -4.341382 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.365944 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.329142 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.316929 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.280459 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:14 -4.304745 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.256286 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.268359 0.0000\n", + "LBFGS: 0 14:53:15 -4.292588 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.244242 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.220239 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.208280 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.232226 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.172575 0.0000\n", + "LBFGS: 0 14:53:15 -4.184448 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.196350 0.0000\n", + "LBFGS: 0 14:53:15 -4.160732 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.148917 0.0000\n", + "LBFGS: 0 14:53:15 -4.137131 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.113646 0.0000\n", + "LBFGS: 0 14:53:15 -4.125374 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.101948 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.078638 0.0000\n", + "LBFGS: 0 14:53:15 -4.090278 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.055446 0.0000\n", + "LBFGS: 0 14:53:15 -4.067028 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.043894 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.009416 0.0000\n", + "LBFGS: 0 14:53:15 -4.020879 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -4.032372 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -3.997982 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -3.975204 0.0000\n", + "LBFGS: 0 14:53:15 -3.963859 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -3.952544 0.0000\n", + "LBFGS: 0 14:53:15 -3.986578 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -3.930004 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:15 -3.941259 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.918778 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.907582 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.896416 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.885280 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.874174 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.852051 0.0000\n", + "LBFGS: 0 14:53:16 -3.863097 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.841034 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.830047 0.0000\n", + "LBFGS: 0 14:53:16 -3.819091 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.808164 0.0000\n", + "LBFGS: 0 14:53:16 -3.797267 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.775562 0.0000\n", + "LBFGS: 0 14:53:16 -3.786400 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.753978 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.743230 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.764755 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.732513 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.721825 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.711167 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.700539 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.668834 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.679373 0.0000\n", + "LBFGS: 0 14:53:16 -3.689941 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.637397 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.658325 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:16 -3.626978 0.0000\n", + "LBFGS: 0 14:53:16 -3.647846 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.616588 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.606228 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.585597 0.0000\n", + "LBFGS: 0 14:53:17 -3.595897 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.575325 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.565084 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.554872 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.544689 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.524412 0.0000\n", + "LBFGS: 0 14:53:17 -3.534536 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.514318 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.504253 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.494218 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.484212 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.474235 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.464287 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.454369 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.434619 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.424788 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.444480 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.414986 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.405213 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.395469 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.385754 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.366410 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.376068 0.0000\n", + "LBFGS: 0 14:53:17 -3.356781 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.347181 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.328067 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.337610 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.309067 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:17 -3.318553 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.299610 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.280781 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.290181 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.252749 0.0000\n", + "LBFGS: 0 14:53:18 -3.271409 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.262065 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.243462 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.215768 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.224971 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.234202 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.206592 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.197445 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.188325 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.170169 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.179233 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.161132 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.152123 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.143141 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.134187 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.125260 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.116361 0.0000\n", + "LBFGS: 0 14:53:18 -3.107488 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.098643 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:18 -3.089825 0.0000\n", + "LBFGS: 0 14:53:18 -3.081034 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -3.063534 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -3.072271 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -3.046140 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -3.054823 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -3.020250 0.0000\n", + "LBFGS: 0 14:53:19 -3.028853 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -3.037483 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -3.011673 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.986101 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.994599 0.0000\n", + "LBFGS: 0 14:53:19 -3.003123 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.977630 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.969185 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.960766 0.0000\n", + "LBFGS: 0 14:53:19 -2.952373 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.944006 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.919060 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.927350 0.0000\n", + "LBFGS: 0 14:53:19 -2.935665 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.910796 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.902558 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.894346 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.877998 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.886159 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.869862 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.853666 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.845605 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.837570 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:19 -2.861751 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:20 -2.829560 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:20 -2.821575 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:20 -2.805679 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 14:53:20 -2.813615 0.0000\n" + ] + } + ], + "source": [ + "exe = m.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "a3069fe3-93bf-4c83-93e7-6d0ac56d8248", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_machine.state" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "71bbb913-7d7a-4bb6-b775-3fbc8e7e1f35", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None),)" + ] + }, + "execution_count": 89, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.status" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "4bf2df15-31dc-474c-b3df-f7c32b0fdaf2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([4.78924238, 4.51769267, 4.25194477, 3.99187529, 3.7373637 ,\n", + " 3.48829227, 3.244546 , 3.00601254, 2.77258214, 2.54414756])" + ] + }, + "execution_count": 90, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].energies[:10]" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "eb0a2daf-9dab-4174-bfee-0cd1ef8c474e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exe.output[0].plot()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.0" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": { + "012a52ac25554558bbcbb8b106b79bef": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "084667f4bad34f3d897a790ea169d3d1": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": {} + }, + "0ae500050c904bdcad53b5d936c381f0": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "SliderStyleModel", + "state": { + "description_width": "" + } + }, + "0cc6cbf6ee384fd88783d0d2b3c7a740": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "189a33f4d4984b8094066039bebff284": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "width": "34px" + } + }, + "1b10815147524b69949cca8b3947a15f": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "2261898b13bf45168106ac3c693491f4": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_f819f493d6424b3f988fffcd4a06461e", + "max" + ], + "target": [ + "IPY_MODEL_9acd0738c709420293ca03c9d7cb65a8", + "max_frame" + ] + } + }, + "23bbcda59ee344d59c43eb48b5fab38a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "icon": "compress", + "layout": "IPY_MODEL_189a33f4d4984b8094066039bebff284", + "style": "IPY_MODEL_9a484ae0199f40df9ea9dcb504a7519b" + } + }, + "28e019aae94f4db4beeeba0004eb69c6": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "IntSliderModel", + "state": { + "layout": "IPY_MODEL_1b10815147524b69949cca8b3947a15f", + "max": 4, + "style": "IPY_MODEL_0ae500050c904bdcad53b5d936c381f0" + } + }, + "29a4b696a29b4a339c0207927b43c38d": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_f318329ff0ab4284832df6987aa08caa", + "value" + ], + "target": [ + "IPY_MODEL_3a33fd7cc94d455ab40b075b6011194b", + "value" + ] + } + }, + "2af614f0e8e6415a8f3d3387518d8d51": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "38bc5381b0a34a2bb5c26de4e9ced220": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ImageModel", + "state": { + "layout": "IPY_MODEL_eb67e23591be4890bc094d2dbfd48737", + "width": "900.0" + } + }, + "3a33fd7cc94d455ab40b075b6011194b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "IntSliderModel", + "state": { + "layout": "IPY_MODEL_5baf9865602342759674d426310ebe57", + "max": 0, + "style": "IPY_MODEL_f6e844f90dc344f99829326587cce4a8" + } + }, + "3f9330a5f2a140daa6098330fdfdfb44": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "width": "34px" + } + }, + "412c481251fe49ef97872f1a95c86765": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "width": "34px" + } + }, + "4315b7650344477ba62e3de9d829e8d0": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "PlayModel", + "state": { + "layout": "IPY_MODEL_0cc6cbf6ee384fd88783d0d2b3c7a740", + "max": 21, + "style": "IPY_MODEL_c486ea6139d24312a32942eeb4308084" + } + }, + "50ee641c092b4f62aae9eff65eef8291": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "5359d4fda4d242998c39e2f4a8b65bca": { + "model_module": "nglview-js-widgets", + "model_module_version": "3.0.1", + "model_name": "ColormakerRegistryModel", + "state": { + "_msg_ar": [], + "_msg_q": [], + "_ready": true, + "layout": "IPY_MODEL_c08c0a5b38fb4c7eaa042e1cf2ae75a5" + } + }, + "55c919e973dd4285983c523c55229f5c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_b579f6e205824ec698867f439d52b70c", + "value" + ], + "target": [ + "IPY_MODEL_28e019aae94f4db4beeeba0004eb69c6", + "value" + ] + } + }, + "5baf9865602342759674d426310ebe57": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "5cff2d0d15044fca9105fa3541411380": { + "model_module": "nglview-js-widgets", + "model_module_version": "3.0.1", + "model_name": "NGLModel", + "state": { + "_camera_orientation": [ + 14, + 0, + 0, + 0, + 0, + 14, + 0, + 0, + 0, + 0, + 14, + 0, + 0, + 0, + 0, + 1 + ], + "_camera_str": "orthographic", + "_gui_theme": null, + "_ibtn_fullscreen": "IPY_MODEL_23bbcda59ee344d59c43eb48b5fab38a", + "_igui": null, + "_iplayer": "IPY_MODEL_a0a84a448aa749a2bc0c8bca3962bdcb", + "_ngl_color_dict": {}, + "_ngl_coordinate_resource": {}, + "_ngl_full_stage_parameters": { + "ambientColor": 14540253, + "ambientIntensity": 0.2, + "backgroundColor": "white", + "cameraEyeSep": 0.3, + "cameraFov": 40, + "cameraType": "orthographic", + "clipDist": 10, + "clipFar": 100, + "clipNear": 0, + "fogFar": 100, + "fogNear": 50, + "hoverTimeout": 0, + "impostor": true, + "lightColor": 14540253, + "lightIntensity": 1, + "mousePreset": "default", + "panSpeed": 1, + "quality": "medium", + "rotateSpeed": 2, + "sampleLevel": 0, + "tooltip": true, + "workerDefault": true, + "zoomSpeed": 1.2 + }, + "_ngl_msg_archive": [ + { + "args": [ + { + "binary": false, + "data": "CRYST1 10.000 10.000 10.000 90.00 90.00 90.00 P 1\nMODEL 1\nATOM 0 Fe Fe 0 0.000 0.000 0.000 1.00 0.00 Fe \nATOM 1 Fe Fe 1 0.000 0.000 0.750 1.00 0.00 Fe \nENDMDL \n", + "type": "blob" + } + ], + "kwargs": { + "defaultRepresentation": true, + "ext": "pdb" + }, + "methodName": "loadFile", + "reconstruc_color_scheme": false, + "target": "Stage", + "type": "call_method" + }, + { + "args": [ + "spacefill" + ], + "component_index": 0, + "kwargs": { + "colorScheme": "element", + "radius": 0.7099019513592786, + "radiusType": "vdw", + "sele": "#Fe" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [ + "ball+stick", + 0 + ], + "kwargs": {}, + "methodName": "removeRepresentationsByName", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [ + "unitcell" + ], + "component_index": 0, + "kwargs": { + "sele": "all" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [ + "shape", + [ + [ + "arrow", + [ + -1, + -1, + -1 + ], + [ + 0, + -1, + -1 + ], + [ + 1, + 0, + 0 + ], + 0.1 + ] + ] + ], + "fire_embed": true, + "kwargs": {}, + "methodName": "addShape", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [ + "shape", + [ + [ + "text", + [ + 0, + -1, + -1 + ], + [ + 0, + 0, + 0 + ], + 1, + "x" + ] + ] + ], + "fire_embed": true, + "kwargs": {}, + "methodName": "addShape", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [ + "shape", + [ + [ + "arrow", + [ + -1, + -1, + -1 + ], + [ + -1, + 0, + -1 + ], + [ + 0, + 1, + 0 + ], + 0.1 + ] + ] + ], + "fire_embed": true, + "kwargs": {}, + "methodName": "addShape", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [ + "shape", + [ + [ + "text", + [ + -1, + 0, + -1 + ], + [ + 0, + 0, + 0 + ], + 1, + "y" + ] + ] + ], + "fire_embed": true, + "kwargs": {}, + "methodName": "addShape", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [ + "shape", + [ + [ + "arrow", + [ + -1, + -1, + -1 + ], + [ + -1, + -1, + 0 + ], + [ + 0, + 0, + 1 + ], + 0.1 + ] + ] + ], + "fire_embed": true, + "kwargs": {}, + "methodName": "addShape", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [ + "shape", + [ + [ + "text", + [ + -1, + -1, + 0 + ], + [ + 0, + 0, + 0 + ], + 1, + "z" + ] + ] + ], + "fire_embed": true, + "kwargs": {}, + "methodName": "addShape", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [], + "kwargs": { + "cameraType": "orthographic" + }, + "methodName": "setParameters", + "reconstruc_color_scheme": false, + "target": "Stage", + "type": "call_method" + }, + { + "args": [ + [ + 14, + 0, + 0, + 0, + 0, + 14, + 0, + 0, + 0, + 0, + 14, + 0, + 0, + 0, + 0, + 14 + ] + ], + "kwargs": {}, + "methodName": "orient", + "reconstruc_color_scheme": false, + "target": "viewerControls", + "type": "call_method" + } + ], + "_ngl_original_stage_parameters": { + "ambientColor": 14540253, + "ambientIntensity": 0.2, + "backgroundColor": "white", + "cameraEyeSep": 0.3, + "cameraFov": 40, + "cameraType": "perspective", + "clipDist": 10, + "clipFar": 100, + "clipNear": 0, + "fogFar": 100, + "fogNear": 50, + "hoverTimeout": 0, + "impostor": true, + "lightColor": 14540253, + "lightIntensity": 1, + "mousePreset": "default", + "panSpeed": 1, + "quality": "medium", + "rotateSpeed": 2, + "sampleLevel": 0, + "tooltip": true, + "workerDefault": true, + "zoomSpeed": 1.2 + }, + "_ngl_repr_dict": { + "0": { + "0": { + "params": { + "assembly": "default", + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorMode": "hcl", + "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": 9474192, + "defaultAssembly": "", + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disableImpostor": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.7099019513592786, + "radiusType": "size", + "roughness": 0.4, + "sele": "#Fe", + "side": "double", + "sphereDetail": 1, + "useInteriorColor": true, + "visible": true, + "wireframe": false + }, + "type": "spacefill" + }, + "1": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorMode": "hcl", + "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": "orange", + "defaultAssembly": "", + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disableImpostor": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "radialSegments": 10, + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.049999997805194256, + "radiusType": "vdw", + "roughness": 0.4, + "sele": "all", + "side": "double", + "sphereDetail": 1, + "useInteriorColor": true, + "visible": true, + "wireframe": false + }, + "type": "unitcell" + } + }, + "1": { + "0": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorReverse": false, + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "roughness": 0.4, + "side": "double", + "useInteriorColor": false, + "visible": true, + "wireframe": false + }, + "type": "buffer" + } + }, + "2": { + "0": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorReverse": false, + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "roughness": 0.4, + "side": "double", + "useInteriorColor": false, + "visible": true, + "wireframe": false + }, + "type": "buffer" + } + }, + "3": { + "0": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorReverse": false, + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "roughness": 0.4, + "side": "double", + "useInteriorColor": false, + "visible": true, + "wireframe": false + }, + "type": "buffer" + } + }, + "4": { + "0": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorReverse": false, + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "roughness": 0.4, + "side": "double", + "useInteriorColor": false, + "visible": true, + "wireframe": false + }, + "type": "buffer" + } + }, + "5": { + "0": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorReverse": false, + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "roughness": 0.4, + "side": "double", + "useInteriorColor": false, + "visible": true, + "wireframe": false + }, + "type": "buffer" + } + }, + "6": { + "0": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorReverse": false, + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "roughness": 0.4, + "side": "double", + "useInteriorColor": false, + "visible": true, + "wireframe": false + }, + "type": "buffer" + } + } + }, + "_ngl_serialize": false, + "_ngl_version": "2.0.0-dev.36", + "_ngl_view_id": [ + "4452CC60-66A4-4075-8A04-F4EA21BE6B54" + ], + "_player_dict": {}, + "_scene_position": {}, + "_scene_rotation": {}, + "_synced_model_ids": [], + "_synced_repr_model_ids": [], + "_view_height": "", + "_view_width": "", + "background": "white", + "frame": 0, + "gui_style": null, + "layout": "IPY_MODEL_dfd94ac53b394ccfa01a42103029dbc1", + "max_frame": 0, + "n_components": 7, + "picked": {} + } + }, + "616b7105d7a14e799fd71f9c7e2ddbc1": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "68113cbdde8a441ab41682def194c341": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "691a27593c0c43d1bad168acc42a0d00": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_4315b7650344477ba62e3de9d829e8d0", + "value" + ], + "target": [ + "IPY_MODEL_9acd0738c709420293ca03c9d7cb65a8", + "frame" + ] + } + }, + "6b34af6fbd8d42879280927d8a9f0815": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ImageModel", + "state": { + "layout": "IPY_MODEL_c5afa7b7bf62440caa2e336d30e3f87a", + "width": "900.0" + } + }, + "6c738e0cd99447eea546fb257581d104": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": {} + }, + "6e92491cab9d4064961c20be5d0352dc": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_f318329ff0ab4284832df6987aa08caa", + "value" + ], + "target": [ + "IPY_MODEL_5cff2d0d15044fca9105fa3541411380", + "frame" + ] + } + }, + "6fe0ae77b7c843519d9a5307d2b83ec5": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "children": [ + "IPY_MODEL_4315b7650344477ba62e3de9d829e8d0", + "IPY_MODEL_f819f493d6424b3f988fffcd4a06461e" + ], + "layout": "IPY_MODEL_7052c34813d545eb84c5c674eb82cd91" + } + }, + "7052c34813d545eb84c5c674eb82cd91": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "77ca3b8d35044a5982b88d68e38e3289": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "icon": "compress", + "layout": "IPY_MODEL_3f9330a5f2a140daa6098330fdfdfb44", + "style": "IPY_MODEL_084667f4bad34f3d897a790ea169d3d1" + } + }, + "7c40f68c2f2b43aea1a8e6ae3e344c20": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ImageModel", + "state": { + "layout": "IPY_MODEL_bd58d6b40ef94f9abc1d621b42bd030f", + "width": "900.0" + } + }, + "863d1ff1c0f94c7cbfb9dc175d1078a0": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "description_width": "" + } + }, + "903d8dc3dee8402b9a481fa1516cc478": { + "model_module": "nglview-js-widgets", + "model_module_version": "3.0.1", + "model_name": "NGLModel", + "state": { + "_camera_orientation": [ + 11.096625056567845, + 0, + 0, + 0, + 0, + 11.096625056567845, + 0, + 0, + 0, + 0, + 11.096625056567845, + 0, + 0, + 0, + -0.375, + 1 + ], + "_camera_str": "orthographic", + "_gui_theme": null, + "_ibtn_fullscreen": "IPY_MODEL_77ca3b8d35044a5982b88d68e38e3289", + "_igui": null, + "_iplayer": "IPY_MODEL_abb060530b644eb7ac83734007e72760", + "_ngl_color_dict": {}, + "_ngl_coordinate_resource": {}, + "_ngl_full_stage_parameters": { + "ambientColor": 14540253, + "ambientIntensity": 0.2, + "backgroundColor": "white", + "cameraEyeSep": 0.3, + "cameraFov": 40, + "cameraType": "orthographic", + "clipDist": 10, + "clipFar": 100, + "clipNear": 0, + "fogFar": 100, + "fogNear": 50, + "hoverTimeout": 0, + "impostor": true, + "lightColor": 14540253, + "lightIntensity": 1, + "mousePreset": "default", + "panSpeed": 1, + "quality": "medium", + "rotateSpeed": 2, + "sampleLevel": 0, + "tooltip": true, + "workerDefault": true, + "zoomSpeed": 1.2 + }, + "_ngl_msg_archive": [ + { + "args": [ + { + "binary": false, + "data": "MODEL 1\nATOM 1 Fe MOL 1 0.000 0.000 0.000 1.00 0.00 FE \nATOM 2 Fe MOL 1 0.000 0.000 0.750 1.00 0.00 FE \nENDMDL\n", + "type": "blob" + } + ], + "kwargs": { + "defaultRepresentation": true, + "ext": "pdb", + "name": "nglview.adaptor.ASETrajectory" + }, + "methodName": "loadFile", + "reconstruc_color_scheme": false, + "target": "Stage", + "type": "call_method" + }, + { + "args": [ + "spacefill" + ], + "component_index": 0, + "kwargs": { + "radius": 0.5, + "radiusType": "vdw", + "scale": 0.5, + "sele": "all" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [ + "ball+stick", + 0 + ], + "kwargs": {}, + "methodName": "removeRepresentationsByName", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [ + "unitcell" + ], + "component_index": 0, + "kwargs": { + "sele": "all" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [], + "kwargs": { + "cameraType": "orthographic" + }, + "methodName": "setParameters", + "reconstruc_color_scheme": false, + "target": "Stage", + "type": "call_method" + } + ], + "_ngl_original_stage_parameters": { + "ambientColor": 14540253, + "ambientIntensity": 0.2, + "backgroundColor": "white", + "cameraEyeSep": 0.3, + "cameraFov": 40, + "cameraType": "perspective", + "clipDist": 10, + "clipFar": 100, + "clipNear": 0, + "fogFar": 100, + "fogNear": 50, + "hoverTimeout": 0, + "impostor": true, + "lightColor": 14540253, + "lightIntensity": 1, + "mousePreset": "default", + "panSpeed": 1, + "quality": "medium", + "rotateSpeed": 2, + "sampleLevel": 0, + "tooltip": true, + "workerDefault": true, + "zoomSpeed": 1.2 + }, + "_ngl_repr_dict": { + "0": { + "0": { + "params": { + "assembly": "default", + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorMode": "hcl", + "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": 9474192, + "defaultAssembly": "", + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disableImpostor": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.5, + "radiusType": "size", + "roughness": 0.4, + "sele": "all", + "side": "double", + "sphereDetail": 1, + "useInteriorColor": true, + "visible": true, + "wireframe": false + }, + "type": "spacefill" + }, + "1": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorMode": "hcl", + "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": "orange", + "defaultAssembly": "", + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disableImpostor": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "radialSegments": 10, + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.5, + "radiusType": "vdw", + "roughness": 0.4, + "sele": "all", + "side": "double", + "sphereDetail": 1, + "useInteriorColor": true, + "visible": true, + "wireframe": false + }, + "type": "unitcell" + } + } + }, + "_ngl_serialize": false, + "_ngl_version": "2.0.0-dev.36", + "_ngl_view_id": [ + "242406BE-824C-47E3-BAAF-8680FC355719" + ], + "_player_dict": {}, + "_scene_position": {}, + "_scene_rotation": {}, + "_synced_model_ids": [], + "_synced_repr_model_ids": [], + "_view_height": "", + "_view_width": "", + "background": "white", + "frame": 0, + "gui_style": null, + "layout": "IPY_MODEL_af292d89fcf44b87900031c9b27c2533", + "max_frame": 4, + "n_components": 1, + "picked": {} + } + }, + "941d6b9e9e004db982b9b599e4735ef8": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_3a33fd7cc94d455ab40b075b6011194b", + "max" + ], + "target": [ + "IPY_MODEL_5cff2d0d15044fca9105fa3541411380", + "max_frame" + ] + } + }, + "97714c28adb94f97ac44e3819311d316": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "SliderStyleModel", + "state": { + "description_width": "" + } + }, + "982d09abf73240c898d5c545d08ed9b6": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "9a484ae0199f40df9ea9dcb504a7519b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": {} + }, + "9acd0738c709420293ca03c9d7cb65a8": { + "model_module": "nglview-js-widgets", + "model_module_version": "3.0.1", + "model_name": "NGLModel", + "state": { + "_camera_orientation": [ + 16.91904731270948, + 0, + 0, + 0, + 0, + 16.91904731270948, + 0, + 0, + 0, + 0, + 16.91904731270948, + 0, + -1.5, + -1.5, + -1.5, + 1 + ], + "_camera_str": "orthographic", + "_gui_theme": null, + "_ibtn_fullscreen": "IPY_MODEL_db8318e27dd048d3b57c56c61fe0125c", + "_igui": null, + "_iplayer": "IPY_MODEL_6fe0ae77b7c843519d9a5307d2b83ec5", + "_ngl_color_dict": {}, + "_ngl_coordinate_resource": {}, + "_ngl_full_stage_parameters": { + "ambientColor": 14540253, + "ambientIntensity": 0.2, + "backgroundColor": "white", + "cameraEyeSep": 0.3, + "cameraFov": 40, + "cameraType": "orthographic", + "clipDist": 10, + "clipFar": 100, + "clipNear": 0, + "fogFar": 100, + "fogNear": 50, + "hoverTimeout": 0, + "impostor": true, + "lightColor": 14540253, + "lightIntensity": 1, + "mousePreset": "default", + "panSpeed": 1, + "quality": "medium", + "rotateSpeed": 2, + "sampleLevel": 0, + "tooltip": true, + "workerDefault": true, + "zoomSpeed": 1.2 + }, + "_ngl_msg_archive": [ + { + "args": [ + { + "binary": false, + "data": "CRYST1 3.600 3.600 3.600 90.00 90.00 90.00 P 1\nMODEL 1\nATOM 1 Fe MOL 1 0.000 0.000 0.000 1.00 0.00 FE \nATOM 2 Fe MOL 1 0.600 0.600 0.600 1.00 0.00 FE \nATOM 3 Fe MOL 1 0.000 0.000 1.200 1.00 0.00 FE \nATOM 4 Fe MOL 1 0.600 0.600 1.800 1.00 0.00 FE \nATOM 5 Fe MOL 1 0.000 0.000 2.400 1.00 0.00 FE \nATOM 6 Fe MOL 1 0.600 0.600 3.000 1.00 0.00 FE \nATOM 7 Fe MOL 1 0.000 1.200 0.000 1.00 0.00 FE \nATOM 8 Fe MOL 1 0.600 1.800 0.600 1.00 0.00 FE \nATOM 9 Fe MOL 1 0.000 1.200 1.200 1.00 0.00 FE \nATOM 10 Fe MOL 1 0.600 1.800 1.800 1.00 0.00 FE \nATOM 11 Fe MOL 1 0.000 1.200 2.400 1.00 0.00 FE \nATOM 12 Fe MOL 1 0.600 1.800 3.000 1.00 0.00 FE \nATOM 13 Fe MOL 1 0.000 2.400 0.000 1.00 0.00 FE \nATOM 14 Fe MOL 1 0.600 3.000 0.600 1.00 0.00 FE \nATOM 15 Fe MOL 1 0.000 2.400 1.200 1.00 0.00 FE \nATOM 16 Fe MOL 1 0.600 3.000 1.800 1.00 0.00 FE \nATOM 17 Fe MOL 1 0.000 2.400 2.400 1.00 0.00 FE \nATOM 18 Fe MOL 1 0.600 3.000 3.000 1.00 0.00 FE \nATOM 19 Fe MOL 1 1.200 0.000 0.000 1.00 0.00 FE \nATOM 20 Fe MOL 1 1.800 0.600 0.600 1.00 0.00 FE \nATOM 21 Fe MOL 1 1.200 0.000 1.200 1.00 0.00 FE \nATOM 22 Fe MOL 1 1.800 0.600 1.800 1.00 0.00 FE \nATOM 23 Fe MOL 1 1.200 0.000 2.400 1.00 0.00 FE \nATOM 24 Fe MOL 1 1.800 0.600 3.000 1.00 0.00 FE \nATOM 25 Fe MOL 1 1.200 1.200 0.000 1.00 0.00 FE \nATOM 26 Fe MOL 1 1.800 1.800 0.600 1.00 0.00 FE \nATOM 27 Fe MOL 1 1.200 1.200 1.200 1.00 0.00 FE \nATOM 28 Fe MOL 1 1.800 1.800 1.800 1.00 0.00 FE \nATOM 29 Fe MOL 1 1.200 1.200 2.400 1.00 0.00 FE \nATOM 30 Fe MOL 1 1.800 1.800 3.000 1.00 0.00 FE \nATOM 31 Fe MOL 1 1.200 2.400 0.000 1.00 0.00 FE \nATOM 32 Fe MOL 1 1.800 3.000 0.600 1.00 0.00 FE \nATOM 33 Fe MOL 1 1.200 2.400 1.200 1.00 0.00 FE \nATOM 34 Fe MOL 1 1.800 3.000 1.800 1.00 0.00 FE \nATOM 35 Fe MOL 1 1.200 2.400 2.400 1.00 0.00 FE \nATOM 36 Fe MOL 1 1.800 3.000 3.000 1.00 0.00 FE \nATOM 37 Fe MOL 1 2.400 0.000 0.000 1.00 0.00 FE \nATOM 38 Fe MOL 1 3.000 0.600 0.600 1.00 0.00 FE \nATOM 39 Fe MOL 1 2.400 0.000 1.200 1.00 0.00 FE \nATOM 40 Fe MOL 1 3.000 0.600 1.800 1.00 0.00 FE \nATOM 41 Fe MOL 1 2.400 0.000 2.400 1.00 0.00 FE \nATOM 42 Fe MOL 1 3.000 0.600 3.000 1.00 0.00 FE \nATOM 43 Fe MOL 1 2.400 1.200 0.000 1.00 0.00 FE \nATOM 44 Fe MOL 1 3.000 1.800 0.600 1.00 0.00 FE \nATOM 45 Fe MOL 1 2.400 1.200 1.200 1.00 0.00 FE \nATOM 46 Fe MOL 1 3.000 1.800 1.800 1.00 0.00 FE \nATOM 47 Fe MOL 1 2.400 1.200 2.400 1.00 0.00 FE \nATOM 48 Fe MOL 1 3.000 1.800 3.000 1.00 0.00 FE \nATOM 49 Fe MOL 1 2.400 2.400 0.000 1.00 0.00 FE \nATOM 50 Fe MOL 1 3.000 3.000 0.600 1.00 0.00 FE \nATOM 51 Fe MOL 1 2.400 2.400 1.200 1.00 0.00 FE \nATOM 52 Fe MOL 1 3.000 3.000 1.800 1.00 0.00 FE \nATOM 53 Fe MOL 1 2.400 2.400 2.400 1.00 0.00 FE \nATOM 54 Fe MOL 1 3.000 3.000 3.000 1.00 0.00 FE \nENDMDL\n", + "type": "blob" + } + ], + "kwargs": { + "defaultRepresentation": true, + "ext": "pdb", + "name": "nglview.adaptor.ASETrajectory" + }, + "methodName": "loadFile", + "reconstruc_color_scheme": false, + "target": "Stage", + "type": "call_method" + }, + { + "args": [ + "spacefill" + ], + "component_index": 0, + "kwargs": { + "radius": 0.5, + "radiusType": "vdw", + "scale": 0.5, + "sele": "all" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [ + "ball+stick", + 0 + ], + "kwargs": {}, + "methodName": "removeRepresentationsByName", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [ + "unitcell" + ], + "component_index": 0, + "kwargs": { + "sele": "all" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [], + "kwargs": { + "cameraType": "orthographic" + }, + "methodName": "setParameters", + "reconstruc_color_scheme": false, + "target": "Stage", + "type": "call_method" + } + ], + "_ngl_original_stage_parameters": { + "ambientColor": 14540253, + "ambientIntensity": 0.2, + "backgroundColor": "white", + "cameraEyeSep": 0.3, + "cameraFov": 40, + "cameraType": "perspective", + "clipDist": 10, + "clipFar": 100, + "clipNear": 0, + "fogFar": 100, + "fogNear": 50, + "hoverTimeout": 0, + "impostor": true, + "lightColor": 14540253, + "lightIntensity": 1, + "mousePreset": "default", + "panSpeed": 1, + "quality": "medium", + "rotateSpeed": 2, + "sampleLevel": 0, + "tooltip": true, + "workerDefault": true, + "zoomSpeed": 1.2 + }, + "_ngl_repr_dict": { + "0": { + "0": { + "params": { + "assembly": "default", + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorMode": "hcl", + "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": 9474192, + "defaultAssembly": "", + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disableImpostor": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.5, + "radiusType": "size", + "roughness": 0.4, + "sele": "all", + "side": "double", + "sphereDetail": 1, + "useInteriorColor": true, + "visible": true, + "wireframe": false + }, + "type": "spacefill" + }, + "1": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorMode": "hcl", + "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": "orange", + "defaultAssembly": "", + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disableImpostor": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "radialSegments": 10, + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.017999999209869933, + "radiusType": "vdw", + "roughness": 0.4, + "sele": "all", + "side": "double", + "sphereDetail": 1, + "useInteriorColor": true, + "visible": true, + "wireframe": false + }, + "type": "unitcell" + } + } + }, + "_ngl_serialize": false, + "_ngl_version": "2.0.0-dev.36", + "_ngl_view_id": [ + "8ED2CDFF-EA6D-4A63-BAAE-A8BF61BE463D" + ], + "_player_dict": {}, + "_scene_position": {}, + "_scene_rotation": {}, + "_synced_model_ids": [], + "_synced_repr_model_ids": [], + "_view_height": "", + "_view_width": "", + "background": "white", + "frame": 0, + "gui_style": null, + "layout": "IPY_MODEL_2af614f0e8e6415a8f3d3387518d8d51", + "max_frame": 21, + "n_components": 1, + "picked": {} + } + }, + "9b9319a09d05487ca4f28005cf495d44": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_4315b7650344477ba62e3de9d829e8d0", + "value" + ], + "target": [ + "IPY_MODEL_f819f493d6424b3f988fffcd4a06461e", + "value" + ] + } + }, + "9c91b7a14718493eb53e9a47ef565c04": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_28e019aae94f4db4beeeba0004eb69c6", + "max" + ], + "target": [ + "IPY_MODEL_903d8dc3dee8402b9a481fa1516cc478", + "max_frame" + ] + } + }, + "a0a84a448aa749a2bc0c8bca3962bdcb": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "children": [ + "IPY_MODEL_f318329ff0ab4284832df6987aa08caa", + "IPY_MODEL_3a33fd7cc94d455ab40b075b6011194b" + ], + "layout": "IPY_MODEL_68113cbdde8a441ab41682def194c341" + } + }, + "abb060530b644eb7ac83734007e72760": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "children": [ + "IPY_MODEL_b579f6e205824ec698867f439d52b70c", + "IPY_MODEL_28e019aae94f4db4beeeba0004eb69c6" + ], + "layout": "IPY_MODEL_616b7105d7a14e799fd71f9c7e2ddbc1" + } + }, + "af292d89fcf44b87900031c9b27c2533": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "b579f6e205824ec698867f439d52b70c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "PlayModel", + "state": { + "layout": "IPY_MODEL_982d09abf73240c898d5c545d08ed9b6", + "max": 4, + "style": "IPY_MODEL_863d1ff1c0f94c7cbfb9dc175d1078a0" + } + }, + "bd58d6b40ef94f9abc1d621b42bd030f": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "c08c0a5b38fb4c7eaa042e1cf2ae75a5": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "c486ea6139d24312a32942eeb4308084": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "description_width": "" + } + }, + "c5afa7b7bf62440caa2e336d30e3f87a": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "c8b5c52c81bc4c9d8bf0b20138613f1d": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_f318329ff0ab4284832df6987aa08caa", + "max" + ], + "target": [ + "IPY_MODEL_5cff2d0d15044fca9105fa3541411380", + "max_frame" + ] + } + }, + "cec9a5a47ffe425788ba310f0381594f": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_b579f6e205824ec698867f439d52b70c", + "value" + ], + "target": [ + "IPY_MODEL_903d8dc3dee8402b9a481fa1516cc478", + "frame" + ] + } + }, + "db8318e27dd048d3b57c56c61fe0125c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "icon": "compress", + "layout": "IPY_MODEL_412c481251fe49ef97872f1a95c86765", + "style": "IPY_MODEL_6c738e0cd99447eea546fb257581d104" + } + }, + "dfd94ac53b394ccfa01a42103029dbc1": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "eb67e23591be4890bc094d2dbfd48737": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "f1f947b6e1fe4a60b61e9cca50328d78": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "description_width": "" + } + }, + "f21fb1ae2a6c4a91ac80d1a58d50587b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_b579f6e205824ec698867f439d52b70c", + "max" + ], + "target": [ + "IPY_MODEL_903d8dc3dee8402b9a481fa1516cc478", + "max_frame" + ] + } + }, + "f318329ff0ab4284832df6987aa08caa": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "PlayModel", + "state": { + "layout": "IPY_MODEL_50ee641c092b4f62aae9eff65eef8291", + "max": 0, + "style": "IPY_MODEL_f1f947b6e1fe4a60b61e9cca50328d78" + } + }, + "f6ae52a0c4a5408ab81b41ae139be0c3": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_4315b7650344477ba62e3de9d829e8d0", + "max" + ], + "target": [ + "IPY_MODEL_9acd0738c709420293ca03c9d7cb65a8", + "max_frame" + ] + } + }, + "f6e844f90dc344f99829326587cce4a8": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "SliderStyleModel", + "state": { + "description_width": "" + } + }, + "f819f493d6424b3f988fffcd4a06461e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "IntSliderModel", + "state": { + "layout": "IPY_MODEL_012a52ac25554558bbcbb8b106b79bef", + "max": 21, + "style": "IPY_MODEL_97714c28adb94f97ac44e3819311d316" + } + } + }, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From e5a40e95d1f46697bb78513df1c76d32d83f759e Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Wed, 8 Mar 2023 17:47:40 +0100 Subject: [PATCH 031/756] First stab and job reconstruction --- notebooks/tinybase/ASE.ipynb | 3441 +++++++------------------- notebooks/tinybase/Basic.ipynb | 364 +-- notebooks/tinybase/TinyJob.ipynb | 1307 ++++++++++ pyiron_contrib/tinybase/__init__.py | 2 + pyiron_contrib/tinybase/container.py | 4 +- pyiron_contrib/tinybase/database.py | 219 ++ pyiron_contrib/tinybase/job.py | 253 ++ pyiron_contrib/tinybase/node.py | 50 +- pyiron_contrib/tinybase/project.py | 100 + pyiron_contrib/tinybase/storage.py | 264 ++ 10 files changed, 3231 insertions(+), 2773 deletions(-) create mode 100644 notebooks/tinybase/TinyJob.ipynb create mode 100644 pyiron_contrib/tinybase/database.py create mode 100644 pyiron_contrib/tinybase/job.py create mode 100644 pyiron_contrib/tinybase/project.py create mode 100644 pyiron_contrib/tinybase/storage.py diff --git a/notebooks/tinybase/ASE.ipynb b/notebooks/tinybase/ASE.ipynb index 385ba4f3c..83daeb82d 100644 --- a/notebooks/tinybase/ASE.ipynb +++ b/notebooks/tinybase/ASE.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 1, "id": "f9ecb713-99e6-4f34-b4c2-fe9f3d96c81e", "metadata": {}, "outputs": [], @@ -20,7 +20,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 2, "id": "31dc7658-dcf0-41a4-9c62-ec92b02e47ea", "metadata": {}, "outputs": [], @@ -30,7 +30,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 3, "id": "e7cc88a5-8492-482e-8c6b-c17ff967fff5", "metadata": {}, "outputs": [], @@ -40,7 +40,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "id": "b3108213-1d94-4354-9537-84982e45683d", "metadata": {}, "outputs": [], @@ -50,7 +50,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "id": "0029125a-55e6-4181-a59b-09f606a1b4dd", "metadata": {}, "outputs": [], @@ -60,7 +60,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "id": "c7c74920-c6b3-4577-a60f-951a0d3276ec", "metadata": {}, "outputs": [], @@ -70,7 +70,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 12, "id": "c6630920-6ab7-4273-883e-999020b1fe5a", "metadata": {}, "outputs": [], @@ -89,7 +89,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 13, "id": "a6af72cb-989b-46c3-a2b5-4d2b9c5fd1eb", "metadata": {}, "outputs": [], @@ -99,7 +99,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 14, "id": "5b2a9d62-3f74-4acf-acb6-e72dcd984704", "metadata": {}, "outputs": [], @@ -109,7 +109,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 15, "id": "1af70322-897e-487d-ba18-239ba5bfb7ba", "metadata": {}, "outputs": [], @@ -119,7 +119,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 16, "id": "273902ef-03f3-4f68-8668-4e6c6055a302", "metadata": {}, "outputs": [ @@ -127,10 +127,10 @@ "data": { "text/plain": [ "(ReturnStatus(Code.DONE, None),\n", - " )" + " )" ] }, - "execution_count": 18, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -141,7 +141,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 17, "id": "497de0b9-5e11-4d6c-8c19-664d0e759ac4", "metadata": {}, "outputs": [ @@ -151,7 +151,7 @@ "-0.00013307075712109978" ] }, - "execution_count": 19, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -162,7 +162,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 18, "id": "57eced2f-6649-4269-b3fa-6061d518f9ee", "metadata": {}, "outputs": [ @@ -172,7 +172,7 @@ "-0.00013307075712109978" ] }, - "execution_count": 20, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -184,477 +184,529 @@ }, { "cell_type": "markdown", - "id": "ca6ebf4d-f7a7-4bc9-8a45-f544df3d7989", - "metadata": { - "tags": [] - }, + "id": "8a102a81-df04-4527-8739-7fe542f0c1fc", + "metadata": {}, "source": [ - "# Murnaghan" + "# ASE MD" ] }, { - "cell_type": "markdown", - "id": "bf7ed983-d810-4193-ac32-b7b50a3f2145", + "cell_type": "code", + "execution_count": 54, + "id": "02cfe01b-0b24-4723-a79b-d41ffb146bf9", "metadata": {}, + "outputs": [], "source": [ - "## Basic" + "md = AseMDNode()" ] }, { "cell_type": "code", - "execution_count": 21, - "id": "ec7e75f1-56b9-4681-902f-65c107e17721", + "execution_count": 55, + "id": "466d1f9a-b707-4c05-a8af-5414d76bd8eb", "metadata": {}, "outputs": [], "source": [ - "m = MurnaghanNode()" + "md.input.structure = bulk(\"Fe\", a=1.2, cubic=True).repeat(3)\n", + "md.input.calculator = MorsePotential()" ] }, { "cell_type": "code", - "execution_count": 22, - "id": "992f6dc3-1a6d-40f3-8429-4b4ce2672fdc", + "execution_count": 56, + "id": "dfdfc027-1608-43ad-9d15-0c649986eb73", "metadata": {}, "outputs": [], "source": [ - "m.input.node = AseStaticNode()\n", - "m.input.node.input.calculator = MorsePotential()\n", - "m.input.structure = bulk(\"Fe\", a=1.2)" + "md.input.steps = 100\n", + "md.input.timestep = 3\n", + "md.input.temperature = 600\n", + "md.input.output_steps = 20" ] }, { "cell_type": "code", - "execution_count": 23, - "id": "cdce8eba-afba-4dcd-923f-717568ccfbb3", + "execution_count": 57, + "id": "db5c7cfe-b075-483e-8b7e-a58cebf1a782", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 12.5 ms, sys: 45.2 ms, total: 57.7 ms\n", + "Wall time: 63.3 ms\n" + ] } ], "source": [ - "m.input.node.input" + "%%time\n", + "exe = md.run(how='process')" ] }, { "cell_type": "code", - "execution_count": 24, - "id": "8faf6257-e776-4257-8abf-2893af06ff2a", + "execution_count": 58, + "id": "80155255-4dcf-48cb-9825-015da13d6ac0", "metadata": {}, "outputs": [], "source": [ - "m.input.set_strain_range(.5, 50)" + "exe.wait()" ] }, { "cell_type": "code", - "execution_count": 25, - "id": "d66e2c31-8341-4771-8ed1-fb679b0a8040", + "execution_count": 59, + "id": "6f7aff4e-9e89-459b-843f-46a4d4139bcf", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "array([0.79370053, 0.8043555 , 0.81473542, 0.82485739, 0.83473686,\n", - " 0.84438786, 0.85382314, 0.86305437, 0.87209225, 0.88094658,\n", - " 0.88962642, 0.89814011, 0.90649538, 0.9146994 , 0.92275884,\n", - " 0.93067991, 0.93846839, 0.94612969, 0.95366889, 0.96109074,\n", - " 0.96839969, 0.97559996, 0.98269548, 0.98968999, 0.996587 ,\n", - " 1.00338986, 1.01010169, 1.0167255 , 1.02326411, 1.0297202 ,\n", - " 1.03609634, 1.04239496, 1.04861836, 1.05476875, 1.06084824,\n", - " 1.06685884, 1.07280247, 1.07868096, 1.08449606, 1.09024946,\n", - " 1.09594278, 1.10157754, 1.10715524, 1.11267731, 1.1181451 ,\n", - " 1.12355993, 1.12892306, 1.13423572, 1.13949907, 1.14471424])" + "{'status': [ReturnStatus(Code.DONE, None)],\n", + " 'output': []}" ] }, - "execution_count": 25, + "execution_count": 59, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "m.input.strains" + "exe._run_machine._data" ] }, { "cell_type": "code", - "execution_count": 26, - "id": "33062219-bbe5-4fa0-88a6-7969e41c30b1", + "execution_count": 60, + "id": "62ce8439-bf95-4818-b35c-b4e2ef649bd2", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "True" + "" ] }, - "execution_count": 26, + "execution_count": 60, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "m.input.check_ready()" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "297455da-542c-4117-9bde-8605d0cc3e80", - "metadata": { - "scrolled": true, - "tags": [] - }, - "outputs": [], - "source": [ - "exe = m.run(how='foreground')" + "exe._run_machine.state" ] }, { "cell_type": "code", - "execution_count": 28, - "id": "d593a930-d19d-45be-9515-e120a71ee588", + "execution_count": 61, + "id": "5bcd1b68-6a48-4a08-92d4-143419071618", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "(ReturnStatus(Code.DONE, None),)" + "15.040622767002787" ] }, - "execution_count": 28, + "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "exe.status" + "exe._run_time" ] }, { "cell_type": "code", - "execution_count": 29, - "id": "131f8dd3-c1b4-4671-9e0a-84729e174483", + "execution_count": 62, + "id": "d21371e0-fa36-44bd-b7bf-a0092177ba17", "metadata": {}, "outputs": [ { "data": { - "image/png": "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", "text/plain": [ - "
" + "3.111499245278537e-05" ] }, + "execution_count": 62, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "exe.output[0].plot()" + "exe._collect_time" ] }, { "cell_type": "code", - "execution_count": 30, - "id": "cee1e3fe-1e6f-4aef-ad75-894f59117cae", + "execution_count": 63, + "id": "9e06cd6d-e0f7-40dd-93f2-777f86ffe2eb", "metadata": {}, "outputs": [ { "data": { + "image/png": "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", "text/plain": [ - "0.6788586373205143" + "
" ] }, - "execution_count": 30, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "exe.output[0].equilibrium_volume" + "exe.output[0].plot_energies()" ] }, { "cell_type": "code", - "execution_count": 31, - "id": "90af087c-e926-4465-b237-848c9e382708", + "execution_count": 64, + "id": "bb70a653-6231-4f4e-9bbe-279811acc895", "metadata": {}, "outputs": [ { "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d702f3bf2990434a85d0792cd2611c5f", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "0.6788586373205143" + "NGLWidget(max_frame=21)" ] }, - "execution_count": 31, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "exe.output[0].get_structure().get_volume()" + "exe.output[0].animate_structures()" + ] + }, + { + "cell_type": "markdown", + "id": "c7844b5f-1f9b-4770-8608-144c8b84fb87", + "metadata": {}, + "source": [ + "# ASE Minimize" ] }, { "cell_type": "code", - "execution_count": 32, - "id": "d2a84263-1dad-41fc-bb6a-87ecc1de2cc9", + "execution_count": 65, + "id": "f816e2af-0455-4e05-9c39-2e9f615d8f34", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Atoms(symbols='Fe', pbc=True, cell=[[-0.5536557129291797, 0.5536557129291797, 0.5536557129291797], [0.5536557129291797, -0.5536557129291797, 0.5536557129291797], [0.5536557129291797, 0.5536557129291797, -0.5536557129291797]])" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "exe.output[0].get_structure()" + "from pyiron_atomistics import ase_to_pyiron" ] }, { - "cell_type": "markdown", - "id": "5a89046e-035b-4f85-9d4d-3d2047d3b963", + "cell_type": "code", + "execution_count": 66, + "id": "22314020-8f48-487b-a765-229a77d79a2f", "metadata": {}, + "outputs": [], "source": [ - "## Again but execute children as background processes, but keep the node itself blocking" + "mi = AseMinimizeNode()" ] }, { "cell_type": "code", - "execution_count": 33, - "id": "163db559-574e-4bdd-af56-2538cc38f615", + "execution_count": 67, + "id": "ff411a05-82e1-4581-b06e-ab2fd7e0be3b", "metadata": {}, "outputs": [], "source": [ - "m = MurnaghanNode()" + "dimer = Atoms(symbols=['Fe', 'Fe'], positions=[[0,0,0], [0,0, .75]], cell=[10,10,10])" ] }, { "cell_type": "code", - "execution_count": 34, - "id": "9cb4b0a3-4fdb-478a-8e23-b39370261413", + "execution_count": 68, + "id": "5574f0d5-d800-472a-9418-8c6ccc1e555b", "metadata": {}, "outputs": [], "source": [ - "m.input.node = AseStaticNode()\n", - "m.input.node.input.calculator = MorsePotential()\n", - "m.input.structure = bulk(\"Fe\", a=1.2)" + "mi.input.structure = dimer\n", + "mi.input.calculator = MorsePotential(rcut1=6,rcut2=10)" ] }, { "cell_type": "code", - "execution_count": 35, - "id": "df71f86e-7764-47ce-a5e2-61df6871bcb3", + "execution_count": 69, + "id": "9e02d6dd-0fa6-4dd6-a7ab-3e648958eb20", "metadata": {}, "outputs": [ { "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "490fa227a15b47d9bfc0fa37ac79e59c", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "NGLWidget()" ] }, - "execution_count": 35, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "m.input.node.input" + "ase_to_pyiron(mi.input.structure).plot3d()" ] }, { "cell_type": "code", - "execution_count": 36, - "id": "79a3872f-d9a0-49a7-b94c-a8618b444e16", + "execution_count": 70, + "id": "663e4435-1cd0-4ce2-9593-85453f4c846a", "metadata": {}, "outputs": [], "source": [ - "m.input.set_strain_range(.6, 1000)" + "mi.input.max_steps = 100\n", + "mi.input.output_steps = 1\n", + "mi.input.ionic_force_tolerance = 1e-6" ] }, { "cell_type": "code", - "execution_count": null, - "id": "a07ff474-9975-4ff4-a8f2-966b5588168b", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "63dfbc8d-9157-4b95-a025-bf854fd6baa9", + "execution_count": 71, + "id": "37440e5a-75ff-4601-813a-f5c8df9413ad", "metadata": {}, "outputs": [], "source": [ - "m.input.child_executor = ProcessExecutor" + "mi.input.gpmin()" ] }, { "cell_type": "code", - "execution_count": 38, - "id": "03d18be4-82fa-4766-84ca-a3558ce6d223", + "execution_count": 72, + "id": "a448ec7f-53bc-4d72-a8a7-f9392de9f3d5", "metadata": { "scrolled": true, "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Step Time Energy fmax\n", + "GPMin: 0 17:12:33 11.122159 187.2462\n", + "GPMin: 1 17:12:33 -0.278268 1.5338\n", + "GPMin: 2 17:12:33 -0.996055 0.8010\n", + "GPMin: 3 17:12:33 -0.000000 0.0000\n", + "CPU times: user 76.8 ms, sys: 34.4 ms, total: 111 ms\n", + "Wall time: 61.9 ms\n" + ] + } + ], "source": [ - "exe = m.run(how='foreground')" + "%%time\n", + "exe = mi.run(how='foreground')" ] }, { "cell_type": "code", - "execution_count": 39, - "id": "6868c82d-fbf0-4fc8-9b70-586a66610a4e", - "metadata": { - "scrolled": true, - "tags": [] - }, + "execution_count": 73, + "id": "5977dd10-c4cf-40c9-944e-5aa52cfa263d", + "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "(ReturnStatus(Code.DONE, None),)" ] }, - "execution_count": 39, + "execution_count": 73, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "exe._run_machine.state" + "exe.status" ] }, { "cell_type": "code", - "execution_count": 40, - "id": "ab8027b5-a0fd-47f2-8b4a-bf743917330a", + "execution_count": 74, + "id": "dd164778-634c-4785-903a-08a5243999ce", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "27.62762461800594" + "2.136147842601888e-07" ] }, - "execution_count": 40, + "execution_count": 74, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "exe._run_time" + "abs(exe.output[0].forces[-1]).max()" ] }, { "cell_type": "code", - "execution_count": 41, - "id": "7cf03eb5-9ac3-4699-a6d2-cb7fd56c7eff", + "execution_count": 75, + "id": "515ea06d-9026-4d9e-9df0-b9c249f0758a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "1.2397998943924904e-05" + "" ] }, - "execution_count": 41, + "execution_count": 75, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "exe._collect_time" + "exe._run_machine.state" ] }, { "cell_type": "code", - "execution_count": 42, - "id": "95a0ddb5-b61a-47e3-8bd4-218202f97d7b", + "execution_count": 76, + "id": "52b7231f-8978-46ec-b698-ea8724a6fea3", "metadata": {}, "outputs": [ { "data": { - "image/png": "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uu+++1RcXCyHw6GxY8fqiSeeiFK18WWg1/aZZ57R5MmTlZaWJrfbrX/8x39UTU1NlKqNHxs2bND8+fNVUFAgi8WiV1555bTPWb9+vaZMmaKUlBSNGTNG//Ef/xH5QuPQQK/tSy+9pDlz5ignJ0eZmZmaPn261qxZE51i40wof2+7hfO9jCASglWrVmnRokW67777tHXrVl1yySWaN2+eDh482OfzGhoadP311+trX/talCqNP6Fc22uuuUZ/+9vf9Pjjj2vXrl167rnnNGHChChWHR8Gem3fffddXX/99brxxhu1fft2vfDCC9q0aZNuuummKFce+3w+nyZPnqwVK1b06/FlZWW6/PLLdckll2jr1q2699579eMf/1gvvvhihCuNPwO9ths2bNCcOXO0evVqbdmyRbNnz9b8+fO1devWCFcafwZ6bbuF/b3MwIBdcMEFxq233trj3IQJE4yf//znfT5vwYIFxj//8z8b999/vzF58uQIVhi/BnptX3/9dcPlchk1NTXRKC+uDfTaPvTQQ8aYMWN6nPvNb35jFBYWRqzGoUCS8fLLL/f5mHvuuceYMGFCj3O33HKLceGFF0awsvjXn2t7KhMnTjQeeOCB8Bc0hAzk2ob7vYwRkQFqbW3Vli1bNHfu3B7n586dq/fff7/X5z355JPau3ev7r///kiXGLdCubavvvqqpk6dqgcffFAjR47U+PHjdffdd6u5uTkaJceNUK7tjBkzdOjQIa1evVqGYejo0aP685//rCuuuCIaJQ9pH3zwwUl/Fl//+te1efNmtbW1mVTV0NTZ2Smv16usrCyzSxkSIvFeFtM3vYtF1dXV6ujoUF5eXo/zeXl5qqysPOVzdu/erZ///Od65513ZLNxyXsTyrXdt2+f3n33XaWkpOjll19WdXW1brvtNtXW1tIncoJQru2MGTP0zDPPaMGCBWppaVF7e7u++c1v6re//W00Sh7SKisrT/ln0d7erurqarndbpMqG3p+9atfyefz6ZprrjG7lLgXqfcyRkRCZLFYenxtGMZJ5ySpo6ND1157rR544AGNHz8+WuXFtf5eWynwrx2LxaJnnnlGF1xwgS6//HI9/PDD+uMf/8ioyCkM5Nru2LFDP/7xj/Uv//Iv2rJli9544w2VlZXp1ltvjUapQ96p/ixOdR6he+6557R06VKtWrVKubm5ZpcT1yL5XsY/zwcoOztbSUlJJ/0rsqqq6qR/4UiS1+vV5s2btXXrVt1xxx2SAm+ehmHIZrPpzTff1KWXXhqV2mPdQK+tJLndbo0cObLH7abPPPNMGYahQ4cOqbS0NKI1x4tQru3y5ct10UUX6ac//akk6ZxzzlF6erouueQS/eIXv+Bf7YOQn59/yj8Lm82mESNGmFTV0LJq1SrdeOONeuGFF3TZZZeZXU7ci+R7GSMiA2S32zVlyhStXbu2x/m1a9dqxowZJz0+MzNTn376qbZt2xY8br31Vp1xxhnatm2bpk2bFq3SY95Ar60kXXTRRTpy5IgaGxuD57744gtZrVYVFhZGtN54Esq1bWpqktXa838RSUlJko7/6x2hmT59+kl/Fm+++aamTp2q5ORkk6oaOp577jndcMMNevbZZ+lpCpOIvpcNut01AT3//PNGcnKy8fjjjxs7duwwFi1aZKSnpxv79+83DMMwfv7znxvXXXddr89n1UzvBnptvV6vUVhYaFx99dXG9u3bjfXr1xulpaXGTTfdZNavELMGem2ffPJJw2azGY899pixd+9e49133zWmTp1qXHDBBWb9CjHL6/UaW7duNbZu3WpIMh5++GFj69atxoEDBwzDOPna7tu3z0hLSzPuuusuY8eOHcbjjz9uJCcnG3/+85/N+hVi1kCv7bPPPmvYbDbj0UcfNSoqKoJHfX29Wb9CzBrotf2ycL2XEURC9OijjxrFxcWG3W43zjvvPGP9+vXB7y1cuNCYOXNmr88liPRtoNd2586dxmWXXWakpqYahYWFxuLFi42mpqYoVx0fBnptf/Ob3xgTJ040UlNTDbfbbfzgBz8wDh06FOWqY9/bb79tSDrpWLhwoWEYp76269atM77yla8YdrvdGD16tLFy5croFx4HBnptZ86c2efjcVwof29PFK73MothMMYKAADMQY8IAAAwDUEEAACYhiACAABMQxABAACmIYgAAADTEEQAAIBpCCIAAMA0BBEAAGAagggAADANQQQAAJiGIAIAAExDEAEAAKb5v1E7qt2HJCfYAAAAAElFTkSuQmCC", "text/plain": [ - "
" + "0.061767543986206874" ] }, + "execution_count": 76, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "exe.output[0].plot()" + "exe._run_time" ] }, { "cell_type": "code", - "execution_count": 43, - "id": "000713cd-9535-4d3e-9df0-3d27447700f4", + "execution_count": 77, + "id": "c845430c-119d-4566-88e1-8465e378fde1", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0.6818586500998999" + "1.0585004929453135e-05" ] }, - "execution_count": 43, + "execution_count": 77, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "exe.output[0].get_structure().get_volume()" + "exe._collect_time" ] }, { "cell_type": "code", - "execution_count": 44, - "id": "7936847d-ef7e-414a-8cbc-21474519eb84", + "execution_count": 78, + "id": "35291d7f-33a9-41ab-9b80-f052c5eb2e55", "metadata": {}, "outputs": [ { "data": { + "image/png": "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", "text/plain": [ - "0.6818586500999" + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exe.output[0].plot_energies()" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "1d5b5203-d07f-485b-9553-9150f4a674e7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[11.122158782511535,\n", + " -0.2782678462106827,\n", + " -0.9960554302957411,\n", + " -3.560246436024868e-08,\n", + " -3.560246436024868e-08]" ] }, - "execution_count": 44, + "execution_count": 79, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "exe.output[0].equilibrium_volume" + "exe.output[0].pot_energies" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "d2cc3b3a-5daa-49bb-9d6d-2994ebc74273", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ae4da31a319e4baca99703ce308889d4", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "NGLWidget(max_frame=4)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exe.output[0].animate_structures()" + ] + }, + { + "cell_type": "markdown", + "id": "32c15731-fd88-40d1-9045-beb9c53de501", + "metadata": { + "tags": [] + }, + "source": [ + "# Murnaghan" ] }, { "cell_type": "markdown", - "id": "af68c813-e409-4efd-85f8-ee3dfee60201", + "id": "aa535698-ddaa-4d60-862c-d6d6d5ecb6c8", "metadata": {}, "source": [ - "## Again but execute everything in the background." + "## Basic" ] }, { "cell_type": "code", - "execution_count": 45, - "id": "d603c588-a0fa-4aa7-ab57-f79fa6224b2b", + "execution_count": 81, + "id": "4acdeafc-90b5-4b3f-9559-c74b9fa221ab", "metadata": {}, "outputs": [], "source": [ @@ -663,8 +715,8 @@ }, { "cell_type": "code", - "execution_count": 46, - "id": "6b9d7aae-996c-4793-9960-debbf3cd2790", + "execution_count": 82, + "id": "f8cf3136-9b7c-4f1e-b630-962795527946", "metadata": {}, "outputs": [], "source": [ @@ -675,17 +727,17 @@ }, { "cell_type": "code", - "execution_count": 47, - "id": "97fe976c-2f7b-40bc-adf1-e9ee8a2520d7", + "execution_count": 83, + "id": "fef21aa4-d9f1-4d4a-8761-af1bc3121e5b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 47, + "execution_count": 83, "metadata": {}, "output_type": "execute_result" } @@ -696,240 +748,283 @@ }, { "cell_type": "code", - "execution_count": 48, - "id": "0f4e2e94-79c6-47f7-ae93-d34a9fe09f08", + "execution_count": 84, + "id": "41a68b17-c7c4-4a5f-8f04-11bee18fe55a", "metadata": {}, "outputs": [], "source": [ - "m.input.set_strain_range(.7, 100)" + "m.input.set_strain_range(.5, 50)" ] }, { - "cell_type": "markdown", - "id": "e9fed5d1-757e-43ad-8adb-653bc464dc3b", + "cell_type": "code", + "execution_count": 85, + "id": "fd107556-99b6-4042-9209-9412b4bbff94", "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0.79370053, 0.8043555 , 0.81473542, 0.82485739, 0.83473686,\n", + " 0.84438786, 0.85382314, 0.86305437, 0.87209225, 0.88094658,\n", + " 0.88962642, 0.89814011, 0.90649538, 0.9146994 , 0.92275884,\n", + " 0.93067991, 0.93846839, 0.94612969, 0.95366889, 0.96109074,\n", + " 0.96839969, 0.97559996, 0.98269548, 0.98968999, 0.996587 ,\n", + " 1.00338986, 1.01010169, 1.0167255 , 1.02326411, 1.0297202 ,\n", + " 1.03609634, 1.04239496, 1.04861836, 1.05476875, 1.06084824,\n", + " 1.06685884, 1.07280247, 1.07868096, 1.08449606, 1.09024946,\n", + " 1.09594278, 1.10157754, 1.10715524, 1.11267731, 1.1181451 ,\n", + " 1.12355993, 1.12892306, 1.13423572, 1.13949907, 1.14471424])" + ] + }, + "execution_count": 85, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "Use the threading backend just to show off." + "m.input.strains" ] }, { "cell_type": "code", - "execution_count": 49, - "id": "07585681-e027-4c42-b5cd-7086c5b9ba39", + "execution_count": 86, + "id": "0715614a-7284-4388-ac6b-c97bfedf7184", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 86, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "from pyiron_contrib.tinybase.executor import BackgroundExecutor\n", - "\n", - "m.input.child_executor = BackgroundExecutor" + "m.input.check_ready()" ] }, { "cell_type": "code", - "execution_count": 50, - "id": "4ba483ff-4a78-4c24-84db-a42ab518945f", + "execution_count": 87, + "id": "c2aa3093-1ea8-4099-bc14-be0c06e9d34b", "metadata": { "scrolled": true, "tags": [] }, "outputs": [], "source": [ - "exe = m.run(how='background')" + "exe = m.run(how='foreground')" ] }, { "cell_type": "code", - "execution_count": 51, - "id": "c597a2dd-f809-4bb6-9935-a0bdc2d35c07", + "execution_count": 88, + "id": "be97853a-f182-4b4f-af74-7fd5a4fbd850", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None),)" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "exe.wait()" + "exe.status" ] }, { "cell_type": "code", - "execution_count": 52, - "id": "71a7b3d1-99a8-494e-8649-f7aceada16df", + "execution_count": 89, + "id": "47f916ef-b140-49c5-adf1-93dca91b4540", "metadata": {}, "outputs": [ { "data": { + "image/png": "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", "text/plain": [ - "" + "
" ] }, - "execution_count": 52, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "exe._run_machine.state" + "exe.output[0].plot()" ] }, { "cell_type": "code", - "execution_count": 53, - "id": "97cf6e6e-c5c4-46eb-bbd0-c36e0cc37589", + "execution_count": 90, + "id": "14162f5b-1318-4595-8c8c-d6346a21721d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "12.170459961984307" + "0.6788586373205143" ] }, - "execution_count": 53, + "execution_count": 90, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "exe._run_time" + "exe.output[0].equilibrium_volume" ] }, { "cell_type": "code", - "execution_count": 54, - "id": "21861420-e58c-414b-aed7-0f6d67d12642", + "execution_count": 91, + "id": "0f5ff296-df33-40d2-851b-02d6ded72dd6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "2.338102785870433e-05" + "0.6788586373205143" ] }, - "execution_count": 54, + "execution_count": 91, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "exe._collect_time" + "exe.output[0].get_structure().get_volume()" ] }, { "cell_type": "code", - "execution_count": 55, - "id": "11cd873c-a43c-4cb8-ac23-39ec6889c30c", + "execution_count": 92, + "id": "92b06330-b1fc-41d0-8bd8-bf1b11bf448c", "metadata": {}, "outputs": [ { "data": { - "image/png": "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zk2aNMm49957e/wzJk+ebNx///2pLs1S+tpON954o/H973/fuO+++4ypU6emsULr6G1b/eEPfzAKCgqMo0ePZqI8y+htO/34xz82Jk6c2OXcQw89ZIwbNy5tNVqRJGP9+vWnvGbx4sXGpEmTupxbuHChcfHFF6exMmvpSTudTDa8n3fXm7YajO/pluxBiUQi2rZtm0pLS7ucLy0t1ebNm3v0M2KxmOrr6zV8+PB0lGgJfW2nJ554Qh9++KHuu+++dJdoGX1pq+eff17Tp0/X8uXLNXbsWJ155pn6zne+o+bm5kyUbIq+tNOsWbO0f/9+vfDCCzIMQ4cOHdJvfvMbXXvttZkoeUB57bXXTmjbq666Slu3blVra6tJVVlfNryf98dgfU+35M0Cjxw5omg0Kr/f3+W83+9XTU1Nj37GT37yEzU2NuqGG25IR4mW0Jd2ev/993Xvvffq1VdfldNpyf/9adGXttqzZ482bdokr9er9evX68iRI1q0aJGOHTs2aOeh9KWdZs2apaefflo33nijWlpa1NbWpuuuu04PP/xwJkoeUGpqak7atm1tbTpy5IjGjBljUmXWlg3v5301mN/TLdmDkmCz2bo8NwzjhHMn88wzz2jZsmVat26dRo8ena7yLKOn7RSNRrVgwQLdf//9OvPMMzNVnqX05m8qFovJZrPp6aef1owZM3TNNddoxYoVevLJJwd1L4rUu3bauXOn7rrrLv3gBz/Qtm3b9Mc//lFVVVW6/fbbM1HqgHOytj3ZecRl2/t5bwz293RLxq2RI0fK4XCc8C+22traE/710d26det066236rnnntOVV16ZzjJN19t2qq+v19atW7V9+3bdeeedkuIfwoZhyOl0asOGDfqHf/iHjNSeaX35mxozZozGjh3b5dbgZ599tgzD0P79+1VSUpLWms3Ql3aqqKjQ7Nmz9d3vfleSdN555ykvL0+XXnqp/u3f/o1egU4CgcBJ29bpdGrEiBEmVWVd2fR+3heD/T3dkj0obrdb06ZN08aNG7uc37hxo2bNmvWJ3/fMM8/olltu0dq1a7Ni/Lu37eTz+fTOO+/ozTffTB633367zjrrLL355puaOXNmpkrPuL78Tc2ePVsHDhxQQ0ND8tzu3btlt9s1bty4tNZrlr60U1NTk+z2rm8lDodDUkfvAOI+85nPnNC2GzZs0PTp0+VyuUyqypqy7f28Lwb9e7pZs3NP59lnnzVcLpfx+OOPGzt37jTKysqMvLw8Y+/evYZhGMa9995rfOUrX0lev3btWsPpdBo//elPjYMHDyaP48ePm/UrZERv26m7wTTj+3R621b19fXGuHHjjM9//vPGjh07jMrKSqOkpMS47bbbzPoVMqK37fTEE08YTqfTeOSRR4wPP/zQ2LRpkzF9+nRjxowZZv0KGVNfX29s377d2L59uyHJWLFihbF9+3bjo48+MgzjxLbas2ePkZuba9x9993Gzp07jccff9xwuVzGb37zG7N+hYzobTtl6/u5YfS+rbobTO/plg0ohmEYP/3pT40zzjjDcLvdxoUXXmhUVlYmX7v55puNOXPmJJ/PmTPHkHTCcfPNN2e+8AzrTTt1N5j+mHuit2313nvvGVdeeaWRk5NjjBs3zrjnnnuMpqamDFedeb1tp4ceesiYPHmykZOTY4wZM8a46aabjP3792e46sx76aWXTvm+c7K2evnll40LLrjAcLvdxoQJE4zVq1dnvvAM6207ZfP7eV/+pjobTO/pNsOgDxYAAFiLJeegAACA7EZAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlvP/AXuzOgpGI5yoAAAAAElFTkSuQmCC", "text/plain": [ - "
" + "Atoms(symbols='Fe', pbc=True, cell=[[-0.5536557129291797, 0.5536557129291797, 0.5536557129291797], [0.5536557129291797, -0.5536557129291797, 0.5536557129291797], [0.5536557129291797, 0.5536557129291797, -0.5536557129291797]])" ] }, + "execution_count": 92, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "exe.output[0].plot()" + "exe.output[0].get_structure()" ] }, { "cell_type": "markdown", - "id": "8a102a81-df04-4527-8739-7fe542f0c1fc", - "metadata": {}, - "source": [ - "# ASE MD" - ] - }, - { - "cell_type": "code", - "execution_count": 56, - "id": "02cfe01b-0b24-4723-a79b-d41ffb146bf9", + "id": "26bade63-559f-4c93-be24-5561f5c8190f", "metadata": {}, - "outputs": [], "source": [ - "md = AseMDNode()" + "## Again but execute children as background processes, but keep the node itself blocking" ] }, { "cell_type": "code", - "execution_count": 57, - "id": "466d1f9a-b707-4c05-a8af-5414d76bd8eb", + "execution_count": 93, + "id": "89169376-be36-4ceb-9f4e-6e1f3247bc62", "metadata": {}, "outputs": [], "source": [ - "md.input.structure = bulk(\"Fe\", a=1.2, cubic=True).repeat(3)\n", - "md.input.calculator = MorsePotential()" + "m = MurnaghanNode()" ] }, { "cell_type": "code", - "execution_count": 58, - "id": "dfdfc027-1608-43ad-9d15-0c649986eb73", + "execution_count": 94, + "id": "2ee9f1d4-5b14-4340-98d4-4bd293af89a4", "metadata": {}, "outputs": [], "source": [ - "md.input.steps = 100\n", - "md.input.timestep = 3\n", - "md.input.temperature = 600\n", - "md.input.output_steps = 20" + "m.input.node = AseStaticNode()\n", + "m.input.node.input.calculator = MorsePotential()\n", + "m.input.structure = bulk(\"Fe\", a=1.2)" ] }, { "cell_type": "code", - "execution_count": 59, - "id": "db5c7cfe-b075-483e-8b7e-a58cebf1a782", + "execution_count": 95, + "id": "3d73a9de-7b4e-476a-b50a-ac6a3957a7ab", "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 2.51 ms, sys: 50.7 ms, total: 53.3 ms\n", - "Wall time: 52.9 ms\n" - ] + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 95, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "%%time\n", - "exe = md.run(how='process')" + "m.input.node.input" ] }, { "cell_type": "code", - "execution_count": 60, - "id": "80155255-4dcf-48cb-9825-015da13d6ac0", + "execution_count": 96, + "id": "0f075d90-e636-49be-b1a6-741a56363f54", "metadata": {}, "outputs": [], "source": [ - "exe.wait()" + "m.input.set_strain_range(.6, 1000)" ] }, { "cell_type": "code", - "execution_count": 61, - "id": "6f7aff4e-9e89-459b-843f-46a4d4139bcf", + "execution_count": null, + "id": "2b2f703c-745b-49f9-a81f-a780248e9cd3", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'status': [ReturnStatus(Code.DONE, None)],\n", - " 'output': []}" - ] - }, - "execution_count": 61, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 97, + "id": "58064e52-1c94-49fd-b38f-614cf6f19004", + "metadata": {}, + "outputs": [], "source": [ - "exe._run_machine._data" + "m.input.child_executor = ProcessExecutor" ] }, { "cell_type": "code", - "execution_count": 62, - "id": "62ce8439-bf95-4818-b35c-b4e2ef649bd2", - "metadata": {}, + "execution_count": 98, + "id": "d82a28ab-1a96-4a3a-8f79-5a875ac20788", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "exe = m.run(how='foreground')" + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "id": "5472daed-f25c-4aab-b101-90c76a0235a5", + "metadata": { + "scrolled": true, + "tags": [] + }, "outputs": [ { "data": { @@ -937,7 +1032,7 @@ "" ] }, - "execution_count": 62, + "execution_count": 99, "metadata": {}, "output_type": "execute_result" } @@ -948,17 +1043,17 @@ }, { "cell_type": "code", - "execution_count": 63, - "id": "5bcd1b68-6a48-4a08-92d4-143419071618", + "execution_count": 100, + "id": "a42865c9-8616-4335-8e46-ec4839daab0a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "16.012012982013403" + "30.503913552995073" ] }, - "execution_count": 63, + "execution_count": 100, "metadata": {}, "output_type": "execute_result" } @@ -969,17 +1064,17 @@ }, { "cell_type": "code", - "execution_count": 64, - "id": "d21371e0-fa36-44bd-b7bf-a0092177ba17", + "execution_count": 101, + "id": "d894a79f-a9e6-4667-9a9b-2bd9a088622f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "4.1224993765354156e-05" + "2.4366017896682024e-05" ] }, - "execution_count": 64, + "execution_count": 101, "metadata": {}, "output_type": "execute_result" } @@ -990,13 +1085,13 @@ }, { "cell_type": "code", - "execution_count": 65, - "id": "9e06cd6d-e0f7-40dd-93f2-777f86ffe2eb", + "execution_count": 102, + "id": "78017969-23fc-46f5-b99f-cd1d2dc74c00", "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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uu+++1RcXCyHw6GxY8fqiSeeiFK18WWg1/aZZ57R5MmTlZaWJrfbrX/8x39UTU1NlKqNHxs2bND8+fNVUFAgi8WiV1555bTPWb9+vaZMmaKUlBSNGTNG//Ef/xH5QuPQQK/tSy+9pDlz5ignJ0eZmZmaPn261qxZE51i40wof2+7hfO9jCASglWrVmnRokW67777tHXrVl1yySWaN2+eDh482OfzGhoadP311+trX/talCqNP6Fc22uuuUZ/+9vf9Pjjj2vXrl167rnnNGHChChWHR8Gem3fffddXX/99brxxhu1fft2vfDCC9q0aZNuuummKFce+3w+nyZPnqwVK1b06/FlZWW6/PLLdckll2jr1q2699579eMf/1gvvvhihCuNPwO9ths2bNCcOXO0evVqbdmyRbNnz9b8+fO1devWCFcafwZ6bbuF/b3MwIBdcMEFxq233trj3IQJE4yf//znfT5vwYIFxj//8z8b999/vzF58uQIVhi/BnptX3/9dcPlchk1NTXRKC+uDfTaPvTQQ8aYMWN6nPvNb35jFBYWRqzGoUCS8fLLL/f5mHvuuceYMGFCj3O33HKLceGFF0awsvjXn2t7KhMnTjQeeOCB8Bc0hAzk2ob7vYwRkQFqbW3Vli1bNHfu3B7n586dq/fff7/X5z355JPau3ev7r///kiXGLdCubavvvqqpk6dqgcffFAjR47U+PHjdffdd6u5uTkaJceNUK7tjBkzdOjQIa1evVqGYejo0aP685//rCuuuCIaJQ9pH3zwwUl/Fl//+te1efNmtbW1mVTV0NTZ2Smv16usrCyzSxkSIvFeFtM3vYtF1dXV6ujoUF5eXo/zeXl5qqysPOVzdu/erZ///Od65513ZLNxyXsTyrXdt2+f3n33XaWkpOjll19WdXW1brvtNtXW1tIncoJQru2MGTP0zDPPaMGCBWppaVF7e7u++c1v6re//W00Sh7SKisrT/ln0d7erurqarndbpMqG3p+9atfyefz6ZprrjG7lLgXqfcyRkRCZLFYenxtGMZJ5ySpo6ND1157rR544AGNHz8+WuXFtf5eWynwrx2LxaJnnnlGF1xwgS6//HI9/PDD+uMf/8ioyCkM5Nru2LFDP/7xj/Uv//Iv2rJli9544w2VlZXp1ltvjUapQ96p/ixOdR6he+6557R06VKtWrVKubm5ZpcT1yL5XsY/zwcoOztbSUlJJ/0rsqqq6qR/4UiS1+vV5s2btXXrVt1xxx2SAm+ehmHIZrPpzTff1KWXXhqV2mPdQK+tJLndbo0cObLH7abPPPNMGYahQ4cOqbS0NKI1x4tQru3y5ct10UUX6ac//akk6ZxzzlF6erouueQS/eIXv+Bf7YOQn59/yj8Lm82mESNGmFTV0LJq1SrdeOONeuGFF3TZZZeZXU7ci+R7GSMiA2S32zVlyhStXbu2x/m1a9dqxowZJz0+MzNTn376qbZt2xY8br31Vp1xxhnatm2bpk2bFq3SY95Ar60kXXTRRTpy5IgaGxuD57744gtZrVYVFhZGtN54Esq1bWpqktXa838RSUlJko7/6x2hmT59+kl/Fm+++aamTp2q5ORkk6oaOp577jndcMMNevbZZ+lpCpOIvpcNut01AT3//PNGcnKy8fjjjxs7duwwFi1aZKSnpxv79+83DMMwfv7znxvXXXddr89n1UzvBnptvV6vUVhYaFx99dXG9u3bjfXr1xulpaXGTTfdZNavELMGem2ffPJJw2azGY899pixd+9e49133zWmTp1qXHDBBWb9CjHL6/UaW7duNbZu3WpIMh5++GFj69atxoEDBwzDOPna7tu3z0hLSzPuuusuY8eOHcbjjz9uJCcnG3/+85/N+hVi1kCv7bPPPmvYbDbj0UcfNSoqKoJHfX29Wb9CzBrotf2ycL2XEURC9OijjxrFxcWG3W43zjvvPGP9+vXB7y1cuNCYOXNmr88liPRtoNd2586dxmWXXWakpqYahYWFxuLFi42mpqYoVx0fBnptf/Ob3xgTJ040UlNTDbfbbfzgBz8wDh06FOWqY9/bb79tSDrpWLhwoWEYp76269atM77yla8YdrvdGD16tLFy5croFx4HBnptZ86c2efjcVwof29PFK73MothMMYKAADMQY8IAAAwDUEEAACYhiACAABMQxABAACmIYgAAADTEEQAAIBpCCIAAMA0BBEAAGAagggAADANQQQAAJiGIAIAAExDEAEAAKb5v1E7qt2HJCfYAAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -1006,204 +1101,159 @@ } ], "source": [ - "exe.output[0].plot_energies()" + "exe.output[0].plot()" ] }, { "cell_type": "code", - "execution_count": 66, - "id": "bb70a653-6231-4f4e-9bbe-279811acc895", + "execution_count": 103, + "id": "45162eb2-b23d-45c6-8aad-dfe9a6a484d1", "metadata": {}, "outputs": [ { "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "9acd0738c709420293ca03c9d7cb65a8", - "version_major": 2, - "version_minor": 0 - }, "text/plain": [ - "NGLWidget(max_frame=21)" + "0.6818586500998999" ] }, + "execution_count": 103, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "exe.output[0].animate_structures()" - ] - }, - { - "cell_type": "markdown", - "id": "c7844b5f-1f9b-4770-8608-144c8b84fb87", - "metadata": {}, - "source": [ - "# ASE Minimize" + "exe.output[0].get_structure().get_volume()" ] }, { "cell_type": "code", - "execution_count": 67, - "id": "f816e2af-0455-4e05-9c39-2e9f615d8f34", + "execution_count": 104, + "id": "f0bcfe59-2168-4e74-9d7a-33d900368907", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.6818586500999" + ] + }, + "execution_count": 104, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "from pyiron_atomistics import ase_to_pyiron" + "exe.output[0].equilibrium_volume" ] }, { - "cell_type": "code", - "execution_count": 68, - "id": "22314020-8f48-487b-a765-229a77d79a2f", + "cell_type": "markdown", + "id": "36b17048-3941-4d86-bd2a-e131371f4bad", "metadata": {}, - "outputs": [], "source": [ - "mi = AseMinimizeNode()" + "## Again but execute everything in the background." ] }, { "cell_type": "code", - "execution_count": 69, - "id": "ff411a05-82e1-4581-b06e-ab2fd7e0be3b", + "execution_count": 105, + "id": "10f6c113-1e35-48f0-8878-291129bd8a60", "metadata": {}, "outputs": [], "source": [ - "dimer = Atoms(symbols=['Fe', 'Fe'], positions=[[0,0,0], [0,0, .75]], cell=[10,10,10])" + "m = MurnaghanNode()" ] }, { "cell_type": "code", - "execution_count": 70, - "id": "5574f0d5-d800-472a-9418-8c6ccc1e555b", + "execution_count": 106, + "id": "70832c31-040e-49be-b0f7-172f930cf31b", "metadata": {}, "outputs": [], "source": [ - "mi.input.structure = dimer\n", - "mi.input.calculator = MorsePotential(rcut1=6,rcut2=10)" + "m.input.node = AseStaticNode()\n", + "m.input.node.input.calculator = MorsePotential()\n", + "m.input.structure = bulk(\"Fe\", a=1.2)" ] }, { "cell_type": "code", - "execution_count": 71, - "id": "9e02d6dd-0fa6-4dd6-a7ab-3e648958eb20", + "execution_count": 107, + "id": "94f4c51d-b69b-4477-a9db-d0ee7627cee6", "metadata": {}, "outputs": [ { "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "5cff2d0d15044fca9105fa3541411380", - "version_major": 2, - "version_minor": 0 - }, "text/plain": [ - "NGLWidget()" + "" ] }, + "execution_count": 107, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "ase_to_pyiron(mi.input.structure).plot3d()" + "m.input.node.input" ] }, { "cell_type": "code", - "execution_count": 72, - "id": "663e4435-1cd0-4ce2-9593-85453f4c846a", + "execution_count": 108, + "id": "9a000824-0a9e-4395-8e07-00e484bc7937", "metadata": {}, "outputs": [], "source": [ - "mi.input.max_steps = 100\n", - "mi.input.output_steps = 1\n", - "mi.input.ionic_force_tolerance = 1e-6" + "m.input.set_strain_range(.7, 100)" + ] + }, + { + "cell_type": "markdown", + "id": "82c9d3a2-d93c-41aa-a1af-52bfab8cd8df", + "metadata": {}, + "source": [ + "Use the threading backend just to show off." ] }, { "cell_type": "code", - "execution_count": 73, - "id": "37440e5a-75ff-4601-813a-f5c8df9413ad", + "execution_count": 109, + "id": "ca50857c-1c2f-4fad-a58c-16b399b8721d", "metadata": {}, "outputs": [], "source": [ - "mi.input.gpmin()" + "from pyiron_contrib.tinybase.executor import BackgroundExecutor\n", + "\n", + "m.input.child_executor = BackgroundExecutor" ] }, { "cell_type": "code", - "execution_count": 74, - "id": "a448ec7f-53bc-4d72-a8a7-f9392de9f3d5", + "execution_count": 110, + "id": "12f20f5c-27a3-4533-ad19-8ec06e1c8a90", "metadata": { "scrolled": true, "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " Step Time Energy fmax\n", - "GPMin: 0 14:53:00 11.122159 187.2462\n", - "GPMin: 1 14:53:00 -0.278268 1.5338\n", - "GPMin: 2 14:53:00 -0.996055 0.8010\n", - "GPMin: 3 14:53:00 -0.000000 0.0000\n", - "CPU times: user 76.7 ms, sys: 50.9 ms, total: 128 ms\n", - "Wall time: 57.3 ms\n" - ] - } - ], - "source": [ - "%%time\n", - "exe = mi.run(how='foreground')" - ] - }, - { - "cell_type": "code", - "execution_count": 75, - "id": "5977dd10-c4cf-40c9-944e-5aa52cfa263d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(ReturnStatus(Code.DONE, None),)" - ] - }, - "execution_count": 75, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "exe.status" + "exe = m.run(how='background')" ] }, { "cell_type": "code", - "execution_count": 76, - "id": "dd164778-634c-4785-903a-08a5243999ce", + "execution_count": 111, + "id": "2c5d8bea-49ec-4004-8a54-3ded7a3f413d", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2.136147842601888e-07" - ] - }, - "execution_count": 76, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "abs(exe.output[0].forces[-1]).max()" + "exe.wait()" ] }, { "cell_type": "code", - "execution_count": 77, - "id": "515ea06d-9026-4d9e-9df0-b9c249f0758a", + "execution_count": 112, + "id": "ea7bcb58-0890-487e-bef5-bd3cb36143c1", "metadata": {}, "outputs": [ { @@ -1212,7 +1262,7 @@ "" ] }, - "execution_count": 77, + "execution_count": 112, "metadata": {}, "output_type": "execute_result" } @@ -1223,17 +1273,17 @@ }, { "cell_type": "code", - "execution_count": 78, - "id": "52b7231f-8978-46ec-b698-ea8724a6fea3", + "execution_count": 113, + "id": "0b7c2912-6847-4262-a62d-7233ca398643", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0.057213895983295515" + "13.346773353026947" ] }, - "execution_count": 78, + "execution_count": 113, "metadata": {}, "output_type": "execute_result" } @@ -1244,17 +1294,17 @@ }, { "cell_type": "code", - "execution_count": 79, - "id": "c845430c-119d-4566-88e1-8465e378fde1", + "execution_count": 114, + "id": "fd5ca921-2062-4f85-b014-382561e9893a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "1.1272000847384334e-05" + "1.0522984666749835e-05" ] }, - "execution_count": 79, + "execution_count": 114, "metadata": {}, "output_type": "execute_result" } @@ -1265,13 +1315,13 @@ }, { "cell_type": "code", - "execution_count": 80, - "id": "35291d7f-33a9-41ab-9b80-f052c5eb2e55", + "execution_count": 115, + "id": "4d1223f7-2d72-413e-b20b-cf42781780bb", "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAigAAAGdCAYAAAA44ojeAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy88F64QAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAytUlEQVR4nO3de3xU9Z3/8ffcJwmZcJ8hECS0UUQUFQQLKrhK/KnF7c9ttcW22moXf2httC2VpVuxj23ykG6RVSqurVVaRand0oe7tRXaapRiV0C8gQWVIEEI4RJmcp1JZs7vj8lMLiDkMjPnJPN6Ph7nkZkzJ/GTr3nMvPnejs0wDEMAAAAWYje7AAAAgO4IKAAAwHIIKAAAwHIIKAAAwHIIKAAAwHIIKAAAwHIIKAAAwHIIKAAAwHKcZhfQF7FYTAcOHFB+fr5sNpvZ5QAAgB4wDEP19fUqLCyU3X7qPpIBGVAOHDigoqIis8sAAAB9UF1drXHjxp3ymgEZUPLz8yXFf0Gfz2dyNQAAoCdCoZCKioqSn+OnMiADSmJYx+fzEVAAABhgejI9g0myAADAcggoAADAcggoAADAcggoAADAcggoAADAcggoAADAcggoAADAcggoAADAcggoAADAcggoAADAcggoAADAcggoAADAcgbkzQLTpSbYoic2V0mSllx9tsnVAACQvehB6aQx0qb/rNyjtX/bZ3YpAABkNQJKJyPzPJKk+nCbWlqjJlcDAED2IqB04stxyuWwSZKONUZMrgYAgOxFQOnEZrNpRHsvypGGsMnVAACQvQgo3YzMd0uSjjbQgwIAgFkIKN0kelAO04MCAIBpCCjdjBwSDyj0oAAAYB4CSjcjh8SHeJiDAgCAeQgo3XT0oBBQAAAwCwGlmxHJHhSGeAAAMAsBpZsRQ1hmDACA2Qgo3STmoBxlozYAAExDQOkmMQflWGNEsZhhcjUAAGQnAko3w/PiPSjRmKHjza0mVwMAQHYioHTjctg1NNcliXkoAACYhYByEiOZKAsAgKkIKCcxIo+lxgAAmImAchJs1gYAgLkIKCfBdvcAAJiLgHISI7hhIAAApup1QHnllVc0f/58FRYWymaz6Xe/+12X1w3D0LJly1RYWKicnBzNnTtXO3bs6HJNOBzWN7/5TY0cOVJ5eXm67rrrtH///n79IqnUMUmWgAIAgBl6HVAaGxs1depUrVq16qSvL1++XCtWrNCqVau0ZcsWBQIBzZs3T/X19clrysrKtH79ej377LPatGmTGhoa9NnPflbRaLTvv0kKjWCIBwAAUzl7+w1XX321rr766pO+ZhiGVq5cqaVLl+r666+XJK1Zs0Z+v19r167VwoULFQwG9fjjj+tXv/qVrrzySknSU089paKiIv3pT3/SVVdd1Y9fJzWSk2QbCSgAAJghpXNQqqqqVFNTo9LS0uQ5j8ejOXPmaPPmzZKkbdu2qbW1tcs1hYWFmjJlSvKa7sLhsEKhUJcjnZKTZOsZ4gEAwAwpDSg1NTWSJL/f3+W83+9PvlZTUyO3261hw4Z94jXdVVRUqKCgIHkUFRWlsuwTJCbJNrdG1RRpS+t/CwAAnCgtq3hsNluX54ZhnHCuu1Nds2TJEgWDweRRXV2dslpPJs/tkNcVbxp6UQAAyLyUBpRAICBJJ/SE1NbWJntVAoGAIpGI6urqPvGa7jwej3w+X5cjnWw2m0bkta/kYR4KAAAZl9KAUlxcrEAgoI0bNybPRSIRVVZWatasWZKkadOmyeVydbnm4MGDevfdd5PXWMHI/PaAUk9AAQAg03q9iqehoUEffPBB8nlVVZXefPNNDR8+XOPHj1dZWZnKy8tVUlKikpISlZeXKzc3VwsWLJAkFRQU6NZbb9W3v/1tjRgxQsOHD9d3vvMdnXvuuclVPVYwsv1+PEcbGeIBACDTeh1Qtm7dqssvvzz5/J577pEk3XzzzXryySe1ePFiNTc3a9GiRaqrq9PMmTO1YcMG5efnJ7/nwQcflNPp1A033KDm5mZdccUVevLJJ+VwOFLwK6UG9+MBAMA8NsMwDLOL6K1QKKSCggIFg8G0zUdZ/se/65GXP9QtsyZo2XXnpOW/AQBANunN5zf34vkEHdvd04MCAECmEVA+AdvdAwBgHgLKJxjJHY0BADANAeUTMMQDAIB5CCifIDHEU9fUqrZozORqAADILgSUTzAs1y17+877x9gLBQCAjCKgfAKH3abheYmJsgQUAAAyiYByCsmJstyPBwCAjCKgnAJLjQEAMAcB5RQSdzRmqTEAAJlFQDmFxBDPYXpQAADIKALKKSSGeOhBAQAgswgopzCKzdoAADAFAeUU6EEBAMAcBJRT6LgfDz0oAABkEgHlFDqWGUdkGIbJ1QAAkD0IKKeQ6EGJRGOqD7eZXA0AANmDgHIKXpdDQzxOSdKReoZ5AADIFALKaSQnynLDQAAAMoaAchqJYR56UAAAyBwCymmMSNzRmB4UAAAyhoByGiPz27e7pwcFAICMIaCchj/fK0mqDbWYXAkAANmDgHIagYJ4D0oNAQUAgIwhoJyG3xfvQakJElAAAMgUAsppBAriAeUQPSgAAGQMAeU0Au09KHVNrWppjZpcDQAA2YGAchoFOS55nPFmqg2xkgcAgEwgoJyGzWZLDvMwURYAgMwgoPRAYqkx81AAAMgMAkoP+JkoCwBARhFQeiDga98LhaXGAABkBAGlB5J7odCDAgBARhBQeoC9UAAAyCwCSg8E6EEBACCjCCg9kBjiORQKyzAMk6sBAGDwI6D0QCKgRNpiqmtqNbkaAAAGPwJKD7iddo3Ic0tiHgoAAJlAQOmh0cxDAQAgYwgoPZTYC+UQe6EAAJB2BJQe4n48AABkDgGlhzpW8hBQAABINwJKDyX3QmGIBwCAtCOg9JA/OcQTNrkSAAAGPwJKDyV6UGoZ4gEAIO0IKD2UCChHGyMKt0VNrgYAgMGNgNJDQ3NdcjvjzVXLMA8AAGlFQOkhm80mf2IvFIZ5AABIq5QHlLa2Nn3/+99XcXGxcnJyNHHiRP3whz9ULBZLXmMYhpYtW6bCwkLl5ORo7ty52rFjR6pLSTnuagwAQGakPKA88MADevTRR7Vq1Sq99957Wr58uX784x/r4YcfTl6zfPlyrVixQqtWrdKWLVsUCAQ0b9481dfXp7qclPKz1BgAgIxIeUB57bXX9I//+I+69tprNWHCBH3+859XaWmptm7dKinee7Jy5UotXbpU119/vaZMmaI1a9aoqalJa9euTXU5KRVgszYAADIi5QHlkksu0Z///Gft3r1bkvTWW29p06ZNuuaaayRJVVVVqqmpUWlpafJ7PB6P5syZo82bN5/0Z4bDYYVCoS6HGQLshQIAQEY4U/0Dv/e97ykYDGrSpElyOByKRqP60Y9+pC996UuSpJqaGkmS3+/v8n1+v18fffTRSX9mRUWF7r///lSX2mtsdw8AQGakvAdl3bp1euqpp7R27Vq98cYbWrNmjf793/9da9as6XKdzWbr8twwjBPOJSxZskTBYDB5VFdXp7rsHkn0oBBQAABIr5T3oHz3u9/Vvffeqy9+8YuSpHPPPVcfffSRKioqdPPNNysQCEiK96SMGTMm+X21tbUn9KokeDweeTyeVJfaa53vx3OqQAUAAPon5T0oTU1Nstu7/liHw5FcZlxcXKxAIKCNGzcmX49EIqqsrNSsWbNSXU5KjW7fByXcFlOwudXkagAAGLxS3oMyf/58/ehHP9L48eN1zjnnaPv27VqxYoW+/vWvS4oP7ZSVlam8vFwlJSUqKSlReXm5cnNztWDBglSXk1Iep0PDcl2qa2pVTahFQ3PdZpcEAMCglPKA8vDDD+tf//VftWjRItXW1qqwsFALFy7UD37wg+Q1ixcvVnNzsxYtWqS6ujrNnDlTGzZsUH5+fqrLSTm/zxsPKMEWTQr4zC4HAIBByWYYhmF2Eb0VCoVUUFCgYDAony+zIeGWJ17Xy7sO64F/Olc3XjQ+o/9tAAAGst58fnMvnl7q2KyNvVAAAEgXAkov+bkfDwAAaUdA6aXkXijcjwcAgLQhoPQSdzQGACD9CCi9xHb3AACkHwGllxJDPEcaIoq0xUyuBgCAwYmA0kvDcl3yuuLNVsM8FAAA0oKA0ks2m03jhuVKkqrrmkyuBgCAwYmA0gfjhuVIkvYTUAAASAsCSh90BJRmkysBAGBwIqD0QWKIh4ACAEB6EFD6gCEeAADSi4DSB/SgAACQXgSUPkj0oNSEWtgLBQCANCCg9MGIPLe8LrsMQzoYpBcFAIBUI6D0Qee9UBjmAQAg9QgofcREWQAA0oeA0kfshQIAQPoQUPqIIR4AANKHgNJHDPEAAJA+BJQ+ogcFAID0IaD0EXuhAACQPgSUPuq8F8qB4/SiAACQSgSUPmIvFAAA0oeA0g9MlAUAID0IKP3AXigAAKQHAaUfOoZ46EEBACCVCCj9QA8KAADpQUDpBybJAgCQHgSUfkj0oByqb1G4LWpyNQAADB4ElH7ovBfKweMtZpcDAMCgQUDpB/ZCAQAgPQgo/cReKAAApB4BpZ9YyQMAQOoRUPqpiL1QAABIOQJKPzEHBQCA1COg9FNiiKeaHhQAAFKGgNJPyb1QQmH2QgEAIEUIKP00PM+tHJdDknSAvVAAAEgJAko/xfdCYakxAACpREBJAZYaAwCQWgSUFBjHUmMAAFKKgJICRcPjPSh7jxJQAABIBQJKChSPHCJJ2nuk0eRKAAAYHAgoKVA8Mk+SVHWkUYZhmFwNAAADHwElBcYPz5XdJjVFoqqtD5tdDgAAAx4BJQXcTruKhscnyu45zDAPAAD9lZaA8vHHH+vLX/6yRowYodzcXJ1//vnatm1b8nXDMLRs2TIVFhYqJydHc+fO1Y4dO9JRSsZ0HuYBAAD9k/KAUldXp9mzZ8vlcukPf/iDdu7cqZ/85CcaOnRo8prly5drxYoVWrVqlbZs2aJAIKB58+apvr4+1eVkTEdAaTC5EgAABj5nqn/gAw88oKKiIj3xxBPJcxMmTEg+NgxDK1eu1NKlS3X99ddLktasWSO/36+1a9dq4cKFqS4pIybSgwIAQMqkvAfl+eef1/Tp0/WFL3xBo0eP1gUXXKCf/exnyderqqpUU1Oj0tLS5DmPx6M5c+Zo8+bNJ/2Z4XBYoVCoy2E1iaXGewgoAAD0W8oDyp49e7R69WqVlJToxRdf1O2336677rpLv/zlLyVJNTU1kiS/39/l+/x+f/K17ioqKlRQUJA8ioqKUl12vxWPiveg7DvapLZozORqAAAY2FIeUGKxmC688EKVl5frggsu0MKFC/WNb3xDq1ev7nKdzWbr8twwjBPOJSxZskTBYDB5VFdXp7rsfhvj88rjtKstZnBPHgAA+inlAWXMmDGaPHlyl3Nnn3229u3bJ0kKBAKSdEJvSW1t7Qm9Kgkej0c+n6/LYTV2u42VPAAApEjKA8rs2bO1a9euLud2796tM844Q5JUXFysQCCgjRs3Jl+PRCKqrKzUrFmzUl1ORiUCCvNQAADon5Sv4rn77rs1a9YslZeX64YbbtDrr7+uxx57TI899pik+NBOWVmZysvLVVJSopKSEpWXlys3N1cLFixIdTkZNXEUS40BAEiFlAeUiy66SOvXr9eSJUv0wx/+UMXFxVq5cqVuuumm5DWLFy9Wc3OzFi1apLq6Os2cOVMbNmxQfn5+qsvJqMRKHoZ4AADoH5sxAO9uFwqFVFBQoGAwaKn5KNs+qtM/rd6sMQVevbbkCrPLAQDAUnrz+c29eFIosVnbwWCLmiJtJlcDAMDARUBJoWF5bg3NdUmS9h5pMrkaAAAGLgJKirHUGACA/iOgpBg3DQQAoP8IKCk2kb1QAADoNwJKirHUGACA/iOgpBhzUAAA6D8CSopNGJkrSTre1Kq6xojJ1QAAMDARUFIs1+3UmAKvJOahAADQVwSUNGCYBwCA/iGgpAFLjQEA6B8CShrQgwIAQP8QUNJg4qj2vVAOE1AAAOgLAkoaTGzfC2Xv0UbFYgPuZtEAAJiOgJIG44blyGm3qaU1poOhFrPLAQBgwCGgpIHTYU/OQ9ldU29yNQAADDwElDQ5K5AvSfo7AQUAgF4joKTJpPaAsqsmZHIlAAAMPASUNDkr4JNEDwoAAH1BQEmTRA/Kh4cb1BqNmVwNAAADCwElTcYOzVGe26HWqMGGbQAA9BIBJU3sdpvOZKIsAAB9QkBJIybKAgDQNwSUNDrLnwgo9KAAANAbBJQ0YiUPAAB9Q0BJo8QQz/66ZjWE20yuBgCAgYOAkkbD8tzy+zySGOYBAKA3CChplhjmIaAAANBzBJQ0YyUPAAC9R0BJs8RKHibKAgDQcwSUNEvc1XjXoXoZhmFyNQAADAwElDT79OghcthtOt7Uqtr6sNnlAAAwIBBQ0szrcmjCiFxJDPMAANBTBJQMmJRcycNEWQAAeoKAkgFncdNAAAB6hYCSAcmJsgQUAAB6hICSAYm9UN6vbVBbNGZyNQAAWB8BJQOKhuUq1+1QpC2mvUebzC4HAADLI6BkgN1u05l+hnkAAOgpAkqGsOU9AAA9R0DJkMRE2ffoQQEA4LQIKBkyZWyBJOmd/UGTKwEAwPoIKBlyTqFPdptUE2rRoVCL2eUAAGBpBJQMyXU7kxNl36o+bm4xAABYHAElg84bFx/meZthHgAATomAkkHnjRsqSXpr/3FT6wAAwOoIKBk0tT2gvL0/KMMwzC0GAAALI6Bk0FmBfLkddgWbW/URO8oCAPCJ0h5QKioqZLPZVFZWljxnGIaWLVumwsJC5eTkaO7cudqxY0e6SzGd22nX2YU+SQzzAABwKmkNKFu2bNFjjz2m8847r8v55cuXa8WKFVq1apW2bNmiQCCgefPmqb5+8G9idj4TZQEAOK20BZSGhgbddNNN+tnPfqZhw4YlzxuGoZUrV2rp0qW6/vrrNWXKFK1Zs0ZNTU1au3ZtusqxjPOS81COm1oHAABWlraAcscdd+jaa6/VlVde2eV8VVWVampqVFpamjzn8Xg0Z84cbd68+aQ/KxwOKxQKdTkGqqlF8R6Udz8OqS0aM7kaAACsKS0B5dlnn9Ubb7yhioqKE16rqamRJPn9/i7n/X5/8rXuKioqVFBQkDyKiopSX3SGTBw5REM8TjW3RvV+bYPZ5QAAYEkpDyjV1dX61re+paeeekper/cTr7PZbF2eG4ZxwrmEJUuWKBgMJo/q6uqU1pxJdrtNU8bGJ8oyzAMAwMmlPKBs27ZNtbW1mjZtmpxOp5xOpyorK/XQQw/J6XQme06695bU1tae0KuS4PF45PP5uhwD2dTkhm1MlAUA4GRSHlCuuOIKvfPOO3rzzTeTx/Tp03XTTTfpzTff1MSJExUIBLRx48bk90QiEVVWVmrWrFmpLseSmCgLAMCpOVP9A/Pz8zVlypQu5/Ly8jRixIjk+bKyMpWXl6ukpEQlJSUqLy9Xbm6uFixYkOpyLClxT56/H6xXS2tUXpfD5IoAALCWlAeUnli8eLGam5u1aNEi1dXVaebMmdqwYYPy8/PNKCfjxg3L0Yg8t442RrTzYEgXjh92+m8CACCL2IwBeFOYUCikgoICBYPBATsf5WtPvK6Xdh3WsvmTdcvsYrPLAQAg7Xrz+c29eExyXqcbBwIAgK4IKCZJbNjGPXkAADgRAcUkiR6UPUcaVd/Sam4xAABYDAHFJCOHeDR2aI4Mg2EeAAC6I6CYaNoZ8dU7/1t1zORKAACwFgKKiWZOHC5Jer3qqMmVAABgLQQUE80sHiFJ2r7vuMJtUZOrAQDAOggoJvrUqDyNHOJWuC3GPBQAADohoJjIZrNpRnF8mOd/9zDMAwBAAgHFZDMmtAcUJsoCAJBEQDHZzInxeSjbPqpTazRmcjUAAFgDAcVkZ/nzVZDjUlMkqh0HQmaXAwCAJRBQTGa323TRBOahAADQGQHFAi5O7ofCPBQAACQCiiUkVvK8vveYojHD5GoAADAfAcUCJo/xaYjHqfqWNv29hnkoAAAQUCzA6bB33JdnD8M8AAAQUCxiJvNQAABIIqBYROK+PK/vPSbDYB4KACC7EVAs4tyxBfK67DrWGNEHtQ1mlwMAgKkIKBbhdnbMQ/kbwzwAgCxHQLGQGRPiwzxs2AYAyHYEFAtJbNj22odHFWM/FABAFiOgWMiFZwzTEI9TRxsjeufjoNnlAABgGgKKhbgcdl3y6ZGSpJd3HTa5GgAAzENAsZjLJ42SJL20q9bkSgAAMA8BxWLmnDlakvTW/uM61hgxuRoAAMxBQLGYQIFXZ4/xyTCkV3YzzAMAyE4EFAuae1Z8mOdlhnkAAFmKgGJBl58VH+ap3H1YUZYbAwCyEAHFgi4cP1T5Xqfqmlr19v7jZpcDAEDGEVAsyOmw69ISlhsDALIXAcWi5rYP8zAPBQCQjQgoFjX3zPhE2bc/DupIQ9jkagAAyCwCikWN9nl1TiHLjQEA2YmAYmEdy40JKACA7EJAsbDEcuNX3me5MQAguxBQLOz8oqHyeZ063tSqN6vrzC4HAICMIaBYmNNh15z2XpQXdxwyuRoAADKHgGJx154bkCT9/u2DMgyGeQAA2YGAYnFzzxqtXLdDHx9v1pvVx80uBwCAjCCgWJzX5dCVZ/slxXtRAADIBgSUAeDa88ZIkl5456BirOYBAGQBAsoAMOfMUcpzO3Qg2KLtDPMAALIAAWUA8LocmjeZYR4AQPYgoAwQ155XKIlhHgBAdiCgDBCXnTlS+R6nakItemMfm7YBAAa3lAeUiooKXXTRRcrPz9fo0aP1uc99Trt27epyjWEYWrZsmQoLC5WTk6O5c+dqx44dqS5lUPE4HZp3TnyY538Y5gEADHIpDyiVlZW644479Le//U0bN25UW1ubSktL1djYmLxm+fLlWrFihVatWqUtW7YoEAho3rx5qq+vT3U5g8pnWc0DAMgSNiPN25MePnxYo0ePVmVlpS677DIZhqHCwkKVlZXpe9/7niQpHA7L7/frgQce0MKFC0/7M0OhkAoKChQMBuXz+dJZvqVE2mKa9m8bVd/Spl8v/IxmFA83uyQAAHqsN5/faZ+DEgwGJUnDh8c/TKuqqlRTU6PS0tLkNR6PR3PmzNHmzZvTXc6A5nbaddU5ia3vD5hcDQAA6ZPWgGIYhu655x5dcsklmjJliiSppqZGkuT3+7tc6/f7k691Fw6HFQqFuhzZKrFp2+/fqVFrNGZyNQAApEdaA8qdd96pt99+W88888wJr9lsti7PDcM44VxCRUWFCgoKkkdRUVFa6h0ILvn0SI0c4taRhrD+8vdas8sBACAt0hZQvvnNb+r555/XSy+9pHHjxiXPBwLxIYruvSW1tbUn9KokLFmyRMFgMHlUV1enq2zLczns+qdp8fZ89vV9JlcDAEB6pDygGIahO++8U7/97W/1l7/8RcXFxV1eLy4uViAQ0MaNG5PnIpGIKisrNWvWrJP+TI/HI5/P1+XIZl+8aLwkqXL3YR043mxyNQAApF7KA8odd9yhp556SmvXrlV+fr5qampUU1Oj5ub4B6nNZlNZWZnKy8u1fv16vfvuu7rllluUm5urBQsWpLqcQal4ZJ4unjhcMUN6but+s8sBACDlUh5QVq9erWAwqLlz52rMmDHJY926dclrFi9erLKyMi1atEjTp0/Xxx9/rA0bNig/Pz/V5QxaiV6UX2+tVpQ9UQAAg0za90FJh2zdB6WzltaoZpb/WcHmVq35+gzNOXOU2SUBAHBKltoHBenhdTn0fy8YK4nJsgCAwYeAMoB9cUZ8ufXGnYd0uD5scjUAAKQOAWUAmxTw6fyioWqLGfrtG0yWBQAMHgSUAe6LF8V7UdZtqdYAnE4EAMBJEVAGuPlTC5XndmjPkUb9b9Uxs8sBACAlCCgDXJ7HqevOL5QkPfnXveYWAwBAihBQBoGvz47v1vvizhrtOdxgcjUAAPQfAWUQKPHn64pJo2UY0s83VZldDgAA/UZAGST++bKJkqTfbNvPkmMAwIBHQBkkZhQP1/lFQxVpi+mXr+01uxwAAPqFgDJI2Gw2LWzvRfnlax+pMdxmckUAAPQdAWUQKT0noAkjchVsbtWvt1abXQ4AAH1GQBlEHHabbrs03ovy81er1BaNmVwRAAB9Q0AZZD4/bZxG5Ln18fFm/f6dg2aXAwBAnxBQBhmvy6GbZ02QJP1n5R62vwcADEgElEHoKxefoVy3QzsPhvTHd2vMLgcAgF4joAxCw/Lcuu2S+O6yP96wi7koAIABh4AySH3jsokanufWnsONem7bfrPLAQCgVwgog1S+16U7Lv+0JGnln3arORI1uSIAAHqOgDKIffni8Ro7NEeHQmE9uXmv2eUAANBjBJRBzON06J55Z0qSVr/8gY43RUyuCACAniGgDHKfu2CsJgXyFWpp0+qXPzS7HAAAeoSAMsg57DYt/j9nSZKe3LxXB4PNJlcEAMDpEVCywOVnjdaMCcMVbotp+R93mV0OAACnRUDJAjabTUuvPVs2m7R++8f66wdHzC4JAIBTIqBkialFQ/XVi8+QJC1d/45aWll2DACwLgJKFvn2VWfJ7/No79EmPfLSB2aXAwDAJyKgZBGf16X75p8jSVpd+aE+qG0wuSIAAE6OgJJlrp4S0D9MGq3WqKF/Wf8OdzsGAFgSASXL2Gw23X/dOcpxOfR61THu0wMAsCQCShYqGp6ru+eVSJLKX3hPtaEWkysCAKArAkqW+trsYp1T6NPxplbd8+u3FIsx1AMAsA4CSpZyOez6jy9eoByXQ5s+OKLHXt1jdkkAACQRULLYp0cP0bLrJkuS/v3FXdq+r87kigAAiCOgZLkbphfp2vPGqC1m6K5ntyvU0mp2SQAAEFCync1mU/n/PVdjh+ao+lizvr/+XZYeAwBMR0CBCnJceuhL58tht+n5tw7oua0sPQYAmIuAAknStDOG6+4r40uPv/+7d7V17zGTKwIAZDMCCpIWzf20rjrHr0g0pn/+1TbtO9pkdkkAgCxFQEGS3W7TgzeeryljfTrWGNHX12xRsJlJswCAzCOgoItct1OP33yRAj6vPqht0J1r31BbNGZ2WQCALENAwQn8Pq9+fvN05bgcevX9I1r23ztY2QMAyCgCCk5qytgC/ccXz5fNJj31t316cONus0sCAGQRAgo+Uek5Ad332fhOsw/95QOt/BMhBQCQGQQUnNIts4u19JqzJUkr//S+Hv7z+yZXBADIBgQUnNY3Lpuoe6+eJEn6ycbd+ulLH5hcEQBgsCOgoEdun/MpffeqsyRJP35xl3760gdMnAUApA0BBT12x+Wf1rfnnSkpHlK+/7t3WYIMAEgLUwPKI488ouLiYnm9Xk2bNk2vvvqqmeWgB755RYnumz9ZNpv09P/u0zd+uVWN4TazywIADDKmBZR169aprKxMS5cu1fbt23XppZfq6quv1r59+8wqCT30tdnFevTL0+R12fXSrsO64T9f06FQi9llAQAGEZth0kSCmTNn6sILL9Tq1auT584++2x97nOfU0VFxSm/NxQKqaCgQMFgUD6fL92l4hNs31en29Zs1dHGiAoLvHr0K9N03rihZpcFALCo3nx+m9KDEolEtG3bNpWWlnY5X1paqs2bN59wfTgcVigU6nLAfBeMH6b1i2Zr4qg8HQi26J9Wb9YvNlUxeRYA0G+mBJQjR44oGo3K7/d3Oe/3+1VTU3PC9RUVFSooKEgeRUVFmSoVpzF+RK7WL5qtq87xqzVq6If/s1MLf7VNwSZuMggA6DtTJ8nabLYuzw3DOOGcJC1ZskTBYDB5VFdXZ6pE9EBBjkuPfnmals2fLLfDrg07D+mah17Vto/qzC4NADBAmRJQRo4cKYfDcUJvSW1t7Qm9KpLk8Xjk8/m6HLAWm82mW2YX67/+3yyNH56rj4836wuPbtYP/3snq3wAAL1mSkBxu92aNm2aNm7c2OX8xo0bNWvWLDNKQoqcO65A/3PXJbr+grGKGdIv/lql0gdf0Uu7as0uDQAwgJg2xHPPPffo5z//uX7xi1/ovffe09133619+/bp9ttvN6skpIjP69KKG8/Xmq/P0NihOfr4eLO+9sQWfevZ7aplOTIAoAdMW2YsxTdqW758uQ4ePKgpU6bowQcf1GWXXXba72OZ8cDRGG7Tio279cRfqxQzpByXQ9+4tFj/POdTGuJxml0eAAxIhmEoEo0p3BZTpP0IJ79GO85FYwq3xhSJdlwXaYt2eR7u9Lg18Tga0+h8r5Zdd05K6+7N57epAaWvCCgDz1vVx3X/f+/QG/uOS5JG5Ln1rStL9KUZ4+VycMcFAANPNGYo3BZVS2vsxK+tUbW0xdTSGlW4reN5OPG8y+Oowq2xjseJ19uviXR+3tbxPN0mjszTX74zN6U/k4ACSzIMQy/uqNHyP+7SniONkqTxw3P1jcsm6gvTxsnrcphcIYCBri0aU3NrVM2t8Q/95taomiPR5LmWzo9b4wEicXQ9Fw8DzZGoWtq6nW+Nn2uNWufj0+2wy+O0y+3s+Jo8HInHjpNf57DL1em6xPlhuW7Nn1qY0joJKLC01mhMz26p1n/8abeONEQkSSOHuHXLrAn6ysUTVJDrMrlCAOnSGo2pKRL/4G+MtKk5ElVTJKqmzo/bg0RTIkxE2tTcGn/e0tr5fEewSLxmVmhwOWzyOh3yuOzytH/teG6X1+U44bnH2X5teyBInnPZ5XY45HUlAoOjS6jwuNqDhsueDBwn26LDiggoGBCaI1H9emu1fvbqHu2va5Yk5bkd+twFY/WlGeM1ZWyByRUC2SsaM9QUaVNTJKrGcLevkTY1heNfE+eSr7dG1dT5XHvwaAzHQ0amAoTNFp/zFg8GduW4HfEjcc6VeGxPnvN0e+5tDxnx1zpChjfxuD1QeF0OOewDIyCYjYCCAaUtGtPv3zmo1S9/qL/X1CfPTxnr040Xjdc/nl8on5deFeBUDMNIhoSGzkdLmxojbWoIx19LvB5/3BEyGsLxXozE+ebWaFrrddptynE7lOt2KNftVI4r/rjzOW/inKsjXOR2Chon++p1xr8OpF6FbEJAwYBkGIZe+/ContlSrRffrVEkGp8E5nbaNffMUbr2vDG64mw/q38wqLRFY2oIt6m+JX7Eg0Vrx+P2r92fN3QKGokQEkvDu7ndJuV5nMpzO5XrcWiIx6lct0N5bmcyTHR+PdflUG7iebfAkevuCBhuBwEiGxFQMOAda4zot2/s17NbqvVBbUPyfCKszJvs15wzR2m0z2tilchmhmEo3BZTqKU1GS7qk4/jX0Ptjxs6hY/6llbVJwJHS1vKeyoSgWJI+5GX/Oro9NipvESwaD+X63Z0eq3jenoikEoEFAwahmHovYP1euGdg/r9OwdV1b76J2FSIF9zzhqly0pG6YLxQ5XrpncFPdO55yLY3JoMGqHmRLhoVai5c9joCB+J4JHK+RQep135XpfyvU7lezuCRL7XqXyPU0O87c/bHw/xuJIhJP6aQ/kel7wuAgWsi4CCQSkRVv747kFV7j6stz8OqvNfr9Nu0zljC3TRGcN0UfFwXVA0lB6WQSzSFkuGhVC3gJEIF6c615Cie0TZbNIQj1O+TuGia9CIP/Z540Eiv/35EG/8exIBg/2AkA0IKMgKRxvC2vTBEVXuOqzNHx5VzUm20R85xKMpY32aUligyYU+lYweojNG5Mnt5MPATIkJnR09EoleiVMHjM6PW1pTs1FVjsshX05HqEgEDV9Ox3Nfe+jofF3i6xC3U3ZWcAA9QkBB1jEMQx8fb9bWvXXasveYtu6t0/u19SedNOiw23TGiFx9atQQFY/MU9GwHI0bnqvxw3M1dmgOG8adRjRmdFkh0hCOh4uOeRYdczJC3eZkdD4XTdGMziEepwo6h4n2ENH5XL43fo0v58QQQs8FkDkEFEDxfVb+XhPSuwdC2vFxUO8dDOnDw42n7dofnufW6HyP/D6v/D6PRuV7NCzXreF5bg3Lc2t4rjv5QZfvdcrjtGagSdyroyUSU0tbx2ZYiY2uOm+W1XlPi8ZI9+Wo8ef14dRP6nTYbV0CQyJI5HtdXUKEL/m1U9jwujTE62T/CWAAIaAAn8AwDB0KhfVBbYM+qK3XR8eaVH2sWfvrmlR9rEmNkd5/+LqdduV7Ou3Z4O7YwCmxdbTLET8cdslhb/9qs510MmPMMNoPKRYz1BYzFI0Zao3G1BY11BaLKRI1FGnfajvS6QZh4U73/mhpjaZl2Wn33zs5kbN9vkVyroX3xOEQXzKMxJ/nuh1M6ASySG8+v1nygKxis9kUKPAqUODVJSUju7xmGIaON7XqUH2LDoXCOhRq0aFgi442RnSsMaK6pvavjZH4kEZ7T0ykLaajbREzfp0ec9htyY2scjtteJXXaU+LXE98r4rEEtMhHqdy21eN5Hk6VonkeRwaYuGeIwCDAwEFaGez2TSsfRhnUuD01yfmYtS3tCZ33uw8hJK8tXk0cRvzeM9ItL1HJGYY6t5/aciQw2aT3W6T3WaT3RbvcXE5bHLYbXI67HLabV16Zjru49H1nh05Loe87WGEeRYABhoCCtBHDrtNBTnxyZgAgNTin1UAAMByCCgAAMByCCgAAMByCCgAAMByCCgAAMByCCgAAMByCCgAAMByCCgAAMByCCgAAMByCCgAAMByCCgAAMByCCgAAMByCCgAAMByBuTdjI32e9SHQiGTKwEAAD2V+NxOfI6fyoAMKPX19ZKkoqIikysBAAC9VV9fr4KCglNeYzN6EmMsJhaLadeuXZo8ebKqq6vl8/nMLsmyQqGQioqKaKceoK16hnbqOdqq52irnhno7WQYhurr61VYWCi7/dSzTAZkD4rdbtfYsWMlST6fb0D+T8o02qnnaKueoZ16jrbqOdqqZwZyO52u5ySBSbIAAMByCCgAAMByBmxA8Xg8uu++++TxeMwuxdJop56jrXqGduo52qrnaKueyaZ2GpCTZAEAwOA2YHtQAADA4EVAAQAAlkNAAQAAlkNAAQAAlmPpgPLII4+ouLhYXq9X06ZN06uvvvqJ1/72t7/VvHnzNGrUKPl8Pn3mM5/Riy++mMFqzdObdursr3/9q5xOp84///z0FmghvW2rcDispUuX6owzzpDH49GnPvUp/eIXv8hQtebpbTs9/fTTmjp1qnJzczVmzBh97Wtf09GjRzNUrXleeeUVzZ8/X4WFhbLZbPrd73532u+prKzUtGnT5PV6NXHiRD366KPpL9RkvW2nbH4/78vfVMJge0+3bEBZt26dysrKtHTpUm3fvl2XXnqprr76au3bt++k17/yyiuaN2+eXnjhBW3btk2XX3655s+fr+3bt2e48szqbTslBINBffWrX9UVV1yRoUrN15e2uuGGG/TnP/9Zjz/+uHbt2qVnnnlGkyZNymDVmdfbdtq0aZO++tWv6tZbb9WOHTv03HPPacuWLbrtttsyXHnmNTY2aurUqVq1alWPrq+qqtI111yjSy+9VNu3b9e//Mu/6K677tJ//dd/pblSc/W2nbL1/VzqfVslDMr3dMOiZsyYYdx+++1dzk2aNMm49957e/wzJk+ebNx///2pLs1S+tpON954o/H973/fuO+++4ypU6emsULr6G1b/eEPfzAKCgqMo0ePZqI8y+htO/34xz82Jk6c2OXcQw89ZIwbNy5tNVqRJGP9+vWnvGbx4sXGpEmTupxbuHChcfHFF6exMmvpSTudTDa8n3fXm7YajO/pluxBiUQi2rZtm0pLS7ucLy0t1ebNm3v0M2KxmOrr6zV8+PB0lGgJfW2nJ554Qh9++KHuu+++dJdoGX1pq+eff17Tp0/X8uXLNXbsWJ155pn6zne+o+bm5kyUbIq+tNOsWbO0f/9+vfDCCzIMQ4cOHdJvfvMbXXvttZkoeUB57bXXTmjbq666Slu3blVra6tJVVlfNryf98dgfU+35M0Cjxw5omg0Kr/f3+W83+9XTU1Nj37GT37yEzU2NuqGG25IR4mW0Jd2ev/993Xvvffq1VdfldNpyf/9adGXttqzZ482bdokr9er9evX68iRI1q0aJGOHTs2aOeh9KWdZs2apaefflo33nijWlpa1NbWpuuuu04PP/xwJkoeUGpqak7atm1tbTpy5IjGjBljUmXWlg3v5301mN/TLdmDkmCz2bo8NwzjhHMn88wzz2jZsmVat26dRo8ena7yLKOn7RSNRrVgwQLdf//9OvPMMzNVnqX05m8qFovJZrPp6aef1owZM3TNNddoxYoVevLJJwd1L4rUu3bauXOn7rrrLv3gBz/Qtm3b9Mc//lFVVVW6/fbbM1HqgHOytj3ZecRl2/t5bwz293RLxq2RI0fK4XCc8C+22traE/710d26det066236rnnntOVV16ZzjJN19t2qq+v19atW7V9+3bdeeedkuIfwoZhyOl0asOGDfqHf/iHjNSeaX35mxozZozGjh3b5dbgZ599tgzD0P79+1VSUpLWms3Ql3aqqKjQ7Nmz9d3vfleSdN555ykvL0+XXnqp/u3f/o1egU4CgcBJ29bpdGrEiBEmVWVd2fR+3heD/T3dkj0obrdb06ZN08aNG7uc37hxo2bNmvWJ3/fMM8/olltu0dq1a7Ni/Lu37eTz+fTOO+/ozTffTB633367zjrrLL355puaOXNmpkrPuL78Tc2ePVsHDhxQQ0ND8tzu3btlt9s1bty4tNZrlr60U1NTk+z2rm8lDodDUkfvAOI+85nPnNC2GzZs0PTp0+VyuUyqypqy7f28Lwb9e7pZs3NP59lnnzVcLpfx+OOPGzt37jTKysqMvLw8Y+/evYZhGMa9995rfOUrX0lev3btWsPpdBo//elPjYMHDyaP48ePm/UrZERv26m7wTTj+3R621b19fXGuHHjjM9//vPGjh07jMrKSqOkpMS47bbbzPoVMqK37fTEE08YTqfTeOSRR4wPP/zQ2LRpkzF9+nRjxowZZv0KGVNfX29s377d2L59uyHJWLFihbF9+3bjo48+MgzjxLbas2ePkZuba9x9993Gzp07jccff9xwuVzGb37zG7N+hYzobTtl6/u5YfS+rbobTO/plg0ohmEYP/3pT40zzjjDcLvdxoUXXmhUVlYmX7v55puNOXPmJJ/PmTPHkHTCcfPNN2e+8AzrTTt1N5j+mHuit2313nvvGVdeeaWRk5NjjBs3zrjnnnuMpqamDFedeb1tp4ceesiYPHmykZOTY4wZM8a46aabjP3792e46sx76aWXTvm+c7K2evnll40LLrjAcLvdxoQJE4zVq1dnvvAM6207ZfP7eV/+pjobTO/pNsOgDxYAAFiLJeegAACA7EZAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlvP/AXuzOgpGI5yoAAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -1281,1864 +1331,113 @@ } ], "source": [ - "exe.output[0].plot_energies()" + "exe.output[0].plot()" ] }, { - "cell_type": "code", - "execution_count": 81, - "id": "1d5b5203-d07f-485b-9553-9150f4a674e7", + "cell_type": "markdown", + "id": "e21f6582-e7ec-43be-80ec-e9ad53aabc43", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[11.122158782511535,\n", - " -0.2782678462106827,\n", - " -0.9960554302957411,\n", - " -3.560246436024868e-08,\n", - " -3.560246436024868e-08]" - ] - }, - "execution_count": 81, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "exe.output[0].pot_energies" + "# Combine Minimize and Murnaghan" ] }, { "cell_type": "code", - "execution_count": 82, - "id": "d2cc3b3a-5daa-49bb-9d6d-2994ebc74273", + "execution_count": null, + "id": "149c52b5-a0ce-4e6b-ba55-d94d33aa2f8a", "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "903d8dc3dee8402b9a481fa1516cc478", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "NGLWidget(max_frame=4)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "exe.output[0].animate_structures()" - ] - }, - { - "cell_type": "markdown", - "id": "32c15731-fd88-40d1-9045-beb9c53de501", - "metadata": { - "tags": [] - }, + "outputs": [], "source": [ - "# Murnaghan" + "m = MurnaghanNode()" ] }, { - "cell_type": "markdown", - "id": "aa535698-ddaa-4d60-862c-d6d6d5ecb6c8", + "cell_type": "code", + "execution_count": null, + "id": "aca24005-ea49-4389-bc26-f292fd0a75a2", "metadata": {}, - "source": [ - "## Basic" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "4acdeafc-90b5-4b3f-9559-c74b9fa221ab", - "metadata": {}, - "outputs": [], - "source": [ - "m = MurnaghanNode()" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "f8cf3136-9b7c-4f1e-b630-962795527946", - "metadata": {}, - "outputs": [], - "source": [ - "m.input.node = AseStaticNode()\n", - "m.input.node.input.calculator = MorsePotential()\n", - "m.input.structure = bulk(\"Fe\", a=1.2)" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "fef21aa4-d9f1-4d4a-8761-af1bc3121e5b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "m.input.node.input" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "41a68b17-c7c4-4a5f-8f04-11bee18fe55a", - "metadata": {}, - "outputs": [], - "source": [ - "m.input.set_strain_range(.5, 50)" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "fd107556-99b6-4042-9209-9412b4bbff94", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([0.79370053, 0.8043555 , 0.81473542, 0.82485739, 0.83473686,\n", - " 0.84438786, 0.85382314, 0.86305437, 0.87209225, 0.88094658,\n", - " 0.88962642, 0.89814011, 0.90649538, 0.9146994 , 0.92275884,\n", - " 0.93067991, 0.93846839, 0.94612969, 0.95366889, 0.96109074,\n", - " 0.96839969, 0.97559996, 0.98269548, 0.98968999, 0.996587 ,\n", - " 1.00338986, 1.01010169, 1.0167255 , 1.02326411, 1.0297202 ,\n", - " 1.03609634, 1.04239496, 1.04861836, 1.05476875, 1.06084824,\n", - " 1.06685884, 1.07280247, 1.07868096, 1.08449606, 1.09024946,\n", - " 1.09594278, 1.10157754, 1.10715524, 1.11267731, 1.1181451 ,\n", - " 1.12355993, 1.12892306, 1.13423572, 1.13949907, 1.14471424])" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "m.input.strains" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "0715614a-7284-4388-ac6b-c97bfedf7184", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "m.input.check_ready()" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "c2aa3093-1ea8-4099-bc14-be0c06e9d34b", - "metadata": { - "scrolled": true, - "tags": [] - }, - "outputs": [], - "source": [ - "exe = m.run(how='foreground')" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "be97853a-f182-4b4f-af74-7fd5a4fbd850", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(ReturnStatus(Code.DONE, None),)" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exe.status" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "47f916ef-b140-49c5-adf1-93dca91b4540", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "exe.output[0].plot()" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "14162f5b-1318-4595-8c8c-d6346a21721d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.6788586373205143" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exe.output[0].equilibrium_volume" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "0f5ff296-df33-40d2-851b-02d6ded72dd6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.6788586373205143" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exe.output[0].get_structure().get_volume()" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "92b06330-b1fc-41d0-8bd8-bf1b11bf448c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Atoms(symbols='Fe', pbc=True, cell=[[-0.5536557129291797, 0.5536557129291797, 0.5536557129291797], [0.5536557129291797, -0.5536557129291797, 0.5536557129291797], [0.5536557129291797, 0.5536557129291797, -0.5536557129291797]])" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exe.output[0].get_structure()" - ] - }, - { - "cell_type": "markdown", - "id": "26bade63-559f-4c93-be24-5561f5c8190f", - "metadata": {}, - "source": [ - "## Again but execute children as background processes, but keep the node itself blocking" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "89169376-be36-4ceb-9f4e-6e1f3247bc62", - "metadata": {}, - "outputs": [], - "source": [ - "m = MurnaghanNode()" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "2ee9f1d4-5b14-4340-98d4-4bd293af89a4", - "metadata": {}, - "outputs": [], - "source": [ - "m.input.node = AseStaticNode()\n", - "m.input.node.input.calculator = MorsePotential()\n", - "m.input.structure = bulk(\"Fe\", a=1.2)" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "3d73a9de-7b4e-476a-b50a-ac6a3957a7ab", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 35, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "m.input.node.input" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "0f075d90-e636-49be-b1a6-741a56363f54", - "metadata": {}, - "outputs": [], - "source": [ - "m.input.set_strain_range(.6, 1000)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2b2f703c-745b-49f9-a81f-a780248e9cd3", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "58064e52-1c94-49fd-b38f-614cf6f19004", - "metadata": {}, - "outputs": [], - "source": [ - "m.input.child_executor = ProcessExecutor" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "d82a28ab-1a96-4a3a-8f79-5a875ac20788", - "metadata": { - "scrolled": true, - "tags": [] - }, - "outputs": [], - "source": [ - "exe = m.run(how='foreground')" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "5472daed-f25c-4aab-b101-90c76a0235a5", - "metadata": { - "scrolled": true, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exe._run_machine.state" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "a42865c9-8616-4335-8e46-ec4839daab0a", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "27.62762461800594" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exe._run_time" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "d894a79f-a9e6-4667-9a9b-2bd9a088622f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1.2397998943924904e-05" - ] - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exe._collect_time" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "78017969-23fc-46f5-b99f-cd1d2dc74c00", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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uu+++1RcXCyHw6GxY8fqiSeeiFK18WWg1/aZZ57R5MmTlZaWJrfbrX/8x39UTU1NlKqNHxs2bND8+fNVUFAgi8WiV1555bTPWb9+vaZMmaKUlBSNGTNG//Ef/xH5QuPQQK/tSy+9pDlz5ignJ0eZmZmaPn261qxZE51i40wof2+7hfO9jCASglWrVmnRokW67777tHXrVl1yySWaN2+eDh482OfzGhoadP311+trX/talCqNP6Fc22uuuUZ/+9vf9Pjjj2vXrl167rnnNGHChChWHR8Gem3fffddXX/99brxxhu1fft2vfDCC9q0aZNuuummKFce+3w+nyZPnqwVK1b06/FlZWW6/PLLdckll2jr1q2699579eMf/1gvvvhihCuNPwO9ths2bNCcOXO0evVqbdmyRbNnz9b8+fO1devWCFcafwZ6bbuF/b3MwIBdcMEFxq233trj3IQJE4yf//znfT5vwYIFxj//8z8b999/vzF58uQIVhi/BnptX3/9dcPlchk1NTXRKC+uDfTaPvTQQ8aYMWN6nPvNb35jFBYWRqzGoUCS8fLLL/f5mHvuuceYMGFCj3O33HKLceGFF0awsvjXn2t7KhMnTjQeeOCB8Bc0hAzk2ob7vYwRkQFqbW3Vli1bNHfu3B7n586dq/fff7/X5z355JPau3ev7r///kiXGLdCubavvvqqpk6dqgcffFAjR47U+PHjdffdd6u5uTkaJceNUK7tjBkzdOjQIa1evVqGYejo0aP685//rCuuuCIaJQ9pH3zwwUl/Fl//+te1efNmtbW1mVTV0NTZ2Smv16usrCyzSxkSIvFeFtM3vYtF1dXV6ujoUF5eXo/zeXl5qqysPOVzdu/erZ///Od65513ZLNxyXsTyrXdt2+f3n33XaWkpOjll19WdXW1brvtNtXW1tIncoJQru2MGTP0zDPPaMGCBWppaVF7e7u++c1v6re//W00Sh7SKisrT/ln0d7erurqarndbpMqG3p+9atfyefz6ZprrjG7lLgXqfcyRkRCZLFYenxtGMZJ5ySpo6ND1157rR544AGNHz8+WuXFtf5eWynwrx2LxaJnnnlGF1xwgS6//HI9/PDD+uMf/8ioyCkM5Nru2LFDP/7xj/Uv//Iv2rJli9544w2VlZXp1ltvjUapQ96p/ixOdR6he+6557R06VKtWrVKubm5ZpcT1yL5XsY/zwcoOztbSUlJJ/0rsqqq6qR/4UiS1+vV5s2btXXrVt1xxx2SAm+ehmHIZrPpzTff1KWXXhqV2mPdQK+tJLndbo0cObLH7abPPPNMGYahQ4cOqbS0NKI1x4tQru3y5ct10UUX6ac//akk6ZxzzlF6erouueQS/eIXv+Bf7YOQn59/yj8Lm82mESNGmFTV0LJq1SrdeOONeuGFF3TZZZeZXU7ci+R7GSMiA2S32zVlyhStXbu2x/m1a9dqxowZJz0+MzNTn376qbZt2xY8br31Vp1xxhnatm2bpk2bFq3SY95Ar60kXXTRRTpy5IgaGxuD57744gtZrVYVFhZGtN54Esq1bWpqktXa838RSUlJko7/6x2hmT59+kl/Fm+++aamTp2q5ORkk6oaOp577jndcMMNevbZZ+lpCpOIvpcNut01AT3//PNGcnKy8fjjjxs7duwwFi1aZKSnpxv79+83DMMwfv7znxvXXXddr89n1UzvBnptvV6vUVhYaFx99dXG9u3bjfXr1xulpaXGTTfdZNavELMGem2ffPJJw2azGY899pixd+9e49133zWmTp1qXHDBBWb9CjHL6/UaW7duNbZu3WpIMh5++GFj69atxoEDBwzDOPna7tu3z0hLSzPuuusuY8eOHcbjjz9uJCcnG3/+85/N+hVi1kCv7bPPPmvYbDbj0UcfNSoqKoJHfX29Wb9CzBrotf2ycL2XEURC9OijjxrFxcWG3W43zjvvPGP9+vXB7y1cuNCYOXNmr88liPRtoNd2586dxmWXXWakpqYahYWFxuLFi42mpqYoVx0fBnptf/Ob3xgTJ040UlNTDbfbbfzgBz8wDh06FOWqY9/bb79tSDrpWLhwoWEYp76269atM77yla8YdrvdGD16tLFy5croFx4HBnptZ86c2efjcVwof29PFK73MothMMYKAADMQY8IAAAwDUEEAACYhiACAABMQxABAACmIYgAAADTEEQAAIBpCCIAAMA0BBEAAGAagggAADANQQQAAJiGIAIAAExDEAEAAKb5v1E7qt2HJCfYAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "exe.output[0].plot()" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "id": "45162eb2-b23d-45c6-8aad-dfe9a6a484d1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.6818586500998999" - ] - }, - "execution_count": 43, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exe.output[0].get_structure().get_volume()" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "id": "f0bcfe59-2168-4e74-9d7a-33d900368907", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.6818586500999" - ] - }, - "execution_count": 44, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exe.output[0].equilibrium_volume" - ] - }, - { - "cell_type": "markdown", - "id": "36b17048-3941-4d86-bd2a-e131371f4bad", - "metadata": {}, - "source": [ - "## Again but execute everything in the background." - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "10f6c113-1e35-48f0-8878-291129bd8a60", - "metadata": {}, - "outputs": [], - "source": [ - "m = MurnaghanNode()" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "70832c31-040e-49be-b0f7-172f930cf31b", - "metadata": {}, - "outputs": [], - "source": [ - "m.input.node = AseStaticNode()\n", - "m.input.node.input.calculator = MorsePotential()\n", - "m.input.structure = bulk(\"Fe\", a=1.2)" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "94f4c51d-b69b-4477-a9db-d0ee7627cee6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 47, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "m.input.node.input" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "9a000824-0a9e-4395-8e07-00e484bc7937", - "metadata": {}, - "outputs": [], - "source": [ - "m.input.set_strain_range(.7, 100)" - ] - }, - { - "cell_type": "markdown", - "id": "82c9d3a2-d93c-41aa-a1af-52bfab8cd8df", - "metadata": {}, - "source": [ - "Use the threading backend just to show off." - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "id": "ca50857c-1c2f-4fad-a58c-16b399b8721d", - "metadata": {}, - "outputs": [], - "source": [ - "from pyiron_contrib.tinybase.executor import BackgroundExecutor\n", - "\n", - "m.input.child_executor = BackgroundExecutor" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "id": "12f20f5c-27a3-4533-ad19-8ec06e1c8a90", - "metadata": { - "scrolled": true, - "tags": [] - }, - "outputs": [], - "source": [ - "exe = m.run(how='background')" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "id": "2c5d8bea-49ec-4004-8a54-3ded7a3f413d", - "metadata": {}, - "outputs": [], - "source": [ - "exe.wait()" - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "id": "ea7bcb58-0890-487e-bef5-bd3cb36143c1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 52, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exe._run_machine.state" - ] - }, - { - "cell_type": "code", - "execution_count": 53, - "id": "0b7c2912-6847-4262-a62d-7233ca398643", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "12.170459961984307" - ] - }, - "execution_count": 53, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exe._run_time" - ] - }, - { - "cell_type": "code", - "execution_count": 54, - "id": "fd5ca921-2062-4f85-b014-382561e9893a", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2.338102785870433e-05" - ] - }, - "execution_count": 54, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exe._collect_time" - ] - }, - { - "cell_type": "code", - "execution_count": 55, - "id": "4d1223f7-2d72-413e-b20b-cf42781780bb", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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zk2aNMm49957e/wzJk+ebNx///2pLs1S+tpON954o/H973/fuO+++4ypU6emsULr6G1b/eEPfzAKCgqMo0ePZqI8y+htO/34xz82Jk6c2OXcQw89ZIwbNy5tNVqRJGP9+vWnvGbx4sXGpEmTupxbuHChcfHFF6exMmvpSTudTDa8n3fXm7YajO/pluxBiUQi2rZtm0pLS7ucLy0t1ebNm3v0M2KxmOrr6zV8+PB0lGgJfW2nJ554Qh9++KHuu+++dJdoGX1pq+eff17Tp0/X8uXLNXbsWJ155pn6zne+o+bm5kyUbIq+tNOsWbO0f/9+vfDCCzIMQ4cOHdJvfvMbXXvttZkoeUB57bXXTmjbq666Slu3blVra6tJVVlfNryf98dgfU+35M0Cjxw5omg0Kr/f3+W83+9XTU1Nj37GT37yEzU2NuqGG25IR4mW0Jd2ev/993Xvvffq1VdfldNpyf/9adGXttqzZ482bdokr9er9evX68iRI1q0aJGOHTs2aOeh9KWdZs2apaefflo33nijWlpa1NbWpuuuu04PP/xwJkoeUGpqak7atm1tbTpy5IjGjBljUmXWlg3v5301mN/TLdmDkmCz2bo8NwzjhHMn88wzz2jZsmVat26dRo8ena7yLKOn7RSNRrVgwQLdf//9OvPMMzNVnqX05m8qFovJZrPp6aef1owZM3TNNddoxYoVevLJJwd1L4rUu3bauXOn7rrrLv3gBz/Qtm3b9Mc//lFVVVW6/fbbM1HqgHOytj3ZecRl2/t5bwz293RLxq2RI0fK4XCc8C+22traE/710d26det066236rnnntOVV16ZzjJN19t2qq+v19atW7V9+3bdeeedkuIfwoZhyOl0asOGDfqHf/iHjNSeaX35mxozZozGjh3b5dbgZ599tgzD0P79+1VSUpLWms3Ql3aqqKjQ7Nmz9d3vfleSdN555ykvL0+XXnqp/u3f/o1egU4CgcBJ29bpdGrEiBEmVWVd2fR+3heD/T3dkj0obrdb06ZN08aNG7uc37hxo2bNmvWJ3/fMM8/olltu0dq1a7Ni/Lu37eTz+fTOO+/ozTffTB633367zjrrLL355puaOXNmpkrPuL78Tc2ePVsHDhxQQ0ND8tzu3btlt9s1bty4tNZrlr60U1NTk+z2rm8lDodDUkfvAOI+85nPnNC2GzZs0PTp0+VyuUyqypqy7f28Lwb9e7pZs3NP59lnnzVcLpfx+OOPGzt37jTKysqMvLw8Y+/evYZhGMa9995rfOUrX0lev3btWsPpdBo//elPjYMHDyaP48ePm/UrZERv26m7wTTj+3R621b19fXGuHHjjM9//vPGjh07jMrKSqOkpMS47bbbzPoVMqK37fTEE08YTqfTeOSRR4wPP/zQ2LRpkzF9+nRjxowZZv0KGVNfX29s377d2L59uyHJWLFihbF9+3bjo48+MgzjxLbas2ePkZuba9x9993Gzp07jccff9xwuVzGb37zG7N+hYzobTtl6/u5YfS+rbobTO/plg0ohmEYP/3pT40zzjjDcLvdxoUXXmhUVlYmX7v55puNOXPmJJ/PmTPHkHTCcfPNN2e+8AzrTTt1N5j+mHuit2313nvvGVdeeaWRk5NjjBs3zrjnnnuMpqamDFedeb1tp4ceesiYPHmykZOTY4wZM8a46aabjP3792e46sx76aWXTvm+c7K2evnll40LLrjAcLvdxoQJE4zVq1dnvvAM6207ZfP7eV/+pjobTO/pNsOgDxYAAFiLJeegAACA7EZAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlvP/AXuzOgpGI5yoAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "exe.output[0].plot()" - ] - }, - { - "cell_type": "markdown", - "id": "e21f6582-e7ec-43be-80ec-e9ad53aabc43", - "metadata": {}, - "source": [ - "# Combine Minimize and Murnaghan" - ] - }, - { - "cell_type": "code", - "execution_count": 83, - "id": "149c52b5-a0ce-4e6b-ba55-d94d33aa2f8a", - "metadata": {}, - "outputs": [], - "source": [ - "m = MurnaghanNode()" - ] - }, - { - "cell_type": "code", - "execution_count": 84, - "id": "aca24005-ea49-4389-bc26-f292fd0a75a2", - "metadata": {}, - "outputs": [], - "source": [ - "m.input.node = AseMinimizeNode()\n", - "m.input.node.input.calculator = MorsePotential()\n", - "m.input.node.input.max_steps = 100\n", - "m.input.node.input.output_steps = 10\n", - "m.input.node.input.ionic_force_tolerance = 1e-6\n", - "m.input.node.input.lbfgs()\n", - "\n", - "m.input.structure = bulk(\"Fe\", a=1.2)" - ] - }, - { - "cell_type": "code", - "execution_count": 85, - "id": "4ae990bd-af18-4dae-8500-779c9509f3f6", - "metadata": {}, - "outputs": [], - "source": [ - "m.input.set_strain_range(.5, 500)" - ] - }, - { - "cell_type": "code", - "execution_count": 86, - "id": "fa62529f-45e6-4e2d-822d-b1cc433a7223", - "metadata": {}, - "outputs": [], - "source": [ - "m.input.child_executor = ProcessExecutor" - ] - }, - { - "cell_type": "code", - "execution_count": 87, - "id": "0925864e-4dd1-4f4e-ace4-aac09c55e787", - "metadata": { - "scrolled": true, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:00 4.789242 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:00 4.517693 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:00 4.251945 0.0000\n", - "LBFGS: 0 14:53:00 3.991875 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:01 3.737364 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:01 3.488292 0.0000\n", - "LBFGS: 0 14:53:01 3.006013 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:01 3.244546 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:01 2.320604 0.0000\n", - "LBFGS: 0 14:53:01 2.544148 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:01 2.772582 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:01 2.101849 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:01 1.678307 0.0000\n", - "LBFGS: 0 14:53:01 1.887783 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:01 1.473327 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:01 1.272749 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:01 1.076481 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:01 0.884435 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:01 0.696523 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 0.156747 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 0.332761 0.0000\n", - "LBFGS: 0 14:53:02 0.512659 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 -0.015462 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 -0.183946 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 -0.510037 0.0000\n", - "LBFGS: 0 14:53:02 -0.348779 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 -0.667792 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 -0.973078 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 -1.265189 0.0000\n", - "LBFGS: 0 14:53:02 -1.120747 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 -0.822116 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 -1.406469 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 -1.544652 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 -1.679799 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 -1.941232 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 -1.811973 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 -2.067636 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:02 -2.191241 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:03 -2.312104 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:03 -2.430279 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:03 -2.545820 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:03 -2.658780 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:03 -2.877160 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:03 -2.769210 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:03 -3.085817 0.0000\n", - "LBFGS: 0 14:53:03 -2.982680 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:03 -3.285134 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:03 -3.186620 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:03 -3.475475 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:03 -3.381404 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:03 -3.567390 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:03 -3.657192 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:03 -3.830620 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:03 -3.744922 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -3.914328 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -4.075925 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -3.996084 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -4.153891 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -4.304338 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -4.376892 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -4.230016 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -4.447711 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -4.516830 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -4.584282 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -4.650099 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -4.714313 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -4.776956 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -4.838057 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -4.897647 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -4.955755 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -5.012409 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:04 -5.067638 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.121469 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.173930 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.225046 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.274844 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.323350 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.370587 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.461356 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.416581 0.0000\n", - "LBFGS: 0 14:53:05 -5.504934 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.547340 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.667742 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.628722 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.588595 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.705677 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.742548 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.813177 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.778375 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:05 -5.846976 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -5.879789 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -5.911636 0.0000\n", - "LBFGS: 0 14:53:06 -5.942536 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -5.972506 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.057017 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.083445 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.001565 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.109029 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.029730 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.133786 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.157731 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.180881 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.224853 0.0000\n", - "LBFGS: 0 14:53:06 -6.203250 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.245705 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.265820 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.321888 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.285213 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.303898 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.355836 0.0000\n", - "LBFGS: 0 14:53:06 -6.339196 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.371820 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.387162 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.401872 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.415965 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.429451 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.454650 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.466386 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.477561 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:06 -6.442342 0.0000\n", - "LBFGS: 0 14:53:06 -6.488186 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.507828 0.0000\n", - "LBFGS: 0 14:53:07 -6.498271 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.525395 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.533425 0.0000\n", - "LBFGS: 0 14:53:07 -6.516865 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.540966 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.548028 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.554620 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.566429 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.560750 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.576465 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.571664 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.580840 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.584797 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.588345 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.591492 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.594245 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.596612 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.598601 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.600220 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.601475 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.602374 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.603132 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.602924 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.603005 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.602549 0.0000\n", - "LBFGS: 0 14:53:07 -6.601771 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.600678 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.599275 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.595568 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:07 -6.597570 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.593275 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.590697 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.587840 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.584710 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.581312 0.0000\n", - "LBFGS: 0 14:53:08 -6.577651 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.573734 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.560493 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.569565 0.0000\n", - "LBFGS: 0 14:53:08 -6.565149 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.555599 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.533761 0.0000\n", - "LBFGS: 0 14:53:08 -6.539551 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.550475 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.545124 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.521548 0.0000\n", - "LBFGS: 0 14:53:08 -6.527759 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.515134 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.508521 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.494713 0.0000\n", - "LBFGS: 0 14:53:08 -6.501712 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.487527 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.480158 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.456996 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.464889 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.472611 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.448936 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.415096 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.440712 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.423789 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:08 -6.432329 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.406254 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.378866 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.388136 0.0000\n", - "LBFGS: 0 14:53:09 -6.397266 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.369460 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.359921 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.350251 0.0000\n", - "LBFGS: 0 14:53:09 -6.340455 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.330535 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.320493 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.310333 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.289669 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.300058 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.279171 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.268565 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.247042 0.0000\n", - "LBFGS: 0 14:53:09 -6.257855 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.236130 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.225120 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.180158 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.191532 0.0000\n", - "LBFGS: 0 14:53:09 -6.214016 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.202819 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:09 -6.157154 0.0000\n", - "LBFGS: 0 14:53:09 -6.168698 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -6.145530 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -6.133827 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -6.122047 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -6.110193 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -6.098266 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -6.086268 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -6.062068 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -6.074202 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -6.049871 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -6.025288 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -6.037610 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -6.012907 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -6.000468 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -5.987974 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -5.975426 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -5.950174 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -5.937473 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -5.924725 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -5.962825 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -5.911931 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -5.899093 0.0000\n", - "LBFGS: 0 14:53:10 -5.886212 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -5.873290 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -5.860327 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -5.821215 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -5.847326 0.0000\n", - "LBFGS: 0 14:53:10 -5.834289 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:10 -5.808107 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.794966 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.768591 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.781794 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.755359 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.728813 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.715501 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.702165 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.742099 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.688805 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.675424 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.662022 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.635159 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.648600 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.621701 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.608226 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.594735 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.581230 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.554180 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.567712 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.540637 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.527083 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.513519 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.499947 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.486366 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.472778 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.459183 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.418369 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.431978 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.404757 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:11 -5.445583 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.391143 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.350293 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.377527 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.363910 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.336676 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.323061 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.295837 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.309448 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.268625 0.0000\n", - "LBFGS: 0 14:53:12 -5.255026 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.282229 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.214262 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.241432 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.227844 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.200687 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.160009 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.146468 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.187119 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.173560 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.132936 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.119414 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.105903 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.092403 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.078915 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:12 -5.051977 0.0000\n", - "LBFGS: 0 14:53:12 -5.065440 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -5.038527 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -5.025090 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -5.011668 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.998260 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.984867 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.971490 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.944782 0.0000\n", - "LBFGS: 0 14:53:13 -4.958128 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.931453 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.918140 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.891567 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.904845 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.878308 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.865067 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.838641 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.825456 0.0000\n", - "LBFGS: 0 14:53:13 -4.851844 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.812292 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.799147 0.0000\n", - "LBFGS: 0 14:53:13 -4.772919 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.786023 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.759837 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.733735 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.720716 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.746775 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.681794 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.694745 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.668865 0.0000\n", - "LBFGS: 0 14:53:13 -4.707720 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:13 -4.655959 0.0000\n", - "LBFGS: 0 14:53:13 -4.643076 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.630216 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.617381 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.591781 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.579018 0.0000\n", - "LBFGS: 0 14:53:14 -4.604569 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.566279 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.553565 0.0000\n", - "LBFGS: 0 14:53:14 -4.528212 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.540876 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.490373 0.0000\n", - "LBFGS: 0 14:53:14 -4.515574 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.477811 0.0000\n", - "LBFGS: 0 14:53:14 -4.502960 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.465275 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.452766 0.0000\n", - "LBFGS: 0 14:53:14 -4.427826 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.440283 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.415396 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.402992 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.390616 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.378267 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.353649 0.0000\n", - "LBFGS: 0 14:53:14 -4.341382 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.365944 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.329142 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.316929 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.280459 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:14 -4.304745 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.256286 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.268359 0.0000\n", - "LBFGS: 0 14:53:15 -4.292588 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.244242 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.220239 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.208280 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.232226 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.172575 0.0000\n", - "LBFGS: 0 14:53:15 -4.184448 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.196350 0.0000\n", - "LBFGS: 0 14:53:15 -4.160732 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.148917 0.0000\n", - "LBFGS: 0 14:53:15 -4.137131 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.113646 0.0000\n", - "LBFGS: 0 14:53:15 -4.125374 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.101948 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.078638 0.0000\n", - "LBFGS: 0 14:53:15 -4.090278 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.055446 0.0000\n", - "LBFGS: 0 14:53:15 -4.067028 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.043894 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.009416 0.0000\n", - "LBFGS: 0 14:53:15 -4.020879 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -4.032372 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -3.997982 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -3.975204 0.0000\n", - "LBFGS: 0 14:53:15 -3.963859 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -3.952544 0.0000\n", - "LBFGS: 0 14:53:15 -3.986578 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -3.930004 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:15 -3.941259 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.918778 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.907582 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.896416 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.885280 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.874174 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.852051 0.0000\n", - "LBFGS: 0 14:53:16 -3.863097 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.841034 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.830047 0.0000\n", - "LBFGS: 0 14:53:16 -3.819091 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.808164 0.0000\n", - "LBFGS: 0 14:53:16 -3.797267 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.775562 0.0000\n", - "LBFGS: 0 14:53:16 -3.786400 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.753978 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.743230 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.764755 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.732513 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.721825 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.711167 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.700539 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.668834 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.679373 0.0000\n", - "LBFGS: 0 14:53:16 -3.689941 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.637397 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.658325 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:16 -3.626978 0.0000\n", - "LBFGS: 0 14:53:16 -3.647846 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.616588 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.606228 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.585597 0.0000\n", - "LBFGS: 0 14:53:17 -3.595897 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.575325 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.565084 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.554872 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.544689 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.524412 0.0000\n", - "LBFGS: 0 14:53:17 -3.534536 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.514318 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.504253 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.494218 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.484212 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.474235 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.464287 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.454369 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.434619 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.424788 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.444480 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.414986 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.405213 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.395469 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.385754 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.366410 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.376068 0.0000\n", - "LBFGS: 0 14:53:17 -3.356781 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.347181 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.328067 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.337610 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.309067 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:17 -3.318553 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.299610 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.280781 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.290181 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.252749 0.0000\n", - "LBFGS: 0 14:53:18 -3.271409 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.262065 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.243462 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.215768 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.224971 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.234202 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.206592 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.197445 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.188325 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.170169 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.179233 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.161132 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.152123 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.143141 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.134187 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.125260 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.116361 0.0000\n", - "LBFGS: 0 14:53:18 -3.107488 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.098643 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:18 -3.089825 0.0000\n", - "LBFGS: 0 14:53:18 -3.081034 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -3.063534 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -3.072271 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -3.046140 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -3.054823 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -3.020250 0.0000\n", - "LBFGS: 0 14:53:19 -3.028853 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -3.037483 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -3.011673 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.986101 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.994599 0.0000\n", - "LBFGS: 0 14:53:19 -3.003123 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.977630 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.969185 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.960766 0.0000\n", - "LBFGS: 0 14:53:19 -2.952373 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.944006 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.919060 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.927350 0.0000\n", - "LBFGS: 0 14:53:19 -2.935665 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.910796 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.902558 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.894346 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.877998 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.886159 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.869862 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.853666 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.845605 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.837570 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:19 -2.861751 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:20 -2.829560 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:20 -2.821575 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:20 -2.805679 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 14:53:20 -2.813615 0.0000\n" - ] - } - ], + "outputs": [], + "source": [ + "m.input.node = AseMinimizeNode()\n", + "m.input.node.input.calculator = MorsePotential()\n", + "m.input.node.input.max_steps = 100\n", + "m.input.node.input.output_steps = 10\n", + "m.input.node.input.ionic_force_tolerance = 1e-6\n", + "m.input.node.input.lbfgs()\n", + "\n", + "m.input.structure = bulk(\"Fe\", a=1.2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ae990bd-af18-4dae-8500-779c9509f3f6", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.set_strain_range(.5, 500)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa62529f-45e6-4e2d-822d-b1cc433a7223", + "metadata": {}, + "outputs": [], + "source": [ + "m.input.child_executor = ProcessExecutor" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0925864e-4dd1-4f4e-ace4-aac09c55e787", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], "source": [ "exe = m.run()" ] }, { "cell_type": "code", - "execution_count": 88, + "execution_count": null, "id": "a3069fe3-93bf-4c83-93e7-6d0ac56d8248", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 88, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "exe._run_machine.state" ] }, { "cell_type": "code", - "execution_count": 89, + "execution_count": null, "id": "71bbb913-7d7a-4bb6-b775-3fbc8e7e1f35", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(ReturnStatus(Code.DONE, None),)" - ] - }, - "execution_count": 89, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "exe.status" ] }, { "cell_type": "code", - "execution_count": 90, + "execution_count": null, "id": "4bf2df15-31dc-474c-b3df-f7c32b0fdaf2", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([4.78924238, 4.51769267, 4.25194477, 3.99187529, 3.7373637 ,\n", - " 3.48829227, 3.244546 , 3.00601254, 2.77258214, 2.54414756])" - ] - }, - "execution_count": 90, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "exe.output[0].energies[:10]" ] }, { "cell_type": "code", - "execution_count": 91, + "execution_count": null, "id": "eb0a2daf-9dab-4174-bfee-0cd1ef8c474e", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "exe.output[0].plot()" ] @@ -3165,186 +1464,179 @@ "widgets": { "application/vnd.jupyter.widget-state+json": { "state": { - "012a52ac25554558bbcbb8b106b79bef": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "084667f4bad34f3d897a790ea169d3d1": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ButtonStyleModel", - "state": {} - }, - "0ae500050c904bdcad53b5d936c381f0": { + "07a1f180db7d4aa8affe9f48c66281a6": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "SliderStyleModel", - "state": { - "description_width": "" - } - }, - "0cc6cbf6ee384fd88783d0d2b3c7a740": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "189a33f4d4984b8094066039bebff284": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", + "model_name": "ButtonModel", "state": { - "width": "34px" + "icon": "compress", + "layout": "IPY_MODEL_e13a786ff807427fae43a5cfed219975", + "style": "IPY_MODEL_be2fba7fd3ff4b849be3f65d03ba13dc" } }, - "1b10815147524b69949cca8b3947a15f": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "2261898b13bf45168106ac3c693491f4": { + "0bf198140cba45c0a145a68988e41219": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "LinkModel", "state": { "source": [ - "IPY_MODEL_f819f493d6424b3f988fffcd4a06461e", + "IPY_MODEL_36cfccf761b44baca72c5d53cf6cfc0a", "max" ], "target": [ - "IPY_MODEL_9acd0738c709420293ca03c9d7cb65a8", + "IPY_MODEL_ae4da31a319e4baca99703ce308889d4", "max_frame" ] } }, - "23bbcda59ee344d59c43eb48b5fab38a": { + "102b613a9c2a4eab96aeaf1a469ae6bb": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "176bb87615954634a7e91fc73bf93128": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "ButtonModel", + "model_name": "SliderStyleModel", "state": { - "icon": "compress", - "layout": "IPY_MODEL_189a33f4d4984b8094066039bebff284", - "style": "IPY_MODEL_9a484ae0199f40df9ea9dcb504a7519b" + "description_width": "" } }, - "28e019aae94f4db4beeeba0004eb69c6": { + "17eef0435ab640e2aef097257a8b9899": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "IntSliderModel", + "model_name": "ImageModel", "state": { - "layout": "IPY_MODEL_1b10815147524b69949cca8b3947a15f", - "max": 4, - "style": "IPY_MODEL_0ae500050c904bdcad53b5d936c381f0" + "layout": "IPY_MODEL_cdfea586c0fb4e44b9313a9e8cfbf525", + "width": "900.0" } }, - "29a4b696a29b4a339c0207927b43c38d": { + "18d0f24252dc44278939495d450dfd86": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "LinkModel", + "model_name": "ImageModel", "state": { - "source": [ - "IPY_MODEL_f318329ff0ab4284832df6987aa08caa", - "value" - ], - "target": [ - "IPY_MODEL_3a33fd7cc94d455ab40b075b6011194b", - "value" - ] + "layout": "IPY_MODEL_24a18c81c2e04c21875e583889c838cc", + "width": "900.0" } }, - "2af614f0e8e6415a8f3d3387518d8d51": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", + "1afea03bd47043fa9669604183c600f3": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", "state": {} }, - "38bc5381b0a34a2bb5c26de4e9ced220": { + "1d8983f2ad514ab990ac8a9c8ec9d8ed": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "ImageModel", + "model_name": "DescriptionStyleModel", "state": { - "layout": "IPY_MODEL_eb67e23591be4890bc094d2dbfd48737", - "width": "900.0" + "description_width": "" } }, - "3a33fd7cc94d455ab40b075b6011194b": { + "2131495c39cd4e688fb5ab9367bcc3d2": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "IntSliderModel", "state": { - "layout": "IPY_MODEL_5baf9865602342759674d426310ebe57", - "max": 0, - "style": "IPY_MODEL_f6e844f90dc344f99829326587cce4a8" + "layout": "IPY_MODEL_bdb15f0c4925488785b36853f2a507c4", + "max": 21, + "style": "IPY_MODEL_48ea0d9bc5d24e15997d34ae1f94b612" } }, - "3f9330a5f2a140daa6098330fdfdfb44": { + "232edc1031014bb48162c281fb240759": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", - "state": { - "width": "34px" - } + "state": {} }, - "412c481251fe49ef97872f1a95c86765": { + "24a18c81c2e04c21875e583889c838cc": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", + "state": {} + }, + "2aeb102499514aeeaa3ac02b9e7ae04b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", "state": { - "width": "34px" + "icon": "compress", + "layout": "IPY_MODEL_5c4b36124793443cad1938479a0b26b4", + "style": "IPY_MODEL_1afea03bd47043fa9669604183c600f3" } }, - "4315b7650344477ba62e3de9d829e8d0": { + "31de7259a7fb47dda852aa5690daff26": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "PlayModel", + "model_name": "LinkModel", "state": { - "layout": "IPY_MODEL_0cc6cbf6ee384fd88783d0d2b3c7a740", - "max": 21, - "style": "IPY_MODEL_c486ea6139d24312a32942eeb4308084" + "source": [ + "IPY_MODEL_4d1e30940a9f4d39ad539132bb9e3a97", + "value" + ], + "target": [ + "IPY_MODEL_577bee1eabef4c21b01870d31b31b844", + "value" + ] } }, - "50ee641c092b4f62aae9eff65eef8291": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} + "36cfccf761b44baca72c5d53cf6cfc0a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "PlayModel", + "state": { + "layout": "IPY_MODEL_d2bc4378320e47da9447235f5ba8c172", + "max": 4, + "style": "IPY_MODEL_bb0156e2570c453fab53e820ff281a50" + } }, - "5359d4fda4d242998c39e2f4a8b65bca": { + "38329f9971164ed8acc6f05286ec0b87": { "model_module": "nglview-js-widgets", "model_module_version": "3.0.1", "model_name": "ColormakerRegistryModel", "state": { "_msg_ar": [], "_msg_q": [], - "_ready": true, - "layout": "IPY_MODEL_c08c0a5b38fb4c7eaa042e1cf2ae75a5" + "_ready": false, + "layout": "IPY_MODEL_99b7a7163688495ca026df2229a46c1d" + } + }, + "40fe843dee8545819f229f799259f716": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "PlayModel", + "state": { + "layout": "IPY_MODEL_7060f26e34204b15ac60179586b326c6", + "max": 21, + "style": "IPY_MODEL_1d8983f2ad514ab990ac8a9c8ec9d8ed" } }, - "55c919e973dd4285983c523c55229f5c": { + "44791309f79943f08eeb4bdd781d7c16": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "LinkModel", "state": { "source": [ - "IPY_MODEL_b579f6e205824ec698867f439d52b70c", + "IPY_MODEL_4d1e30940a9f4d39ad539132bb9e3a97", "value" ], "target": [ - "IPY_MODEL_28e019aae94f4db4beeeba0004eb69c6", - "value" + "IPY_MODEL_490fa227a15b47d9bfc0fa37ac79e59c", + "frame" ] } }, - "5baf9865602342759674d426310ebe57": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} + "48ea0d9bc5d24e15997d34ae1f94b612": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "SliderStyleModel", + "state": { + "description_width": "" + } }, - "5cff2d0d15044fca9105fa3541411380": { + "490fa227a15b47d9bfc0fa37ac79e59c": { "model_module": "nglview-js-widgets", "model_module_version": "3.0.1", "model_name": "NGLModel", @@ -3369,9 +1661,9 @@ ], "_camera_str": "orthographic", "_gui_theme": null, - "_ibtn_fullscreen": "IPY_MODEL_23bbcda59ee344d59c43eb48b5fab38a", + "_ibtn_fullscreen": "IPY_MODEL_07a1f180db7d4aa8affe9f48c66281a6", "_igui": null, - "_iplayer": "IPY_MODEL_a0a84a448aa749a2bc0c8bca3962bdcb", + "_iplayer": "IPY_MODEL_b7fbffee80154c97bedfd8966a43bfc0", "_ngl_color_dict": {}, "_ngl_coordinate_resource": {}, "_ngl_full_stage_parameters": { @@ -4137,7 +2429,7 @@ "_ngl_serialize": false, "_ngl_version": "2.0.0-dev.36", "_ngl_view_id": [ - "4452CC60-66A4-4075-8A04-F4EA21BE6B54" + "667690D3-6FEE-45C1-8831-CE7922A214C1" ], "_player_dict": {}, "_scene_position": {}, @@ -4149,115 +2441,176 @@ "background": "white", "frame": 0, "gui_style": null, - "layout": "IPY_MODEL_dfd94ac53b394ccfa01a42103029dbc1", + "layout": "IPY_MODEL_abed0a7e394b4d96a23e5e49c6550396", "max_frame": 0, "n_components": 7, "picked": {} } }, - "616b7105d7a14e799fd71f9c7e2ddbc1": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} + "4a437568edb6486bb6f42d78cb503321": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_40fe843dee8545819f229f799259f716", + "value" + ], + "target": [ + "IPY_MODEL_2131495c39cd4e688fb5ab9367bcc3d2", + "value" + ] + } }, - "68113cbdde8a441ab41682def194c341": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} + "4d1e30940a9f4d39ad539132bb9e3a97": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "PlayModel", + "state": { + "layout": "IPY_MODEL_90a56f475dff4490bed56c96a0cb2d24", + "max": 0, + "style": "IPY_MODEL_fde0b8d494b14f87b50672b32c077416" + } }, - "691a27593c0c43d1bad168acc42a0d00": { + "535dd339949e422cbcc7361d221197a1": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "LinkModel", "state": { "source": [ - "IPY_MODEL_4315b7650344477ba62e3de9d829e8d0", + "IPY_MODEL_36cfccf761b44baca72c5d53cf6cfc0a", "value" ], "target": [ - "IPY_MODEL_9acd0738c709420293ca03c9d7cb65a8", + "IPY_MODEL_ae4da31a319e4baca99703ce308889d4", "frame" ] } }, - "6b34af6fbd8d42879280927d8a9f0815": { + "554ad1cbd4cb49178f7fb82699279805": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "IntSliderModel", + "state": { + "layout": "IPY_MODEL_5de42c821a114c07beeee90ceb543a62", + "max": 4, + "style": "IPY_MODEL_72de8832cfa341e7be1c3e8af9ef5836" + } + }, + "56b6a1023dd748c5b2ad01fb5b538763": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_4d1e30940a9f4d39ad539132bb9e3a97", + "max" + ], + "target": [ + "IPY_MODEL_490fa227a15b47d9bfc0fa37ac79e59c", + "max_frame" + ] + } + }, + "577bee1eabef4c21b01870d31b31b844": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "ImageModel", + "model_name": "IntSliderModel", "state": { - "layout": "IPY_MODEL_c5afa7b7bf62440caa2e336d30e3f87a", - "width": "900.0" + "layout": "IPY_MODEL_92762f6dd2a34d95a4ffdba0937f6e07", + "max": 0, + "style": "IPY_MODEL_176bb87615954634a7e91fc73bf93128" } }, - "6c738e0cd99447eea546fb257581d104": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ButtonStyleModel", + "5c4b36124793443cad1938479a0b26b4": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "width": "34px" + } + }, + "5de42c821a114c07beeee90ceb543a62": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", "state": {} }, - "6e92491cab9d4064961c20be5d0352dc": { + "650ffcac60dd466d99a3636f9aceb344": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "LinkModel", + "model_name": "ImageModel", "state": { - "source": [ - "IPY_MODEL_f318329ff0ab4284832df6987aa08caa", - "value" - ], - "target": [ - "IPY_MODEL_5cff2d0d15044fca9105fa3541411380", - "frame" - ] + "layout": "IPY_MODEL_e7187596a3b845d2b7f369c7fa1201c7", + "width": "900.0" } }, - "6fe0ae77b7c843519d9a5307d2b83ec5": { + "6a9347cdf7904fc2a5875e6a6dcbae6e": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HBoxModel", "state": { "children": [ - "IPY_MODEL_4315b7650344477ba62e3de9d829e8d0", - "IPY_MODEL_f819f493d6424b3f988fffcd4a06461e" + "IPY_MODEL_36cfccf761b44baca72c5d53cf6cfc0a", + "IPY_MODEL_554ad1cbd4cb49178f7fb82699279805" ], - "layout": "IPY_MODEL_7052c34813d545eb84c5c674eb82cd91" + "layout": "IPY_MODEL_bdb611883eb04a1eb9a640d4fcbf633c" } }, - "7052c34813d545eb84c5c674eb82cd91": { + "7060f26e34204b15ac60179586b326c6": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, - "77ca3b8d35044a5982b88d68e38e3289": { + "72de8832cfa341e7be1c3e8af9ef5836": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "ButtonModel", + "model_name": "SliderStyleModel", "state": { - "icon": "compress", - "layout": "IPY_MODEL_3f9330a5f2a140daa6098330fdfdfb44", - "style": "IPY_MODEL_084667f4bad34f3d897a790ea169d3d1" + "description_width": "" } }, - "7c40f68c2f2b43aea1a8e6ae3e344c20": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ImageModel", - "state": { - "layout": "IPY_MODEL_bd58d6b40ef94f9abc1d621b42bd030f", - "width": "900.0" - } + "90a56f475dff4490bed56c96a0cb2d24": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "92762f6dd2a34d95a4ffdba0937f6e07": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "99b7a7163688495ca026df2229a46c1d": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} }, - "863d1ff1c0f94c7cbfb9dc175d1078a0": { + "9f5126572d5042239621624f1c52e83b": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", + "model_name": "LinkModel", "state": { - "description_width": "" + "source": [ + "IPY_MODEL_40fe843dee8545819f229f799259f716", + "value" + ], + "target": [ + "IPY_MODEL_d702f3bf2990434a85d0792cd2611c5f", + "frame" + ] } }, - "903d8dc3dee8402b9a481fa1516cc478": { + "abed0a7e394b4d96a23e5e49c6550396": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "ae4da31a319e4baca99703ce308889d4": { "model_module": "nglview-js-widgets", "model_module_version": "3.0.1", "model_name": "NGLModel", @@ -4282,9 +2635,9 @@ ], "_camera_str": "orthographic", "_gui_theme": null, - "_ibtn_fullscreen": "IPY_MODEL_77ca3b8d35044a5982b88d68e38e3289", + "_ibtn_fullscreen": "IPY_MODEL_f34e98e4c72945838d54a52a5e081315", "_igui": null, - "_iplayer": "IPY_MODEL_abb060530b644eb7ac83734007e72760", + "_iplayer": "IPY_MODEL_6a9347cdf7904fc2a5875e6a6dcbae6e", "_ngl_color_dict": {}, "_ngl_coordinate_resource": {}, "_ngl_full_stage_parameters": { @@ -4538,7 +2891,7 @@ "_ngl_serialize": false, "_ngl_version": "2.0.0-dev.36", "_ngl_view_id": [ - "242406BE-824C-47E3-BAAF-8680FC355719" + "94D4F05B-4B91-4EBB-AD85-EC9DEB97D514" ], "_player_dict": {}, "_scene_position": {}, @@ -4550,48 +2903,137 @@ "background": "white", "frame": 0, "gui_style": null, - "layout": "IPY_MODEL_af292d89fcf44b87900031c9b27c2533", + "layout": "IPY_MODEL_232edc1031014bb48162c281fb240759", "max_frame": 4, "n_components": 1, "picked": {} } }, - "941d6b9e9e004db982b9b599e4735ef8": { + "af4e2d19c99f4c449bb13afe7baa471d": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "LinkModel", "state": { "source": [ - "IPY_MODEL_3a33fd7cc94d455ab40b075b6011194b", + "IPY_MODEL_2131495c39cd4e688fb5ab9367bcc3d2", "max" ], "target": [ - "IPY_MODEL_5cff2d0d15044fca9105fa3541411380", + "IPY_MODEL_d702f3bf2990434a85d0792cd2611c5f", "max_frame" ] } }, - "97714c28adb94f97ac44e3819311d316": { + "b174844c258f40a881d42ddd952c95fc": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "SliderStyleModel", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_577bee1eabef4c21b01870d31b31b844", + "max" + ], + "target": [ + "IPY_MODEL_490fa227a15b47d9bfc0fa37ac79e59c", + "max_frame" + ] + } + }, + "b4038c5bf965445c93b0d0e697c2830b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_554ad1cbd4cb49178f7fb82699279805", + "max" + ], + "target": [ + "IPY_MODEL_ae4da31a319e4baca99703ce308889d4", + "max_frame" + ] + } + }, + "b7fbffee80154c97bedfd8966a43bfc0": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "children": [ + "IPY_MODEL_4d1e30940a9f4d39ad539132bb9e3a97", + "IPY_MODEL_577bee1eabef4c21b01870d31b31b844" + ], + "layout": "IPY_MODEL_102b613a9c2a4eab96aeaf1a469ae6bb" + } + }, + "bb0156e2570c453fab53e820ff281a50": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, - "982d09abf73240c898d5c545d08ed9b6": { + "bdb15f0c4925488785b36853f2a507c4": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "bdb611883eb04a1eb9a640d4fcbf633c": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, - "9a484ae0199f40df9ea9dcb504a7519b": { + "be2fba7fd3ff4b849be3f65d03ba13dc": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "ButtonStyleModel", "state": {} }, - "9acd0738c709420293ca03c9d7cb65a8": { + "c6f4e62a00074fd9b9a26d86059fdfdf": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_36cfccf761b44baca72c5d53cf6cfc0a", + "value" + ], + "target": [ + "IPY_MODEL_554ad1cbd4cb49178f7fb82699279805", + "value" + ] + } + }, + "c9bb8f068708498a8963f68d1f5e21c9": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "cdfea586c0fb4e44b9313a9e8cfbf525": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "d2bc4378320e47da9447235f5ba8c172": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "d5347f1a62b84865a7375f54f807ca33": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "width": "34px" + } + }, + "d702f3bf2990434a85d0792cd2611c5f": { "model_module": "nglview-js-widgets", "model_module_version": "3.0.1", "model_name": "NGLModel", @@ -4616,9 +3058,9 @@ ], "_camera_str": "orthographic", "_gui_theme": null, - "_ibtn_fullscreen": "IPY_MODEL_db8318e27dd048d3b57c56c61fe0125c", + "_ibtn_fullscreen": "IPY_MODEL_2aeb102499514aeeaa3ac02b9e7ae04b", "_igui": null, - "_iplayer": "IPY_MODEL_6fe0ae77b7c843519d9a5307d2b83ec5", + "_iplayer": "IPY_MODEL_d840e72db72e40b1926c1d6a46a0ebcf", "_ngl_color_dict": {}, "_ngl_coordinate_resource": {}, "_ngl_full_stage_parameters": { @@ -4872,7 +3314,7 @@ "_ngl_serialize": false, "_ngl_version": "2.0.0-dev.36", "_ngl_view_id": [ - "8ED2CDFF-EA6D-4A63-BAAE-A8BF61BE463D" + "F3DFC146-3E56-4815-8862-4714ED3F6F6F" ], "_player_dict": {}, "_scene_position": {}, @@ -4884,225 +3326,82 @@ "background": "white", "frame": 0, "gui_style": null, - "layout": "IPY_MODEL_2af614f0e8e6415a8f3d3387518d8d51", + "layout": "IPY_MODEL_c9bb8f068708498a8963f68d1f5e21c9", "max_frame": 21, "n_components": 1, "picked": {} } }, - "9b9319a09d05487ca4f28005cf495d44": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "LinkModel", - "state": { - "source": [ - "IPY_MODEL_4315b7650344477ba62e3de9d829e8d0", - "value" - ], - "target": [ - "IPY_MODEL_f819f493d6424b3f988fffcd4a06461e", - "value" - ] - } - }, - "9c91b7a14718493eb53e9a47ef565c04": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "LinkModel", - "state": { - "source": [ - "IPY_MODEL_28e019aae94f4db4beeeba0004eb69c6", - "max" - ], - "target": [ - "IPY_MODEL_903d8dc3dee8402b9a481fa1516cc478", - "max_frame" - ] - } - }, - "a0a84a448aa749a2bc0c8bca3962bdcb": { + "d7837d55497b4e9b894e8a310288b9b1": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "children": [ - "IPY_MODEL_f318329ff0ab4284832df6987aa08caa", - "IPY_MODEL_3a33fd7cc94d455ab40b075b6011194b" - ], - "layout": "IPY_MODEL_68113cbdde8a441ab41682def194c341" - } + "model_name": "ButtonStyleModel", + "state": {} }, - "abb060530b644eb7ac83734007e72760": { + "d840e72db72e40b1926c1d6a46a0ebcf": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HBoxModel", "state": { "children": [ - "IPY_MODEL_b579f6e205824ec698867f439d52b70c", - "IPY_MODEL_28e019aae94f4db4beeeba0004eb69c6" + "IPY_MODEL_40fe843dee8545819f229f799259f716", + "IPY_MODEL_2131495c39cd4e688fb5ab9367bcc3d2" ], - "layout": "IPY_MODEL_616b7105d7a14e799fd71f9c7e2ddbc1" + "layout": "IPY_MODEL_e80a07b509d24321a5f728427a7579ce" } }, - "af292d89fcf44b87900031c9b27c2533": { + "e13a786ff807427fae43a5cfed219975": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", - "state": {} - }, - "b579f6e205824ec698867f439d52b70c": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "PlayModel", "state": { - "layout": "IPY_MODEL_982d09abf73240c898d5c545d08ed9b6", - "max": 4, - "style": "IPY_MODEL_863d1ff1c0f94c7cbfb9dc175d1078a0" + "width": "34px" } }, - "bd58d6b40ef94f9abc1d621b42bd030f": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "c08c0a5b38fb4c7eaa042e1cf2ae75a5": { + "e7187596a3b845d2b7f369c7fa1201c7": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, - "c486ea6139d24312a32942eeb4308084": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "description_width": "" - } - }, - "c5afa7b7bf62440caa2e336d30e3f87a": { + "e80a07b509d24321a5f728427a7579ce": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, - "c8b5c52c81bc4c9d8bf0b20138613f1d": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "LinkModel", - "state": { - "source": [ - "IPY_MODEL_f318329ff0ab4284832df6987aa08caa", - "max" - ], - "target": [ - "IPY_MODEL_5cff2d0d15044fca9105fa3541411380", - "max_frame" - ] - } - }, - "cec9a5a47ffe425788ba310f0381594f": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "LinkModel", - "state": { - "source": [ - "IPY_MODEL_b579f6e205824ec698867f439d52b70c", - "value" - ], - "target": [ - "IPY_MODEL_903d8dc3dee8402b9a481fa1516cc478", - "frame" - ] - } - }, - "db8318e27dd048d3b57c56c61fe0125c": { + "f34e98e4c72945838d54a52a5e081315": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "ButtonModel", "state": { "icon": "compress", - "layout": "IPY_MODEL_412c481251fe49ef97872f1a95c86765", - "style": "IPY_MODEL_6c738e0cd99447eea546fb257581d104" - } - }, - "dfd94ac53b394ccfa01a42103029dbc1": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "eb67e23591be4890bc094d2dbfd48737": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "f1f947b6e1fe4a60b61e9cca50328d78": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "description_width": "" - } - }, - "f21fb1ae2a6c4a91ac80d1a58d50587b": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "LinkModel", - "state": { - "source": [ - "IPY_MODEL_b579f6e205824ec698867f439d52b70c", - "max" - ], - "target": [ - "IPY_MODEL_903d8dc3dee8402b9a481fa1516cc478", - "max_frame" - ] + "layout": "IPY_MODEL_d5347f1a62b84865a7375f54f807ca33", + "style": "IPY_MODEL_d7837d55497b4e9b894e8a310288b9b1" } }, - "f318329ff0ab4284832df6987aa08caa": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "PlayModel", - "state": { - "layout": "IPY_MODEL_50ee641c092b4f62aae9eff65eef8291", - "max": 0, - "style": "IPY_MODEL_f1f947b6e1fe4a60b61e9cca50328d78" - } - }, - "f6ae52a0c4a5408ab81b41ae139be0c3": { + "f70fe483cde24341b4074f933a0d5574": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "LinkModel", "state": { "source": [ - "IPY_MODEL_4315b7650344477ba62e3de9d829e8d0", + "IPY_MODEL_40fe843dee8545819f229f799259f716", "max" ], "target": [ - "IPY_MODEL_9acd0738c709420293ca03c9d7cb65a8", + "IPY_MODEL_d702f3bf2990434a85d0792cd2611c5f", "max_frame" ] } }, - "f6e844f90dc344f99829326587cce4a8": { + "fde0b8d494b14f87b50672b32c077416": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "SliderStyleModel", + "model_name": "DescriptionStyleModel", "state": { "description_width": "" } - }, - "f819f493d6424b3f988fffcd4a06461e": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "IntSliderModel", - "state": { - "layout": "IPY_MODEL_012a52ac25554558bbcbb8b106b79bef", - "max": 21, - "style": "IPY_MODEL_97714c28adb94f97ac44e3819311d316" - } } }, "version_major": 2, diff --git a/notebooks/tinybase/Basic.ipynb b/notebooks/tinybase/Basic.ipynb index 1f54259e4..872b8a98e 100644 --- a/notebooks/tinybase/Basic.ipynb +++ b/notebooks/tinybase/Basic.ipynb @@ -25,7 +25,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "d1c3ced555be42948758b8ee70dfe5a8", + "model_id": "cf2ad1379a3b4306981fef55c99a0e26", "version_major": 2, "version_minor": 0 }, @@ -232,7 +232,7 @@ "data": { "text/plain": [ "{'status': (ReturnStatus(Code.DONE, None),),\n", - " 'output': (,)}" + " 'output': (,)}" ] }, "execution_count": 13, @@ -274,7 +274,7 @@ { "data": { "text/plain": [ - "(1.0013468409888446, 2.5904009817168117e-05)" + "(1.0009997060114983, 9.59599856287241e-06)" ] }, "execution_count": 15, @@ -286,6 +286,56 @@ "exe._run_time, exe._collect_time" ] }, + { + "cell_type": "markdown", + "id": "a6cbcd0e-2a04-4e93-add5-0f423fc1fa69", + "metadata": {}, + "source": [ + "## We don't actually have to use an executor if we just want a result" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "a622ef14-4543-4cc1-bdfe-3625052f9b04", + "metadata": {}, + "outputs": [], + "source": [ + "f = FunctionNode(calc_fib)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "b0a0ff5d-6c5f-4c66-bc71-a9de5bf58220", + "metadata": {}, + "outputs": [], + "source": [ + "f.input.kwargs['n'] = 10" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "b196759b-8cd6-45c7-8d3f-aad861b02edc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None),\n", + " )" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f.execute()" + ] + }, { "cell_type": "markdown", "id": "8a3b6481-3605-44d3-8061-cb00c9fbcd34", @@ -296,7 +346,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 19, "id": "1e1b986e-9e00-41f2-86c2-945ff7818580", "metadata": {}, "outputs": [], @@ -306,7 +356,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 20, "id": "0b612150-f654-4995-8910-e46e766fdce2", "metadata": {}, "outputs": [], @@ -316,7 +366,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 21, "id": "bac98046-09c1-457c-881a-e31f03267788", "metadata": {}, "outputs": [], @@ -326,7 +376,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 22, "id": "ba09ae22-d2b4-41ba-8637-cb7f0fb3bfe9", "metadata": {}, "outputs": [ @@ -336,7 +386,7 @@ "{}" ] }, - "execution_count": 19, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -347,7 +397,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 23, "id": "0d2f427a-21e1-449e-a8cc-c2296bff6c10", "metadata": {}, "outputs": [ @@ -357,7 +407,7 @@ "" ] }, - "execution_count": 20, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -368,7 +418,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 24, "id": "a9631d5e-d46a-419c-a929-68ddd77487bb", "metadata": {}, "outputs": [], @@ -378,7 +428,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 25, "id": "408ffab0-70a1-4d08-9007-4d9f0513935d", "metadata": {}, "outputs": [ @@ -388,7 +438,7 @@ "927372692193078999176" ] }, - "execution_count": 22, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -407,7 +457,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 26, "id": "1e2178c0-ee70-4ec0-8b4b-2c7c55873b43", "metadata": {}, "outputs": [], @@ -417,7 +467,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 27, "id": "998c4724-c7ab-4fc0-8794-8b60da819090", "metadata": {}, "outputs": [], @@ -427,7 +477,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 28, "id": "7b66b215-33cd-425c-bb9b-62e3eaa0451e", "metadata": {}, "outputs": [], @@ -437,7 +487,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 29, "id": "fcdcbe0b-f44a-4c37-acd8-10eedf5b3aa2", "metadata": {}, "outputs": [ @@ -447,7 +497,7 @@ "{}" ] }, - "execution_count": 26, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -458,7 +508,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 30, "id": "5354db31-169d-4c7b-a8cc-6ddc3358b4c1", "metadata": {}, "outputs": [ @@ -468,7 +518,7 @@ "" ] }, - "execution_count": 27, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -479,7 +529,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 31, "id": "282a6ae8-e869-4ae2-bfea-8644ba693866", "metadata": {}, "outputs": [], @@ -489,7 +539,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 32, "id": "fd24c4c8-6b17-433f-ac28-a490911a3628", "metadata": {}, "outputs": [ @@ -499,7 +549,7 @@ "927372692193078999176" ] }, - "execution_count": 29, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -518,7 +568,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 33, "id": "e2fed9f1-590b-4ab5-9922-a126444e6169", "metadata": {}, "outputs": [], @@ -528,7 +578,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 34, "id": "fdfc8943-8c0b-4bc6-98f0-71a64b3fae27", "metadata": {}, "outputs": [], @@ -547,7 +597,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 35, "id": "dd709cfa-775f-41c1-a015-7e0647ec3d27", "metadata": { "scrolled": true, @@ -561,26 +611,26 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 36, "id": "0ac1b35a-b130-4330-bf20-a1222bdc6103", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "(,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " )" + "(,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " )" ] }, - "execution_count": 74, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -591,7 +641,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 37, "id": "1ef8d9d6-e5dc-4db1-9e20-7181321f07ce", "metadata": {}, "outputs": [ @@ -601,7 +651,7 @@ "55" ] }, - "execution_count": 75, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } @@ -620,7 +670,7 @@ }, { "cell_type": "code", - "execution_count": 88, + "execution_count": 38, "id": "25fe617c-ae8e-4b83-bf58-b790441a1126", "metadata": { "scrolled": true, @@ -634,7 +684,7 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": 39, "id": "19e5d3e8-6779-4c36-a636-2d8cd549e99c", "metadata": {}, "outputs": [], @@ -644,7 +694,7 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": 40, "id": "66feb98b-3f99-4bfb-9bb5-cccaf26d009b", "metadata": {}, "outputs": [ @@ -663,7 +713,7 @@ " ReturnStatus(Code.DONE, None)]" ] }, - "execution_count": 90, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } @@ -674,26 +724,26 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": 41, "id": "fbb40611-9f53-479e-854c-82c8c99a8070", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ]" + "[,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ]" ] }, - "execution_count": 91, + "execution_count": 41, "metadata": {}, "output_type": "execute_result" } @@ -704,7 +754,7 @@ }, { "cell_type": "code", - "execution_count": 92, + "execution_count": 42, "id": "250f9c2d-5c71-4ddb-a94e-fd42f42cbeff", "metadata": {}, "outputs": [ @@ -714,7 +764,7 @@ "55" ] }, - "execution_count": 92, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } @@ -733,7 +783,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 43, "id": "3dba0814-6a50-41f9-a78f-040014fdc140", "metadata": {}, "outputs": [], @@ -743,7 +793,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 44, "id": "52aae339-ebad-4621-b2e0-c55d4fea3d1b", "metadata": {}, "outputs": [], @@ -753,7 +803,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 45, "id": "e10f7ee9-98db-48c7-affd-465c2011f7b1", "metadata": {}, "outputs": [], @@ -763,7 +813,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 46, "id": "b7e58b55-b4f5-4e2a-aef5-f4e080e4d50c", "metadata": {}, "outputs": [], @@ -774,17 +824,17 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 47, "id": "b4b2212a-64df-4284-834d-8836c9a59b70", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 63, + "execution_count": 47, "metadata": {}, "output_type": "execute_result" } @@ -795,7 +845,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 48, "id": "af337125-c4fe-497d-9374-b2d9301abe08", "metadata": {}, "outputs": [], @@ -805,7 +855,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 49, "id": "810a17bb-9f5d-4c50-9665-fa2f93070d60", "metadata": {}, "outputs": [], @@ -815,7 +865,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 50, "id": "4af47287-ab42-4cb4-8e65-c6efb7982ab4", "metadata": {}, "outputs": [ @@ -825,7 +875,7 @@ "ReturnStatus(Code.DONE, None)" ] }, - "execution_count": 66, + "execution_count": 50, "metadata": {}, "output_type": "execute_result" } @@ -836,7 +886,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 51, "id": "705637d8-8da7-4429-ae6f-5401fc15cc9e", "metadata": {}, "outputs": [ @@ -846,7 +896,7 @@ "12.0" ] }, - "execution_count": 67, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -873,7 +923,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 52, "id": "b9807c98-6df8-450f-a8dd-1a53cb4ded35", "metadata": {}, "outputs": [], @@ -883,7 +933,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 53, "id": "ac2b9aa8-c118-4a1a-bf8b-96d6853b9be6", "metadata": {}, "outputs": [], @@ -893,7 +943,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 54, "id": "ef092015-5756-409a-bd1a-a31793c0b2b8", "metadata": {}, "outputs": [], @@ -903,7 +953,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 55, "id": "91a3d26f-d1fc-44a9-b06d-a9c452dfb3db", "metadata": {}, "outputs": [ @@ -911,15 +961,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.06831008568469543\n", - "0.4397644993497386\n", - "0.4710237017187203\n", - "0.6351871327200411\n", - "0.09403372149094191\n", - "0.45571674954951835\n", - "0.8693040125694965\n", - "0.03592129945541278\n", - "0.2842080026440601\n" + "0.6416842803528255\n", + "0.7583838349829116\n", + "0.25412630351718535\n", + "0.978146926974964\n", + "0.6346764217817196\n", + "0.8760354316006344\n", + "0.3598792613379673\n", + "0.7532969335152777\n", + "0.3682944984993325\n" ] } ], @@ -929,7 +979,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 56, "id": "dbc8730e-9ebc-403b-9987-0de04e1f77f3", "metadata": {}, "outputs": [ @@ -939,7 +989,7 @@ "(ReturnStatus(Code.DONE, None),)" ] }, - "execution_count": 42, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -958,7 +1008,7 @@ }, { "cell_type": "code", - "execution_count": 93, + "execution_count": 57, "id": "6c251bfa-e8cf-4e1a-990d-451ebb53f713", "metadata": {}, "outputs": [], @@ -968,7 +1018,7 @@ }, { "cell_type": "code", - "execution_count": 94, + "execution_count": 58, "id": "563c7fe1-b96f-463c-8903-50f054c831f6", "metadata": {}, "outputs": [], @@ -978,7 +1028,7 @@ }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 59, "id": "10130bfd-636f-4771-b30b-4648a8822f04", "metadata": {}, "outputs": [], @@ -991,7 +1041,7 @@ }, { "cell_type": "code", - "execution_count": 100, + "execution_count": 60, "id": "f875b6c9-8cd1-4e6b-9ec8-16b93b6e7f64", "metadata": { "scrolled": true, @@ -1002,74 +1052,56 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.6180549529309188\n", - "0.9433407283326789\n", - "0.6650129766719227\n", - "0.9246096624581522\n", - "0.702924921444492\n", - "0.4932765584360923\n", - "0.10209510690867707\n", - "0.19723819666451714\n", - "0.6319420933414326\n", - "0.9376161926340415\n", - "0.1911762555082791\n", - "0.7812203005244642\n", - "0.36658807729956766\n", - "0.997651587491596\n", - "0.17214861003243775\n", - "0.49700246072622345\n", - "0.8929166329882523\n", - "0.9069634041837235\n", - "0.928329630027329\n", - "0.14530372536131697\n", - "0.4551759858923593\n", - "0.8299354186855429\n", - "0.9971370925238271\n", - "0.3922295916439884\n", - "0.43629137886178726\n", - "0.13481396015844416\n", - "0.06396401175605293\n", - "0.0502648932556814\n", - "0.0919464823724655\n", - "0.2478196375875663\n", - "0.5547919839305524\n", - "0.9950273201349219\n", - "0.7490433592510488\n", - "0.5708404460188841\n", - "0.2800227217981094\n", - "0.452859610657651\n", - "0.5086825431878267\n", - "0.7486390124589416\n", - "0.34312007912192777\n", - "0.771168396478236\n", - "0.4539288607160801\n", - "0.7642828950901653\n", - "0.9944398067831015\n", - "0.8876987515750713\n", - "0.7498600155938839\n", - "0.8124747754930199\n", - "0.9020421405237081\n", - "0.40694715335295206\n", - "0.6880129743298647\n", - "0.8457057679143185\n", - "0.10612064010204925\n", - "0.4658543363818123\n", - "0.35949607240217285\n", - "0.9031175105972618\n", - "0.651652451274804\n", - "0.40381401433722386\n", - "0.6465594430809206\n", - "0.1129458759346127\n", - "0.07455862107161915\n", - "0.7246939877012769\n", - "0.6406247398029579\n", - "0.3875703065028444\n", - "0.6329595311691336\n", - "0.16772887766889388\n", - "0.4353447524968901\n", - "0.8271185273102784\n", - "0.5888175907051821\n", - "0.7213444488699345\n" + "0.9413063812991156\n", + "0.057607401981599304\n", + "0.5397672296450867\n", + "0.09250834651031281\n", + "0.07879485371081929\n", + "0.11682404864093698\n", + "0.40972529768552957\n", + "0.4949451538792201\n", + "0.4185353179411203\n", + "0.8901597039186674\n", + "0.7351951144010666\n", + "0.23869310822669132\n", + "0.31807750515467925\n", + "0.4612495341559427\n", + "0.7640290216688888\n", + "0.3869288752008373\n", + "0.23391774163562495\n", + "0.5139434388248725\n", + "0.6190472000582091\n", + "0.22725085086512875\n", + "0.8568498193403175\n", + "0.20466606514335917\n", + "0.6031013104131141\n", + "0.4322284299797473\n", + "0.6921598384356622\n", + "0.9143146262866392\n", + "0.7142585471813697\n", + "0.8067120253887571\n", + "0.2985613290985082\n", + "0.08197401100526269\n", + "0.8502914897450804\n", + "0.5441851005084206\n", + "0.4514940877891841\n", + "0.4895956404459735\n", + "0.23293653173840512\n", + "0.9152221474508541\n", + "0.35937794575487036\n", + "0.9560404151460253\n", + "0.1681320614012014\n", + "0.3887573415069143\n", + "0.936771887470757\n", + "0.8987739992014511\n", + "0.22822592639583683\n", + "0.5306744820161604\n", + "0.21711390374139883\n", + "0.7720820846158695\n", + "0.694832684749003\n", + "0.9880804928246634\n", + "0.8877319250523327\n", + "0.7389376534050393\n" ] } ], @@ -1079,7 +1111,7 @@ }, { "cell_type": "code", - "execution_count": 101, + "execution_count": 61, "id": "8df83822-0bbd-4157-8bb2-f6e93433eefc", "metadata": {}, "outputs": [ @@ -1089,7 +1121,7 @@ "ReturnStatus(Code.DONE, None)" ] }, - "execution_count": 101, + "execution_count": 61, "metadata": {}, "output_type": "execute_result" } @@ -1100,17 +1132,17 @@ }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 62, "id": "815ba264-9bdb-4758-ab20-92e3650bdbae", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0.022068342941342967" + "0.03279942765741395" ] }, - "execution_count": 102, + "execution_count": 62, "metadata": {}, "output_type": "execute_result" } @@ -1141,13 +1173,13 @@ "widgets": { "application/vnd.jupyter.widget-state+json": { "state": { - "a0f7f042a295465588d884090818b8a6": { + "3ad1e8b33bfd46ceab434ce32e3bd06a": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, - "d1c3ced555be42948758b8ee70dfe5a8": { + "cf2ad1379a3b4306981fef55c99a0e26": { "model_module": "nglview-js-widgets", "model_module_version": "3.0.1", "model_name": "ColormakerRegistryModel", @@ -1155,7 +1187,7 @@ "_msg_ar": [], "_msg_q": [], "_ready": true, - "layout": "IPY_MODEL_a0f7f042a295465588d884090818b8a6" + "layout": "IPY_MODEL_3ad1e8b33bfd46ceab434ce32e3bd06a" } } }, diff --git a/notebooks/tinybase/TinyJob.ipynb b/notebooks/tinybase/TinyJob.ipynb new file mode 100644 index 000000000..67609085a --- /dev/null +++ b/notebooks/tinybase/TinyJob.ipynb @@ -0,0 +1,1307 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f4408d8a-2da9-4cc2-834f-f36ca47b23dc", + "metadata": {}, + "source": [ + "# Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "95763abb-4480-4ced-bf73-db72dae9ffe0", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/ponder/science/phd/dev/pyiron_contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n", + " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "63cd145ecad142378ffe5f9bbe3c9a95", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from pyiron_contrib.tinybase.job import GenericTinyJob" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9730e2a5-9079-4863-905d-f21e47b65816", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.executor import ProcessExecutor" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "09003a07-be8d-4607-b533-54214ae64056", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.murn import MurnaghanNode" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "70c5d3c8-2d81-4539-9453-cd85010e7373", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.ase import AseMDNode, AseMinimizeNode, AseStaticNode" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "1e5208d9-d7b8-4ca3-92cf-5d32c30c20f9", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.job import ProjectAdapter" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5606f2b7-ba23-4b0d-b44e-b41cda6e5d4d", + "metadata": {}, + "outputs": [], + "source": [ + "from ase import Atoms" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "9a28ea42-360a-4e35-9fce-427b661a05c5", + "metadata": {}, + "outputs": [], + "source": [ + "from ase.build import bulk" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "059021a8-6e20-4265-94f4-8fb9bdaebde3", + "metadata": {}, + "outputs": [], + "source": [ + "from ase.calculators.morse import MorsePotential" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "f6fb49d0-dbfc-4b33-8b4d-999a2002831e", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_base import Project" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "f2e0123f-f0be-41ef-9733-af547b38d846", + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "logging.getLogger().setLevel(0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa9112b0-c679-49f3-a4c2-e506deb72f4b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bc092b74-64c6-49c9-9576-ef5d871fcd1e", + "metadata": {}, + "source": [ + "# Create Project and a new Job\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "0f30286b-4434-4569-8f03-fadab46ebe34", + "metadata": {}, + "outputs": [], + "source": [ + "pr = ProjectAdapter(Project('tinyjob'))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2ab27401-8db2-4130-b5ae-764eaa22c8f5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "450e6b64-9824-4b8a-8854-3999a93fc781", + "metadata": {}, + "source": [ + "## MD Job" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "ba190052-7bc1-4383-b3da-c7221c4e38d0", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "j = GenericTinyJob(pr, 'md')" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "1e21980e-14a6-4578-b4b9-8195a74a3593", + "metadata": {}, + "outputs": [], + "source": [ + "j.node_class = AseMDNode" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "18e6de26-308c-46ae-9672-b2db43447ea5", + "metadata": {}, + "outputs": [], + "source": [ + "j.input.structure = bulk('Fe', a=1.2, cubic=True).repeat(2)\n", + "j.input.calculator = MorsePotential()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "72848cd2-fd51-4ad8-b56e-ca686074bb26", + "metadata": {}, + "outputs": [], + "source": [ + "j.input.steps = 100\n", + "j.input.timestep = 3\n", + "j.input.temperature = 600\n", + "j.input.output_steps = 20" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "56c0e73a-c42b-4814-a25a-e6974fea3d00", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:root:Job already finished!\n" + ] + } + ], + "source": [ + "j.run(how='foreground')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49ccfe01-7b7e-4615-bf43-21c1bbffec66", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "558dcbd7-7237-4067-b2e6-b797e3c63aaa", + "metadata": {}, + "source": [ + "## Min Job" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "049f056e-47ff-4c2f-9e85-612744af15a8", + "metadata": {}, + "outputs": [], + "source": [ + "j = GenericTinyJob(pr, 'min')" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "1137a899-b00b-4ce4-92df-23a4bbcf7aa8", + "metadata": {}, + "outputs": [], + "source": [ + "j.node_class = AseMinimizeNode" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "3a8cda32-df2e-4884-8cfd-84e438c5be69", + "metadata": {}, + "outputs": [], + "source": [ + "j.input.structure = Atoms(symbols=['Fe', 'Fe'], positions=[[0,0,0], [0,0, .75]], cell=[10,10,10])\n", + "j.input.structure.rattle(1e-3)\n", + "j.input.calculator = MorsePotential()" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "d4b81c3f-4667-4b99-a2b3-08c7ee7e2c82", + "metadata": {}, + "outputs": [], + "source": [ + "j.input.lbfgs(damping=.25)\n", + "j.input.max_steps = 100\n", + "j.input.output_steps = 10" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "e7494fee-d565-45e3-a819-c77ab0d2c7f6", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:root:Job already finished!\n" + ] + } + ], + "source": [ + "exe = j.run(how='process')\n", + "if exe is not None:\n", + " exe.wait()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "be5a6842-d70d-4aa0-ad75-1e3927fbac50", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idusernamenamejobtype_idproject_idstatus_idlocationstatustype
01pyironmd111/home/ponder/science/phd/dev/pyiron_contrib/no...finishedAseMDNode
12pyironmin212/home/ponder/science/phd/dev/pyiron_contrib/no...finishedAseMinimizeNode
23pyironmurn315/home/ponder/science/phd/dev/pyiron_contrib/no...finishedMurnaghanNode
\n", + "
" + ], + "text/plain": [ + " id username name jobtype_id project_id status_id \\\n", + "0 1 pyiron md 1 1 1 \n", + "1 2 pyiron min 2 1 2 \n", + "2 3 pyiron murn 3 1 5 \n", + "\n", + " location status \\\n", + "0 /home/ponder/science/phd/dev/pyiron_contrib/no... finished \n", + "1 /home/ponder/science/phd/dev/pyiron_contrib/no... finished \n", + "2 /home/ponder/science/phd/dev/pyiron_contrib/no... finished \n", + "\n", + " type \n", + "0 AseMDNode \n", + "1 AseMinimizeNode \n", + "2 MurnaghanNode " + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pr.job_table()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "665d50a5-f602-4527-8469-5a04f1c0ee35", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pr.load('min')" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "73a759cb-7217-4d06-87d0-f18c4f18d55e", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "DEBUG:pyiron_log:sql_query: {'project': 'science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/'}\n", + "DEBUG:pyiron_log:sql_query: {'project': 'science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/'}\n" + ] + }, + { + "data": { + "text/plain": [ + "{'groups': [], 'nodes': ['MODULE', 'NAME', 'VERSION', 'node', 'output']}" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pr._project['min/min']" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "359e983d-0fe5-43a4-9e56-73ee77ae3c79", + "metadata": {}, + "outputs": [], + "source": [ + "j.load()" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "8d90b7e6-8530-403b-9f62-dbf81ea35004", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['MODULE', 'NAME', 'VERSION', 'node', 'output']" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "j.storage.list_nodes()" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "61b7df6c-2b50-46a4-ab94-52aa3a9d07f9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "j.storage.list_groups()" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "3fb09d42-f800-46ee-9919-83180863e1ee", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4f766721bc5b43d6a64c39fe10f7f28d", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "NGLWidget(max_frame=11)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "j.output.animate_structures()" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "8298e728-bd31-43ab-b68b-3a26370951af", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "j.id" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43ead40e-bb9e-47e9-adfb-b5406eb04f91", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "f21c8fa9-cd77-4220-bb23-5bcbc49ba62d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idusernamenamejobtype_idproject_idstatus_idlocationstatustype
01pyironmd111/home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/finishedAseMDNode
12pyironmin212/home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/finishedAseMinimizeNode
23pyironmurn315/home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/finishedMurnaghanNode
\n", + "
" + ], + "text/plain": [ + " id username name jobtype_id project_id status_id \\\n", + "0 1 pyiron md 1 1 1 \n", + "1 2 pyiron min 2 1 2 \n", + "2 3 pyiron murn 3 1 5 \n", + "\n", + " location \\\n", + "0 /home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/ \n", + "1 /home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/ \n", + "2 /home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/ \n", + "\n", + " status type \n", + "0 finished AseMDNode \n", + "1 finished AseMinimizeNode \n", + "2 finished MurnaghanNode " + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "j.project.job_table()" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "0d1eb866-8970-45cf-a0c3-7eff3ffea5c2", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "DEBUG:pyiron_log:sql_query: {'project': 'science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/'}\n", + "DEBUG:pyiron_log:sql_query: {'project': 'science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/'}\n" + ] + }, + { + "data": { + "text/plain": [ + "{'groups': [], 'nodes': ['MODULE', 'NAME', 'VERSION', 'node', 'output']}" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pr._project['test/test']" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "7593a54e-168a-4549-8326-50a6103c92b1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "j.id" + ] + }, + { + "cell_type": "markdown", + "id": "858edf07-75ee-4a40-8cff-c26f34d555f5", + "metadata": {}, + "source": [ + "### Loading from job id or name works" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "6d203728-fa24-4a33-bd2c-51709123a7fc", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "DEBUG:matplotlib.pyplot:Loaded backend module://matplotlib_inline.backend_inline version unknown.\n", + "DEBUG:matplotlib.pyplot:Loaded backend module://matplotlib_inline.backend_inline version unknown.\n", + "DEBUG:matplotlib.font_manager:findfont: Matching sans\\-serif:style=normal:variant=normal:weight=normal:stretch=normal:size=10.0.\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBolIta.ttf', name='STIXNonUnicode', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmr10.ttf', name='cmr10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Oblique.ttf', name='DejaVu Sans Mono', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymReg.ttf', name='STIXSizeTwoSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymReg.ttf', name='STIXSizeOneSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneral.ttf', name='STIXGeneral', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-BoldItalic.ttf', name='DejaVu Serif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymBol.ttf', name='STIXSizeFourSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymReg.ttf', name='STIXSizeFourSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansDisplay.ttf', name='DejaVu Sans Display', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Oblique.ttf', name='DejaVu Sans', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 1.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralItalic.ttf', name='STIXGeneral', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymBol.ttf', name='STIXSizeTwoSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmss10.ttf', name='cmss10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFiveSymReg.ttf', name='STIXSizeFiveSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBol.ttf', name='STIXGeneral', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-BoldOblique.ttf', name='DejaVu Sans', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 1.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Bold.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 0.33499999999999996\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Italic.ttf', name='DejaVu Serif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmsy10.ttf', name='cmsy10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmb10.ttf', name='cmb10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymBol.ttf', name='STIXSizeThreeSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-BoldOblique.ttf', name='DejaVu Sans Mono', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymBol.ttf', name='STIXSizeOneSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniIta.ttf', name='STIXNonUnicode', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBol.ttf', name='STIXNonUnicode', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBolIta.ttf', name='STIXGeneral', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmex10.ttf', name='cmex10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUni.ttf', name='STIXNonUnicode', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmmi10.ttf', name='cmmi10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerifDisplay.ttf', name='DejaVu Serif Display', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Bold.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymReg.ttf', name='STIXSizeThreeSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 0.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmtt10.ttf', name='cmtt10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Bold.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/Z003-MediumItalic.otf', name='Z003', style='italic', variant='normal', weight=500, stretch='normal', size='scalable')) = 11.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-Regular.ttf', name='Liberation Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-BlackIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=900, stretch='normal', size='scalable')) = 11.525\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-BoldOblique.otf', name='Nimbus Sans Narrow', style='oblique', variant='normal', weight=700, stretch='condensed', size='scalable')) = 11.535\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-SemiboldIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-BoldItalic.otf', name='Nimbus Roman', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-Bold.ttf', name='Liberation Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-BoldItalic.otf', name='P052', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-DemiItalic.otf', name='URW Bookman', style='italic', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-Bold.otf', name='Nimbus Sans Narrow', style='normal', variant='normal', weight=700, stretch='condensed', size='scalable')) = 10.535\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-Regular.ttf', name='Hack', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-Oblique.otf', name='Nimbus Sans Narrow', style='oblique', variant='normal', weight=400, stretch='condensed', size='scalable')) = 11.25\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-It.otf', name='Source Code Pro', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-Bold.otf', name='P052', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerifItalic.otf', name='FreeSerif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-ExtraLight.otf', name='Source Code Pro', style='normal', variant='normal', weight=200, stretch='normal', size='scalable')) = 10.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-Italic.ttf', name='Liberation Serif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-Italic.otf', name='Nimbus Roman', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSansOblique.otf', name='FreeSans', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-BoldItalic.otf', name='Nimbus Sans', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-BookOblique.otf', name='URW Gothic', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-Bold.otf', name='C059', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/D050000L.otf', name='D050000L', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Light.otf', name='Source Code Pro', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/StandardSymbolsPS.otf', name='Standard Symbols PS', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-Bold.ttf', name='Hack', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-Roman.otf', name='P052', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-BoldItalic.ttf', name='Hack', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Regular.otf', name='Source Code Pro', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-Bold.ttf', name='Liberation Serif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-Italic.otf', name='P052', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-Italic.otf', name='Nimbus Sans', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Light.otf', name='Cantarell', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-Italic.ttf', name='Hack', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Regular.otf', name='Cantarell', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Thin.otf', name='Cantarell', style='normal', variant='normal', weight=100, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Bold.otf', name='Source Code Pro', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerifBold.otf', name='FreeSerif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-Light.otf', name='URW Bookman', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-Roman.otf', name='C059', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-Regular.otf', name='Nimbus Sans Narrow', style='normal', variant='normal', weight=400, stretch='condensed', size='scalable')) = 10.25\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-ExtraBold.otf', name='Cantarell', style='normal', variant='normal', weight=800, stretch='normal', size='scalable')) = 10.43\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Black.otf', name='Source Code Pro', style='normal', variant='normal', weight=900, stretch='normal', size='scalable')) = 10.525\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerif.otf', name='FreeSerif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-BoldItalic.otf', name='Nimbus Mono PS', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-BoldItalic.ttf', name='Liberation Serif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Bold.otf', name='Cantarell', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-Italic.otf', name='Nimbus Mono PS', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-DemiOblique.otf', name='URW Gothic', style='oblique', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-Regular.otf', name='Nimbus Mono PS', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSans.otf', name='FreeSans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSansBold.otf', name='FreeSans', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-Italic.ttf', name='Liberation Sans', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-BoldItalic.ttf', name='Liberation Sans', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-BoldItalic.ttf', name='Liberation Mono', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Medium.otf', name='Source Code Pro', style='normal', variant='normal', weight=500, stretch='normal', size='scalable')) = 10.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-LightItalic.otf', name='URW Bookman', style='italic', variant='normal', weight=300, stretch='normal', size='scalable')) = 11.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-LightIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=300, stretch='normal', size='scalable')) = 11.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-Italic.ttf', name='Liberation Mono', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-Bold.ttf', name='Liberation Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-Italic.otf', name='C059', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-Demi.otf', name='URW Gothic', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-MediumIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=500, stretch='normal', size='scalable')) = 11.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-BoldIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-BdIta.otf', name='C059', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMonoBoldOblique.otf', name='FreeMono', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-ExtraLightIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=200, stretch='normal', size='scalable')) = 11.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-Bold.otf', name='Nimbus Roman', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSansBoldOblique.otf', name='FreeSans', style='oblique', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-Book.otf', name='URW Gothic', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMonoBold.otf', name='FreeMono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-Regular.ttf', name='Liberation Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-Demi.otf', name='URW Bookman', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Semibold.otf', name='Source Code Pro', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerifBoldItalic.otf', name='FreeSerif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMono.otf', name='FreeMono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-Bold.otf', name='Nimbus Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-Bold.otf', name='Nimbus Mono PS', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMonoOblique.otf', name='FreeMono', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-Regular.ttf', name='Liberation Serif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-Regular.otf', name='Nimbus Roman', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-Regular.otf', name='Nimbus Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: Matching sans\\-serif:style=normal:variant=normal:weight=normal:stretch=normal:size=10.0 to DejaVu Sans ('/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf') with score of 0.050000.\n" + ] + }, + { + "data": { + "image/png": "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PaW77rrL6dJSVvS3ZbDfnaNHjzpRUtIZ9+EjyjCMfs9N0xxwDCO3bNkyffzxx9q9e7fTpaSUuro6LV++XG+88YYyMjKcLieldXV1ac6cOVq7dq0k6corr9Snn36qDRs2ED7isHXrVr388svasmWLZs6cqQMHDmjFihUqKSnR4sWLnS4vpfG7M7RxHz4mTpwoj8czYJWjoaFhQCrFyDz88MPavn27du3apbKyMqfLSSn79u1TQ0ODZs+eHTsWiUS0a9cu1dTUKBQKyePxOFhh6iguLtYll1zS79iMGTP02muvOVRRavrZz36mVatW6c4775QkXXrppTp69Kiqq6sJH6NUVFQkqXsFpLi4OHac351e477nw+fzafbs2aqtre13vLa2VnPnznWoqtRkmqaWLVum119/XTt27NC0adOcLinlfPe739Unn3yiAwcOxB5z5szRPffcowMHDhA84jBv3rwBW70PHz6sqVOnOlRRamptbVVaWv+fAo/Hw1bbMZg2bZqKior6/e6Ew2Ht3LmT350e437lQ5Kqqqr0wx/+UHPmzNG1116rjRs36tixY1qyZInTpaWUpUuXasuWLfrDH/6gnJyc2GpSIBBQZmamw9WlhpycnAE9MtnZ2SosLKR3Jk4//elPNXfuXK1du1bf//739cEHH2jjxo3auHGj06WllEWLFumpp57SlClTNHPmTH344Yd67rnn9OCDDzpdWlJraWnRF198EXt+5MgRHThwQAUFBZoyZYpWrFihtWvXqqKiQhUVFVq7dq2ysrJ09913O1h1EnF2s03i/Mu//Is5depU0+fzmd/+9rfZHjoKkgZ9bNq0yenSUhpbbUfvP/7jP8xZs2aZfr/fnD59urlx40anS0o5wWDQXL58uTllyhQzIyPDvOiii8zHHnvMDIVCTpeW1N56661B/3m4ePFi0zS7t9s+8cQTZlFRken3+83rrrvO/OSTT5wtOokYpmmaDuUeAADgQuO+5wMAACQXwgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEio/w9hHopmxnvDfAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "j.project.load(j.id).output.plot_energies()" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "23ce6822-b38b-41f3-9269-109dbb152ecf", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "0ab49f229fe34634ad5ab3b4c233bddc", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "NGLWidget(max_frame=11)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "j.project.load(j.name).output.animate_structures()" + ] + }, + { + "cell_type": "markdown", + "id": "eec174f8-d6a8-47dd-baff-f23087265375", + "metadata": {}, + "source": [ + "## Murnaghan" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "d0f62439-3492-4392-ac2a-2b2545b85527", + "metadata": {}, + "outputs": [], + "source": [ + "murn = GenericTinyJob(pr, 'murn')" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "5c9ab533-cf97-49a1-8c4b-0e5e2f9758c2", + "metadata": {}, + "outputs": [], + "source": [ + "murn.node_class = MurnaghanNode" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "253237f0-b338-470c-bc54-3c7400a757b7", + "metadata": {}, + "outputs": [], + "source": [ + "murn.input.node = AseStaticNode()\n", + "murn.input.node.input.calculator = MorsePotential()" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "c801093b-499e-48a7-8444-77602ed88a96", + "metadata": {}, + "outputs": [], + "source": [ + "murn.input.structure = bulk(\"Fe\", a=1.2)" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "1e30b36e-11e6-47d1-836e-cffea7b73cdd", + "metadata": {}, + "outputs": [], + "source": [ + "murn.input.set_strain_range(.5, 500)" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "9920e4b7-8395-4fb9-96c3-792b044c4e3a", + "metadata": {}, + "outputs": [], + "source": [ + "murn.input.child_executor = ProcessExecutor" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "18b5305a-8950-44af-bc2e-c9734b059713", + "metadata": {}, + "outputs": [], + "source": [ + "exe = murn.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "565408a4-4e7f-4c15-b5d7-1aa63e979b8e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None),)" + ] + }, + "execution_count": 75, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.status" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "836bb2ec-4295-4a3c-b976-7a35d04aad36", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "murn.output.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "d8a98b01-9999-428e-9211-c1df032e7ba3", + "metadata": {}, + "source": [ + "# Database Tests" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "a6fefefb-b09c-4cee-b632-29f88bccfeee", + "metadata": {}, + "outputs": [], + "source": [ + "db = j.project.database" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5c1a24bd-ccbe-4cad-94b7-1539084f5924", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "3419f273-b94b-48cf-9373-7441baec2353", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "DatabaseEntry(name='md', username='pyiron', project='/home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/', status='finished', jobtype='AseMDNode')" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db.get_item(1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a7521b67-12cd-4835-ac1d-fc42f80c01bb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "cf75b2e8-ec15-4846-bda4-9b6661e8b5fa", + "metadata": {}, + "outputs": [], + "source": [ + "eng = db.get_engine()" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "1b23728b-1050-47a4-bda0-bd5967ceed2e", + "metadata": {}, + "outputs": [], + "source": [ + "from sqlalchemy.orm import Session" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "4cd73be9-2c6d-4501-8a6c-0afc6083a4b9", + "metadata": {}, + "outputs": [], + "source": [ + "s = Session(eng)" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "e9d9a3e2-5c86-46b4-b6d0-bec3a9f448b0", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.database import Job, Project as DProject, JobStatus, JobType" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "99059ff6-18bd-40b9-85fc-d76e1828f7ac", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(2,)]" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s.query(Job.id).where(Job.name == \"min\", ).all()" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "cb4bb9fe-13bf-4736-aa68-662b980d4f00", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(2, 'finished')]" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s.query(JobStatus.__table__).select_from(Job).where(Job.id == 2, Job.status_id == JobStatus.id).all()" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "4641b048-b7c7-46a2-b67c-835c08916cb0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(1, 'finished'), (2, 'finished'), (3, 'finished')]" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s.query(JobStatus.__table__).all()" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "94364e54-b980-48cc-995b-daf977437b1b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(1, '/home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/')]" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s.query(DProject.__table__).all()" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "f32c43d5-19c8-4505-bf5c-9ac32bca51d0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(1, 'AseMDNode'), (2, 'AseMinimizeNode'), (3, 'MurnaghanNode')]" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s.query(JobType.__table__).all()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0dc1921b-8eb2-46ef-a228-d6b86d63fea7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "905c06d2-0f66-4fa3-b771-8a4b6e7ed3a4", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.0" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": { + "63cd145ecad142378ffe5f9bbe3c9a95": { + "model_module": "nglview-js-widgets", + "model_module_version": "3.0.1", + "model_name": "ColormakerRegistryModel", + "state": { + "_msg_ar": [], + "_msg_q": [], + "_ready": true, + "layout": "IPY_MODEL_ed7d7881cda84e47a33b538e45e24bff" + } + }, + "ed7d7881cda84e47a33b538e45e24bff": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + } + }, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyiron_contrib/tinybase/__init__.py b/pyiron_contrib/tinybase/__init__.py index e69de29bb..36e61c641 100644 --- a/pyiron_contrib/tinybase/__init__.py +++ b/pyiron_contrib/tinybase/__init__.py @@ -0,0 +1,2 @@ + +__version__ = "0.1.0" diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index 2f59aff75..1b8406015 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -3,6 +3,8 @@ import abc import sys +from pyiron_contrib.tinybase.storage import HasHDFAdapaterMixin + from pyiron_base.interfaces.object import HasStorage from pyiron_atomistics.atomistics.structure.has_structure import HasStructure @@ -92,7 +94,7 @@ def default(self, default_constructor): def doc(self, text): self.__doc__ = text -class AbstractContainer(HasStorage, abc.ABC): +class AbstractContainer(HasStorage, HasHDFAdapaterMixin, abc.ABC): # TODO: this should go into HasStorage, exists here only to give one location to define from_ methods @classmethod def from_attributes(cls, name, *attrs, module=None, bases=(), **default_attrs): diff --git a/pyiron_contrib/tinybase/database.py b/pyiron_contrib/tinybase/database.py new file mode 100644 index 000000000..5f2f8312a --- /dev/null +++ b/pyiron_contrib/tinybase/database.py @@ -0,0 +1,219 @@ +import abc +from collections import namedtuple +import os.path +from typing import List +from typing import Optional +from sqlalchemy import ( + ForeignKey, + String, + Integer, + Column, + create_engine +) +from sqlalchemy.exc import ( + MultipleResultsFound, + NoResultFound +) +from sqlalchemy.orm import declarative_base, relationship +from sqlalchemy.orm import Session + +import pandas as pd + +DatabaseEntry = namedtuple("DatabaseEntry", + ["name", "username", "project", "status", "jobtype"] +) + +Base = declarative_base() + +class Project(Base): + __tablename__ = "project_table" + + id = Column(Integer, primary_key=True) + location = Column(String(250)) + # too stupid to get the bydirectional thing going, whatevs... + # jobs = relationship("Job", back_populates="project") + # jobs = relationship("Job", backref="project") + +# FIXME: Can be many-to-many later +class JobStatus(Base): + __tablename__ = "job_status_table" + + id = Column(Integer, primary_key=True) + status = Column(String(250)) + +# FIXME: Can be many-to-many later +class JobType(Base): + __tablename__ = "job_type_table" + + id = Column(Integer, primary_key=True) + type = Column(String(250)) + +class Job(Base): + __tablename__ = "job_table" + + id = Column(Integer, primary_key=True) + username = Column(String(250)) + name = Column(String(250)) + + jobtype_id = Column(Integer, ForeignKey("job_type_table.id")) + project_id = Column(Integer, ForeignKey("project_table.id")) + status_id = Column(Integer, ForeignKey("job_status_table.id")) + # project = relationship("Project", back_populates="jobs") + +# TODO: this will be pyiron_base.IsDatabase +class GenericDatabase(abc.ABC): + """ + Defines the database interface used by the :class:`.ProjectInterface`. + """ + + @abc.abstractmethod + def add_item(self, entry: DatabaseEntry) -> int: + pass + + @abc.abstractmethod + def get_item(self, job_id: int) -> DatabaseEntry: + pass + + @abc.abstractmethod + def get_item_id(self, job_name: str, project_id: int) -> Optional[int]: + pass + + @abc.abstractmethod + def get_project_id(self, location: str) -> Optional[int]: + pass + + @abc.abstractmethod + def update_status(self, job_id, status): + pass + + @abc.abstractmethod + def remove_item(self, job_id: int) -> DatabaseEntry: + pass + + @abc.abstractmethod + def job_table(self) -> pd.DataFrame: + pass + +class TinyDB(GenericDatabase): + """ + Minimal database implementation and "reference". Exists mostly to allow easy testing without messing with + DatabaseAccess. + """ + + def __init__(self, path, echo=False): + self._path = path + self._echo = echo + engine = self.get_engine() + Base.metadata.create_all(engine) + Base.metadata.reflect(engine, extend_existing=True) + + def get_engine(self): + return create_engine(f"sqlite:///{self._path}", echo=self._echo) + + def add_item(self, entry: DatabaseEntry) -> int: + with Session(self.get_engine()) as session: + project = session.query(Project).where(Project.location==entry.project).one_or_none() + if project is None: + project = Project(location=entry.project) + session.add(project) + jobtype = session.query(JobType).where(JobType.type==entry.jobtype).one_or_none() + if jobtype is None: + jobtype = JobType(type=entry.jobtype) + session.add(jobtype) + status = JobStatus(status=entry.status) + session.add(status) + session.flush() + job = Job( + name=entry.name, + username=entry.username, + project_id=project.id, + status_id=status.id, + jobtype_id=jobtype.id + ) + session.add(job) + session.flush() + job_id = job.id + session.commit() + return job_id + + def update_status(self, job_id, status): + with Session(self.get_engine()) as session: + try: + s = session.query(JobStatus).select_from(Job).where( + Job.id == job_id, + JobStatus.id == Job.status_id + ).one() + s.status = status + session.commit() + except Exception as e: + raise ValueError(f"job_id {job_id} doesn't exist: {e}") + + def _row_to_entry(self, job_data): + return DatabaseEntry( + name=job_data["name"], + project=job_data["location"], + username=job_data["username"], + status=job_data["status"], + jobtype=job_data["type"] + ) + + def get_item(self, job_id: int) -> DatabaseEntry: + with Session(self.get_engine()) as session: + job_data = session.query( + Job.__table__, Project.location, JobStatus.status, JobType.type + ).select_from( + Job + ).where( + Job.id == job_id + ).join( + Project, Job.project_id==Project.id + ).join( + JobStatus, Job.status_id==JobStatus.id + ).join( + JobType, Job.jobtype_id==JobType.id + ).one() + return self._row_to_entry(job_data) + + def get_item_id(self, job_name: str, project_id: int) -> Optional[int]: + with Session(self.get_engine()) as session: + try: + return session.query(Job.id).where( + Job.name == job_name, + Job.project_id == project_id, + ).one().id + except (MultipleResultsFound, NoResultFound): + return None + + def get_project_id(self, location: str) -> Optional[int]: + with Session(self.get_engine()) as session: + try: + return session.query(Project.id).where(Project.location == location).one().id + # FIXME: MultipleResultsFound should be reraised because it indicates a broken database + except (MultipleResultsFound, NoResultFound): + return None + + def remove_item(self, job_id: int) -> DatabaseEntry: + # FIXME: probably a bit inefficient, because it makes two connections to the DB + entry = self.get_item(job_id) + with Session(self.get_engine()) as session: + job = session.get(Job, job_id) + session.delete(job) + session.commit() + return entry + + def job_table(self) -> pd.DataFrame: + with Session(self.get_engine()) as session: + query = session.query( + Job.__table__, Project.location, JobStatus.status, JobType.type + ).select_from( + Job + ).join( + Project, Job.project_id==Project.id + ).join( + JobStatus, Job.status_id==JobStatus.id + ).join( + JobType, Job.jobtype_id==JobType.id + ) + return pd.DataFrame( + list(map(dict, query.all())) + ) diff --git a/pyiron_contrib/tinybase/job.py b/pyiron_contrib/tinybase/job.py new file mode 100644 index 000000000..e81de1f25 --- /dev/null +++ b/pyiron_contrib/tinybase/job.py @@ -0,0 +1,253 @@ +import abc +import logging +from typing import Optional + +from pyiron_contrib.tinybase.node import AbstractNode +from pyiron_contrib.tinybase.storage import ( + Storable, + GenericStorage, + pickle_load, + pickle_dump +) +from pyiron_contrib.tinybase.executor import ( + Executor, + BackgroundExecutor, + ProcessExecutor +) +from pyiron_contrib.tinybase.database import ( + DatabaseEntry +) +from pyiron_contrib.tinybase.project import ProjectInterface, ProjectAdapter +from pyiron_base.state import state + + +class TinyJob(Storable, abc.ABC): + """ + A tiny job unifies an executor, a node and its output. + + The job adds the node to the database and persists its input and output in a storage location. + + The input of the node is available from :attr:`~.input`. After the job has finished the output of the node can be + accessed from :attr:`~.output` and the data written to storage from :attr:`.~storage`. + + This is an abstracat base class that works with any execution node without specifying it. To create specialized + jobs you can derive from it and overload :meth:`._get_node()` to return an instance of the node, e.g. + + >>> from somewhere import MyNode + >>> class MyJob(TinyJob): + ... def _get_node(self): + ... return MyNode() + + The return value of :meth:`._get_node()` is persisted during the life time of the job. + + You can use :class:`.GenericTinyJob` to dynamically specify which node the job should execute. + """ + + _executors = { + 'foreground': Executor, + 'background': BackgroundExecutor, + 'process': ProcessExecutor + } + + def __init__(self, project: ProjectInterface, job_name: str): + """ + Create a new job. + + If the given `job_name` is already present in the `project` it is reloaded. No checks are performed that the + node type of the already present job and the current one match. This is also not always necessary, e.g. when + reloading a :class:`.GenericTinyJob` it will automatically read the correct node from storage. + + Args: + project (:class:`.ProjectInterface`): the project the job should live in + job_name (str): the name of the job. + """ + if not isinstance(project, ProjectInterface): + project = ProjectAdapter(project) + self._project = project + self._name = job_name + self._node = None + self._output = None + self._storage = None + self._executor = None + self._id = None + # FIXME: this should go into the job creation logic on the project + if project.exists_storage(job_name): + try: + self.load() + except Exception as e: + raise RuntimeError(f"Failed to reload run job from storage: {e}") from None + + @property + def name(self): + return self._name + + @property + def id(self): + return self._id + + def _update_id(self): + self._id = self.project.get_job_id(self.name) + + @property + def project(self): + return self._project + + @abc.abstractmethod + def _get_node(self) -> AbstractNode: + """ + Return an instance of the :class:`.AbstractNode`. + + The value return from here is saved automatically in :prop:`.node`. + """ + pass + + @property + def node(self): + if self._node is None: + self._node = self._get_node() + return self._node + + @property + def jobtype(self): + return self.node.__class__.__name__ + + @property + def input(self): + return self.node.input + + @property + def output(self): + if self._output is not None: + return self._output + else: + raise RuntimeError("Job not finished yet!") + + @property + def storage(self): + if self._storage is None: + self._storage = self._project.create_storage(self.name) + return self._storage + + def _set_output(self, data): + self._output = data["output"][0] + + def _setup_executor_callbacks(self): + self._executor._run_machine.observe("ready", lambda _: self.store(self.storage)) + self._executor._run_machine.observe("finished", self._set_output) + self._executor._run_machine.observe("finished", lambda _: self.store(self.storage)) + + self._executor._run_machine.observe("ready", self._add_to_database) + self._executor._run_machine.observe("running", self._update_status("running")) + self._executor._run_machine.observe("collect", self._update_status("collect")) + self._executor._run_machine.observe("finished", self._update_status("finished")) + + def run(self, how='foreground') -> Optional[Executor]: + """ + Start execution of the job. + + If the job already has a database id and is not in "ready" state, do nothing. + + Args: + how (string): specifies which executor to use + + Returns: + :class:`.Executor`: the executor that is running the node or nothing. + """ + if self._id is None or self.project.database.get_item(self.id).status == "ready": + exe = self._executor = self._executors[how](nodes=[self.node]) + self._setup_executor_callbacks() + exe.run() + return exe + else: + logging.info("Job already finished!") + + def remove(self): + """ + Remove the job from the database and storage. + + Resets the internal state so that the job could be re-run from the same instance. + """ + self.project.remove(self.id) + self._id = None + self._output = None + self._storage = None + self._executor = None + + def _add_to_database(self, _data): + if self._id is None: + entry = DatabaseEntry( + name=self.name, + project=self.project.path, + username=state.settings.login_user, + status="ready", + jobtype=self.jobtype, + ) + self._id = self.project.database.add_item(entry) + return self.id + + def _update_status(self, status): + return lambda _data: self.project.database.update_status(self.id, status) + + # Storable Impl' + def _store(self, storage): + # preferred solution, but not everything that can be pickled can go into HDF atm + # self._executor.output[-1].store(storage, "output") + storage["node"] = pickle_dump(self.node) + if self._output is not None: + storage["output"] = pickle_dump(self._output) + + def load(self, storage: GenericStorage = None): + """ + Load job from storage and reload id from database. + + If `storage` is not given, use the default provided by the project. + + Args: + storage (:class:`.GenericStorage`): where to read from + """ + self._update_id() + if storage is None: + storage = self.storage + self._node = pickle_load(storage["node"]) + # this would be correct, but since we pickle output and put it into a HDF node it doesn't appear here yet! + # if "output" in storage.list_groups(): + if "output" in storage.list_nodes(): + self._output = pickle_load(storage["output"]) + + @classmethod + def _restore(cls, storage, version): + job = cls(project=storage.project, job_name=storage.name) + job.load(storage=storage) + return job + + +# I'm not perfectly happy with this, but three thoughts led to this class: +# 1. I want to be able to set any node on a tiny job with subclassing, to make the prototyping new jobs in the notebook +# easy +# 2. I do *not* want people to accidently change the node instance/class while the job is running +class GenericTinyJob(TinyJob): + """ + A generic tiny job is a tiny job that allows to set any node class after instantiating it. + + Set a node class via :attr:`.node_class`, e.g. + + >>> from somewhere import MyNode + >>> job = GenericTinyJob(Project(...), "myjob") + >>> job.node_class = MyNode + >>> isinstance(job.input, type(MyNode.input)) + True + """ + def __init__(self, project, job_name): + super().__init__(project=project, job_name=job_name) + self._node_class = None + + @property + def node_class(self): + return self._node_class + + @node_class.setter + def node_class(self, cls): + self._node_class = cls + + def _get_node(self): + return self.node_class() diff --git a/pyiron_contrib/tinybase/node.py b/pyiron_contrib/tinybase/node.py index dcbdeb49d..bc396bbf8 100644 --- a/pyiron_contrib/tinybase/node.py +++ b/pyiron_contrib/tinybase/node.py @@ -5,6 +5,7 @@ from pyiron_base.interfaces.object import HasStorage +from pyiron_contrib.tinybase.storage import Storable, pickle_dump from .container import AbstractInput, AbstractOutput, StorageAttribute from .executor import ( Executor, @@ -32,7 +33,7 @@ def __str__(self): def is_done(self): return self.code == self.Code.DONE -class AbstractNode(abc.ABC): +class AbstractNode(Storable, abc.ABC): _executors = { 'foreground': Executor, @@ -76,6 +77,19 @@ def run(self, how='foreground'): exe.run() return exe + # Storable Impl' + # We might even avoid this by deriving from HasStorage and put _input in there + def _store(self, storage): + # right now not all ASE objects can be stored in HDF, so let's just pickle for now + storage["input"] = pickle_dump(self.input) + # self.input.store(storage, "input") + + @classmethod + def _restore(cls, storage, version): + node = cls() + node._input = pickle_load(storage["input"]) + return node + FunctionInput = AbstractInput.from_attributes("FunctionInput", args=list, kwargs=dict) FunctionOutput = AbstractOutput.from_attributes("FunctionOutput", "result") class FunctionNode(AbstractNode): @@ -301,37 +315,3 @@ def _execute(self, output): break control.restart(out, node.input) output.transfer(out) - - -class TinyJob(abc.ABC): - - _executors = { - 'foreground': Executor, - 'background': BackgroundExecutor, - 'process': ProcessExecutor - } - - def __init__(self, project, job_name): - self._project = project - self._name = job_name - - @abc.abstractmethod - def _get_node(self): - pass - - @property - def input(self): - return self._node.input - - @property - def output(self): - return self._node.output - - def run(self, how='foreground'): - exe = self._executor = self._executors[how](nodes=[self._node]) - exe._run_machine.observe("ready", self.save_input) - exe._run_machine.observe("collect", self.save_output) - exe.run() - return exe - - diff --git a/pyiron_contrib/tinybase/project.py b/pyiron_contrib/tinybase/project.py new file mode 100644 index 000000000..a99066f73 --- /dev/null +++ b/pyiron_contrib/tinybase/project.py @@ -0,0 +1,100 @@ +import abc +import os.path + +from pyiron_base import Project +from pyiron_contrib.tinybase.storage import GenericStorage, ProjectHDFioStorageAdapter +from pyiron_contrib.tinybase.database import TinyDB, GenericDatabase + +class ProjectInterface(abc.ABC): + + @classmethod + @abc.abstractmethod + def open_location(cls, location) -> "ProjectInterface": + pass + + @abc.abstractmethod + def create_storage(self, name) -> GenericStorage: + pass + + @abc.abstractmethod + def exists_storage(self, name) -> bool: + pass + + @abc.abstractmethod + def remove_storage(self, name): + pass + + @abc.abstractmethod + def _get_database(self) -> GenericDatabase: + pass + + @property + def database(self): + return self._get_database() + + def load(self, name_or_id: int | str) -> "TinyJob": + # if a name is given, job must be in the current project + if isinstance(name_or_id, str): + pr = self + name = name_or_id + else: + entry = self.database.get_item(name_or_id) + pr = self.open_location(entry.project) + name = entry.name + return pr.create_storage(name).to_object() + + @property + @abc.abstractmethod + def path(self): + pass + + @property + @abc.abstractmethod + def name(self): + pass + +class ProjectAdapter(ProjectInterface): + + def __init__(self, project): + self._project = project + self._database = None + + @classmethod + def open_location(cls, location): + return cls(Project(location)) + + def create_storage(self, name): + return ProjectHDFioStorageAdapter( + self, + self._project.create_hdf(self._project.path, name) + ) + + def exists_storage(self, name) -> bool: + return self._project.create_hdf(self._project.path, name).file_exists + + def remove_storage(self, name): + self._project.create_hdf(self._project.path, name).remove_file() + + def remove(self, job_id): + entry = self.database.remove_item(job_id) + self.remove_storage(entry.name) + + def _get_database(self): + if self._database is None: + self._database = TinyDB(os.path.join(self._project.path, "pyiron.db")) + return self._database + + @property + def name(self): + return self._project.name + + @property + def path(self): + return self._project.path + + def job_table(self): + return self.database.job_table() + + def get_job_id(self, name): + project_id = self.database.get_project_id(self.path) + return self.database.get_item_id(name, project_id) diff --git a/pyiron_contrib/tinybase/storage.py b/pyiron_contrib/tinybase/storage.py new file mode 100644 index 000000000..0b13920b2 --- /dev/null +++ b/pyiron_contrib/tinybase/storage.py @@ -0,0 +1,264 @@ +import abc +import importlib +from typing import Any, Union, Optional + +from pyiron_contrib.tinybase import __version__ as base__version__ + +import pickle +import codecs + +# utility functions until ASE can be HDF'd +def pickle_dump(obj): + return codecs.encode(pickle.dumps(obj), "base64").decode() + +def pickle_load(buf): + return pickle.loads(codecs.decode(buf.encode(), "base64")) + + +class GenericStorage(abc.ABC): + """ + Generic interface to store things. + + The canonical implementation and model is ProjectHDFio from base, but other implementations are thinkable (S3, in + memory, etc.) + + The concepts are borrowed mostly from HDF5 files. There are groups, :meth:`.list_groups()`, and nodes, + :meth:`.list_nodes()`, inside any storage. Every group has a name, :attr:`.name`. + + Implementations must allow multiple objects of this class to refer to the same underlying storage group at the same + time and access via the methods here must be atomic. + """ + + @abc.abstractmethod + def __getitem__(self, item: str) -> Union["GenericStorage", Any]: + """ + Return a value from storage. + + If `item` is in :meth:`.list_groups()` this must return another :class:`.GenericStorage`. + + Args: + item (str): name of value + + Returns: + :class:`.GenericStorage`: if `item` refers to a sub group + object: value that is stored under `item` + + Raises: + KeyError: `item` is neither a node or a sub group of this group + """ + pass + + @abc.abstractmethod + def __setitem__(self, item: str, value: Any): + """ + Set a value to storage. + + Args: + item (str): name of the value + value (object): value to store + """ + pass + + @abc.abstractmethod + def create_group(self, name): + """ + Create a new sub group. + + Args: + name (str): name of the new group + """ + pass + + def open(self, name: str) -> "GenericStorage": + """ + Descend into a sub group. + + If `name` does not exist yet, create a new group. Calling :meth:`~.close` on the returned object returns this + object. + + Args: + name (str): name of sub group + + Returns: + :class:`.GenericStorage`: sub group + """ + # FIXME: what if name in self.list_nodes() + new = self.create_group(name) + new._prev = self + return new + + def close(self) -> "GenericStorage": + """ + Surface from a sub group. + + If this object was not returned from a previous call to :meth:`.open` it returns itself silently. + """ + try: + return self._prev + except AttributeError: + return self + + @abc.abstractmethod + def list_nodes(self) -> list[str]: + """ + List names of values inside group. + """ + pass + + @abc.abstractmethod + def list_groups(self) -> list[str]: + """ + List name of sub groups. + """ + pass + + # DESIGN: this mostly exists to help to_object()ing GenericTinyJob, but it introduces a circular-ish connection. + # Maybe there's another way to do it? + @property + @abc.abstractmethod + def project(self): + """ + The project that this storage belongs to. + """ + pass + + @property + @abc.abstractmethod + def name(self): + """ + The name of the group that this object is currently pointing to. + + (In ProjectHDFio speak, this is the os.path.basename of h5_path) + """ + pass + + def to_object(self) -> "Storable": + """ + Instantiate an object serialized to this group. + + Returns: + :class:`.Storable`: deserialized object + + Raises: + ValueError: no was object serialized in this group (either NAME, MODULE or VERSION values missing) + RuntimeError: failed to import serialized object (probably some libraries not installed) + """ + try: + name = self["NAME"] + module = self["MODULE"] + version = self["VERSION"] + except KeyError: + raise ValueError("No object was serialized in this group!") + try: + cls = getattr(importlib.import_module(module), name) + except ImportError: + raise RuntimeError("Failed to import serialized object!") + return cls.restore(self, version=version) + +class ProjectHDFioStorageAdapter(GenericStorage): + """ + Adapter class around ProjectHDFio to let it be used as a GenericStorage. + """ + + def __init__(self, project, hdf): + self._project = project + self._hdf = hdf + + def __getitem__(self, item): + return self._hdf[item] + + def __setitem__(self, item, value): + self._hdf[item] = value + + def create_group(self, name): + return ProjectHDFioStorageAdapter(self._project, self._hdf.create_group(name)) + + def list_nodes(self): + return self._hdf.list_nodes() + + def list_groups(self): + return self._hdf.list_groups() + + # compat with small bug in base ProjectHDFio + list_dirs = list_groups + + @property + def project(self): + return self._project + + @property + def name(self): + return self._hdf.name + +# DESIGN: equivalent of HasHDF but with generalized language +class Storable(abc.ABC): + """ + Interface for classes that can store themselves to a :class:`~.GenericStorage` + + Necessary overrides are :meth:`._store` and :meth:`._restore`. + """ + + @abc.abstractmethod + def _store(self, storage): + pass + + def _store_type(self, storage): + storage["NAME"] = self.__class__.__name__ + storage["MODULE"] = self.__class__.__module__ + # DESIGN: what happens with objects defined in different parts of pyiron{_contrib,_atomistics,_base}? Which + # version do we save? + storage["VERSION"] = base__version__ + + def store(self, storage: GenericStorage, group_name: Optional[str] = None): + """ + Store object into storage. + + Args: + storage (:class:`.GenericStorage`): storage to write to + group_name (str): if given descend into this subgroup first + """ + if group_name is not None: + storage = storage.create_group(group_name) + self._store_type(storage) + self._store(storage) + + @classmethod + @abc.abstractmethod + def _restore(cls, storage: GenericStorage, version: str) -> "Storable": + pass + + @classmethod + def restore(cls, storage: GenericStorage, version: str) -> "Storable": + """ + Restore an object of type `cls` from storage. + + Args: + storage (:class:`.GenericStorage`): storage to read from + version (str): version string of pyiron that wrote the object + + Return: + :class:`.Storable`: deserialized object + + Raises: + ValueError: failed to restore object + """ + try: + return cls._restore(storage, version) + except Exception as e: + raise ValueError(f"Failed to restore object with {e}") + +class HasHDFAdapaterMixin(Storable): + """ + Implements :class:`.Storable` in terms of HasHDF. Make any sub class of it a subclass :class:`.Storable` as well by + mixing this class in. + """ + + def _store(self, storage): + self._to_hdf(storage) + + @classmethod + def _restore(cls, storage, version): + kw = cls.from_hdf_args(storage) + obj = cls(**kw) + obj._from_hdf(storage, version) + return obj From a6e5a6d292cddcbf7d092f3ccdf67d76f2db1fb4 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Thu, 9 Mar 2023 16:31:34 +0100 Subject: [PATCH 032/756] Add an example for an in memory project Purely as a demonstrator to test different implementations of ProjectInterface --- notebooks/tinybase/ASE.ipynb | 3735 +++++++++++++++++---------- notebooks/tinybase/Basic.ipynb | 20 +- notebooks/tinybase/TinyJob.ipynb | 2478 ++++++++++++++---- pyiron_contrib/tinybase/database.py | 34 +- pyiron_contrib/tinybase/murn.py | 5 +- pyiron_contrib/tinybase/project.py | 63 +- pyiron_contrib/tinybase/storage.py | 44 + 7 files changed, 4529 insertions(+), 1850 deletions(-) diff --git a/notebooks/tinybase/ASE.ipynb b/notebooks/tinybase/ASE.ipynb index 83daeb82d..81de19372 100644 --- a/notebooks/tinybase/ASE.ipynb +++ b/notebooks/tinybase/ASE.ipynb @@ -40,17 +40,38 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 4, "id": "b3108213-1d94-4354-9537-84982e45683d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/ponder/science/phd/dev/pyiron_contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n", + " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d3dba68b040c436a87aa1acf4b90a1c9", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from pyiron_contrib.tinybase.ase import AseStaticNode, AseMDNode, AseMinimizeNode" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 5, "id": "0029125a-55e6-4181-a59b-09f606a1b4dd", "metadata": {}, "outputs": [], @@ -60,7 +81,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 6, "id": "c7c74920-c6b3-4577-a60f-951a0d3276ec", "metadata": {}, "outputs": [], @@ -70,7 +91,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 7, "id": "c6630920-6ab7-4273-883e-999020b1fe5a", "metadata": {}, "outputs": [], @@ -89,7 +110,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 8, "id": "a6af72cb-989b-46c3-a2b5-4d2b9c5fd1eb", "metadata": {}, "outputs": [], @@ -99,7 +120,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 9, "id": "5b2a9d62-3f74-4acf-acb6-e72dcd984704", "metadata": {}, "outputs": [], @@ -109,7 +130,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 10, "id": "1af70322-897e-487d-ba18-239ba5bfb7ba", "metadata": {}, "outputs": [], @@ -119,7 +140,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 11, "id": "273902ef-03f3-4f68-8668-4e6c6055a302", "metadata": {}, "outputs": [ @@ -127,10 +148,10 @@ "data": { "text/plain": [ "(ReturnStatus(Code.DONE, None),\n", - " )" + " )" ] }, - "execution_count": 16, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -141,7 +162,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 12, "id": "497de0b9-5e11-4d6c-8c19-664d0e759ac4", "metadata": {}, "outputs": [ @@ -151,7 +172,7 @@ "-0.00013307075712109978" ] }, - "execution_count": 17, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -162,7 +183,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 13, "id": "57eced2f-6649-4269-b3fa-6061d518f9ee", "metadata": {}, "outputs": [ @@ -172,7 +193,7 @@ "-0.00013307075712109978" ] }, - "execution_count": 18, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -192,7 +213,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 14, "id": "02cfe01b-0b24-4723-a79b-d41ffb146bf9", "metadata": {}, "outputs": [], @@ -202,7 +223,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 15, "id": "466d1f9a-b707-4c05-a8af-5414d76bd8eb", "metadata": {}, "outputs": [], @@ -213,7 +234,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 16, "id": "dfdfc027-1608-43ad-9d15-0c649986eb73", "metadata": {}, "outputs": [], @@ -226,7 +247,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 17, "id": "db5c7cfe-b075-483e-8b7e-a58cebf1a782", "metadata": {}, "outputs": [ @@ -234,8 +255,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 12.5 ms, sys: 45.2 ms, total: 57.7 ms\n", - "Wall time: 63.3 ms\n" + "CPU times: user 8.76 ms, sys: 29.8 ms, total: 38.5 ms\n", + "Wall time: 38.1 ms\n" ] } ], @@ -246,7 +267,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 18, "id": "80155255-4dcf-48cb-9825-015da13d6ac0", "metadata": {}, "outputs": [], @@ -256,7 +277,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 19, "id": "6f7aff4e-9e89-459b-843f-46a4d4139bcf", "metadata": {}, "outputs": [ @@ -264,10 +285,10 @@ "data": { "text/plain": [ "{'status': [ReturnStatus(Code.DONE, None)],\n", - " 'output': []}" + " 'output': []}" ] }, - "execution_count": 59, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -278,7 +299,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 20, "id": "62ce8439-bf95-4818-b35c-b4e2ef649bd2", "metadata": {}, "outputs": [ @@ -288,7 +309,7 @@ "" ] }, - "execution_count": 60, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -299,17 +320,17 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 21, "id": "5bcd1b68-6a48-4a08-92d4-143419071618", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "15.040622767002787" + "15.664004709979054" ] }, - "execution_count": 61, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -320,17 +341,17 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 22, "id": "d21371e0-fa36-44bd-b7bf-a0092177ba17", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "3.111499245278537e-05" + "2.8318027034401894e-05" ] }, - "execution_count": 62, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -341,13 +362,13 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 23, "id": "9e06cd6d-e0f7-40dd-93f2-777f86ffe2eb", "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] @@ -362,14 +383,14 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 24, "id": "bb70a653-6231-4f4e-9bbe-279811acc895", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "d702f3bf2990434a85d0792cd2611c5f", + "model_id": "72151a9d47704d1fb535afa54a44cdda", "version_major": 2, "version_minor": 0 }, @@ -395,7 +416,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 25, "id": "f816e2af-0455-4e05-9c39-2e9f615d8f34", "metadata": {}, "outputs": [], @@ -405,7 +426,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 26, "id": "22314020-8f48-487b-a765-229a77d79a2f", "metadata": {}, "outputs": [], @@ -415,7 +436,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 27, "id": "ff411a05-82e1-4581-b06e-ab2fd7e0be3b", "metadata": {}, "outputs": [], @@ -425,7 +446,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 28, "id": "5574f0d5-d800-472a-9418-8c6ccc1e555b", "metadata": {}, "outputs": [], @@ -436,14 +457,14 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 29, "id": "9e02d6dd-0fa6-4dd6-a7ab-3e648958eb20", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "490fa227a15b47d9bfc0fa37ac79e59c", + "model_id": "cdde213a7a3445d08efacffc1c7b0581", "version_major": 2, "version_minor": 0 }, @@ -461,7 +482,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 30, "id": "663e4435-1cd0-4ce2-9593-85453f4c846a", "metadata": {}, "outputs": [], @@ -473,7 +494,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 31, "id": "37440e5a-75ff-4601-813a-f5c8df9413ad", "metadata": {}, "outputs": [], @@ -483,7 +504,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 32, "id": "a448ec7f-53bc-4d72-a8a7-f9392de9f3d5", "metadata": { "scrolled": true, @@ -495,12 +516,12 @@ "output_type": "stream", "text": [ " Step Time Energy fmax\n", - "GPMin: 0 17:12:33 11.122159 187.2462\n", - "GPMin: 1 17:12:33 -0.278268 1.5338\n", - "GPMin: 2 17:12:33 -0.996055 0.8010\n", - "GPMin: 3 17:12:33 -0.000000 0.0000\n", - "CPU times: user 76.8 ms, sys: 34.4 ms, total: 111 ms\n", - "Wall time: 61.9 ms\n" + "GPMin: 0 09:04:10 11.122159 187.2462\n", + "GPMin: 1 09:04:10 -0.278268 1.5338\n", + "GPMin: 2 09:04:10 -0.996055 0.8010\n", + "GPMin: 3 09:04:10 -0.000000 0.0000\n", + "CPU times: user 91 ms, sys: 45.6 ms, total: 137 ms\n", + "Wall time: 75.7 ms\n" ] } ], @@ -511,7 +532,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 33, "id": "5977dd10-c4cf-40c9-944e-5aa52cfa263d", "metadata": {}, "outputs": [ @@ -521,7 +542,7 @@ "(ReturnStatus(Code.DONE, None),)" ] }, - "execution_count": 73, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -532,7 +553,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 34, "id": "dd164778-634c-4785-903a-08a5243999ce", "metadata": {}, "outputs": [ @@ -542,7 +563,7 @@ "2.136147842601888e-07" ] }, - "execution_count": 74, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -553,7 +574,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 35, "id": "515ea06d-9026-4d9e-9df0-b9c249f0758a", "metadata": {}, "outputs": [ @@ -563,7 +584,7 @@ "" ] }, - "execution_count": 75, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -574,17 +595,17 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 36, "id": "52b7231f-8978-46ec-b698-ea8724a6fea3", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0.061767543986206874" + "0.07557120098499581" ] }, - "execution_count": 76, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -595,17 +616,17 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 37, "id": "c845430c-119d-4566-88e1-8465e378fde1", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "1.0585004929453135e-05" + "1.684704329818487e-05" ] }, - "execution_count": 77, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } @@ -616,7 +637,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 38, "id": "35291d7f-33a9-41ab-9b80-f052c5eb2e55", "metadata": {}, "outputs": [ @@ -637,7 +658,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 39, "id": "1d5b5203-d07f-485b-9553-9150f4a674e7", "metadata": {}, "outputs": [ @@ -651,7 +672,7 @@ " -3.560246436024868e-08]" ] }, - "execution_count": 79, + "execution_count": 39, "metadata": {}, "output_type": "execute_result" } @@ -662,14 +683,14 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 40, "id": "d2cc3b3a-5daa-49bb-9d6d-2994ebc74273", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "ae4da31a319e4baca99703ce308889d4", + "model_id": "63d6a4ee513142359ee6edc4754f18ab", "version_major": 2, "version_minor": 0 }, @@ -705,7 +726,7 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 41, "id": "4acdeafc-90b5-4b3f-9559-c74b9fa221ab", "metadata": {}, "outputs": [], @@ -715,7 +736,7 @@ }, { "cell_type": "code", - "execution_count": 82, + "execution_count": 42, "id": "f8cf3136-9b7c-4f1e-b630-962795527946", "metadata": {}, "outputs": [], @@ -727,17 +748,17 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 43, "id": "fef21aa4-d9f1-4d4a-8761-af1bc3121e5b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 83, + "execution_count": 43, "metadata": {}, "output_type": "execute_result" } @@ -748,7 +769,7 @@ }, { "cell_type": "code", - "execution_count": 84, + "execution_count": 44, "id": "41a68b17-c7c4-4a5f-8f04-11bee18fe55a", "metadata": {}, "outputs": [], @@ -758,7 +779,7 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": 45, "id": "fd107556-99b6-4042-9209-9412b4bbff94", "metadata": {}, "outputs": [ @@ -777,7 +798,7 @@ " 1.12355993, 1.12892306, 1.13423572, 1.13949907, 1.14471424])" ] }, - "execution_count": 85, + "execution_count": 45, "metadata": {}, "output_type": "execute_result" } @@ -788,7 +809,7 @@ }, { "cell_type": "code", - "execution_count": 86, + "execution_count": 46, "id": "0715614a-7284-4388-ac6b-c97bfedf7184", "metadata": {}, "outputs": [ @@ -798,7 +819,7 @@ "True" ] }, - "execution_count": 86, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } @@ -809,7 +830,7 @@ }, { "cell_type": "code", - "execution_count": 87, + "execution_count": 47, "id": "c2aa3093-1ea8-4099-bc14-be0c06e9d34b", "metadata": { "scrolled": true, @@ -822,7 +843,7 @@ }, { "cell_type": "code", - "execution_count": 88, + "execution_count": 48, "id": "be97853a-f182-4b4f-af74-7fd5a4fbd850", "metadata": {}, "outputs": [ @@ -832,7 +853,7 @@ "(ReturnStatus(Code.DONE, None),)" ] }, - "execution_count": 88, + "execution_count": 48, "metadata": {}, "output_type": "execute_result" } @@ -843,7 +864,7 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": 49, "id": "47f916ef-b140-49c5-adf1-93dca91b4540", "metadata": {}, "outputs": [ @@ -864,7 +885,7 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": 50, "id": "14162f5b-1318-4595-8c8c-d6346a21721d", "metadata": {}, "outputs": [ @@ -874,7 +895,7 @@ "0.6788586373205143" ] }, - "execution_count": 90, + "execution_count": 50, "metadata": {}, "output_type": "execute_result" } @@ -885,7 +906,7 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": 51, "id": "0f5ff296-df33-40d2-851b-02d6ded72dd6", "metadata": {}, "outputs": [ @@ -895,7 +916,7 @@ "0.6788586373205143" ] }, - "execution_count": 91, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -906,7 +927,7 @@ }, { "cell_type": "code", - "execution_count": 92, + "execution_count": 52, "id": "92b06330-b1fc-41d0-8bd8-bf1b11bf448c", "metadata": {}, "outputs": [ @@ -916,7 +937,7 @@ "Atoms(symbols='Fe', pbc=True, cell=[[-0.5536557129291797, 0.5536557129291797, 0.5536557129291797], [0.5536557129291797, -0.5536557129291797, 0.5536557129291797], [0.5536557129291797, 0.5536557129291797, -0.5536557129291797]])" ] }, - "execution_count": 92, + "execution_count": 52, "metadata": {}, "output_type": "execute_result" } @@ -935,7 +956,7 @@ }, { "cell_type": "code", - "execution_count": 93, + "execution_count": 53, "id": "89169376-be36-4ceb-9f4e-6e1f3247bc62", "metadata": {}, "outputs": [], @@ -945,7 +966,7 @@ }, { "cell_type": "code", - "execution_count": 94, + "execution_count": 54, "id": "2ee9f1d4-5b14-4340-98d4-4bd293af89a4", "metadata": {}, "outputs": [], @@ -957,17 +978,17 @@ }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 55, "id": "3d73a9de-7b4e-476a-b50a-ac6a3957a7ab", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 95, + "execution_count": 55, "metadata": {}, "output_type": "execute_result" } @@ -978,7 +999,7 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 56, "id": "0f075d90-e636-49be-b1a6-741a56363f54", "metadata": {}, "outputs": [], @@ -996,7 +1017,7 @@ }, { "cell_type": "code", - "execution_count": 97, + "execution_count": 57, "id": "58064e52-1c94-49fd-b38f-614cf6f19004", "metadata": {}, "outputs": [], @@ -1006,7 +1027,7 @@ }, { "cell_type": "code", - "execution_count": 98, + "execution_count": 58, "id": "d82a28ab-1a96-4a3a-8f79-5a875ac20788", "metadata": { "scrolled": true, @@ -1019,7 +1040,7 @@ }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 59, "id": "5472daed-f25c-4aab-b101-90c76a0235a5", "metadata": { "scrolled": true, @@ -1032,7 +1053,7 @@ "" ] }, - "execution_count": 99, + "execution_count": 59, "metadata": {}, "output_type": "execute_result" } @@ -1043,17 +1064,17 @@ }, { "cell_type": "code", - "execution_count": 100, + "execution_count": 60, "id": "a42865c9-8616-4335-8e46-ec4839daab0a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "30.503913552995073" + "27.43538186798105" ] }, - "execution_count": 100, + "execution_count": 60, "metadata": {}, "output_type": "execute_result" } @@ -1064,17 +1085,17 @@ }, { "cell_type": "code", - "execution_count": 101, + "execution_count": 61, "id": "d894a79f-a9e6-4667-9a9b-2bd9a088622f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "2.4366017896682024e-05" + "8.610018994659185e-06" ] }, - "execution_count": 101, + "execution_count": 61, "metadata": {}, "output_type": "execute_result" } @@ -1085,7 +1106,7 @@ }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 62, "id": "78017969-23fc-46f5-b99f-cd1d2dc74c00", "metadata": {}, "outputs": [ @@ -1106,7 +1127,7 @@ }, { "cell_type": "code", - "execution_count": 103, + "execution_count": 63, "id": "45162eb2-b23d-45c6-8aad-dfe9a6a484d1", "metadata": {}, "outputs": [ @@ -1116,7 +1137,7 @@ "0.6818586500998999" ] }, - "execution_count": 103, + "execution_count": 63, "metadata": {}, "output_type": "execute_result" } @@ -1127,7 +1148,7 @@ }, { "cell_type": "code", - "execution_count": 104, + "execution_count": 64, "id": "f0bcfe59-2168-4e74-9d7a-33d900368907", "metadata": {}, "outputs": [ @@ -1137,7 +1158,7 @@ "0.6818586500999" ] }, - "execution_count": 104, + "execution_count": 64, "metadata": {}, "output_type": "execute_result" } @@ -1156,7 +1177,7 @@ }, { "cell_type": "code", - "execution_count": 105, + "execution_count": 85, "id": "10f6c113-1e35-48f0-8878-291129bd8a60", "metadata": {}, "outputs": [], @@ -1166,7 +1187,7 @@ }, { "cell_type": "code", - "execution_count": 106, + "execution_count": 86, "id": "70832c31-040e-49be-b0f7-172f930cf31b", "metadata": {}, "outputs": [], @@ -1178,17 +1199,17 @@ }, { "cell_type": "code", - "execution_count": 107, + "execution_count": 87, "id": "94f4c51d-b69b-4477-a9db-d0ee7627cee6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 107, + "execution_count": 87, "metadata": {}, "output_type": "execute_result" } @@ -1199,12 +1220,12 @@ }, { "cell_type": "code", - "execution_count": 108, + "execution_count": 88, "id": "9a000824-0a9e-4395-8e07-00e484bc7937", "metadata": {}, "outputs": [], "source": [ - "m.input.set_strain_range(.7, 100)" + "m.input.set_strain_range(.5, 100)" ] }, { @@ -1217,7 +1238,7 @@ }, { "cell_type": "code", - "execution_count": 109, + "execution_count": 89, "id": "ca50857c-1c2f-4fad-a58c-16b399b8721d", "metadata": {}, "outputs": [], @@ -1229,7 +1250,7 @@ }, { "cell_type": "code", - "execution_count": 110, + "execution_count": 90, "id": "12f20f5c-27a3-4533-ad19-8ec06e1c8a90", "metadata": { "scrolled": true, @@ -1242,7 +1263,7 @@ }, { "cell_type": "code", - "execution_count": 111, + "execution_count": 91, "id": "2c5d8bea-49ec-4004-8a54-3ded7a3f413d", "metadata": {}, "outputs": [], @@ -1252,7 +1273,7 @@ }, { "cell_type": "code", - "execution_count": 112, + "execution_count": 92, "id": "ea7bcb58-0890-487e-bef5-bd3cb36143c1", "metadata": {}, "outputs": [ @@ -1262,7 +1283,7 @@ "" ] }, - "execution_count": 112, + "execution_count": 92, "metadata": {}, "output_type": "execute_result" } @@ -1273,17 +1294,17 @@ }, { "cell_type": "code", - "execution_count": 113, + "execution_count": 93, "id": "0b7c2912-6847-4262-a62d-7233ca398643", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "13.346773353026947" + "10.82231778698042" ] }, - "execution_count": 113, + "execution_count": 93, "metadata": {}, "output_type": "execute_result" } @@ -1294,17 +1315,17 @@ }, { "cell_type": "code", - "execution_count": 114, + "execution_count": 94, "id": "fd5ca921-2062-4f85-b014-382561e9893a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "1.0522984666749835e-05" + "5.160982254892588e-06" ] }, - "execution_count": 114, + "execution_count": 94, "metadata": {}, "output_type": "execute_result" } @@ -1315,13 +1336,13 @@ }, { "cell_type": "code", - "execution_count": 115, + "execution_count": 95, "id": "4d1223f7-2d72-413e-b20b-cf42781780bb", "metadata": {}, "outputs": [ { "data": { - "image/png": "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zk2aNMm49957e/wzJk+ebNx///2pLs1S+tpON954o/H973/fuO+++4ypU6emsULr6G1b/eEPfzAKCgqMo0ePZqI8y+htO/34xz82Jk6c2OXcQw89ZIwbNy5tNVqRJGP9+vWnvGbx4sXGpEmTupxbuHChcfHFF6exMmvpSTudTDa8n3fXm7YajO/pluxBiUQi2rZtm0pLS7ucLy0t1ebNm3v0M2KxmOrr6zV8+PB0lGgJfW2nJ554Qh9++KHuu+++dJdoGX1pq+eff17Tp0/X8uXLNXbsWJ155pn6zne+o+bm5kyUbIq+tNOsWbO0f/9+vfDCCzIMQ4cOHdJvfvMbXXvttZkoeUB57bXXTmjbq666Slu3blVra6tJVVlfNryf98dgfU+35M0Cjxw5omg0Kr/f3+W83+9XTU1Nj37GT37yEzU2NuqGG25IR4mW0Jd2ev/993Xvvffq1VdfldNpyf/9adGXttqzZ482bdokr9er9evX68iRI1q0aJGOHTs2aOeh9KWdZs2apaefflo33nijWlpa1NbWpuuuu04PP/xwJkoeUGpqak7atm1tbTpy5IjGjBljUmXWlg3v5301mN/TLdmDkmCz2bo8NwzjhHMn88wzz2jZsmVat26dRo8ena7yLKOn7RSNRrVgwQLdf//9OvPMMzNVnqX05m8qFovJZrPp6aef1owZM3TNNddoxYoVevLJJwd1L4rUu3bauXOn7rrrLv3gBz/Qtm3b9Mc//lFVVVW6/fbbM1HqgHOytj3ZecRl2/t5bwz293RLxq2RI0fK4XCc8C+22traE/710d26det066236rnnntOVV16ZzjJN19t2qq+v19atW7V9+3bdeeedkuIfwoZhyOl0asOGDfqHf/iHjNSeaX35mxozZozGjh3b5dbgZ599tgzD0P79+1VSUpLWms3Ql3aqqKjQ7Nmz9d3vfleSdN555ykvL0+XXnqp/u3f/o1egU4CgcBJ29bpdGrEiBEmVWVd2fR+3heD/T3dkj0obrdb06ZN08aNG7uc37hxo2bNmvWJ3/fMM8/olltu0dq1a7Ni/Lu37eTz+fTOO+/ozTffTB633367zjrrLL355puaOXNmpkrPuL78Tc2ePVsHDhxQQ0ND8tzu3btlt9s1bty4tNZrlr60U1NTk+z2rm8lDodDUkfvAOI+85nPnNC2GzZs0PTp0+VyuUyqypqy7f28Lwb9e7pZs3NP59lnnzVcLpfx+OOPGzt37jTKysqMvLw8Y+/evYZhGMa9995rfOUrX0lev3btWsPpdBo//elPjYMHDyaP48ePm/UrZERv26m7wTTj+3R621b19fXGuHHjjM9//vPGjh07jMrKSqOkpMS47bbbzPoVMqK37fTEE08YTqfTeOSRR4wPP/zQ2LRpkzF9+nRjxowZZv0KGVNfX29s377d2L59uyHJWLFihbF9+3bjo48+MgzjxLbas2ePkZuba9x9993Gzp07jccff9xwuVzGb37zG7N+hYzobTtl6/u5YfS+rbobTO/plg0ohmEYP/3pT40zzjjDcLvdxoUXXmhUVlYmX7v55puNOXPmJJ/PmTPHkHTCcfPNN2e+8AzrTTt1N5j+mHuit2313nvvGVdeeaWRk5NjjBs3zrjnnnuMpqamDFedeb1tp4ceesiYPHmykZOTY4wZM8a46aabjP3792e46sx76aWXTvm+c7K2evnll40LLrjAcLvdxoQJE4zVq1dnvvAM6207ZfP7eV/+pjobTO/pNsOgDxYAAFiLJeegAACA7EZAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlkNAAQAAlvP/AXuzOgpGI5yoAAAAAElFTkSuQmCC", + "image/png": "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", "text/plain": [ "
" ] @@ -1344,7 +1365,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 76, "id": "149c52b5-a0ce-4e6b-ba55-d94d33aa2f8a", "metadata": {}, "outputs": [], @@ -1354,7 +1375,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 77, "id": "aca24005-ea49-4389-bc26-f292fd0a75a2", "metadata": {}, "outputs": [], @@ -1371,7 +1392,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 78, "id": "4ae990bd-af18-4dae-8500-779c9509f3f6", "metadata": {}, "outputs": [], @@ -1381,7 +1402,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 79, "id": "fa62529f-45e6-4e2d-822d-b1cc433a7223", "metadata": {}, "outputs": [], @@ -1391,279 +1412,1382 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 80, "id": "0925864e-4dd1-4f4e-ace4-aac09c55e787", "metadata": { "scrolled": true, "tags": [] }, - "outputs": [], - "source": [ - "exe = m.run()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a3069fe3-93bf-4c83-93e7-6d0ac56d8248", - "metadata": {}, - "outputs": [], - "source": [ - "exe._run_machine.state" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "71bbb913-7d7a-4bb6-b775-3fbc8e7e1f35", - "metadata": {}, - "outputs": [], - "source": [ - "exe.status" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4bf2df15-31dc-474c-b3df-f7c32b0fdaf2", - "metadata": {}, - "outputs": [], - "source": [ - "exe.output[0].energies[:10]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "eb0a2daf-9dab-4174-bfee-0cd1ef8c474e", - "metadata": {}, - "outputs": [], - "source": [ - "exe.output[0].plot()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.0" - }, - "widgets": { - "application/vnd.jupyter.widget-state+json": { - "state": { - "07a1f180db7d4aa8affe9f48c66281a6": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ButtonModel", - "state": { - "icon": "compress", - "layout": "IPY_MODEL_e13a786ff807427fae43a5cfed219975", - "style": "IPY_MODEL_be2fba7fd3ff4b849be3f65d03ba13dc" - } - }, - "0bf198140cba45c0a145a68988e41219": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "LinkModel", - "state": { - "source": [ - "IPY_MODEL_36cfccf761b44baca72c5d53cf6cfc0a", - "max" - ], - "target": [ - "IPY_MODEL_ae4da31a319e4baca99703ce308889d4", - "max_frame" - ] - } - }, - "102b613a9c2a4eab96aeaf1a469ae6bb": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "176bb87615954634a7e91fc73bf93128": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "SliderStyleModel", - "state": { - "description_width": "" - } - }, - "17eef0435ab640e2aef097257a8b9899": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ImageModel", - "state": { - "layout": "IPY_MODEL_cdfea586c0fb4e44b9313a9e8cfbf525", - "width": "900.0" - } - }, - "18d0f24252dc44278939495d450dfd86": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ImageModel", - "state": { - "layout": "IPY_MODEL_24a18c81c2e04c21875e583889c838cc", - "width": "900.0" - } - }, - "1afea03bd47043fa9669604183c600f3": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ButtonStyleModel", - "state": {} - }, - "1d8983f2ad514ab990ac8a9c8ec9d8ed": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "description_width": "" - } - }, - "2131495c39cd4e688fb5ab9367bcc3d2": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "IntSliderModel", - "state": { - "layout": "IPY_MODEL_bdb15f0c4925488785b36853f2a507c4", - "max": 21, - "style": "IPY_MODEL_48ea0d9bc5d24e15997d34ae1f94b612" - } - }, - "232edc1031014bb48162c281fb240759": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "24a18c81c2e04c21875e583889c838cc": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "2aeb102499514aeeaa3ac02b9e7ae04b": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ButtonModel", - "state": { - "icon": "compress", - "layout": "IPY_MODEL_5c4b36124793443cad1938479a0b26b4", - "style": "IPY_MODEL_1afea03bd47043fa9669604183c600f3" + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:53 4.517693 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:53 4.251945 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:53 3.991875 0.0000\n", + "LBFGS: 0 09:04:53 4.789242 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 3.737364 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 3.488292 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 3.006013 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 3.244546 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 2.772582 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 2.544148 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 2.320604 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 2.101849 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 1.887783 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 1.272749 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 1.473327 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 1.678307 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 1.076481 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 0.696523 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 0.884435 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 0.512659 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 0.332761 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 0.156747 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 -0.015462 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 -0.183946 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 -0.348779 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 -0.510037 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:54 -0.667792 0.0000\n", + "LBFGS: 0 09:04:54 -0.822116 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -0.973078 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -1.120747 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -1.406469 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -1.265189 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -1.544652 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -1.811973 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -1.679799 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -1.941232 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -2.067636 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -2.191241 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -2.312104 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -2.430279 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -2.658780 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -2.545820 0.0000\n", + "LBFGS: 0 09:04:55 -2.769210 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -2.877160 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -2.982680 0.0000\n", + "LBFGS: 0 09:04:55 -3.186620 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -3.085817 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -3.285134 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -3.381404 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -3.475475 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:55 -3.567390 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -3.657192 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -3.744922 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -3.830620 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -3.914328 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -3.996084 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -4.075925 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -4.153891 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -4.230016 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -4.304338 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -4.376892 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -4.447711 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -4.516830 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -4.584282 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -4.650099 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -4.714313 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -4.838057 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -4.776956 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -4.897647 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -4.955755 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -5.012409 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -5.067638 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:56 -5.121469 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.173930 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.225046 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.274844 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.323350 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.370587 0.0000\n", + "LBFGS: 0 09:04:57 -5.416581 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.461356 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.504934 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.588595 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.547340 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.628722 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.667742 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.705677 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.742548 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.778375 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.813177 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.846976 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.879789 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.911636 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.942536 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -5.972506 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -6.001565 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -6.029730 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -6.057017 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:57 -6.083445 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.109029 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.133786 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.157731 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.180881 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.203250 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.245705 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.224853 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.265820 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.285213 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.303898 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.321888 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.339196 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.355836 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.371820 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.387162 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.401872 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.415965 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.442342 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.429451 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.454650 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.466386 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.477561 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.488186 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.498271 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.507828 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.516865 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.525395 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.533425 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.540966 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.548028 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.554620 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.560750 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.566429 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.571664 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.576465 0.0000\n", + "LBFGS: 0 09:04:58 -6.580840 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.584797 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:58 -6.588345 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.594245 0.0000\n", + "LBFGS: 0 09:04:59 -6.591492 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.596612 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.598601 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.601475 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.600220 0.0000\n", + "LBFGS: 0 09:04:59 -6.602374 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.602924 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.603132 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.602549 0.0000\n", + "LBFGS: 0 09:04:59 -6.603005 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.601771 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.600678 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.597570 0.0000\n", + "LBFGS: 0 09:04:59 -6.599275 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.595568 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.593275 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.590697 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.587840 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.584710 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.581312 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.577651 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.573734 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.569565 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.565149 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.560493 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.550475 0.0000\n", + "LBFGS: 0 09:04:59 -6.555599 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.545124 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.539551 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.533761 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.527759 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.521548 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.515134 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.508521 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.501712 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.494713 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.487527 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.480158 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:04:59 -6.472611 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.464889 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.440712 0.0000\n", + "LBFGS: 0 09:05:00 -6.448936 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.456996 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.432329 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.415096 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.423789 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.406254 0.0000\n", + "LBFGS: 0 09:05:00 -6.397266 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.388136 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.378866 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.369460 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.359921 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.350251 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.340455 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.330535 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.310333 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.320493 0.0000\n", + "LBFGS: 0 09:05:00 -6.300058 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.289669 0.0000\n", + "LBFGS: 0 09:05:00 -6.279171 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.268565 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.247042 0.0000\n", + "LBFGS: 0 09:05:00 -6.257855 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.236130 0.0000\n", + "LBFGS: 0 09:05:00 -6.225120 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.202819 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.214016 0.0000\n", + "LBFGS: 0 09:05:00 -6.180158 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.168698 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.191532 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.157154 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.145530 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:00 -6.133827 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -6.122047 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -6.110193 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -6.098266 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -6.086268 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -6.074202 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -6.062068 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -6.049871 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -6.037610 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -6.025288 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -6.000468 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -6.012907 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.987974 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.975426 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.962825 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.950174 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.937473 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.924725 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.911931 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.899093 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.886212 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.873290 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.860327 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.847326 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.821215 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.834289 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.808107 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.794966 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.781794 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.755359 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.768591 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.742099 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.728813 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.715501 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.702165 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.688805 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.675424 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.662022 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.648600 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:01 -5.635159 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.621701 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.608226 0.0000\n", + "LBFGS: 0 09:05:02 -5.594735 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.581230 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.567712 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.554180 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.540637 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.527083 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.513519 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.499947 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.486366 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.459183 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.472778 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.431978 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.445583 0.0000\n", + "LBFGS: 0 09:05:02 -5.418369 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.404757 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.391143 0.0000\n", + "LBFGS: 0 09:05:02 -5.377527 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.363910 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.350293 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.336676 0.0000\n", + "LBFGS: 0 09:05:02 -5.309448 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.323061 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.295837 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.268625 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.282229 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.255026 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.241432 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.227844 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.214262 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.200687 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.187119 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.160009 0.0000\n", + "LBFGS: 0 09:05:02 -5.173560 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:02 -5.146468 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -5.119414 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -5.132936 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -5.092403 0.0000\n", + "LBFGS: 0 09:05:03 -5.105903 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -5.065440 0.0000\n", + "LBFGS: 0 09:05:03 -5.078915 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -5.051977 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -5.038527 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -5.025090 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -5.011668 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.984867 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.998260 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.958128 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.971490 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.944782 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.931453 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.918140 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.904845 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.891567 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.878308 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.865067 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.851844 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.838641 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.825456 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.812292 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.799147 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.786023 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.772919 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.759837 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.746775 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.733735 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.720716 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.707720 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.694745 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.681794 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.668865 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.643076 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.630216 0.0000\n", + "LBFGS: 0 09:05:03 -4.655959 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:03 -4.617381 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.604569 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.579018 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.591781 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.566279 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.540876 0.0000\n", + "LBFGS: 0 09:05:04 -4.553565 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.528212 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.515574 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.490373 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.502960 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.477811 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.452766 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.440283 0.0000\n", + "LBFGS: 0 09:05:04 -4.465275 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.427826 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.415396 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.402992 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.390616 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.378267 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.365944 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.353649 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.341382 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.329142 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.316929 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.304745 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.292588 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.280459 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.268359 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.256286 0.0000\n", + "LBFGS: 0 09:05:04 -4.244242 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.232226 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.220239 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.208280 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.196350 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.184448 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:04 -4.172575 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -4.160732 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -4.148917 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -4.137131 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -4.125374 0.0000\n", + "LBFGS: 0 09:05:05 -4.113646 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -4.101948 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -4.090278 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -4.067028 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -4.055446 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -4.078638 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -4.043894 0.0000\n", + "LBFGS: 0 09:05:05 -4.032372 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -4.020879 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -4.009416 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.997982 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.975204 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.986578 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.952544 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.963859 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.941259 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.918778 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.930004 0.0000\n", + "LBFGS: 0 09:05:05 -3.896416 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.907582 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.885280 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.874174 0.0000\n", + "LBFGS: 0 09:05:05 -3.852051 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.863097 0.0000\n", + "LBFGS: 0 09:05:05 -3.841034 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.819091 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.808164 0.0000\n", + "LBFGS: 0 09:05:05 -3.830047 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.797267 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:05 -3.786400 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.775562 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.753978 0.0000\n", + "LBFGS: 0 09:05:06 -3.764755 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.743230 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.732513 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.711167 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.721825 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.700539 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.689941 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.679373 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.668834 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.658325 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.647846 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.637397 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.626978 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.616588 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.606228 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.595897 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.585597 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.575325 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.544689 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.565084 0.0000\n", + "LBFGS: 0 09:05:06 -3.554872 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.534536 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.524412 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.504253 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.514318 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.494218 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.484212 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.474235 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.464287 0.0000\n", + "LBFGS: 0 09:05:06 -3.454369 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.434619 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.444480 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.424788 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.405213 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:06 -3.414986 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.395469 0.0000\n", + "LBFGS: 0 09:05:07 -3.385754 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.376068 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.347181 0.0000\n", + "LBFGS: 0 09:05:07 -3.366410 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.356781 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.337610 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.309067 0.0000\n", + "LBFGS: 0 09:05:07 -3.328067 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.318553 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.299610 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.290181 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.271409 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.280781 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.262065 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.252749 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.243462 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.224971 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.234202 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.215768 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.206592 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.188325 0.0000\n", + "LBFGS: 0 09:05:07 -3.197445 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.179233 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.170169 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.161132 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.143141 0.0000\n", + "LBFGS: 0 09:05:07 -3.152123 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.134187 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.116361 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.107488 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.125260 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.098643 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.089825 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:07 -3.081034 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -3.072271 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -3.063534 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -3.054823 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -3.046140 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -3.037483 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -3.028853 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -3.020250 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -3.011673 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -3.003123 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.994599 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.986101 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.977630 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.969185 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.960766 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.952373 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.944006 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.935665 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.927350 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.919060 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.910796 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.902558 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.894346 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.886159 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.877998 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.869862 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.861751 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.853666 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.845605 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.837570 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.829560 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.821575 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.813615 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 09:05:08 -2.805679 0.0000\n" + ] + } + ], + "source": [ + "exe = m.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "a3069fe3-93bf-4c83-93e7-6d0ac56d8248", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 81, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_machine.state" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "71bbb913-7d7a-4bb6-b775-3fbc8e7e1f35", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None),)" + ] + }, + "execution_count": 82, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.status" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "4bf2df15-31dc-474c-b3df-f7c32b0fdaf2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([4.78924238, 4.51769267, 4.25194477, 3.99187529, 3.7373637 ,\n", + " 3.48829227, 3.244546 , 3.00601254, 2.77258214, 2.54414756])" + ] + }, + "execution_count": 83, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].energies[:10]" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "eb0a2daf-9dab-4174-bfee-0cd1ef8c474e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "exe.output[0].plot()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.0" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": { + "00a17ba0b124451cbde0fad3b84353ee": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "icon": "compress", + "layout": "IPY_MODEL_06890714f26b4a38bc114f4555c24bfa", + "style": "IPY_MODEL_910eb9e2748644ae844251d1462540df" } }, - "31de7259a7fb47dda852aa5690daff26": { + "024eb52716ed4318b0626147bf790cf1": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": {} + }, + "0596ea65e79842aa8da3e12a4dd4c324": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "06890714f26b4a38bc114f4555c24bfa": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "width": "34px" + } + }, + "0a5483f8e9f14cb0a2c84098493c66e7": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "LinkModel", "state": { "source": [ - "IPY_MODEL_4d1e30940a9f4d39ad539132bb9e3a97", + "IPY_MODEL_c70e261d6cf44f32bf2b6dae9c2030bc", "value" ], "target": [ - "IPY_MODEL_577bee1eabef4c21b01870d31b31b844", + "IPY_MODEL_81ab0afcff0549fe9cdc34038bcb8065", "value" ] } }, - "36cfccf761b44baca72c5d53cf6cfc0a": { + "0aa308d445ae4fa3a87e2d31352aaf75": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_a13a50d58406485cbfa71d192fdc504d", + "max" + ], + "target": [ + "IPY_MODEL_72151a9d47704d1fb535afa54a44cdda", + "max_frame" + ] + } + }, + "0cb88e7cb2924370b51b3475a2ecedd3": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "28677f6679f04d989db938d88030310a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": {} + }, + "2964c4b4b96e47ed977a65d55c4c30f8": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "PlayModel", "state": { - "layout": "IPY_MODEL_d2bc4378320e47da9447235f5ba8c172", + "layout": "IPY_MODEL_59c8fe8d50dc434d97ad407aa794ae9d", "max": 4, - "style": "IPY_MODEL_bb0156e2570c453fab53e820ff281a50" + "style": "IPY_MODEL_c8b8572514b34b33aff97adc70ec50d6" } }, - "38329f9971164ed8acc6f05286ec0b87": { - "model_module": "nglview-js-widgets", - "model_module_version": "3.0.1", - "model_name": "ColormakerRegistryModel", + "2bf88d342df54490a8d562803f726d8b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "2d621d97ad4840f08a792eef56349b30": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "SliderStyleModel", "state": { - "_msg_ar": [], - "_msg_q": [], - "_ready": false, - "layout": "IPY_MODEL_99b7a7163688495ca026df2229a46c1d" + "description_width": "" } }, - "40fe843dee8545819f229f799259f716": { + "3006a5ee838c43d2aafb422a89033515": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "PlayModel", + "model_name": "HBoxModel", "state": { - "layout": "IPY_MODEL_7060f26e34204b15ac60179586b326c6", - "max": 21, - "style": "IPY_MODEL_1d8983f2ad514ab990ac8a9c8ec9d8ed" + "children": [ + "IPY_MODEL_2964c4b4b96e47ed977a65d55c4c30f8", + "IPY_MODEL_f42dbefe1afa44ceac086275a099407a" + ], + "layout": "IPY_MODEL_0596ea65e79842aa8da3e12a4dd4c324" + } + }, + "31e5c0c3795a498d87c8a8b1d8327917": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "331e65cb645e4261b0d521035d6f9fa8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "37d7c2ab2a74486fb7304bffbca48859": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "3b60682933684f1ea5058a3211578972": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "460d462ed7a64f8085e25f5d5c223ea9": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ImageModel", + "state": { + "layout": "IPY_MODEL_9df118d922724f7f984c7a2a354672c4", + "width": "900.0" + } + }, + "46480e766c6b4c43bcad9aef7493a9fc": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_78a1f9a2fe23401bb112cf028369300d", + "max" + ], + "target": [ + "IPY_MODEL_72151a9d47704d1fb535afa54a44cdda", + "max_frame" + ] + } + }, + "47298cbae5234c2ba97fc00f30f32fbd": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "width": "34px" + } + }, + "4d0edbaec6bb442e9d6618b698ced009": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "537d9b3a42ff4c4b9069f347889c6a6c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_c70e261d6cf44f32bf2b6dae9c2030bc", + "max" + ], + "target": [ + "IPY_MODEL_cdde213a7a3445d08efacffc1c7b0581", + "max_frame" + ] + } + }, + "539b9bb00350429dac26a298efa0c127": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ImageModel", + "state": { + "layout": "IPY_MODEL_4d0edbaec6bb442e9d6618b698ced009", + "width": "900.0" } }, - "44791309f79943f08eeb4bdd781d7c16": { + "580fe06b91ad41eca7674506eb879c54": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "59c8fe8d50dc434d97ad407aa794ae9d": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "608e267572af412bab629eb7c94fa567": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "LinkModel", + "model_name": "SliderStyleModel", "state": { - "source": [ - "IPY_MODEL_4d1e30940a9f4d39ad539132bb9e3a97", - "value" - ], - "target": [ - "IPY_MODEL_490fa227a15b47d9bfc0fa37ac79e59c", - "frame" - ] + "description_width": "" } }, - "48ea0d9bc5d24e15997d34ae1f94b612": { + "62964029f705409193860678497ca49f": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "SliderStyleModel", + "model_name": "ImageModel", "state": { - "description_width": "" + "layout": "IPY_MODEL_580fe06b91ad41eca7674506eb879c54", + "width": "900.0" } }, - "490fa227a15b47d9bfc0fa37ac79e59c": { + "63d6a4ee513142359ee6edc4754f18ab": { "model_module": "nglview-js-widgets", "model_module_version": "3.0.1", "model_name": "NGLModel", "state": { "_camera_orientation": [ - 14, - 0, + 11.096625056567845, 0, 0, 0, - 14, 0, + 11.096625056567845, 0, 0, 0, - 14, 0, + 11.096625056567845, 0, 0, 0, + -0.375, 1 ], "_camera_str": "orthographic", "_gui_theme": null, - "_ibtn_fullscreen": "IPY_MODEL_07a1f180db7d4aa8affe9f48c66281a6", + "_ibtn_fullscreen": "IPY_MODEL_8cb24cb6978f4c998d27b645ea5757fd", "_igui": null, - "_iplayer": "IPY_MODEL_b7fbffee80154c97bedfd8966a43bfc0", + "_iplayer": "IPY_MODEL_3006a5ee838c43d2aafb422a89033515", "_ngl_color_dict": {}, "_ngl_coordinate_resource": {}, "_ngl_full_stage_parameters": { @@ -1696,13 +2820,14 @@ "args": [ { "binary": false, - "data": "CRYST1 10.000 10.000 10.000 90.00 90.00 90.00 P 1\nMODEL 1\nATOM 0 Fe Fe 0 0.000 0.000 0.000 1.00 0.00 Fe \nATOM 1 Fe Fe 1 0.000 0.000 0.750 1.00 0.00 Fe \nENDMDL \n", + "data": "MODEL 1\nATOM 1 Fe MOL 1 0.000 0.000 0.000 1.00 0.00 FE \nATOM 2 Fe MOL 1 0.000 0.000 0.750 1.00 0.00 FE \nENDMDL\n", "type": "blob" } ], "kwargs": { "defaultRepresentation": true, - "ext": "pdb" + "ext": "pdb", + "name": "nglview.adaptor.ASETrajectory" }, "methodName": "loadFile", "reconstruc_color_scheme": false, @@ -1715,10 +2840,10 @@ ], "component_index": 0, "kwargs": { - "colorScheme": "element", - "radius": 0.7099019513592786, + "radius": 0.5, "radiusType": "vdw", - "sele": "#Fe" + "scale": 0.5, + "sele": "all" }, "methodName": "addRepresentation", "reconstruc_color_scheme": false, @@ -1748,222 +2873,15 @@ "reconstruc_color_scheme": false, "target": "compList", "type": "call_method" - }, - { - "args": [ - "shape", - [ - [ - "arrow", - [ - -1, - -1, - -1 - ], - [ - 0, - -1, - -1 - ], - [ - 1, - 0, - 0 - ], - 0.1 - ] - ] - ], - "fire_embed": true, - "kwargs": {}, - "methodName": "addShape", - "reconstruc_color_scheme": false, - "target": "Widget", - "type": "call_method" - }, - { - "args": [ - "shape", - [ - [ - "text", - [ - 0, - -1, - -1 - ], - [ - 0, - 0, - 0 - ], - 1, - "x" - ] - ] - ], - "fire_embed": true, - "kwargs": {}, - "methodName": "addShape", - "reconstruc_color_scheme": false, - "target": "Widget", - "type": "call_method" - }, - { - "args": [ - "shape", - [ - [ - "arrow", - [ - -1, - -1, - -1 - ], - [ - -1, - 0, - -1 - ], - [ - 0, - 1, - 0 - ], - 0.1 - ] - ] - ], - "fire_embed": true, - "kwargs": {}, - "methodName": "addShape", - "reconstruc_color_scheme": false, - "target": "Widget", - "type": "call_method" - }, - { - "args": [ - "shape", - [ - [ - "text", - [ - -1, - 0, - -1 - ], - [ - 0, - 0, - 0 - ], - 1, - "y" - ] - ] - ], - "fire_embed": true, - "kwargs": {}, - "methodName": "addShape", - "reconstruc_color_scheme": false, - "target": "Widget", - "type": "call_method" - }, - { - "args": [ - "shape", - [ - [ - "arrow", - [ - -1, - -1, - -1 - ], - [ - -1, - -1, - 0 - ], - [ - 0, - 0, - 1 - ], - 0.1 - ] - ] - ], - "fire_embed": true, - "kwargs": {}, - "methodName": "addShape", - "reconstruc_color_scheme": false, - "target": "Widget", - "type": "call_method" - }, - { - "args": [ - "shape", - [ - [ - "text", - [ - -1, - -1, - 0 - ], - [ - 0, - 0, - 0 - ], - 1, - "z" - ] - ] - ], - "fire_embed": true, - "kwargs": {}, - "methodName": "addShape", - "reconstruc_color_scheme": false, - "target": "Widget", - "type": "call_method" - }, - { - "args": [], - "kwargs": { - "cameraType": "orthographic" - }, - "methodName": "setParameters", - "reconstruc_color_scheme": false, - "target": "Stage", - "type": "call_method" - }, - { - "args": [ - [ - 14, - 0, - 0, - 0, - 0, - 14, - 0, - 0, - 0, - 0, - 14, - 0, - 0, - 0, - 0, - 14 - ] - ], - "kwargs": {}, - "methodName": "orient", + }, + { + "args": [], + "kwargs": { + "cameraType": "orthographic" + }, + "methodName": "setParameters", "reconstruc_color_scheme": false, - "target": "viewerControls", + "target": "Stage", "type": "call_method" } ], @@ -1975,255 +2893,28 @@ "cameraFov": 40, "cameraType": "perspective", "clipDist": 10, - "clipFar": 100, - "clipNear": 0, - "fogFar": 100, - "fogNear": 50, - "hoverTimeout": 0, - "impostor": true, - "lightColor": 14540253, - "lightIntensity": 1, - "mousePreset": "default", - "panSpeed": 1, - "quality": "medium", - "rotateSpeed": 2, - "sampleLevel": 0, - "tooltip": true, - "workerDefault": true, - "zoomSpeed": 1.2 - }, - "_ngl_repr_dict": { - "0": { - "0": { - "params": { - "assembly": "default", - "clipCenter": { - "x": 0, - "y": 0, - "z": 0 - }, - "clipNear": 0, - "clipRadius": 0, - "colorMode": "hcl", - "colorReverse": false, - "colorScale": "", - "colorScheme": "element", - "colorValue": 9474192, - "defaultAssembly": "", - "depthWrite": true, - "diffuse": 16777215, - "diffuseInterior": false, - "disableImpostor": false, - "disablePicking": false, - "flatShaded": false, - "interiorColor": 2236962, - "interiorDarkening": 0, - "lazy": false, - "matrix": { - "elements": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1 - ] - }, - "metalness": 0, - "opacity": 1, - "quality": "medium", - "radiusData": {}, - "radiusScale": 1, - "radiusSize": 0.7099019513592786, - "radiusType": "size", - "roughness": 0.4, - "sele": "#Fe", - "side": "double", - "sphereDetail": 1, - "useInteriorColor": true, - "visible": true, - "wireframe": false - }, - "type": "spacefill" - }, - "1": { - "params": { - "clipCenter": { - "x": 0, - "y": 0, - "z": 0 - }, - "clipNear": 0, - "clipRadius": 0, - "colorMode": "hcl", - "colorReverse": false, - "colorScale": "", - "colorScheme": "element", - "colorValue": "orange", - "defaultAssembly": "", - "depthWrite": true, - "diffuse": 16777215, - "diffuseInterior": false, - "disableImpostor": false, - "disablePicking": false, - "flatShaded": false, - "interiorColor": 2236962, - "interiorDarkening": 0, - "lazy": false, - "matrix": { - "elements": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1 - ] - }, - "metalness": 0, - "opacity": 1, - "quality": "medium", - "radialSegments": 10, - "radiusData": {}, - "radiusScale": 1, - "radiusSize": 0.049999997805194256, - "radiusType": "vdw", - "roughness": 0.4, - "sele": "all", - "side": "double", - "sphereDetail": 1, - "useInteriorColor": true, - "visible": true, - "wireframe": false - }, - "type": "unitcell" - } - }, - "1": { - "0": { - "params": { - "clipCenter": { - "x": 0, - "y": 0, - "z": 0 - }, - "clipNear": 0, - "clipRadius": 0, - "colorReverse": false, - "depthWrite": true, - "diffuse": 16777215, - "diffuseInterior": false, - "disablePicking": false, - "flatShaded": false, - "interiorColor": 2236962, - "interiorDarkening": 0, - "lazy": false, - "matrix": { - "elements": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1 - ] - }, - "metalness": 0, - "opacity": 1, - "quality": "medium", - "roughness": 0.4, - "side": "double", - "useInteriorColor": false, - "visible": true, - "wireframe": false - }, - "type": "buffer" - } - }, - "2": { - "0": { - "params": { - "clipCenter": { - "x": 0, - "y": 0, - "z": 0 - }, - "clipNear": 0, - "clipRadius": 0, - "colorReverse": false, - "depthWrite": true, - "diffuse": 16777215, - "diffuseInterior": false, - "disablePicking": false, - "flatShaded": false, - "interiorColor": 2236962, - "interiorDarkening": 0, - "lazy": false, - "matrix": { - "elements": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1 - ] - }, - "metalness": 0, - "opacity": 1, - "quality": "medium", - "roughness": 0.4, - "side": "double", - "useInteriorColor": false, - "visible": true, - "wireframe": false - }, - "type": "buffer" - } - }, - "3": { + "clipFar": 100, + "clipNear": 0, + "fogFar": 100, + "fogNear": 50, + "hoverTimeout": 0, + "impostor": true, + "lightColor": 14540253, + "lightIntensity": 1, + "mousePreset": "default", + "panSpeed": 1, + "quality": "medium", + "rotateSpeed": 2, + "sampleLevel": 0, + "tooltip": true, + "workerDefault": true, + "zoomSpeed": 1.2 + }, + "_ngl_repr_dict": { + "0": { "0": { "params": { + "assembly": "default", "clipCenter": { "x": 0, "y": 0, @@ -2231,10 +2922,16 @@ }, "clipNear": 0, "clipRadius": 0, + "colorMode": "hcl", "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": 9474192, + "defaultAssembly": "", "depthWrite": true, "diffuse": 16777215, "diffuseInterior": false, + "disableImpostor": false, "disablePicking": false, "flatShaded": false, "interiorColor": 2236962, @@ -2263,17 +2960,21 @@ "metalness": 0, "opacity": 1, "quality": "medium", + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.5, + "radiusType": "size", "roughness": 0.4, + "sele": "all", "side": "double", - "useInteriorColor": false, + "sphereDetail": 1, + "useInteriorColor": true, "visible": true, "wireframe": false }, - "type": "buffer" - } - }, - "4": { - "0": { + "type": "spacefill" + }, + "1": { "params": { "clipCenter": { "x": 0, @@ -2282,10 +2983,16 @@ }, "clipNear": 0, "clipRadius": 0, + "colorMode": "hcl", "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": "orange", + "defaultAssembly": "", "depthWrite": true, "diffuse": 16777215, "diffuseInterior": false, + "disableImpostor": false, "disablePicking": false, "flatShaded": false, "interiorColor": 2236962, @@ -2314,18 +3021,207 @@ "metalness": 0, "opacity": 1, "quality": "medium", + "radialSegments": 10, + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.5, + "radiusType": "vdw", "roughness": 0.4, + "sele": "all", "side": "double", - "useInteriorColor": false, + "sphereDetail": 1, + "useInteriorColor": true, "visible": true, "wireframe": false }, - "type": "buffer" + "type": "unitcell" } + } + }, + "_ngl_serialize": false, + "_ngl_version": "2.0.0-dev.36", + "_ngl_view_id": [ + "28B9672B-4897-4070-9AC6-9C4B4F2AAC73" + ], + "_player_dict": {}, + "_scene_position": {}, + "_scene_rotation": {}, + "_synced_model_ids": [], + "_synced_repr_model_ids": [], + "_view_height": "", + "_view_width": "", + "background": "white", + "frame": 0, + "gui_style": null, + "layout": "IPY_MODEL_0cb88e7cb2924370b51b3475a2ecedd3", + "max_frame": 4, + "n_components": 1, + "picked": {} + } + }, + "68940526b8cf4c2997207324c7e7888b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "SliderStyleModel", + "state": { + "description_width": "" + } + }, + "72151a9d47704d1fb535afa54a44cdda": { + "model_module": "nglview-js-widgets", + "model_module_version": "3.0.1", + "model_name": "NGLModel", + "state": { + "_camera_orientation": [ + 16.91904731270948, + 0, + 0, + 0, + 0, + 16.91904731270948, + 0, + 0, + 0, + 0, + 16.91904731270948, + 0, + -1.5, + -1.5, + -1.5, + 1 + ], + "_camera_str": "orthographic", + "_gui_theme": null, + "_ibtn_fullscreen": "IPY_MODEL_7970f82f392f44379408589deaaf3ff5", + "_igui": null, + "_iplayer": "IPY_MODEL_ba5a5d97828140d5bc2678f11310a4b5", + "_ngl_color_dict": {}, + "_ngl_coordinate_resource": {}, + "_ngl_full_stage_parameters": { + "ambientColor": 14540253, + "ambientIntensity": 0.2, + "backgroundColor": "white", + "cameraEyeSep": 0.3, + "cameraFov": 40, + "cameraType": "orthographic", + "clipDist": 10, + "clipFar": 100, + "clipNear": 0, + "fogFar": 100, + "fogNear": 50, + "hoverTimeout": 0, + "impostor": true, + "lightColor": 14540253, + "lightIntensity": 1, + "mousePreset": "default", + "panSpeed": 1, + "quality": "medium", + "rotateSpeed": 2, + "sampleLevel": 0, + "tooltip": true, + "workerDefault": true, + "zoomSpeed": 1.2 + }, + "_ngl_msg_archive": [ + { + "args": [ + { + "binary": false, + "data": "CRYST1 3.600 3.600 3.600 90.00 90.00 90.00 P 1\nMODEL 1\nATOM 1 Fe MOL 1 0.000 0.000 0.000 1.00 0.00 FE \nATOM 2 Fe MOL 1 0.600 0.600 0.600 1.00 0.00 FE \nATOM 3 Fe MOL 1 0.000 0.000 1.200 1.00 0.00 FE \nATOM 4 Fe MOL 1 0.600 0.600 1.800 1.00 0.00 FE \nATOM 5 Fe MOL 1 0.000 0.000 2.400 1.00 0.00 FE \nATOM 6 Fe MOL 1 0.600 0.600 3.000 1.00 0.00 FE \nATOM 7 Fe MOL 1 0.000 1.200 0.000 1.00 0.00 FE \nATOM 8 Fe MOL 1 0.600 1.800 0.600 1.00 0.00 FE \nATOM 9 Fe MOL 1 0.000 1.200 1.200 1.00 0.00 FE \nATOM 10 Fe MOL 1 0.600 1.800 1.800 1.00 0.00 FE \nATOM 11 Fe MOL 1 0.000 1.200 2.400 1.00 0.00 FE \nATOM 12 Fe MOL 1 0.600 1.800 3.000 1.00 0.00 FE \nATOM 13 Fe MOL 1 0.000 2.400 0.000 1.00 0.00 FE \nATOM 14 Fe MOL 1 0.600 3.000 0.600 1.00 0.00 FE \nATOM 15 Fe MOL 1 0.000 2.400 1.200 1.00 0.00 FE \nATOM 16 Fe MOL 1 0.600 3.000 1.800 1.00 0.00 FE \nATOM 17 Fe MOL 1 0.000 2.400 2.400 1.00 0.00 FE \nATOM 18 Fe MOL 1 0.600 3.000 3.000 1.00 0.00 FE \nATOM 19 Fe MOL 1 1.200 0.000 0.000 1.00 0.00 FE \nATOM 20 Fe MOL 1 1.800 0.600 0.600 1.00 0.00 FE \nATOM 21 Fe MOL 1 1.200 0.000 1.200 1.00 0.00 FE \nATOM 22 Fe MOL 1 1.800 0.600 1.800 1.00 0.00 FE \nATOM 23 Fe MOL 1 1.200 0.000 2.400 1.00 0.00 FE \nATOM 24 Fe MOL 1 1.800 0.600 3.000 1.00 0.00 FE \nATOM 25 Fe MOL 1 1.200 1.200 0.000 1.00 0.00 FE \nATOM 26 Fe MOL 1 1.800 1.800 0.600 1.00 0.00 FE \nATOM 27 Fe MOL 1 1.200 1.200 1.200 1.00 0.00 FE \nATOM 28 Fe MOL 1 1.800 1.800 1.800 1.00 0.00 FE \nATOM 29 Fe MOL 1 1.200 1.200 2.400 1.00 0.00 FE \nATOM 30 Fe MOL 1 1.800 1.800 3.000 1.00 0.00 FE \nATOM 31 Fe MOL 1 1.200 2.400 0.000 1.00 0.00 FE \nATOM 32 Fe MOL 1 1.800 3.000 0.600 1.00 0.00 FE \nATOM 33 Fe MOL 1 1.200 2.400 1.200 1.00 0.00 FE \nATOM 34 Fe MOL 1 1.800 3.000 1.800 1.00 0.00 FE \nATOM 35 Fe MOL 1 1.200 2.400 2.400 1.00 0.00 FE \nATOM 36 Fe MOL 1 1.800 3.000 3.000 1.00 0.00 FE \nATOM 37 Fe MOL 1 2.400 0.000 0.000 1.00 0.00 FE \nATOM 38 Fe MOL 1 3.000 0.600 0.600 1.00 0.00 FE \nATOM 39 Fe MOL 1 2.400 0.000 1.200 1.00 0.00 FE \nATOM 40 Fe MOL 1 3.000 0.600 1.800 1.00 0.00 FE \nATOM 41 Fe MOL 1 2.400 0.000 2.400 1.00 0.00 FE \nATOM 42 Fe MOL 1 3.000 0.600 3.000 1.00 0.00 FE \nATOM 43 Fe MOL 1 2.400 1.200 0.000 1.00 0.00 FE \nATOM 44 Fe MOL 1 3.000 1.800 0.600 1.00 0.00 FE \nATOM 45 Fe MOL 1 2.400 1.200 1.200 1.00 0.00 FE \nATOM 46 Fe MOL 1 3.000 1.800 1.800 1.00 0.00 FE \nATOM 47 Fe MOL 1 2.400 1.200 2.400 1.00 0.00 FE \nATOM 48 Fe MOL 1 3.000 1.800 3.000 1.00 0.00 FE \nATOM 49 Fe MOL 1 2.400 2.400 0.000 1.00 0.00 FE \nATOM 50 Fe MOL 1 3.000 3.000 0.600 1.00 0.00 FE \nATOM 51 Fe MOL 1 2.400 2.400 1.200 1.00 0.00 FE \nATOM 52 Fe MOL 1 3.000 3.000 1.800 1.00 0.00 FE \nATOM 53 Fe MOL 1 2.400 2.400 2.400 1.00 0.00 FE \nATOM 54 Fe MOL 1 3.000 3.000 3.000 1.00 0.00 FE \nENDMDL\n", + "type": "blob" + } + ], + "kwargs": { + "defaultRepresentation": true, + "ext": "pdb", + "name": "nglview.adaptor.ASETrajectory" + }, + "methodName": "loadFile", + "reconstruc_color_scheme": false, + "target": "Stage", + "type": "call_method" }, - "5": { + { + "args": [ + "spacefill" + ], + "component_index": 0, + "kwargs": { + "radius": 0.5, + "radiusType": "vdw", + "scale": 0.5, + "sele": "all" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [ + "ball+stick", + 0 + ], + "kwargs": {}, + "methodName": "removeRepresentationsByName", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [ + "unitcell" + ], + "component_index": 0, + "kwargs": { + "sele": "all" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [], + "kwargs": { + "cameraType": "orthographic" + }, + "methodName": "setParameters", + "reconstruc_color_scheme": false, + "target": "Stage", + "type": "call_method" + } + ], + "_ngl_original_stage_parameters": { + "ambientColor": 14540253, + "ambientIntensity": 0.2, + "backgroundColor": "white", + "cameraEyeSep": 0.3, + "cameraFov": 40, + "cameraType": "perspective", + "clipDist": 10, + "clipFar": 100, + "clipNear": 0, + "fogFar": 100, + "fogNear": 50, + "hoverTimeout": 0, + "impostor": true, + "lightColor": 14540253, + "lightIntensity": 1, + "mousePreset": "default", + "panSpeed": 1, + "quality": "medium", + "rotateSpeed": 2, + "sampleLevel": 0, + "tooltip": true, + "workerDefault": true, + "zoomSpeed": 1.2 + }, + "_ngl_repr_dict": { + "0": { "0": { "params": { + "assembly": "default", "clipCenter": { "x": 0, "y": 0, @@ -2333,10 +3229,16 @@ }, "clipNear": 0, "clipRadius": 0, + "colorMode": "hcl", "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": 9474192, + "defaultAssembly": "", "depthWrite": true, "diffuse": 16777215, "diffuseInterior": false, + "disableImpostor": false, "disablePicking": false, "flatShaded": false, "interiorColor": 2236962, @@ -2365,17 +3267,21 @@ "metalness": 0, "opacity": 1, "quality": "medium", + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.5, + "radiusType": "size", "roughness": 0.4, + "sele": "all", "side": "double", - "useInteriorColor": false, + "sphereDetail": 1, + "useInteriorColor": true, "visible": true, "wireframe": false }, - "type": "buffer" - } - }, - "6": { - "0": { + "type": "spacefill" + }, + "1": { "params": { "clipCenter": { "x": 0, @@ -2384,10 +3290,16 @@ }, "clipNear": 0, "clipRadius": 0, + "colorMode": "hcl", "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": "orange", + "defaultAssembly": "", "depthWrite": true, "diffuse": 16777215, "diffuseInterior": false, + "disableImpostor": false, "disablePicking": false, "flatShaded": false, "interiorColor": 2236962, @@ -2416,20 +3328,27 @@ "metalness": 0, "opacity": 1, "quality": "medium", + "radialSegments": 10, + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.017999999209869933, + "radiusType": "vdw", "roughness": 0.4, + "sele": "all", "side": "double", - "useInteriorColor": false, + "sphereDetail": 1, + "useInteriorColor": true, "visible": true, "wireframe": false }, - "type": "buffer" + "type": "unitcell" } } }, "_ngl_serialize": false, "_ngl_version": "2.0.0-dev.36", "_ngl_view_id": [ - "667690D3-6FEE-45C1-8831-CE7922A214C1" + "AEE7E543-F286-4AAC-843E-55903D8E906C" ], "_player_dict": {}, "_scene_position": {}, @@ -2441,88 +3360,95 @@ "background": "white", "frame": 0, "gui_style": null, - "layout": "IPY_MODEL_abed0a7e394b4d96a23e5e49c6550396", - "max_frame": 0, - "n_components": 7, + "layout": "IPY_MODEL_b9ab72d5e1de4c7d99fc1f3fc22216da", + "max_frame": 21, + "n_components": 1, "picked": {} } }, - "4a437568edb6486bb6f42d78cb503321": { + "7518d4e0d82142ee9dd604b8cff07a07": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "LinkModel", "state": { "source": [ - "IPY_MODEL_40fe843dee8545819f229f799259f716", - "value" + "IPY_MODEL_2964c4b4b96e47ed977a65d55c4c30f8", + "max" ], "target": [ - "IPY_MODEL_2131495c39cd4e688fb5ab9367bcc3d2", - "value" + "IPY_MODEL_63d6a4ee513142359ee6edc4754f18ab", + "max_frame" ] } }, - "4d1e30940a9f4d39ad539132bb9e3a97": { + "78a1f9a2fe23401bb112cf028369300d": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "PlayModel", + "model_name": "IntSliderModel", "state": { - "layout": "IPY_MODEL_90a56f475dff4490bed56c96a0cb2d24", - "max": 0, - "style": "IPY_MODEL_fde0b8d494b14f87b50672b32c077416" + "layout": "IPY_MODEL_37d7c2ab2a74486fb7304bffbca48859", + "max": 21, + "style": "IPY_MODEL_68940526b8cf4c2997207324c7e7888b" } }, - "535dd339949e422cbcc7361d221197a1": { + "7970f82f392f44379408589deaaf3ff5": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "LinkModel", + "model_name": "ButtonModel", "state": { - "source": [ - "IPY_MODEL_36cfccf761b44baca72c5d53cf6cfc0a", - "value" - ], - "target": [ - "IPY_MODEL_ae4da31a319e4baca99703ce308889d4", - "frame" - ] + "icon": "compress", + "layout": "IPY_MODEL_47298cbae5234c2ba97fc00f30f32fbd", + "style": "IPY_MODEL_28677f6679f04d989db938d88030310a" } }, - "554ad1cbd4cb49178f7fb82699279805": { + "81ab0afcff0549fe9cdc34038bcb8065": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "IntSliderModel", "state": { - "layout": "IPY_MODEL_5de42c821a114c07beeee90ceb543a62", - "max": 4, - "style": "IPY_MODEL_72de8832cfa341e7be1c3e8af9ef5836" + "layout": "IPY_MODEL_3b60682933684f1ea5058a3211578972", + "max": 0, + "style": "IPY_MODEL_608e267572af412bab629eb7c94fa567" + } + }, + "8cb24cb6978f4c998d27b645ea5757fd": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "icon": "compress", + "layout": "IPY_MODEL_95a007abd96a4cf88dc9d1972bfcb885", + "style": "IPY_MODEL_024eb52716ed4318b0626147bf790cf1" } }, - "56b6a1023dd748c5b2ad01fb5b538763": { + "8fcd49b7e0254e05b76f005d4d3ab56f": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "910eb9e2748644ae844251d1462540df": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": {} + }, + "9161bdfbd4154693b32bb3ead65d3db6": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "LinkModel", "state": { "source": [ - "IPY_MODEL_4d1e30940a9f4d39ad539132bb9e3a97", + "IPY_MODEL_f42dbefe1afa44ceac086275a099407a", "max" ], "target": [ - "IPY_MODEL_490fa227a15b47d9bfc0fa37ac79e59c", + "IPY_MODEL_63d6a4ee513142359ee6edc4754f18ab", "max_frame" ] } }, - "577bee1eabef4c21b01870d31b31b844": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "IntSliderModel", - "state": { - "layout": "IPY_MODEL_92762f6dd2a34d95a4ffdba0937f6e07", - "max": 0, - "style": "IPY_MODEL_176bb87615954634a7e91fc73bf93128" - } - }, - "5c4b36124793443cad1938479a0b26b4": { + "95a007abd96a4cf88dc9d1972bfcb885": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", @@ -2530,114 +3456,143 @@ "width": "34px" } }, - "5de42c821a114c07beeee90ceb543a62": { + "9df118d922724f7f984c7a2a354672c4": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, - "650ffcac60dd466d99a3636f9aceb344": { + "a13a50d58406485cbfa71d192fdc504d": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "ImageModel", + "model_name": "PlayModel", "state": { - "layout": "IPY_MODEL_e7187596a3b845d2b7f369c7fa1201c7", - "width": "900.0" + "layout": "IPY_MODEL_afd6af21b4a148edb9dd992bd5fa132e", + "max": 21, + "style": "IPY_MODEL_d7bba2a76a9647f7a7788f0c6b8fbab7" } }, - "6a9347cdf7904fc2a5875e6a6dcbae6e": { + "a14ed0d326274c498b91e78604b4fccd": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "HBoxModel", + "model_name": "LinkModel", "state": { - "children": [ - "IPY_MODEL_36cfccf761b44baca72c5d53cf6cfc0a", - "IPY_MODEL_554ad1cbd4cb49178f7fb82699279805" + "source": [ + "IPY_MODEL_81ab0afcff0549fe9cdc34038bcb8065", + "max" ], - "layout": "IPY_MODEL_bdb611883eb04a1eb9a640d4fcbf633c" + "target": [ + "IPY_MODEL_cdde213a7a3445d08efacffc1c7b0581", + "max_frame" + ] } }, - "7060f26e34204b15ac60179586b326c6": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "72de8832cfa341e7be1c3e8af9ef5836": { + "a776064405af4084b8591f82b2e1fcdf": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "SliderStyleModel", + "model_name": "LinkModel", "state": { - "description_width": "" + "source": [ + "IPY_MODEL_c70e261d6cf44f32bf2b6dae9c2030bc", + "value" + ], + "target": [ + "IPY_MODEL_cdde213a7a3445d08efacffc1c7b0581", + "frame" + ] } }, - "90a56f475dff4490bed56c96a0cb2d24": { + "afd6af21b4a148edb9dd992bd5fa132e": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, - "92762f6dd2a34d95a4ffdba0937f6e07": { + "b36c0c9105454af78422278b6847d006": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, - "99b7a7163688495ca026df2229a46c1d": { + "b9ab72d5e1de4c7d99fc1f3fc22216da": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, - "9f5126572d5042239621624f1c52e83b": { + "ba5a5d97828140d5bc2678f11310a4b5": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "children": [ + "IPY_MODEL_a13a50d58406485cbfa71d192fdc504d", + "IPY_MODEL_78a1f9a2fe23401bb112cf028369300d" + ], + "layout": "IPY_MODEL_b36c0c9105454af78422278b6847d006" + } + }, + "c70e261d6cf44f32bf2b6dae9c2030bc": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "PlayModel", + "state": { + "layout": "IPY_MODEL_d0d4bd7a685c42ada61108ebc1b2d2cf", + "max": 0, + "style": "IPY_MODEL_e75a2359044347888b56002f945db287" + } + }, + "c8b8572514b34b33aff97adc70ec50d6": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "description_width": "" + } + }, + "c8cc0fe989a74fe290ab251b3afda8ba": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "LinkModel", "state": { "source": [ - "IPY_MODEL_40fe843dee8545819f229f799259f716", + "IPY_MODEL_a13a50d58406485cbfa71d192fdc504d", "value" ], "target": [ - "IPY_MODEL_d702f3bf2990434a85d0792cd2611c5f", - "frame" + "IPY_MODEL_78a1f9a2fe23401bb112cf028369300d", + "value" ] } }, - "abed0a7e394b4d96a23e5e49c6550396": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "ae4da31a319e4baca99703ce308889d4": { + "cdde213a7a3445d08efacffc1c7b0581": { "model_module": "nglview-js-widgets", "model_module_version": "3.0.1", "model_name": "NGLModel", "state": { "_camera_orientation": [ - 11.096625056567845, + 14, 0, 0, 0, 0, - 11.096625056567845, + 14, 0, 0, 0, 0, - 11.096625056567845, + 14, + 0, 0, 0, 0, - -0.375, 1 ], "_camera_str": "orthographic", "_gui_theme": null, - "_ibtn_fullscreen": "IPY_MODEL_f34e98e4c72945838d54a52a5e081315", + "_ibtn_fullscreen": "IPY_MODEL_00a17ba0b124451cbde0fad3b84353ee", "_igui": null, - "_iplayer": "IPY_MODEL_6a9347cdf7904fc2a5875e6a6dcbae6e", + "_iplayer": "IPY_MODEL_e517781696d84f9aad35efa9ad443846", "_ngl_color_dict": {}, "_ngl_coordinate_resource": {}, "_ngl_full_stage_parameters": { @@ -2670,58 +3625,237 @@ "args": [ { "binary": false, - "data": "MODEL 1\nATOM 1 Fe MOL 1 0.000 0.000 0.000 1.00 0.00 FE \nATOM 2 Fe MOL 1 0.000 0.000 0.750 1.00 0.00 FE \nENDMDL\n", + "data": "CRYST1 10.000 10.000 10.000 90.00 90.00 90.00 P 1\nMODEL 1\nATOM 0 Fe Fe 0 0.000 0.000 0.000 1.00 0.00 Fe \nATOM 1 Fe Fe 1 0.000 0.000 0.750 1.00 0.00 Fe \nENDMDL \n", "type": "blob" } ], - "kwargs": { - "defaultRepresentation": true, - "ext": "pdb", - "name": "nglview.adaptor.ASETrajectory" - }, - "methodName": "loadFile", + "kwargs": { + "defaultRepresentation": true, + "ext": "pdb" + }, + "methodName": "loadFile", + "reconstruc_color_scheme": false, + "target": "Stage", + "type": "call_method" + }, + { + "args": [ + "spacefill" + ], + "component_index": 0, + "kwargs": { + "colorScheme": "element", + "radius": 0.7099019513592786, + "radiusType": "vdw", + "sele": "#Fe" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [ + "ball+stick", + 0 + ], + "kwargs": {}, + "methodName": "removeRepresentationsByName", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [ + "unitcell" + ], + "component_index": 0, + "kwargs": { + "sele": "all" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [ + "shape", + [ + [ + "arrow", + [ + -1, + -1, + -1 + ], + [ + 0, + -1, + -1 + ], + [ + 1, + 0, + 0 + ], + 0.1 + ] + ] + ], + "fire_embed": true, + "kwargs": {}, + "methodName": "addShape", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [ + "shape", + [ + [ + "text", + [ + 0, + -1, + -1 + ], + [ + 0, + 0, + 0 + ], + 1, + "x" + ] + ] + ], + "fire_embed": true, + "kwargs": {}, + "methodName": "addShape", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [ + "shape", + [ + [ + "arrow", + [ + -1, + -1, + -1 + ], + [ + -1, + 0, + -1 + ], + [ + 0, + 1, + 0 + ], + 0.1 + ] + ] + ], + "fire_embed": true, + "kwargs": {}, + "methodName": "addShape", "reconstruc_color_scheme": false, - "target": "Stage", + "target": "Widget", "type": "call_method" }, { "args": [ - "spacefill" + "shape", + [ + [ + "text", + [ + -1, + 0, + -1 + ], + [ + 0, + 0, + 0 + ], + 1, + "y" + ] + ] ], - "component_index": 0, - "kwargs": { - "radius": 0.5, - "radiusType": "vdw", - "scale": 0.5, - "sele": "all" - }, - "methodName": "addRepresentation", + "fire_embed": true, + "kwargs": {}, + "methodName": "addShape", "reconstruc_color_scheme": false, - "target": "compList", + "target": "Widget", "type": "call_method" }, { "args": [ - "ball+stick", - 0 + "shape", + [ + [ + "arrow", + [ + -1, + -1, + -1 + ], + [ + -1, + -1, + 0 + ], + [ + 0, + 0, + 1 + ], + 0.1 + ] + ] ], + "fire_embed": true, "kwargs": {}, - "methodName": "removeRepresentationsByName", + "methodName": "addShape", "reconstruc_color_scheme": false, "target": "Widget", "type": "call_method" }, { "args": [ - "unitcell" + "shape", + [ + [ + "text", + [ + -1, + -1, + 0 + ], + [ + 0, + 0, + 0 + ], + 1, + "z" + ] + ] ], - "component_index": 0, - "kwargs": { - "sele": "all" - }, - "methodName": "addRepresentation", + "fire_embed": true, + "kwargs": {}, + "methodName": "addShape", "reconstruc_color_scheme": false, - "target": "compList", + "target": "Widget", "type": "call_method" }, { @@ -2733,6 +3867,33 @@ "reconstruc_color_scheme": false, "target": "Stage", "type": "call_method" + }, + { + "args": [ + [ + 14, + 0, + 0, + 0, + 0, + 14, + 0, + 0, + 0, + 0, + 14, + 0, + 0, + 0, + 0, + 14 + ] + ], + "kwargs": {}, + "methodName": "orient", + "reconstruc_color_scheme": false, + "target": "viewerControls", + "type": "call_method" } ], "_ngl_original_stage_parameters": { @@ -2781,7 +3942,126 @@ "depthWrite": true, "diffuse": 16777215, "diffuseInterior": false, - "disableImpostor": false, + "disableImpostor": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.7099019513592786, + "radiusType": "size", + "roughness": 0.4, + "sele": "#Fe", + "side": "double", + "sphereDetail": 1, + "useInteriorColor": true, + "visible": true, + "wireframe": false + }, + "type": "spacefill" + }, + "1": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorMode": "hcl", + "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": "orange", + "defaultAssembly": "", + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disableImpostor": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "radialSegments": 10, + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.049999997805194256, + "radiusType": "vdw", + "roughness": 0.4, + "sele": "all", + "side": "double", + "sphereDetail": 1, + "useInteriorColor": true, + "visible": true, + "wireframe": false + }, + "type": "unitcell" + } + }, + "1": { + "0": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorReverse": false, + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, "disablePicking": false, "flatShaded": false, "interiorColor": 2236962, @@ -2810,21 +4090,17 @@ "metalness": 0, "opacity": 1, "quality": "medium", - "radiusData": {}, - "radiusScale": 1, - "radiusSize": 0.5, - "radiusType": "size", "roughness": 0.4, - "sele": "all", "side": "double", - "sphereDetail": 1, - "useInteriorColor": true, + "useInteriorColor": false, "visible": true, "wireframe": false }, - "type": "spacefill" - }, - "1": { + "type": "buffer" + } + }, + "2": { + "0": { "params": { "clipCenter": { "x": 0, @@ -2833,16 +4109,10 @@ }, "clipNear": 0, "clipRadius": 0, - "colorMode": "hcl", "colorReverse": false, - "colorScale": "", - "colorScheme": "element", - "colorValue": "orange", - "defaultAssembly": "", "depthWrite": true, "diffuse": 16777215, "diffuseInterior": false, - "disableImpostor": false, "disablePicking": false, "flatShaded": false, "interiorColor": 2236962, @@ -2871,323 +4141,120 @@ "metalness": 0, "opacity": 1, "quality": "medium", - "radialSegments": 10, - "radiusData": {}, - "radiusScale": 1, - "radiusSize": 0.5, - "radiusType": "vdw", "roughness": 0.4, - "sele": "all", "side": "double", - "sphereDetail": 1, - "useInteriorColor": true, + "useInteriorColor": false, "visible": true, "wireframe": false }, - "type": "unitcell" + "type": "buffer" } - } - }, - "_ngl_serialize": false, - "_ngl_version": "2.0.0-dev.36", - "_ngl_view_id": [ - "94D4F05B-4B91-4EBB-AD85-EC9DEB97D514" - ], - "_player_dict": {}, - "_scene_position": {}, - "_scene_rotation": {}, - "_synced_model_ids": [], - "_synced_repr_model_ids": [], - "_view_height": "", - "_view_width": "", - "background": "white", - "frame": 0, - "gui_style": null, - "layout": "IPY_MODEL_232edc1031014bb48162c281fb240759", - "max_frame": 4, - "n_components": 1, - "picked": {} - } - }, - "af4e2d19c99f4c449bb13afe7baa471d": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "LinkModel", - "state": { - "source": [ - "IPY_MODEL_2131495c39cd4e688fb5ab9367bcc3d2", - "max" - ], - "target": [ - "IPY_MODEL_d702f3bf2990434a85d0792cd2611c5f", - "max_frame" - ] - } - }, - "b174844c258f40a881d42ddd952c95fc": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "LinkModel", - "state": { - "source": [ - "IPY_MODEL_577bee1eabef4c21b01870d31b31b844", - "max" - ], - "target": [ - "IPY_MODEL_490fa227a15b47d9bfc0fa37ac79e59c", - "max_frame" - ] - } - }, - "b4038c5bf965445c93b0d0e697c2830b": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "LinkModel", - "state": { - "source": [ - "IPY_MODEL_554ad1cbd4cb49178f7fb82699279805", - "max" - ], - "target": [ - "IPY_MODEL_ae4da31a319e4baca99703ce308889d4", - "max_frame" - ] - } - }, - "b7fbffee80154c97bedfd8966a43bfc0": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "children": [ - "IPY_MODEL_4d1e30940a9f4d39ad539132bb9e3a97", - "IPY_MODEL_577bee1eabef4c21b01870d31b31b844" - ], - "layout": "IPY_MODEL_102b613a9c2a4eab96aeaf1a469ae6bb" - } - }, - "bb0156e2570c453fab53e820ff281a50": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "description_width": "" - } - }, - "bdb15f0c4925488785b36853f2a507c4": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "bdb611883eb04a1eb9a640d4fcbf633c": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "be2fba7fd3ff4b849be3f65d03ba13dc": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ButtonStyleModel", - "state": {} - }, - "c6f4e62a00074fd9b9a26d86059fdfdf": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "LinkModel", - "state": { - "source": [ - "IPY_MODEL_36cfccf761b44baca72c5d53cf6cfc0a", - "value" - ], - "target": [ - "IPY_MODEL_554ad1cbd4cb49178f7fb82699279805", - "value" - ] - } - }, - "c9bb8f068708498a8963f68d1f5e21c9": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "cdfea586c0fb4e44b9313a9e8cfbf525": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "d2bc4378320e47da9447235f5ba8c172": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "d5347f1a62b84865a7375f54f807ca33": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "width": "34px" - } - }, - "d702f3bf2990434a85d0792cd2611c5f": { - "model_module": "nglview-js-widgets", - "model_module_version": "3.0.1", - "model_name": "NGLModel", - "state": { - "_camera_orientation": [ - 16.91904731270948, - 0, - 0, - 0, - 0, - 16.91904731270948, - 0, - 0, - 0, - 0, - 16.91904731270948, - 0, - -1.5, - -1.5, - -1.5, - 1 - ], - "_camera_str": "orthographic", - "_gui_theme": null, - "_ibtn_fullscreen": "IPY_MODEL_2aeb102499514aeeaa3ac02b9e7ae04b", - "_igui": null, - "_iplayer": "IPY_MODEL_d840e72db72e40b1926c1d6a46a0ebcf", - "_ngl_color_dict": {}, - "_ngl_coordinate_resource": {}, - "_ngl_full_stage_parameters": { - "ambientColor": 14540253, - "ambientIntensity": 0.2, - "backgroundColor": "white", - "cameraEyeSep": 0.3, - "cameraFov": 40, - "cameraType": "orthographic", - "clipDist": 10, - "clipFar": 100, - "clipNear": 0, - "fogFar": 100, - "fogNear": 50, - "hoverTimeout": 0, - "impostor": true, - "lightColor": 14540253, - "lightIntensity": 1, - "mousePreset": "default", - "panSpeed": 1, - "quality": "medium", - "rotateSpeed": 2, - "sampleLevel": 0, - "tooltip": true, - "workerDefault": true, - "zoomSpeed": 1.2 - }, - "_ngl_msg_archive": [ - { - "args": [ - { - "binary": false, - "data": "CRYST1 3.600 3.600 3.600 90.00 90.00 90.00 P 1\nMODEL 1\nATOM 1 Fe MOL 1 0.000 0.000 0.000 1.00 0.00 FE \nATOM 2 Fe MOL 1 0.600 0.600 0.600 1.00 0.00 FE \nATOM 3 Fe MOL 1 0.000 0.000 1.200 1.00 0.00 FE \nATOM 4 Fe MOL 1 0.600 0.600 1.800 1.00 0.00 FE \nATOM 5 Fe MOL 1 0.000 0.000 2.400 1.00 0.00 FE \nATOM 6 Fe MOL 1 0.600 0.600 3.000 1.00 0.00 FE \nATOM 7 Fe MOL 1 0.000 1.200 0.000 1.00 0.00 FE \nATOM 8 Fe MOL 1 0.600 1.800 0.600 1.00 0.00 FE \nATOM 9 Fe MOL 1 0.000 1.200 1.200 1.00 0.00 FE \nATOM 10 Fe MOL 1 0.600 1.800 1.800 1.00 0.00 FE \nATOM 11 Fe MOL 1 0.000 1.200 2.400 1.00 0.00 FE \nATOM 12 Fe MOL 1 0.600 1.800 3.000 1.00 0.00 FE \nATOM 13 Fe MOL 1 0.000 2.400 0.000 1.00 0.00 FE \nATOM 14 Fe MOL 1 0.600 3.000 0.600 1.00 0.00 FE \nATOM 15 Fe MOL 1 0.000 2.400 1.200 1.00 0.00 FE \nATOM 16 Fe MOL 1 0.600 3.000 1.800 1.00 0.00 FE \nATOM 17 Fe MOL 1 0.000 2.400 2.400 1.00 0.00 FE \nATOM 18 Fe MOL 1 0.600 3.000 3.000 1.00 0.00 FE \nATOM 19 Fe MOL 1 1.200 0.000 0.000 1.00 0.00 FE \nATOM 20 Fe MOL 1 1.800 0.600 0.600 1.00 0.00 FE \nATOM 21 Fe MOL 1 1.200 0.000 1.200 1.00 0.00 FE \nATOM 22 Fe MOL 1 1.800 0.600 1.800 1.00 0.00 FE \nATOM 23 Fe MOL 1 1.200 0.000 2.400 1.00 0.00 FE \nATOM 24 Fe MOL 1 1.800 0.600 3.000 1.00 0.00 FE \nATOM 25 Fe MOL 1 1.200 1.200 0.000 1.00 0.00 FE \nATOM 26 Fe MOL 1 1.800 1.800 0.600 1.00 0.00 FE \nATOM 27 Fe MOL 1 1.200 1.200 1.200 1.00 0.00 FE \nATOM 28 Fe MOL 1 1.800 1.800 1.800 1.00 0.00 FE \nATOM 29 Fe MOL 1 1.200 1.200 2.400 1.00 0.00 FE \nATOM 30 Fe MOL 1 1.800 1.800 3.000 1.00 0.00 FE \nATOM 31 Fe MOL 1 1.200 2.400 0.000 1.00 0.00 FE \nATOM 32 Fe MOL 1 1.800 3.000 0.600 1.00 0.00 FE \nATOM 33 Fe MOL 1 1.200 2.400 1.200 1.00 0.00 FE \nATOM 34 Fe MOL 1 1.800 3.000 1.800 1.00 0.00 FE \nATOM 35 Fe MOL 1 1.200 2.400 2.400 1.00 0.00 FE \nATOM 36 Fe MOL 1 1.800 3.000 3.000 1.00 0.00 FE \nATOM 37 Fe MOL 1 2.400 0.000 0.000 1.00 0.00 FE \nATOM 38 Fe MOL 1 3.000 0.600 0.600 1.00 0.00 FE \nATOM 39 Fe MOL 1 2.400 0.000 1.200 1.00 0.00 FE \nATOM 40 Fe MOL 1 3.000 0.600 1.800 1.00 0.00 FE \nATOM 41 Fe MOL 1 2.400 0.000 2.400 1.00 0.00 FE \nATOM 42 Fe MOL 1 3.000 0.600 3.000 1.00 0.00 FE \nATOM 43 Fe MOL 1 2.400 1.200 0.000 1.00 0.00 FE \nATOM 44 Fe MOL 1 3.000 1.800 0.600 1.00 0.00 FE \nATOM 45 Fe MOL 1 2.400 1.200 1.200 1.00 0.00 FE \nATOM 46 Fe MOL 1 3.000 1.800 1.800 1.00 0.00 FE \nATOM 47 Fe MOL 1 2.400 1.200 2.400 1.00 0.00 FE \nATOM 48 Fe MOL 1 3.000 1.800 3.000 1.00 0.00 FE \nATOM 49 Fe MOL 1 2.400 2.400 0.000 1.00 0.00 FE \nATOM 50 Fe MOL 1 3.000 3.000 0.600 1.00 0.00 FE \nATOM 51 Fe MOL 1 2.400 2.400 1.200 1.00 0.00 FE \nATOM 52 Fe MOL 1 3.000 3.000 1.800 1.00 0.00 FE \nATOM 53 Fe MOL 1 2.400 2.400 2.400 1.00 0.00 FE \nATOM 54 Fe MOL 1 3.000 3.000 3.000 1.00 0.00 FE \nENDMDL\n", - "type": "blob" - } - ], - "kwargs": { - "defaultRepresentation": true, - "ext": "pdb", - "name": "nglview.adaptor.ASETrajectory" - }, - "methodName": "loadFile", - "reconstruc_color_scheme": false, - "target": "Stage", - "type": "call_method" - }, - { - "args": [ - "spacefill" - ], - "component_index": 0, - "kwargs": { - "radius": 0.5, - "radiusType": "vdw", - "scale": 0.5, - "sele": "all" - }, - "methodName": "addRepresentation", - "reconstruc_color_scheme": false, - "target": "compList", - "type": "call_method" }, - { - "args": [ - "ball+stick", - 0 - ], - "kwargs": {}, - "methodName": "removeRepresentationsByName", - "reconstruc_color_scheme": false, - "target": "Widget", - "type": "call_method" + "3": { + "0": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorReverse": false, + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "roughness": 0.4, + "side": "double", + "useInteriorColor": false, + "visible": true, + "wireframe": false + }, + "type": "buffer" + } }, - { - "args": [ - "unitcell" - ], - "component_index": 0, - "kwargs": { - "sele": "all" - }, - "methodName": "addRepresentation", - "reconstruc_color_scheme": false, - "target": "compList", - "type": "call_method" + "4": { + "0": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorReverse": false, + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "roughness": 0.4, + "side": "double", + "useInteriorColor": false, + "visible": true, + "wireframe": false + }, + "type": "buffer" + } }, - { - "args": [], - "kwargs": { - "cameraType": "orthographic" - }, - "methodName": "setParameters", - "reconstruc_color_scheme": false, - "target": "Stage", - "type": "call_method" - } - ], - "_ngl_original_stage_parameters": { - "ambientColor": 14540253, - "ambientIntensity": 0.2, - "backgroundColor": "white", - "cameraEyeSep": 0.3, - "cameraFov": 40, - "cameraType": "perspective", - "clipDist": 10, - "clipFar": 100, - "clipNear": 0, - "fogFar": 100, - "fogNear": 50, - "hoverTimeout": 0, - "impostor": true, - "lightColor": 14540253, - "lightIntensity": 1, - "mousePreset": "default", - "panSpeed": 1, - "quality": "medium", - "rotateSpeed": 2, - "sampleLevel": 0, - "tooltip": true, - "workerDefault": true, - "zoomSpeed": 1.2 - }, - "_ngl_repr_dict": { - "0": { + "5": { "0": { "params": { - "assembly": "default", "clipCenter": { "x": 0, "y": 0, @@ -3195,16 +4262,10 @@ }, "clipNear": 0, "clipRadius": 0, - "colorMode": "hcl", "colorReverse": false, - "colorScale": "", - "colorScheme": "element", - "colorValue": 9474192, - "defaultAssembly": "", "depthWrite": true, "diffuse": 16777215, "diffuseInterior": false, - "disableImpostor": false, "disablePicking": false, "flatShaded": false, "interiorColor": 2236962, @@ -3233,21 +4294,17 @@ "metalness": 0, "opacity": 1, "quality": "medium", - "radiusData": {}, - "radiusScale": 1, - "radiusSize": 0.5, - "radiusType": "size", "roughness": 0.4, - "sele": "all", "side": "double", - "sphereDetail": 1, - "useInteriorColor": true, + "useInteriorColor": false, "visible": true, "wireframe": false }, - "type": "spacefill" - }, - "1": { + "type": "buffer" + } + }, + "6": { + "0": { "params": { "clipCenter": { "x": 0, @@ -3256,16 +4313,10 @@ }, "clipNear": 0, "clipRadius": 0, - "colorMode": "hcl", "colorReverse": false, - "colorScale": "", - "colorScheme": "element", - "colorValue": "orange", - "defaultAssembly": "", "depthWrite": true, "diffuse": 16777215, "diffuseInterior": false, - "disableImpostor": false, "disablePicking": false, "flatShaded": false, "interiorColor": 2236962, @@ -3294,27 +4345,20 @@ "metalness": 0, "opacity": 1, "quality": "medium", - "radialSegments": 10, - "radiusData": {}, - "radiusScale": 1, - "radiusSize": 0.017999999209869933, - "radiusType": "vdw", "roughness": 0.4, - "sele": "all", "side": "double", - "sphereDetail": 1, - "useInteriorColor": true, + "useInteriorColor": false, "visible": true, "wireframe": false }, - "type": "unitcell" + "type": "buffer" } } }, "_ngl_serialize": false, "_ngl_version": "2.0.0-dev.36", "_ngl_view_id": [ - "F3DFC146-3E56-4815-8862-4714ED3F6F6F" + "EEA5383B-89E4-42F3-A148-FC24CADE8089" ], "_player_dict": {}, "_scene_position": {}, @@ -3326,81 +4370,110 @@ "background": "white", "frame": 0, "gui_style": null, - "layout": "IPY_MODEL_c9bb8f068708498a8963f68d1f5e21c9", - "max_frame": 21, - "n_components": 1, + "layout": "IPY_MODEL_2bf88d342df54490a8d562803f726d8b", + "max_frame": 0, + "n_components": 7, "picked": {} } }, - "d7837d55497b4e9b894e8a310288b9b1": { + "d0d4bd7a685c42ada61108ebc1b2d2cf": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "d3dba68b040c436a87aa1acf4b90a1c9": { + "model_module": "nglview-js-widgets", + "model_module_version": "3.0.1", + "model_name": "ColormakerRegistryModel", + "state": { + "_msg_ar": [], + "_msg_q": [], + "_ready": true, + "layout": "IPY_MODEL_8fcd49b7e0254e05b76f005d4d3ab56f" + } + }, + "d7bba2a76a9647f7a7788f0c6b8fbab7": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "ButtonStyleModel", - "state": {} + "model_name": "DescriptionStyleModel", + "state": { + "description_width": "" + } + }, + "e479162c7f164eb28512cf35cef91c2f": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_2964c4b4b96e47ed977a65d55c4c30f8", + "value" + ], + "target": [ + "IPY_MODEL_f42dbefe1afa44ceac086275a099407a", + "value" + ] + } }, - "d840e72db72e40b1926c1d6a46a0ebcf": { + "e517781696d84f9aad35efa9ad443846": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HBoxModel", "state": { "children": [ - "IPY_MODEL_40fe843dee8545819f229f799259f716", - "IPY_MODEL_2131495c39cd4e688fb5ab9367bcc3d2" + "IPY_MODEL_c70e261d6cf44f32bf2b6dae9c2030bc", + "IPY_MODEL_81ab0afcff0549fe9cdc34038bcb8065" ], - "layout": "IPY_MODEL_e80a07b509d24321a5f728427a7579ce" + "layout": "IPY_MODEL_331e65cb645e4261b0d521035d6f9fa8" } }, - "e13a786ff807427fae43a5cfed219975": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", + "e75a2359044347888b56002f945db287": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", "state": { - "width": "34px" + "description_width": "" } }, - "e7187596a3b845d2b7f369c7fa1201c7": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "e80a07b509d24321a5f728427a7579ce": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "f34e98e4c72945838d54a52a5e081315": { + "f42dbefe1afa44ceac086275a099407a": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "ButtonModel", + "model_name": "IntSliderModel", "state": { - "icon": "compress", - "layout": "IPY_MODEL_d5347f1a62b84865a7375f54f807ca33", - "style": "IPY_MODEL_d7837d55497b4e9b894e8a310288b9b1" + "layout": "IPY_MODEL_31e5c0c3795a498d87c8a8b1d8327917", + "max": 4, + "style": "IPY_MODEL_2d621d97ad4840f08a792eef56349b30" } }, - "f70fe483cde24341b4074f933a0d5574": { + "f9278ff760c54f8fa5ad460b82946227": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "LinkModel", "state": { "source": [ - "IPY_MODEL_40fe843dee8545819f229f799259f716", - "max" + "IPY_MODEL_a13a50d58406485cbfa71d192fdc504d", + "value" ], "target": [ - "IPY_MODEL_d702f3bf2990434a85d0792cd2611c5f", - "max_frame" + "IPY_MODEL_72151a9d47704d1fb535afa54a44cdda", + "frame" ] } }, - "fde0b8d494b14f87b50672b32c077416": { + "fda30267a096477a882c30d1c3fe783e": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", + "model_name": "LinkModel", "state": { - "description_width": "" + "source": [ + "IPY_MODEL_2964c4b4b96e47ed977a65d55c4c30f8", + "value" + ], + "target": [ + "IPY_MODEL_63d6a4ee513142359ee6edc4754f18ab", + "frame" + ] } } }, diff --git a/notebooks/tinybase/Basic.ipynb b/notebooks/tinybase/Basic.ipynb index 872b8a98e..bacc3d7a0 100644 --- a/notebooks/tinybase/Basic.ipynb +++ b/notebooks/tinybase/Basic.ipynb @@ -1172,25 +1172,7 @@ }, "widgets": { "application/vnd.jupyter.widget-state+json": { - "state": { - "3ad1e8b33bfd46ceab434ce32e3bd06a": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": {} - }, - "cf2ad1379a3b4306981fef55c99a0e26": { - "model_module": "nglview-js-widgets", - "model_module_version": "3.0.1", - "model_name": "ColormakerRegistryModel", - "state": { - "_msg_ar": [], - "_msg_q": [], - "_ready": true, - "layout": "IPY_MODEL_3ad1e8b33bfd46ceab434ce32e3bd06a" - } - } - }, + "state": {}, "version_major": 2, "version_minor": 0 } diff --git a/notebooks/tinybase/TinyJob.ipynb b/notebooks/tinybase/TinyJob.ipynb index 67609085a..79744ea42 100644 --- a/notebooks/tinybase/TinyJob.ipynb +++ b/notebooks/tinybase/TinyJob.ipynb @@ -25,7 +25,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "63cd145ecad142378ffe5f9bbe3c9a95", + "model_id": "152f5a5b455b49b587903b4c8ce65857", "version_major": 2, "version_minor": 0 }, @@ -76,7 +76,7 @@ "metadata": {}, "outputs": [], "source": [ - "from pyiron_contrib.tinybase.job import ProjectAdapter" + "from pyiron_contrib.tinybase.project import ProjectAdapter, InMemoryProject" ] }, { @@ -130,14 +130,6 @@ "logging.getLogger().setLevel(0)" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "fa9112b0-c679-49f3-a4c2-e506deb72f4b", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "id": "bc092b74-64c6-49c9-9576-ef5d871fcd1e", @@ -156,14 +148,6 @@ "pr = ProjectAdapter(Project('tinyjob'))" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "2ab27401-8db2-4130-b5ae-764eaa22c8f5", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "id": "450e6b64-9824-4b8a-8854-3999a93fc781", @@ -239,11 +223,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "49ccfe01-7b7e-4615-bf43-21c1bbffec66", "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "76b5f43e298747c2b94d0904a444374c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "NGLWidget(max_frame=21)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "j.output.animate_structures()" + ] }, { "cell_type": "markdown", @@ -255,7 +256,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "049f056e-47ff-4c2f-9e85-612744af15a8", "metadata": {}, "outputs": [], @@ -265,7 +266,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "1137a899-b00b-4ce4-92df-23a4bbcf7aa8", "metadata": {}, "outputs": [], @@ -275,7 +276,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "3a8cda32-df2e-4884-8cfd-84e438c5be69", "metadata": {}, "outputs": [], @@ -287,7 +288,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "id": "d4b81c3f-4667-4b99-a2b3-08c7ee7e2c82", "metadata": {}, "outputs": [], @@ -299,9 +300,12 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "id": "e7494fee-d565-45e3-a819-c77ab0d2c7f6", - "metadata": {}, + "metadata": { + "scrolled": true, + "tags": [] + }, "outputs": [ { "name": "stderr", @@ -319,12 +323,9 @@ }, { "cell_type": "code", - "execution_count": 22, - "id": "be5a6842-d70d-4aa0-ad75-1e3927fbac50", - "metadata": { - "scrolled": true, - "tags": [] - }, + "execution_count": 23, + "id": "7c16a615-0913-4880-9694-2c125285babc", + "metadata": {}, "outputs": [ { "data": { @@ -390,7 +391,7 @@ " murn\n", " 3\n", " 1\n", - " 5\n", + " 3\n", " /home/ponder/science/phd/dev/pyiron_contrib/no...\n", " finished\n", " MurnaghanNode\n", @@ -403,7 +404,7 @@ " id username name jobtype_id project_id status_id \\\n", "0 1 pyiron md 1 1 1 \n", "1 2 pyiron min 2 1 2 \n", - "2 3 pyiron murn 3 1 5 \n", + "2 3 pyiron murn 3 1 3 \n", "\n", " location status \\\n", "0 /home/ponder/science/phd/dev/pyiron_contrib/no... finished \n", @@ -416,40 +417,52 @@ "2 MurnaghanNode " ] }, - "execution_count": 22, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "pr.job_table()" + "j.project.database.job_table()" ] }, { "cell_type": "code", - "execution_count": 23, - "id": "665d50a5-f602-4527-8469-5a04f1c0ee35", + "execution_count": 24, + "id": "3fb09d42-f800-46ee-9919-83180863e1ee", "metadata": {}, "outputs": [ { "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "9c1507f4d5c84e2d9f390ba74bdf4dc8", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "" + "NGLWidget(max_frame=11)" ] }, - "execution_count": 23, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "pr.load('min')" + "j.output.animate_structures()" + ] + }, + { + "cell_type": "markdown", + "id": "023fbf27-f75c-4177-b35a-a18c33cc2f87", + "metadata": {}, + "source": [ + "Escape hatch to old HDF output." ] }, { "cell_type": "code", - "execution_count": 24, - "id": "73a759cb-7217-4d06-87d0-f18c4f18d55e", + "execution_count": 25, + "id": "0d1eb866-8970-45cf-a0c3-7eff3ffea5c2", "metadata": {}, "outputs": [ { @@ -466,77 +479,184 @@ "{'groups': [], 'nodes': ['MODULE', 'NAME', 'VERSION', 'node', 'output']}" ] }, - "execution_count": 24, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "pr._project['min/min']" + "pr._project['test/test']" ] }, { - "cell_type": "code", - "execution_count": 25, - "id": "359e983d-0fe5-43a4-9e56-73ee77ae3c79", + "cell_type": "markdown", + "id": "858edf07-75ee-4a40-8cff-c26f34d555f5", "metadata": {}, - "outputs": [], "source": [ - "j.load()" + "### Loading from job id or name works" ] }, { "cell_type": "code", "execution_count": 26, - "id": "8d90b7e6-8530-403b-9f62-dbf81ea35004", + "id": "db691097-72c6-45a4-89b1-6ec16018c8b8", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "['MODULE', 'NAME', 'VERSION', 'node', 'output']" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "j.storage.list_nodes()" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "61b7df6c-2b50-46a4-ab94-52aa3a9d07f9", - "metadata": {}, - "outputs": [ + "name": "stderr", + "output_type": "stream", + "text": [ + "DEBUG:matplotlib.pyplot:Loaded backend module://matplotlib_inline.backend_inline version unknown.\n", + "DEBUG:matplotlib.pyplot:Loaded backend module://matplotlib_inline.backend_inline version unknown.\n", + "DEBUG:matplotlib.font_manager:findfont: Matching sans\\-serif:style=normal:variant=normal:weight=normal:stretch=normal:size=10.0.\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBolIta.ttf', name='STIXNonUnicode', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmr10.ttf', name='cmr10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Oblique.ttf', name='DejaVu Sans Mono', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymReg.ttf', name='STIXSizeTwoSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymReg.ttf', name='STIXSizeOneSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneral.ttf', name='STIXGeneral', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-BoldItalic.ttf', name='DejaVu Serif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymBol.ttf', name='STIXSizeFourSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymReg.ttf', name='STIXSizeFourSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansDisplay.ttf', name='DejaVu Sans Display', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Oblique.ttf', name='DejaVu Sans', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 1.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralItalic.ttf', name='STIXGeneral', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymBol.ttf', name='STIXSizeTwoSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmss10.ttf', name='cmss10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFiveSymReg.ttf', name='STIXSizeFiveSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBol.ttf', name='STIXGeneral', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-BoldOblique.ttf', name='DejaVu Sans', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 1.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Bold.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 0.33499999999999996\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Italic.ttf', name='DejaVu Serif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmsy10.ttf', name='cmsy10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmb10.ttf', name='cmb10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymBol.ttf', name='STIXSizeThreeSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-BoldOblique.ttf', name='DejaVu Sans Mono', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymBol.ttf', name='STIXSizeOneSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniIta.ttf', name='STIXNonUnicode', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBol.ttf', name='STIXNonUnicode', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBolIta.ttf', name='STIXGeneral', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmex10.ttf', name='cmex10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUni.ttf', name='STIXNonUnicode', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmmi10.ttf', name='cmmi10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerifDisplay.ttf', name='DejaVu Serif Display', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Bold.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymReg.ttf', name='STIXSizeThreeSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 0.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmtt10.ttf', name='cmtt10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Bold.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/Z003-MediumItalic.otf', name='Z003', style='italic', variant='normal', weight=500, stretch='normal', size='scalable')) = 11.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-Regular.ttf', name='Liberation Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-BlackIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=900, stretch='normal', size='scalable')) = 11.525\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-BoldOblique.otf', name='Nimbus Sans Narrow', style='oblique', variant='normal', weight=700, stretch='condensed', size='scalable')) = 11.535\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-SemiboldIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-BoldItalic.otf', name='Nimbus Roman', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-Bold.ttf', name='Liberation Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-BoldItalic.otf', name='P052', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-DemiItalic.otf', name='URW Bookman', style='italic', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-Bold.otf', name='Nimbus Sans Narrow', style='normal', variant='normal', weight=700, stretch='condensed', size='scalable')) = 10.535\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-Regular.ttf', name='Hack', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-Oblique.otf', name='Nimbus Sans Narrow', style='oblique', variant='normal', weight=400, stretch='condensed', size='scalable')) = 11.25\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-It.otf', name='Source Code Pro', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-Bold.otf', name='P052', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerifItalic.otf', name='FreeSerif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-ExtraLight.otf', name='Source Code Pro', style='normal', variant='normal', weight=200, stretch='normal', size='scalable')) = 10.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-Italic.ttf', name='Liberation Serif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-Italic.otf', name='Nimbus Roman', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSansOblique.otf', name='FreeSans', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-BoldItalic.otf', name='Nimbus Sans', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-BookOblique.otf', name='URW Gothic', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-Bold.otf', name='C059', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/D050000L.otf', name='D050000L', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Light.otf', name='Source Code Pro', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/StandardSymbolsPS.otf', name='Standard Symbols PS', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-Bold.ttf', name='Hack', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-Roman.otf', name='P052', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-BoldItalic.ttf', name='Hack', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Regular.otf', name='Source Code Pro', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-Bold.ttf', name='Liberation Serif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-Italic.otf', name='P052', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-Italic.otf', name='Nimbus Sans', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Light.otf', name='Cantarell', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-Italic.ttf', name='Hack', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Regular.otf', name='Cantarell', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Thin.otf', name='Cantarell', style='normal', variant='normal', weight=100, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Bold.otf', name='Source Code Pro', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerifBold.otf', name='FreeSerif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-Light.otf', name='URW Bookman', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-Roman.otf', name='C059', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-Regular.otf', name='Nimbus Sans Narrow', style='normal', variant='normal', weight=400, stretch='condensed', size='scalable')) = 10.25\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-ExtraBold.otf', name='Cantarell', style='normal', variant='normal', weight=800, stretch='normal', size='scalable')) = 10.43\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Black.otf', name='Source Code Pro', style='normal', variant='normal', weight=900, stretch='normal', size='scalable')) = 10.525\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerif.otf', name='FreeSerif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-BoldItalic.otf', name='Nimbus Mono PS', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-BoldItalic.ttf', name='Liberation Serif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Bold.otf', name='Cantarell', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-Italic.otf', name='Nimbus Mono PS', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-DemiOblique.otf', name='URW Gothic', style='oblique', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-Regular.otf', name='Nimbus Mono PS', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSans.otf', name='FreeSans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSansBold.otf', name='FreeSans', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-Italic.ttf', name='Liberation Sans', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-BoldItalic.ttf', name='Liberation Sans', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-BoldItalic.ttf', name='Liberation Mono', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Medium.otf', name='Source Code Pro', style='normal', variant='normal', weight=500, stretch='normal', size='scalable')) = 10.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-LightItalic.otf', name='URW Bookman', style='italic', variant='normal', weight=300, stretch='normal', size='scalable')) = 11.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-LightIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=300, stretch='normal', size='scalable')) = 11.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-Italic.ttf', name='Liberation Mono', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-Bold.ttf', name='Liberation Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-Italic.otf', name='C059', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-Demi.otf', name='URW Gothic', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-MediumIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=500, stretch='normal', size='scalable')) = 11.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-BoldIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-BdIta.otf', name='C059', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMonoBoldOblique.otf', name='FreeMono', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-ExtraLightIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=200, stretch='normal', size='scalable')) = 11.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-Bold.otf', name='Nimbus Roman', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSansBoldOblique.otf', name='FreeSans', style='oblique', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-Book.otf', name='URW Gothic', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMonoBold.otf', name='FreeMono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-Regular.ttf', name='Liberation Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-Demi.otf', name='URW Bookman', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Semibold.otf', name='Source Code Pro', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerifBoldItalic.otf', name='FreeSerif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMono.otf', name='FreeMono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-Bold.otf', name='Nimbus Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-Bold.otf', name='Nimbus Mono PS', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMonoOblique.otf', name='FreeMono', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-Regular.ttf', name='Liberation Serif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-Regular.otf', name='Nimbus Roman', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-Regular.otf', name='Nimbus Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: Matching sans\\-serif:style=normal:variant=normal:weight=normal:stretch=normal:size=10.0 to DejaVu Sans ('/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf') with score of 0.050000.\n" + ] + }, { "data": { + "image/png": "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PaW77rrL6dJSVvS3ZbDfnaNHjzpRUtIZ9+EjyjCMfs9N0xxwDCO3bNkyffzxx9q9e7fTpaSUuro6LV++XG+88YYyMjKcLieldXV1ac6cOVq7dq0k6corr9Snn36qDRs2ED7isHXrVr388svasmWLZs6cqQMHDmjFihUqKSnR4sWLnS4vpfG7M7RxHz4mTpwoj8czYJWjoaFhQCrFyDz88MPavn27du3apbKyMqfLSSn79u1TQ0ODZs+eHTsWiUS0a9cu1dTUKBQKyePxOFhh6iguLtYll1zS79iMGTP02muvOVRRavrZz36mVatW6c4775QkXXrppTp69Kiqq6sJH6NUVFQkqXsFpLi4OHac351e477nw+fzafbs2aqtre13vLa2VnPnznWoqtRkmqaWLVum119/XTt27NC0adOcLinlfPe739Unn3yiAwcOxB5z5szRPffcowMHDhA84jBv3rwBW70PHz6sqVOnOlRRamptbVVaWv+fAo/Hw1bbMZg2bZqKior6/e6Ew2Ht3LmT350e437lQ5Kqqqr0wx/+UHPmzNG1116rjRs36tixY1qyZInTpaWUpUuXasuWLfrDH/6gnJyc2GpSIBBQZmamw9WlhpycnAE9MtnZ2SosLKR3Jk4//elPNXfuXK1du1bf//739cEHH2jjxo3auHGj06WllEWLFumpp57SlClTNHPmTH344Yd67rnn9OCDDzpdWlJraWnRF198EXt+5MgRHThwQAUFBZoyZYpWrFihtWvXqqKiQhUVFVq7dq2ysrJ09913O1h1EnF2s03i/Mu//Is5depU0+fzmd/+9rfZHjoKkgZ9bNq0yenSUhpbbUfvP/7jP8xZs2aZfr/fnD59urlx40anS0o5wWDQXL58uTllyhQzIyPDvOiii8zHHnvMDIVCTpeW1N56661B/3m4ePFi0zS7t9s+8cQTZlFRken3+83rrrvO/OSTT5wtOokYpmmaDuUeAADgQuO+5wMAACQXwgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEio/w9hHopmxnvDfAAAAABJRU5ErkJggg==", "text/plain": [ - "[]" + "
" ] }, - "execution_count": 27, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "j.storage.list_groups()" + "j.project.load(j.id).output.plot_energies()" ] }, { "cell_type": "code", - "execution_count": 28, - "id": "3fb09d42-f800-46ee-9919-83180863e1ee", + "execution_count": 27, + "id": "23ce6822-b38b-41f3-9269-109dbb152ecf", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "4f766721bc5b43d6a64c39fe10f7f28d", + "model_id": "ac63e7c8d2b84a478b194a451a0ede25", "version_major": 2, "version_minor": 0 }, @@ -549,42 +669,219 @@ } ], "source": [ - "j.output.animate_structures()" + "j.project.load(j.name).output.animate_structures()" + ] + }, + { + "cell_type": "markdown", + "id": "eec174f8-d6a8-47dd-baff-f23087265375", + "metadata": {}, + "source": [ + "## Murnaghan" ] }, { "cell_type": "code", - "execution_count": 29, - "id": "8298e728-bd31-43ab-b68b-3a26370951af", + "execution_count": 28, + "id": "d0f62439-3492-4392-ac2a-2b2545b85527", + "metadata": {}, + "outputs": [], + "source": [ + "murn = GenericTinyJob(pr, 'murn')" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "5c9ab533-cf97-49a1-8c4b-0e5e2f9758c2", + "metadata": {}, + "outputs": [], + "source": [ + "murn.node_class = MurnaghanNode" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "253237f0-b338-470c-bc54-3c7400a757b7", + "metadata": {}, + "outputs": [], + "source": [ + "murn.input.node = AseStaticNode()\n", + "murn.input.node.input.calculator = MorsePotential()" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "c801093b-499e-48a7-8444-77602ed88a96", + "metadata": {}, + "outputs": [], + "source": [ + "murn.input.structure = bulk(\"Fe\", a=1.2)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "1e30b36e-11e6-47d1-836e-cffea7b73cdd", + "metadata": {}, + "outputs": [], + "source": [ + "murn.input.set_strain_range(.5, 500)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "9920e4b7-8395-4fb9-96c3-792b044c4e3a", + "metadata": {}, + "outputs": [], + "source": [ + "murn.input.child_executor = ProcessExecutor" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "18b5305a-8950-44af-bc2e-c9734b059713", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:root:Job already finished!\n" + ] + } + ], + "source": [ + "exe = murn.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "836bb2ec-4295-4a3c-b976-7a35d04aad36", "metadata": {}, "outputs": [ { "data": { + "image/png": "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", "text/plain": [ - "2" + "
" ] }, - "execution_count": 29, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "j.id" + "murn.output.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "1dfdd96a-41dc-4e39-a029-9d10eb6ecf5e", + "metadata": {}, + "source": [ + "# Pyiron purely in memory\n", + "\n", + "Because the new ProjectInterface completely controls both the storage class and the database a job sees, we can easily swap them without changing anything in `TinyJob` at all. As a demonstration we can make here a project that persists only for the duration of the process life time. But it's not hard to imagine a number of different project type depending on database configuration or storage location (S3, a single HDF5 for all jobs, etc.)" ] }, { "cell_type": "code", - "execution_count": null, - "id": "43ead40e-bb9e-47e9-adfb-b5406eb04f91", + "execution_count": 36, + "id": "79a2bb61-0a5e-4a3a-b195-46d027738a0e", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "pr = InMemoryProject('/')" + ] }, { "cell_type": "code", - "execution_count": 30, - "id": "f21c8fa9-cd77-4220-bb23-5bcbc49ba62d", + "execution_count": 37, + "id": "b4e6e0c9-a2c6-40ab-884e-a46e16c37b04", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
" + ], + "text/plain": [ + "Empty DataFrame\n", + "Columns: []\n", + "Index: []" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pr.job_table()" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "cef7c46f-551f-401e-96c2-214628e23967", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "murn = GenericTinyJob(pr, 'murn')\n", + "murn.node_class = MurnaghanNode\n", + "murn.input.node = AseStaticNode()\n", + "murn.input.node.input.calculator = MorsePotential()\n", + "murn.input.structure = bulk(\"Fe\", a=1.2)\n", + "murn.input.set_strain_range(.5, 500)\n", + "murn.input.child_executor = ProcessExecutor\n", + "murn.run()\n", + "murn.output.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "e8a7ee30-d7a6-46fc-bf98-1b52c981470f", "metadata": {}, "outputs": [ { @@ -624,35 +921,11 @@ " 0\n", " 1\n", " pyiron\n", - " md\n", - " 1\n", - " 1\n", + " murn\n", " 1\n", - " /home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/\n", - " finished\n", - " AseMDNode\n", - " \n", - " \n", - " 1\n", - " 2\n", - " pyiron\n", - " min\n", - " 2\n", " 1\n", - " 2\n", - " /home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/\n", - " finished\n", - " AseMinimizeNode\n", - " \n", - " \n", - " 2\n", - " 3\n", - " pyiron\n", - " murn\n", - " 3\n", " 1\n", - " 5\n", - " /home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/\n", + " /\n", " finished\n", " MurnaghanNode\n", " \n", @@ -661,384 +934,259 @@ "" ], "text/plain": [ - " id username name jobtype_id project_id status_id \\\n", - "0 1 pyiron md 1 1 1 \n", - "1 2 pyiron min 2 1 2 \n", - "2 3 pyiron murn 3 1 5 \n", - "\n", - " location \\\n", - "0 /home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/ \n", - "1 /home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/ \n", - "2 /home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/ \n", + " id username name jobtype_id project_id status_id location status \\\n", + "0 1 pyiron murn 1 1 1 / finished \n", "\n", - " status type \n", - "0 finished AseMDNode \n", - "1 finished AseMinimizeNode \n", - "2 finished MurnaghanNode " + " type \n", + "0 MurnaghanNode " ] }, - "execution_count": 30, + "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "j.project.job_table()" + "pr.job_table()" ] }, { "cell_type": "code", - "execution_count": 31, - "id": "0d1eb866-8970-45cf-a0c3-7eff3ffea5c2", + "execution_count": 40, + "id": "30871447-3e20-46ee-a58e-853d4f4cb5d9", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "DEBUG:pyiron_log:sql_query: {'project': 'science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/'}\n", - "DEBUG:pyiron_log:sql_query: {'project': 'science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/'}\n" - ] - }, { "data": { "text/plain": [ - "{'groups': [], 'nodes': ['MODULE', 'NAME', 'VERSION', 'node', 'output']}" + "" ] }, - "execution_count": 31, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "pr._project['test/test']" + "j = GenericTinyJob(pr, 'md')\n", + "j.node_class = AseMDNode\n", + "j.input.structure = bulk('Fe', a=1.2, cubic=True).repeat(2)\n", + "j.input.calculator = MorsePotential()\n", + "j.input.steps = 100\n", + "j.input.timestep = 3\n", + "j.input.temperature = 600\n", + "j.input.output_steps = 20\n", + "j.run(how='background')" ] }, { "cell_type": "code", - "execution_count": 32, - "id": "7593a54e-168a-4549-8326-50a6103c92b1", + "execution_count": 41, + "id": "e63d43c1-341f-4ec0-b0cf-9cd5ead51926", "metadata": {}, "outputs": [ { "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idusernamenamejobtype_idproject_idstatus_idlocationstatustype
01pyironmurn111/finishedMurnaghanNode
12pyironmd212/runningAseMDNode
\n", + "
" + ], "text/plain": [ - "2" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "j.id" - ] - }, - { - "cell_type": "markdown", - "id": "858edf07-75ee-4a40-8cff-c26f34d555f5", - "metadata": {}, - "source": [ - "### Loading from job id or name works" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "6d203728-fa24-4a33-bd2c-51709123a7fc", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "DEBUG:matplotlib.pyplot:Loaded backend module://matplotlib_inline.backend_inline version unknown.\n", - "DEBUG:matplotlib.pyplot:Loaded backend module://matplotlib_inline.backend_inline version unknown.\n", - "DEBUG:matplotlib.font_manager:findfont: Matching sans\\-serif:style=normal:variant=normal:weight=normal:stretch=normal:size=10.0.\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBolIta.ttf', name='STIXNonUnicode', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmr10.ttf', name='cmr10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Oblique.ttf', name='DejaVu Sans Mono', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymReg.ttf', name='STIXSizeTwoSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymReg.ttf', name='STIXSizeOneSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneral.ttf', name='STIXGeneral', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-BoldItalic.ttf', name='DejaVu Serif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymBol.ttf', name='STIXSizeFourSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymReg.ttf', name='STIXSizeFourSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansDisplay.ttf', name='DejaVu Sans Display', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Oblique.ttf', name='DejaVu Sans', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 1.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralItalic.ttf', name='STIXGeneral', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymBol.ttf', name='STIXSizeTwoSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmss10.ttf', name='cmss10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFiveSymReg.ttf', name='STIXSizeFiveSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBol.ttf', name='STIXGeneral', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-BoldOblique.ttf', name='DejaVu Sans', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 1.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Bold.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 0.33499999999999996\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Italic.ttf', name='DejaVu Serif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmsy10.ttf', name='cmsy10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmb10.ttf', name='cmb10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymBol.ttf', name='STIXSizeThreeSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-BoldOblique.ttf', name='DejaVu Sans Mono', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymBol.ttf', name='STIXSizeOneSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniIta.ttf', name='STIXNonUnicode', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBol.ttf', name='STIXNonUnicode', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBolIta.ttf', name='STIXGeneral', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmex10.ttf', name='cmex10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUni.ttf', name='STIXNonUnicode', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmmi10.ttf', name='cmmi10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerifDisplay.ttf', name='DejaVu Serif Display', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Bold.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymReg.ttf', name='STIXSizeThreeSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 0.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmtt10.ttf', name='cmtt10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Bold.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/Z003-MediumItalic.otf', name='Z003', style='italic', variant='normal', weight=500, stretch='normal', size='scalable')) = 11.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-Regular.ttf', name='Liberation Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-BlackIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=900, stretch='normal', size='scalable')) = 11.525\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-BoldOblique.otf', name='Nimbus Sans Narrow', style='oblique', variant='normal', weight=700, stretch='condensed', size='scalable')) = 11.535\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-SemiboldIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-BoldItalic.otf', name='Nimbus Roman', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-Bold.ttf', name='Liberation Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-BoldItalic.otf', name='P052', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-DemiItalic.otf', name='URW Bookman', style='italic', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-Bold.otf', name='Nimbus Sans Narrow', style='normal', variant='normal', weight=700, stretch='condensed', size='scalable')) = 10.535\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-Regular.ttf', name='Hack', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-Oblique.otf', name='Nimbus Sans Narrow', style='oblique', variant='normal', weight=400, stretch='condensed', size='scalable')) = 11.25\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-It.otf', name='Source Code Pro', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-Bold.otf', name='P052', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerifItalic.otf', name='FreeSerif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-ExtraLight.otf', name='Source Code Pro', style='normal', variant='normal', weight=200, stretch='normal', size='scalable')) = 10.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-Italic.ttf', name='Liberation Serif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-Italic.otf', name='Nimbus Roman', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSansOblique.otf', name='FreeSans', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-BoldItalic.otf', name='Nimbus Sans', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-BookOblique.otf', name='URW Gothic', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-Bold.otf', name='C059', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/D050000L.otf', name='D050000L', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Light.otf', name='Source Code Pro', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/StandardSymbolsPS.otf', name='Standard Symbols PS', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-Bold.ttf', name='Hack', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-Roman.otf', name='P052', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-BoldItalic.ttf', name='Hack', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Regular.otf', name='Source Code Pro', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-Bold.ttf', name='Liberation Serif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-Italic.otf', name='P052', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-Italic.otf', name='Nimbus Sans', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Light.otf', name='Cantarell', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-Italic.ttf', name='Hack', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Regular.otf', name='Cantarell', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Thin.otf', name='Cantarell', style='normal', variant='normal', weight=100, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Bold.otf', name='Source Code Pro', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerifBold.otf', name='FreeSerif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-Light.otf', name='URW Bookman', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-Roman.otf', name='C059', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-Regular.otf', name='Nimbus Sans Narrow', style='normal', variant='normal', weight=400, stretch='condensed', size='scalable')) = 10.25\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-ExtraBold.otf', name='Cantarell', style='normal', variant='normal', weight=800, stretch='normal', size='scalable')) = 10.43\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Black.otf', name='Source Code Pro', style='normal', variant='normal', weight=900, stretch='normal', size='scalable')) = 10.525\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerif.otf', name='FreeSerif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-BoldItalic.otf', name='Nimbus Mono PS', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-BoldItalic.ttf', name='Liberation Serif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Bold.otf', name='Cantarell', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-Italic.otf', name='Nimbus Mono PS', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-DemiOblique.otf', name='URW Gothic', style='oblique', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-Regular.otf', name='Nimbus Mono PS', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSans.otf', name='FreeSans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSansBold.otf', name='FreeSans', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-Italic.ttf', name='Liberation Sans', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-BoldItalic.ttf', name='Liberation Sans', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-BoldItalic.ttf', name='Liberation Mono', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Medium.otf', name='Source Code Pro', style='normal', variant='normal', weight=500, stretch='normal', size='scalable')) = 10.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-LightItalic.otf', name='URW Bookman', style='italic', variant='normal', weight=300, stretch='normal', size='scalable')) = 11.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-LightIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=300, stretch='normal', size='scalable')) = 11.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-Italic.ttf', name='Liberation Mono', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-Bold.ttf', name='Liberation Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-Italic.otf', name='C059', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-Demi.otf', name='URW Gothic', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-MediumIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=500, stretch='normal', size='scalable')) = 11.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-BoldIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-BdIta.otf', name='C059', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMonoBoldOblique.otf', name='FreeMono', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-ExtraLightIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=200, stretch='normal', size='scalable')) = 11.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-Bold.otf', name='Nimbus Roman', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSansBoldOblique.otf', name='FreeSans', style='oblique', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-Book.otf', name='URW Gothic', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMonoBold.otf', name='FreeMono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-Regular.ttf', name='Liberation Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-Demi.otf', name='URW Bookman', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Semibold.otf', name='Source Code Pro', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerifBoldItalic.otf', name='FreeSerif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMono.otf', name='FreeMono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-Bold.otf', name='Nimbus Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-Bold.otf', name='Nimbus Mono PS', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMonoOblique.otf', name='FreeMono', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-Regular.ttf', name='Liberation Serif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-Regular.otf', name='Nimbus Roman', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-Regular.otf', name='Nimbus Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: Matching sans\\-serif:style=normal:variant=normal:weight=normal:stretch=normal:size=10.0 to DejaVu Sans ('/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf') with score of 0.050000.\n" - ] - }, - { - "data": { - "image/png": "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PaW77rrL6dJSVvS3ZbDfnaNHjzpRUtIZ9+EjyjCMfs9N0xxwDCO3bNkyffzxx9q9e7fTpaSUuro6LV++XG+88YYyMjKcLieldXV1ac6cOVq7dq0k6corr9Snn36qDRs2ED7isHXrVr388svasmWLZs6cqQMHDmjFihUqKSnR4sWLnS4vpfG7M7RxHz4mTpwoj8czYJWjoaFhQCrFyDz88MPavn27du3apbKyMqfLSSn79u1TQ0ODZs+eHTsWiUS0a9cu1dTUKBQKyePxOFhh6iguLtYll1zS79iMGTP02muvOVRRavrZz36mVatW6c4775QkXXrppTp69Kiqq6sJH6NUVFQkqXsFpLi4OHac351e477nw+fzafbs2aqtre13vLa2VnPnznWoqtRkmqaWLVum119/XTt27NC0adOcLinlfPe739Unn3yiAwcOxB5z5szRPffcowMHDhA84jBv3rwBW70PHz6sqVOnOlRRamptbVVaWv+fAo/Hw1bbMZg2bZqKior6/e6Ew2Ht3LmT350e437lQ5Kqqqr0wx/+UHPmzNG1116rjRs36tixY1qyZInTpaWUpUuXasuWLfrDH/6gnJyc2GpSIBBQZmamw9WlhpycnAE9MtnZ2SosLKR3Jk4//elPNXfuXK1du1bf//739cEHH2jjxo3auHGj06WllEWLFumpp57SlClTNHPmTH344Yd67rnn9OCDDzpdWlJraWnRF198EXt+5MgRHThwQAUFBZoyZYpWrFihtWvXqqKiQhUVFVq7dq2ysrJ09913O1h1EnF2s03i/Mu//Is5depU0+fzmd/+9rfZHjoKkgZ9bNq0yenSUhpbbUfvP/7jP8xZs2aZfr/fnD59urlx40anS0o5wWDQXL58uTllyhQzIyPDvOiii8zHHnvMDIVCTpeW1N56661B/3m4ePFi0zS7t9s+8cQTZlFRken3+83rrrvO/OSTT5wtOokYpmmaDuUeAADgQuO+5wMAACQXwgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEio/w9hHopmxnvDfAAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "j.project.load(j.id).output.plot_energies()" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "23ce6822-b38b-41f3-9269-109dbb152ecf", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "0ab49f229fe34634ad5ab3b4c233bddc", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "NGLWidget(max_frame=11)" + " id username name jobtype_id project_id status_id location status \\\n", + "0 1 pyiron murn 1 1 1 / finished \n", + "1 2 pyiron md 2 1 2 / running \n", + "\n", + " type \n", + "0 MurnaghanNode \n", + "1 AseMDNode " ] }, + "execution_count": 41, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "j.project.load(j.name).output.animate_structures()" + "pr.job_table()" ] }, { "cell_type": "markdown", - "id": "eec174f8-d6a8-47dd-baff-f23087265375", + "id": "52ef6246-32c1-47c9-bc42-21d6135477f2", "metadata": {}, "source": [ - "## Murnaghan" + "Subprojects work." ] }, { "cell_type": "code", - "execution_count": 63, - "id": "d0f62439-3492-4392-ac2a-2b2545b85527", - "metadata": {}, - "outputs": [], - "source": [ - "murn = GenericTinyJob(pr, 'murn')" - ] - }, - { - "cell_type": "code", - "execution_count": 64, - "id": "5c9ab533-cf97-49a1-8c4b-0e5e2f9758c2", - "metadata": {}, - "outputs": [], - "source": [ - "murn.node_class = MurnaghanNode" - ] - }, - { - "cell_type": "code", - "execution_count": 65, - "id": "253237f0-b338-470c-bc54-3c7400a757b7", - "metadata": {}, - "outputs": [], - "source": [ - "murn.input.node = AseStaticNode()\n", - "murn.input.node.input.calculator = MorsePotential()" - ] - }, - { - "cell_type": "code", - "execution_count": 66, - "id": "c801093b-499e-48a7-8444-77602ed88a96", - "metadata": {}, - "outputs": [], - "source": [ - "murn.input.structure = bulk(\"Fe\", a=1.2)" - ] - }, - { - "cell_type": "code", - "execution_count": 68, - "id": "1e30b36e-11e6-47d1-836e-cffea7b73cdd", - "metadata": {}, - "outputs": [], - "source": [ - "murn.input.set_strain_range(.5, 500)" - ] - }, - { - "cell_type": "code", - "execution_count": 69, - "id": "9920e4b7-8395-4fb9-96c3-792b044c4e3a", - "metadata": {}, - "outputs": [], - "source": [ - "murn.input.child_executor = ProcessExecutor" - ] - }, - { - "cell_type": "code", - "execution_count": 74, - "id": "18b5305a-8950-44af-bc2e-c9734b059713", - "metadata": {}, - "outputs": [], - "source": [ - "exe = murn.run()" - ] - }, - { - "cell_type": "code", - "execution_count": 75, - "id": "565408a4-4e7f-4c15-b5d7-1aa63e979b8e", + "execution_count": 60, + "id": "80da39e2-76d1-42e6-977f-241d2683188d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "(ReturnStatus(Code.DONE, None),)" + "" ] }, - "execution_count": 75, + "execution_count": 60, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "exe.status" + "sub = pr.open_location(\"/foo\")\n", + "j = GenericTinyJob(sub, 'md')\n", + "j.node_class = AseMDNode\n", + "j.input.structure = bulk('Fe', a=1.2, cubic=True).repeat(2)\n", + "j.input.calculator = MorsePotential()\n", + "j.input.steps = 100\n", + "j.input.timestep = 3\n", + "j.input.temperature = 600\n", + "j.input.output_steps = 20\n", + "j.run(how='process')" ] }, { "cell_type": "code", - "execution_count": 76, - "id": "836bb2ec-4295-4a3c-b976-7a35d04aad36", + "execution_count": 61, + "id": "a567f96a-cbb3-4d2d-95d1-6dcecee7ddb8", "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idusernamenamejobtype_idproject_idstatus_idlocationstatustype
01pyironmurn111/finishedMurnaghanNode
12pyironmd224/foorunningAseMDNode
\n", + "
" + ], "text/plain": [ - "
" + " id username name jobtype_id project_id status_id location status \\\n", + "0 1 pyiron murn 1 1 1 / finished \n", + "1 2 pyiron md 2 2 4 /foo running \n", + "\n", + " type \n", + "0 MurnaghanNode \n", + "1 AseMDNode " ] }, + "execution_count": 61, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "murn.output.plot()" + "pr.job_table()" ] }, { @@ -1051,35 +1199,27 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 45, "id": "a6fefefb-b09c-4cee-b632-29f88bccfeee", "metadata": {}, "outputs": [], "source": [ "db = j.project.database" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5c1a24bd-ccbe-4cad-94b7-1539084f5924", - "metadata": {}, - "outputs": [], - "source": [] + ] }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 46, "id": "3419f273-b94b-48cf-9373-7441baec2353", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "DatabaseEntry(name='md', username='pyiron', project='/home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/', status='finished', jobtype='AseMDNode')" + "DatabaseEntry(name='murn', username='pyiron', project='/', status='finished', jobtype='MurnaghanNode')" ] }, - "execution_count": 47, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } @@ -1098,17 +1238,17 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 47, "id": "cf75b2e8-ec15-4846-bda4-9b6661e8b5fa", "metadata": {}, "outputs": [], "source": [ - "eng = db.get_engine()" + "eng = db.engine" ] }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 48, "id": "1b23728b-1050-47a4-bda0-bd5967ceed2e", "metadata": {}, "outputs": [], @@ -1118,7 +1258,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 49, "id": "4cd73be9-2c6d-4501-8a6c-0afc6083a4b9", "metadata": {}, "outputs": [], @@ -1128,7 +1268,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 50, "id": "e9d9a3e2-5c86-46b4-b6d0-bec3a9f448b0", "metadata": {}, "outputs": [], @@ -1138,17 +1278,17 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 51, "id": "99059ff6-18bd-40b9-85fc-d76e1828f7ac", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[(2,)]" + "[]" ] }, - "execution_count": 52, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -1159,17 +1299,17 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 52, "id": "cb4bb9fe-13bf-4736-aa68-662b980d4f00", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[(2, 'finished')]" + "[(2, 'running')]" ] }, - "execution_count": 53, + "execution_count": 52, "metadata": {}, "output_type": "execute_result" } @@ -1180,17 +1320,17 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 53, "id": "4641b048-b7c7-46a2-b67c-835c08916cb0", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[(1, 'finished'), (2, 'finished'), (3, 'finished')]" + "[(1, 'finished'), (2, 'running'), (3, 'running')]" ] }, - "execution_count": 54, + "execution_count": 53, "metadata": {}, "output_type": "execute_result" } @@ -1201,17 +1341,17 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 54, "id": "94364e54-b980-48cc-995b-daf977437b1b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[(1, '/home/ponder/science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/')]" + "[(1, '/'), (2, '/foo')]" ] }, - "execution_count": 55, + "execution_count": 54, "metadata": {}, "output_type": "execute_result" } @@ -1222,17 +1362,17 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 55, "id": "f32c43d5-19c8-4505-bf5c-9ac32bca51d0", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[(1, 'AseMDNode'), (2, 'AseMinimizeNode'), (3, 'MurnaghanNode')]" + "[(1, 'MurnaghanNode'), (2, 'AseMDNode')]" ] }, - "execution_count": 56, + "execution_count": 55, "metadata": {}, "output_type": "execute_result" } @@ -1240,22 +1380,6 @@ "source": [ "s.query(JobType.__table__).all()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0dc1921b-8eb2-46ef-a228-d6b86d63fea7", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "905c06d2-0f66-4fa3-b771-8a4b6e7ed3a4", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { @@ -1279,7 +1403,31 @@ "widgets": { "application/vnd.jupyter.widget-state+json": { "state": { - "63cd145ecad142378ffe5f9bbe3c9a95": { + "01513848d0e142c19b486707145aac2b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "PlayModel", + "state": { + "layout": "IPY_MODEL_2baff459d76d4d4195b376c513f20464", + "max": 11, + "style": "IPY_MODEL_6a1e7e89d69443f3af1d3070dcd5f743" + } + }, + "02942fbc3e1c40f4becd44b4acfb0325": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "SliderStyleModel", + "state": { + "description_width": "" + } + }, + "10be40fdff5d48f59854385a9e7437e0": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": {} + }, + "152f5a5b455b49b587903b4c8ce65857": { "model_module": "nglview-js-widgets", "model_module_version": "3.0.1", "model_name": "ColormakerRegistryModel", @@ -1287,14 +1435,1400 @@ "_msg_ar": [], "_msg_q": [], "_ready": true, - "layout": "IPY_MODEL_ed7d7881cda84e47a33b538e45e24bff" + "layout": "IPY_MODEL_b7fef6c8c4f54afc8b0ab6cd8df3e66b" + } + }, + "1f676bc52a71404cac6436e0a83bfa43": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "SliderStyleModel", + "state": { + "description_width": "" + } + }, + "232c6544e5134d6db790fe885e75a4e0": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_d03c7a1320874169b1cb7f506bedf1d3", + "max" + ], + "target": [ + "IPY_MODEL_9c1507f4d5c84e2d9f390ba74bdf4dc8", + "max_frame" + ] + } + }, + "2baff459d76d4d4195b376c513f20464": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "3027a3adbccc481d8e44bd6a73c2f3b5": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "32b56ca3d2714a919e6fda2bda75b840": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "children": [ + "IPY_MODEL_d77cbd6d9a6b4f379349ba2242da96be", + "IPY_MODEL_414a367a402644e6a24ec7f61e79689f" + ], + "layout": "IPY_MODEL_d219818914194ca796dcd6620d67e1da" + } + }, + "3412eb1b649d487496b8c2287ff0cc1d": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "width": "34px" + } + }, + "357b9196051d4d8cade7e554e5a8e789": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_01513848d0e142c19b486707145aac2b", + "value" + ], + "target": [ + "IPY_MODEL_d03c7a1320874169b1cb7f506bedf1d3", + "value" + ] + } + }, + "377a7e31f31445758b9fe5b9d7b35922": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "39b6391e86da45ffa8c513e8653fc6c5": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "414a367a402644e6a24ec7f61e79689f": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "IntSliderModel", + "state": { + "layout": "IPY_MODEL_39b6391e86da45ffa8c513e8653fc6c5", + "max": 11, + "style": "IPY_MODEL_02942fbc3e1c40f4becd44b4acfb0325" + } + }, + "44934da01fab4ddeb34ec8fce1793432": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "width": "34px" + } + }, + "45e8ce58f366409db6a65035b3c7e8ea": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "children": [ + "IPY_MODEL_01513848d0e142c19b486707145aac2b", + "IPY_MODEL_d03c7a1320874169b1cb7f506bedf1d3" + ], + "layout": "IPY_MODEL_6af4147c8e36483783d861307e4208f8" + } + }, + "46e9ec85470c49bbae783ccb989baac6": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "486d6774e21240209db01f3a304dceb4": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "icon": "compress", + "layout": "IPY_MODEL_3412eb1b649d487496b8c2287ff0cc1d", + "style": "IPY_MODEL_c6092c42c17b4fadbf8ba80b0145cede" + } + }, + "4e8c55c8836a40e2bc91a8cb2224f8ca": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_d77cbd6d9a6b4f379349ba2242da96be", + "max" + ], + "target": [ + "IPY_MODEL_ac63e7c8d2b84a478b194a451a0ede25", + "max_frame" + ] + } + }, + "50a64a1dc31641abbacfb82be21fe493": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_529f43bed2a445419cdaa8c9f1b748c1", + "value" + ], + "target": [ + "IPY_MODEL_6f48caf2815c4b6696c4a3709aa50497", + "value" + ] + } + }, + "529f43bed2a445419cdaa8c9f1b748c1": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "PlayModel", + "state": { + "layout": "IPY_MODEL_a1c548569b5e4dd298b096a8be070a6d", + "max": 21, + "style": "IPY_MODEL_90b9fb050b0f4966aaa958aac04a9143" + } + }, + "69e28476441f48fe95232e86889454d1": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "6a1e7e89d69443f3af1d3070dcd5f743": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "description_width": "" + } + }, + "6af4147c8e36483783d861307e4208f8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "6ec27eda3b6e461d821642ef264200ce": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": {} + }, + "6f48caf2815c4b6696c4a3709aa50497": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "IntSliderModel", + "state": { + "layout": "IPY_MODEL_83a7f768dc3545b085ff8b5dda276104", + "max": 21, + "style": "IPY_MODEL_1f676bc52a71404cac6436e0a83bfa43" + } + }, + "767f5dc744e84d9ebff773d9d352cdec": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_529f43bed2a445419cdaa8c9f1b748c1", + "value" + ], + "target": [ + "IPY_MODEL_76b5f43e298747c2b94d0904a444374c", + "frame" + ] + } + }, + "76b5f43e298747c2b94d0904a444374c": { + "model_module": "nglview-js-widgets", + "model_module_version": "3.0.1", + "model_name": "NGLModel", + "state": { + "_camera_orientation": [ + 14.593602577909778, + 0, + 0, + 0, + 0, + 14.593602577909778, + 0, + 0, + 0, + 0, + 14.593602577909778, + 0, + -0.8999999761581421, + -0.8999999761581421, + -0.8999999761581421, + 1 + ], + "_camera_str": "orthographic", + "_gui_theme": null, + "_ibtn_fullscreen": "IPY_MODEL_92336429a0a04914a5c2a0241a4c44ae", + "_igui": null, + "_iplayer": "IPY_MODEL_caf558fa0d2c435389c51dceae9fe832", + "_ngl_color_dict": {}, + "_ngl_coordinate_resource": {}, + "_ngl_full_stage_parameters": { + "ambientColor": 14540253, + "ambientIntensity": 0.2, + "backgroundColor": "white", + "cameraEyeSep": 0.3, + "cameraFov": 40, + "cameraType": "orthographic", + "clipDist": 10, + "clipFar": 100, + "clipNear": 0, + "fogFar": 100, + "fogNear": 50, + "hoverTimeout": 0, + "impostor": true, + "lightColor": 14540253, + "lightIntensity": 1, + "mousePreset": "default", + "panSpeed": 1, + "quality": "medium", + "rotateSpeed": 2, + "sampleLevel": 0, + "tooltip": true, + "workerDefault": true, + "zoomSpeed": 1.2 + }, + "_ngl_msg_archive": [ + { + "args": [ + { + "binary": false, + "data": "CRYST1 2.400 2.400 2.400 90.00 90.00 90.00 P 1\nMODEL 1\nATOM 1 Fe MOL 1 0.000 0.000 0.000 1.00 0.00 FE \nATOM 2 Fe MOL 1 0.600 0.600 0.600 1.00 0.00 FE \nATOM 3 Fe MOL 1 0.000 0.000 1.200 1.00 0.00 FE \nATOM 4 Fe MOL 1 0.600 0.600 1.800 1.00 0.00 FE \nATOM 5 Fe MOL 1 0.000 1.200 0.000 1.00 0.00 FE \nATOM 6 Fe MOL 1 0.600 1.800 0.600 1.00 0.00 FE \nATOM 7 Fe MOL 1 0.000 1.200 1.200 1.00 0.00 FE \nATOM 8 Fe MOL 1 0.600 1.800 1.800 1.00 0.00 FE \nATOM 9 Fe MOL 1 1.200 0.000 0.000 1.00 0.00 FE \nATOM 10 Fe MOL 1 1.800 0.600 0.600 1.00 0.00 FE \nATOM 11 Fe MOL 1 1.200 0.000 1.200 1.00 0.00 FE \nATOM 12 Fe MOL 1 1.800 0.600 1.800 1.00 0.00 FE \nATOM 13 Fe MOL 1 1.200 1.200 0.000 1.00 0.00 FE \nATOM 14 Fe MOL 1 1.800 1.800 0.600 1.00 0.00 FE \nATOM 15 Fe MOL 1 1.200 1.200 1.200 1.00 0.00 FE \nATOM 16 Fe MOL 1 1.800 1.800 1.800 1.00 0.00 FE \nENDMDL\n", + "type": "blob" + } + ], + "kwargs": { + "defaultRepresentation": true, + "ext": "pdb", + "name": "nglview.adaptor.ASETrajectory" + }, + "methodName": "loadFile", + "reconstruc_color_scheme": false, + "target": "Stage", + "type": "call_method" + }, + { + "args": [ + "spacefill" + ], + "component_index": 0, + "kwargs": { + "radius": 0.5, + "radiusType": "vdw", + "scale": 0.5, + "sele": "all" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [ + "ball+stick", + 0 + ], + "kwargs": {}, + "methodName": "removeRepresentationsByName", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [ + "unitcell" + ], + "component_index": 0, + "kwargs": { + "sele": "all" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [], + "kwargs": { + "cameraType": "orthographic" + }, + "methodName": "setParameters", + "reconstruc_color_scheme": false, + "target": "Stage", + "type": "call_method" + } + ], + "_ngl_original_stage_parameters": { + "ambientColor": 14540253, + "ambientIntensity": 0.2, + "backgroundColor": "white", + "cameraEyeSep": 0.3, + "cameraFov": 40, + "cameraType": "perspective", + "clipDist": 10, + "clipFar": 100, + "clipNear": 0, + "fogFar": 100, + "fogNear": 50, + "hoverTimeout": 0, + "impostor": true, + "lightColor": 14540253, + "lightIntensity": 1, + "mousePreset": "default", + "panSpeed": 1, + "quality": "medium", + "rotateSpeed": 2, + "sampleLevel": 0, + "tooltip": true, + "workerDefault": true, + "zoomSpeed": 1.2 + }, + "_ngl_repr_dict": { + "0": { + "0": { + "params": { + "assembly": "default", + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorMode": "hcl", + "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": 9474192, + "defaultAssembly": "", + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disableImpostor": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.5, + "radiusType": "size", + "roughness": 0.4, + "sele": "all", + "side": "double", + "sphereDetail": 1, + "useInteriorColor": true, + "visible": true, + "wireframe": false + }, + "type": "spacefill" + }, + "1": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorMode": "hcl", + "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": "orange", + "defaultAssembly": "", + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disableImpostor": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "radialSegments": 10, + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.011999999473246623, + "radiusType": "vdw", + "roughness": 0.4, + "sele": "all", + "side": "double", + "sphereDetail": 1, + "useInteriorColor": true, + "visible": true, + "wireframe": false + }, + "type": "unitcell" + } + } + }, + "_ngl_serialize": false, + "_ngl_version": "2.0.0-dev.36", + "_ngl_view_id": [ + "32F2B45E-F16D-4184-922C-AB66ECAD88AF" + ], + "_player_dict": {}, + "_scene_position": {}, + "_scene_rotation": {}, + "_synced_model_ids": [], + "_synced_repr_model_ids": [], + "_view_height": "", + "_view_width": "", + "background": "white", + "frame": 0, + "gui_style": null, + "layout": "IPY_MODEL_9875bab03c8f4bfbaec33570279cf115", + "max_frame": 21, + "n_components": 1, + "picked": {} + } + }, + "77aacc13b5bc4330afef3f15fa27c15c": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "7e14e24a4172450c9f2b8d366842a726": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "825db80ef9144c50b608e08bfc0ad89c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_d77cbd6d9a6b4f379349ba2242da96be", + "value" + ], + "target": [ + "IPY_MODEL_414a367a402644e6a24ec7f61e79689f", + "value" + ] + } + }, + "83a7f768dc3545b085ff8b5dda276104": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "85c3044bc6714b30bdff4f451f7a5dab": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_529f43bed2a445419cdaa8c9f1b748c1", + "max" + ], + "target": [ + "IPY_MODEL_76b5f43e298747c2b94d0904a444374c", + "max_frame" + ] + } + }, + "89088764b9f54bef9878c78ba26c5b42": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ImageModel", + "state": { + "layout": "IPY_MODEL_bcb522f35abc417591714281f15b6231", + "width": "900.0" + } + }, + "8a4e3529d09f4a2bab53ca611f490a74": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_01513848d0e142c19b486707145aac2b", + "max" + ], + "target": [ + "IPY_MODEL_9c1507f4d5c84e2d9f390ba74bdf4dc8", + "max_frame" + ] + } + }, + "90b9fb050b0f4966aaa958aac04a9143": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "description_width": "" + } + }, + "92336429a0a04914a5c2a0241a4c44ae": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "icon": "compress", + "layout": "IPY_MODEL_44934da01fab4ddeb34ec8fce1793432", + "style": "IPY_MODEL_6ec27eda3b6e461d821642ef264200ce" + } + }, + "9875bab03c8f4bfbaec33570279cf115": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "9c1507f4d5c84e2d9f390ba74bdf4dc8": { + "model_module": "nglview-js-widgets", + "model_module_version": "3.0.1", + "model_name": "NGLModel", + "state": { + "_camera_orientation": [ + 11.095162889756295, + 0, + 0, + 0, + 0, + 11.095162889756295, + 0, + 0, + 0, + 0, + 11.095162889756295, + 0, + -0.0010000000474974513, + 0, + -0.3755000000237487, + 1 + ], + "_camera_str": "orthographic", + "_gui_theme": null, + "_ibtn_fullscreen": "IPY_MODEL_486d6774e21240209db01f3a304dceb4", + "_igui": null, + "_iplayer": "IPY_MODEL_45e8ce58f366409db6a65035b3c7e8ea", + "_ngl_color_dict": {}, + "_ngl_coordinate_resource": {}, + "_ngl_full_stage_parameters": { + "ambientColor": 14540253, + "ambientIntensity": 0.2, + "backgroundColor": "white", + "cameraEyeSep": 0.3, + "cameraFov": 40, + "cameraType": "orthographic", + "clipDist": 10, + "clipFar": 100, + "clipNear": 0, + "fogFar": 100, + "fogNear": 50, + "hoverTimeout": 0, + "impostor": true, + "lightColor": 14540253, + "lightIntensity": 1, + "mousePreset": "default", + "panSpeed": 1, + "quality": "medium", + "rotateSpeed": 2, + "sampleLevel": 0, + "tooltip": true, + "workerDefault": true, + "zoomSpeed": 1.2 + }, + "_ngl_msg_archive": [ + { + "args": [ + { + "binary": false, + "data": "MODEL 1\nATOM 1 Fe MOL 1 0.000 -0.000 0.001 1.00 0.00 FE \nATOM 2 Fe MOL 1 0.002 -0.000 0.750 1.00 0.00 FE \nENDMDL\n", + "type": "blob" + } + ], + "kwargs": { + "defaultRepresentation": true, + "ext": "pdb", + "name": "nglview.adaptor.ASETrajectory" + }, + "methodName": "loadFile", + "reconstruc_color_scheme": false, + "target": "Stage", + "type": "call_method" + }, + { + "args": [ + "spacefill" + ], + "component_index": 0, + "kwargs": { + "radius": 0.5, + "radiusType": "vdw", + "scale": 0.5, + "sele": "all" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [ + "ball+stick", + 0 + ], + "kwargs": {}, + "methodName": "removeRepresentationsByName", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [ + "unitcell" + ], + "component_index": 0, + "kwargs": { + "sele": "all" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [], + "kwargs": { + "cameraType": "orthographic" + }, + "methodName": "setParameters", + "reconstruc_color_scheme": false, + "target": "Stage", + "type": "call_method" + } + ], + "_ngl_original_stage_parameters": { + "ambientColor": 14540253, + "ambientIntensity": 0.2, + "backgroundColor": "white", + "cameraEyeSep": 0.3, + "cameraFov": 40, + "cameraType": "perspective", + "clipDist": 10, + "clipFar": 100, + "clipNear": 0, + "fogFar": 100, + "fogNear": 50, + "hoverTimeout": 0, + "impostor": true, + "lightColor": 14540253, + "lightIntensity": 1, + "mousePreset": "default", + "panSpeed": 1, + "quality": "medium", + "rotateSpeed": 2, + "sampleLevel": 0, + "tooltip": true, + "workerDefault": true, + "zoomSpeed": 1.2 + }, + "_ngl_repr_dict": { + "0": { + "0": { + "params": { + "assembly": "default", + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorMode": "hcl", + "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": 9474192, + "defaultAssembly": "", + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disableImpostor": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.5, + "radiusType": "size", + "roughness": 0.4, + "sele": "all", + "side": "double", + "sphereDetail": 1, + "useInteriorColor": true, + "visible": true, + "wireframe": false + }, + "type": "spacefill" + }, + "1": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorMode": "hcl", + "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": "orange", + "defaultAssembly": "", + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disableImpostor": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "radialSegments": 10, + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.5, + "radiusType": "vdw", + "roughness": 0.4, + "sele": "all", + "side": "double", + "sphereDetail": 1, + "useInteriorColor": true, + "visible": true, + "wireframe": false + }, + "type": "unitcell" + } + } + }, + "_ngl_serialize": false, + "_ngl_version": "2.0.0-dev.36", + "_ngl_view_id": [ + "699B59EB-55CE-4425-ADFE-ED47C0293DDA" + ], + "_player_dict": {}, + "_scene_position": {}, + "_scene_rotation": {}, + "_synced_model_ids": [], + "_synced_repr_model_ids": [], + "_view_height": "", + "_view_width": "", + "background": "white", + "frame": 0, + "gui_style": null, + "layout": "IPY_MODEL_69e28476441f48fe95232e86889454d1", + "max_frame": 11, + "n_components": 1, + "picked": {} + } + }, + "a1c548569b5e4dd298b096a8be070a6d": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "a6fe37986c1241b09ff3ff3f085ed7c2": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "width": "34px" + } + }, + "ac63e7c8d2b84a478b194a451a0ede25": { + "model_module": "nglview-js-widgets", + "model_module_version": "3.0.1", + "model_name": "NGLModel", + "state": { + "_camera_orientation": [ + 11.095162889756295, + 0, + 0, + 0, + 0, + 11.095162889756295, + 0, + 0, + 0, + 0, + 11.095162889756295, + 0, + -0.0010000000474974513, + 0, + -0.3755000000237487, + 1 + ], + "_camera_str": "orthographic", + "_gui_theme": null, + "_ibtn_fullscreen": "IPY_MODEL_cdf89c40e3344be6afa00e1f39128993", + "_igui": null, + "_iplayer": "IPY_MODEL_32b56ca3d2714a919e6fda2bda75b840", + "_ngl_color_dict": {}, + "_ngl_coordinate_resource": {}, + "_ngl_full_stage_parameters": { + "ambientColor": 14540253, + "ambientIntensity": 0.2, + "backgroundColor": "white", + "cameraEyeSep": 0.3, + "cameraFov": 40, + "cameraType": "orthographic", + "clipDist": 10, + "clipFar": 100, + "clipNear": 0, + "fogFar": 100, + "fogNear": 50, + "hoverTimeout": 0, + "impostor": true, + "lightColor": 14540253, + "lightIntensity": 1, + "mousePreset": "default", + "panSpeed": 1, + "quality": "medium", + "rotateSpeed": 2, + "sampleLevel": 0, + "tooltip": true, + "workerDefault": true, + "zoomSpeed": 1.2 + }, + "_ngl_msg_archive": [ + { + "args": [ + { + "binary": false, + "data": "MODEL 1\nATOM 1 Fe MOL 1 0.000 -0.000 0.001 1.00 0.00 FE \nATOM 2 Fe MOL 1 0.002 -0.000 0.750 1.00 0.00 FE \nENDMDL\n", + "type": "blob" + } + ], + "kwargs": { + "defaultRepresentation": true, + "ext": "pdb", + "name": "nglview.adaptor.ASETrajectory" + }, + "methodName": "loadFile", + "reconstruc_color_scheme": false, + "target": "Stage", + "type": "call_method" + }, + { + "args": [ + "spacefill" + ], + "component_index": 0, + "kwargs": { + "radius": 0.5, + "radiusType": "vdw", + "scale": 0.5, + "sele": "all" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [ + "ball+stick", + 0 + ], + "kwargs": {}, + "methodName": "removeRepresentationsByName", + "reconstruc_color_scheme": false, + "target": "Widget", + "type": "call_method" + }, + { + "args": [ + "unitcell" + ], + "component_index": 0, + "kwargs": { + "sele": "all" + }, + "methodName": "addRepresentation", + "reconstruc_color_scheme": false, + "target": "compList", + "type": "call_method" + }, + { + "args": [], + "kwargs": { + "cameraType": "orthographic" + }, + "methodName": "setParameters", + "reconstruc_color_scheme": false, + "target": "Stage", + "type": "call_method" + } + ], + "_ngl_original_stage_parameters": { + "ambientColor": 14540253, + "ambientIntensity": 0.2, + "backgroundColor": "white", + "cameraEyeSep": 0.3, + "cameraFov": 40, + "cameraType": "perspective", + "clipDist": 10, + "clipFar": 100, + "clipNear": 0, + "fogFar": 100, + "fogNear": 50, + "hoverTimeout": 0, + "impostor": true, + "lightColor": 14540253, + "lightIntensity": 1, + "mousePreset": "default", + "panSpeed": 1, + "quality": "medium", + "rotateSpeed": 2, + "sampleLevel": 0, + "tooltip": true, + "workerDefault": true, + "zoomSpeed": 1.2 + }, + "_ngl_repr_dict": { + "0": { + "0": { + "params": { + "assembly": "default", + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorMode": "hcl", + "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": 9474192, + "defaultAssembly": "", + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disableImpostor": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.5, + "radiusType": "size", + "roughness": 0.4, + "sele": "all", + "side": "double", + "sphereDetail": 1, + "useInteriorColor": true, + "visible": true, + "wireframe": false + }, + "type": "spacefill" + }, + "1": { + "params": { + "clipCenter": { + "x": 0, + "y": 0, + "z": 0 + }, + "clipNear": 0, + "clipRadius": 0, + "colorMode": "hcl", + "colorReverse": false, + "colorScale": "", + "colorScheme": "element", + "colorValue": "orange", + "defaultAssembly": "", + "depthWrite": true, + "diffuse": 16777215, + "diffuseInterior": false, + "disableImpostor": false, + "disablePicking": false, + "flatShaded": false, + "interiorColor": 2236962, + "interiorDarkening": 0, + "lazy": false, + "matrix": { + "elements": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + }, + "metalness": 0, + "opacity": 1, + "quality": "medium", + "radialSegments": 10, + "radiusData": {}, + "radiusScale": 1, + "radiusSize": 0.5, + "radiusType": "vdw", + "roughness": 0.4, + "sele": "all", + "side": "double", + "sphereDetail": 1, + "useInteriorColor": true, + "visible": true, + "wireframe": false + }, + "type": "unitcell" + } + } + }, + "_ngl_serialize": false, + "_ngl_version": "2.0.0-dev.36", + "_ngl_view_id": [ + "4FE92B85-322A-43FB-A544-B442042ECB09" + ], + "_player_dict": {}, + "_scene_position": {}, + "_scene_rotation": {}, + "_synced_model_ids": [], + "_synced_repr_model_ids": [], + "_view_height": "", + "_view_width": "", + "background": "white", + "frame": 0, + "gui_style": null, + "layout": "IPY_MODEL_d4f483bcea5f4ca2aac4272eed22083a", + "max_frame": 11, + "n_components": 1, + "picked": {} + } + }, + "b7fef6c8c4f54afc8b0ab6cd8df3e66b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "b8bba1376ea54a03b1c8714a6ca64559": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ImageModel", + "state": { + "layout": "IPY_MODEL_377a7e31f31445758b9fe5b9d7b35922", + "width": "900.0" + } + }, + "bcb522f35abc417591714281f15b6231": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "c6092c42c17b4fadbf8ba80b0145cede": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": {} + }, + "caf558fa0d2c435389c51dceae9fe832": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "children": [ + "IPY_MODEL_529f43bed2a445419cdaa8c9f1b748c1", + "IPY_MODEL_6f48caf2815c4b6696c4a3709aa50497" + ], + "layout": "IPY_MODEL_7e14e24a4172450c9f2b8d366842a726" + } + }, + "cdf89c40e3344be6afa00e1f39128993": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "icon": "compress", + "layout": "IPY_MODEL_a6fe37986c1241b09ff3ff3f085ed7c2", + "style": "IPY_MODEL_10be40fdff5d48f59854385a9e7437e0" } }, - "ed7d7881cda84e47a33b538e45e24bff": { + "ce093890ee6649b1a970930fbdbbe878": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "description_width": "" + } + }, + "cf19b664d4d9466eae5313a530dc4c62": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ImageModel", + "state": { + "layout": "IPY_MODEL_77aacc13b5bc4330afef3f15fa27c15c", + "width": "900.0" + } + }, + "d03c7a1320874169b1cb7f506bedf1d3": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "IntSliderModel", + "state": { + "layout": "IPY_MODEL_46e9ec85470c49bbae783ccb989baac6", + "max": 11, + "style": "IPY_MODEL_dcd8bcf8e7064370aafdaf883581e209" + } + }, + "d219818914194ca796dcd6620d67e1da": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": {} + }, + "d4f483bcea5f4ca2aac4272eed22083a": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} + }, + "d77cbd6d9a6b4f379349ba2242da96be": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "PlayModel", + "state": { + "layout": "IPY_MODEL_3027a3adbccc481d8e44bd6a73c2f3b5", + "max": 11, + "style": "IPY_MODEL_ce093890ee6649b1a970930fbdbbe878" + } + }, + "dcd8bcf8e7064370aafdaf883581e209": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "SliderStyleModel", + "state": { + "description_width": "" + } + }, + "f21b59c26c3a4bfab6b5c4c3aeccb961": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_414a367a402644e6a24ec7f61e79689f", + "max" + ], + "target": [ + "IPY_MODEL_ac63e7c8d2b84a478b194a451a0ede25", + "max_frame" + ] + } + }, + "f2d3d7b0885944d09a1d9723dc507b5e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_d77cbd6d9a6b4f379349ba2242da96be", + "value" + ], + "target": [ + "IPY_MODEL_ac63e7c8d2b84a478b194a451a0ede25", + "frame" + ] + } + }, + "f3319be68c4944e9832bdd57da448d71": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_01513848d0e142c19b486707145aac2b", + "value" + ], + "target": [ + "IPY_MODEL_9c1507f4d5c84e2d9f390ba74bdf4dc8", + "frame" + ] + } + }, + "f44ec76bf085463d85525d2c8363acca": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LinkModel", + "state": { + "source": [ + "IPY_MODEL_6f48caf2815c4b6696c4a3709aa50497", + "max" + ], + "target": [ + "IPY_MODEL_76b5f43e298747c2b94d0904a444374c", + "max_frame" + ] + } } }, "version_major": 2, diff --git a/pyiron_contrib/tinybase/database.py b/pyiron_contrib/tinybase/database.py index 5f2f8312a..c694064bc 100644 --- a/pyiron_contrib/tinybase/database.py +++ b/pyiron_contrib/tinybase/database.py @@ -16,6 +16,7 @@ ) from sqlalchemy.orm import declarative_base, relationship from sqlalchemy.orm import Session +from sqlalchemy.pool import StaticPool import pandas as pd @@ -103,15 +104,22 @@ class TinyDB(GenericDatabase): def __init__(self, path, echo=False): self._path = path self._echo = echo - engine = self.get_engine() - Base.metadata.create_all(engine) - Base.metadata.reflect(engine, extend_existing=True) - - def get_engine(self): - return create_engine(f"sqlite:///{self._path}", echo=self._echo) + kwargs = {} + if path in (":memory:", ""): + # this allows to access the same DB from the different threads in one process + # it's necessary for an in memory database, otherwise all threads see different dbs + kwargs["poolclass"] = StaticPool + kwargs["connect_args"] = {'check_same_thread':False} + self._engine = create_engine(f"sqlite:///{self._path}", echo=self._echo, **kwargs) + Base.metadata.create_all(self.engine) + Base.metadata.reflect(self.engine, extend_existing=True) + + @property + def engine(self): + return self._engine def add_item(self, entry: DatabaseEntry) -> int: - with Session(self.get_engine()) as session: + with Session(self.engine) as session: project = session.query(Project).where(Project.location==entry.project).one_or_none() if project is None: project = Project(location=entry.project) @@ -137,7 +145,7 @@ def add_item(self, entry: DatabaseEntry) -> int: return job_id def update_status(self, job_id, status): - with Session(self.get_engine()) as session: + with Session(self.engine) as session: try: s = session.query(JobStatus).select_from(Job).where( Job.id == job_id, @@ -158,7 +166,7 @@ def _row_to_entry(self, job_data): ) def get_item(self, job_id: int) -> DatabaseEntry: - with Session(self.get_engine()) as session: + with Session(self.engine) as session: job_data = session.query( Job.__table__, Project.location, JobStatus.status, JobType.type ).select_from( @@ -175,7 +183,7 @@ def get_item(self, job_id: int) -> DatabaseEntry: return self._row_to_entry(job_data) def get_item_id(self, job_name: str, project_id: int) -> Optional[int]: - with Session(self.get_engine()) as session: + with Session(self.engine) as session: try: return session.query(Job.id).where( Job.name == job_name, @@ -185,7 +193,7 @@ def get_item_id(self, job_name: str, project_id: int) -> Optional[int]: return None def get_project_id(self, location: str) -> Optional[int]: - with Session(self.get_engine()) as session: + with Session(self.engine) as session: try: return session.query(Project.id).where(Project.location == location).one().id # FIXME: MultipleResultsFound should be reraised because it indicates a broken database @@ -195,14 +203,14 @@ def get_project_id(self, location: str) -> Optional[int]: def remove_item(self, job_id: int) -> DatabaseEntry: # FIXME: probably a bit inefficient, because it makes two connections to the DB entry = self.get_item(job_id) - with Session(self.get_engine()) as session: + with Session(self.engine) as session: job = session.get(Job, job_id) session.delete(job) session.commit() return entry def job_table(self) -> pd.DataFrame: - with Session(self.get_engine()) as session: + with Session(self.engine) as session: query = session.query( Job.__table__, Project.location, JobStatus.status, JobType.type ).select_from( diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index a1ff84a3d..3a1674fc5 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -79,10 +79,7 @@ def _get_output(self): def _execute(self, output): output.base_structure = self.input.structure - ret = super()._execute(output) - I = np.argsort(output.volumes) - output.volumes = output.volumes[I] - output.energies = output.energies[I] + return super()._execute(output) def _extract_output(self, output, i, node, ret, node_output): if len(output.energies) == 0: diff --git a/pyiron_contrib/tinybase/project.py b/pyiron_contrib/tinybase/project.py index a99066f73..518cb1f53 100644 --- a/pyiron_contrib/tinybase/project.py +++ b/pyiron_contrib/tinybase/project.py @@ -1,8 +1,8 @@ import abc import os.path -from pyiron_base import Project -from pyiron_contrib.tinybase.storage import GenericStorage, ProjectHDFioStorageAdapter +from pyiron_base import Project, DataContainer +from pyiron_contrib.tinybase.storage import GenericStorage, ProjectHDFioStorageAdapter, DataContainerAdapter from pyiron_contrib.tinybase.database import TinyDB, GenericDatabase class ProjectInterface(abc.ABC): @@ -53,6 +53,22 @@ def path(self): def name(self): pass + def job_table(self): + return self.database.job_table() + + def get_job_id(self, name): + project_id = self.database.get_project_id(self.path) + return self.database.get_item_id(name, project_id) + + def remove(self, job_id): + entry = self.database.remove_item(job_id) + if entry.project == self.path: + pr = self + else: + pr = self.open_location(entry.project) + pr.remove_storage(entry.name) + + class ProjectAdapter(ProjectInterface): def __init__(self, project): @@ -75,10 +91,6 @@ def exists_storage(self, name) -> bool: def remove_storage(self, name): self._project.create_hdf(self._project.path, name).remove_file() - def remove(self, job_id): - entry = self.database.remove_item(job_id) - self.remove_storage(entry.name) - def _get_database(self): if self._database is None: self._database = TinyDB(os.path.join(self._project.path, "pyiron.db")) @@ -92,9 +104,38 @@ def name(self): def path(self): return self._project.path - def job_table(self): - return self.database.job_table() +class InMemoryProject(ProjectInterface): - def get_job_id(self, name): - project_id = self.database.get_project_id(self.path) - return self.database.get_item_id(name, project_id) + def __init__(self, location, db=None, storage=None): + if db is None: + db = TinyDB(":memory:") + self._db = db + self._location = location + if storage is None: + storage = {} + self._storage = storage + if location not in storage: + self._storage[location] = DataContainer() + + def open_location(self, location): + return self.__class__(location, db=self.database, storage=self._storage) + + def create_storage(self, name) -> GenericStorage: + return DataContainerAdapter(self, self._storage[self._location], "/").create_group(name) + + def exists_storage(self, name) -> bool: + return name in self._storage[self._location].list_groups() + + def remove_storage(self, name): + self._storage[self._location].pop(name) + + def _get_database(self) -> GenericDatabase: + return self._db + + @property + def path(self): + return self._location + + @property + def name(self): + return os.path.basename(self.path) diff --git a/pyiron_contrib/tinybase/storage.py b/pyiron_contrib/tinybase/storage.py index 0b13920b2..ca0cab251 100644 --- a/pyiron_contrib/tinybase/storage.py +++ b/pyiron_contrib/tinybase/storage.py @@ -2,6 +2,7 @@ import importlib from typing import Any, Union, Optional +from pyiron_base import DataContainer from pyiron_contrib.tinybase import __version__ as base__version__ import pickle @@ -190,6 +191,48 @@ def project(self): def name(self): return self._hdf.name +class DataContainerAdapter(GenericStorage): + """ + Provides in memory location to store objects. + """ + + def __init__(self, project, cont: DataContainer, name): + self._project = project + self._cont = cont + self._name = name + + def __getitem__(self, item: str) -> Union["GenericStorage", Any]: + v = self._cont[item] + if isinstance(v, DataContainer): + return self.__class__(self._project, v, item) + else: + return v + + def __setitem__(self, item: str, value: Any): + self._cont[item] = value + + def create_group(self, name): + if name not in self._cont: + d = self._cont.create_group(name) + else: + d = self._cont[name] + return self.__class__(self._project, d, name) + + + def list_nodes(self): + return self._cont.list_nodes() + + def list_groups(self): + return self._cont.list_groups() + + @property + def project(self): + return self._project + + @property + def name(self): + return self._name + # DESIGN: equivalent of HasHDF but with generalized language class Storable(abc.ABC): """ @@ -262,3 +305,4 @@ def _restore(cls, storage, version): obj = cls(**kw) obj._from_hdf(storage, version) return obj + From 6ad06b6df1db00602c66ae6bf80a367a7b3feabd Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 13 Mar 2023 15:00:00 +0100 Subject: [PATCH 033/756] Do not rename argument of inherited method --- pyiron_contrib/tinybase/murn.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index 3a1674fc5..919aff65c 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -81,11 +81,11 @@ def _execute(self, output): output.base_structure = self.input.structure return super()._execute(output) - def _extract_output(self, output, i, node, ret, node_output): + def _extract_output(self, output, step, node, ret, node_output): if len(output.energies) == 0: output.energies = np.zeros(len(self.input.strains)) if len(output.volumes) == 0: output.volumes = np.zeros(len(self.input.strains)) if ret.is_done(): - output.energies[i] = node_output.energy_pot - output.volumes[i] = node.input.structure.get_volume() + output.energies[step] = node_output.energy_pot + output.volumes[step] = node.input.structure.get_volume() From fcdfd977a0b8c8d5017436c6c9fbdd4b2fdece0c Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 13 Mar 2023 15:00:29 +0100 Subject: [PATCH 034/756] Add some docstrings to node module --- pyiron_contrib/tinybase/node.py | 89 ++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/node.py b/pyiron_contrib/tinybase/node.py index bc396bbf8..bb6c71bbb 100644 --- a/pyiron_contrib/tinybase/node.py +++ b/pyiron_contrib/tinybase/node.py @@ -14,6 +14,9 @@ ) class ReturnStatus: + """ + Status of the calculation. + """ class Code(enum.Enum): DONE = "done" @@ -30,10 +33,19 @@ def __repr__(self): def __str__(self): return f"{self.code}({self.msg})" - def is_done(self): + def is_done(self) -> True: + """ + Returns True if calculation was successful. + """ return self.code == self.Code.DONE class AbstractNode(Storable, abc.ABC): + """ + Basic unit of calculations. + + Subclasses must implement :meth:`._get_input()`, :meth:`._get_output()` and :meth:`._execute()` and generally supply + their own :class:`.AbstractInput` and :class:`.AbstractOutput`. + """ _executors = { 'foreground': Executor, @@ -46,6 +58,11 @@ def __init__(self): @abc.abstractmethod def _get_input(self) -> AbstractInput: + """ + Return an instance of the input class. + + This is called once per life time of the node object on first access to :attr:`.input` and then saved. + """ pass @property @@ -56,10 +73,29 @@ def input(self) -> AbstractInput: @abc.abstractmethod def _get_output(self) -> AbstractOutput: + """ + Return an instance of the output class. + + This is called every time :meth:`.execute()` is called. + """ pass @abc.abstractmethod def _execute(self, output) -> Optional[ReturnStatus]: + """ + Run the calculation. + + Every time this method is called a new instance of the output is created and passed as the argument. This + method should populate it. + + If no value is returned from the method, a return status of DONE is assumed implicitly. + + Args: + output (:class:`.AbstractOutput`): instance returned by :meth:`._get_output()`. + + Returns: + :class:`.ReturnStatus`: optional + """ pass def execute(self) -> ReturnStatus: @@ -93,6 +129,12 @@ def _restore(cls, storage, version): FunctionInput = AbstractInput.from_attributes("FunctionInput", args=list, kwargs=dict) FunctionOutput = AbstractOutput.from_attributes("FunctionOutput", "result") class FunctionNode(AbstractNode): + """ + A node that wraps a generic function. + + The given function is called with :attr:`.FunctionInput.args` and :attr:`.FunctionInput.kwargs` as `*args` and + `**kwargs` respectively. The return value is set to :attr:`.FunctionOutput.result`. + """ def __init__(self, function): super().__init__() @@ -113,18 +155,44 @@ def _execute(self, output): ) class ListInput(MasterInput, abc.ABC): + """ + The input of :class:`.ListNode`. + + To use it overload :meth:`._create_nodes()` here and subclass :class:`.ListNode` as well. + """ @abc.abstractmethod def _create_nodes(self): + """ + Return a list of nodes to execute. + + This is called once by :class:`.ListNode.execute`. + """ pass class ListNode(AbstractNode, abc.ABC): + """ + A node that executes other nodes in parallel. + + To use it overload :meth:`._extract_output` here and subclass :class:`.ListInput` as well. + """ def __init__(self): super().__init__() @abc.abstractmethod def _extract_output(self, output, step, node, ret, node_output): + """ + Extract the output of each node. + + Args: + output (:class:`.AbstractOutput`): output of this node to populate + step (int): index of the node to extract the output from, corresponds to the index of the node in the list + returned by :meth:`.ListInput._create_nodes()`. + node (:class:`.AbstractNode`): node to extract the output from, you can use this to extract parts of the input as well + ret (:class:`.ReturnStatus`): the return status of the execution of the node + node_output (:class:`.AbstractOutput`): the output of the node to extract + """ pass def _execute(self, output): @@ -143,6 +211,19 @@ def _execute(self, output): ) class SeriesInput(SeriesInputBase, MasterInput): + """ + Keeps a list of nodes and their connection functions to run sequentially. + + The number of added nodes must be equal to the number of connections plus one. It's recommended to set up this + input with :meth:`.first()` and :meth:`.then()` which can be composed in a builder pattern. The connection + functions take as arguments the output of the last node and the input of the next node. You may call + :meth:`.then()` any number of times. + + >>> node = SeriesNode() + >>> def transfer(input, output): + ... input.my_param = output.my_result + >>> node.input.first(MyNode()).then(MyNode(), transfer) + """ def check_ready(self): return len(self.nodes) == len(connections) + 1 @@ -178,6 +259,12 @@ def then(self, next_node, connection): return self class SeriesNode(AbstractNode): + """ + Executes a series of nodes sequentially. + + Its input specifies the nodes to execute and functions (:attr:`.SeriesInput.connections`) to move input from one + output to the next input in the series. + """ def _get_input(self): return SeriesInput() From de96bb96dd9db8a2f8c8bd3ab88492c5641573ed Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 14 Apr 2023 19:53:25 +0200 Subject: [PATCH 035/756] Rename Node to Task --- notebooks/tinybase/ASE.ipynb | 2097 +++--------------- notebooks/tinybase/Basic.ipynb | 388 ++-- notebooks/tinybase/TinyJob.ipynb | 581 ++--- pyiron_contrib/tinybase/ase.py | 8 +- pyiron_contrib/tinybase/executor.py | 46 +- pyiron_contrib/tinybase/job.py | 90 +- pyiron_contrib/tinybase/murn.py | 30 +- pyiron_contrib/tinybase/{node.py => task.py} | 126 +- 8 files changed, 960 insertions(+), 2406 deletions(-) rename pyiron_contrib/tinybase/{node.py => task.py} (74%) diff --git a/notebooks/tinybase/ASE.ipynb b/notebooks/tinybase/ASE.ipynb index 81de19372..ff87da6ec 100644 --- a/notebooks/tinybase/ASE.ipynb +++ b/notebooks/tinybase/ASE.ipynb @@ -10,9 +10,11 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "f9ecb713-99e6-4f34-b4c2-fe9f3d96c81e", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "from ase import Atoms" @@ -20,9 +22,11 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "31dc7658-dcf0-41a4-9c62-ec92b02e47ea", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "from ase.build import bulk" @@ -30,9 +34,11 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "e7cc88a5-8492-482e-8c6b-c17ff967fff5", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "from ase.calculators.morse import MorsePotential" @@ -40,50 +46,35 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "b3108213-1d94-4354-9537-84982e45683d", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/ponder/science/phd/dev/pyiron_contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n", - " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "d3dba68b040c436a87aa1acf4b90a1c9", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ - "from pyiron_contrib.tinybase.ase import AseStaticNode, AseMDNode, AseMinimizeNode" + "from pyiron_contrib.tinybase.ase import AseStaticTask, AseMDTask, AseMinimizeTask" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "0029125a-55e6-4181-a59b-09f606a1b4dd", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "from pyiron_contrib.tinybase.murn import MurnaghanNode" + "from pyiron_contrib.tinybase.murn import MurnaghanTask" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "c7c74920-c6b3-4577-a60f-951a0d3276ec", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "from pyiron_contrib.tinybase.executor import ProcessExecutor, BackgroundExecutor, Executor" @@ -91,9 +82,11 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "c6630920-6ab7-4273-883e-999020b1fe5a", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "import logging\n", @@ -110,19 +103,23 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "a6af72cb-989b-46c3-a2b5-4d2b9c5fd1eb", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "a = AseStaticNode()" + "a = AseStaticTask()" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "5b2a9d62-3f74-4acf-acb6-e72dcd984704", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "a.input.structure = bulk(\"Fe\")" @@ -130,9 +127,11 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "1af70322-897e-487d-ba18-239ba5bfb7ba", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "a.input.calculator = MorsePotential()" @@ -140,64 +139,36 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "273902ef-03f3-4f68-8668-4e6c6055a302", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(ReturnStatus(Code.DONE, None),\n", - " )" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "ret, output = a.execute(); ret, output" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "497de0b9-5e11-4d6c-8c19-664d0e759ac4", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "-0.00013307075712109978" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "output.energy_pot" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "57eced2f-6649-4269-b3fa-6061d518f9ee", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "-0.00013307075712109978" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe = a.run()\n", "exe.output[0].energy_pot" @@ -213,19 +184,23 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "id": "02cfe01b-0b24-4723-a79b-d41ffb146bf9", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "md = AseMDNode()" + "md = AseMDTask()" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "466d1f9a-b707-4c05-a8af-5414d76bd8eb", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "md.input.structure = bulk(\"Fe\", a=1.2, cubic=True).repeat(3)\n", @@ -234,9 +209,11 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "id": "dfdfc027-1608-43ad-9d15-0c649986eb73", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "md.input.steps = 100\n", @@ -247,19 +224,12 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "id": "db5c7cfe-b075-483e-8b7e-a58cebf1a782", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 8.76 ms, sys: 29.8 ms, total: 38.5 ms\n", - "Wall time: 38.1 ms\n" - ] - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "%%time\n", "exe = md.run(how='process')" @@ -267,9 +237,11 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "80155255-4dcf-48cb-9825-015da13d6ac0", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "exe.wait()" @@ -277,131 +249,72 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "id": "6f7aff4e-9e89-459b-843f-46a4d4139bcf", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'status': [ReturnStatus(Code.DONE, None)],\n", - " 'output': []}" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe._run_machine._data" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "id": "62ce8439-bf95-4818-b35c-b4e2ef649bd2", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe._run_machine.state" ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "id": "5bcd1b68-6a48-4a08-92d4-143419071618", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "15.664004709979054" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe._run_time" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "id": "d21371e0-fa36-44bd-b7bf-a0092177ba17", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2.8318027034401894e-05" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe._collect_time" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "id": "9e06cd6d-e0f7-40dd-93f2-777f86ffe2eb", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAhYAAAGdCAYAAABO2DpVAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy88F64QAAAACXBIWXMAAA9hAAAPYQGoP6dpAABlL0lEQVR4nO3dd3xT1fsH8E9Gm+4FnbSUVfaSsodsFARBBVFUQAVEEUGc4MIF6veHoqKoiAgiS5GhAgqy994buqDQQkt3m7bJ/f1xkpZCV9qb3KT9vF+vvkjb5N6ntEmee85znqOSJEkCERERkQzUSgdAREREVQcTCyIiIpINEwsiIiKSDRMLIiIikg0TCyIiIpINEwsiIiKSDRMLIiIikg0TCyIiIpKN1tYnNBqNiI+Ph6enJ1Qqla1PT0RERBUgSRLS09MREhICtbrkcQmbJxbx8fEICwuz9WmJiIhIBnFxcQgNDS3x+zZPLDw9PQGIwLy8vGx9eiIiIqqAtLQ0hIWFFbyPl8TmiYV5+sPLy4uJBRERkYMpq4yBxZtEREQkGyYWREREJBsmFkRERCQbm9dYEBERKUmSJOTn58NgMCgdil3RaDTQarWVbgXBxIKIiKqN3NxcXLt2DVlZWUqHYpfc3NwQHBwMZ2fnCh+DiQUREVULRqMRUVFR0Gg0CAkJgbOzMxs1mkiShNzcXNy4cQNRUVGIiIgotQlWaZhYEBFRtZCbmwuj0YiwsDC4ubkpHY7dcXV1hZOTE2JiYpCbmwsXF5cKHYfFm0REVK1U9Eq8OpDj/4b/u0RERCQbJhZEREQkGyYWREREVdjPP/8MHx8fm52PiQURERHJpuqsCtn8MaBPl/+4Gieg7dOAXz35j01ERFSGHj16oHnz5gCAxYsXQ6PR4Pnnn8eHH34IlUqFW7duYdKkSfjzzz+h1+vRvXt3fPXVV4iIiMDWrVvx9NNPAyjcPOy9997D9OnTrRZv1UksDi8CMq5b59hp8cDQ+dY5NhERKUKSJGTnKdN909VJY1EPjYULF+LZZ5/Fvn37cPDgQYwbNw7h4eEYO3YsRo8ejQsXLmDt2rXw8vLCG2+8gQEDBuD06dPo3LkzZs+ejXfffRfnzp0DAHh4eFjrxwJQlRKLDs8BuRnyHjMlFjjxG3DznLzHJSIixWXnGdD03X8UOffpD+6Dm3P534LDwsLwxRdfQKVSoVGjRjhx4gS++OIL9OjRA2vXrsWuXbvQuXNnAMCvv/6KsLAwrF69GsOGDYO3tzdUKhWCgoKs9eMUUXUSi25T5D/mzQsisUiOAiQJYIc2IiJSQMeOHYuMcHTq1AmzZs3C6dOnodVq0aFDh4Lv1ahRA40aNcKZM2eUCLUKJRbW4FMbUKnFSEjmDcAjQOmIiIhIJq5OGpz+4D7Fzm1NkiQp1q6ciUVptDrAO1RMiSRfZmJBRFSFqFQqi6YjlLR37967Po+IiEDTpk2Rn5+Pffv2FUyFJCUl4fz582jSpAkAwNnZ2aY7uXK5aVnMq0GSLysbBxERVVtxcXGYMmUKzp07h6VLl+Lrr7/GpEmTEBERgcGDB2Ps2LHYuXMnjh07hieffBK1atXC4MGDAQB16tRBRkYG/vvvP9y8edPqO7sysSgLEwsiIlLYyJEjkZ2djfbt22PChAmYOHEixo0bBwBYsGABIiMjMXDgQHTq1AmSJGHdunVwcnICAHTu3Bnjx4/H8OHD4e/vj88++8yqsTrGGJCSmFgQEZHCnJycMHv2bMydO/eu7/n6+mLRokWlPn7u3LnFPtYaOGJRFiYWRERE5cbEoizmxCLpslhySkRERCXiVEhZfOuIf/WpQPYtwM1P0XCIiKh62bp1q9IhWIQjFmVxcgW8aonbnA4hIiIqFROL8mCdBRERUbkwsSgPv7riXyYWREREpbIosahTpw5UKtVdHxMmTLBWfPaBIxZERETlYlHx5oEDB4q0BT158iT69u2LYcOGyR6YXWFiQUREVC4WJRb+/v5FPv/kk09Qv359dO/eXdag7A4TCyIionKpcI1Fbm4uFi9ejGeeeUaxHdRsxtdUY5GVBGSnKBoKERFVLz169MDkyZOL/d7o0aMxZMgQm8ZTlgr3sVi9ejVSUlIwevToUu+n1+uh1+sLPk9LS6voKZWj8wA8AoGMBOBWFOB6j9IRERER4csvv4RkZ80bKzxiMX/+fPTv3x8hISGl3m/mzJnw9vYu+AgLC6voKZXF6RAiIrIz3t7e8PHxUTqMIiqUWMTExGDTpk0YM2ZMmfedOnUqUlNTCz7i4uIqckrlMbEgIiI7sGHDBnh7e2PRokV3TYX06NEDL730El5//XX4+fkhKCgI06dPt2l8FZoKWbBgAQICAvDAAw+UeV+dTgedTleR09iXgl4WUcrGQURE8pAkIC9LmXM7uQEVqE9ctmwZxo0bh19++QWDBw/G5s2b77rPwoULMWXKFOzbtw979uzB6NGj0aVLF/Tt21eOyMtkcWJhNBqxYMECjBo1ClptNdpqhCMWRERVS14WMKP06XyrmRYPOLtb9JBvv/0W06ZNw5o1a9CzZ88S79eyZUu89957AICIiAjMmTMH//33n/0mFps2bUJsbCyeeeYZa8Rjv5hYEBGRQlauXImEhATs3LkT7du3L/W+LVu2LPJ5cHAwEhMTrRleERYnFv369bO7ClSbMC85zUgA9BlipQgRETkuJzcxcqDUuS3QunVrHD58GAsWLEC7du1KbfPg5ORU5HOVSgWj0VihMCuiGs1lVJKrD+BWQ/SyuBUFBLVQOiIiIqoMlcri6Qil1K9fH7NmzUKPHj2g0WgwZ84cpUMqETchswSnQ4iISCENGzbEli1bsHLlyhIbZtkDjlhYwq8ecOUAEwsiIlJEo0aNsHnz5oKRC3vExMISHLEgIiIb27p1a5HPmzRpgoSEhHLdFxCdsm2JUyGWKEgs2MuCiIioOEwsLMERCyIiolIxsbCEObFIuwrkZSsbCxERkR1iYmEJNz/AxUfcvhWtZCRERER2iYmFpTgdQkREVCImFpZiYkFE5NCqZffocpLj/4aJhaWYWBAROSRzq+usLIV2NHUA5v+bO9uCW4J9LCxlTiySLikbBxERWUSj0cDHx6dgQy43N7dS99yoTiRJQlZWFhITE+Hj41Op5ltMLCzFXhZERA4rKCgIAGy626cj8fHxKfg/qigmFpYyJxapcUC+HtDqlI2HiIjKTaVSITg4GAEBAcjLy1M6HLvi5OQkS5twJhaWcq8JOHsCuenArRjAv6HSERERkYU0Go3d7rXh6Fi8aSmVCvCrK26zgJOIiKgIJhYVwZUhRERExWJiURFMLIiIiIrFxKIimFgQEREVi4lFRTCxICIiKhYTi4owJxYpsYCBy5WIiIjMmFhUhGcQoHUFJINILoiIiAgAE4uKUanYgZOIiKgYTCwqir0siIiI7sLEoqJYwElERHQXJhYVxcSCiIjoLkwsKoqJBRER0V2YWFSUObG4FQ0YDYqGQkREZC+YWFSUVy1AowOMeUDqFaWjISIisgtMLCpKrQZ864jbnA4hIiICwMSiclhnQUREVAQTi8pgYkFERFQEE4vKKGiSxe6bREREABOLyuGIBRERURFMLCqjYMlpFGA0KhsLERGRHbA4sbh69SqefPJJ1KhRA25ubmjdujUOHTpkjdjsn3cYoNYC+TlA+jWloyEiIlKc1pI737p1C126dEHPnj2xfv16BAQE4NKlS/Dx8bFSeHZOowV8woHkS2I6xLuW0hEREREpyqLE4tNPP0VYWBgWLFhQ8LU6derIHZNj8atXmFjU7aZ0NERERIqyaCpk7dq1aNu2LYYNG4aAgADcc889mDdvXqmP0ev1SEtLK/JRpbCAk4iIqIBFicXly5cxd+5cRERE4J9//sH48ePx0ksvYdGiRSU+ZubMmfD29i74CAsLq3TQdoWJBRERUQGVJElSee/s7OyMtm3bYvfu3QVfe+mll3DgwAHs2bOn2Mfo9Xro9fqCz9PS0hAWFobU1FR4eXlVInQ7cf5fYMkwILAF8PxOpaMhIiKyirS0NHh7e5f5/m3RiEVwcDCaNm1a5GtNmjRBbGxsiY/R6XTw8vIq8lGl3D5iUf4cjYiIqEqyKLHo0qULzp07V+Rr58+fR3h4uKxBORSf2oBKDeRlAhmJSkdDRESkKIsSi5dffhl79+7FjBkzcPHiRSxZsgQ//PADJkyYYK347J/WWfSzAFhnQURE1Z5FiUW7du2watUqLF26FM2bN8eHH36I2bNn44knnrBWfI6BBZxEREQALOxjAQADBw7EwIEDrRGL4/KrB1zewsSCiIiqPe4VIgeOWBAREQFgYiEPJhZEREQAmFjIg0tOiYiIADCxkIdvHQAqQJ8GZCUpHQ0REZFimFjIwckF8A4VtzkdQkRE1RgTC7n41RX/MrEgIqJqjImFXFjASURExMRCNkwsiIiImFjIhokFEREREwvZMLEgIiJiYiEb3zri3+xbQFayoqEQEREphYmFXJzdAc9gcftWlLKxEBERKYSJhZwKpkOYWBARUfXExEJO7GVBRETVHBMLObGAk4iIqjkmFnJiYkFERNUcEws5MbEgIqJqjomFnHxNNRaZN4CcNGVjISIiUgATCzm5eAHu/uI2l5wSEVE1xMRCbpwOISKiaoyJhdyYWBARUTXGxEJuTCyIiKgs0buA358FrhxSOhLZaZUOoMph900iIipJXg6w+UNgzzcAJODKAWDCfsDJRenIZMMRC7mx+yYRERXn6mHg+3uBPXMASICTG5ASA+z7TunIZMXEQm7mEYv0a0BuprKxEBGR8gx5wJYZwI99gJvnAI9AYMQKYOAX4vvb/w/ISFQ2RhkxsZCbq6/4AIBb0YqGQkRECks8A/zYG9j2KSAZgGYPAy/sBRreB7R4FAi5B8hNB7Z8rHSksmFiYQ0s4CQiqt6MBmDXV8D33YFrx8QF59CfgGELADc/cR+1Grhvprh9eBFw/aRy8cqIiYU1MLEgIqq+kqOAnwcCG98BDHogop8YpWj+yN33De8ENHsIkIzAP9MASbJ9vDJjYmENTCyIiKofSQIO/gTM7QLE7gacPYBBX4l6Cs+gkh/X531AowOitgHn1tsuXithYmENTCyIiKqXtHjg16HAXy8DeZlAeFfg+V1A5ChApSr9sb7hQKcJ4va/bwP5udaP14qYWFgDe1lQWYyGKjHkSVTtSRJw/Dfg247AxU1i5OG+GcCoPwHfOuU/TrcpgHsAkHwJODDPauHaAhMLazAnFqlXRDMUottdOwZ82UosPauuu+Dm64HEs0pHQVQ5mUnAb6OAP8YAOalihcf4HWL0QW3h26vOE+j9jri97VMgK1n+eG2EiYU1uNUAdF4AJNH8hMgsdh/w8yAgNQ64ehD4/RkxelGdZKeI5XffdgD2Vq3GQFSNnFsvRilOrwHUWqDHNODZjYB/o4ofs/UTQFALkaRsnSlfrDbGxMIaVCp24LS1vBxgyXBgyWP222jm0hbglyGAPhUIaQNoXYGLG4F/31E6MtvRZwC/DgOunxCfb3yn8DaRI8hJBVa/ACx9DMhMBPwbA2M2AT3eADROlTu2WiOmUQDgwHyHHdWzKLGYPn06VCpVkY+goFIqXaszFnDa1s7PgfMbgPPrgXm9gIRTSkdU1Nl1wJJHgbwsoH5vYPTfwENzxff2fgMcWqhsfLaQlwMsexy4sh9w8QHCuwCGXGDlGCAvW+noyJokCUi/DsTuBY4tB7Z+Cqx6Hlj/ppgWcxSXt4kVH0d/BaACOk8Exm0TUyByqXsv0HigaKb179vyHdeGLN6ErFmzZti0aVPB5xqNRtaAqgwmFrZz4zyw43Nx291fTDPMv080oonoq2xsAHDid+CPceKFoskg4JH5gFYn1q7fOA9snQH8PUX8zdTtpnS01mHIA34bDURtF0vwnvxDVMLP7QzcOCteQB+YpXSUVBn6DDH1eysauGX+1/SREgvkl5A8uvqKq317lpsFbJoO7P9efO5bBxgyFwjvbJ3z9f0AOP+PGNG8sAmI6GOd81iJxYmFVqvlKEV5MLGwDUkC/poMGPOAiPuAh74Dlj8FxOwUIwT3fwJ0eE65+A79DPw5GYAEtHoceHAOoLntadf9dbF3wMmVwIqngLGbC/92qgqjAVj1nBhN0roAI5YDoZHie0PmAosfBg78CDToAzTqr2ysxbl2HIjZBUQ+XaV2oLSY0QCkXb0tYbgjeci6WfrjVWrAOxTwCRdvzGotcGgBsOP/ROOomg2s/RNUTH4usHAgcNW0vXnk00C/jwCdh/XOWaO+eN3aM0c0zarXo+jrhp2zONILFy4gJCQEOp0OHTp0wIwZM1CvXskvhHq9Hnp94VBXWlo1qYJnYmEbR38VL/pObsCA/4lWuU+tEmvJjy4G1r8O3LwgEgxbPzF3zwH+fUvcbjcG6P+/uyvFVSpg8DdiaXL8YVEjMmYj4OJt21itxZz4nVwJqJ2A4YuBOl0Lv9+gN9BxgpgOWjMBeH536Y2EbC3hFLBggNjLIeEUMHiO0hHZ3q1oYNkTYmTJmF/6fV19RdJgTh5864iRKd86gHdY0RoESRKjixc3ib+RUX+W3e9BCTu/EEmFi48YbbTV6MG9rwHHlooLj0MLgPZjbXNeGagkqfyL6devX4+srCw0bNgQCQkJ+Oijj3D27FmcOnUKNWrUKPYx06dPx/vvv3/X11NTU+Hl5VXxyO1d+nVgViORpb+VAGidlY6o6sm8CcxpC2TfAvp+CHR5qfB7kgTs+lIMX0ISV8NDFwAuNvibkyRg6yfAtk/E510mA32ml/6imXZN1Iakx4sajBErHOoKpViSJK629n4rngfDfgaaDr77fvl6YF5vIOEEUL8X8MRKy5fqWUN6gli9khpX+LUhc4HWI5SLSQl/jAOOLxe3Nc6AT+3ikwefcMDVx7Jj34oGvukopkns8f824ZTY68OYJ5KKFkNte/4DPwJ/vwK4+gEvHS7c4FIhaWlp8Pb2LvP926LE4k6ZmZmoX78+Xn/9dUyZMqXY+xQ3YhEWFlb1EwtJAmaEiGK9iYfF0BbJa9V4kdEHtgDGbSm+IvvMn8DKseKFy7+JGIb3DbdeTJIk6gX2mK5se70D3Ptq+R4bfxT46X4Ra4fngf6fWC1Mm9gyQ6zHB8p+00g8C/zQHcjPEVXx5i6ESsnLFns9XD0I+NUHGg8Adn8tVvKM3QwENlU2Plu5eRH4pp3Yx2LUX6KmQC1zXd3O2cCm98Sb54sHAffiL1JtzpAPzO8DxB8BGg0AHlti+xEVQz7wXVfgxhkxsnf/DNue/w7lTSwqdVng7u6OFi1a4MKFCyXeR6fTwcvLq8hHtaBSFU6HJF1SNhZLSRJw45x4If3lIdMbs51Vbl/eJpIKqIBBs0te5tVkEPDMesAjSDw5f+wNxB2wTkxGA/DnpMKkov9n5U8qACCkNfCwqThs31zg4ALZQ7SZXV8VJhUD/q/sK9GAxsB9pm2jN00XdQ1KMRqB1c+LpMLVF3jiN6DPB2IkKT8bWDES0KcrF58tbf+fSCoa9heFxXInFYBIIgOaAdnJYvmxvdjztUgqXLyBBz5XZppGoy18Xuz/XiR6DqBSiYVer8eZM2cQHBwsVzxViyP1ssjNFA1f/poCzG4JfNNeXHlf2gycWAFseFPpCAvl5YgaCkDULoS2Lf3+IfeIq8ygFkDmDeDnB8RKDTkZ8sSQ8eGFYth/8DcVKxptOhjoaVpitu5VsYrC0RyYX/gG0fu98s8Nt31WXBmal6DmZlkvxtJsnQGcWlVYE1KjvpiaeXge4BkCJF0QCWRVb8l+86J47gPWXbWhcQIGfQlAJWqm7OFv/sZ5YIupQdV9MwEvBd/jGvQWu6Ma8+0r8SqFRYnFq6++im3btiEqKgr79u3D0KFDkZaWhlGjRlkrPsdmzwWckiSKGvd8CywaAnxaRzR8OTgfSI0V/e7r9wK6TAKgEjv2Hf5F4aBNdn4u+ul7BBW2wC2Ldy3g6Q2mNy49sPJZsZZejjeHvBxxFXvyd1HpPvQn4J4nK368e18Fmg8VLyTLn3KsEa9jy8WcMAB0nSL2PygvlUqsmvEIEgVrSqzhP7pUXKUD4s3u9kJT9xqiTkStFcWoB+fbPj5b2vF/ptGK++Xt01CcsHZAu2fF7b9eVnYrBKNBFBIb9GKUyh7qPvp9BKg0wLl1YrTWzlmUWFy5cgWPP/44GjVqhIcffhjOzs7Yu3cvwsOtOGftyOwtscjNAs7/C/z9KvBVa1H4+M9U4PIWcZXoU1uMADy+HHgjSqyu6PsB0HOaePzfrwBXDyv6IxTpWdH/U8tWT+g8xBVopxfF51tniFGGyryI6TPEstZz68RSyseWiv4UlaFSidUHtdoCOSmio2h2SuWOaQtn/hRTCJCA9s8Bvd+1/BjuNQobhx2cLxqL2UrMbmDtRHG76xTgnifuvk/tDmKLawDYMFX554O1JF0qLNjsbqMeE73fBTwCgaSLYiWGUvZ9L5q4OXuK5NIeVqr4NxKvzYAoiLbzbQAqVbxZEeUt/qgSorYDCweJ4q+XFHoBSroEXNgoGq1E7xTFcWZqJ6BOF6BBXzHUVjOi+CeR0QgsGyH6EHiHiU5zShRYGY1iPXnMLtGzYsTyij/pD/0sEiVjPhDWQRRmude07BjZKaI99ZX9ounT48vkbXCVngDM6yl6B9TvBYz4zX5Xilz8T4x4GXLFfgcPzqncyo5/3hK1Km41bLMENemS2BQuO1lMRw39ueT4JQlY/iRw9i+RjD+3XfFqfdmteh44tkQ8z55YYbvznlolGqlpnIHxuwD/hrY7NyAuAr/tLGppBn4BtH3GtucvTVYy8NU94mJj0JdA5Gibh2CT4k0qg3nEIiVGVPfaQl626NS2/g3xR/h1G2DDG2KteH6OSAwinxZX1m9EAyPXAJ1fFE/gkt6k1WpRVOhXXyy9+/1p2/08t7uzZ0VlriQiRwNPrgR03kDcPrHU05K+/Jk3RZJjbk89co38XTM9A4HHl4qf99JmcaVij2L2iD4Hhlzxpjzoq8ovF+39rqiJyUoSq3+MRnliLU72LdOoULLYw2XId6XHb+494ltHdJRc/ULVqre4fbTC1h0xmw4RyYwhV0yJ2PL/1WgE1r4kkoo63YA2o2137vJw8wN6mGrdNn9k1zsjM7GwJs8QMTxuzC+6Ft4aUq8AS0cAn9YFfn0E2PedyL7VTqL3fN8PgRf2ApNPiFUUjQdY1jnOxVtMIzi5A1HbgM0fWu1HKVbGjcI59x5T5VkyWq+H2DzIt45I/ub3FW/gZUm9CizoLzbPcvcX+36UVUBaUcGtgId/ELf3fy8KI+1J/BExFZSfLUa9Hv5RnlEVrQ545CexvPPyFtELwxoMeaI+JukC4BUqEjlnt7If5+oDDFsoapHOrRMrqKqKHbNE+/mIfkCtSNueW6USFw1ObqJ77tFfbXfuQz8B0TvEuR/82j56qdyp3RigRgNRhL7Dflvg2+H/XBWiVgO+NlgZIkmiVuDc3+IF3qsW0GYUMPxXUSsx6k/RPCqgSeWu8gObFnYe3DVbbBdsK/++LYYAA1sAHZ+X77j+DYExm4HanQB9GrB4aOlv3slRwIL7gZvnxRvR0xuAoObyxVOcJoMK6xXWvWY/xVuJZ4BfHhb/b+FdgUcXydsIzr9h4br9/96XfwmqJInpMPP+JSOWWTblEtK6sNfIpuli5MbRJV8Gji0Tt7srtBLMN1xcPADieZ9ZRqtwOaTEAhvfE7d7v1e4os/eaJyAfqblp3u/FQ3G7BATC2uzRQHn0SWFUwRjNgMvnwIe/ApoMhDQecp7ruYPFxY/rn5B9LuwtstbgePLUGbPiopyryGmMlo9Lq7U/p4iCvPuLJBKPCsaWKXEit/rM+ttt79B1ylAy+EivhUjlV/PnnQJWDRYTB/UihRvyk6u8p8n8mmg0QOmJajPyrsEdc+cwuXBQ38SUy8Via/FMPF7+f1pMbLmyLabRisa9C3cz0UJHV8QFxHZt0S9jTVJklg+nJsBhHUE2o+z7vkqq+F9YrTVkAtsrECBtA0wsbC2gl4WUdY5flbybVMEb4oXA2tXMfd5X8xB5maIuXVrzvVZ2rOiorQ60R2yl2n56t5vRcGquRFS/FHg5wFAxnUgoCnw9HpRuGcrKpWoXQhtJ0Zulg4XL7pKSL0ilihnJIjGRk/8Ln8Ca6ZSiWFpjyAxSvSvTG8yZ/4C/jX9ru+bIV6sKxrfwNlAzUZA+jXgjzF2X7FfouTLpqZzKJzLV4pGW9jb4vgycXFhLUd/FVOgWhdRO2OPUyC3U6nE36xKLUaNY3YrHdFd7Px/sAqw9ojFxnfFVWNAM5Hl24JGK/bd8Kol5qZXP2+94rods8T/nWdw+XtWVJRKJXpIDPtZvMic3yBGKE7+IVb3ZCWJ9fyj/1ZmoywnF7F6xStULMn7bbSoEbCljEQxUpEaK4p5R64WRWXW5F5D7FoLiH4qZ/+u3PHijwJ/jAUgiaZcHcZX7ng6DzEN5OQm3gDNfTAcjbm2okEf6yXwlgiNLGyuZq3eFmnxwAZTUXTPafa7w+qdApuJ6W7ANLpqxeLmCmBiYW3WTCxidgNHTE2rBn4h/xRBaTz8xYupxlksu9tlhXXnN84Vrme3tGdFZTR7CBi9DnAPABJOiiFufRpQuzMwcq3130hL4xFgmnZwF29iG6ba7txZyaLFe9JFsbpo5BoRjy3U7wl0NvWYWPOi2LStItLixbLYvCyxhLf/Z/KM8AU0Fs9BQGxAV54iYHuSHCWagwHK1VYUp9c74qIi+bJo2CUnSRIJiz5VrAbqqPD+NJbq+Rag8wKuHTVNFdsPJhbWZk4sbkXJO0SanyvabwMic63dQb5jl1doW1HBDYjlTxf/k+/YRiPw52Sxq2DD+4EmD8p37PIIjRRtwAOaic8b9BHLU22xO2pZglqYVoqogAPzgP3zrH9Ofbro2ZFwUjQxGrkG8Amz/nlv1+tdIKilGKFbXYElqLmZYllp+jXAv7EYmZKzL0irx0xXkZLYXyctXr5jW5t5tKJ+b9EF0164eInkDxCblVmyJLwsJ34Xo5JqJzEFYq89Ykri4V+4F9Gm90WzPjvBxMLavEPFH64hV94Xmj1zxKZabjXFltxKiRwNtBkpWv+ufBa4FSPPcY/+CsTulqdnRUX5hInlqKP+Es2vyrMM0VaaDAT6mKrY179h3SvkvGxg6eOFm3I9tVqZ3Xq1zmLraq2rGK3Z+035H2s0iDf768fFc2bEcuuMgPX/1NR/4ybw+zO2n6qqiFvR9lNbUZwmg8QmaMY84K/J8gz7ZyQC618Tt7u/4bi71XYYL5bLZ1wHdn2pdDQFmFhYm1ojfvGAfNMht6KBbaYs/r6PlR2aB4D+/xO1B9m3REfCvOzKHe/2nhU9p9m2SPJOzm6i8ZUtp5nKq8vkwpUsv40We79UhiSJpX3XjgPn/xH1DJs/FjUV0TtEi+MnVyr7IuzfELjftDnUpvdFvUR5bHpPLMfW6ESvCvNzUm5OrqK/hc4LiN0D/PeBdc4jpx2zRK+d+r2AsPZKR3O3gt4W7uL/1Dz9WxnrXhWvV0EtgK6TK388pWh1okcRAOz+Ckixcr+kcnKwsR8H5VdPFDkmXwbqda/csSRJ9DIwd4drOVyeGCvDyQV49Bfgh+7iivCvKcCQbys+ynB7z4oOMvasqGpUKlE5n3xZdA9dMlyMsBSXaObniquatGuiRXj6NTGClhZfeDv9uth4qThaV3GVb+uGScWJHC06yZ79S+yC+tw2wNm95PsfWljYwGrIt9Z/86xRX/R7WTFSvNjX7iQa0tmjWzFiuTpgX7UVd/IJA3q9JbrPbnwHaNS/4vU9p1aL1RRqrWkKxA4vGizRZJDoIxOzU/R7eeRHpSNiYmETchZwnlkLXPhXFE0O/MI+NsgBxBN/6ALglyFij4HQyMJNcyxRpGfFl44372lrWp1ohDavp9jxddkTQINepgQiHkiPF7czE8t/THd/UTDnVUtsF+0ZAjR+wH6Gi81LUK8eEgn7P9NMSxOLcXmr6EsCAD2mAS2G2ibGpoNFUrxvrqgHeW679UZJKsM8WlGvpzJ1WpZo/5xo3nX9uOht8UgFaosyk8RoBQB0fVl0tnV0KpUYuf6hB3DiN/H/pHCdDF+1bUGuxCInTcynA2IYvGZE5Y4nt3rdRY+Lje8A698UIw6WvFjd3rOi/VhlG/Q4Eg9/UQPy032iLiW2hHXtaqfCRMHL9OEZXPS2Z5BIVuydm59YgrpoiNhQrkEfceV2uxvnxaiBMR9o8SjQ/XXbxtj3A1GXcuWAmKp65h/7+r+9FVPYMtseayvuZO5t8WNv4MQKoPXjYvrGEhveFO2w/RsD975mnTiVENJabP53dLHYsfrZjYpedDKxsIWCxKKSTbK2zBDD1r51gW6vVD4ua+g8UVxJnl4tXtSf2y420yqP23tW9HrbqmFWOUHNgcd+BfZ+J3YE9Qq+e9TBrYb9N/+xRL0e4u9t91diu/NakSJBAsSV6ZJHgZxUsXvtg1/b/oVW6yxG8b7vJvZU+ect4AGZl0xWxs7PTaMVPYDaHZWOpnxqtRFX5PvmiouQF/aWv+PrufUiIVGpgcHf2leSJ4fe74jdYa8cAE6utN3oXDGq0KuMHSvovnm54rv1xR8Vm1ABwMDPRV2DPVKpxPyyf2Mxp1/eJk6JZ5XpWVGV1OshelwM+UYkZu2eBRrdL4Z7PfyrVlJh1usd8fNl3yrcBTVfL4qIb0UBPuGiqZhSzxefMOBh05D9gXniBd8epMQCRxaL2/ZcW1GcXm+JRPlWdPmbkWWnFI6Gdnqxao6GegYB3Uw/48b35G1/b6Eq+Epjh3xqAyqNKLhMv275440GscxKMgLNh1o+/GdrOk+xE6rOSwzLm1snl8RoNC0jU6hnBTku8xJUJzex6+6er8XW17G7xd/fiBWAe01lY4zoWzjCuPalyq/ekcMO02hF3e5AeCelo7GMzrOwf86uL4GE02U/5t+3xGivX32x0qyq6vSiaF6Xkyp2X1YIEwtb0DgVLpmsSJ3FgfliKFXnLXrEO4KaEYVtmPfNBY7/VvJ9jy4Wy8iU7FlBjqtmROES1I3viuJflQZ4dKHoiGkPekwr3F9nxUhFryaRElc4WuEItRXFaTIQaDxQJEdl9ba4+J/p51WJVSDW2CzPXpiXO790RNFiXCYWtlLRAs60a4Vr4Xu/U/56BXvQ+AGgm6kCe+1E4PrJu++TcaNwREPpnhXkuNqMEm80Zg/8n32N7Gm0YhmgewCQeLpwZYISdn4uRgfr3guEd1Yujsrq/6nY7j5un9iltjj6dLFzKQB0eM7xRmcqIjRSTH0qiImFrVQ0sfhnKpCbLgrT2j4jf1zW1nOaaBOcnw0sf+LuHTn/fYs9K6jyzEtQmw8F7v/EPp8rnkFie3aVWqzGOCxDoydLpcQVntfRaivu5B1aWOS96T0gPeHu+2x8D0iNE7U2ve1zi/GqiImFrVQksbiwSVT5qtSiZ4VaY53YrEmtEVdqPuGi2Grl2MJhy0tbgOPLwZ4VJAs3P2DofKCjHSeodbuJzaMAMWph63nwnV+I0Yo63YA6XWx7bmtoPw4Ibi1qCv65Y0O+qB3Awfni9oNfld5EjWTFxMJWLE0s8rKBdaaCrw7PO3YjFzc/YPgvYivyixuBbZ+In8/cuIg9K6g66ToFaNAXyM8Bfh4InPnTNudNvQIcXiRuO2ptxZ3UGnFRolKLFTcXNomv52YCa18UtyNHixVTZDNMLGzl9l4W5Vlyuv3/xBW+Vy2gpw23xraW4FaF3RG3fSo6RLJnBVVHarXYnbZWpJgGXP6kWApZ2T12ylJktKKrdc9lSyGtC6dR/54iCmM3f1T4+mneS4NshomFrfiGA1CJeonMm6Xf98a5wp3q+n8qlldVBa0eE0OXAHDJtMU6e1ZQdeTmBzy9QXTQBcSGbz/0LN/SyYpIvVo4WtH9DeucQ0k9pwFeoUBKDPD708DeueLrg74UW6+TTTGxsBWtTqwvBkqfDpEkcfVi7ulwe6V7VdDvYyDM1OWPPSuoOtM6A33fB55aBXgEAjfOiD1fDvxY8UZ6Jdn5BWDIFZtV1e0m77Htgc6jsKvp+Q0AJKDVCNFDhGyOiYUt3d6BsyRHlwAxu8Rukv0/q3o9HbTOojvkgP8DHvq+6v18RJaq3wsYvwuI6CfqLv5+RUyPZCXLc/zUq4XLMXtUwdEKs0b9C/eL8QgE7neQnj9VEBMLWyqrgDMrWWwZDojiKt9w28Rla66+omDT1UfpSIjsg4e/6BJ630yxWdzZv4C5XYDonZU/9q7ZptGKLqK+oiobOBtoN1a0cXf1VTqaaouJhS2VlVhsfBfITgYCmgKdJtguLiJSnkoFdHoBGPsfUKOB2PL+54HA5o8BQ37FjpkWL3Z/BURtRVUfIXSvKaZEQtsqHUm1xsTClkpLLGJ2A0dMjWsGfiHagBNR9RPcChi3DbjnSQASsP0z4OcBYuMwS+2cLUYrancWnTaJbICJhS2VlFjk5wJ/mXo6tBnpOFsYE5F16DzEvhZDfxKbqcXtA+Z2FQ3zyivtWuFoRY9qMFpBdoOJhS351hH/5qQULczaM0dUhLvVAPq8r0RkRGSPmj8CjN8BhLYD9KnAb6PFvju5mWU/dtdswKAHancSu5gS2QgTC1tydgM8Q8Tt5Cjx761oYNtn4na/j8X6diIiM986wNPrTRv6qUQ/ih96lN4OPO0acHCBuF0daivIrjCxsLXbp0MkCVj3mtigq0430UCKiOhOGiexu/GotaJb7c3zwLxewN7viu95setLMVoR1pHtrMnmmFjYWkEvi0vAmbXAhX/F8rIHPudVBRGVru69oudFw/6iKHPDG8DSx4p2802/DhwyjVawtoIUwMTC1swjFteOAetNzWq6Tgb8GyoWEhE5EPcawONLgf7/AzQ60Wlybhfg8lbx/V1fikZbYR2Aej0VDZWqJ+5TbWvmxOLcOvGvb12g2yvKxUNEjkelAjqMA8I7A78/A9w8BywaAnR4rnr1rSC7VKkRi5kzZ0KlUmHy5MkyhVMNmBMLswdmAU6uysRCRI4tqDkwbqvYGhwSsO87MVoR2l60CidSQIUTiwMHDuCHH35Ay5Yt5Yyn6jPXWABiKVmD3srFQkSOz9lN7OI5bKFpp2CV2O2ToxWkkAolFhkZGXjiiScwb948+PqyH7tFdJ5iTbl3beA+bpJDRDJpNgSYeAR4fhdQn7UVpJwKJRYTJkzAAw88gD59+pR5X71ej7S0tCIf1d7INcBLhwHPIKUjIaKqxL0GENhM6SiomrO4eHPZsmU4fPgwDhw4UK77z5w5E++/z26SRahU3AuEiIiqJItGLOLi4jBp0iQsXrwYLi4u5XrM1KlTkZqaWvARFxdXoUCJiIjI/qkkqbi2bcVbvXo1HnroIWg0moKvGQwGqFQqqNVq6PX6It8rTlpaGry9vZGamgovL6+KR05EREQ2U973b4umQnr37o0TJ4r2p3/66afRuHFjvPHGG2UmFURERFS1WZRYeHp6onnz5kW+5u7ujho1atz1dSIiIqp+2NKbiIiIZFPplt5bt26VIQwiIiKqCjhiQURERLJhYkFERESyYWJBREREsmFiQURERLJhYkFERESyYWJBREREsmFiQURERLJhYkFERESyYWJBREREsmFiQURERLJhYkFERESyYWJBREREsmFiQURUBRiNEv44fAWzN51Hbr5R6XCoGqv07qZERKSsi4kZmPbHCeyPTgYAeLs64ekudRWOiqorjlgQETkofb4Bszedx4Avd2B/dDLUKvH177ddhj7foGxwVG0xsSAickD7o5Ix4MsdmL3pAnINRvRs5I//XumBIC8XXE/LwcpDV5UOkaopJhZERA4kNSsPU/84jke/34NLNzJR00OHOSPuwU+j26FuTXc8170eAGDutovIN7DWgmyPiYUC1hy9im+2XITRKCkdChE5CEmS8NfxePT+fBuW7o8DADzePgz/TemOgS1DoFKJeZDH2tVGTQ9nxCVnY+2xeCVDpmqKxZs2ZjBKeP3349DnG+HqpMEzXVlgRUSlu3IrC++uOYXNZxMBAPX93THz4ZZoX9fvrvu6Omswpls9fLL+LL7ZchGDW9eCxlx8QWQDHLGwsfiUbOhNS8E+3XAWl25kKBwREdkrg1HC/J1R6PfFdmw+mwhnjRqT+0Rg3aRuxSYVZk92DIe3qxMu3cjEhpPXbRgxERMLm4tNziq4rc834pUVxzgPSkR3OXk1FUO+2YUP/zqNrFwD2tfxw7pJXTG5T0PotJpSH+uh0+IZ03LTrzdfgCRx2pVsh4mFjcUkicSieS0veLpocTQuBd9vv6xwVERkL7Jy8zFz3RkM/mYXTlxNhaeLFjMfboFl4zqiQYBnuY8zunMdeOi0OHs9Hf+dSbRixERFMbGwsZjkTABA23A/TB/UDAAwe9N5nI5PUzIsIrID287fQL8vtuP77ZdhMEp4oGUw/pvSHY+3rw21hXUS3m5OeKpTOADg6y0XOWpBNsPEwsZiTSMWtf3c8HCbWujbNBB5BglTVhxlG16iaupmhh6Tlh3BqJ/248qtbIR4u2D+qLb4ZkQbBHi5VPi4z3atCxcnNY7FpWDnxZsyRmyfbmboMfWP4zgUk6x0KNUaEwsbizYlFuE13KBSqTDjoRbwdXPC2evp+Oq/CwpHR0S2JEkSVhyMQ+9Z27DmaDzUKpEMbJzSHb2bBFb6+DU9dBjR3jRqsflipY9n795ZfRJL98fh+cWHkZaTp3Q41RYTCxuSJAmxSWIqJLyGOwDA31OHjx9qAQD4dutFHIm9pVh8tpKVm481R68iO5cth6n6unwjA4/P24vXfz+O1Ow8NA32wuoJXfDOwKZw18nXCWDcvfXgrFFjf1Qy9l1Oku249mbHhRtYb1oBk5iux+f/nlc4ouqLiYUNJWXmIjPXAJUKCPNzLfj6gBbBeLBVCIwS8Mpvx5CTV3XfcI1GCc8vPoxJy45i6h/HlQ6HSBFH41LQ/8sd2Hs5GS5Oakwb0BhrX+yClqE+sp8ryNsFw9qGAgDmbKmaoxa5+Ua8t/YUAKCDaRnuoj3ROH4lRcGoqi8mFjZkXhES7OVy13KxDwY3Q4CnDpdvZOJ//5xTIjyb+HbrRWw7fwMAsPpofJW+giIqTk6eAVNWHIU+34j2df2w8eXuGHdvfWg11ns5Ht+9PjRqFXZcuImjcSlWO49S5u+MwmVTe/N5o9oWXKi9teokDOxwbHNMLGwo1rQipHYNt7u+5+PmjE8faQkA+GlXFPZWwTfc3Zdu4vONYniycZBYNvfe2lPs40HVyv/9cw6Xb2QiwFOHeU+1RZjf3a8Hcgvzc8ND99QCAMypYrUW8SnZBfVp0wY0hpeLE94e2ASeLlqcuJqKxXtjFI6w+mFiYUPRN02Fm37uxX6/Z+MAPNYuDJIEvPb7MWTo820ZnlUlpufgpaVHYZSAYZGhWDK2I3xMRat84lN1sT8qGfN3RQEAPn2kJbzdnGx27hd61IdKBWw6k1Cllrd//PcZZOcZ0K6Ob0HyFODpgtfvbwwA+N8/55CQlqNkiNUOEwsbMnfdDK9Z8hXKWw80QS0fV8QlZ+Pjv8/YKjSryjcY8dLSI7iZoUejQE98MLg5/Nyd8Wq/RgCAWRvP42aGXuEoiawrU5+PV387BkkCHm0bip6NA2x6/nr+HhjYMgQA8M3WqjFqsfPCTfx94hrUKuD9B5sXbMQGACPa10arMB9k6PPxwV+nFYyy+mFiYUMx5hUhJYxYAICnixP+N0xMiSzdH4ut5xy/Y97sTRew93Iy3J01+PbJNnB1FvUlj7evjWYhXkjPycdnG84qHCWRdX2y/ixik7MQ4u2Ctwc2VSSGCT3rAwDWnbiGi4mOvU+RKNg8CQAY2akOmoZ4Ffm+Rq3Cx0OaQ60C/j5+rUq8ljoKJhY2VDBiUUyNxe0616+J0Z3rAADeWHkcqVmOux57y7nEgkr0Tx5pifr+HgXf06hV+GCw6D664uCVarHUlqqnXRdv4hfTlN9nQ1vBy8V2UyC3axzkhX5NAyFJopDakf20KwqXbmSipoczXu7bsNj7NK/ljdGdxZ4p7645VaVX3NkTJhY2kqHPx82MXADFF2/e6Y37G6NuTXckpOkx/c9T1g7PKuJTsvHy8qMAgKc6hmNQq5C77hMZ7odH2oilcO+uOcUKbqpy0nPy8PrvYmn1kx1ro2tETUXjebFXAwDAmqPxBZ2AHc211MKCzTf7N4G3a8mJ2pR+DRHk5YLY5Cx8U0WX295u/s4o/HksXtEW7hYlFnPnzkXLli3h5eUFLy8vdOrUCevXr7dWbFWKeRrE182pXFcrrs4azHq0FdQqYNWRq9hw8pq1Q5RVbr4RE5YcRkpWHlrU8sbbA5uUeN83+jeCp05UcK84GGfDKIms76O/zuBqSjZq+7lhav+Snwe20jLUB90b+sNglPDd9ktKh1MhH/99Blm5BkSG++JhU8FmSTx0Wkx/UEw9fbftksNPAZXmako2Pt1wFhOXHsEeBVcWWpRYhIaG4pNPPsHBgwdx8OBB9OrVC4MHD8apU455RW1LsQWtvEuur7hTm9q+GN9dzIm+teqkQxU4frrhLI7EpsDTRYtvn2hT6jbPAZ4umGwayvxsw1mkZOXaKkyqYo5fSbGr58mWs4lYfjAOKhXwf8NaydpRszLMoxa/H7yCa6nZCkdjmV0Xb+Kv46Jg84PBzcq1Odt9zYLQq3EA8gwS3lp1ospuyDbr33PIzTeiYz0/dKpXQ7E4LEosBg0ahAEDBqBhw4Zo2LAhPv74Y3h4eGDv3r3Wiq/KiClnfcWdJvWJQOMgTyRl5uLtVScd4gmx4eR1zN8pltTNGtaqXOv0R3YKR8NAD9zKysMstuKlClhz9CoenLML/b7YjvMJ6UqHg9SsPLxp6i77TJe6aG/qCGkP2tXxQ4e6fsg1GPHD9stKh1Nut3fYfKpjOJqFeJfrcSqVCu8/2AwuTmrsi0rGH4evWjNMRZyKT8WqI+LnmjagSZEVMrZW4RoLg8GAZcuWITMzE506dSrxfnq9HmlpaUU+qiNz181wC5vh6LRiSkSrVmHDqetYczTeGuHJJiYpE6/9dgyA2KOgX7Ogcj3OSaPG9AdFIeev+2Jw8mqq1WKkqicuOQtvrxIrBJIzczFi3j5cuqHskPf0P08hIU2Pev7ueO2+RorGUpyJvSIAiNVnN9LtZ5SnND/vjsLFxAzUcHfGlH6W/Z+G+bnhpd7iZ/543ZkqNzL6yfqzkCTgwVYhVmkNbwmLE4sTJ07Aw8MDOp0O48ePx6pVq9C0aclLp2bOnAlvb++Cj7CwsEoF7KjMNRa1LZgKMWsW4o1JpifEu2tO4nqqfTZ7yckz4IVfDyNdn4+24b4Wv5h2rl8TA1sGwyiJjpyOMDpDyjMYJUxZcRTp+ny0DvNB02Av3MzQY8S8vYi+malITBtOXseqI1ehVolROxenkqcCldKlQQ20DvNBTp6xYITRnl1PzcGXm8wFm41LLdgsyZiu9RAR4IHkzFx8WoWWuG8/fwM7LtyEs0ZtF0msxYlFo0aNcPToUezduxfPP/88Ro0ahdOnS24+MnXqVKSmphZ8xMVVz+K8mKSKTYWYPd+jPlqFeiMtJx+vrzxul2+6H/x1Gqfi0+Dn7oyvR9wDpwrsffDWA03g6qTBoZhbBcN6RKWZu/UiDkTfgodOi68euweLx3RAw0APJKSJ5CIu2bYrH5Iy9Hhr1QkAwHPd6+Oe2r42PX95qVQqTDTVWvyyJ9rur+A/XncGmbkGtKntU7CSzFLOWnXBbtJL98fhUEyynCEqwmCUMGOdaKY4slO4TVrEl8XiSiJnZ2c0aCD+GNu2bYsDBw7gyy+/xPfff1/s/XU6HXQ6XeWidHC5+caCAqmKJhZajRqzHm2FAV/txPbzN7B0fxxGdKgtZ5iVsvrIVSzZFwuVCpg9vDWCvV3LflAxgr1dMbF3A3y24RxmrDuLvk0D4anQmn9Hk28wYvelJGTq85FnlJBvMCLfICHPaPrXYES+6et5Bgn5RvFvnul+5s/zDcYijzdIEvo3D8Lwdvbz92Z2NC4FX5iuYj8Y3KxgKfevYzrisR/24NKNTDw+by9WPNcJIT4V+5u0hCRJeGfNSSRl5qJRoCcm94mw+jkro1fjADQJ9sKZa2lYsCu6xH4QStt96Sb+PBZvKthsXq6CzZK0r+uHR9uGYsXBK3hr1Un8ObFrhS6C7MWqI1dx9no6vFy0BUW5Sqt0ibIkSdDrHWN+TilXbmXBKAFuzhr4e1Q8yWoQ4InX72uEj/4+g4/+Po2uDWqWqyeGtV1MTMc00xXaxF4RuLehf6WO92zXuvjt4BVE3czEl5suKNal0JHkGYwY9dN+7L5knSVmW8/dgFqlwrC29jOVmanPx6RlR2AwShjUKqRgnwgA8PfUYcnYjhj+/R5EJ2UVJBeBXi5WjenP49ew7sR1aNUqzHq0VamroeyBedTihV8PY8GuKIzpVtfuEvk8gxHvrREFm090CEfzWuUr2CzNm/2bYOPpBJy9no6fdkbhOdPqO0eTk2fArH/Fbtgv9moAHzdnhSMSLEospk2bhv79+yMsLAzp6elYtmwZtm7dig0bNlgrvirBvCKktp9bpSt1n+5SF/+eSsD+6GS8+vsxLBvbsVLZe2Vl5ebj+cWHkZVrQOf6NQpqQSpDp9XgvUFNMXrBASzYHY1H24WhYaCnDNFWXR/8eRq7LyXB1UmDZiFe0GpUcNKooVWroNWo4axRQ6tRQatWw0mjuuO2Gk6m+2k1KjipTffVqOGsUeFoXCqW7o/FtFUnUNvPDR0UXMZ2u/f/PIWYpCzU8nHFR0Oa3/XcCvRyEcnFD3sQk5SFEfP2Ytm4TvD3tM4IamJ6Dt5dIwpIJ/RsIMsboC3c3ywIDQI8cDExA7/sjcELPezjqtds4e5oXEjMKLK/UGX5uTtj6oAmeP3345i96QIeaBmMUF/lL9Is9dOuKFxLzUEtH1eM7FRH6XAKWJRYJCQk4KmnnsK1a9fg7e2Nli1bYsOGDejbt6+14qsSYkwFZLVlmPvSqFX4v2GtcP+X27E/KhkLdkfj2a51K33cipAkCW+vOokLiRkI8NThy8fugUamJKdHowD0bRqIjacTMH3tKfw6poOiy6fs2eK9MfhlbwxUKuCrx+9B36aBsh5/WKSE1OxcrDtxHeMXH8LqCV0s6sdiDetOXMOKg1egUgGfP9qqxEK+EB9XLBkjRi4u3cjEEz/uxdKxHVGjEiOHxZEkCdP+OIGUrDw0C/GymyHp8lCrVZjQsz5eXn4MP+6IwujOdeDmbB/9NhLScvDFRrH8/M37G8u6G+ywyFD8fvAK9kcnY/ra0/hxVFvZjm0LSRl6zN0iGpy9dl8juyoQtmhiaf78+YiOjoZer0diYiI2bdrEpKIczCMWdWrK82Jcu4Yb3npAdPD7bMNZxTrJLT8Qhz9Mle9fP36P7FeC7w5sCmetGrsvJWHdieuyHtsSRjtuM77nUhKmm9b1v9qvkexJBSDeeGYNa42Wod64lZWHZxceRFqOcvvXXEvNxtQ/xNTb893rlzmCEubnhqXjOiLQS4fzCRl4cv5+2QsVVx6+ik1nEuGsUePzR1s73Jz9oJYhqO3nhuTMXCzdbz8F9jNMBZutw3wwNLJiBZslUalU+Pih5tCqVdh0JgH/nlLuNaYivt58Een6fDSv5YUHi9kuQUmO9dfvoMxdN+UYsTAb0b42ukXUhD7fiFd+O4Z8g1G2Y5fHqfhUvGt+Q7uvkVWGx8P83PC8ae7zo79PIys3X/ZzlEafb8Dbq0+gxfR/8MueaJueuzzikrPwwq+HkG+U8GCrELzQw3rzxK7OGswb2RZBXi64mJiBCb8etvnfHCCSvCnLjyE1Ow8tQ70xuU/5ig3Da7hjydiOqOmhw5lraXhq/n6kZsuTHMWnZON903Nhct8INApyvGk7rUZd8Pfzw/ZLdrFZ197LSVhzNB4qFfBhJQs2SxIR6Ilx99YDAExfewqZetu+xlRU9M1MLDZtajetfxNFp8OLw8TCBiradbM0KpUKnw1tCU8XLY7FpeC7bbbr+Z+Wk4cJvx5Gbr4RvRoHYPy91ntDe75HfYT6uuJaao5NNxCKT8nGo9/tweK9scjMNeCdNacKnsj2IEOfjzELD+KWaS+Wz4a2tPpUUaCXC34c1RauThrsuHATH/5V8jJza5m34zL2XBa1JLOHt4aztvwvYfX9PbBkbAf4uTvjxNVUjF6wHxmVfCORJAlvrDxe0ENjXLd6lTqekh5uE4pgbxckpOnx+6ErisZStGCzNlqEWq9eZWKvCIT6uiI+NQdfmjY2s3f/++cc8o0SejTyR+cGym5qVxwmFlZmNEqF26X7yTsvHeztivdN3Sq//O8CTsVbv1ulJEl4c+VxRJuK5mYNa2XVbNnFSYN3TKtC5m2PQpQNGh7tungTA7/eiWNXUuHt6lQwzPj26pNYtj/W6ucvi9Eo4eXlR3EuIR3+njr8MDLSZvOrzWt544vhrQAAC/fE2HQk5+TVVPyfqQL+vUFNUc/fw+JjNAz0xOJnO8DHzQlHYlPw9IL9lRoJW7I/Fjsu3IROK5aDax1sCuR2zlp1wd5Ec7deQp4CI1JmC3dH41xCOnzdnGQr2CyJq7MGHw5uDkDsDHo63r67Qx+OvYW/T4i9UuxhU7viOO6zwEFcT8tBbr4RWrUKIT7yL3V76J5a6Nc0EHkGCa+sOAZ9vnWHMBfujsa6E9fhpFFhzoh74Otu/eVN/ZoG4t6G/sg1GPH+n9bryClJEr7dehFPzd+H5MxcNAvxwl8Tu+LLx1oXFMhOXXUCvym8A+vnG89j4+kEOGvV+P6pyAr3DKmo+5sHF3T3m/7naey4cMPq58zONeClZUeQZ5BwX7NADG9X8WWvTUO8sPjZDvB00eJA9C08+/NBZOda/ryJS87Cx3+LxkSv3dcI9SuQ6Nib4e3CUNNDh6sp2VitUIO6xLQczDb1Jnnj/sY2WULZs3EA+jcPgsEo4a3VJ+y2rkqSJMw0NcMaGhlqt9NuTCyszNxxM9TX1SpXM6IAqQX83J1x9no6Os/cjLGLDuL7bZdwKCZZ1kTjSOwtfGz6o542oInNOgqqVCq8N6gpnDQqbD13A/+dSZT9HGk5eRi/+BA+23AORklUjK98vjPCTEuE336gCUZ3rgNJAl5feRyrjigzVPznsXjMMU0JzXyoBdoo1NXxhR718XCbWjAYJbzw62GrFxB/9PdpXL6RiUAvHT55uPLTPs1reWPRM+3hodNiz+UkjPvloEV1BUajhFd/O4asXAPa1/HDM12UWZklNxcnDcZ2Ez/Lt1svwaDAG+zM9WeRoc9HqzAfPGrDvinvDmoKd2cNjsSmYNkB+ylgvd2/pxNwIPoWXJzUmNJX+dbdJWFiYWWxyRXfI6S8/D11mPVoK3jotEjKzMXG0wmYuf4sHpm7By2m/4uhc3dj5voz2Hg6AcmZFauGT8nKxYtLxBVj/+ZBGN25jrw/RBnq+3vg2a5i/vr9v07JWlx2PiEdQ+bswj+nEuCsUWPGQy3w2dCWRaYXzMnNEx1qQ5KAV1Ycw9pjtt0Q7sSVVLz2e+EGb4/IXCVvCZVKhZkPt0DbcF+k5+Tj2YUHcKuCf1tl2Xg6Ab/uE1NQs4a1lm2U7J7avvj56XZwcxY1Iy+Y6obKY+GeaOyLSoarkwb/G9bS7ornKuOJjuHwcXNC1M1MrDtxzabn3nc5CauOXDUVbJZvS3S5BHu74hXTtMsn68/gZoZ9NX7MMxjx6Xqxv8mYrvUQ5G3dZm+VwcTCyiq6q6mlejYKwKF3+mDl850xtX9j9G0aCD93Z+TmG3Ew5ha+33YZYxcdRJsPN6LXrK14/fdjWHEgDpduZJQ5tWA0Spiy4hiupmSjTg03fGqDQsHiTOzVAIFeOsQlZ8u21fPaY/EYPGcXLt/MRIi3C1aM74QRHWoX+/OpVCp8OLg5HmsXBqMEvLz8qM1eeBPTc0xX1Ub0aOSPN+5vbJPzlkan1eD7pyIR6uuKmKQsPLf4ULnfmMsrMT0Hb6wUW4+P7VYXXSPkLVRrW8cP80e1g4uTGpvPJuLFJYfLrC24fCOjYAOraQMaK97TQ24eOm3BCMyczRdtNi2QbyjcEv3x9rUV2aFzZKdwNAvxQlpOPmaYprnsxbIDcbh8MxM13J3xXHf7LhJmYmFlld18zBI6rQaR4b54rnt9zBvZFofe7oPNr3THZ0NbYnjbMNT3Fy+Al29kYsXBK3h95XH0nrUNkR9tKnX65Pvtl7H5bCKctWp880QbeCnU8tddp8VbD4hCzm+2XKzU5lJ5BiM++PM0Xlp6BNl5BnRpUAN/TuyK1mE+pT5OrVZhxkMtMDQyFAajhJeWHsE/Vl7/rs834LlfDuFaag7q+bvjq8fla0RWWTU8dPhpdDt46LTYH5WMt1efkK0GRkw3HEdyZi6aBHvhVSvt2tipfg38OLIdnLVq/Hs6AZOXHS1xKa3BNAWSk2dElwY18ESHcKvEpLRRnevAU6fFuYR0bDqTYJNzLtoTg7PX0+Hj5oTXrFywWRKtRmxSplIBfxy5it0XbyoSx50y9Pn4cpNoFDapT4TdtV2/ExMLK4tJlq/rpqVUKhXq+Xvg0bZh+HRoS/z3Sg8ceacvfhzZFuO710e7Or5w1qqRXMr0ycLd0QWV+B882AzNQpRtUzyoZTA61PWDPt9YUDhnqcS0HIyYtxc/7RJbRb/Qoz4WPdOh3N0Y1WoVPn2kJR66pxbyjRJeXHIY/1npxVd0dDyJI7Ep8HLRYv6odooldiVpGOiJr0fcA7UKWHHwCubtkGc0aeGeaGw/fwM6rRpfPdbaqvtudI2oie+fioSzRo2/T1zDq78dK7a+YN6OyzgcmwIPnRafDbXuiiglebs6YWRnkTTN2XLR6rspJ6YXdth84/7GNikKL0nrMB88aUoY31590uoF8eXxw7ZLuJmRi7o13fF4e/vbDPBOTCysSJKkghELubpuVpavuzP6NA3Em/0b47fxnXFiej+sfL4zpg1ojH5NA1HjjumT99aegsEo4eF7alWqEl8uKpUK7w9uBo1ahQ2nrmP7ectWJByITsYDX+/Egehb8NRp8f1TkXj9/sYWjwBo1Cr8b2hLDGoVgjyDhOcXH8bWc/IXlc7fGYWVh69ArQK+eaIN6trJ39GdejYKwNum0aSZ689i4+nKJVpnr6dhpmk++e0HmiDCBnvF9GwUgG+eaAOtWoXVR+PxxsrjRaYBziek4/N/xZvfuwObopYNdktV0jNd6sLVSYPjV1Kx/YJ1r9w/WXcW6fp8tAr1xnA72Oju1fsawd9Th8s3M/H9NnkS5YpKSMvBvB3iIuiN+xs5RFdX+4/QgaVk5SE9R6yRV2LEojzM0yfj7q2PH0a2xcG3+2DLqz3wv6Et8Vi7MEQEeKBrg5r46KG7N3lSSuMgL4zsJK4opv95qlzz+pIk4aedUXj8h724ka5Hw0APrHmxC+5rFlThOLQaNb54tBX6Nw9CrsGIcb8cknXp5ZZziZhhWoXz9gNN0S2icrvGWtvTXepghKm4ddKyIxXuB5CTZ8CkpUcLGrA92dF20w19mwbia9NU0++HruDtNSchSWJr+VdWHEOuwYiejfwxrK1yhbO2UsNDhyc6iKvjr/+7YLVRiwPRyfjDVLBZ2S3R5eLt6lTQP2fOlouItkH/nJJ8sfE8svMMiAz3rdTrlS0xsbAic8fNQC+dXW0QUxqVSoW6Nd0xrG0YPnmkJTZO6Y7FYzrYzaZEZpP7NERND2dcvpGJBaYpjZJk5eZj0rKj+OCv08g3bbG96oUuFWqwdCetRo2vHr8H/ZoGIjffiDELD8oyL3sxMQMvLTkCowQMbxuGp7vUqfQxrU2lUuH9B5uhS4MayMo1YMzCA0hMz7H4OJ9uOItzCemo6eFsk46id+rfIhifP9oKahWwZF8s3v/zNOZuvYQTV0XDtE8eUaZ4WQlj760HZ60aB2NuYfXRq7iemiPrEtR8gxHvrBY7wj7WLgytyqhxsqVBLYPRLaImcvONeMeUYNra+YR0rDD1zZk2oLHD/N0xsbCimCSR5crdcZPEFYV5ZcRX/13A9dTi38Au38jAQ9/sxtpj8dCqVXh3YFN89VhruOvkS5ScNGrMGdEGvRsHQJ9vxLMLD2Lf5aQKHy81Kw/jFh1Euj4fbcN98cGQZg7zguKkUePbEZGoV9Md8ak5GLfokEVLg7eeS8SCXdEAgP8Na4WaMu9CWl6DW9fCZ0NbQaUCft4djc9N8//vP9gMgV72u8xPboFeLgVTEy8vP4aOM/9Do7fXo8snm/Hod3swadkRfLrhLH7ZE43/ziTgdHwaUrPyyv0mvHjvbQWb9ym/0ul2KpUKHwxuDmetGjsu3MRfx2279BYAPl1/FkZJbG0fGe5n8/NXlH1dhlYx5vqK2jZYEVIdPdImFEv2x+JIbApmrj+DLx+7p8j3/z11Ha+sOIZ0fT78PXX4ZkQbtK9rnSens1aNb59sg3GLDmHb+Rt4+ucDWPRMe7StY9n58g1GvLj0cMHy1++eirRq0aI1eLs5Yf7odhjyzS4cjUvB678fx5ePtS4zOUrK0OPV38TS0tGd66BnowBbhFuioZGhyDcY8aZpJ9X7mgVicGv72kXSFib2boCY5CxcSszA9bQc5BslXE3JxtWU7BIf4+asQYiPK4K9XVDLxxXB3q4I8XFBiI9rwdfTc/Ixy1Sz8tp9jeCnYMFmSerWdMeEHg3wxabzeP/PUwjxcUVkuG2a0u25lIT/ziZCq1bh9fvttxlWcVSSjcd30tLS4O3tjdTUVHh5edny1Db3yopjWHn4Cl7t1xAv9opQOpwq6cSVVDz4zU5IErB8XEd0qFcDBqOEWf+ew7dbxcZs7er44psRbRBggyvNnDwDxiw8iJ0Xb8JDp8WiZ9tb1B3zw79OY/7OKLg6afD7850UX4VTGbsv3cTI+fuRb5Twcp+GmNSn5OeAJEkYu+ggNp1JRMNAD6x9savdTB/+eSweOy/cxBv9G9vlm58tGYwSEtNzEJ+SjfgU8e+1VNPnqdm4lpKDpHI2SnNxUiMnz4gWtbyxekIXu1lCfSd9vgGDvt6J8wkZUKuA57rXx+Q+EVZN+I1GCUO+3YXjV1IxslM4PjDtZaK08r5/M7GwomHf7caB6Fv46vF7CjayIvlNW3UCS/bFonGQJxY92x5Tlh/DTlOdwzNd6mLqgMY2raTOzjXgmZ8PYM/lJHjqtFg8pkO55o5XHIjD66ZmUN8+0QYDWgRbOVLrW7o/FlNNV/xzRtyDgS2Lfx4s3huDt1efhLNGjTUvdkGT4Kr92lCV5eQZCpMNUwJyLVWMcJi/nmXam0WrVuG38Z1stj1ARaVm5eH9P0/hD9P+KY0CPfH58FZWS/zXHL2KScuOwkOnxdbXeig2JXgnJhZ2oP3Hm5CYrseaCV3sqiipqrmVmYues7YiJSsPbs4aZOUa4OqkwadDWyqW0GXl5mP0ggPYH5UMLxctloztiOa1Sn4ROhidjMfn7UWeQcKk3hF4uW9DG0ZrXeZRGJ1WjeXPdbqrCdnFxHQM/HoncvKMeGdg04IN36hqkiQJadn5uJqSDXedxqE6l244eQ1vrTqJpMxcaNUqTOodged71Jd1Hyh9vgG9Z23DlVvZdjfaXd73bxZvWklWbj4S00WveVt03azOfN2dC3bbzMo1oG5Nd6ye0EXRUSI3Zy1+Gt0OkeG+SMvJx5Pz95W4/PJqSjbGLz5UsA/LpN7280Iih2kDmqCXqbB17KKDiL9tbl6fb8BLS48iJ8+IbhE18bSN96Ah21OpVPB2c0LTEC+HSioAsbPvPy/fi/uaBSLfKGHWxvN4ZO5uWTfh+2VPDK7cykagl65gfyRHw8TCSmJNS029XZ1ssu1vdfdYu9oY2SkcT3asjTUvdrGL7YQ9dFr8/HQ7tA7zQUpWHp6cvw/nrqcXuU9Wbj7GLTqImxmibfWsR6teN0eNWoUvH2uNRoGeuJGux5iFB5GpF/1dPv/3PE5fS4OvmxNmDat6PztVPTU9dPjuyUh8MbwVPF20OHYlFQ98tQM/7rhc6X1VUrPy8PVmsXvxK30bwdXZPuqMLMXEwkpsuUcIiTevDwY3x0dDWthVy2tPFycsfKY9WoZ6IzkzF0/8uBcXE0VyIUkSXvvtOE7Fp6GGuzPmjYy0u34hcvF0ccKPo9qihrszTl9Lw8vLj2LHhRv43rSZ3KePtLRJcS2RHFQqFR66JxT/vnwvukXUhD7fiI/+PoPH5+2t1B5G32y9iNTsPDQK9FR09+LKYmJhJbHmpaZ22nGTbMfb1Qm/PNMBzUK8cDMjF4/P24dLNzLw9eaL+PvENThpVJj7ZCRCfav230qYnxt+GCn24/j3dAKe+fkAAGBEh9ro5yAdBYluF+ztikXPtMfHDzWHm7MG+6KScf/s7Vi6P9bihlpxyVn42dTD5c0Blm8zYE+YWFhJtLk5FkcsCKK3w+JnO6BxkJgOGDp3d0HTpQ8HN7dafw17Exnuh8+GtgQA5Bkk1PN3x9sPNFE4KqKKU6lUeKJDONZP6oZ2dXyRmWvA1D9O4OmfDyAhrfydZ2f9ew65BiM616+BHg3tu31/WZhYWIm5xoJdN8nM190Zv47pgIaBHriVlQdANIJ6zAF2K5TTkHtq4Z2BTdEq1BtzHm9TZad/qHoJr+GOZeM64a0BTeCsVWPruRvo98V2rDl6tczRi5NXU7H6aDwAUezsKJ12S8LEwkpYY0HFqeGhw69jOqJbRE0Miwyttlfrz3atizUvdkXTkKq95JyqF41ahbH31sPfE7uiRS1vpGbnYdKyo3jh18NIytAX+xhJkgo2GxzSOqTUZemOgomFFeQZjAXtbh1tORVZn7+nDr882wH/G9ZK1vXvRGQfIgI98ccLnfFyn4bQqlVYf/I67pu9HRtPJ9x1363nb2D3pSQ4a9R49T7Hat1dEr6qWUF8SjYMRgk6rRoBnvbRMY2IiGzHSaPGpD4RWPVCFzQM9MDNjFyMXXQQr6w4hrQcMRVqMEr4ZN1ZAMDoLnWqTAE3EwsriL5tRQjX5RMRVV8tQr2x9sWueK57PahUwMrDV3D/F9ux88JNrDx0BecS0uHt6oQJPRooHapsWDVlBbFcEUJERCYuThpM7d8EfZsE4pXfjiEmKQtPzt8HN1MDrIm9GsDbzX7671QWRyysoLBwk/UVREQktK3jh/WTuuGpjuEAxBYEob6ueKpTuMKRyYsjFlYQk8wVIUREdDc3Zy0+HNIc/ZoF4te9sRh7b12rbsGuBCYWVsCum0REVJpuEf7oFuHYjbBKwqkQmUmShJhkc40Fp0KIiKh6YWIhs8R0PXLyjFCrgFo+rkqHQ0REZFNMLGRmLtys5esKZy3/e4mIqHqx6J1v5syZaNeuHTw9PREQEIAhQ4bg3Llz1orNIcWYl5pyjxAiIqqGLEostm3bhgkTJmDv3r3YuHEj8vPz0a9fP2RmZlorPodj3nysNleEEBFRNWTRqpANGzYU+XzBggUICAjAoUOHcO+998oamKMyd90M54oQIiKqhipVBJCamgoA8PPzkyWYqqCw6yanQoiIqPqpcB8LSZIwZcoUdO3aFc2bNy/xfnq9Hnp94XaxaWlpFT2lQ2BzLCIiqs4qPGLx4osv4vjx41i6dGmp95s5cya8vb0LPsLCwip6SruXmpWHlCyxax2bYxERUXVUocRi4sSJWLt2LbZs2YLQ0NBS7zt16lSkpqYWfMTFxVUoUEdgboxV00MHdx2bmhIRUfVj0bufJEmYOHEiVq1aha1bt6Ju3bplPkan00Gn01U4QEdSuPkYRyuIiKh6siixmDBhApYsWYI1a9bA09MT169fBwB4e3vD1ZVdJmNZX0FERNWcRVMhc+fORWpqKnr06IHg4OCCj+XLl1srPofC5lhERFTdWTwVQiXjVAgREVV33MxCRubEgl03iYioumJiIZOcPAOup+UAYNdNIiKqvphYyCTOVLjpqdPCz91Z4WiIiIiUwcRCJrdPg6hUKoWjISIiUgYTC5mwlTcRERETC9mYl5rW5lJTIiKqxphYyIRLTYmIiJhYyIZdN4mIiJhYyMJglHDlljmx4FQIERFVX0wsZBCfko08gwRnjRpBXi5Kh0NERKQYJhYyMNdXhPq5QqPmUlMiIqq+mFjIICZZrAipw2kQIiKq5phYyCDW3ByLrbyJiKiaY2IhAy41JSIiEphYyCDa1ByLiQUREVV3TCwqSZKkgh4W7LpJRETVHROLSrqZkYusXANUKiDMz1XpcIiIiBTFxKKSYk0rQkK8XaHTahSOhoiISFlMLCophitCiIiICjCxqKRorgghIiIqwMSikmLN26UzsSAiImJiUVkxphUh7LpJRETExKLS2HWTiIioEBOLSkjPyUNSZi4A1lgQEREBTCwqxbwixM/dGZ4uTgpHQ0REpDwmFpVQ2HGToxVEREQAE4tKMY9Y1OE0CBEREQAmFpVi7rpZmytCiIiIADCxqJSC7dI5FUJERASAiUWlxLDrJhERURFMLCpIn29AfGo2AHbdJCIiMmNiUUFXbmVDkgA3Zw38PXRKh0NERGQXmFhU0O0dN1UqlcLREBER2QcmFhUUbdp8jPUVREREhZhYVFBh4SaXmhIREZlZnFhs374dgwYNQkhICFQqFVavXm2FsOyfuesmRyyIiIgKWZxYZGZmolWrVpgzZ4414nEYMeapED+OWBAREZlpLX1A//790b9/f2vE4jAMRglxyWKpKUcsiIiIClmcWFhKr9dDr9cXfJ6WlmbtU1rd9bQc5BqM0KpVCPZ2UTocIiIiu2H14s2ZM2fC29u74CMsLMzap7Q68zRIqK8rtBrWvxIREZlZ/V1x6tSpSE1NLfiIi4uz9imtLpYrQoiIiIpl9akQnU4Hna5qdaaM4YoQIiKiYnEcvwJu77pJREREhSwescjIyMDFixcLPo+KisLRo0fh5+eH2rVryxqcvSrsusmpECIiottZnFgcPHgQPXv2LPh8ypQpAIBRo0bh559/li0weyVJ0m01FhyxICIiup3FiUWPHj0gSZI1YnEIt7LykK7PB8CpECIiojuxxsJC5qWmQV4ucHHSKBwNERGRfWFiYSHzHiG1OQ1CRER0FyYWFoq+aaqv4DQIERHRXZhYWCgm2bwihIkFERHRnZhYWIhdN4mIiErGxMJC7LpJRERUMiYWFsjU5+NGutipNdyPIxZERER3YmJhAfOKEG9XJ3i7OSkcDRERkf1hYmGBGFN9RR1OgxARERWLiYUFYk0rQmqzcJOIiKhYTCwsYB6xYA8LIiKi4jGxsIA5sWDXTSIiouIxsbBAQXMsjlgQEREVi4lFOeUZjIhPyQEA1KnJGgsiIqLiMLEop6u3smEwSnBxUiPAU6d0OERERHaJiUU5mTtu1vZzg0qlUjgaIiIi+8TEopxikkxLTdlxk4iIqERMLMqpYKkpV4QQERGViIlFObHrJhERUdmYWJQTu24SERGVjYlFORiNErtuEhERlQMTi3JITNdDn2+ERq1CLV9XpcMhIiKyW0wsysG8IiTExwVOGv6XERERlYTvkuVg7mFRh/UVREREpWJiUQ6xSYXNsYiIiKhkTCzKIdo0FcIeFkRERKVjYlEOsQXtvDkVQkREVBomFuVQ0ByrJkcsiIiISsPEogypWXlIzc4DwBoLIiKisjCxKEOMqeOmv6cObs5ahaMhIiKyb0wsyhDNjptERETlxsSiDLHm7dK5IoSIiKhMTCzKULirKVeEEBERlYWJRRnMXTfZw4KIiKhsFUosvv32W9StWxcuLi6IjIzEjh075I7LbrDrJhERUflZnFgsX74ckydPxltvvYUjR46gW7du6N+/P2JjY60Rn6Jy8gy4npYDAAjnVAgREVGZLE4sPv/8czz77LMYM2YMmjRpgtmzZyMsLAxz5861RnyKMnfc9NRp4evmpHA0RERE9s+ixgy5ubk4dOgQ3nzzzSJf79evH3bv3l3sY/R6PfR6fcHnaWlpFQizbJ//ew7p+nxZjxmfkg0ACK/pBpVKJeuxiYiIqiKLEoubN2/CYDAgMDCwyNcDAwNx/fr1Yh8zc+ZMvP/++xWPsJyWHYhDYrq+7DtWQESAp1WOS0REVNVUqJXknVfvkiSVeEU/depUTJkypeDztLQ0hIWFVeS0pRrdpQ4yZR6xAABnjQZD24bKflwiIqKqyKLEombNmtBoNHeNTiQmJt41imGm0+mg0+kqHmE5vdCjgdXPQURERKWzqHjT2dkZkZGR2LhxY5Gvb9y4EZ07d5Y1MCIiInI8Fk+FTJkyBU899RTatm2LTp064YcffkBsbCzGjx9vjfiIiIjIgVicWAwfPhxJSUn44IMPcO3aNTRv3hzr1q1DeHi4NeIjIiIiB6KSJEmy5QnT0tLg7e2N1NRUeHl52fLUREREVEHlff/mXiFEREQkGyYWREREJBsmFkRERCQbJhZEREQkGyYWREREJBsmFkRERCQbJhZEREQkGyYWREREJBsmFkRERCSbCm2bXhnmRp9paWm2PjURERFVkPl9u6yG3TZPLNLT0wEAYWFhtj41ERERVVJ6ejq8vb1L/L7N9woxGo2Ij4+Hp6cnVCqVbMdNS0tDWFgY4uLiuAeJA+Dvy3Hwd+U4+LtyLI72+5IkCenp6QgJCYFaXXIlhc1HLNRqNUJDQ612fC8vL4f4BZHA35fj4O/KcfB35Vgc6fdV2kiFGYs3iYiISDZMLIiIiEg2VSax0Ol0eO+996DT6ZQOhcqBvy/Hwd+V4+DvyrFU1d+XzYs3iYiIqOqqMiMWREREpDwmFkRERCQbJhZEREQkGyYWREREJJsqk1h8++23qFu3LlxcXBAZGYkdO3YoHRLdYfr06VCpVEU+goKClA6LTLZv345BgwYhJCQEKpUKq1evLvJ9SZIwffp0hISEwNXVFT169MCpU6eUCbaaK+t3NXr06Lueax07dlQm2Gpu5syZaNeuHTw9PREQEIAhQ4bg3LlzRe5T1Z5bVSKxWL58OSZPnoy33noLR44cQbdu3dC/f3/ExsYqHRrdoVmzZrh27VrBx4kTJ5QOiUwyMzPRqlUrzJkzp9jvf/bZZ/j8888xZ84cHDhwAEFBQejbt2/B/j9kO2X9rgDg/vvvL/JcW7dunQ0jJLNt27ZhwoQJ2Lt3LzZu3Ij8/Hz069cPmZmZBfepcs8tqQpo3769NH78+CJfa9y4sfTmm28qFBEV57333pNatWqldBhUDgCkVatWFXxuNBqloKAg6ZNPPin4Wk5OjuTt7S199913CkRIZnf+riRJkkaNGiUNHjxYkXiodImJiRIAadu2bZIkVc3nlsOPWOTm5uLQoUPo169fka/369cPu3fvVigqKsmFCxcQEhKCunXr4rHHHsPly5eVDonKISoqCtevXy/yPNPpdOjevTufZ3Zq69atCAgIQMOGDTF27FgkJiYqHRIBSE1NBQD4+fkBqJrPLYdPLG7evAmDwYDAwMAiXw8MDMT169cVioqK06FDByxatAj//PMP5s2bh+vXr6Nz585ISkpSOjQqg/m5xOeZY+jfvz9+/fVXbN68GbNmzcKBAwfQq1cv6PV6pUOr1iRJwpQpU9C1a1c0b94cQNV8btl8d1NruXMLdkmSZN2WnSqvf//+BbdbtGiBTp06oX79+li4cCGmTJmiYGRUXnyeOYbhw4cX3G7evDnatm2L8PBw/P3333j44YcVjKx6e/HFF3H8+HHs3Lnzru9VpeeWw49Y1KxZExqN5q7MLjEx8a4MkOyLu7s7WrRogQsXLigdCpXBvHqHzzPHFBwcjPDwcD7XFDRx4kSsXbsWW7ZsQWhoaMHXq+Jzy+ETC2dnZ0RGRmLjxo1Fvr5x40Z07txZoaioPPR6Pc6cOYPg4GClQ6Ey1K1bF0FBQUWeZ7m5udi2bRufZw4gKSkJcXFxfK4pQJIkvPjii/jjjz+wefNm1K1bt8j3q+Jzq0pMhUyZMgVPPfUU2rZti06dOuGHH35AbGwsxo8fr3RodJtXX30VgwYNQu3atZGYmIiPPvoIaWlpGDVqlNKhEYCMjAxcvHix4POoqCgcPXoUfn5+qF27NiZPnowZM2YgIiICERERmDFjBtzc3DBixAgFo66eSvtd+fn5Yfr06XjkkUcQHByM6OhoTJs2DTVr1sRDDz2kYNTV04QJE7BkyRKsWbMGnp6eBSMT3t7ecHV1hUqlqnrPLUXXpMjom2++kcLDwyVnZ2epTZs2BUt5yH4MHz5cCg4OlpycnKSQkBDp4Ycflk6dOqV0WGSyZcsWCcBdH6NGjZIkSSyLe++996SgoCBJp9NJ9957r3TixAllg66mSvtdZWVlSf369ZP8/f0lJycnqXbt2tKoUaOk2NhYpcOulor7PQGQFixYUHCfqvbc4rbpREREJBuHr7EgIiIi+8HEgoiIiGTDxIKIiIhkw8SCiIiIZMPEgoiIiGTDxIKIiIhkw8SCiIiIZMPEgoiIiGTDxIKIiIhkw8SCiIiIZMPEgoiIiGTDxIKIiIhk8/8Ceg59buyhMgAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe.output[0].plot_energies()" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "id": "bb70a653-6231-4f4e-9bbe-279811acc895", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "72151a9d47704d1fb535afa54a44cdda", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "NGLWidget(max_frame=21)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe.output[0].animate_structures()" ] @@ -416,9 +329,11 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "id": "f816e2af-0455-4e05-9c39-2e9f615d8f34", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "from pyiron_atomistics import ase_to_pyiron" @@ -426,19 +341,23 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "id": "22314020-8f48-487b-a765-229a77d79a2f", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "mi = AseMinimizeNode()" + "mi = AseMinimizeTask()" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "id": "ff411a05-82e1-4581-b06e-ab2fd7e0be3b", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "dimer = Atoms(symbols=['Fe', 'Fe'], positions=[[0,0,0], [0,0, .75]], cell=[10,10,10])" @@ -446,9 +365,11 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "id": "5574f0d5-d800-472a-9418-8c6ccc1e555b", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "mi.input.structure = dimer\n", @@ -457,34 +378,23 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "id": "9e02d6dd-0fa6-4dd6-a7ab-3e648958eb20", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "cdde213a7a3445d08efacffc1c7b0581", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "NGLWidget()" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "ase_to_pyiron(mi.input.structure).plot3d()" ] }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "id": "663e4435-1cd0-4ce2-9593-85453f4c846a", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "mi.input.max_steps = 100\n", @@ -494,9 +404,11 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "id": "37440e5a-75ff-4601-813a-f5c8df9413ad", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "mi.input.gpmin()" @@ -504,27 +416,13 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": null, "id": "a448ec7f-53bc-4d72-a8a7-f9392de9f3d5", "metadata": { "scrolled": true, "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " Step Time Energy fmax\n", - "GPMin: 0 09:04:10 11.122159 187.2462\n", - "GPMin: 1 09:04:10 -0.278268 1.5338\n", - "GPMin: 2 09:04:10 -0.996055 0.8010\n", - "GPMin: 3 09:04:10 -0.000000 0.0000\n", - "CPU times: user 91 ms, sys: 45.6 ms, total: 137 ms\n", - "Wall time: 75.7 ms\n" - ] - } - ], + "outputs": [], "source": [ "%%time\n", "exe = mi.run(how='foreground')" @@ -532,176 +430,96 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "id": "5977dd10-c4cf-40c9-944e-5aa52cfa263d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(ReturnStatus(Code.DONE, None),)" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe.status" ] }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "id": "dd164778-634c-4785-903a-08a5243999ce", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2.136147842601888e-07" - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "abs(exe.output[0].forces[-1]).max()" ] }, { "cell_type": "code", - "execution_count": 35, + "execution_count": null, "id": "515ea06d-9026-4d9e-9df0-b9c249f0758a", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 35, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe._run_machine.state" ] }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "id": "52b7231f-8978-46ec-b698-ea8724a6fea3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.07557120098499581" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe._run_time" ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": null, "id": "c845430c-119d-4566-88e1-8465e378fde1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1.684704329818487e-05" - ] - }, - "execution_count": 37, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe._collect_time" ] }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "id": "35291d7f-33a9-41ab-9b80-f052c5eb2e55", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe.output[0].plot_energies()" ] }, { "cell_type": "code", - "execution_count": 39, + "execution_count": null, "id": "1d5b5203-d07f-485b-9553-9150f4a674e7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[11.122158782511535,\n", - " -0.2782678462106827,\n", - " -0.9960554302957411,\n", - " -3.560246436024868e-08,\n", - " -3.560246436024868e-08]" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe.output[0].pot_energies" ] }, { "cell_type": "code", - "execution_count": 40, + "execution_count": null, "id": "d2cc3b3a-5daa-49bb-9d6d-2994ebc74273", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "63d6a4ee513142359ee6edc4754f18ab", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "NGLWidget(max_frame=4)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe.output[0].animate_structures()" ] @@ -726,52 +544,49 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": null, "id": "4acdeafc-90b5-4b3f-9559-c74b9fa221ab", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "m = MurnaghanNode()" + "m = MurnaghanTask()" ] }, { "cell_type": "code", - "execution_count": 42, + "execution_count": null, "id": "f8cf3136-9b7c-4f1e-b630-962795527946", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "m.input.node = AseStaticNode()\n", - "m.input.node.input.calculator = MorsePotential()\n", + "m.input.task = AseStaticTask()\n", + "m.input.task.input.calculator = MorsePotential()\n", "m.input.structure = bulk(\"Fe\", a=1.2)" ] }, { "cell_type": "code", - "execution_count": 43, + "execution_count": null, "id": "fef21aa4-d9f1-4d4a-8761-af1bc3121e5b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 43, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ - "m.input.node.input" + "m.input.task.input" ] }, { "cell_type": "code", - "execution_count": 44, + "execution_count": null, "id": "41a68b17-c7c4-4a5f-8f04-11bee18fe55a", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "m.input.set_strain_range(.5, 50)" @@ -779,58 +594,31 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": null, "id": "fd107556-99b6-4042-9209-9412b4bbff94", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([0.79370053, 0.8043555 , 0.81473542, 0.82485739, 0.83473686,\n", - " 0.84438786, 0.85382314, 0.86305437, 0.87209225, 0.88094658,\n", - " 0.88962642, 0.89814011, 0.90649538, 0.9146994 , 0.92275884,\n", - " 0.93067991, 0.93846839, 0.94612969, 0.95366889, 0.96109074,\n", - " 0.96839969, 0.97559996, 0.98269548, 0.98968999, 0.996587 ,\n", - " 1.00338986, 1.01010169, 1.0167255 , 1.02326411, 1.0297202 ,\n", - " 1.03609634, 1.04239496, 1.04861836, 1.05476875, 1.06084824,\n", - " 1.06685884, 1.07280247, 1.07868096, 1.08449606, 1.09024946,\n", - " 1.09594278, 1.10157754, 1.10715524, 1.11267731, 1.1181451 ,\n", - " 1.12355993, 1.12892306, 1.13423572, 1.13949907, 1.14471424])" - ] - }, - "execution_count": 45, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "m.input.strains" ] }, { "cell_type": "code", - "execution_count": 46, + "execution_count": null, "id": "0715614a-7284-4388-ac6b-c97bfedf7184", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 46, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "m.input.check_ready()" ] }, { "cell_type": "code", - "execution_count": 47, + "execution_count": null, "id": "c2aa3093-1ea8-4099-bc14-be0c06e9d34b", "metadata": { "scrolled": true, @@ -843,105 +631,50 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": null, "id": "be97853a-f182-4b4f-af74-7fd5a4fbd850", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(ReturnStatus(Code.DONE, None),)" - ] - }, - "execution_count": 48, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "exe.status" ] }, { "cell_type": "code", - "execution_count": 49, + "execution_count": null, "id": "47f916ef-b140-49c5-adf1-93dca91b4540", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "exe.output[0].plot()" ] }, { "cell_type": "code", - "execution_count": 50, + "execution_count": null, "id": "14162f5b-1318-4595-8c8c-d6346a21721d", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.6788586373205143" - ] - }, - "execution_count": 50, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "exe.output[0].equilibrium_volume" ] }, { "cell_type": "code", - "execution_count": 51, + "execution_count": null, "id": "0f5ff296-df33-40d2-851b-02d6ded72dd6", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.6788586373205143" - ] - }, - "execution_count": 51, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "exe.output[0].get_structure().get_volume()" ] }, { "cell_type": "code", - "execution_count": 52, + "execution_count": null, "id": "92b06330-b1fc-41d0-8bd8-bf1b11bf448c", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Atoms(symbols='Fe', pbc=True, cell=[[-0.5536557129291797, 0.5536557129291797, 0.5536557129291797], [0.5536557129291797, -0.5536557129291797, 0.5536557129291797], [0.5536557129291797, 0.5536557129291797, -0.5536557129291797]])" - ] - }, - "execution_count": 52, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "exe.output[0].get_structure()" ] @@ -951,55 +684,44 @@ "id": "26bade63-559f-4c93-be24-5561f5c8190f", "metadata": {}, "source": [ - "## Again but execute children as background processes, but keep the node itself blocking" + "## Again but execute children as background processes, but keep the task itself blocking" ] }, { "cell_type": "code", - "execution_count": 53, + "execution_count": null, "id": "89169376-be36-4ceb-9f4e-6e1f3247bc62", "metadata": {}, "outputs": [], "source": [ - "m = MurnaghanNode()" + "m = MurnaghanTask()" ] }, { "cell_type": "code", - "execution_count": 54, + "execution_count": null, "id": "2ee9f1d4-5b14-4340-98d4-4bd293af89a4", "metadata": {}, "outputs": [], "source": [ - "m.input.node = AseStaticNode()\n", - "m.input.node.input.calculator = MorsePotential()\n", + "m.input.task = AseStaticTask()\n", + "m.input.task.input.calculator = MorsePotential()\n", "m.input.structure = bulk(\"Fe\", a=1.2)" ] }, { "cell_type": "code", - "execution_count": 55, + "execution_count": null, "id": "3d73a9de-7b4e-476a-b50a-ac6a3957a7ab", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 55, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "m.input.node.input" + "m.input.task.input" ] }, { "cell_type": "code", - "execution_count": 56, + "execution_count": null, "id": "0f075d90-e636-49be-b1a6-741a56363f54", "metadata": {}, "outputs": [], @@ -1017,7 +739,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": null, "id": "58064e52-1c94-49fd-b38f-614cf6f19004", "metadata": {}, "outputs": [], @@ -1027,7 +749,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": null, "id": "d82a28ab-1a96-4a3a-8f79-5a875ac20788", "metadata": { "scrolled": true, @@ -1040,129 +762,63 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": null, "id": "5472daed-f25c-4aab-b101-90c76a0235a5", "metadata": { "scrolled": true, "tags": [] }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 59, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "exe._run_machine.state" ] }, { "cell_type": "code", - "execution_count": 60, + "execution_count": null, "id": "a42865c9-8616-4335-8e46-ec4839daab0a", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "27.43538186798105" - ] - }, - "execution_count": 60, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "exe._run_time" ] }, { "cell_type": "code", - "execution_count": 61, + "execution_count": null, "id": "d894a79f-a9e6-4667-9a9b-2bd9a088622f", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "8.610018994659185e-06" - ] - }, - "execution_count": 61, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "exe._collect_time" ] }, { "cell_type": "code", - "execution_count": 62, + "execution_count": null, "id": "78017969-23fc-46f5-b99f-cd1d2dc74c00", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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uu+++1RcXCyHw6GxY8fqiSeeiFK18WWg1/aZZ57R5MmTlZaWJrfbrX/8x39UTU1NlKqNHxs2bND8+fNVUFAgi8WiV1555bTPWb9+vaZMmaKUlBSNGTNG//Ef/xH5QuPQQK/tSy+9pDlz5ignJ0eZmZmaPn261qxZE51i40wof2+7hfO9jCASglWrVmnRokW67777tHXrVl1yySWaN2+eDh482OfzGhoadP311+trX/talCqNP6Fc22uuuUZ/+9vf9Pjjj2vXrl167rnnNGHChChWHR8Gem3fffddXX/99brxxhu1fft2vfDCC9q0aZNuuummKFce+3w+nyZPnqwVK1b06/FlZWW6/PLLdckll2jr1q2699579eMf/1gvvvhihCuNPwO9ths2bNCcOXO0evVqbdmyRbNnz9b8+fO1devWCFcafwZ6bbuF/b3MwIBdcMEFxq233trj3IQJE4yf//znfT5vwYIFxj//8z8b999/vzF58uQIVhi/BnptX3/9dcPlchk1NTXRKC+uDfTaPvTQQ8aYMWN6nPvNb35jFBYWRqzGoUCS8fLLL/f5mHvuuceYMGFCj3O33HKLceGFF0awsvjXn2t7KhMnTjQeeOCB8Bc0hAzk2ob7vYwRkQFqbW3Vli1bNHfu3B7n586dq/fff7/X5z355JPau3ev7r///kiXGLdCubavvvqqpk6dqgcffFAjR47U+PHjdffdd6u5uTkaJceNUK7tjBkzdOjQIa1evVqGYejo0aP685//rCuuuCIaJQ9pH3zwwUl/Fl//+te1efNmtbW1mVTV0NTZ2Smv16usrCyzSxkSIvFeFtM3vYtF1dXV6ujoUF5eXo/zeXl5qqysPOVzdu/erZ///Od65513ZLNxyXsTyrXdt2+f3n33XaWkpOjll19WdXW1brvtNtXW1tIncoJQru2MGTP0zDPPaMGCBWppaVF7e7u++c1v6re//W00Sh7SKisrT/ln0d7erurqarndbpMqG3p+9atfyefz6ZprrjG7lLgXqfcyRkRCZLFYenxtGMZJ5ySpo6ND1157rR544AGNHz8+WuXFtf5eWynwrx2LxaJnnnlGF1xwgS6//HI9/PDD+uMf/8ioyCkM5Nru2LFDP/7xj/Uv//Iv2rJli9544w2VlZXp1ltvjUapQ96p/ixOdR6he+6557R06VKtWrVKubm5ZpcT1yL5XsY/zwcoOztbSUlJJ/0rsqqq6qR/4UiS1+vV5s2btXXrVt1xxx2SAm+ehmHIZrPpzTff1KWXXhqV2mPdQK+tJLndbo0cObLH7abPPPNMGYahQ4cOqbS0NKI1x4tQru3y5ct10UUX6ac//akk6ZxzzlF6erouueQS/eIXv+Bf7YOQn59/yj8Lm82mESNGmFTV0LJq1SrdeOONeuGFF3TZZZeZXU7ci+R7GSMiA2S32zVlyhStXbu2x/m1a9dqxowZJz0+MzNTn376qbZt2xY8br31Vp1xxhnatm2bpk2bFq3SY95Ar60kXXTRRTpy5IgaGxuD57744gtZrVYVFhZGtN54Esq1bWpqktXa838RSUlJko7/6x2hmT59+kl/Fm+++aamTp2q5ORkk6oaOp577jndcMMNevbZZ+lpCpOIvpcNut01AT3//PNGcnKy8fjjjxs7duwwFi1aZKSnpxv79+83DMMwfv7znxvXXXddr89n1UzvBnptvV6vUVhYaFx99dXG9u3bjfXr1xulpaXGTTfdZNavELMGem2ffPJJw2azGY899pixd+9e49133zWmTp1qXHDBBWb9CjHL6/UaW7duNbZu3WpIMh5++GFj69atxoEDBwzDOPna7tu3z0hLSzPuuusuY8eOHcbjjz9uJCcnG3/+85/N+hVi1kCv7bPPPmvYbDbj0UcfNSoqKoJHfX29Wb9CzBrotf2ycL2XEURC9OijjxrFxcWG3W43zjvvPGP9+vXB7y1cuNCYOXNmr88liPRtoNd2586dxmWXXWakpqYahYWFxuLFi42mpqYoVx0fBnptf/Ob3xgTJ040UlNTDbfbbfzgBz8wDh06FOWqY9/bb79tSDrpWLhwoWEYp76269atM77yla8YdrvdGD16tLFy5croFx4HBnptZ86c2efjcVwof29PFK73MothMMYKAADMQY8IAAAwDUEEAACYhiACAABMQxABAACmIYgAAADTEEQAAIBpCCIAAMA0BBEAAGAagggAADANQQQAAJiGIAIAAExDEAEAAKb5v1E7qt2HJCfYAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "exe.output[0].plot()" ] }, { "cell_type": "code", - "execution_count": 63, + "execution_count": null, "id": "45162eb2-b23d-45c6-8aad-dfe9a6a484d1", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.6818586500998999" - ] - }, - "execution_count": 63, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "exe.output[0].get_structure().get_volume()" ] }, { "cell_type": "code", - "execution_count": 64, + "execution_count": null, "id": "f0bcfe59-2168-4e74-9d7a-33d900368907", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.6818586500999" - ] - }, - "execution_count": 64, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "exe.output[0].equilibrium_volume" ] @@ -1177,52 +833,49 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": null, "id": "10f6c113-1e35-48f0-8878-291129bd8a60", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "m = MurnaghanNode()" + "m = MurnaghanTask()" ] }, { "cell_type": "code", - "execution_count": 86, + "execution_count": null, "id": "70832c31-040e-49be-b0f7-172f930cf31b", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "m.input.node = AseStaticNode()\n", - "m.input.node.input.calculator = MorsePotential()\n", + "m.input.task = AseStaticTask()\n", + "m.input.task.input.calculator = MorsePotential()\n", "m.input.structure = bulk(\"Fe\", a=1.2)" ] }, { "cell_type": "code", - "execution_count": 87, + "execution_count": null, "id": "94f4c51d-b69b-4477-a9db-d0ee7627cee6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 87, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ - "m.input.node.input" + "m.input.task.input" ] }, { "cell_type": "code", - "execution_count": 88, + "execution_count": null, "id": "9a000824-0a9e-4395-8e07-00e484bc7937", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "m.input.set_strain_range(.5, 100)" @@ -1238,7 +891,7 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": null, "id": "ca50857c-1c2f-4fad-a58c-16b399b8721d", "metadata": {}, "outputs": [], @@ -1250,7 +903,7 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": null, "id": "12f20f5c-27a3-4533-ad19-8ec06e1c8a90", "metadata": { "scrolled": true, @@ -1263,9 +916,11 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": null, "id": "2c5d8bea-49ec-4004-8a54-3ded7a3f413d", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "exe.wait()" @@ -1273,84 +928,48 @@ }, { "cell_type": "code", - "execution_count": 92, + "execution_count": null, "id": "ea7bcb58-0890-487e-bef5-bd3cb36143c1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 92, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe._run_machine.state" ] }, { "cell_type": "code", - "execution_count": 93, + "execution_count": null, "id": "0b7c2912-6847-4262-a62d-7233ca398643", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "10.82231778698042" - ] - }, - "execution_count": 93, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe._run_time" ] }, { "cell_type": "code", - "execution_count": 94, + "execution_count": null, "id": "fd5ca921-2062-4f85-b014-382561e9893a", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5.160982254892588e-06" - ] - }, - "execution_count": 94, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe._collect_time" ] }, { "cell_type": "code", - "execution_count": 95, + "execution_count": null, "id": "4d1223f7-2d72-413e-b20b-cf42781780bb", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe.output[0].plot()" ] @@ -1365,36 +984,42 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": null, "id": "149c52b5-a0ce-4e6b-ba55-d94d33aa2f8a", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "m = MurnaghanNode()" + "m = MurnaghanTask()" ] }, { "cell_type": "code", - "execution_count": 77, + "execution_count": null, "id": "aca24005-ea49-4389-bc26-f292fd0a75a2", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "m.input.node = AseMinimizeNode()\n", - "m.input.node.input.calculator = MorsePotential()\n", - "m.input.node.input.max_steps = 100\n", - "m.input.node.input.output_steps = 10\n", - "m.input.node.input.ionic_force_tolerance = 1e-6\n", - "m.input.node.input.lbfgs()\n", + "m.input.task = AseMinimizeTask()\n", + "m.input.task.input.calculator = MorsePotential()\n", + "m.input.task.input.max_steps = 100\n", + "m.input.task.input.output_steps = 10\n", + "m.input.task.input.ionic_force_tolerance = 1e-6\n", + "m.input.task.input.lbfgs()\n", "\n", "m.input.structure = bulk(\"Fe\", a=1.2)" ] }, { "cell_type": "code", - "execution_count": 78, + "execution_count": null, "id": "4ae990bd-af18-4dae-8500-779c9509f3f6", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "m.input.set_strain_range(.5, 500)" @@ -1402,9 +1027,11 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": null, "id": "fa62529f-45e6-4e2d-822d-b1cc433a7223", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "m.input.child_executor = ProcessExecutor" @@ -1412,1105 +1039,59 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": null, "id": "0925864e-4dd1-4f4e-ace4-aac09c55e787", "metadata": { "scrolled": true, "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:53 4.517693 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:53 4.251945 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:53 3.991875 0.0000\n", - "LBFGS: 0 09:04:53 4.789242 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 3.737364 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 3.488292 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 3.006013 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 3.244546 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 2.772582 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 2.544148 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 2.320604 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 2.101849 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 1.887783 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 1.272749 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 1.473327 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 1.678307 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 1.076481 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 0.696523 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 0.884435 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 0.512659 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 0.332761 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 0.156747 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 -0.015462 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 -0.183946 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 -0.348779 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 -0.510037 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:54 -0.667792 0.0000\n", - "LBFGS: 0 09:04:54 -0.822116 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -0.973078 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -1.120747 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -1.406469 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -1.265189 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -1.544652 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -1.811973 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -1.679799 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -1.941232 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -2.067636 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -2.191241 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -2.312104 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -2.430279 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -2.658780 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -2.545820 0.0000\n", - "LBFGS: 0 09:04:55 -2.769210 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -2.877160 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -2.982680 0.0000\n", - "LBFGS: 0 09:04:55 -3.186620 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -3.085817 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -3.285134 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -3.381404 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -3.475475 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:55 -3.567390 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -3.657192 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -3.744922 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -3.830620 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -3.914328 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -3.996084 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -4.075925 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -4.153891 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -4.230016 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -4.304338 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -4.376892 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -4.447711 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -4.516830 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -4.584282 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -4.650099 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -4.714313 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -4.838057 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -4.776956 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -4.897647 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -4.955755 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -5.012409 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -5.067638 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:56 -5.121469 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.173930 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.225046 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.274844 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.323350 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.370587 0.0000\n", - "LBFGS: 0 09:04:57 -5.416581 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.461356 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.504934 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.588595 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.547340 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.628722 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.667742 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.705677 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.742548 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.778375 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.813177 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.846976 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.879789 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.911636 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.942536 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -5.972506 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -6.001565 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -6.029730 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -6.057017 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:57 -6.083445 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.109029 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.133786 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.157731 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.180881 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.203250 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.245705 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.224853 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.265820 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.285213 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.303898 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.321888 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.339196 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.355836 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.371820 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.387162 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.401872 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.415965 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.442342 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.429451 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.454650 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.466386 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.477561 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.488186 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.498271 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.507828 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.516865 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.525395 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.533425 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.540966 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.548028 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.554620 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.560750 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.566429 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.571664 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.576465 0.0000\n", - "LBFGS: 0 09:04:58 -6.580840 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.584797 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:58 -6.588345 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.594245 0.0000\n", - "LBFGS: 0 09:04:59 -6.591492 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.596612 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.598601 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.601475 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.600220 0.0000\n", - "LBFGS: 0 09:04:59 -6.602374 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.602924 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.603132 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.602549 0.0000\n", - "LBFGS: 0 09:04:59 -6.603005 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.601771 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.600678 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.597570 0.0000\n", - "LBFGS: 0 09:04:59 -6.599275 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.595568 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.593275 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.590697 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.587840 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.584710 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.581312 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.577651 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.573734 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.569565 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.565149 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.560493 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.550475 0.0000\n", - "LBFGS: 0 09:04:59 -6.555599 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.545124 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.539551 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.533761 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.527759 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.521548 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.515134 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.508521 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.501712 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.494713 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.487527 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.480158 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:04:59 -6.472611 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.464889 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.440712 0.0000\n", - "LBFGS: 0 09:05:00 -6.448936 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.456996 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.432329 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.415096 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.423789 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.406254 0.0000\n", - "LBFGS: 0 09:05:00 -6.397266 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.388136 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.378866 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.369460 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.359921 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.350251 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.340455 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.330535 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.310333 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.320493 0.0000\n", - "LBFGS: 0 09:05:00 -6.300058 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.289669 0.0000\n", - "LBFGS: 0 09:05:00 -6.279171 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.268565 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.247042 0.0000\n", - "LBFGS: 0 09:05:00 -6.257855 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.236130 0.0000\n", - "LBFGS: 0 09:05:00 -6.225120 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.202819 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.214016 0.0000\n", - "LBFGS: 0 09:05:00 -6.180158 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.168698 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.191532 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.157154 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.145530 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:00 -6.133827 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -6.122047 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -6.110193 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -6.098266 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -6.086268 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -6.074202 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -6.062068 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -6.049871 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -6.037610 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -6.025288 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -6.000468 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -6.012907 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.987974 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.975426 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.962825 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.950174 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.937473 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.924725 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.911931 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.899093 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.886212 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.873290 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.860327 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.847326 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.821215 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.834289 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.808107 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.794966 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.781794 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.755359 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.768591 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.742099 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.728813 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.715501 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.702165 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.688805 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.675424 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.662022 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.648600 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:01 -5.635159 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.621701 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.608226 0.0000\n", - "LBFGS: 0 09:05:02 -5.594735 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.581230 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.567712 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.554180 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.540637 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.527083 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.513519 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.499947 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.486366 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.459183 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.472778 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.431978 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.445583 0.0000\n", - "LBFGS: 0 09:05:02 -5.418369 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.404757 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.391143 0.0000\n", - "LBFGS: 0 09:05:02 -5.377527 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.363910 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.350293 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.336676 0.0000\n", - "LBFGS: 0 09:05:02 -5.309448 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.323061 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.295837 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.268625 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.282229 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.255026 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.241432 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.227844 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.214262 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.200687 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.187119 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.160009 0.0000\n", - "LBFGS: 0 09:05:02 -5.173560 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:02 -5.146468 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -5.119414 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -5.132936 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -5.092403 0.0000\n", - "LBFGS: 0 09:05:03 -5.105903 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -5.065440 0.0000\n", - "LBFGS: 0 09:05:03 -5.078915 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -5.051977 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -5.038527 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -5.025090 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -5.011668 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.984867 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.998260 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.958128 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.971490 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.944782 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.931453 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.918140 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.904845 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.891567 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.878308 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.865067 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.851844 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.838641 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.825456 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.812292 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.799147 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.786023 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.772919 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.759837 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.746775 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.733735 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.720716 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.707720 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.694745 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.681794 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.668865 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.643076 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.630216 0.0000\n", - "LBFGS: 0 09:05:03 -4.655959 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:03 -4.617381 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.604569 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.579018 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.591781 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.566279 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.540876 0.0000\n", - "LBFGS: 0 09:05:04 -4.553565 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.528212 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.515574 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.490373 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.502960 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.477811 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.452766 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.440283 0.0000\n", - "LBFGS: 0 09:05:04 -4.465275 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.427826 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.415396 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.402992 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.390616 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.378267 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.365944 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.353649 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.341382 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.329142 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.316929 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.304745 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.292588 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.280459 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.268359 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.256286 0.0000\n", - "LBFGS: 0 09:05:04 -4.244242 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.232226 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.220239 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.208280 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.196350 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.184448 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:04 -4.172575 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -4.160732 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -4.148917 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -4.137131 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -4.125374 0.0000\n", - "LBFGS: 0 09:05:05 -4.113646 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -4.101948 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -4.090278 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -4.067028 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -4.055446 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -4.078638 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -4.043894 0.0000\n", - "LBFGS: 0 09:05:05 -4.032372 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -4.020879 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -4.009416 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.997982 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.975204 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.986578 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.952544 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.963859 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.941259 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.918778 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.930004 0.0000\n", - "LBFGS: 0 09:05:05 -3.896416 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.907582 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.885280 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.874174 0.0000\n", - "LBFGS: 0 09:05:05 -3.852051 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.863097 0.0000\n", - "LBFGS: 0 09:05:05 -3.841034 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.819091 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.808164 0.0000\n", - "LBFGS: 0 09:05:05 -3.830047 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.797267 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:05 -3.786400 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.775562 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.753978 0.0000\n", - "LBFGS: 0 09:05:06 -3.764755 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.743230 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.732513 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.711167 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.721825 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.700539 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.689941 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.679373 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.668834 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.658325 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.647846 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.637397 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.626978 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.616588 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.606228 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.595897 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.585597 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.575325 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.544689 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.565084 0.0000\n", - "LBFGS: 0 09:05:06 -3.554872 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.534536 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.524412 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.504253 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.514318 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.494218 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.484212 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.474235 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.464287 0.0000\n", - "LBFGS: 0 09:05:06 -3.454369 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.434619 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.444480 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.424788 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.405213 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:06 -3.414986 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.395469 0.0000\n", - "LBFGS: 0 09:05:07 -3.385754 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.376068 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.347181 0.0000\n", - "LBFGS: 0 09:05:07 -3.366410 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.356781 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.337610 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.309067 0.0000\n", - "LBFGS: 0 09:05:07 -3.328067 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.318553 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.299610 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.290181 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.271409 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.280781 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.262065 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.252749 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.243462 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.224971 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.234202 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.215768 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.206592 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.188325 0.0000\n", - "LBFGS: 0 09:05:07 -3.197445 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.179233 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.170169 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.161132 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.143141 0.0000\n", - "LBFGS: 0 09:05:07 -3.152123 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.134187 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.116361 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.107488 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.125260 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.098643 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.089825 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:07 -3.081034 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -3.072271 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -3.063534 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -3.054823 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -3.046140 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -3.037483 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -3.028853 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -3.020250 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -3.011673 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -3.003123 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.994599 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.986101 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.977630 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.969185 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.960766 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.952373 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.944006 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.935665 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.927350 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.919060 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.910796 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.902558 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.894346 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.886159 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.877998 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.869862 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.861751 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.853666 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.845605 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.837570 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.829560 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.821575 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.813615 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 09:05:08 -2.805679 0.0000\n" - ] - } - ], + "outputs": [], "source": [ "exe = m.run()" ] }, { "cell_type": "code", - "execution_count": 81, + "execution_count": null, "id": "a3069fe3-93bf-4c83-93e7-6d0ac56d8248", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 81, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe._run_machine.state" ] }, { "cell_type": "code", - "execution_count": 82, + "execution_count": null, "id": "71bbb913-7d7a-4bb6-b775-3fbc8e7e1f35", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(ReturnStatus(Code.DONE, None),)" - ] - }, - "execution_count": 82, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe.status" ] }, { "cell_type": "code", - "execution_count": 83, + "execution_count": null, "id": "4bf2df15-31dc-474c-b3df-f7c32b0fdaf2", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([4.78924238, 4.51769267, 4.25194477, 3.99187529, 3.7373637 ,\n", - " 3.48829227, 3.244546 , 3.00601254, 2.77258214, 2.54414756])" - ] - }, - "execution_count": 83, - "metadata": {}, - "output_type": "execute_result" - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "exe.output[0].energies[:10]" ] }, { "cell_type": "code", - "execution_count": 84, + "execution_count": null, "id": "eb0a2daf-9dab-4174-bfee-0cd1ef8c474e", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "exe.output[0].plot()" ] @@ -2532,7 +1113,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.0" + "version": "3.10.8" }, "widgets": { "application/vnd.jupyter.widget-state+json": { diff --git a/notebooks/tinybase/Basic.ipynb b/notebooks/tinybase/Basic.ipynb index bacc3d7a0..dfb089343 100644 --- a/notebooks/tinybase/Basic.ipynb +++ b/notebooks/tinybase/Basic.ipynb @@ -10,38 +10,17 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "fd11c1fd-6b5b-4739-ad10-9ebe47c0db49", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/ponder/science/phd/dev/pyiron_contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n", - " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "cf2ad1379a3b4306981fef55c99a0e26", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "from pyiron_contrib.tinybase.node import AbstractNode, FunctionNode, SeriesNode, LoopNode" + "from pyiron_contrib.tinybase.task import AbstractTask, FunctionTask, SeriesTask, LoopTask" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "95594ff4-2f77-49c2-b4a2-467268ecac00", "metadata": {}, "outputs": [], @@ -51,7 +30,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "88b1b600-28e0-4ad9-82d6-b2bd993efbda", "metadata": {}, "outputs": [], @@ -62,7 +41,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "e3d8cf33-1f39-4ef9-b92c-2dfd43cf4dd3", "metadata": {}, "outputs": [], @@ -75,7 +54,7 @@ "id": "5a1c480f-545b-411b-9cfc-a50af282de29", "metadata": {}, "source": [ - "# Function Node" + "# Function Task" ] }, { @@ -88,7 +67,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "9f2f3102-d15c-470a-b38c-f8084c9535ec", "metadata": {}, "outputs": [], @@ -106,17 +85,17 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "e125f49c-257b-4a24-bc81-83fe345d1dcf", "metadata": {}, "outputs": [], "source": [ - "f = FunctionNode(calc_fib)" + "f = FunctionTask(calc_fib)" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "324f3c10-385e-4577-b089-c305f8203ca5", "metadata": {}, "outputs": [ @@ -130,7 +109,7 @@ "DataContainer([])" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -141,7 +120,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "6c1f5af7-f5e9-41d9-a849-bab0ebc7dd9f", "metadata": {}, "outputs": [ @@ -151,7 +130,7 @@ "[]" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -162,7 +141,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "e0afb76d-d1b7-4b42-925f-fb117d58025e", "metadata": {}, "outputs": [ @@ -172,7 +151,7 @@ "{}" ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -183,7 +162,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "6a5c3235-9c6b-481f-b316-db7420d1ad43", "metadata": {}, "outputs": [], @@ -193,7 +172,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "4ade8d6a-6ce2-4f3a-b43d-71e1f87125bf", "metadata": {}, "outputs": [ @@ -203,7 +182,7 @@ "{'n': 10}" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -214,7 +193,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "id": "a39cfb50-4ed6-49ab-8ffb-af236cf61153", "metadata": {}, "outputs": [], @@ -224,7 +203,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "id": "aa095e57-3d3c-4af5-9a94-eda6af34e2b3", "metadata": {}, "outputs": [ @@ -232,10 +211,10 @@ "data": { "text/plain": [ "{'status': (ReturnStatus(Code.DONE, None),),\n", - " 'output': (,)}" + " 'output': (,)}" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -246,7 +225,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "id": "82d0ca07-12c2-4cd9-bba3-1f210a907b41", "metadata": {}, "outputs": [ @@ -256,7 +235,7 @@ "144" ] }, - "execution_count": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -267,17 +246,17 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "24994f4a-d5cd-4aad-857f-a33ddd0eaf23", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "(1.0009997060114983, 9.59599856287241e-06)" + "(1.0841432279994478, 3.1526011298410594e-05)" ] }, - "execution_count": 15, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -296,17 +275,17 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "a622ef14-4543-4cc1-bdfe-3625052f9b04", "metadata": {}, "outputs": [], "source": [ - "f = FunctionNode(calc_fib)" + "f = FunctionTask(calc_fib)" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "b0a0ff5d-6c5f-4c66-bc71-a9de5bf58220", "metadata": {}, "outputs": [], @@ -316,7 +295,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "b196759b-8cd6-45c7-8d3f-aad861b02edc", "metadata": {}, "outputs": [ @@ -324,10 +303,10 @@ "data": { "text/plain": [ "(ReturnStatus(Code.DONE, None),\n", - " )" + " )" ] }, - "execution_count": 18, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -346,17 +325,17 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "1e1b986e-9e00-41f2-86c2-945ff7818580", "metadata": {}, "outputs": [], "source": [ - "f = FunctionNode(calc_fib)" + "f = FunctionTask(calc_fib)" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "id": "0b612150-f654-4995-8910-e46e766fdce2", "metadata": {}, "outputs": [], @@ -366,7 +345,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "id": "bac98046-09c1-457c-881a-e31f03267788", "metadata": {}, "outputs": [], @@ -376,7 +355,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "id": "ba09ae22-d2b4-41ba-8637-cb7f0fb3bfe9", "metadata": {}, "outputs": [ @@ -386,7 +365,7 @@ "{}" ] }, - "execution_count": 22, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -397,7 +376,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 24, "id": "0d2f427a-21e1-449e-a8cc-c2296bff6c10", "metadata": {}, "outputs": [ @@ -407,7 +386,7 @@ "" ] }, - "execution_count": 23, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -418,7 +397,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "id": "a9631d5e-d46a-419c-a929-68ddd77487bb", "metadata": {}, "outputs": [], @@ -428,7 +407,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "id": "408ffab0-70a1-4d08-9007-4d9f0513935d", "metadata": {}, "outputs": [ @@ -438,7 +417,7 @@ "927372692193078999176" ] }, - "execution_count": 25, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -457,37 +436,37 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "id": "1e2178c0-ee70-4ec0-8b4b-2c7c55873b43", "metadata": {}, "outputs": [], "source": [ - "fib_node = FunctionNode(calc_fib)" + "fib_Task = FunctionTask(calc_fib)" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 28, "id": "998c4724-c7ab-4fc0-8794-8b60da819090", "metadata": {}, "outputs": [], "source": [ - "fib_node.input.kwargs['n'] = 100" + "fib_Task.input.kwargs['n'] = 100" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "id": "7b66b215-33cd-425c-bb9b-62e3eaa0451e", "metadata": {}, "outputs": [], "source": [ - "exe = fib_node.run(how='process')" + "exe = fib_Task.run(how='process')" ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 30, "id": "fcdcbe0b-f44a-4c37-acd8-10eedf5b3aa2", "metadata": {}, "outputs": [ @@ -497,7 +476,7 @@ "{}" ] }, - "execution_count": 29, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -508,7 +487,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "id": "5354db31-169d-4c7b-a8cc-6ddc3358b4c1", "metadata": {}, "outputs": [ @@ -518,7 +497,7 @@ "" ] }, - "execution_count": 30, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -529,7 +508,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 32, "id": "282a6ae8-e869-4ae2-bfea-8644ba693866", "metadata": {}, "outputs": [], @@ -539,7 +518,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 33, "id": "fd24c4c8-6b17-433f-ac28-a490911a3628", "metadata": {}, "outputs": [ @@ -549,7 +528,7 @@ "927372692193078999176" ] }, - "execution_count": 32, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -563,27 +542,27 @@ "id": "85ec26e2-db1f-4858-a3ab-b7955e85e572", "metadata": {}, "source": [ - "# Executors handle single nodes and lists of them on the same footing" + "# Executors handle single Tasks and lists of them on the same footing" ] }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 35, "id": "e2fed9f1-590b-4ab5-9922-a126444e6169", "metadata": {}, "outputs": [], "source": [ - "nodes = [FunctionNode(calc_fib) for _ in range(10)]" + "tasks = [FunctionTask(calc_fib) for _ in range(10)]" ] }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 49, "id": "fdfc8943-8c0b-4bc6-98f0-71a64b3fae27", "metadata": {}, "outputs": [], "source": [ - "for i, n in enumerate(nodes):\n", + "for i, n in enumerate(tasks):\n", " n.input.kwargs['n'] = 3 + i" ] }, @@ -597,7 +576,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 51, "id": "dd709cfa-775f-41c1-a015-7e0647ec3d27", "metadata": { "scrolled": true, @@ -605,32 +584,32 @@ }, "outputs": [], "source": [ - "exe = Executor(nodes)\n", + "exe = Executor(tasks)\n", "exe.run()" ] }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 52, "id": "0ac1b35a-b130-4330-bf20-a1222bdc6103", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "(,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " )" + "(,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " )" ] }, - "execution_count": 36, + "execution_count": 52, "metadata": {}, "output_type": "execute_result" } @@ -641,23 +620,23 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 53, "id": "1ef8d9d6-e5dc-4db1-9e20-7181321f07ce", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "55" + "8" ] }, - "execution_count": 37, + "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "exe.output[5].result" + "exe.output[1].result" ] }, { @@ -670,7 +649,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 54, "id": "25fe617c-ae8e-4b83-bf58-b790441a1126", "metadata": { "scrolled": true, @@ -678,13 +657,13 @@ }, "outputs": [], "source": [ - "exe = ProcessExecutor(nodes)\n", + "exe = ProcessExecutor(tasks)\n", "exe.run()" ] }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 55, "id": "19e5d3e8-6779-4c36-a636-2d8cd549e99c", "metadata": {}, "outputs": [], @@ -694,7 +673,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 56, "id": "66feb98b-3f99-4bfb-9bb5-cccaf26d009b", "metadata": {}, "outputs": [ @@ -713,7 +692,7 @@ " ReturnStatus(Code.DONE, None)]" ] }, - "execution_count": 40, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -724,26 +703,26 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 57, "id": "fbb40611-9f53-479e-854c-82c8c99a8070", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ]" + "[,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ]" ] }, - "execution_count": 41, + "execution_count": 57, "metadata": {}, "output_type": "execute_result" } @@ -754,7 +733,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 58, "id": "250f9c2d-5c71-4ddb-a94e-fd42f42cbeff", "metadata": {}, "outputs": [ @@ -764,7 +743,7 @@ "55" ] }, - "execution_count": 42, + "execution_count": 58, "metadata": {}, "output_type": "execute_result" } @@ -778,42 +757,42 @@ "id": "3fc133cd-e685-499e-b139-82fc2678652a", "metadata": {}, "source": [ - "# SeriesNode" + "# SeriesTask" ] }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 59, "id": "3dba0814-6a50-41f9-a78f-040014fdc140", "metadata": {}, "outputs": [], "source": [ - "s = SeriesNode()" + "s = SeriesTask()" ] }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 60, "id": "52aae339-ebad-4621-b2e0-c55d4fea3d1b", "metadata": {}, "outputs": [], "source": [ - "f1 = FunctionNode(calc_fib)" + "f1 = FunctionTask(calc_fib)" ] }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 61, "id": "e10f7ee9-98db-48c7-affd-465c2011f7b1", "metadata": {}, "outputs": [], "source": [ - "f2 = FunctionNode(np.sqrt)" + "f2 = FunctionTask(np.sqrt)" ] }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 62, "id": "b7e58b55-b4f5-4e2a-aef5-f4e080e4d50c", "metadata": {}, "outputs": [], @@ -824,17 +803,17 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 63, "id": "b4b2212a-64df-4284-834d-8836c9a59b70", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 47, + "execution_count": 63, "metadata": {}, "output_type": "execute_result" } @@ -845,17 +824,17 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 65, "id": "af337125-c4fe-497d-9374-b2d9301abe08", "metadata": {}, "outputs": [], "source": [ - "s.input.nodes[0].input.kwargs['n'] = 10" + "s.input.tasks[0].input.kwargs['n'] = 10" ] }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 66, "id": "810a17bb-9f5d-4c50-9665-fa2f93070d60", "metadata": {}, "outputs": [], @@ -865,7 +844,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 67, "id": "4af47287-ab42-4cb4-8e65-c6efb7982ab4", "metadata": {}, "outputs": [ @@ -875,7 +854,7 @@ "ReturnStatus(Code.DONE, None)" ] }, - "execution_count": 50, + "execution_count": 67, "metadata": {}, "output_type": "execute_result" } @@ -886,7 +865,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 68, "id": "705637d8-8da7-4429-ae6f-5401fc15cc9e", "metadata": {}, "outputs": [ @@ -896,7 +875,7 @@ "12.0" ] }, - "execution_count": 51, + "execution_count": 68, "metadata": {}, "output_type": "execute_result" } @@ -910,7 +889,7 @@ "id": "fc672a15-6943-410e-91b2-7dfac8326948", "metadata": {}, "source": [ - "# Loop Node" + "# Loop Task" ] }, { @@ -923,27 +902,27 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 69, "id": "b9807c98-6df8-450f-a8dd-1a53cb4ded35", "metadata": {}, "outputs": [], "source": [ - "l = LoopNode()" + "l = LoopTask()" ] }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 71, "id": "ac2b9aa8-c118-4a1a-bf8b-96d6853b9be6", "metadata": {}, "outputs": [], "source": [ - "l.input.node = FunctionNode(lambda: np.random.rand())" + "l.input.task = FunctionTask(lambda: np.random.rand())" ] }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 72, "id": "ef092015-5756-409a-bd1a-a31793c0b2b8", "metadata": {}, "outputs": [], @@ -953,7 +932,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 73, "id": "91a3d26f-d1fc-44a9-b06d-a9c452dfb3db", "metadata": {}, "outputs": [ @@ -961,15 +940,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.6416842803528255\n", - "0.7583838349829116\n", - "0.25412630351718535\n", - "0.978146926974964\n", - "0.6346764217817196\n", - "0.8760354316006344\n", - "0.3598792613379673\n", - "0.7532969335152777\n", - "0.3682944984993325\n" + "0.9953802852390651\n", + "0.4979426757738342\n", + "0.7496647591996423\n", + "0.9064905781297882\n", + "0.15763598102657594\n", + "0.5111053249038157\n", + "0.8777179700937587\n", + "0.6172844516713738\n", + "0.025877138756273066\n" ] } ], @@ -979,7 +958,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 74, "id": "dbc8730e-9ebc-403b-9987-0de04e1f77f3", "metadata": {}, "outputs": [ @@ -989,7 +968,7 @@ "(ReturnStatus(Code.DONE, None),)" ] }, - "execution_count": 56, + "execution_count": 74, "metadata": {}, "output_type": "execute_result" } @@ -1008,40 +987,40 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 75, "id": "6c251bfa-e8cf-4e1a-990d-451ebb53f713", "metadata": {}, "outputs": [], "source": [ - "l = LoopNode()" + "l = LoopTask()" ] }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 76, "id": "563c7fe1-b96f-463c-8903-50f054c831f6", "metadata": {}, "outputs": [], "source": [ - "l.input.node = FunctionNode(lambda: np.random.rand())" + "l.input.task = FunctionTask(lambda: np.random.rand())" ] }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 77, "id": "10130bfd-636f-4771-b30b-4648a8822f04", "metadata": {}, "outputs": [], "source": [ "l.input.control_with(\n", - " condition=lambda node, output, scratch: output.result < .05,\n", + " condition=lambda task, output, scratch: output.result < .05,\n", " restart=lambda output, input, scratch: print(output.result)\n", ")" ] }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 78, "id": "f875b6c9-8cd1-4e6b-9ec8-16b93b6e7f64", "metadata": { "scrolled": true, @@ -1052,56 +1031,21 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.9413063812991156\n", - "0.057607401981599304\n", - "0.5397672296450867\n", - "0.09250834651031281\n", - "0.07879485371081929\n", - "0.11682404864093698\n", - "0.40972529768552957\n", - "0.4949451538792201\n", - "0.4185353179411203\n", - "0.8901597039186674\n", - "0.7351951144010666\n", - "0.23869310822669132\n", - "0.31807750515467925\n", - "0.4612495341559427\n", - "0.7640290216688888\n", - "0.3869288752008373\n", - "0.23391774163562495\n", - "0.5139434388248725\n", - "0.6190472000582091\n", - "0.22725085086512875\n", - "0.8568498193403175\n", - "0.20466606514335917\n", - "0.6031013104131141\n", - "0.4322284299797473\n", - "0.6921598384356622\n", - "0.9143146262866392\n", - "0.7142585471813697\n", - "0.8067120253887571\n", - "0.2985613290985082\n", - "0.08197401100526269\n", - "0.8502914897450804\n", - "0.5441851005084206\n", - "0.4514940877891841\n", - "0.4895956404459735\n", - "0.23293653173840512\n", - "0.9152221474508541\n", - "0.35937794575487036\n", - "0.9560404151460253\n", - "0.1681320614012014\n", - "0.3887573415069143\n", - "0.936771887470757\n", - "0.8987739992014511\n", - "0.22822592639583683\n", - "0.5306744820161604\n", - "0.21711390374139883\n", - "0.7720820846158695\n", - "0.694832684749003\n", - "0.9880804928246634\n", - "0.8877319250523327\n", - "0.7389376534050393\n" + "0.3510052419168129\n", + "0.9583658097014569\n", + "0.3822386305905213\n", + "0.9446397962657214\n", + "0.5190580019824569\n", + "0.9004145100900804\n", + "0.1214505719318919\n", + "0.15782601335162105\n", + "0.2467458443276147\n", + "0.32779186092001633\n", + "0.3545881045937541\n", + "0.2742999138135499\n", + "0.1346229590051038\n", + "0.6519388412108668\n", + "0.5656985488590841\n" ] } ], @@ -1111,7 +1055,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 79, "id": "8df83822-0bbd-4157-8bb2-f6e93433eefc", "metadata": {}, "outputs": [ @@ -1121,7 +1065,7 @@ "ReturnStatus(Code.DONE, None)" ] }, - "execution_count": 61, + "execution_count": 79, "metadata": {}, "output_type": "execute_result" } @@ -1132,17 +1076,19 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 80, "id": "815ba264-9bdb-4758-ab20-92e3650bdbae", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "data": { "text/plain": [ - "0.03279942765741395" + "0.013353255375349593" ] }, - "execution_count": 62, + "execution_count": 80, "metadata": {}, "output_type": "execute_result" } @@ -1168,7 +1114,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.0" + "version": "3.10.8" }, "widgets": { "application/vnd.jupyter.widget-state+json": { diff --git a/notebooks/tinybase/TinyJob.ipynb b/notebooks/tinybase/TinyJob.ipynb index 79744ea42..e7e0a1904 100644 --- a/notebooks/tinybase/TinyJob.ipynb +++ b/notebooks/tinybase/TinyJob.ipynb @@ -12,20 +12,22 @@ "cell_type": "code", "execution_count": 1, "id": "95763abb-4480-4ced-bf73-db72dae9ffe0", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/home/ponder/science/phd/dev/pyiron_contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n", + "/home/poul/pyiron/contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n", " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "152f5a5b455b49b587903b4c8ce65857", + "model_id": "ce0f06e235fe4423b82c302dc66a5b06", "version_major": 2, "version_minor": 0 }, @@ -43,7 +45,9 @@ "cell_type": "code", "execution_count": 2, "id": "9730e2a5-9079-4863-905d-f21e47b65816", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "from pyiron_contrib.tinybase.executor import ProcessExecutor" @@ -53,27 +57,33 @@ "cell_type": "code", "execution_count": 3, "id": "09003a07-be8d-4607-b533-54214ae64056", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "from pyiron_contrib.tinybase.murn import MurnaghanNode" + "from pyiron_contrib.tinybase.murn import MurnaghanTask" ] }, { "cell_type": "code", "execution_count": 4, "id": "70c5d3c8-2d81-4539-9453-cd85010e7373", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "from pyiron_contrib.tinybase.ase import AseMDNode, AseMinimizeNode, AseStaticNode" + "from pyiron_contrib.tinybase.ase import AseMDTask, AseMinimizeTask, AseStaticTask" ] }, { "cell_type": "code", "execution_count": 5, "id": "1e5208d9-d7b8-4ca3-92cf-5d32c30c20f9", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "from pyiron_contrib.tinybase.project import ProjectAdapter, InMemoryProject" @@ -83,7 +93,9 @@ "cell_type": "code", "execution_count": 6, "id": "5606f2b7-ba23-4b0d-b44e-b41cda6e5d4d", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "from ase import Atoms" @@ -93,7 +105,9 @@ "cell_type": "code", "execution_count": 7, "id": "9a28ea42-360a-4e35-9fce-427b661a05c5", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "from ase.build import bulk" @@ -103,7 +117,9 @@ "cell_type": "code", "execution_count": 8, "id": "059021a8-6e20-4265-94f4-8fb9bdaebde3", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "from ase.calculators.morse import MorsePotential" @@ -113,7 +129,9 @@ "cell_type": "code", "execution_count": 9, "id": "f6fb49d0-dbfc-4b33-8b4d-999a2002831e", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "from pyiron_base import Project" @@ -123,7 +141,9 @@ "cell_type": "code", "execution_count": 10, "id": "f2e0123f-f0be-41ef-9733-af547b38d846", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "import logging\n", @@ -142,7 +162,9 @@ "cell_type": "code", "execution_count": 11, "id": "0f30286b-4434-4569-8f03-fadab46ebe34", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "pr = ProjectAdapter(Project('tinyjob'))" @@ -173,17 +195,21 @@ "cell_type": "code", "execution_count": 13, "id": "1e21980e-14a6-4578-b4b9-8195a74a3593", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "j.node_class = AseMDNode" + "j.task_class = AseMDTask" ] }, { "cell_type": "code", "execution_count": 14, "id": "18e6de26-308c-46ae-9672-b2db43447ea5", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "j.input.structure = bulk('Fe', a=1.2, cubic=True).repeat(2)\n", @@ -194,7 +220,9 @@ "cell_type": "code", "execution_count": 15, "id": "72848cd2-fd51-4ad8-b56e-ca686074bb26", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "j.input.steps = 100\n", @@ -207,7 +235,9 @@ "cell_type": "code", "execution_count": 16, "id": "56c0e73a-c42b-4814-a25a-e6974fea3d00", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "name": "stderr", @@ -225,12 +255,14 @@ "cell_type": "code", "execution_count": 17, "id": "49ccfe01-7b7e-4615-bf43-21c1bbffec66", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "76b5f43e298747c2b94d0904a444374c", + "model_id": "2496fdc11cfb4cf788b9a98f243aabcc", "version_major": 2, "version_minor": 0 }, @@ -258,7 +290,9 @@ "cell_type": "code", "execution_count": 18, "id": "049f056e-47ff-4c2f-9e85-612744af15a8", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "j = GenericTinyJob(pr, 'min')" @@ -268,17 +302,21 @@ "cell_type": "code", "execution_count": 19, "id": "1137a899-b00b-4ce4-92df-23a4bbcf7aa8", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "j.node_class = AseMinimizeNode" + "j.task_class = AseMinimizeTask" ] }, { "cell_type": "code", "execution_count": 20, "id": "3a8cda32-df2e-4884-8cfd-84e438c5be69", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "j.input.structure = Atoms(symbols=['Fe', 'Fe'], positions=[[0,0,0], [0,0, .75]], cell=[10,10,10])\n", @@ -290,7 +328,9 @@ "cell_type": "code", "execution_count": 21, "id": "d4b81c3f-4667-4b99-a2b3-08c7ee7e2c82", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "j.input.lbfgs(damping=.25)\n", @@ -325,7 +365,9 @@ "cell_type": "code", "execution_count": 23, "id": "7c16a615-0913-4880-9694-2c125285babc", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "data": { @@ -368,9 +410,9 @@ " 1\n", " 1\n", " 1\n", - " /home/ponder/science/phd/dev/pyiron_contrib/no...\n", + " /home/poul/pyiron/contrib/notebooks/tinybase/t...\n", " finished\n", - " AseMDNode\n", + " AseMDTask\n", " \n", " \n", " 1\n", @@ -380,41 +422,26 @@ " 2\n", " 1\n", " 2\n", - " /home/ponder/science/phd/dev/pyiron_contrib/no...\n", + " /home/poul/pyiron/contrib/notebooks/tinybase/t...\n", " finished\n", - " AseMinimizeNode\n", - " \n", - " \n", - " 2\n", - " 3\n", - " pyiron\n", - " murn\n", - " 3\n", - " 1\n", - " 3\n", - " /home/ponder/science/phd/dev/pyiron_contrib/no...\n", - " finished\n", - " MurnaghanNode\n", + " AseMinimizeTask\n", " \n", " \n", "\n", "" ], "text/plain": [ - " id username name jobtype_id project_id status_id \\\n", - "0 1 pyiron md 1 1 1 \n", - "1 2 pyiron min 2 1 2 \n", - "2 3 pyiron murn 3 1 3 \n", + " id username name jobtype_id project_id status_id \\\n", + "0 1 pyiron md 1 1 1 \n", + "1 2 pyiron min 2 1 2 \n", "\n", " location status \\\n", - "0 /home/ponder/science/phd/dev/pyiron_contrib/no... finished \n", - "1 /home/ponder/science/phd/dev/pyiron_contrib/no... finished \n", - "2 /home/ponder/science/phd/dev/pyiron_contrib/no... finished \n", + "0 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", + "1 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", "\n", " type \n", - "0 AseMDNode \n", - "1 AseMinimizeNode \n", - "2 MurnaghanNode " + "0 AseMDTask \n", + "1 AseMinimizeTask " ] }, "execution_count": 23, @@ -430,12 +457,14 @@ "cell_type": "code", "execution_count": 24, "id": "3fb09d42-f800-46ee-9919-83180863e1ee", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "9c1507f4d5c84e2d9f390ba74bdf4dc8", + "model_id": "b2e2ef42441543a0b3ad2b46cfec1386", "version_major": 2, "version_minor": 0 }, @@ -459,35 +488,6 @@ "Escape hatch to old HDF output." ] }, - { - "cell_type": "code", - "execution_count": 25, - "id": "0d1eb866-8970-45cf-a0c3-7eff3ffea5c2", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "DEBUG:pyiron_log:sql_query: {'project': 'science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/'}\n", - "DEBUG:pyiron_log:sql_query: {'project': 'science/phd/dev/pyiron_contrib/notebooks/tinybase/tinyjob/'}\n" - ] - }, - { - "data": { - "text/plain": [ - "{'groups': [], 'nodes': ['MODULE', 'NAME', 'VERSION', 'node', 'output']}" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pr._project['test/test']" - ] - }, { "cell_type": "markdown", "id": "858edf07-75ee-4a40-8cff-c26f34d555f5", @@ -498,9 +498,11 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 25, "id": "db691097-72c6-45a4-89b1-6ec16018c8b8", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "name": "stderr", @@ -509,132 +511,137 @@ "DEBUG:matplotlib.pyplot:Loaded backend module://matplotlib_inline.backend_inline version unknown.\n", "DEBUG:matplotlib.pyplot:Loaded backend module://matplotlib_inline.backend_inline version unknown.\n", "DEBUG:matplotlib.font_manager:findfont: Matching sans\\-serif:style=normal:variant=normal:weight=normal:stretch=normal:size=10.0.\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBolIta.ttf', name='STIXNonUnicode', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmr10.ttf', name='cmr10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Oblique.ttf', name='DejaVu Sans Mono', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymReg.ttf', name='STIXSizeTwoSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymReg.ttf', name='STIXSizeOneSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneral.ttf', name='STIXGeneral', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-BoldItalic.ttf', name='DejaVu Serif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymBol.ttf', name='STIXSizeFourSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymReg.ttf', name='STIXSizeFourSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansDisplay.ttf', name='DejaVu Sans Display', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Oblique.ttf', name='DejaVu Sans', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 1.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralItalic.ttf', name='STIXGeneral', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymBol.ttf', name='STIXSizeTwoSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmss10.ttf', name='cmss10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFiveSymReg.ttf', name='STIXSizeFiveSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBol.ttf', name='STIXGeneral', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-BoldOblique.ttf', name='DejaVu Sans', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 1.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Bold.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 0.33499999999999996\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Italic.ttf', name='DejaVu Serif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmsy10.ttf', name='cmsy10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmb10.ttf', name='cmb10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymBol.ttf', name='STIXSizeThreeSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-BoldOblique.ttf', name='DejaVu Sans Mono', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymBol.ttf', name='STIXSizeOneSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniIta.ttf', name='STIXNonUnicode', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBol.ttf', name='STIXNonUnicode', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBolIta.ttf', name='STIXGeneral', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmex10.ttf', name='cmex10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUni.ttf', name='STIXNonUnicode', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmmi10.ttf', name='cmmi10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerifDisplay.ttf', name='DejaVu Serif Display', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Bold.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymReg.ttf', name='STIXSizeThreeSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 0.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/cmtt10.ttf', name='cmtt10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Bold.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/Z003-MediumItalic.otf', name='Z003', style='italic', variant='normal', weight=500, stretch='normal', size='scalable')) = 11.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-Regular.ttf', name='Liberation Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-BlackIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=900, stretch='normal', size='scalable')) = 11.525\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-BoldOblique.otf', name='Nimbus Sans Narrow', style='oblique', variant='normal', weight=700, stretch='condensed', size='scalable')) = 11.535\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-SemiboldIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-BoldItalic.otf', name='Nimbus Roman', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-Bold.ttf', name='Liberation Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-BoldItalic.otf', name='P052', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-DemiItalic.otf', name='URW Bookman', style='italic', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-Bold.otf', name='Nimbus Sans Narrow', style='normal', variant='normal', weight=700, stretch='condensed', size='scalable')) = 10.535\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-Regular.ttf', name='Hack', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-Oblique.otf', name='Nimbus Sans Narrow', style='oblique', variant='normal', weight=400, stretch='condensed', size='scalable')) = 11.25\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-It.otf', name='Source Code Pro', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-Bold.otf', name='P052', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerifItalic.otf', name='FreeSerif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-ExtraLight.otf', name='Source Code Pro', style='normal', variant='normal', weight=200, stretch='normal', size='scalable')) = 10.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-Italic.ttf', name='Liberation Serif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-Italic.otf', name='Nimbus Roman', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSansOblique.otf', name='FreeSans', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-BoldItalic.otf', name='Nimbus Sans', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-BookOblique.otf', name='URW Gothic', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-Bold.otf', name='C059', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/D050000L.otf', name='D050000L', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Light.otf', name='Source Code Pro', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/StandardSymbolsPS.otf', name='Standard Symbols PS', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-Bold.ttf', name='Hack', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-Roman.otf', name='P052', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-BoldItalic.ttf', name='Hack', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Regular.otf', name='Source Code Pro', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-Bold.ttf', name='Liberation Serif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/P052-Italic.otf', name='P052', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-Italic.otf', name='Nimbus Sans', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Light.otf', name='Cantarell', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/TTF/Hack-Italic.ttf', name='Hack', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Regular.otf', name='Cantarell', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Thin.otf', name='Cantarell', style='normal', variant='normal', weight=100, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Bold.otf', name='Source Code Pro', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerifBold.otf', name='FreeSerif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-Light.otf', name='URW Bookman', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-Roman.otf', name='C059', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSansNarrow-Regular.otf', name='Nimbus Sans Narrow', style='normal', variant='normal', weight=400, stretch='condensed', size='scalable')) = 10.25\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-ExtraBold.otf', name='Cantarell', style='normal', variant='normal', weight=800, stretch='normal', size='scalable')) = 10.43\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Black.otf', name='Source Code Pro', style='normal', variant='normal', weight=900, stretch='normal', size='scalable')) = 10.525\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerif.otf', name='FreeSerif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-BoldItalic.otf', name='Nimbus Mono PS', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-BoldItalic.ttf', name='Liberation Serif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/cantarell/Cantarell-Bold.otf', name='Cantarell', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-Italic.otf', name='Nimbus Mono PS', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-DemiOblique.otf', name='URW Gothic', style='oblique', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-Regular.otf', name='Nimbus Mono PS', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSans.otf', name='FreeSans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSansBold.otf', name='FreeSans', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-Italic.ttf', name='Liberation Sans', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-BoldItalic.ttf', name='Liberation Sans', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-BoldItalic.ttf', name='Liberation Mono', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Medium.otf', name='Source Code Pro', style='normal', variant='normal', weight=500, stretch='normal', size='scalable')) = 10.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-LightItalic.otf', name='URW Bookman', style='italic', variant='normal', weight=300, stretch='normal', size='scalable')) = 11.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-LightIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=300, stretch='normal', size='scalable')) = 11.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationMono-Italic.ttf', name='Liberation Mono', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-Bold.ttf', name='Liberation Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-Italic.otf', name='C059', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-Demi.otf', name='URW Gothic', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-MediumIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=500, stretch='normal', size='scalable')) = 11.145\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-BoldIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/C059-BdIta.otf', name='C059', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMonoBoldOblique.otf', name='FreeMono', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-ExtraLightIt.otf', name='Source Code Pro', style='italic', variant='normal', weight=200, stretch='normal', size='scalable')) = 11.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-Bold.otf', name='Nimbus Roman', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSansBoldOblique.otf', name='FreeSans', style='oblique', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWGothic-Book.otf', name='URW Gothic', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMonoBold.otf', name='FreeMono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSans-Regular.ttf', name='Liberation Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/URWBookman-Demi.otf', name='URW Bookman', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/adobe-source-code-pro/SourceCodePro-Semibold.otf', name='Source Code Pro', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeSerifBoldItalic.otf', name='FreeSerif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMono.otf', name='FreeMono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-Bold.otf', name='Nimbus Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusMonoPS-Bold.otf', name='Nimbus Mono PS', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gnu-free/FreeMonoOblique.otf', name='FreeMono', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/liberation/LiberationSerif-Regular.ttf', name='Liberation Serif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusRoman-Regular.otf', name='Nimbus Roman', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/gsfonts/NimbusSans-Regular.otf', name='Nimbus Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", - "DEBUG:matplotlib.font_manager:findfont: Matching sans\\-serif:style=normal:variant=normal:weight=normal:stretch=normal:size=10.0 to DejaVu Sans ('/home/ponder/micromamba/envs/pyiron_contrib/lib/python3.11/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf') with score of 0.050000.\n" + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralItalic.ttf', name='STIXGeneral', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/cmex10.ttf', name='cmex10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Oblique.ttf', name='DejaVu Sans Mono', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/cmtt10.ttf', name='cmtt10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneral.ttf', name='STIXGeneral', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 0.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Bold.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 0.33499999999999996\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBolIta.ttf', name='STIXNonUnicode', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/cmsy10.ttf', name='cmsy10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymReg.ttf', name='STIXSizeOneSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBol.ttf', name='STIXNonUnicode', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-BoldOblique.ttf', name='DejaVu Sans', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 1.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/cmmi10.ttf', name='cmmi10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBolIta.ttf', name='STIXGeneral', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymReg.ttf', name='STIXSizeThreeSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerifDisplay.ttf', name='DejaVu Serif Display', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUni.ttf', name='STIXNonUnicode', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Italic.ttf', name='DejaVu Serif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Bold.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/cmr10.ttf', name='cmr10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymBol.ttf', name='STIXSizeOneSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Bold.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/cmss10.ttf', name='cmss10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-BoldOblique.ttf', name='DejaVu Sans Mono', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansDisplay.ttf', name='DejaVu Sans Display', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymBol.ttf', name='STIXSizeThreeSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/cmb10.ttf', name='cmb10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniIta.ttf', name='STIXNonUnicode', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-BoldItalic.ttf', name='DejaVu Serif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFiveSymReg.ttf', name='STIXSizeFiveSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymBol.ttf', name='STIXSizeTwoSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymBol.ttf', name='STIXSizeFourSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Oblique.ttf', name='DejaVu Sans', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 1.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymReg.ttf', name='STIXSizeTwoSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymReg.ttf', name='STIXSizeFourSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBol.ttf', name='STIXGeneral', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/P052-BoldItalic.otf', name='P052', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/UbuntuMono-BI.ttf', name='Ubuntu Mono', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/rsfs10.ttf', name='rsfs10', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSansNarrow-Bold.otf', name='Nimbus Sans Narrow', style='normal', variant='normal', weight=700, stretch='condensed', size='scalable')) = 10.535\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSansNarrow-Oblique.otf', name='Nimbus Sans Narrow', style='oblique', variant='normal', weight=400, stretch='condensed', size='scalable')) = 11.25\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/D050000L.otf', name='D050000L', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-L.ttf', name='Ubuntu', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/UbuntuMono-R.ttf', name='Ubuntu Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusRoman-Bold.otf', name='Nimbus Roman', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSans-Italic.otf', name='Nimbus Sans', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSansNarrow-BoldOblique.otf', name='Nimbus Sans Narrow', style='oblique', variant='normal', weight=700, stretch='condensed', size='scalable')) = 11.535\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/noto/NotoSansMono-Regular.ttf', name='Noto Sans Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSansNarrow-Regular.ttf', name='Liberation Sans Narrow', style='normal', variant='normal', weight=400, stretch='condensed', size='scalable')) = 10.25\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf', name='Liberation Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-R.ttf', name='Ubuntu', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/noto/NotoSansMono-Bold.ttf', name='Noto Sans Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/esint10.ttf', name='esint10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/cmmi10.ttf', name='cmmi10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWBookman-Demi.otf', name='URW Bookman', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/P052-Bold.otf', name='P052', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-MI.ttf', name='Ubuntu', style='italic', variant='normal', weight=500, stretch='normal', size='scalable')) = 11.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusMonoPS-BoldItalic.otf', name='Nimbus Mono PS', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSans-Italic.ttf', name='Liberation Sans', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWBookman-DemiItalic.otf', name='URW Bookman', style='italic', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSansNarrow-Bold.ttf', name='Liberation Sans Narrow', style='normal', variant='normal', weight=700, stretch='condensed', size='scalable')) = 10.535\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf', name='Droid Sans Fallback', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf', name='Noto Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/msam10.ttf', name='msam10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-BI.ttf', name='Ubuntu', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-RI.ttf', name='Ubuntu', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/StandardSymbolsPS.otf', name='Standard Symbols PS', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/UbuntuMono-B.ttf', name='Ubuntu Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusMonoPS-Italic.otf', name='Nimbus Mono PS', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSansNarrow-Italic.ttf', name='Liberation Sans Narrow', style='italic', variant='normal', weight=400, stretch='condensed', size='scalable')) = 11.25\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/cmsy10.ttf', name='cmsy10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWGothic-Book.otf', name='URW Gothic', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 0.33499999999999996\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationMono-Bold.ttf', name='Liberation Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSansNarrow-BoldItalic.ttf', name='Liberation Sans Narrow', style='italic', variant='normal', weight=700, stretch='condensed', size='scalable')) = 11.535\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusMonoPS-Regular.otf', name='Nimbus Mono PS', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusRoman-BoldItalic.otf', name='Nimbus Roman', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWGothic-BookOblique.otf', name='URW Gothic', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/msbm10.ttf', name='msbm10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/UbuntuMono-RI.ttf', name='Ubuntu Mono', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/C059-BdIta.otf', name='C059', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/cmex10.ttf', name='cmex10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/C059-Italic.otf', name='C059', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWGothic-DemiOblique.otf', name='URW Gothic', style='oblique', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusRoman-Regular.otf', name='Nimbus Roman', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationMono-Italic.ttf', name='Liberation Mono', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/Z003-MediumItalic.otf', name='Z003', style='italic', variant='normal', weight=500, stretch='normal', size='scalable')) = 11.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSerif-Italic.ttf', name='Liberation Serif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf', name='Liberation Serif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/C059-Roman.otf', name='C059', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/P052-Roman.otf', name='P052', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationMono-BoldItalic.ttf', name='Liberation Mono', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/cmr10.ttf', name='cmr10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-B.ttf', name='Ubuntu', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-M.ttf', name='Ubuntu', style='normal', variant='normal', weight=500, stretch='normal', size='scalable')) = 10.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/eufm10.ttf', name='eufm10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWGothic-Demi.otf', name='URW Gothic', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSerif-BoldItalic.ttf', name='Liberation Serif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf', name='Liberation Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/stmary10.ttf', name='stmary10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/P052-Italic.otf', name='P052', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSans-BoldItalic.otf', name='Nimbus Sans', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-Th.ttf', name='Ubuntu', style='normal', variant='normal', weight=250, stretch='normal', size='scalable')) = 10.1925\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 0.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/C059-Bold.otf', name='C059', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-C.ttf', name='Ubuntu Condensed', style='normal', variant='normal', weight=400, stretch='condensed', size='scalable')) = 10.25\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSans-BoldItalic.ttf', name='Liberation Sans', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWBookman-LightItalic.otf', name='URW Bookman', style='italic', variant='normal', weight=300, stretch='normal', size='scalable')) = 11.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-LI.ttf', name='Ubuntu', style='italic', variant='normal', weight=300, stretch='normal', size='scalable')) = 11.145\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/dsrom10.ttf', name='dsrom10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusRoman-Italic.otf', name='Nimbus Roman', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusMonoPS-Bold.otf', name='Nimbus Mono PS', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSans-Regular.otf', name='Nimbus Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSans-Bold.otf', name='Nimbus Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/wasy10.ttf', name='wasy10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf', name='Liberation Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSansNarrow-Regular.otf', name='Nimbus Sans Narrow', style='normal', variant='normal', weight=400, stretch='condensed', size='scalable')) = 10.25\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSerif-Bold.ttf', name='Liberation Serif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n", + "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWBookman-Light.otf', name='URW Bookman', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n", + "DEBUG:matplotlib.font_manager:findfont: Matching sans\\-serif:style=normal:variant=normal:weight=normal:stretch=normal:size=10.0 to DejaVu Sans ('/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf') with score of 0.050000.\n" ] }, { "data": { - "image/png": "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PaW77rrL6dJSVvS3ZbDfnaNHjzpRUtIZ9+EjyjCMfs9N0xxwDCO3bNkyffzxx9q9e7fTpaSUuro6LV++XG+88YYyMjKcLieldXV1ac6cOVq7dq0k6corr9Snn36qDRs2ED7isHXrVr388svasmWLZs6cqQMHDmjFihUqKSnR4sWLnS4vpfG7M7RxHz4mTpwoj8czYJWjoaFhQCrFyDz88MPavn27du3apbKyMqfLSSn79u1TQ0ODZs+eHTsWiUS0a9cu1dTUKBQKyePxOFhh6iguLtYll1zS79iMGTP02muvOVRRavrZz36mVatW6c4775QkXXrppTp69Kiqq6sJH6NUVFQkqXsFpLi4OHac351e477nw+fzafbs2aqtre13vLa2VnPnznWoqtRkmqaWLVum119/XTt27NC0adOcLinlfPe739Unn3yiAwcOxB5z5szRPffcowMHDhA84jBv3rwBW70PHz6sqVOnOlRRamptbVVaWv+fAo/Hw1bbMZg2bZqKior6/e6Ew2Ht3LmT350e437lQ5Kqqqr0wx/+UHPmzNG1116rjRs36tixY1qyZInTpaWUpUuXasuWLfrDH/6gnJyc2GpSIBBQZmamw9WlhpycnAE9MtnZ2SosLKR3Jk4//elPNXfuXK1du1bf//739cEHH2jjxo3auHGj06WllEWLFumpp57SlClTNHPmTH344Yd67rnn9OCDDzpdWlJraWnRF198EXt+5MgRHThwQAUFBZoyZYpWrFihtWvXqqKiQhUVFVq7dq2ysrJ09913O1h1EnF2s03i/Mu//Is5depU0+fzmd/+9rfZHjoKkgZ9bNq0yenSUhpbbUfvP/7jP8xZs2aZfr/fnD59urlx40anS0o5wWDQXL58uTllyhQzIyPDvOiii8zHHnvMDIVCTpeW1N56661B/3m4ePFi0zS7t9s+8cQTZlFRken3+83rrrvO/OSTT5wtOokYpmmaDuUeAADgQuO+5wMAACQXwgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEio/w9hHopmxnvDfAAAAABJRU5ErkJggg==", + "image/png": "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PaW77rrL6dJSVvS3ZbDfnaNHjzpRUtIZ9+EjyjCMfs9N0xxwDCO3bNkyffzxx9q9e7fTpaSUuro6LV++XG+88YYyMjKcLieldXV1ac6cOVq7dq0k6corr9Snn36qDRs2ED7isHXrVr388svasmWLZs6cqQMHDmjFihUqKSnR4sWLnS4vpfG7M7RxHz4mTpwoj8czYJWjoaFhQCrFyDz88MPavn27du3apbKyMqfLSSn79u1TQ0ODZs+eHTsWiUS0a9cu1dTUKBQKyePxOFhh6iguLtYll1zS79iMGTP02muvOVRRavrZz36mVatW6c4775QkXXrppTp69Kiqq6sJH6NUVFQkqXsFpLi4OHac351e477nw+fzafbs2aqtre13vLa2VnPnznWoqtRkmqaWLVum119/XTt27NC0adOcLinlfPe739Unn3yiAwcOxB5z5szRPffcowMHDhA84jBv3rwBW70PHz6sqVOnOlRRamptbVVaWv+fAo/Hw1bbMZg2bZqKior6/e6Ew2Ht3LmT350e437lQ5Kqqqr0wx/+UHPmzNG1116rjRs36tixY1qyZInTpaWUpUuXasuWLfrDH/6gnJyc2GpSIBBQZmamw9WlhpycnAE9MtnZ2SosLKR3Jk4//elPNXfuXK1du1bf//739cEHH2jjxo3auHGj06WllEWLFumpp57SlClTNHPmTH344Yd67rnn9OCDDzpdWlJraWnRF198EXt+5MgRHThwQAUFBZoyZYpWrFihtWvXqqKiQhUVFVq7dq2ysrJ09913O1h1EnF2s03i/Mu//Is5depU0+fzmd/+9rfZHjoKkgZ9bNq0yenSUhpbbUfvP/7jP8xZs2aZfr/fnD59urlx40anS0o5wWDQXL58uTllyhQzIyPDvOiii8zHHnvMDIVCTpeW1N56661B/3m4ePFi0zS7t9s+8cQTZlFRken3+83rrrvO/OSTT5wtOokYpmmaDuUeAADgQuO+5wMAACQXwgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEio/w9hHopmxnvDfAAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] @@ -649,14 +656,14 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 26, "id": "23ce6822-b38b-41f3-9269-109dbb152ecf", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "ac63e7c8d2b84a478b194a451a0ede25", + "model_id": "67335e733e9042d3871308302a8348e2", "version_major": 2, "version_minor": 0 }, @@ -682,7 +689,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 27, "id": "d0f62439-3492-4392-ac2a-2b2545b85527", "metadata": {}, "outputs": [], @@ -692,28 +699,28 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 28, "id": "5c9ab533-cf97-49a1-8c4b-0e5e2f9758c2", "metadata": {}, "outputs": [], "source": [ - "murn.node_class = MurnaghanNode" + "murn.task_class = MurnaghanTask" ] }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 29, "id": "253237f0-b338-470c-bc54-3c7400a757b7", "metadata": {}, "outputs": [], "source": [ - "murn.input.node = AseStaticNode()\n", - "murn.input.node.input.calculator = MorsePotential()" + "murn.input.task = AseStaticTask()\n", + "murn.input.task.input.calculator = MorsePotential()" ] }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 30, "id": "c801093b-499e-48a7-8444-77602ed88a96", "metadata": {}, "outputs": [], @@ -723,7 +730,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 31, "id": "1e30b36e-11e6-47d1-836e-cffea7b73cdd", "metadata": {}, "outputs": [], @@ -733,7 +740,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 32, "id": "9920e4b7-8395-4fb9-96c3-792b044c4e3a", "metadata": {}, "outputs": [], @@ -743,7 +750,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 33, "id": "18b5305a-8950-44af-bc2e-c9734b059713", "metadata": {}, "outputs": [ @@ -751,7 +758,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "INFO:root:Job already finished!\n" + "DEBUG:h5py._conv:Creating converter from 5 to 3\n" ] } ], @@ -761,13 +768,13 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 34, "id": "836bb2ec-4295-4a3c-b976-7a35d04aad36", "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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\n", "text/plain": [ "
" ] @@ -792,7 +799,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 35, "id": "79a2bb61-0a5e-4a3a-b195-46d027738a0e", "metadata": {}, "outputs": [], @@ -802,7 +809,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 36, "id": "b4e6e0c9-a2c6-40ab-884e-a46e16c37b04", "metadata": {}, "outputs": [ @@ -840,7 +847,7 @@ "Index: []" ] }, - "execution_count": 37, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -851,13 +858,13 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 37, "id": "cef7c46f-551f-401e-96c2-214628e23967", "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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\n", "text/plain": [ "
" ] @@ -868,9 +875,9 @@ ], "source": [ "murn = GenericTinyJob(pr, 'murn')\n", - "murn.node_class = MurnaghanNode\n", - "murn.input.node = AseStaticNode()\n", - "murn.input.node.input.calculator = MorsePotential()\n", + "murn.task_class = MurnaghanTask\n", + "murn.input.task = AseStaticTask()\n", + "murn.input.task.input.calculator = MorsePotential()\n", "murn.input.structure = bulk(\"Fe\", a=1.2)\n", "murn.input.set_strain_range(.5, 500)\n", "murn.input.child_executor = ProcessExecutor\n", @@ -880,7 +887,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 38, "id": "e8a7ee30-d7a6-46fc-bf98-1b52c981470f", "metadata": {}, "outputs": [ @@ -927,7 +934,7 @@ " 1\n", " /\n", " finished\n", - " MurnaghanNode\n", + " MurnaghanTask\n", " \n", " \n", "\n", @@ -938,10 +945,10 @@ "0 1 pyiron murn 1 1 1 / finished \n", "\n", " type \n", - "0 MurnaghanNode " + "0 MurnaghanTask " ] }, - "execution_count": 39, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" } @@ -952,24 +959,24 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 39, "id": "30871447-3e20-46ee-a58e-853d4f4cb5d9", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 40, + "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "j = GenericTinyJob(pr, 'md')\n", - "j.node_class = AseMDNode\n", + "j.task_class = AseMDTask\n", "j.input.structure = bulk('Fe', a=1.2, cubic=True).repeat(2)\n", "j.input.calculator = MorsePotential()\n", "j.input.steps = 100\n", @@ -981,7 +988,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 40, "id": "e63d43c1-341f-4ec0-b0cf-9cd5ead51926", "metadata": {}, "outputs": [ @@ -1028,7 +1035,7 @@ " 1\n", " /\n", " finished\n", - " MurnaghanNode\n", + " MurnaghanTask\n", " \n", " \n", " 1\n", @@ -1040,7 +1047,7 @@ " 2\n", " /\n", " running\n", - " AseMDNode\n", + " AseMDTask\n", " \n", " \n", "\n", @@ -1052,11 +1059,11 @@ "1 2 pyiron md 2 1 2 / running \n", "\n", " type \n", - "0 MurnaghanNode \n", - "1 AseMDNode " + "0 MurnaghanTask \n", + "1 AseMDTask " ] }, - "execution_count": 41, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } @@ -1075,17 +1082,17 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 41, "id": "80da39e2-76d1-42e6-977f-241d2683188d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 60, + "execution_count": 41, "metadata": {}, "output_type": "execute_result" } @@ -1093,7 +1100,7 @@ "source": [ "sub = pr.open_location(\"/foo\")\n", "j = GenericTinyJob(sub, 'md')\n", - "j.node_class = AseMDNode\n", + "j.task_class = AseMDTask\n", "j.input.structure = bulk('Fe', a=1.2, cubic=True).repeat(2)\n", "j.input.calculator = MorsePotential()\n", "j.input.steps = 100\n", @@ -1105,7 +1112,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 42, "id": "a567f96a-cbb3-4d2d-95d1-6dcecee7ddb8", "metadata": {}, "outputs": [ @@ -1152,7 +1159,7 @@ " 1\n", " /\n", " finished\n", - " MurnaghanNode\n", + " MurnaghanTask\n", " \n", " \n", " 1\n", @@ -1160,11 +1167,23 @@ " pyiron\n", " md\n", " 2\n", + " 1\n", + " 2\n", + " /\n", + " running\n", + " AseMDTask\n", + " \n", + " \n", + " 2\n", + " 3\n", + " pyiron\n", + " md\n", + " 2\n", " 2\n", - " 4\n", + " 3\n", " /foo\n", " running\n", - " AseMDNode\n", + " AseMDTask\n", " \n", " \n", "\n", @@ -1173,14 +1192,16 @@ "text/plain": [ " id username name jobtype_id project_id status_id location status \\\n", "0 1 pyiron murn 1 1 1 / finished \n", - "1 2 pyiron md 2 2 4 /foo running \n", + "1 2 pyiron md 2 1 2 / running \n", + "2 3 pyiron md 2 2 3 /foo running \n", "\n", " type \n", - "0 MurnaghanNode \n", - "1 AseMDNode " + "0 MurnaghanTask \n", + "1 AseMDTask \n", + "2 AseMDTask " ] }, - "execution_count": 61, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } @@ -1199,7 +1220,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 43, "id": "a6fefefb-b09c-4cee-b632-29f88bccfeee", "metadata": {}, "outputs": [], @@ -1209,17 +1230,17 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 44, "id": "3419f273-b94b-48cf-9373-7441baec2353", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "DatabaseEntry(name='murn', username='pyiron', project='/', status='finished', jobtype='MurnaghanNode')" + "DatabaseEntry(name='murn', username='pyiron', project='/', status='finished', jobtype='MurnaghanTask')" ] }, - "execution_count": 46, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } @@ -1238,7 +1259,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 45, "id": "cf75b2e8-ec15-4846-bda4-9b6661e8b5fa", "metadata": {}, "outputs": [], @@ -1248,7 +1269,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 46, "id": "1b23728b-1050-47a4-bda0-bd5967ceed2e", "metadata": {}, "outputs": [], @@ -1258,7 +1279,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 47, "id": "4cd73be9-2c6d-4501-8a6c-0afc6083a4b9", "metadata": {}, "outputs": [], @@ -1268,7 +1289,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 48, "id": "e9d9a3e2-5c86-46b4-b6d0-bec3a9f448b0", "metadata": {}, "outputs": [], @@ -1278,7 +1299,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 49, "id": "99059ff6-18bd-40b9-85fc-d76e1828f7ac", "metadata": {}, "outputs": [ @@ -1288,7 +1309,7 @@ "[]" ] }, - "execution_count": 51, + "execution_count": 49, "metadata": {}, "output_type": "execute_result" } @@ -1299,7 +1320,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 50, "id": "cb4bb9fe-13bf-4736-aa68-662b980d4f00", "metadata": {}, "outputs": [ @@ -1309,7 +1330,7 @@ "[(2, 'running')]" ] }, - "execution_count": 52, + "execution_count": 50, "metadata": {}, "output_type": "execute_result" } @@ -1320,7 +1341,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 51, "id": "4641b048-b7c7-46a2-b67c-835c08916cb0", "metadata": {}, "outputs": [ @@ -1330,7 +1351,7 @@ "[(1, 'finished'), (2, 'running'), (3, 'running')]" ] }, - "execution_count": 53, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -1341,7 +1362,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 52, "id": "94364e54-b980-48cc-995b-daf977437b1b", "metadata": {}, "outputs": [ @@ -1351,7 +1372,7 @@ "[(1, '/'), (2, '/foo')]" ] }, - "execution_count": 54, + "execution_count": 52, "metadata": {}, "output_type": "execute_result" } @@ -1362,17 +1383,19 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 53, "id": "f32c43d5-19c8-4505-bf5c-9ac32bca51d0", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "data": { "text/plain": [ - "[(1, 'MurnaghanNode'), (2, 'AseMDNode')]" + "[(1, 'MurnaghanTask'), (2, 'AseMDTask')]" ] }, - "execution_count": 55, + "execution_count": 53, "metadata": {}, "output_type": "execute_result" } @@ -1398,7 +1421,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.0" + "version": "3.10.8" }, "widgets": { "application/vnd.jupyter.widget-state+json": { diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index 91cbe684d..cc5423b9c 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -6,7 +6,7 @@ EnergyOutput, MDOutput ) -from pyiron_contrib.tinybase.node import AbstractNode, ReturnStatus +from pyiron_contrib.tinybase.task import AbstractTask, ReturnStatus import numpy as np import matplotlib.pyplot as plt @@ -26,7 +26,7 @@ class AseStaticInput(AseInput, StructureInput): pass -class AseStaticNode(AbstractNode): +class AseStaticTask(AbstractTask): def _get_input(self): return AseStaticInput() @@ -43,7 +43,7 @@ def _execute(self, output): class AseMDInput(AseInput, MDInput): pass -class AseMDNode(AbstractNode): +class AseMDTask(AbstractTask): def _get_input(self): return AseMDInput() @@ -110,7 +110,7 @@ def get_ase_optimizer(self, structure): }.get(self.algo)(structure, **self.minimizer_kwargs) -class AseMinimizeNode(AbstractNode): +class AseMinimizeTask(AbstractTask): def _get_input(self): return AseMinimizeInput() diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index efb7cdc07..1a0ccb60d 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -52,8 +52,8 @@ def step(self, state: Union[str, Code, None] = None, **kwargs): class Executor: - def __init__(self, nodes): - self._nodes = nodes + def __init__(self, tasks): + self._tasks = tasks self._run_machine = RunMachine("init") # Set up basic flow @@ -85,20 +85,20 @@ def _time_finished(self, _data): self._collect_time = self._finished_start - self._collect_start @property - def nodes(self): - return self._nodes + def tasks(self): + return self._tasks def _run_init(self): - if all(node.input.check_ready() for node in self.nodes): + if all(task.input.check_ready() for task in self.tasks): self._run_machine.step("ready") else: - logging.info("Node is not ready yet!") + logging.info("Task is not ready yet!") def _run_ready(self): self._run_machine.step("running") def _run_running(self): - status, output = zip(*[node.execute() for node in self.nodes]) + status, output = zip(*[task.execute() for task in self.tasks]) self._run_machine.step("collect", status=status, output=output) def _run_collect(self): @@ -137,8 +137,8 @@ def output(self): ) from threading import Lock -def run_node(node): - return node.execute() +def run_task(task): + return task.execute() class FuturesExecutor(Executor, abc.ABC): @@ -149,8 +149,8 @@ def __init_subclass__(cls): if cls._FuturePoolExecutor is None: raise TypeError(f"Subclass {cls} of FuturesExecutor does not define 'FuturePoolExecutor'!") - def __init__(self, nodes, max_tasks=None): - super().__init__(nodes=nodes) + def __init__(self, tasks, max_tasks=None): + super().__init__(tasks=tasks) self._max_tasks = max_tasks if max_tasks is not None else 4 self._done = 0 self._futures = {} @@ -160,39 +160,39 @@ def __init__(self, nodes, max_tasks=None): self._lock = Lock() def _process_future(self, future): - node = self._futures[future] + task = self._futures[future] status, output = future.result(timeout=0) - self._status[node] = status - self._output[node] = output + self._status[task] = status + self._output[task] = output with self._lock: self._done += 1 self._check_finish() def _check_finish(self, log=False): with self._lock: - if self._done == len(self.nodes): - status = [self._status[n] for n in sorted(self.nodes, key=lambda n: self._index[n])] - output = [self._output[n] for n in sorted(self.nodes, key=lambda n: self._index[n])] + if self._done == len(self.tasks): + status = [self._status[n] for n in sorted(self.tasks, key=lambda n: self._index[n])] + output = [self._output[n] for n in sorted(self.tasks, key=lambda n: self._index[n])] self._run_machine.step("collect", status=status, output=output, ) def _run_running(self): - if len(self._futures) < len(self.nodes): + if len(self._futures) < len(self.tasks): pool = self._FuturePoolExecutor(max_workers=self._max_tasks) try: - for i, node in enumerate(self.nodes): - future = pool.submit(run_node, node) - self._futures[future] = node - self._index[node] = i + for i, task in enumerate(self.tasks): + future = pool.submit(run_task, task) + self._futures[future] = task + self._index[task] = i future.add_done_callback(self._process_future) finally: # with statement doesn't allow me to put wait=False, so I gotta do it here with try/finally. pool.shutdown(wait=False) else: - logging.info("Some nodes are still executing!") + logging.info("Some tasks are still executing!") class BackgroundExecutor(FuturesExecutor): _FuturePoolExecutor = ThreadPoolExecutor diff --git a/pyiron_contrib/tinybase/job.py b/pyiron_contrib/tinybase/job.py index e81de1f25..3c9eb1a11 100644 --- a/pyiron_contrib/tinybase/job.py +++ b/pyiron_contrib/tinybase/job.py @@ -2,7 +2,7 @@ import logging from typing import Optional -from pyiron_contrib.tinybase.node import AbstractNode +from pyiron_contrib.tinybase.task import AbstractTask from pyiron_contrib.tinybase.storage import ( Storable, GenericStorage, @@ -23,24 +23,26 @@ class TinyJob(Storable, abc.ABC): """ - A tiny job unifies an executor, a node and its output. + A tiny job unifies an executor, a task and its output. - The job adds the node to the database and persists its input and output in a storage location. + The job adds the task to the database and persists its input and output in a storage location. - The input of the node is available from :attr:`~.input`. After the job has finished the output of the node can be + The input of the task is available from :attr:`~.input`. After the job has + finished the output of the task can be accessed from :attr:`~.output` and the data written to storage from :attr:`.~storage`. - This is an abstracat base class that works with any execution node without specifying it. To create specialized - jobs you can derive from it and overload :meth:`._get_node()` to return an instance of the node, e.g. + This is an abstracat base class that works with any execution task without specifying it. To create specialized + jobs you can derive from it and overload :meth:`._get_task()` to return an + instance of the task, e.g. - >>> from somewhere import MyNode + >>> from somewhere import MyTask >>> class MyJob(TinyJob): - ... def _get_node(self): - ... return MyNode() + ... def _get_task(self): + ... return MyTask() - The return value of :meth:`._get_node()` is persisted during the life time of the job. + The return value of :meth:`._get_task()` is persisted during the life time of the job. - You can use :class:`.GenericTinyJob` to dynamically specify which node the job should execute. + You can use :class:`.GenericTinyJob` to dynamically specify which task the job should execute. """ _executors = { @@ -54,8 +56,9 @@ def __init__(self, project: ProjectInterface, job_name: str): Create a new job. If the given `job_name` is already present in the `project` it is reloaded. No checks are performed that the - node type of the already present job and the current one match. This is also not always necessary, e.g. when - reloading a :class:`.GenericTinyJob` it will automatically read the correct node from storage. + task type of the already present job and the current one match. This is also not always necessary, e.g. when + reloading a :class:`.GenericTinyJob` it will automatically read the + correct task from storage. Args: project (:class:`.ProjectInterface`): the project the job should live in @@ -65,7 +68,7 @@ def __init__(self, project: ProjectInterface, job_name: str): project = ProjectAdapter(project) self._project = project self._name = job_name - self._node = None + self._task = None self._output = None self._storage = None self._executor = None @@ -93,27 +96,27 @@ def project(self): return self._project @abc.abstractmethod - def _get_node(self) -> AbstractNode: + def _get_task(self) -> AbstractTask: """ - Return an instance of the :class:`.AbstractNode`. + Return an instance of the :class:`.AbstractTask`. - The value return from here is saved automatically in :prop:`.node`. + The value return from here is saved automatically in :prop:`.task`. """ pass @property - def node(self): - if self._node is None: - self._node = self._get_node() - return self._node + def task(self): + if self._task is None: + self._task = self._get_task() + return self._task @property def jobtype(self): - return self.node.__class__.__name__ + return self.task.__class__.__name__ @property def input(self): - return self.node.input + return self.task.input @property def output(self): @@ -151,10 +154,10 @@ def run(self, how='foreground') -> Optional[Executor]: how (string): specifies which executor to use Returns: - :class:`.Executor`: the executor that is running the node or nothing. + :class:`.Executor`: the executor that is running the task or nothing. """ if self._id is None or self.project.database.get_item(self.id).status == "ready": - exe = self._executor = self._executors[how](nodes=[self.node]) + exe = self._executor = self._executors[how](tasks=[self.task]) self._setup_executor_callbacks() exe.run() return exe @@ -192,7 +195,7 @@ def _update_status(self, status): def _store(self, storage): # preferred solution, but not everything that can be pickled can go into HDF atm # self._executor.output[-1].store(storage, "output") - storage["node"] = pickle_dump(self.node) + storage["task"] = pickle_dump(self.task) if self._output is not None: storage["output"] = pickle_dump(self._output) @@ -208,8 +211,9 @@ def load(self, storage: GenericStorage = None): self._update_id() if storage is None: storage = self.storage - self._node = pickle_load(storage["node"]) - # this would be correct, but since we pickle output and put it into a HDF node it doesn't appear here yet! + self._task = pickle_load(storage["task"]) + # this would be correct, but since we pickle output and put it into a + # HDF task it doesn't appear here yet! # if "output" in storage.list_groups(): if "output" in storage.list_nodes(): self._output = pickle_load(storage["output"]) @@ -222,32 +226,32 @@ def _restore(cls, storage, version): # I'm not perfectly happy with this, but three thoughts led to this class: -# 1. I want to be able to set any node on a tiny job with subclassing, to make the prototyping new jobs in the notebook +# 1. I want to be able to set any task on a tiny job with subclassing, to make the prototyping new jobs in the notebook # easy -# 2. I do *not* want people to accidently change the node instance/class while the job is running +# 2. I do *not* want people to accidently change the task instance/class while the job is running class GenericTinyJob(TinyJob): """ - A generic tiny job is a tiny job that allows to set any node class after instantiating it. + A generic tiny job is a tiny job that allows to set any task class after instantiating it. - Set a node class via :attr:`.node_class`, e.g. + Set a task class via :attr:`.task_class`, e.g. - >>> from somewhere import MyNode + >>> from somewhere import MyTask >>> job = GenericTinyJob(Project(...), "myjob") - >>> job.node_class = MyNode - >>> isinstance(job.input, type(MyNode.input)) + >>> job.task_class = MyTask + >>> isinstance(job.input, type(MyTask.input)) True """ def __init__(self, project, job_name): super().__init__(project=project, job_name=job_name) - self._node_class = None + self._task_class = None @property - def node_class(self): - return self._node_class + def task_class(self): + return self._task_class - @node_class.setter - def node_class(self, cls): - self._node_class = cls + @task_class.setter + def task_class(self, cls): + self._task_class = cls - def _get_node(self): - return self.node_class() + def _get_task(self): + return self.task_class() diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index 919aff65c..493d69e80 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -2,9 +2,9 @@ AbstractOutput, StructureInput, ) -from pyiron_contrib.tinybase.node import ( - AbstractNode, - ListNode, +from pyiron_contrib.tinybase.task import ( + AbstractTask, + ListTask, ListInput, ReturnStatus ) @@ -21,29 +21,29 @@ MurnaghanInputBase = StructureInput.from_attributes( "MurnaghanInputBase", "strains", - "node" + "task" ) class MurnaghanInput(MurnaghanInputBase, ListInput): def check_ready(self): structure_ready = self.structure is not None strain_ready = len(self.strains) > 0 - node = self.node - node.input.structure = self.structure - return structure_ready and strain_ready and node.input.check_ready() + task = self.task + task.input.structure = self.structure + return structure_ready and strain_ready and task.input.check_ready() def set_strain_range(self, range, steps): self.strains = (1 + np.linspace(-range, range, steps))**(1/3) - def _create_nodes(self): + def _create_tasks(self): cell = self.structure.get_cell() - nodes = [] + tasks = [] for s in self.strains: - n = deepcopy(self.node) + n = deepcopy(self.task) n.input.structure = self.structure.copy() n.input.structure.set_cell(cell * s, scale_atoms=True) - nodes.append(n) - return nodes + tasks.append(n) + return tasks MurnaghanOutputBase = AbstractOutput.from_attributes( "MurnaghanOutputBase", @@ -69,7 +69,7 @@ def _get_structure(self, frame, wrap_atoms=True): s.set_cell(s.get_cell() * (self.equilibrium_volume/s.get_volume())**(1/3)) return s -class MurnaghanNode(ListNode): +class MurnaghanTask(ListTask): def _get_input(self): return MurnaghanInput() @@ -87,5 +87,5 @@ def _extract_output(self, output, step, node, ret, node_output): if len(output.volumes) == 0: output.volumes = np.zeros(len(self.input.strains)) if ret.is_done(): - output.energies[step] = node_output.energy_pot - output.volumes[step] = node.input.structure.get_volume() + output.energies[step] = task_output.energy_pot + output.volumes[step] = task.input.structure.get_volume() diff --git a/pyiron_contrib/tinybase/node.py b/pyiron_contrib/tinybase/task.py similarity index 74% rename from pyiron_contrib/tinybase/node.py rename to pyiron_contrib/tinybase/task.py index bb6c71bbb..8e61786e8 100644 --- a/pyiron_contrib/tinybase/node.py +++ b/pyiron_contrib/tinybase/task.py @@ -39,7 +39,7 @@ def is_done(self) -> True: """ return self.code == self.Code.DONE -class AbstractNode(Storable, abc.ABC): +class AbstractTask(Storable, abc.ABC): """ Basic unit of calculations. @@ -61,7 +61,7 @@ def _get_input(self) -> AbstractInput: """ Return an instance of the input class. - This is called once per life time of the node object on first access to :attr:`.input` and then saved. + This is called once per life time of the task object on first access to :attr:`.input` and then saved. """ pass @@ -109,7 +109,7 @@ def execute(self) -> ReturnStatus: return ret, output def run(self, how='foreground'): - exe = self._executors[how](nodes=[self]) + exe = self._executors[how](tasks=[self]) exe.run() return exe @@ -122,15 +122,15 @@ def _store(self, storage): @classmethod def _restore(cls, storage, version): - node = cls() - node._input = pickle_load(storage["input"]) - return node + task = cls() + task._input = pickle_load(storage["input"]) + return task FunctionInput = AbstractInput.from_attributes("FunctionInput", args=list, kwargs=dict) FunctionOutput = AbstractOutput.from_attributes("FunctionOutput", "result") -class FunctionNode(AbstractNode): +class FunctionTask(AbstractTask): """ - A node that wraps a generic function. + A task that wraps a generic function. The given function is called with :attr:`.FunctionInput.args` and :attr:`.FunctionInput.kwargs` as `*args` and `**kwargs` respectively. The return value is set to :attr:`.FunctionOutput.result`. @@ -158,21 +158,21 @@ class ListInput(MasterInput, abc.ABC): """ The input of :class:`.ListNode`. - To use it overload :meth:`._create_nodes()` here and subclass :class:`.ListNode` as well. + To use it overload :meth:`._create_tasks()` here and subclass :class:`.ListNode` as well. """ @abc.abstractmethod - def _create_nodes(self): + def _create_tasks(self): """ - Return a list of nodes to execute. + Return a list of tasks to execute. This is called once by :class:`.ListNode.execute`. """ pass -class ListNode(AbstractNode, abc.ABC): +class ListTask(AbstractTask, abc.ABC): """ - A node that executes other nodes in parallel. + A task that executes other tasks in parallel. To use it overload :meth:`._extract_output` here and subclass :class:`.ListInput` as well. """ @@ -181,88 +181,88 @@ def __init__(self): super().__init__() @abc.abstractmethod - def _extract_output(self, output, step, node, ret, node_output): + def _extract_output(self, output, step, task, ret, task_output): """ - Extract the output of each node. + Extract the output of each task. Args: - output (:class:`.AbstractOutput`): output of this node to populate - step (int): index of the node to extract the output from, corresponds to the index of the node in the list - returned by :meth:`.ListInput._create_nodes()`. - node (:class:`.AbstractNode`): node to extract the output from, you can use this to extract parts of the input as well - ret (:class:`.ReturnStatus`): the return status of the execution of the node - node_output (:class:`.AbstractOutput`): the output of the node to extract + output (:class:`.AbstractOutput`): output of this task to populate + step (int): index of the task to extract the output from, + corresponds to the index of the task in the list returned by :meth:`.ListInput._create_tasks()`. + task (:class:`.AbstractTask`): task to extract the output from, you can use this to extract parts of the input as well + ret (:class:`.ReturnStatus`): the return status of the execution of the task + task_output (:class:`.AbstractOutput`): the output of the task to extract """ pass def _execute(self, output): - nodes = self.input._create_nodes() - exe = self.input.child_executor(nodes) + tasks = self.input._create_tasks() + exe = self.input.child_executor(tasks) exe.run() exe.wait() - for i, (node, ret, node_output) in enumerate(zip(nodes, exe.status, exe.output)): - self._extract_output(output, i, node, ret, node_output) + for i, (task, ret, task_output) in enumerate(zip(tasks, exe.status, exe.output)): + self._extract_output(output, i, task, ret, task_output) SeriesInputBase = AbstractInput.from_attributes( "SeriesInputBase", - nodes=list, + tasks=list, connections=list ) class SeriesInput(SeriesInputBase, MasterInput): """ - Keeps a list of nodes and their connection functions to run sequentially. + Keeps a list of tasks and their connection functions to run sequentially. - The number of added nodes must be equal to the number of connections plus one. It's recommended to set up this + The number of added tasks must be equal to the number of connections plus one. It's recommended to set up this input with :meth:`.first()` and :meth:`.then()` which can be composed in a builder pattern. The connection - functions take as arguments the output of the last node and the input of the next node. You may call + functions take as arguments the output of the last task and the input of the next task. You may call :meth:`.then()` any number of times. - >>> node = SeriesNode() + >>> task = SeriesNode() >>> def transfer(input, output): ... input.my_param = output.my_result - >>> node.input.first(MyNode()).then(MyNode(), transfer) + >>> task.input.first(MyNode()).then(MyNode(), transfer) """ def check_ready(self): - return len(self.nodes) == len(connections) + 1 + return len(self.tasks) == len(connections) + 1 - def first(self, node): + def first(self, task): """ - Set initial node. + Set initial task. Resets whole input Args: - node (AbstractNode): the first node to execute + task (AbstractTask): the first task to execute Returns: self: the input object """ - self.nodes = [node] + self.tasks = [task] self.connections = [] return self - def then(self, next_node, connection): + def then(self, next_task, connection): """ - Add a new node and how to connect it to the previous node. + Add a new task and how to connect it to the previous task. Args: - next_node (:class:`~.AbstractNode`): next node to execute - connection (function): takes the input of next_node and the output of the previous node - + next_task (:class:`~.AbstractTask`): next task to execute + connection (function): takes the input of next_task and the output + of the previous task Returns: self: the input object """ - self.nodes.append(next_node) + self.tasks.append(next_task) self.connections.append(connection) return self -class SeriesNode(AbstractNode): +class SeriesTask(AbstractTask): """ - Executes a series of nodes sequentially. + Executes a series of tasks sequentially. - Its input specifies the nodes to execute and functions (:attr:`.SeriesInput.connections`) to move input from one + Its input specifies the tasks to execute and functions (:attr:`.SeriesInput.connections`) to move input from one output to the next input in the series. """ @@ -270,21 +270,21 @@ def _get_input(self): return SeriesInput() def _get_output(self): - return self.input.nodes[-1]._get_output() + return self.input.tasks[-1]._get_output() def _execute(self, output): Exe = self.input.child_executor - exe = Exe(self.input.nodes[:1]) + exe = Exe(self.input.tasks[:1]) exe.run() exe.wait() ret = exe.status[0] if not ret.is_done(): return ReturnStatus("aborted", ret) - for node, connection in zip(self.input.nodes[1:], self.input.connections): - connection(node.input, exe.output[0]) - exe = Exe([node]) + for task, connection in zip(self.input.tasks[1:], self.input.connections): + connection(task.input, exe.output[0]) + exe = Exe([task]) exe.run() exe.wait() ret = exe.status[0] @@ -307,18 +307,18 @@ def __init__(self, condition, restart): scratch = StorageAttribute().default(dict) - def condition(self, node: AbstractNode, output: AbstractNode): + def condition(self, task: AbstractTask, output: AbstractTask): """ Whether to terminate the loop or not. Args: - node (AbstractNode): the loop body + task (AbstractTask): the loop body output (AbstractOutput): output of the loop body Args: bool: True to terminate the loop; False to keep it running """ - return self._condition(node, output, self.scratch) + return self._condition(task, output, self.scratch) def restart(self, output: AbstractOutput, input: AbstractInput): """ @@ -344,10 +344,10 @@ def _count_steps(self, output, input, scratch={}): class LoopInput(LoopInputBase, MasterInput): """ - Input for :class:`~.LoopNode`. + Input for :class:`~.LoopTask`. Attributes: - node (:class:`~.AbstractNode`): the loop body + task (:class:`~.AbstractTask`): the loop body control (:class:`.LoopControl`): encapsulates control flow of the loop """ @@ -364,7 +364,7 @@ def repeat(self, steps: int, restart: Optional[Callable[[AbstractOutput, Abstrac def control_with( self, - condition: Callable[[AbstractNode, AbstractOutput, dict], bool], + condition: Callable[[AbstractTask, AbstractOutput, dict], bool], restart: Callable[[AbstractOutput, AbstractInput, dict], None] ): """ @@ -376,29 +376,29 @@ def control_with( """ self.control = LoopControl(condition, restart) -class LoopNode(AbstractNode): +class LoopTask(AbstractTask): """ - Generic node to loop over a given input node. + Generic task to loop over a given input task. """ def _get_input(self): return LoopInput() def _get_output(self): - return self.input.node._get_output() + return self.input.task._get_output() def _execute(self, output): - node = deepcopy(self.input.node) + task = deepcopy(self.input.task) control = deepcopy(self.input.control) scratch = {} while True: - exe = self.input.child_executor([node]) + exe = self.input.child_executor([task]) exe.run() ret = exe.status[-1] out = exe.output[-1] if not ret.is_done(): return ReturnStatus("aborted", ret) - if control.condition(node, out): + if control.condition(task, out): break - control.restart(out, node.input) + control.restart(out, task.input) output.transfer(out) From 3b9d6d73a6c8c7c3e3a19050d8c38c47c41d52f3 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Wed, 3 May 2023 17:10:10 +0200 Subject: [PATCH 036/756] Add TaskGenerators and remove Masters Extending on the theme of independent tasks, this commit introduces task generators to replace the "meta" nodes. Task generators are python generator functions that yield lists of independent tasks are fed back their results. This design allows to run this generator on a central machine and distribute the tasks to any number of configured remote compute resources with very loose coupling. It's not clear to me yet, that python generator functions are sufficient beyond the PoC stage, but it works for now. As part of this development the previous executors are split into execution contexts, concerned with just list of parallel tasks and executors themselves which manage some compute resource (i.e. a process pool) and let the execution contexts run within. We've also lost the ability to run jobs and tasks completely in the background, because I haven't figured it out completely yet. --- notebooks/tinybase/ASE.ipynb | 1778 +++++++++++++++++++++++--- notebooks/tinybase/Basic.ipynb | 541 ++++---- notebooks/tinybase/TinyJob.ipynb | 34 +- pyiron_contrib/tinybase/container.py | 18 +- pyiron_contrib/tinybase/executor.py | 80 +- pyiron_contrib/tinybase/job.py | 8 +- pyiron_contrib/tinybase/murn.py | 14 +- pyiron_contrib/tinybase/project.py | 3 + pyiron_contrib/tinybase/task.py | 143 ++- 9 files changed, 1991 insertions(+), 628 deletions(-) diff --git a/notebooks/tinybase/ASE.ipynb b/notebooks/tinybase/ASE.ipynb index ff87da6ec..10ca9a53e 100644 --- a/notebooks/tinybase/ASE.ipynb +++ b/notebooks/tinybase/ASE.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "f9ecb713-99e6-4f34-b4c2-fe9f3d96c81e", "metadata": { "tags": [] @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "31dc7658-dcf0-41a4-9c62-ec92b02e47ea", "metadata": { "tags": [] @@ -34,7 +34,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "e7cc88a5-8492-482e-8c6b-c17ff967fff5", "metadata": { "tags": [] @@ -46,19 +46,40 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "b3108213-1d94-4354-9537-84982e45683d", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/poul/pyiron/contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n", + " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "9ed5730af8334887a97ceca1e75f28c9", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from pyiron_contrib.tinybase.ase import AseStaticTask, AseMDTask, AseMinimizeTask" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "0029125a-55e6-4181-a59b-09f606a1b4dd", "metadata": { "tags": [] @@ -70,7 +91,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "c7c74920-c6b3-4577-a60f-951a0d3276ec", "metadata": { "tags": [] @@ -82,7 +103,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "c6630920-6ab7-4273-883e-999020b1fe5a", "metadata": { "tags": [] @@ -103,7 +124,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 39, "id": "a6af72cb-989b-46c3-a2b5-4d2b9c5fd1eb", "metadata": { "tags": [] @@ -115,7 +136,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 40, "id": "5b2a9d62-3f74-4acf-acb6-e72dcd984704", "metadata": { "tags": [] @@ -127,7 +148,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 41, "id": "1af70322-897e-487d-ba18-239ba5bfb7ba", "metadata": { "tags": [] @@ -139,41 +160,51 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 42, "id": "273902ef-03f3-4f68-8668-4e6c6055a302", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None),\n", + " )" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "ret, output = a.execute(); ret, output" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "497de0b9-5e11-4d6c-8c19-664d0e759ac4", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "-0.00013307075712109978" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "output.energy_pot" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "57eced2f-6649-4269-b3fa-6061d518f9ee", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "exe = a.run()\n", - "exe.output[0].energy_pot" - ] - }, { "cell_type": "markdown", "id": "8a102a81-df04-4527-8739-7fe542f0c1fc", @@ -184,7 +215,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 43, "id": "02cfe01b-0b24-4723-a79b-d41ffb146bf9", "metadata": { "tags": [] @@ -196,7 +227,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 44, "id": "466d1f9a-b707-4c05-a8af-5414d76bd8eb", "metadata": { "tags": [] @@ -209,7 +240,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 45, "id": "dfdfc027-1608-43ad-9d15-0c649986eb73", "metadata": { "tags": [] @@ -224,20 +255,20 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "db5c7cfe-b075-483e-8b7e-a58cebf1a782", + "execution_count": 50, + "id": "a91f71b6-25f9-4977-b5b7-563a34f30016", "metadata": { "tags": [] }, "outputs": [], "source": [ - "%%time\n", - "exe = md.run(how='process')" + "exe = ProcessExecutor(max_processes=4).submit([md])\n", + "exe.run()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 51, "id": "80155255-4dcf-48cb-9825-015da13d6ac0", "metadata": { "tags": [] @@ -249,72 +280,143 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 52, "id": "6f7aff4e-9e89-459b-843f-46a4d4139bcf", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'status': [ReturnStatus(Code.DONE, None)],\n", + " 'output': []}" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "exe._run_machine._data" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 53, "id": "62ce8439-bf95-4818-b35c-b4e2ef649bd2", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "exe._run_machine.state" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 54, "id": "5bcd1b68-6a48-4a08-92d4-143419071618", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "10.619871525006602" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "exe._run_time" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 55, "id": "d21371e0-fa36-44bd-b7bf-a0092177ba17", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.00010890099656535313" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "exe._collect_time" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 56, "id": "9e06cd6d-e0f7-40dd-93f2-777f86ffe2eb", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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HTp0QN26dbFixQqMGDFCxchubc888wwOHTqEbdu23fQ7T3ptuf2IRY0aNaDT6W7K7JKTk2/KAMm1BAYGolWrVjh9+rTaoVA5rKt3+DpzT2FhYahbty5fayp69tln8ffff2Pjxo2oU6eO7eee+Npy+8TCx8cH0dHRWLt2bbGfr127Ft26dVMpKqoIk8mE48ePIywsTO1QqBxRUVEIDQ0t9jrLzc3F5s2b+TpzA1evXkVcXBxfayqQJAnPPPMMFi1ahA0bNiAqKqrY7z3xteURUyGTJ0/G6NGj0aFDB3Tt2hWzZs1CbGwsJkyYoHZoVMRLL72EoUOHIjIyEsnJyXj//feRlpaGMWPGqB0aAcjIyMCZM2ds358/fx4HDhxAtWrVEBkZiUmTJuGDDz5Ao0aN0KhRI3zwwQcICAjAQw89pGLUt6aynqtq1arh7bffxj333IOwsDBcuHABU6dORY0aNXD33XerGPWtaeLEiViwYAGWLl2KoKAg28iEwWCAv78/NBqN5722VF2TIqOvv/5aqlu3ruTj4yO1b9/etpSHXMf9998vhYWFSd7e3lJ4eLg0YsQI6ejRo2qHRRYbN26UANx0GTNmjCRJYlncW2+9JYWGhkq+vr5Sr169pMOHD6sb9C2qrOcqKytLGjhwoFSzZk3J29tbioyMlMaMGSPFxsaqHfYtqaTnCYA0d+5c22087bXFbdOJiIhINm5fY0FERESug4kFERERyYaJBREREcmGiQURERHJhokFERERyYaJBREREcmGiQURERHJhokFERERyYaJBREREcmGiQURERHJhokFERERyYaJBREREcnm//UUcyxKxOPjAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "exe.output[0].plot_energies()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 58, "id": "bb70a653-6231-4f4e-9bbe-279811acc895", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e166420cb3a24a529f16a00f05ffa30d", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "NGLWidget(max_frame=21)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "exe.output[0].animate_structures()" ] @@ -329,7 +431,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 59, "id": "f816e2af-0455-4e05-9c39-2e9f615d8f34", "metadata": { "tags": [] @@ -341,7 +443,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 60, "id": "22314020-8f48-487b-a765-229a77d79a2f", "metadata": { "tags": [] @@ -353,7 +455,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 61, "id": "ff411a05-82e1-4581-b06e-ab2fd7e0be3b", "metadata": { "tags": [] @@ -365,7 +467,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 62, "id": "5574f0d5-d800-472a-9418-8c6ccc1e555b", "metadata": { "tags": [] @@ -378,19 +480,34 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 63, "id": "9e02d6dd-0fa6-4dd6-a7ab-3e648958eb20", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "adddb5640a9d43e396a2f89877656552", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "NGLWidget()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "ase_to_pyiron(mi.input.structure).plot3d()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 64, "id": "663e4435-1cd0-4ce2-9593-85453f4c846a", "metadata": { "tags": [] @@ -404,7 +521,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 65, "id": "37440e5a-75ff-4601-813a-f5c8df9413ad", "metadata": { "tags": [] @@ -416,110 +533,228 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "a448ec7f-53bc-4d72-a8a7-f9392de9f3d5", + "execution_count": 66, + "id": "00fbac35-11cd-468b-990e-0b97c3f4dec1", "metadata": { - "scrolled": true, "tags": [] }, "outputs": [], "source": [ - "%%time\n", - "exe = mi.run(how='foreground')" + "exe = Executor().submit([mi])" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 67, + "id": "8506cba9-b045-40f9-8514-2982db2470ca", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Step Time Energy fmax\n", + "GPMin: 0 16:26:11 11.122159 187.2462\n", + "GPMin: 1 16:26:11 -0.278268 1.5338\n", + "GPMin: 2 16:26:11 -0.996055 0.8010\n", + "GPMin: 3 16:26:11 -0.000000 0.0000\n" + ] + } + ], + "source": [ + "exe.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 68, "id": "5977dd10-c4cf-40c9-944e-5aa52cfa263d", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None),)" + ] + }, + "execution_count": 68, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "exe.status" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 69, "id": "dd164778-634c-4785-903a-08a5243999ce", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "2.136147842601888e-07" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "abs(exe.output[0].forces[-1]).max()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 70, "id": "515ea06d-9026-4d9e-9df0-b9c249f0758a", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "exe._run_machine.state" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 71, "id": "52b7231f-8978-46ec-b698-ea8724a6fea3", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.07873644600476837" + ] + }, + "execution_count": 71, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "exe._run_time" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 72, "id": "c845430c-119d-4566-88e1-8465e378fde1", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "1.3546996342483908e-05" + ] + }, + "execution_count": 72, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "exe._collect_time" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 73, "id": "35291d7f-33a9-41ab-9b80-f052c5eb2e55", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "exe.output[0].plot_energies()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 74, "id": "1d5b5203-d07f-485b-9553-9150f4a674e7", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[11.122158782511535,\n", + " -0.2782678462106827,\n", + " -0.9960554302957411,\n", + " -3.560246436024868e-08,\n", + " -3.560246436024868e-08]" + ] + }, + "execution_count": 74, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "exe.output[0].pot_energies" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 75, "id": "d2cc3b3a-5daa-49bb-9d6d-2994ebc74273", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "834b47ef1b224ffc92b0f0392a0a39ac", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "NGLWidget(max_frame=4)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "exe.output[0].animate_structures()" ] @@ -544,7 +779,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "4acdeafc-90b5-4b3f-9559-c74b9fa221ab", "metadata": { "tags": [] @@ -556,7 +791,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "f8cf3136-9b7c-4f1e-b630-962795527946", "metadata": { "tags": [] @@ -570,19 +805,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "fef21aa4-d9f1-4d4a-8761-af1bc3121e5b", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "m.input.task.input" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "41a68b17-c7c4-4a5f-8f04-11bee18fe55a", "metadata": { "tags": [] @@ -594,89 +840,155 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "fd107556-99b6-4042-9209-9412b4bbff94", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([0.79370053, 0.8043555 , 0.81473542, 0.82485739, 0.83473686,\n", + " 0.84438786, 0.85382314, 0.86305437, 0.87209225, 0.88094658,\n", + " 0.88962642, 0.89814011, 0.90649538, 0.9146994 , 0.92275884,\n", + " 0.93067991, 0.93846839, 0.94612969, 0.95366889, 0.96109074,\n", + " 0.96839969, 0.97559996, 0.98269548, 0.98968999, 0.996587 ,\n", + " 1.00338986, 1.01010169, 1.0167255 , 1.02326411, 1.0297202 ,\n", + " 1.03609634, 1.04239496, 1.04861836, 1.05476875, 1.06084824,\n", + " 1.06685884, 1.07280247, 1.07868096, 1.08449606, 1.09024946,\n", + " 1.09594278, 1.10157754, 1.10715524, 1.11267731, 1.1181451 ,\n", + " 1.12355993, 1.12892306, 1.13423572, 1.13949907, 1.14471424])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "m.input.strains" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "0715614a-7284-4388-ac6b-c97bfedf7184", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "m.input.check_ready()" ] }, { "cell_type": "code", - "execution_count": null, - "id": "c2aa3093-1ea8-4099-bc14-be0c06e9d34b", + "execution_count": 23, + "id": "9820e859-f1c2-4b25-b121-feea7843a9c5", "metadata": { - "scrolled": true, "tags": [] }, "outputs": [], "source": [ - "exe = m.run(how='foreground')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "be97853a-f182-4b4f-af74-7fd5a4fbd850", - "metadata": {}, - "outputs": [], - "source": [ - "exe.status" + "_, output = m.execute()" ] }, { "cell_type": "code", - "execution_count": null, - "id": "47f916ef-b140-49c5-adf1-93dca91b4540", - "metadata": {}, - "outputs": [], + "execution_count": 25, + "id": "98c712a2-ee74-4c28-bce0-cb4824a930e6", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiIAAAGdCAYAAAAvwBgXAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy88F64QAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA5kElEQVR4nO3dd3ic1Z328XvURr13aaziJsvGTS64Y2MbA4FAQkscWkwSL5As4d0kkOxLyWbX+24I2TQIScCQBIzpJqHZFHfc5Sp3SVazeu/tef+QNbZcJVszz0jz/VyXrqBnZjQ/5bGl2+f8zjkWwzAMAQAAmMDD7AIAAID7IogAAADTEEQAAIBpCCIAAMA0BBEAAGAagggAADANQQQAAJiGIAIAAEzjZXYBF9PZ2amioiIFBQXJYrGYXQ4AAOgFwzBUV1en+Ph4eXhcfMzDpYNIUVGRbDab2WUAAIDLkJ+fr8TExIs+x6WDSFBQkKSubyQ4ONjkagAAQG/U1tbKZrPZf49fjEsHke7pmODgYIIIAAADTG/aKmhWBQAApiGIAAAA0xBEAACAaQgiAADANAQRAABgGoIIAAAwDUEEAACYhiACAABMQxABAACmIYgAAADTEEQAAIBpCCIAAMA0Ln3onaMcKanT2zsLFBbgo6VzhppdDgAAbsstR0SKqpv0wvpsvZdZaHYpAAC4NbcMIolh/pKkwqomGYZhcjUAALgvNw0ifpKkupZ21Ta1m1wNAADuyy2DiK+3pyIDfSRJ+VWNJlcDAID7cssgIkkJp6ZnCqqaTK4EAAD35bZBpHt6prCaIAIAgFncPogUMDUDAIBp3DiIMDUDAIDZ3DiIdI+IEEQAADCL+waRUKZmAAAwm9sGkYTuvUSa21XT1GZyNQAAuCe3DSL+Pl6KCOjaS6SQ6RkAAEzhtkFEYuUMAABmc/MgwsoZAADM5OZBhJUzAACYya2DSAJTMwAAmMqtgwgjIgAAmMvNg0hXjwjnzQAAYA63DiIJpzY1q2lqU20ze4kAAOBsbh1EAqxeCmcvEQAATOO0ILJs2TJZLBY98sgjznrLXkkIpU8EAACzOCWIbN++XX/60580duxYZ7xdn7CpGQAA5nF4EKmvr9fixYv15z//WWFhYY5+uz5j5QwAAOZxeBB56KGHdOONN2r+/PmXfG5LS4tqa2t7fDiafeUMQQQAAKfzcuQXf/3117Vr1y5t3769V89ftmyZnn76aUeWdA77iEg1UzMAADibw0ZE8vPz9a//+q/6+9//Ll9f31695vHHH1dNTY39Iz8/31Hl2XHeDAAA5nHYiMjOnTtVWlqqjIwM+7WOjg6tX79ev//979XS0iJPT88er7FarbJarY4q6by6t3mvbmxTXXObgny9nfr+AAC4M4cFkWuvvVb79u3rce3+++9XWlqafvKTn5wTQswSaPVSqL+3qhvbVFjdpLRYgggAAM7isCASFBSkMWPG9LgWEBCgiIiIc66bLTHMT9WNbSqobFJabLDZ5QAA4DbcemfVbomhnDkDAIAZHLpq5mxr16515tv1GpuaAQBgDkZExKZmAACYhSAiKYElvAAAmIIgIqZmAAAwC0FEp/cSqWpsU0NLu8nVAADgPggikoJ9vRXi17V/CCtnAABwHoLIKUzPAADgfASRU1g5AwCA8xFETkkIZeUMAADORhA5hakZAACcjyBySncQKWREBAAApyGInJLIpmYAADgdQeSU7r1EKhpa1djKXiIAADgDQeSUED9vBft2nQHI9AwAAM5BEDkDZ84AAOBcBJEzsHIGAADnIoicwR5E2OYdAACnIIicgZUzAAA4F0HkDGzzDgCAcxFEznB6UzN6RAAAcAaCyBkST503U17fqqbWDpOrAQBg8COInCHYz0tB1lN7iVQzKgIAgKMRRM5gsVjsO6zSJwIAgOMRRM7CyhkAAJyHIHIWVs4AAOA8BJGzsLsqAADOQxA5CyMiAAA4D0HkLPSIAADgPASRs3SPiJTXt6i5jb1EAABwJILIWUL8vBVo30uEUREAAByJIHIWi8VCnwgAAE5CEDkPVs4AAOAcBJHzSAhlRAQAAGcgiJwHK2cAAHAOgsh5dE/NFDI1AwCAQxFEzoMREQAAnIMgch7dIyKldewlAgCAIxFEziPU31sBPp6SpCL2EgEAwGEIIudhsViUwF4iAAA4HEHkAugTAQDA8QgiF2BfOVPNyhkAAByFIHIBbPMOAIDjEUQugKkZAAAcjyByAae3eWdqBgAARyGIXED31ExJLXuJAADgKASRCwgP8FGIn7ckKae8weRqAAAYnAgiF2CxWDQ0KkCSlF1GEAEAwBEIIheRGhUoSTpeVm9yJQAADE4EkYsYShABAMChHBpEli1bpsmTJysoKEjR0dG65ZZbdPjwYUe+Zb/qnpohiAAA4BgODSLr1q3TQw89pC1btmjNmjVqb2/XwoUL1dAwMHouuqdmsssaZBiGydUAADD4eDnyi3/88cc9Pl++fLmio6O1c+dOzZ4925Fv3S+SIvzl5WFRY2uHimubFRfiZ3ZJAAAMKk7tEampqZEkhYeHn/fxlpYW1dbW9vgwk7enh4ZEdO2werx0YIziAAAwkDgtiBiGoUcffVQzZ87UmDFjzvucZcuWKSQkxP5hs9mcVd4FpUaemp4pp08EAID+5rQg8vDDD2vv3r1asWLFBZ/z+OOPq6amxv6Rn5/vrPIuaGj0qYbVUoIIAAD9zaE9It2+//3v6/3339f69euVmJh4wedZrVZZrVZnlNRrp5fwMjUDAEB/c2gQMQxD3//+9/Xuu+9q7dq1SklJceTbOcTp3VUZEQEAoL85NIg89NBDeu2117Rq1SoFBQWpuLhYkhQSEiI/v4GxAqW7R6SoplkNLe0KsDplEAkAALfg0B6R559/XjU1NbrmmmsUFxdn/1i5cqUj37ZfhQX4KCLARxKH3wEA0N8cPjUzGAyNClRFQ6WOl9VrTEKI2eUAADBocNZML6Tat3pnRAQAgP5EEOkFDr8DAMAxCCK9wF4iAAA4BkGkF7pXzuSUN6izc3D0vQAA4AoIIr2QGOYnH08PtbR3qrC6yexyAAAYNAgiveDl6aHkyFOH39EnAgBAvyGI9JL98DtWzgAA0G8IIr1kb1hlRAQAgH5DEOkllvACAND/CCK9lMopvAAA9DuCSC91765aVtei2uY2k6sBAGBwIIj0UrCvt6KDrJJoWAUAoL8QRPrAfuYMO6wCANAvCCJ90N2wml1OEAEAoD8QRPrAvnKmlKkZAAD6A0GkD4ZGs4QXAID+RBDpg9TIrh6RExWNau/oNLkaAAAGPoJIHySE+snq5aHWjk4VVHH4HQAAV4og0gceHpYzNjZjegYAgCtFEOmj7iW87CUCAMCVI4j0EWfOAADQfwgifTQ0ilN4AQDoLwSRPhrK4XcAAPQbgkgfpZxawlvZ0KqqhlaTqwEAYGAjiPRRgNVL8SG+ktjqHQCAK0UQuQypbPUOAEC/IIhcBnvDKiMiAABcEYLIZbCfOcOICAAAV4QgchlSI7uCSDZLeAEAuCIEkcswNLpraiavslFtHH4HAMBlI4hchthgX/n7eKq909CJikazywEAYMAiiFwGi8ViP3OGHVYBALh8BJHLxJkzAABcOYLIZeoOIpzCCwDA5SOIXCZGRAAAuHIEkctk7xEprZdhGCZXAwDAwEQQuUwpkQGyWKTa5nZVcPgdAACXhSBymXy9PZUY5iepa1QEAAD0HUHkCnTvsHqchlUAAC4LQeQKnF45w4gIAACXgyByBbq3emflDAAAl4cgcgWYmgEA4MoQRK5A94hIflWjmts6TK4GAICBhyByBaICrQry9ZJhSLkVjIoAANBXBJErYLFYNDImSJJ08GStydUAADDwEESu0JiEEEnS/kKCCAAAfUUQuULp8cGSpANFNSZXAgDAwEMQuUJj4rtGRA4U1qqzkzNnAADoC6cEkeeee04pKSny9fVVRkaGNmzY4Iy3dYrhMYHy8fRQXUu78qsazS4HAIABxeFBZOXKlXrkkUf0s5/9TJmZmZo1a5auv/565eXlOfqtncLb00MjY7saVg8U0ScCAEBfODyIPPvss1qyZIkeeOABjRo1Sv/7v/8rm82m559/3tFv7TRjErr6RPYX0icCAEBfODSItLa2aufOnVq4cGGP6wsXLtTmzZvPeX5LS4tqa2t7fAwE6d19IoyIAADQJw4NIuXl5ero6FBMTEyP6zExMSouLj7n+cuWLVNISIj9w2azObK8fjPmjJUzhkHDKgAAveWUZlWLxdLjc8MwzrkmSY8//rhqamrsH/n5+c4o74qlxQbLwyKV17eqtK7F7HIAABgwvBz5xSMjI+Xp6XnO6Edpaek5oySSZLVaZbVaHVmSQ/j5eGpYdKCOlNRrf2GNYoJ9zS4JAIABwaEjIj4+PsrIyNCaNWt6XF+zZo2mT5/uyLd2uu79RNhhFQCA3nP41Myjjz6qv/zlL3rppZd08OBB/fCHP1ReXp6WLl3q6Ld2KnZYBQCg7xw6NSNJd955pyoqKvTzn/9cJ0+e1JgxY/Thhx8qKSnJ0W/tVN1nzrByBgCA3nN4EJGkBx98UA8++KAz3so03SMihdVNqmpoVViAj8kVAQDg+jhrpp8E+3orKcJfEqMiAAD0FkGkH42mTwQAgD4hiPSj0d0rZxgRAQCgVwgi/eh0wyojIgAA9AZBpB91T83klDeovqXd5GoAAHB9BJF+FBloVWywrwxDOniS6RkAAC6FINLP7A2rhUzPAABwKQSRfjY6gYZVAAB6iyDSz04v4SWIAABwKQSRfta9cuZoSZ1a2jtMrgYAANdGEOln8SG+CvP3VnunoSPF9WaXAwCASyOI9DOLxXLGxmY0rAIAcDEEEQcYncBW7wAA9AZBxAHsIyKFNKwCAHAxBBEHGHNq5czBk7Vq7+g0uRoAAFwXQcQBkiMCFODjqZb2TmWXN5hdDgAALosg4gAeHhalnxoV2c8OqwAAXBBBxEG6+0TY2AwAgAsjiDjIaEZEAAC4JIKIg3TvsJpVVKvOTsPkagAAcE0EEQcZFh0oHy8P1bW0K7+q0exyAABwSQQRB/H29FBabJAk9hMBAOBCCCIOdLphlT4RAADOhyDiQPaGVVbOAABwXgQRB+puWD1QWCPDoGEVAICzEUQcKC02SJ4eFlU0tKqktsXscgAAcDkEEQfy9fbUsKhASfSJAABwPgQRBxud0L2xGX0iAACcjSDiYKycAQDgwggiDjbm1MoZzpwBAOBcBBEH6z6Ft7C6SVUNrSZXAwCAayGIOFiQr7eSI/wlMSoCAMDZCCJO0L2fyO78KpMrAQDAtRBEnGBycrgkaVsuQQQAgDMRRJxgSkpXENmZW6n2jk6TqwEAwHUQRJxgZEyQgn291NDaQZ8IAABnIIg4gYeHxT4qsi2n0uRqAABwHQQRJ+kOIlsJIgAA2BFEnGRKSoQkaXtupTo7OYkXAACJIOI0o+OD5e/jqZqmNh0prTO7HAAAXAJBxEm8PT2UkRQmiT4RAAC6EUScaCp9IgAA9EAQcaLuPpGt2ZUyDPpEAAAgiDjR2MQQ+Xh5qLy+RTnlDWaXAwCA6QgiTuTr7anxtlBJ9IkAACARRJxuKhubAQBgRxBxMjY2AwDgNIKIk00cEiZPD4sKq5tUUNVodjkAAJjKYUEkNzdXS5YsUUpKivz8/DR06FA9+eSTam1tddRbDggBVi9dlRAiqWuXVQAA3JnDgsihQ4fU2dmpF154QQcOHNCvf/1r/fGPf9RPf/pTR73lgGHfTySbIAIAcG9ejvrCixYt0qJFi+yfp6am6vDhw3r++ef1zDPPOOptB4QpKeF6YX02DasAALfnsCByPjU1NQoPD7/g4y0tLWppabF/Xltb64yynG5SUrgsFim7vEGldc2KDvI1uyQAAEzhtGbV48eP63e/+52WLl16wecsW7ZMISEh9g+bzeas8pwqxN9babHBkqTtOVUmVwMAgHn6HESeeuopWSyWi37s2LGjx2uKioq0aNEi3X777XrggQcu+LUff/xx1dTU2D/y8/P7/h0NEKf3E6kwuRIAAMzT56mZhx9+WHfddddFn5OcnGz/76KiIs2dO1fTpk3Tn/70p4u+zmq1ymq19rWkAWlKSrhe3pzLfiIAALfW5yASGRmpyMjIXj23sLBQc+fOVUZGhpYvXy4PD7Yt6TY5uWtE5HBJnaobWxXq72NyRQAAOJ/DkkFRUZGuueYa2Ww2PfPMMyorK1NxcbGKi4sd9ZYDSlSQVUOjAmQY0vZc+kQAAO7JYatmVq9erWPHjunYsWNKTEzs8ZhhGI562wFlSkqEjpc1aFtOhRakx5hdDgAATuewEZH77rtPhmGc9wNdOAAPAODuaNowUfcBePuLalXf0m5yNQAAOB9BxETxoX5KDPNTR6ehXSfoEwEAuB+CiMmmMD0DAHBjBBGT0ScCAHBnBBGTTUmJkCTtzq9Wc1uHydUAAOBcBBGTJUf4KzrIqtaOTu3Orza7HAAAnIogYjKLxUKfCADAbRFEXAB9IgAAd0UQcQHdfSI7T1SpraPT5GoAAHAegogLGB4dqFB/bzW1dWh/YY3Z5QAA4DQEERfg4WGxn8a7lekZAIAbIYi4iKtTu6ZnNhwtM7kSAACchyDiIualRUuStmZXqqapzeRqAABwDoKIi0iJDNCw6EC1dxpad4RREQCAeyCIuJD5o2IkSZ9mlZhcCQAAzkEQcSEL0rumZ744XMoyXgCAwzS0tOuLw6X6zw+ytHJ7nqm1eJn67uhhvC1MkYE+Kq9v1bacSs0YFml2SQCAQaDt1DEim46Va/OxCmXmV6mtw5DUdQr8nZOHmFYbQcSFeHpYNC8tWm/sKNCarBKCCADgsnR2GjpcUqdNx8q16Vi5tuVUqqG158GqCaF+mjksUrNHRJlUZReCiIuZPypGb+wo0KcHS/TkTemyWCxmlwQAGABKapu14Wi5Nh4t08Zj5Sqvb+3xeHiAj6YNjdCMoZGaMSxCQ8L9XeJ3DEHExcwcHimrl4cKqpp0qLhOo+KCzS4JAOCCGlvbtTW7sit8HCvTkZL6Ho/7+3hqSkr4qeARqbTYIHl4mB88zkYQcTH+Pl6aNTxSnx4s1adZJQQRAICkrumWrJO1Wn+0TOuPlJ06n8ywP26xSGMTQjRreJRmDo/UxCFh8vFy/TUpBBEXNH9UTFcQOVii71873OxyAAAmqahv0cZj5Vp3pEzrj5SrvL6lx+MJoX6aPSJSM4dFafrQCIUF+JhU6eUjiLigeaO6lvHuKahRSW2zYoJ9Ta4IAOAM7adWt3QFjzLtLayRcXrQQ/4+npo+NEKzR0Rp1vAoJUe4Rp/HlSCIuKDoIF+Nt4Vqd361Pj1YosVTk8wuCQDgIGV1LVp3pExfHC7VhiNlqm1u7/F4WmyQ5oyM0pwRUcpICpPVy9OkSh2DIOKiFqTHdAWRLIIIAAwmHZ2G9hZU64vDZVp7uFR7C2p6PB7q762ZwyI1Z0SUZo+IGvSj4gQRF7UgPUa//OSwNh2vUENLuwKs3CoAGKhqGtu09kip1h4u07ojZaps6Lm0dkxCsOaOjNY1I6M13hYqTxdc3eIo/HZzUcOjAzUk3F95lY3acLRci8bEml0SAKCXDMPQ8bIGfX6oRJ8eLNXOE1Xq6Dzd7BFk9dLsEVGaMzJK14yIUvQgH/W4GIKIi7JYLJo/KkYvbcrRmqwSgggAuLjW9k5tz63UZwdL9fmhEuVWNPZ4fERMoOamRWveyGhNTAqTt6frL611BoKIC1uQ3hVEPj9Uoo5Ow62G6gBgIKhubNUXh0v1aVap1h8pU13L6UZTH08PTU0N1/xRMZqXFi1buL+JlbougogLm5QcphA/b1U1tmlXXpUmJ4ebXRIAuL38ykatzirRmqxibc/tOeUSGWjVvLQozUuL0czhkQqkv++S+H/IhXl7emjuyCi9t7tIn2aVEEQAwASdnYb2FdZoTVaJPj1YokPFdT0eT4sN0vxRMZqfHqOxCSEuuY26KyOIuLj56TF6b3eR1mSV6PEbRpldDgC4hbaOTm3JrtAnB4q1JqtEJbWndzT19LBocnKYFqTHasGoGA2JYMrlShBEXNycEVHy9rQou7xBx8vqNTQq0OySAGBQamxt1/ojZfrkQIk+O1jSY2OxAB9PzRkZpQXpMZo7Mlqh/gNvK3VXRRBxcUG+3ro6NUIbjpbr06wSDZ1DEAGA/lLd2KrPDpbq4wPF2nC0TM1tnfbHIgN9tCA9RgtHx2r60IhBt6OpqyCIDAAL0mO6gsjBEn1vzlCzywGAAa2srkWrs4r10b5ifZld0aPZ1Bbup+vSY3XdmFhNHBLGakUnIIgMANeOitETqw5o54kqVdS3KCLQanZJADCgFNc06+P9J/Xh/mJtz63scZBcWmyQrhsdq+tGx2pUXNCAP0RuoCGIDAAJoX5KjwtW1slafX6oVLdPspldEgC4vPzKRn28v1gf7T+pXXnVPR4blxii66+K06LRsUqODDCnQEgiiAwYC9JjlHWyVp8eLCGIAMAF5Fc26sN9J/XBvpPnHCaXkRSm68fEatGYWCWGsdLFVRBEBogF6TH6zWdHtf5IuZrbOuTrTdMUAEhSYXWTPtx7Uv/cd1J78qvt1z0s0pSUcF0/Jk7XjY5VbIj7nufiyggiA8To+GDFBvuquLZZm4+Xa15ajNklAYBpTtY06cN9xfpgb1GPaReLRZqaEq4bx8Zr0ehYRQXRU+fqCCIDhMVi0fz0aP19S57WZJUSRAC4nbK6Fn20/6Te312kHSeq7NctFmlycri+MjZOi8bEKjqIkY+BhCAygCxIj9Xft+TpkwPFevrm0fLx4uRGAINbTVObPjlQrH/sKdKmY+U6Y6WtJieH6car4nT9VXGKCSZ8DFQEkQFkxtAIRQdZVVrXok8PluiGq+LMLgkA+l1ja7s+O1iq9/cUad3hMrV2nN5kbFxiiG4aF68bx8YpLsTPxCrRXwgiA4iXp4duy0jUc2uPa+X2fIIIgEGjraNTG46W6b3MIn16sESNrR32x0bEBOrmcfG6aVy8kiJYajvYEEQGmDsm2fTc2uNaf7RMRdVNig/lXwQABibDMLQrr0rvZRbpg30nVdnQan9sSLi/bhoXp5vHJWhkbJCJVcLRCCIDTHJkgK5ODdeW7Eq9uaNA/zp/uNklAUCfHCut03uZRVq1p1D5lU3265GB1lPhI17jbaHscOomCCID0J2TbV1BZGe+vj9vmDw4CwGAiyutbdaq3UV6b3ehDhTV2q8H+HjqutGxumVCgqYPjZCXJ0347oYgMgBdPyZOT6w6oIKqJm0+XqGZwyPNLgkAztHU2qHVWcV6e1ehNh4ts6948fKwaM6IKH11QoIWjIqRnw8bNLozpwSRlpYWTZ06VXv27FFmZqbGjx/vjLcdtHy9PfXV8fH6+5Y8rdyRTxAB4DI6Ow1tyanQO7sK9dG+k2o4o+l04pBQ3ToxUTdeFafwAB8Tq4QrcUoQ+fGPf6z4+Hjt2bPHGW/nFu6aPKRrT5H9xapqaFUYf6kBmOhYaZ3e2VWo9zILVVTTbL9uC/fT1yYk6tYJCRwuh/NyeBD56KOPtHr1ar399tv66KOPHP12bmNMQoj9RN73dhfq/hkpZpcEwM3UNLbp/b1FemtnQY8zXoJ8vfSVsfH62sQETUoKo+kUF+XQIFJSUqLvfOc7eu+99+Tvf+mTDltaWtTS0mL/vLa29iLPxp2TbXry/QNauT1f901P5i87AIfr6DS04WiZ3txZoDVZJWpt79psrLvv42sTE3XtqGgO5kSvOSyIGIah++67T0uXLtWkSZOUm5t7ydcsW7ZMTz/9tKNKGnRuGZ+g//zwoA4V12lfYY3GJoaaXRKAQepYab3e2lmgdzMLVFJ7+h+MabFBui0jUbdMSFBkIAfMoe/6HESeeuqpS4aF7du3a/PmzaqtrdXjjz/e66/9+OOP69FHH7V/XltbK5vN1tcS3UaIv7euHxOrVbuLtHJ7PkEEQL+qbW7TP/ec1Js785V5xgm3Yf7e+ur4BN2WkajR8cGMxuKKWAzDMC79tNPKy8tVXl5+0eckJyfrrrvu0j/+8Y8ef0A7Ojrk6empxYsX65VXXrnke9XW1iokJEQ1NTUKDg7uS5luY/Oxcn3zL1sVZPXStp/NZxkcgCtiGIa25lTqjR35+nDfSTW3dU29eHpYNHdklG7LSNTctGhZvfhZgwvry+/vPgeR3srLy+vR41FUVKTrrrtOb731lqZOnarExMRLfg2CyKV1dhqa88wXyq9s0q9uH6evZ1z6/1cAOFtJbbPe2lmgN3fkK7ei0X59WHSg7pxk0y0TEhQVxNQLeqcvv78d1iMyZMiQHp8HBgZKkoYOHdqrEILe8fCw6I4Mm3615ohW7sgniADotbaOTn12sFRv7MjX2sOl9g3HAnw8ddO4eN0x2aYJbLUOB2Nn1UHgtkmJ+vWnR7Qtp1I55Q1KYa0+gIvIKW/Q69vz9PbOApXXnz5obnJymO6YZNMNV8UpwMqvBziH0/6kJScny0GzQG4vLsRPc0ZE6YvDZXpjR75+sijN7JIAuJjmtg59cqBYK7blaUt2pf16ZKBVX89I0B2TbBoaFWhihXBXRN5B4s7JNn1xuExv7SzQ/1kwgoOjAEiSjpbUacW2fL2TWaDqxjZJksUiXTMiSndNGaJ5adHy5ucFTEQQGSTmpcUoIsBHZXUt+uJwmRakx5hdEgCTNLV26IN9J7ViW552nqiyX48P8dXtk2y6Y7JNCaF+JlYInEYQGSR8vDz09YxE/Wl9tlZuzyeIAG7oaEmdXt2ap3d2Fai2uV1S17Lba9Oi9Y0pQzR7RJQ8PWg8hWshiAwid0yy6U/rs/XF4VKV1jYrOtjX7JIAOFhzW4c+3l+s17bmaVvu6d4PW7if7po8RLdnJPKzAC6NIDKIDIsOVEZSmHaeqNLbuwr1L9cMNbskAA6SXVavFdvy9NbOAlWd6v3w9LBo/qhofXNqkmYNi5QHox8YAAgig8ydk2zaeaJKb+zI1/dmp/KDCBhE2jo6tSarRH/fckKbj1fYr8eH+OquKUN052SbYhj9wABDEBlkbhwbp//4IEs55Q36+ECxbrgqzuySAFyhkzVNWrEtX69vy1NpXdeBcxaLNHdktBZPHaJrRkbT+4EBiyAyyARYvXT/jBT99rOj+u1nR7VodCyjIsAAZBiGNh2r0N+3nNCagyXqOLXtaWSgVXdNtukbU4ew8gWDAkFkEFoyI0XLN+boUHGdVmcVa9EYRkWAgaKmsU1v7SrQq1tOKLu8wX59akq47p6WpIXpsfLxYt8PDB4EkUEoxN9b981I1u8+P6bffHZMC9MZFQFc3f7CGv3tyxNatafQfuJtoNVLX5+YoMVXJ2lETJDJFQKOQRAZpJbMTNHyTbk6eLJWaw6W6LrRsWaXBOAsre2d+mj/Sf3tyxPaccbGY2mxQbp7WpJuGZ/AmS8Y9PgTPkiF+vvo3ulJ+sMXx/WbT49qYXoMJ2gCLqKktlmvbs3Ta1vzVF7f1Xzq5WHRDVfF6Z5pScpICuPvK9wGQWQQe2Bmql7elKusk7Vak1WihYyKAKYxDEPbcir11y0n9Mn+YrWfaj6NDrJq8dQkfWOKjY3H4JYIIoNYWICP7p2erOfWHtdvPjuqBYyKAE7X1NqhVbsL9fLmXB0qrrNfn5IcrnumJ+m60bEcOge3RhAZ5B6YlaqXN+fqQFGtPjtYqvmcQQM4RUFVo/625YRWbs+3n3rr6+2hWyck6p5pSRoVF2xyhYBrIIgMcuEBPrpnWrL+uK5rVOTaUdGMigAOYhiGtmRX6uXNOVqTVaJTsy9KDPPTvdOSdcckm0L8vc0tEnAxBBE38J1ZKXplc672Fdboi8OlmpfGqAjQn5paO/Te7kK9ctb0y4xhEbpveormpbHzKXAhBBE3EBFo1T3TkvTC+mz95tOjmjuSURGgPxRVN+mvX57Qim15qmnqmn7x8/bU1yYm6N7pyez9AfQCQcRNfGd2qv765QntKajR2iNlmjsy2uySgAHJMAztPFGl5Zty9fGBYvvW67bwrumX2zOYfgH6giDiJiIDrfrW1UP05w05+s2nR3XNiChGRYA+aG3v1D/3Fmn5pq5pzm7TUiN0/4xkXTsqhukX4DIQRNzId2cP1d+2nNDu/GqtO1KmaxgVAS6pvL5Fr27J09+3nlDZqZNvfbw8dOv4BN03I5nVL8AVIoi4kaggq741NUl/2Zij33x2VHMYFQEuKKuoVss35WjV7iK1dnSd/RITbNXdVyfpG1OGKCLQanKFwOBAEHEz352Tqr9tOaHMvGptOFqu2SOizC4JcBmdnYY+P1SqFzfm6MvsCvv1cbZQfXtGsq4fE8fJt0A/I4i4meggXy2emqSXNnWNiswaHsmoCNxeQ0u73tpZoOWbcpRb0ShJ8vSwaNGYWH17RooyksJMrhAYvAgibmjpnFS9uvWEdp6o0icHirVoTJzZJQGmKKxu0iubc7ViW57qmtslSUG+XvrmlCG6Z3qyEkL9TK4QGPwIIm4oOthX352dqt99fkz/d9UBTUuNZLkh3MquvCq9uDFHH+8/vfw2JTJA989I1tcnJirAyo9GwFn42+amHpo7TB/sO6nssgb914cH9f9uG2t2SYBDtXd06pMDJfrLxmxl5lXbr08fGqElM1M0d2S0PFh+CzgdQcRN+Xp76v99faxu/+OXWrkjX18dH6/pwyLNLgvod7XNbVq5LV8vb85VYXWTJMnH00M3j4/Xt2ekKD2e5beAmQgibmxycrjuvjpJf9tyQo+9s0+fPDJbfj6eZpcF9Iu8ikYt35yjN7bnq6G1Q1LXIZDfmjpE35qWpOggX5MrBCARRNzejxeN1KcHS5RX2ahff3pEP71hlNklAZete/v1v2zI0eqsYvvpt8OjA7VkZopumZAgX2/CNuBKCCJuLsjXW/956xh9++Ud+suGbN14VZzG2ULNLgvok7aOTn20v1gvbszRnvxq+/XZI6K0ZGaKZrNMHXBZBBFoXlqMvjo+Xqt2F+knb+/VP74/U96ebNoE11fT1KaV2/P08qZcFdU0S+rafv1rExL07ZkpnH4LDAAEEUiSnvhKutYfKdOh4jq9sO64Hp433OySgAvKq2jUS5ty9OaO0/0fEQE+untakr51dZIi2X4dGDAIIpAkRQRa9eRNo/XIyt367WfHtGhMnIZFB5pdFmBnGIZ2nKjSi2f1f4yICdQDM1N18/h4+j+AAYggAruvjo/Xe7sLtfZwmR5/Z69Wfnca+yrAdG0dnfpw30m9tDFHewpq7Ndnj4jSAzNTOKYAGOAIIrCzWCz6z1uv0sJn12l7bpVe3XpCd09LNrssuKmapja9vi1PL2/O1Un6P4BBiyCCHhJC/fST69P0xKoD+u+PDmneqBjO24BTnaho0PJNuXpjR74aT/V/RAb66O6rk7X46iH0fwCDDEEE5/jW1CS9v7tIO05U6d/f3aeX7pvM0DccyjAMbcup1Isbc7TmYImMU/0fI2OCtGRWim4eR/8HMFgRRHAODw+L/vvrY3XDbzboi8Nlem1bnhZPTTK7LAxCre2d+ufeIr24MUcHimrt168ZGaUHZqZqxrAIQjAwyBFEcF7DogP16MIR+u+PDumJVQeUFB6gmcM5iwb9o6K+Ra9tzdNft5xQWV2LJMnX20Nfm5iob89I1rBo+j8Ad0EQwQV9b3aqDhfX6d3MQv3Lqzv1zr9M13AaBHEFDhfXafmmHL2bWaiW9k5JUkywVfdMS9Y3pwxRWICPyRUCcDaCCC7IYrHov79+lQqqGrU9t0r3v7xd7z44Q1FBNAui9zo7Da09Uqrlm3K14Wi5/frYxBAtmZmi68fEyceLnXwBd2UxjO62MNdTW1urkJAQ1dTUKDiYo7rNUtnQqluf26QTFY0abwvV69+9msZBXFJdc5ve2lmgVzbnKreiUZLkYZGuGx2rb89M0aSkMPo/gEGqL7+/CSLoleyyet363GbVNLXpxqvi9LtvTGCzM5zXiYoGvbw5V2/uKFB9S7skKcjXS3dNtumeacmyhfubXCEAR+vL72+mZtArqVGBeuHuDN394lZ9sO+kkiP99aPr0swuCy7CMAxtOlah5Zty9PnhUvvy29SoAN0/PVlfm5ioACs/bgCci58M6LWrUyO07Gtj9W9v7tEfvjiupIgA3THJZnZZMFFDS7vezSzUX7/M1ZGSevv1uSOjdN+MFM0aFsnIGYCLIoigT27LSFRueYN+/8Ux/fSdfUoM89P0oSzrdTfZZfX625YTemtHgepOTb8E+HjqtoxE3Ts9WalRHJgIoHcIIuizRxeMUG5Fg/6596SW/m2n3nlwBif1uoGOTkNrD5fqlS9PaP2RMvv1lMgAfevqJN0+KVHBvt4mVghgIHJ4EPnggw/085//XHv37lVAQIBmz56td955x9FvCwfy8LDomdvHqai6SbvyqvXtl7fr3QenK4IzQAalqoZWvbEjX3/bckIFVU2SJItFujYtWvdMS9ZMpl8AXAGHBpG3335b3/nOd/Rf//VfmjdvngzD0L59+xz5lnASX29P/fmeSbrluU3Kq2zUnX/aouX3TWZFxCBhGIZ251fr1a15+seeIvvmYyF+3rprsk3fujqJew2gXzhs+W57e7uSk5P19NNPa8mSJZf1NVi+6/qOldbrW3/ZquLaZkUE+OjP907SxCFhZpeFy1Tf0q73Mgv12tY8ZZ08ffZLelyw7puerJvGxcvPhz1kAFycSyzf3bVrlwoLC+Xh4aEJEyaouLhY48eP1zPPPKPRo0ef9zUtLS1qaWmxf15bW3ve58F1DIsO1HsPzdCSV7brQFGtvvGnLXr2jvG6cWyc2aWhD/YX1ujVrXl6f3ehGlo7JEk+Xh76ytg4LZ46RBOHsPkYAMdwWBDJzs6WJD311FN69tlnlZycrF/96leaM2eOjhw5ovDw8HNes2zZMj399NOOKgkOEhviqze+N00/WJGpzw6V6qHXdimvMk1L56Tyy8uFNbV26B97ivTqtjztya+2X0+NCtDiqUn6+sQEhfpz9gsAx+rz1MxTTz11ybCwfft2HTlyRIsXL9YLL7yg7373u5K6RjwSExP1i1/8Qt/73vfOed35RkRsNhtTMwNER6eh//hnll7enCtJunOSTb+4dYy8PTlHxFV09368saNA/9xTZF966+1p0aIxXaMfU1PCCZAArohDp2Yefvhh3XXXXRd9TnJysurq6iRJ6enp9utWq1WpqanKy8s77+usVqusVlZeDFSeHhY9dfNoJUf46+f/zNLKHfkqqG7Uc4szFOLHsk4zldW16N3MAr25o0BHS09vPDYk3F/fnDpEt2UkKpJVTwBM0OcgEhkZqcjIS29glZGRIavVqsOHD2vmzJmSpLa2NuXm5iopKanvlWLAuG9GioZE+Ovh1zK16ViFbnt+s15iRY3TtXV0au3hMr2xI19fHCpVe2fX4Kevt4duGBOn2yfZNDUlnKW3AEzlsB6R4OBgLV26VE8++aRsNpuSkpL0y1/+UpJ0++23O+pt4SLmpcXozaXT9O2Xt+toab1ufW6T/vitDE1KPrc3CP3HMAxlnazVqt1FemdXocrrT091jreF6o5JNn1lXBwbjwFwGQ7dR+SXv/ylvLy8dPfdd6upqUlTp07V559/rrAwlne6g9HxIV0ral7eoayTtbr9hS911+Qh+vF1IxUWQBNkf8qvbNSq3YVatbuox9RLZKCPvjYxUbdnJGp4TJCJFQLA+TlsH5H+wD4ig0NDS7ueWHVAb+8qkCSF+nvrJ4vSdOckG9MCV6C8vkUf7D2pVbsLtSuv2n7dx8tD16ZF65YJCZqXFk2zMACn68vvb4IInGZbTqWeWLVfh4q7GpnH2UL1H18drbGJoeYWNoDUNLXps4MlWrW7SBuPlavjVN+Hh0WaPjRSN4+P13WjY2kOBmAqgghcVntHp/765Qk9u+aI6lvaZbFI35wyRD+6biR7VlxASW2zVmeVaPWBYn15vMLedCpJ4xJDdPP4BN00Nk7Rwb4mVgkApxFE4PJKa5u17KNDejezUJIUHuCjxxal6baMRKZrJB0vq9fqAyX65ECxdp+x2ZjUtZvtV8bG6eZx8UqN4tRjAK6HIIIBY0t2hZ5YtV9HSroaLNPjgrX46iG6eVy8gtxoZUdbR6cy86q19nCpVmeV6NgZDaeSNGFIqK4bHauF6TGEDwAujyCCAaWto1OvbM7Vr9ccsZ9z4uftqa+MjdNdU2yD8pwTwzB0pKReG4+Va9Oxcm3NrrB/71LXTqfThkZqYXqMFqbHMO0CYEAhiGBAqmxo1Tu7CvT69vweIwLDowN152SbvjYxUeEDeNnvyZombTpWoY1Hy7TpeIXK6lp6PB4e4KMZwyI1f1S05qZFs9cHgAGLIIIBzTAM7cqr0opt+frn3iI1t3VKknw8PbRgdIxum5iojOQwl/5FXdfcpv2FtdpbUK29BTXaW1it/MqmHs/x9fbQlJQIzRwWoZnDopQWG0R/DIBBgSCCQaO2uU3v7y7Syu352ldYY79usXSNlEywhWliUqgmDAnTsKhAU36RN7a26+DJOu0tqNa+ghrtKahWdnmDzv6b5WGRxiaGauawSM0YFqmJSaGyenk6vV4AcDSCCAal/YU1XeemHC49Z3RBkoKsXhpnC9WEIaEamxiqmGCrwvx9FB7gI38fz8vuM2nr6NTJ6mblVzUqr7JR+ZWNyq9q6vrfykZVNLSe93UJoX4amxiisYmhGpsYoqsSQ1x6FAcA+gtBBINeWV2LdudXKzOvSrvyqrS3oEaNZzR7ns3Hy0Ph/j4KC/BReIC3wvx9FOrvrY5OQ81tnWpq7VBze4ea2zrU1Naplrau/25s7VB5fYs6L/G3JDLQqnFnhQ5OswXgrggicDvtHZ06UlKvXXlVysyr1qHiWlU1tKqioVUt7Z1X/PV9vDxkC/OTLdxftjB/DQn3ly3cT4lh/rKF+7OTKQCcgSACnGIYhpraOlTZ0KqqhjZVNraqqqFVlQ2tqm5qk5eHRX7envL19pDV2/PUf3d93v3fUUFWRQVaaSQFgF7qy+9vh56+C5jNYrHI38dL/j5eSuTQZwBwORzLCQAATEMQAQAApiGIAAAA0xBEAACAaQgiAADANAQRAABgGoIIAAAwDUEEAACYhiACAABMQxABAACmIYgAAADTEEQAAIBpCCIAAMA0Ln36rmEYkrqOEwYAAAND9+/t7t/jF+PSQaSurk6SZLPZTK4EAAD0VV1dnUJCQi76HIvRm7hiks7OThUVFSkoKEgWi8V+vba2VjabTfn5+QoODjaxQpwP98f1cY9cG/fH9XGPLs4wDNXV1Sk+Pl4eHhfvAnHpEREPDw8lJiZe8PHg4GD+ALgw7o/r4x65Nu6P6+MeXdilRkK60awKAABMQxABAACmGZBBxGq16sknn5TVajW7FJwH98f1cY9cG/fH9XGP+o9LN6sCAIDBbUCOiAAAgMGBIAIAAExDEAEAAKYhiAAAANO4bBB57rnnlJKSIl9fX2VkZGjDhg29et2mTZvk5eWl8ePHO7ZAN9fX+9PS0qKf/exnSkpKktVq1dChQ/XSSy85qVr31Nd79Oqrr2rcuHHy9/dXXFyc7r//flVUVDipWveyfv163XTTTYqPj5fFYtF77713ydesW7dOGRkZ8vX1VWpqqv74xz86vlA31df7884772jBggWKiopScHCwpk2bpk8++cQ5xQ4CLhlEVq5cqUceeUQ/+9nPlJmZqVmzZun6669XXl7eRV9XU1Oje+65R9dee62TKnVPl3N/7rjjDn322Wd68cUXdfjwYa1YsUJpaWlOrNq99PUebdy4Uffcc4+WLFmiAwcO6M0339T27dv1wAMPOLly99DQ0KBx48bp97//fa+en5OToxtuuEGzZs1SZmamfvrTn+oHP/iB3n77bQdX6p76en/Wr1+vBQsW6MMPP9TOnTs1d+5c3XTTTcrMzHRwpYOE4YKmTJliLF26tMe1tLQ047HHHrvo6+68807j3//9340nn3zSGDdunAMrdG99vT8fffSRERISYlRUVDijPBh9v0e//OUvjdTU1B7Xfvvb3xqJiYkOqxFdJBnvvvvuRZ/z4x//2EhLS+tx7Xvf+55x9dVXO7AyGEbv7s/5pKenG08//XT/FzQIudyISGtrq3bu3KmFCxf2uL5w4UJt3rz5gq9bvny5jh8/rieffNLRJbq1y7k/77//viZNmqT/+Z//UUJCgkaMGKF/+7d/U1NTkzNKdjuXc4+mT5+ugoICffjhhzIMQyUlJXrrrbd04403OqNkXMKXX355zv287rrrtGPHDrW1tZlUFS6ks7NTdXV1Cg8PN7uUAcHlDr0rLy9XR0eHYmJielyPiYlRcXHxeV9z9OhRPfbYY9qwYYO8vFzuWxpULuf+ZGdna+PGjfL19dW7776r8vJyPfjgg6qsrKRPxAEu5x5Nnz5dr776qu688041Nzervb1dN998s373u985o2RcQnFx8XnvZ3t7u8rLyxUXF2dSZTifX/3qV2poaNAdd9xhdikDgsuNiHSzWCw9PjcM45xrktTR0aFvfvObevrppzVixAhnlef2ent/pK5/HVgsFr366quaMmWKbrjhBj377LN6+eWXGRVxoL7co6ysLP3gBz/QE088oZ07d+rjjz9WTk6Oli5d6oxS0Qvnu5/nuw5zrVixQk899ZRWrlyp6Ohos8sZEFxu+CAyMlKenp7n/MuttLT0nH8RSFJdXZ127NihzMxMPfzww5K6fvEZhiEvLy+tXr1a8+bNc0rt7qCv90eS4uLilJCQ0ONI6FGjRskwDBUUFGj48OEOrdndXM49WrZsmWbMmKEf/ehHkqSxY8cqICBAs2bN0i9+8Qv+xW2y2NjY895PLy8vRUREmFQVzrZy5UotWbJEb775pubPn292OQOGy42I+Pj4KCMjQ2vWrOlxfc2aNZo+ffo5zw8ODta+ffu0e/du+8fSpUs1cuRI7d69W1OnTnVW6W6hr/dHkmbMmKGioiLV19fbrx05ckQeHh5KTEx0aL3u6HLuUWNjozw8ev448PT0lHT6X94wz7Rp0865n6tXr9akSZPk7e1tUlU404oVK3Tffffptddeo7eqr8zrk72w119/3fD29jZefPFFIysry3jkkUeMgIAAIzc31zAMw3jssceMu++++4KvZ9WMY/X1/tTV1RmJiYnGbbfdZhw4cMBYt26dMXz4cOOBBx4w61sY9Pp6j5YvX254eXkZzz33nHH8+HFj48aNxqRJk4wpU6aY9S0ManV1dUZmZqaRmZlpSDKeffZZIzMz0zhx4oRhGOfen+zsbMPf39/44Q9/aGRlZRkvvvii4e3tbbz11ltmfQuDWl/vz2uvvWZ4eXkZf/jDH4yTJ0/aP6qrq836FgYUlwwihmEYf/jDH4ykpCTDx8fHmDhxorFu3Tr7Y/fee68xZ86cC76WIOJ4fb0/Bw8eNObPn2/4+fkZiYmJxqOPPmo0NjY6uWr30td79Nvf/tZIT083/Pz8jLi4OGPx4sVGQUGBk6t2D1988YUh6ZyPe++91zCM89+ftWvXGhMmTDB8fHyM5ORk4/nnn3d+4W6ir/dnzpw5F30+Ls5iGIy7AgAAc7hcjwgAAHAfBBEAAGAagggAADANQQQAAJiGIAIAAExDEAEAAKYhiAAAANMQRAAAgGkIIgAAwDQEEQAAYBqCCAAAMA1BBAAAmOb/A2YjZONlihRIAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "exe.output[0].plot()" + "output.plot()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "id": "14162f5b-1318-4595-8c8c-d6346a21721d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.6788586373205143" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "exe.output[0].equilibrium_volume" + "output.equilibrium_volume" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 27, "id": "0f5ff296-df33-40d2-851b-02d6ded72dd6", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.6788586373205143" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "exe.output[0].get_structure().get_volume()" + "output.get_structure().get_volume()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 28, "id": "92b06330-b1fc-41d0-8bd8-bf1b11bf448c", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "Atoms(symbols='Fe', pbc=True, cell=[[-0.5536557129291797, 0.5536557129291797, 0.5536557129291797], [0.5536557129291797, -0.5536557129291797, 0.5536557129291797], [0.5536557129291797, 0.5536557129291797, -0.5536557129291797]])" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "exe.output[0].get_structure()" + "output.get_structure()" ] }, { @@ -689,7 +1001,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "89169376-be36-4ceb-9f4e-6e1f3247bc62", "metadata": {}, "outputs": [], @@ -699,7 +1011,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "2ee9f1d4-5b14-4340-98d4-4bd293af89a4", "metadata": {}, "outputs": [], @@ -711,17 +1023,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "3d73a9de-7b4e-476a-b50a-ac6a3957a7ab", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "m.input.task.input" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "0f075d90-e636-49be-b1a6-741a56363f54", "metadata": {}, "outputs": [], @@ -739,17 +1062,7 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "58064e52-1c94-49fd-b38f-614cf6f19004", - "metadata": {}, - "outputs": [], - "source": [ - "m.input.child_executor = ProcessExecutor" - ] - }, - { - "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "d82a28ab-1a96-4a3a-8f79-5a875ac20788", "metadata": { "scrolled": true, @@ -757,70 +1070,70 @@ }, "outputs": [], "source": [ - "exe = m.run(how='foreground')" + "ret, output = ProcessExecutor(max_processes=4).run(m)" ] }, { "cell_type": "code", - "execution_count": null, - "id": "5472daed-f25c-4aab-b101-90c76a0235a5", - "metadata": { - "scrolled": true, - "tags": [] - }, - "outputs": [], - "source": [ - "exe._run_machine.state" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a42865c9-8616-4335-8e46-ec4839daab0a", - "metadata": {}, - "outputs": [], - "source": [ - "exe._run_time" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d894a79f-a9e6-4667-9a9b-2bd9a088622f", - "metadata": {}, - "outputs": [], - "source": [ - "exe._collect_time" - ] - }, - { - "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "78017969-23fc-46f5-b99f-cd1d2dc74c00", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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uu+++1RcXCyHw6GxY8fqiSeeiFK18WWg1/aZZ57R5MmTlZaWJrfbrX/8x39UTU1NlKqNHxs2bND8+fNVUFAgi8WiV1555bTPWb9+vaZMmaKUlBSNGTNG//Ef/xH5QuPQQK/tSy+9pDlz5ignJ0eZmZmaPn261qxZE51i40wof2+7hfO9jCASglWrVmnRokW67777tHXrVl1yySWaN2+eDh482OfzGhoadP311+trX/talCqNP6Fc22uuuUZ/+9vf9Pjjj2vXrl167rnnNGHChChWHR8Gem3fffddXX/99brxxhu1fft2vfDCC9q0aZNuuummKFce+3w+nyZPnqwVK1b06/FlZWW6/PLLdckll2jr1q2699579eMf/1gvvvhihCuNPwO9ths2bNCcOXO0evVqbdmyRbNnz9b8+fO1devWCFcafwZ6bbuF/b3MwIBdcMEFxq233trj3IQJE4yf//znfT5vwYIFxj//8z8b999/vzF58uQIVhi/BnptX3/9dcPlchk1NTXRKC+uDfTaPvTQQ8aYMWN6nPvNb35jFBYWRqzGoUCS8fLLL/f5mHvuuceYMGFCj3O33HKLceGFF0awsvjXn2t7KhMnTjQeeOCB8Bc0hAzk2ob7vYwRkQFqbW3Vli1bNHfu3B7n586dq/fff7/X5z355JPau3ev7r///kiXGLdCubavvvqqpk6dqgcffFAjR47U+PHjdffdd6u5uTkaJceNUK7tjBkzdOjQIa1evVqGYejo0aP685//rCuuuCIaJQ9pH3zwwUl/Fl//+te1efNmtbW1mVTV0NTZ2Smv16usrCyzSxkSIvFeFtM3vYtF1dXV6ujoUF5eXo/zeXl5qqysPOVzdu/erZ///Od65513ZLNxyXsTyrXdt2+f3n33XaWkpOjll19WdXW1brvtNtXW1tIncoJQru2MGTP0zDPPaMGCBWppaVF7e7u++c1v6re//W00Sh7SKisrT/ln0d7erurqarndbpMqG3p+9atfyefz6ZprrjG7lLgXqfcyRkRCZLFYenxtGMZJ5ySpo6ND1157rR544AGNHz8+WuXFtf5eWynwrx2LxaJnnnlGF1xwgS6//HI9/PDD+uMf/8ioyCkM5Nru2LFDP/7xj/Uv//Iv2rJli9544w2VlZXp1ltvjUapQ96p/ixOdR6he+6557R06VKtWrVKubm5ZpcT1yL5XsY/zwcoOztbSUlJJ/0rsqqq6qR/4UiS1+vV5s2btXXrVt1xxx2SAm+ehmHIZrPpzTff1KWXXhqV2mPdQK+tJLndbo0cObLH7abPPPNMGYahQ4cOqbS0NKI1x4tQru3y5ct10UUX6ac//akk6ZxzzlF6erouueQS/eIXv+Bf7YOQn59/yj8Lm82mESNGmFTV0LJq1SrdeOONeuGFF3TZZZeZXU7ci+R7GSMiA2S32zVlyhStXbu2x/m1a9dqxowZJz0+MzNTn376qbZt2xY8br31Vp1xxhnatm2bpk2bFq3SY95Ar60kXXTRRTpy5IgaGxuD57744gtZrVYVFhZGtN54Esq1bWpqktXa838RSUlJko7/6x2hmT59+kl/Fm+++aamTp2q5ORkk6oaOp577jndcMMNevbZZ+lpCpOIvpcNut01AT3//PNGcnKy8fjjjxs7duwwFi1aZKSnpxv79+83DMMwfv7znxvXXXddr89n1UzvBnptvV6vUVhYaFx99dXG9u3bjfXr1xulpaXGTTfdZNavELMGem2ffPJJw2azGY899pixd+9e49133zWmTp1qXHDBBWb9CjHL6/UaW7duNbZu3WpIMh5++GFj69atxoEDBwzDOPna7tu3z0hLSzPuuusuY8eOHcbjjz9uJCcnG3/+85/N+hVi1kCv7bPPPmvYbDbj0UcfNSoqKoJHfX29Wb9CzBrotf2ycL2XEURC9OijjxrFxcWG3W43zjvvPGP9+vXB7y1cuNCYOXNmr88liPRtoNd2586dxmWXXWakpqYahYWFxuLFi42mpqYoVx0fBnptf/Ob3xgTJ040UlNTDbfbbfzgBz8wDh06FOWqY9/bb79tSDrpWLhwoWEYp76269atM77yla8YdrvdGD16tLFy5croFx4HBnptZ86c2efjcVwof29PFK73MothMMYKAADMQY8IAAAwDUEEAACYhiACAABMQxABAACmIYgAAADTEEQAAIBpCCIAAMA0BBEAAGAagggAADANQQQAAJiGIAIAAExDEAEAAKb5v1E7qt2HJCfYAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "exe.output[0].plot()" + "output.plot()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "45162eb2-b23d-45c6-8aad-dfe9a6a484d1", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.6818586500998999" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "exe.output[0].get_structure().get_volume()" + "output.get_structure().get_volume()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "id": "f0bcfe59-2168-4e74-9d7a-33d900368907", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.6818586500999" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "exe.output[0].equilibrium_volume" + "output.equilibrium_volume" ] }, { @@ -831,6 +1144,30 @@ "## Again but execute everything in the background." ] }, + { + "cell_type": "code", + "execution_count": 19, + "id": "c4758ca5-0760-4fd9-80d6-b02f78da0e5c", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "ename": "AssertionError", + "evalue": "broken in the TaskGenerator formalism", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[19], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28;01mFalse\u001b[39;00m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mbroken in the TaskGenerator formalism\u001b[39m\u001b[38;5;124m\"\u001b[39m\n", + "\u001b[0;31mAssertionError\u001b[0m: broken in the TaskGenerator formalism" + ] + } + ], + "source": [ + "assert False, \"broken in the TaskGenerator formalism\"" + ] + }, { "cell_type": "code", "execution_count": null, @@ -984,7 +1321,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "id": "149c52b5-a0ce-4e6b-ba55-d94d33aa2f8a", "metadata": { "tags": [] @@ -996,7 +1333,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "id": "aca24005-ea49-4389-bc26-f292fd0a75a2", "metadata": { "tags": [] @@ -1015,7 +1352,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "id": "4ae990bd-af18-4dae-8500-779c9509f3f6", "metadata": { "tags": [] @@ -1027,73 +1364,1090 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "fa62529f-45e6-4e2d-822d-b1cc433a7223", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "m.input.child_executor = ProcessExecutor" - ] - }, - { - "cell_type": "code", - "execution_count": null, + "execution_count": 23, "id": "0925864e-4dd1-4f4e-ace4-aac09c55e787", "metadata": { "scrolled": true, "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 4.251945 0.0000\n", + "LBFGS: 0 17:04:52 3.244546 0.0000\n", + "LBFGS: 0 17:04:52 4.517693 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 3.488292 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 3.006013 0.0000\n", + "LBFGS: 0 17:04:52 4.789242 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 3.737364 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 3.991875 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 1.473327 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 2.544148 0.0000\n", + "LBFGS: 0 17:04:52 1.887783 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 2.101849 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 2.772582 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 2.320604 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 1.678307 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 1.272749 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 1.076481 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 0.884435 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 0.696523 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 -0.015462 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 0.332761 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 -0.348779 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 0.156747 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 0.512659 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 -0.183946 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 -0.510037 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 -0.822116 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 -0.667792 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 -0.973078 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 -1.120747 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 -1.265189 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 -1.406469 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 -1.544652 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 -2.191241 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:52 -1.679799 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -1.941232 0.0000\n", + "LBFGS: 0 17:04:53 -2.312104 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -1.811973 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -2.067636 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -2.430279 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -2.658780 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -2.982680 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -2.545820 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -2.769210 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -3.085817 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -3.285134 0.0000\n", + "LBFGS: 0 17:04:53 -2.877160 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -3.186620 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -3.475475 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -3.567390 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -3.914328 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -3.381404 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -3.830620 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -3.744922 0.0000\n", + "LBFGS: 0 17:04:53 -3.657192 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -3.996084 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -4.075925 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -4.376892 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -4.153891 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -4.230016 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -4.304338 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -4.584282 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -4.516830 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -4.447711 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -4.650099 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -4.714313 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -4.776956 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -4.838057 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.012409 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -4.897647 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -4.955755 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.067638 0.0000\n", + "LBFGS: 0 17:04:53 -5.225046 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.173930 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.121469 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.323350 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.274844 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.461356 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.370587 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.416581 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.504934 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.547340 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.588595 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.628722 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.705677 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.813177 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.742548 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.778375 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.879789 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.667742 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.846976 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.109029 0.0000\n", + "LBFGS: 0 17:04:53 -5.911636 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.083445 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.942536 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.057017 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -5.972506 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.029730 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.001565 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.157731 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.203250 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.133786 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.180881 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.245705 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.224853 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.303898 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.265820 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.285213 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.321888 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.339196 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.355836 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.387162 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.371820 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.401872 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.429451 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.415965 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.442342 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.454650 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.498271 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.466386 0.0000\n", + "LBFGS: 0 17:04:53 -6.488186 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.477561 0.0000\n", + "LBFGS: 0 17:04:53 -6.507828 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.516865 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.548028 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.533425 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:53 -6.525395 0.0000\n", + "LBFGS: 0 17:04:53 -6.560750 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.540966 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.571664 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.554620 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.580840 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.566429 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.588345 0.0000\n", + "LBFGS: 0 17:04:54 -6.576465 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.591492 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.584797 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.598601 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.594245 0.0000\n", + "LBFGS: 0 17:04:54 -6.596612 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.600220 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.602374 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.601475 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.603005 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.603132 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.601771 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.602924 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.602549 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.597570 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.599275 0.0000\n", + "LBFGS: 0 17:04:54 -6.600678 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.595568 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.590697 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.593275 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.587840 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.584710 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.577651 0.0000\n", + "LBFGS: 0 17:04:54 -6.581312 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.565149 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.573734 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.569565 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.555599 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.560493 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.545124 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.533761 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.539551 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.521548 0.0000\n", + "LBFGS: 0 17:04:54 -6.550475 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.527759 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.515134 0.0000\n", + "LBFGS: 0 17:04:54 -6.501712 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.508521 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.494713 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.480158 0.0000\n", + "LBFGS: 0 17:04:54 -6.464889 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.487527 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.472611 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.440712 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.456996 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.432329 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.448936 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.423789 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.415096 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.397266 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.406254 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.378866 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.388136 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.350251 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.359921 0.0000\n", + "LBFGS: 0 17:04:54 -6.340455 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.369460 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.330535 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.310333 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.320493 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.300058 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.289669 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.268565 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.257855 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.279171 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.247042 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.236130 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.202819 0.0000\n", + "LBFGS: 0 17:04:54 -6.191532 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.225120 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.214016 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.168698 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.180158 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.157154 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.145530 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.098266 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.133827 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.122047 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.110193 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.086268 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.074202 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.062068 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.049871 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.012907 0.0000\n", + "LBFGS: 0 17:04:54 -6.037610 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.000468 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.987974 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -6.025288 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.975426 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.962825 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.937473 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.950174 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.911931 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.924725 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.886212 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.899093 0.0000\n", + "LBFGS: 0 17:04:54 -5.873290 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.860327 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.808107 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.821215 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.794966 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.847326 0.0000\n", + "LBFGS: 0 17:04:54 -5.834289 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.781794 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.755359 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.768591 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.728813 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.742099 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.702165 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.662022 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.715501 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.688805 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.675424 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.648600 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.608226 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.581230 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.594735 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.635159 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:54 -5.621701 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.540637 0.0000\n", + "LBFGS: 0 17:04:54 -5.567712 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.554180 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.486366 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.527083 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.499947 0.0000\n", + "LBFGS: 0 17:04:55 -5.472778 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.513519 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.431978 0.0000\n", + "LBFGS: 0 17:04:55 -5.445583 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.459183 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.418369 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.363910 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.404757 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.377527 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.391143 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.323061 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.350293 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.336676 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.268625 0.0000\n", + "LBFGS: 0 17:04:55 -5.309448 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.295837 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.282229 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.241432 0.0000\n", + "LBFGS: 0 17:04:55 -5.255026 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.227844 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.214262 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.200687 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.160009 0.0000\n", + "LBFGS: 0 17:04:55 -5.187119 0.0000\n", + "LBFGS: 0 17:04:55 -5.173560 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.119414 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.146468 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.132936 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.105903 0.0000\n", + "LBFGS: 0 17:04:55 -5.078915 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.092403 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.051977 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.065440 0.0000\n", + "LBFGS: 0 17:04:55 -5.038527 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.025090 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -5.011668 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.984867 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.971490 0.0000\n", + "LBFGS: 0 17:04:55 -4.998260 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.944782 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.958128 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.931453 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.918140 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.904845 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.851844 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.878308 0.0000\n", + "LBFGS: 0 17:04:55 -4.891567 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.838641 0.0000\n", + "LBFGS: 0 17:04:55 -4.865067 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.825456 0.0000\n", + "LBFGS: 0 17:04:55 -4.812292 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.799147 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.746775 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.786023 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.772919 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.759837 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.733735 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.720716 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.707720 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.643076 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.668865 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.655959 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.694745 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.604569 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.681794 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.630216 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.617381 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.540876 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.591781 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.579018 0.0000\n", + "LBFGS: 0 17:04:55 -4.553565 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.515574 0.0000\n", + "LBFGS: 0 17:04:55 -4.566279 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.502960 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.528212 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.440283 0.0000\n", + "LBFGS: 0 17:04:55 -4.490373 0.0000\n", + "LBFGS: 0 17:04:55 -4.452766 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.427826 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.477811 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.465275 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.402992 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.415396 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.353649 0.0000\n", + "LBFGS: 0 17:04:55 -4.390616 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.365944 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.378267 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.341382 0.0000\n", + "LBFGS: 0 17:04:55 -4.316929 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.304745 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.329142 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.292588 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.256286 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.244242 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.280459 0.0000\n", + "LBFGS: 0 17:04:55 -4.268359 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.232226 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.208280 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.220239 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.196350 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.172575 0.0000\n", + "LBFGS: 0 17:04:55 -4.125374 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.184448 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.148917 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.160732 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.137131 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.113646 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.090278 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.078638 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.067028 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.101948 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.032372 0.0000\n", + "LBFGS: 0 17:04:55 -4.043894 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.020879 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -4.009416 0.0000\n", + "LBFGS: 0 17:04:55 -4.055446 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -3.997982 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -3.963859 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -3.975204 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -3.986578 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -3.930004 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -3.952544 0.0000\n", + "LBFGS: 0 17:04:55 -3.907582 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:55 -3.918778 0.0000\n", + "LBFGS: 0 17:04:55 -3.941259 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.863097 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.874174 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.841034 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.896416 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.885280 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.797267 0.0000\n", + "LBFGS: 0 17:04:56 -3.852051 0.0000\n", + "LBFGS: 0 17:04:56 -3.819091 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.830047 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.786400 0.0000\n", + "LBFGS: 0 17:04:56 -3.775562 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.808164 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.764755 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.721825 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.700539 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.711167 0.0000\n", + "LBFGS: 0 17:04:56 -3.753978 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.743230 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.732513 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.679373 0.0000\n", + "LBFGS: 0 17:04:56 -3.689941 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.668834 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.595897 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.626978 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.658325 0.0000\n", + "LBFGS: 0 17:04:56 -3.637397 0.0000\n", + "LBFGS: 0 17:04:56 -3.647846 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.616588 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.606228 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.575325 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.585597 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.565084 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.554872 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.504253 0.0000\n", + "LBFGS: 0 17:04:56 -3.514318 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.524412 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.534536 0.0000\n", + "LBFGS: 0 17:04:56 -3.544689 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.494218 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.484212 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.444480 0.0000\n", + "LBFGS: 0 17:04:56 -3.474235 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.464287 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.424788 0.0000\n", + "LBFGS: 0 17:04:56 -3.454369 0.0000\n", + "LBFGS: 0 17:04:56 -3.434619 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.414986 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.395469 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.347181 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.385754 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.376068 0.0000\n", + "LBFGS: 0 17:04:56 -3.405213 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.366410 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.356781 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.328067 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.318553 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.337610 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.299610 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.290181 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.309067 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.280781 0.0000\n", + "LBFGS: 0 17:04:56 -3.262065 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.271409 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.252749 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.243462 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.234202 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.206592 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.215768 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.197445 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.224971 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.188325 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.179233 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.161132 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.170169 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.134187 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.143141 0.0000\n", + "LBFGS: 0 17:04:56 -3.125260 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.152123 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.116361 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.107488 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.089825 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.054823 0.0000\n", + "LBFGS: 0 17:04:56 -3.072271 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.098643 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.081034 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.063534 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.037483 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.046140 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.028853 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.020250 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.994599 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.986101 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.003123 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.977630 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -3.011673 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.960766 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.969185 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.952373 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.910796 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.935665 0.0000\n", + "LBFGS: 0 17:04:56 -2.944006 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.894346 0.0000\n", + "LBFGS: 0 17:04:56 -2.902558 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.919060 0.0000\n", + "LBFGS: 0 17:04:56 -2.927350 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.886159 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.837570 0.0000\n", + "LBFGS: 0 17:04:56 -2.861751 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.845605 0.0000\n", + "LBFGS: 0 17:04:56 -2.877998 0.0000\n", + " Step Time Energy fmax\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.853666 0.0000\n", + "LBFGS: 0 17:04:56 -2.869862 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.821575 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.829560 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.813615 0.0000\n", + " Step Time Energy fmax\n", + "LBFGS: 0 17:04:56 -2.805679 0.0000\n" + ] + } + ], "source": [ - "exe = m.run()" + "ret, output = ProcessExecutor(max_processes=8).run(m)" ] }, { "cell_type": "code", - "execution_count": null, - "id": "a3069fe3-93bf-4c83-93e7-6d0ac56d8248", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "exe._run_machine.state" - ] - }, - { - "cell_type": "code", - "execution_count": null, + "execution_count": 24, "id": "71bbb913-7d7a-4bb6-b775-3fbc8e7e1f35", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "ReturnStatus(Code.DONE, None)" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "exe.status" + "ret" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 25, "id": "4bf2df15-31dc-474c-b3df-f7c32b0fdaf2", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([4.78924238, 4.51769267, 4.25194477, 3.99187529, 3.7373637 ,\n", + " 3.48829227, 3.244546 , 3.00601254, 2.77258214, 2.54414756])" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "exe.output[0].energies[:10]" + "output.energies[:10]" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "id": "eb0a2daf-9dab-4174-bfee-0cd1ef8c474e", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "exe.output[0].plot()" + "output.plot()" ] } ], diff --git a/notebooks/tinybase/Basic.ipynb b/notebooks/tinybase/Basic.ipynb index dfb089343..c0e4b62c2 100644 --- a/notebooks/tinybase/Basic.ipynb +++ b/notebooks/tinybase/Basic.ipynb @@ -10,17 +10,38 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "id": "fd11c1fd-6b5b-4739-ad10-9ebe47c0db49", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/poul/pyiron/contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n", + " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "cfc617d25c58458ab02e487bed85e341", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from pyiron_contrib.tinybase.task import AbstractTask, FunctionTask, SeriesTask, LoopTask" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "95594ff4-2f77-49c2-b4a2-467268ecac00", "metadata": {}, "outputs": [], @@ -30,7 +51,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "88b1b600-28e0-4ad9-82d6-b2bd993efbda", "metadata": {}, "outputs": [], @@ -41,7 +62,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "e3d8cf33-1f39-4ef9-b92c-2dfd43cf4dd3", "metadata": {}, "outputs": [], @@ -67,7 +88,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 9, "id": "9f2f3102-d15c-470a-b38c-f8084c9535ec", "metadata": {}, "outputs": [], @@ -85,7 +106,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 10, "id": "e125f49c-257b-4a24-bc81-83fe345d1dcf", "metadata": {}, "outputs": [], @@ -95,7 +116,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 11, "id": "324f3c10-385e-4577-b089-c305f8203ca5", "metadata": {}, "outputs": [ @@ -109,7 +130,7 @@ "DataContainer([])" ] }, - "execution_count": 8, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -120,7 +141,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 12, "id": "6c1f5af7-f5e9-41d9-a849-bab0ebc7dd9f", "metadata": {}, "outputs": [ @@ -130,7 +151,7 @@ "[]" ] }, - "execution_count": 9, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -141,7 +162,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 13, "id": "e0afb76d-d1b7-4b42-925f-fb117d58025e", "metadata": {}, "outputs": [ @@ -151,7 +172,7 @@ "{}" ] }, - "execution_count": 10, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -162,7 +183,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 14, "id": "6a5c3235-9c6b-481f-b316-db7420d1ad43", "metadata": {}, "outputs": [], @@ -172,7 +193,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 15, "id": "4ade8d6a-6ce2-4f3a-b43d-71e1f87125bf", "metadata": {}, "outputs": [ @@ -182,7 +203,7 @@ "{'n': 10}" ] }, - "execution_count": 12, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -193,126 +214,112 @@ }, { "cell_type": "code", - "execution_count": 13, - "id": "a39cfb50-4ed6-49ab-8ffb-af236cf61153", - "metadata": {}, - "outputs": [], - "source": [ - "exe = f.run()" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "aa095e57-3d3c-4af5-9a94-eda6af34e2b3", - "metadata": {}, + "execution_count": 16, + "id": "da69a4cc-409a-4f51-b329-06a69ce8e7f5", + "metadata": { + "tags": [] + }, "outputs": [ { "data": { "text/plain": [ - "{'status': (ReturnStatus(Code.DONE, None),),\n", - " 'output': (,)}" + "(ReturnStatus(Code.DONE, None),\n", + " )" ] }, - "execution_count": 14, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "exe._run_machine._data" + "f.execute()" ] }, { - "cell_type": "code", - "execution_count": 15, - "id": "82d0ca07-12c2-4cd9-bba3-1f210a907b41", + "cell_type": "markdown", + "id": "e4c8370f-fa93-44b8-a5d5-6fbdc59e3f4b", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "144" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "exe.output[0].result" + "## We can use an executor to distribute the task to any compute resource" ] }, { - "cell_type": "code", - "execution_count": 16, - "id": "24994f4a-d5cd-4aad-857f-a33ddd0eaf23", + "cell_type": "markdown", + "id": "aefcf8f6-fa1e-4902-aeff-7b3060e8290c", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(1.0841432279994478, 3.1526011298410594e-05)" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "exe._run_time, exe._collect_time" + "### Directly in the foreground" ] }, { - "cell_type": "markdown", - "id": "a6cbcd0e-2a04-4e93-add5-0f423fc1fa69", - "metadata": {}, + "cell_type": "code", + "execution_count": 18, + "id": "9bf053ed-14a1-4d05-80df-d5e135f2722f", + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ - "## We don't actually have to use an executor if we just want a result" + "exe = Executor().submit([f])" ] }, { "cell_type": "code", - "execution_count": 17, - "id": "a622ef14-4543-4cc1-bdfe-3625052f9b04", - "metadata": {}, + "execution_count": 19, + "id": "ab2584f3-4c66-4573-b3ab-265af626f5a5", + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "f = FunctionTask(calc_fib)" + "exe.run()" ] }, { "cell_type": "code", - "execution_count": 18, - "id": "b0a0ff5d-6c5f-4c66-bc71-a9de5bf58220", - "metadata": {}, - "outputs": [], + "execution_count": 20, + "id": "9b995df8-09a5-45a0-b03e-ffa2706db25c", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ReturnStatus(Code.DONE, None)" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "f.input.kwargs['n'] = 10" + "exe.status[0]" ] }, { "cell_type": "code", - "execution_count": 19, - "id": "b196759b-8cd6-45c7-8d3f-aad861b02edc", - "metadata": {}, + "execution_count": 21, + "id": "b7ee90e1-7d99-46eb-bc69-138b986e6ebd", + "metadata": { + "tags": [] + }, "outputs": [ { "data": { "text/plain": [ - "(ReturnStatus(Code.DONE, None),\n", - " )" + "144" ] }, - "execution_count": 19, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "f.execute()" + "exe.output[0].result" ] }, { @@ -320,12 +327,12 @@ "id": "8a3b6481-3605-44d3-8061-cb00c9fbcd34", "metadata": {}, "source": [ - "## Do the same but in the background" + "### Do the same but in the background" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 22, "id": "1e1b986e-9e00-41f2-86c2-945ff7818580", "metadata": {}, "outputs": [], @@ -335,7 +342,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 23, "id": "0b612150-f654-4995-8910-e46e766fdce2", "metadata": {}, "outputs": [], @@ -345,38 +352,31 @@ }, { "cell_type": "code", - "execution_count": 22, - "id": "bac98046-09c1-457c-881a-e31f03267788", - "metadata": {}, + "execution_count": 57, + "id": "097b7515-4875-4e22-8b7a-07594ce16204", + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "exe = f.run(how='background')" + "exe = BackgroundExecutor(max_threads=1).submit([f])" ] }, { "cell_type": "code", - "execution_count": 23, - "id": "ba09ae22-d2b4-41ba-8637-cb7f0fb3bfe9", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{}" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": 58, + "id": "b334ac7e-35ae-4160-b6cf-96fa8672975a", + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ - "exe._run_machine._data" + "exe.run()" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 59, "id": "0d2f427a-21e1-449e-a8cc-c2296bff6c10", "metadata": {}, "outputs": [ @@ -386,7 +386,7 @@ "" ] }, - "execution_count": 24, + "execution_count": 59, "metadata": {}, "output_type": "execute_result" } @@ -397,7 +397,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 60, "id": "a9631d5e-d46a-419c-a929-68ddd77487bb", "metadata": {}, "outputs": [], @@ -407,7 +407,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 61, "id": "408ffab0-70a1-4d08-9007-4d9f0513935d", "metadata": {}, "outputs": [ @@ -417,7 +417,7 @@ "927372692193078999176" ] }, - "execution_count": 26, + "execution_count": 61, "metadata": {}, "output_type": "execute_result" } @@ -431,64 +431,57 @@ "id": "c255f51a-950f-4e2e-a9b9-feb2f64f3ac5", "metadata": {}, "source": [ - "## Do the same but in the background as process" + "### Do the same but in the background as process" ] }, { "cell_type": "code", - "execution_count": 27, - "id": "1e2178c0-ee70-4ec0-8b4b-2c7c55873b43", + "execution_count": 36, + "id": "ef72a65d-9020-46f6-b9f2-6cc57d7d016b", "metadata": {}, "outputs": [], "source": [ - "fib_Task = FunctionTask(calc_fib)" + "f = FunctionTask(calc_fib)" ] }, { "cell_type": "code", - "execution_count": 28, - "id": "998c4724-c7ab-4fc0-8794-8b60da819090", + "execution_count": 37, + "id": "18607afd-8c43-4c88-8b40-5f758b1afab8", "metadata": {}, "outputs": [], "source": [ - "fib_Task.input.kwargs['n'] = 100" + "f.input.kwargs['n'] = 100" ] }, { "cell_type": "code", - "execution_count": 29, - "id": "7b66b215-33cd-425c-bb9b-62e3eaa0451e", - "metadata": {}, + "execution_count": 40, + "id": "418a3d1b-abba-4609-881c-109e1e73fcff", + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "exe = fib_Task.run(how='process')" + "exe = ProcessExecutor(max_processes=1).submit([f])" ] }, { "cell_type": "code", - "execution_count": 30, - "id": "fcdcbe0b-f44a-4c37-acd8-10eedf5b3aa2", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{}" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": 41, + "id": "71a470dd-f25a-484b-9fbf-c758968ffb83", + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ - "exe._run_machine._data" + "exe.run()" ] }, { "cell_type": "code", - "execution_count": 31, - "id": "5354db31-169d-4c7b-a8cc-6ddc3358b4c1", + "execution_count": 43, + "id": "0c5bf27b-31da-48ce-9344-4b24638f237a", "metadata": {}, "outputs": [ { @@ -497,7 +490,7 @@ "" ] }, - "execution_count": 31, + "execution_count": 43, "metadata": {}, "output_type": "execute_result" } @@ -508,8 +501,8 @@ }, { "cell_type": "code", - "execution_count": 32, - "id": "282a6ae8-e869-4ae2-bfea-8644ba693866", + "execution_count": 44, + "id": "9765f4fe-262d-43fd-a122-a0ed1f97bc29", "metadata": {}, "outputs": [], "source": [ @@ -518,8 +511,8 @@ }, { "cell_type": "code", - "execution_count": 33, - "id": "fd24c4c8-6b17-433f-ac28-a490911a3628", + "execution_count": 45, + "id": "21f5ae38-f3e7-4f79-a38e-ef2531d537a1", "metadata": {}, "outputs": [ { @@ -528,7 +521,7 @@ "927372692193078999176" ] }, - "execution_count": 33, + "execution_count": 45, "metadata": {}, "output_type": "execute_result" } @@ -537,6 +530,27 @@ "exe.output[0].result" ] }, + { + "cell_type": "code", + "execution_count": 46, + "id": "760de692-42d0-4827-abf5-8f3afaf1a2b2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe._run_machine.state" + ] + }, { "cell_type": "markdown", "id": "85ec26e2-db1f-4858-a3ab-b7955e85e572", @@ -547,7 +561,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 47, "id": "e2fed9f1-590b-4ab5-9922-a126444e6169", "metadata": {}, "outputs": [], @@ -557,7 +571,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 48, "id": "fdfc8943-8c0b-4bc6-98f0-71a64b3fae27", "metadata": {}, "outputs": [], @@ -576,7 +590,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 49, "id": "dd709cfa-775f-41c1-a015-7e0647ec3d27", "metadata": { "scrolled": true, @@ -584,32 +598,32 @@ }, "outputs": [], "source": [ - "exe = Executor(tasks)\n", + "exe = Executor().submit(tasks)\n", "exe.run()" ] }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 50, "id": "0ac1b35a-b130-4330-bf20-a1222bdc6103", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "(,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " )" + "(,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " )" ] }, - "execution_count": 52, + "execution_count": 50, "metadata": {}, "output_type": "execute_result" } @@ -620,7 +634,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 51, "id": "1ef8d9d6-e5dc-4db1-9e20-7181321f07ce", "metadata": {}, "outputs": [ @@ -630,7 +644,7 @@ "8" ] }, - "execution_count": 53, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -649,7 +663,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 62, "id": "25fe617c-ae8e-4b83-bf58-b790441a1126", "metadata": { "scrolled": true, @@ -657,13 +671,13 @@ }, "outputs": [], "source": [ - "exe = ProcessExecutor(tasks)\n", + "exe = ProcessExecutor(max_processes=4).submit(tasks)\n", "exe.run()" ] }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 63, "id": "19e5d3e8-6779-4c36-a636-2d8cd549e99c", "metadata": {}, "outputs": [], @@ -673,7 +687,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 64, "id": "66feb98b-3f99-4bfb-9bb5-cccaf26d009b", "metadata": {}, "outputs": [ @@ -692,7 +706,7 @@ " ReturnStatus(Code.DONE, None)]" ] }, - "execution_count": 56, + "execution_count": 64, "metadata": {}, "output_type": "execute_result" } @@ -703,26 +717,26 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 65, "id": "fbb40611-9f53-479e-854c-82c8c99a8070", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ]" + "[,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ]" ] }, - "execution_count": 57, + "execution_count": 65, "metadata": {}, "output_type": "execute_result" } @@ -733,7 +747,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 66, "id": "250f9c2d-5c71-4ddb-a94e-fd42f42cbeff", "metadata": {}, "outputs": [ @@ -743,7 +757,7 @@ "55" ] }, - "execution_count": 58, + "execution_count": 66, "metadata": {}, "output_type": "execute_result" } @@ -762,7 +776,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 67, "id": "3dba0814-6a50-41f9-a78f-040014fdc140", "metadata": {}, "outputs": [], @@ -772,7 +786,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 68, "id": "52aae339-ebad-4621-b2e0-c55d4fea3d1b", "metadata": {}, "outputs": [], @@ -782,7 +796,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 69, "id": "e10f7ee9-98db-48c7-affd-465c2011f7b1", "metadata": {}, "outputs": [], @@ -792,7 +806,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 70, "id": "b7e58b55-b4f5-4e2a-aef5-f4e080e4d50c", "metadata": {}, "outputs": [], @@ -803,17 +817,17 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 71, "id": "b4b2212a-64df-4284-834d-8836c9a59b70", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 63, + "execution_count": 71, "metadata": {}, "output_type": "execute_result" } @@ -824,7 +838,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 72, "id": "af337125-c4fe-497d-9374-b2d9301abe08", "metadata": {}, "outputs": [], @@ -834,7 +848,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 73, "id": "810a17bb-9f5d-4c50-9665-fa2f93070d60", "metadata": {}, "outputs": [], @@ -844,7 +858,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 74, "id": "4af47287-ab42-4cb4-8e65-c6efb7982ab4", "metadata": {}, "outputs": [ @@ -854,7 +868,7 @@ "ReturnStatus(Code.DONE, None)" ] }, - "execution_count": 67, + "execution_count": 74, "metadata": {}, "output_type": "execute_result" } @@ -865,17 +879,17 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 75, "id": "705637d8-8da7-4429-ae6f-5401fc15cc9e", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "12.0" + "144" ] }, - "execution_count": 68, + "execution_count": 75, "metadata": {}, "output_type": "execute_result" } @@ -902,7 +916,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 76, "id": "b9807c98-6df8-450f-a8dd-1a53cb4ded35", "metadata": {}, "outputs": [], @@ -912,7 +926,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 77, "id": "ac2b9aa8-c118-4a1a-bf8b-96d6853b9be6", "metadata": {}, "outputs": [], @@ -922,7 +936,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 78, "id": "ef092015-5756-409a-bd1a-a31793c0b2b8", "metadata": {}, "outputs": [], @@ -932,49 +946,41 @@ }, { "cell_type": "code", - "execution_count": 73, - "id": "91a3d26f-d1fc-44a9-b06d-a9c452dfb3db", - "metadata": {}, + "execution_count": 80, + "id": "10b67618-f56e-4348-9fdc-35514d0e83a4", + "metadata": { + "tags": [] + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "0.9953802852390651\n", - "0.4979426757738342\n", - "0.7496647591996423\n", - "0.9064905781297882\n", - "0.15763598102657594\n", - "0.5111053249038157\n", - "0.8777179700937587\n", - "0.6172844516713738\n", - "0.025877138756273066\n" + "0.6362656980328528\n", + "0.9334281883854404\n", + "0.6924270609164432\n", + "0.6860250441744892\n", + "0.022525309153174855\n", + "0.7312372220390315\n", + "0.48944745224752595\n", + "0.4136240195901667\n", + "0.2259678116613234\n" ] - } - ], - "source": [ - "exe = l.run()" - ] - }, - { - "cell_type": "code", - "execution_count": 74, - "id": "dbc8730e-9ebc-403b-9987-0de04e1f77f3", - "metadata": {}, - "outputs": [ + }, { "data": { "text/plain": [ - "(ReturnStatus(Code.DONE, None),)" + "(ReturnStatus(Code.DONE, None),\n", + " )" ] }, - "execution_count": 74, + "execution_count": 80, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "exe.status" + "l.execute()" ] }, { @@ -987,7 +993,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 89, "id": "6c251bfa-e8cf-4e1a-990d-451ebb53f713", "metadata": {}, "outputs": [], @@ -997,7 +1003,7 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 90, "id": "563c7fe1-b96f-463c-8903-50f054c831f6", "metadata": {}, "outputs": [], @@ -1007,23 +1013,22 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 102, "id": "10130bfd-636f-4771-b30b-4648a8822f04", "metadata": {}, "outputs": [], "source": [ "l.input.control_with(\n", - " condition=lambda task, output, scratch: output.result < .05,\n", + " condition=lambda task, output, scratch: output.result < .15,\n", " restart=lambda output, input, scratch: print(output.result)\n", ")" ] }, { "cell_type": "code", - "execution_count": 78, - "id": "f875b6c9-8cd1-4e6b-9ec8-16b93b6e7f64", + "execution_count": 107, + "id": "e65a16c1-40b4-4aa6-b382-c38405edd41e", "metadata": { - "scrolled": true, "tags": [] }, "outputs": [ @@ -1031,70 +1036,28 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.3510052419168129\n", - "0.9583658097014569\n", - "0.3822386305905213\n", - "0.9446397962657214\n", - "0.5190580019824569\n", - "0.9004145100900804\n", - "0.1214505719318919\n", - "0.15782601335162105\n", - "0.2467458443276147\n", - "0.32779186092001633\n", - "0.3545881045937541\n", - "0.2742999138135499\n", - "0.1346229590051038\n", - "0.6519388412108668\n", - "0.5656985488590841\n" + "0.4416017514257269\n", + "0.3160638768343853\n", + "0.20690423045422135\n", + "0.5952022105132233\n", + "0.3844701289093476\n", + "0.996852574386064\n" ] - } - ], - "source": [ - "exe = l.run()" - ] - }, - { - "cell_type": "code", - "execution_count": 79, - "id": "8df83822-0bbd-4157-8bb2-f6e93433eefc", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ReturnStatus(Code.DONE, None)" - ] - }, - "execution_count": 79, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exe.status[0]" - ] - }, - { - "cell_type": "code", - "execution_count": 80, - "id": "815ba264-9bdb-4758-ab20-92e3650bdbae", - "metadata": { - "tags": [] - }, - "outputs": [ + }, { "data": { "text/plain": [ - "0.013353255375349593" + "(ReturnStatus(Code.DONE, None),\n", + " )" ] }, - "execution_count": 80, + "execution_count": 107, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "exe.output[0].result" + "l.execute()" ] } ], diff --git a/notebooks/tinybase/TinyJob.ipynb b/notebooks/tinybase/TinyJob.ipynb index e7e0a1904..ed273aee4 100644 --- a/notebooks/tinybase/TinyJob.ipynb +++ b/notebooks/tinybase/TinyJob.ipynb @@ -27,7 +27,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "ce0f06e235fe4423b82c302dc66a5b06", + "model_id": "b404f9ce2d094b63a28e9e902bb1f18c", "version_major": 2, "version_minor": 0 }, @@ -193,7 +193,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 19, "id": "1e21980e-14a6-4578-b4b9-8195a74a3593", "metadata": { "tags": [] @@ -205,7 +205,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 20, "id": "18e6de26-308c-46ae-9672-b2db43447ea5", "metadata": { "tags": [] @@ -218,7 +218,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 21, "id": "72848cd2-fd51-4ad8-b56e-ca686074bb26", "metadata": { "tags": [] @@ -233,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 22, "id": "56c0e73a-c42b-4814-a25a-e6974fea3d00", "metadata": { "tags": [] @@ -243,8 +243,18 @@ "name": "stderr", "output_type": "stream", "text": [ - "INFO:root:Job already finished!\n" + "DEBUG:h5py._conv:Creating converter from 5 to 3\n" ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -253,7 +263,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 23, "id": "49ccfe01-7b7e-4615-bf43-21c1bbffec66", "metadata": { "tags": [] @@ -262,7 +272,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "2496fdc11cfb4cf788b9a98f243aabcc", + "model_id": "e0c1174050024971b55df1bf3f7d89a1", "version_major": 2, "version_minor": 0 }, @@ -288,7 +298,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 24, "id": "049f056e-47ff-4c2f-9e85-612744af15a8", "metadata": { "tags": [] @@ -300,7 +310,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 25, "id": "1137a899-b00b-4ce4-92df-23a4bbcf7aa8", "metadata": { "tags": [] @@ -312,7 +322,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 26, "id": "3a8cda32-df2e-4884-8cfd-84e438c5be69", "metadata": { "tags": [] @@ -326,7 +336,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "id": "d4b81c3f-4667-4b99-a2b3-08c7ee7e2c82", "metadata": { "tags": [] diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index 1b8406015..e4b26d8a3 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -119,15 +119,19 @@ def from_attributes(cls, name, *attrs, module=None, bases=(), **default_attrs): T.__module__ = module return T - def transfer(self, other): - """ - Copy the contents of another - """ - if isinstance(self, other.__class__): - self.storage.update(other.storage) - else: + def take(self, other: 'AbstractContainer'): + # TODO: think hard about variance of types + if not isinstance(self, type(other)): raise TypeError("Must pass a superclass to transfer from!") + mro_iter = {k: v for c in type(other).__mro__ for k, v in c.__dict__.items()} + for name, attr in mro_iter.items(): + if isinstance(attr, StorageAttribute): + setattr(self, name, getattr(other, name)) + + def put(self, other: 'AbstractContainer'): + other.take(self) + class AbstractInput(AbstractContainer, abc.ABC): def check_ready(self): diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index 1a0ccb60d..7352cff60 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -1,11 +1,12 @@ import abc import enum from collections import defaultdict -from typing import Union +from typing import Union, List import time - import logging +from pyiron_contrib.tinybase.task import AbstractTask, TaskGenerator + class RunMachine: class Code(enum.Enum): @@ -50,7 +51,7 @@ def step(self, state: Union[str, Code, None] = None, **kwargs): self._callbacks.get(self._state, lambda: None)() -class Executor: +class ExecutionContext: def __init__(self, tasks): self._tasks = tasks @@ -129,6 +130,24 @@ def status(self): def output(self): return self._run_machine._data["output"] +class Executor: + + def submit(self, tasks: List[AbstractTask]) -> ExecutionContext: + return ExecutionContext(tasks) + + def run(self, gen: TaskGenerator): + gen = iter(gen) + tasks = next(gen) + while True: + exe = self.submit(tasks) + exe.run() + exe.wait() + try: + tasks = gen.send(list(zip(exe.status, exe.output))) + except StopIteration as stop: + ret, output = stop.args[0] + break + return ret, output from concurrent.futures import ( ThreadPoolExecutor, @@ -140,18 +159,12 @@ def output(self): def run_task(task): return task.execute() -class FuturesExecutor(Executor, abc.ABC): - - _FuturePoolExecutor: FExecutor = None - - # poor programmer's abstract attribute check - def __init_subclass__(cls): - if cls._FuturePoolExecutor is None: - raise TypeError(f"Subclass {cls} of FuturesExecutor does not define 'FuturePoolExecutor'!") +class FuturesExecutionContext(ExecutionContext): - def __init__(self, tasks, max_tasks=None): + def __init__(self, pool, tasks): super().__init__(tasks=tasks) - self._max_tasks = max_tasks if max_tasks is not None else 4 + self._pool = pool + # self._max_tasks = max_tasks if max_tasks is not None else 4 self._done = 0 self._futures = {} self._status = {} @@ -180,22 +193,31 @@ def _check_finish(self, log=False): ) def _run_running(self): - if len(self._futures) < len(self.tasks): - pool = self._FuturePoolExecutor(max_workers=self._max_tasks) - try: - for i, task in enumerate(self.tasks): - future = pool.submit(run_task, task) - self._futures[future] = task - self._index[task] = i - future.add_done_callback(self._process_future) - finally: - # with statement doesn't allow me to put wait=False, so I gotta do it here with try/finally. - pool.shutdown(wait=False) + if len(self._futures) == 0: + for i, task in enumerate(self.tasks): + future = self._pool.submit(run_task, task) + self._futures[future] = task + self._index[task] = i + future.add_done_callback(self._process_future) else: logging.info("Some tasks are still executing!") -class BackgroundExecutor(FuturesExecutor): - _FuturePoolExecutor = ThreadPoolExecutor - -class ProcessExecutor(FuturesExecutor): - _FuturePoolExecutor = ProcessPoolExecutor +class BackgroundExecutor(Executor): + def __init__(self, max_threads): + self._max_threads = max_threads + self._pool = None + + def submit(self, tasks): + if self._pool is None: + self._pool = ThreadPoolExecutor(max_workers=self._max_threads) + return FuturesExecutionContext(self._pool, tasks) + +class ProcessExecutor(Executor): + def __init__(self, max_processes): + self._max_processes = max_processes + self._pool = None + + def submit(self, tasks): + if self._pool is None: + self._pool = ProcessPoolExecutor(max_workers=self._max_processes) + return FuturesExecutionContext(self._pool, tasks) diff --git a/pyiron_contrib/tinybase/job.py b/pyiron_contrib/tinybase/job.py index 3c9eb1a11..f0220fb0f 100644 --- a/pyiron_contrib/tinybase/job.py +++ b/pyiron_contrib/tinybase/job.py @@ -46,9 +46,9 @@ class TinyJob(Storable, abc.ABC): """ _executors = { - 'foreground': Executor, - 'background': BackgroundExecutor, - 'process': ProcessExecutor + 'foreground': Executor(), + 'background': BackgroundExecutor(max_threads=4), + 'process': ProcessExecutor(max_processes=4) } def __init__(self, project: ProjectInterface, job_name: str): @@ -157,7 +157,7 @@ def run(self, how='foreground') -> Optional[Executor]: :class:`.Executor`: the executor that is running the task or nothing. """ if self._id is None or self.project.database.get_item(self.id).status == "ready": - exe = self._executor = self._executors[how](tasks=[self.task]) + exe = self._executor = self._executors[how].submit(tasks=[self.task]) self._setup_executor_callbacks() exe.run() return exe diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index 493d69e80..4b882ebc4 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -4,7 +4,7 @@ ) from pyiron_contrib.tinybase.task import ( AbstractTask, - ListTask, + ListTaskGenerator, ListInput, ReturnStatus ) @@ -69,19 +69,17 @@ def _get_structure(self, frame, wrap_atoms=True): s.set_cell(s.get_cell() * (self.equilibrium_volume/s.get_volume())**(1/3)) return s -class MurnaghanTask(ListTask): +class MurnaghanTask(ListTaskGenerator): def _get_input(self): return MurnaghanInput() def _get_output(self): - return MurnaghanOutput() + out = MurnaghanOutput() + out.base_structure = self.input.structure + return out - def _execute(self, output): - output.base_structure = self.input.structure - return super()._execute(output) - - def _extract_output(self, output, step, node, ret, node_output): + def _extract_output(self, output, step, task, ret, task_output): if len(output.energies) == 0: output.energies = np.zeros(len(self.input.strains)) if len(output.volumes) == 0: diff --git a/pyiron_contrib/tinybase/project.py b/pyiron_contrib/tinybase/project.py index 518cb1f53..5233d3498 100644 --- a/pyiron_contrib/tinybase/project.py +++ b/pyiron_contrib/tinybase/project.py @@ -68,6 +68,9 @@ def remove(self, job_id): pr = self.open_location(entry.project) pr.remove_storage(entry.name) + #TODO: + # def copy_to/move_to across types of ProjectInterface + class ProjectAdapter(ProjectInterface): diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py index 8e61786e8..cc2a238c1 100644 --- a/pyiron_contrib/tinybase/task.py +++ b/pyiron_contrib/tinybase/task.py @@ -1,17 +1,12 @@ import abc from copy import deepcopy import enum -from typing import Optional, Callable +from typing import Optional, Callable, List, Generator, Tuple from pyiron_base.interfaces.object import HasStorage from pyiron_contrib.tinybase.storage import Storable, pickle_dump from .container import AbstractInput, AbstractOutput, StorageAttribute -from .executor import ( - Executor, - BackgroundExecutor, - ProcessExecutor -) class ReturnStatus: """ @@ -47,12 +42,6 @@ class AbstractTask(Storable, abc.ABC): their own :class:`.AbstractInput` and :class:`.AbstractOutput`. """ - _executors = { - 'foreground': Executor, - 'background': BackgroundExecutor, - 'process': ProcessExecutor - } - def __init__(self): self._input = None @@ -98,7 +87,7 @@ def _execute(self, output) -> Optional[ReturnStatus]: """ pass - def execute(self) -> ReturnStatus: + def execute(self) -> Tuple[ReturnStatus, AbstractOutput]: output = self._get_output() try: ret = self._execute(output) @@ -108,10 +97,14 @@ def execute(self) -> ReturnStatus: ret = ReturnStatus("aborted", msg=e) return ret, output - def run(self, how='foreground'): - exe = self._executors[how](tasks=[self]) - exe.run() - return exe + # TaskIterator Impl' + def __iter__(self) -> Generator[ + List['Task'], + List[Tuple[ReturnStatus, AbstractOutput]], + Tuple[ReturnStatus, AbstractOutput] + ]: + ret, *_ = yield [self] + return ret # Storable Impl' # We might even avoid this by deriving from HasStorage and put _input in there @@ -126,6 +119,37 @@ def _restore(cls, storage, version): task._input = pickle_load(storage["input"]) return task +class TaskGenerator(AbstractTask, abc.ABC): + """ + A generator that yields collections of tasks that can be executed in + parallel. + + In each iteration it will yield a list of tasks and accept a list of their + return status and outputs for the next iteration. When it is done, it will + return a final return status and output. + """ + + @abc.abstractmethod + def __iter__(self) -> Generator[List['Task'], + List[Tuple[ReturnStatus, AbstractOutput]], + Tuple[ReturnStatus, AbstractOutput]]: + pass + + def _execute(self, output): + gen = iter(self) + tasks = next(gen) + while True: + ret = [t.execute() for t in tasks] + try: + tasks = gen.send(ret) + except StopIteration as stop: + ret, out = stop.args[0] + output.take(out) + return ret + +# TaskGenerator.register(AbstractTask) +# assert + FunctionInput = AbstractInput.from_attributes("FunctionInput", args=list, kwargs=dict) FunctionOutput = AbstractOutput.from_attributes("FunctionOutput", "result") class FunctionTask(AbstractTask): @@ -149,16 +173,11 @@ def _get_output(self): def _execute(self, output): output.result = self._function(*self.input.args, **self.input.kwargs) -MasterInput = AbstractInput.from_attributes( - "MasterInput", - child_executor=lambda: Executor -) - -class ListInput(MasterInput, abc.ABC): +class ListInput(abc.ABC): """ - The input of :class:`.ListNode`. + The input of :class:`.ListTaskGenerator`. - To use it overload :meth:`._create_tasks()` here and subclass :class:`.ListNode` as well. + To use it overload :meth:`._create_tasks()` here and subclass :class:`.ListTaskGenerator` as well. """ @abc.abstractmethod @@ -170,7 +189,7 @@ def _create_tasks(self): """ pass -class ListTask(AbstractTask, abc.ABC): +class ListTaskGenerator(TaskGenerator, abc.ABC): """ A task that executes other tasks in parallel. @@ -180,6 +199,9 @@ class ListTask(AbstractTask, abc.ABC): def __init__(self): super().__init__() + def _get_input(self): + return ListInput() + @abc.abstractmethod def _extract_output(self, output, step, task, ret, task_output): """ @@ -195,22 +217,18 @@ def _extract_output(self, output, step, task, ret, task_output): """ pass - def _execute(self, output): + def __iter__(self): tasks = self.input._create_tasks() - exe = self.input.child_executor(tasks) - exe.run() - exe.wait() + returns, outputs = zip(*(yield tasks)) - for i, (task, ret, task_output) in enumerate(zip(tasks, exe.status, exe.output)): + output = self._get_output() + for i, (task, ret, task_output) in enumerate(zip(tasks, returns, outputs)): self._extract_output(output, i, task, ret, task_output) -SeriesInputBase = AbstractInput.from_attributes( - "SeriesInputBase", - tasks=list, - connections=list -) + return ReturnStatus("done"), output + -class SeriesInput(SeriesInputBase, MasterInput): +class SeriesInput(AbstractInput): """ Keeps a list of tasks and their connection functions to run sequentially. @@ -224,6 +242,10 @@ class SeriesInput(SeriesInputBase, MasterInput): ... input.my_param = output.my_result >>> task.input.first(MyNode()).then(MyNode(), transfer) """ + + tasks = StorageAttribute().type(list) + connections = StorageAttribute().type(list) + def check_ready(self): return len(self.tasks) == len(connections) + 1 @@ -258,7 +280,7 @@ def then(self, next_task, connection): self.connections.append(connection) return self -class SeriesTask(AbstractTask): +class SeriesTask(TaskGenerator): """ Executes a series of tasks sequentially. @@ -272,32 +294,19 @@ def _get_input(self): def _get_output(self): return self.input.tasks[-1]._get_output() - def _execute(self, output): - Exe = self.input.child_executor - - exe = Exe(self.input.tasks[:1]) - exe.run() - exe.wait() - ret = exe.status[0] + def __iter__(self): + (ret, out), *_ = yield [self.input.tasks[0]] if not ret.is_done(): - return ReturnStatus("aborted", ret) + return ReturnStatus("aborted", ret), None for task, connection in zip(self.input.tasks[1:], self.input.connections): - connection(task.input, exe.output[0]) - exe = Exe([task]) - exe.run() - exe.wait() - ret = exe.status[0] + connection(task.input, out) + (ret, out), *_ = yield [self.input.tasks[0]] if not ret.is_done(): - return ReturnStatus("aborted", ret) + return ReturnStatus("aborted", ret), None - output.transfer(exe.output[0]) + return ret, out -LoopInputBase = AbstractInput.from_attributes( - "LoopInput", - "control", - trace=bool, -) class LoopControl(HasStorage): def __init__(self, condition, restart): @@ -342,7 +351,7 @@ def _count_steps(self, output, input, scratch={}): return c >= self._steps -class LoopInput(LoopInputBase, MasterInput): +class LoopInput(AbstractInput): """ Input for :class:`~.LoopTask`. @@ -351,6 +360,8 @@ class LoopInput(LoopInputBase, MasterInput): control (:class:`.LoopControl`): encapsulates control flow of the loop """ + trace = StorageAttribute().type(bool).default(False) + def repeat(self, steps: int, restart: Optional[Callable[[AbstractOutput, AbstractInput, dict], None]] = None): """ Set up a loop control that loops for steps and calls restart in between. @@ -376,7 +387,7 @@ def control_with( """ self.control = LoopControl(condition, restart) -class LoopTask(AbstractTask): +class LoopTask(TaskGenerator): """ Generic task to loop over a given input task. """ @@ -387,18 +398,16 @@ def _get_input(self): def _get_output(self): return self.input.task._get_output() - def _execute(self, output): + def __iter__(self): task = deepcopy(self.input.task) control = deepcopy(self.input.control) + scratch = {} while True: - exe = self.input.child_executor([task]) - exe.run() - ret = exe.status[-1] - out = exe.output[-1] + (ret, out), *_ = yield [task] if not ret.is_done(): - return ReturnStatus("aborted", ret) + return ReturnStatus("aborted", ret), None if control.condition(task, out): break control.restart(out, task.input) - output.transfer(out) + return ret, out From cce873dcb1f28b3470af46b21fbec025342ac26b Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Wed, 3 May 2023 17:34:03 +0200 Subject: [PATCH 037/756] Do not use from_attributes in murn.py --- pyiron_contrib/tinybase/murn.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index 4b882ebc4..554129fa9 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -1,6 +1,7 @@ from pyiron_contrib.tinybase.container import ( AbstractOutput, StructureInput, + StorageAttribute ) from pyiron_contrib.tinybase.task import ( AbstractTask, @@ -18,13 +19,10 @@ from pyiron_atomistics.atomistics.structure.has_structure import HasStructure -MurnaghanInputBase = StructureInput.from_attributes( - "MurnaghanInputBase", - "strains", - "task" -) +class MurnaghanInput(StructureInput, ListInput): + strains = StorageAttribute() + task = StorageAttribute() -class MurnaghanInput(MurnaghanInputBase, ListInput): def check_ready(self): structure_ready = self.structure is not None strain_ready = len(self.strains) > 0 @@ -45,14 +43,11 @@ def _create_tasks(self): tasks.append(n) return tasks -MurnaghanOutputBase = AbstractOutput.from_attributes( - "MurnaghanOutputBase", - 'base_structure', - volumes=list, - energies=list -) +class MurnaghanOutput(AbstractOutput, HasStructure): + base_structures = StorageAttribute() + volumes = StorageAttribute().type(list) + energies = StorageAttribute().type(list) -class MurnaghanOutput(MurnaghanOutputBase, HasStructure): def plot(self): plt.plot(self.volumes, self.energies) From 431bfc86632426833d6ecdd5f0ac708cc526cdb9 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Wed, 3 May 2023 17:35:47 +0200 Subject: [PATCH 038/756] Do not use from_attributes in task.py --- pyiron_contrib/tinybase/task.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py index cc2a238c1..ae1325aaa 100644 --- a/pyiron_contrib/tinybase/task.py +++ b/pyiron_contrib/tinybase/task.py @@ -150,8 +150,13 @@ def _execute(self, output): # TaskGenerator.register(AbstractTask) # assert -FunctionInput = AbstractInput.from_attributes("FunctionInput", args=list, kwargs=dict) -FunctionOutput = AbstractOutput.from_attributes("FunctionOutput", "result") +class FunctionInput(AbstractInput): + args = StorageAttribute().type(list).default(list) + kwargs = StorageAttribute().type(dict).default(dict) + +class FunctionOutput(AbstractOutput): + result = StorageAttribute() + class FunctionTask(AbstractTask): """ A task that wraps a generic function. From 310775e285ee2e34d5bfe950a26cdb2bd69902c8 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Wed, 3 May 2023 17:42:30 +0200 Subject: [PATCH 039/756] Do not use from_attributes anywhere --- notebooks/tinybase/TinyJob.ipynb | 182 +++++++++++++++++++++------ pyiron_contrib/tinybase/ase.py | 4 +- pyiron_contrib/tinybase/container.py | 35 +++--- 3 files changed, 162 insertions(+), 59 deletions(-) diff --git a/notebooks/tinybase/TinyJob.ipynb b/notebooks/tinybase/TinyJob.ipynb index ed273aee4..d87a3148d 100644 --- a/notebooks/tinybase/TinyJob.ipynb +++ b/notebooks/tinybase/TinyJob.ipynb @@ -336,7 +336,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 27, "id": "d4b81c3f-4667-4b99-a2b3-08c7ee7e2c82", "metadata": { "tags": [] @@ -350,7 +350,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 30, "id": "e7494fee-d565-45e3-a819-c77ab0d2c7f6", "metadata": { "scrolled": true, @@ -358,10 +358,111 @@ }, "outputs": [ { - "name": "stderr", + "name": "stdout", "output_type": "stream", "text": [ - "INFO:root:Job already finished!\n" + " Step Time Energy fmax\n", + "LBFGS: 0 17:09:11 11.288146 189.5231\n", + "LBFGS: 1 17:09:11 1.168671 43.6957\n", + "LBFGS: 2 17:09:11 0.860403 38.6924\n", + "LBFGS: 3 17:09:11 0.362400 30.3554\n", + "LBFGS: 4 17:09:11 0.004806 24.0865\n", + "LBFGS: 5 17:09:11 -0.267437 19.0615\n", + "LBFGS: 6 17:09:11 -0.471646 15.0628\n", + "LBFGS: 7 17:09:11 -0.623506 11.8810\n", + "LBFGS: 8 17:09:11 -0.735237 9.3518\n", + "LBFGS: 9 17:09:11 -0.816458 7.3435\n", + "LBFGS: 10 17:09:11 -0.874705 5.7512\n", + "LBFGS: 11 17:09:11 -0.915849 4.4909\n", + "LBFGS: 12 17:09:11 -0.944435 3.4955\n", + "LBFGS: 13 17:09:11 -0.963943 2.7113\n", + "LBFGS: 14 17:09:11 -0.977006 2.0956\n", + "LBFGS: 15 17:09:11 -0.985585 1.6137\n", + "LBFGS: 16 17:09:11 -0.991109 1.2382\n", + "LBFGS: 17 17:09:11 -0.994598 0.9468\n", + "LBFGS: 18 17:09:11 -0.996763 0.7216\n", + "LBFGS: 19 17:09:11 -0.998083 0.5484\n", + "LBFGS: 20 17:09:11 -0.998876 0.4157\n", + "LBFGS: 21 17:09:11 -0.999347 0.3144\n", + "LBFGS: 22 17:09:11 -0.999623 0.2374\n", + "LBFGS: 23 17:09:11 -0.999784 0.1790\n", + "LBFGS: 24 17:09:11 -0.999877 0.1348\n", + "LBFGS: 25 17:09:11 -0.999930 0.1014\n", + "LBFGS: 26 17:09:11 -0.999960 0.0762\n", + "LBFGS: 27 17:09:11 -0.999977 0.0573\n", + "LBFGS: 28 17:09:11 -0.999987 0.0430\n", + "LBFGS: 29 17:09:11 -0.999993 0.0323\n", + "LBFGS: 30 17:09:11 -0.999996 0.0242\n", + "LBFGS: 31 17:09:11 -0.999998 0.0182\n", + "LBFGS: 32 17:09:11 -0.999999 0.0136\n", + "LBFGS: 33 17:09:11 -0.999999 0.0102\n", + "LBFGS: 34 17:09:11 -1.000000 0.0077\n", + "LBFGS: 35 17:09:11 -1.000000 0.0058\n", + "LBFGS: 36 17:09:11 -1.000000 0.0043\n", + "LBFGS: 37 17:09:11 -1.000000 0.0032\n", + "LBFGS: 38 17:09:11 -1.000000 0.0024\n", + "LBFGS: 39 17:09:11 -1.000000 0.0018\n", + "LBFGS: 40 17:09:11 -1.000000 0.0014\n", + "LBFGS: 41 17:09:11 -1.000000 0.0010\n", + "LBFGS: 42 17:09:11 -1.000000 0.0008\n", + "LBFGS: 43 17:09:11 -1.000000 0.0006\n", + "LBFGS: 44 17:09:11 -1.000000 0.0004\n", + "LBFGS: 45 17:09:11 -1.000000 0.0003\n", + "LBFGS: 46 17:09:11 -1.000000 0.0002\n", + "LBFGS: 47 17:09:11 -1.000000 0.0002\n", + "LBFGS: 48 17:09:11 -1.000000 0.0001\n", + "LBFGS: 49 17:09:11 -1.000000 0.0001\n", + "LBFGS: 50 17:09:11 -1.000000 0.0001\n", + "LBFGS: 51 17:09:11 -1.000000 0.0001\n", + "LBFGS: 52 17:09:11 -1.000000 0.0000\n", + "LBFGS: 53 17:09:11 -1.000000 0.0000\n", + "LBFGS: 54 17:09:11 -1.000000 0.0000\n", + "LBFGS: 55 17:09:11 -1.000000 0.0000\n", + "LBFGS: 56 17:09:11 -1.000000 0.0000\n", + "LBFGS: 57 17:09:11 -1.000000 0.0000\n", + "LBFGS: 58 17:09:11 -1.000000 0.0000\n", + "LBFGS: 59 17:09:11 -1.000000 0.0000\n", + "LBFGS: 60 17:09:11 -1.000000 0.0000\n", + "LBFGS: 61 17:09:11 -1.000000 0.0000\n", + "LBFGS: 62 17:09:11 -1.000000 0.0000\n", + "LBFGS: 63 17:09:11 -1.000000 0.0000\n", + "LBFGS: 64 17:09:11 -1.000000 0.0000\n", + "LBFGS: 65 17:09:11 -1.000000 0.0000\n", + "LBFGS: 66 17:09:11 -1.000000 0.0000\n", + "LBFGS: 67 17:09:11 -1.000000 0.0000\n", + "LBFGS: 68 17:09:11 -1.000000 0.0000\n", + "LBFGS: 69 17:09:11 -1.000000 0.0000\n", + "LBFGS: 70 17:09:11 -1.000000 0.0000\n", + "LBFGS: 71 17:09:11 -1.000000 0.0000\n", + "LBFGS: 72 17:09:11 -1.000000 0.0000\n", + "LBFGS: 73 17:09:11 -1.000000 0.0000\n", + "LBFGS: 74 17:09:11 -1.000000 0.0000\n", + "LBFGS: 75 17:09:11 -1.000000 0.0000\n", + "LBFGS: 76 17:09:11 -1.000000 0.0000\n", + "LBFGS: 77 17:09:11 -1.000000 0.0000\n", + "LBFGS: 78 17:09:11 -1.000000 0.0000\n", + "LBFGS: 79 17:09:11 -1.000000 0.0000\n", + "LBFGS: 80 17:09:11 -1.000000 0.0000\n", + "LBFGS: 81 17:09:11 -1.000000 0.0000\n", + "LBFGS: 82 17:09:11 -1.000000 0.0000\n", + "LBFGS: 83 17:09:11 -1.000000 0.0000\n", + "LBFGS: 84 17:09:11 -1.000000 0.0000\n", + "LBFGS: 85 17:09:11 -1.000000 0.0000\n", + "LBFGS: 86 17:09:11 -1.000000 0.0000\n", + "LBFGS: 87 17:09:11 -1.000000 0.0000\n", + "LBFGS: 88 17:09:11 -1.000000 0.0000\n", + "LBFGS: 89 17:09:11 -1.000000 0.0000\n", + "LBFGS: 90 17:09:11 -1.000000 0.0000\n", + "LBFGS: 91 17:09:11 -1.000000 0.0000\n", + "LBFGS: 92 17:09:11 -1.000000 0.0000\n", + "LBFGS: 93 17:09:11 -1.000000 0.0000\n", + "LBFGS: 94 17:09:11 -1.000000 0.0000\n", + "LBFGS: 95 17:09:11 -1.000000 0.0000\n", + "LBFGS: 96 17:09:11 -1.000000 0.0000\n", + "LBFGS: 97 17:09:11 -1.000000 0.0000\n", + "LBFGS: 98 17:09:11 -1.000000 0.0000\n", + "LBFGS: 99 17:09:11 -1.000000 0.0000\n", + "LBFGS: 100 17:09:11 -1.000000 0.0000\n" ] } ], @@ -373,7 +474,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 31, "id": "7c16a615-0913-4880-9694-2c125285babc", "metadata": { "tags": [] @@ -414,24 +515,36 @@ " \n", " \n", " 0\n", + " 3\n", + " pyiron\n", + " murn\n", + " 3\n", " 1\n", + " 3\n", + " /home/poul/pyiron/contrib/notebooks/tinybase/t...\n", + " finished\n", + " MurnaghanTask\n", + " \n", + " \n", + " 1\n", + " 4\n", " pyiron\n", " md\n", " 1\n", " 1\n", - " 1\n", + " 4\n", " /home/poul/pyiron/contrib/notebooks/tinybase/t...\n", " finished\n", " AseMDTask\n", " \n", " \n", - " 1\n", - " 2\n", + " 2\n", + " 5\n", " pyiron\n", " min\n", " 2\n", " 1\n", - " 2\n", + " 5\n", " /home/poul/pyiron/contrib/notebooks/tinybase/t...\n", " finished\n", " AseMinimizeTask\n", @@ -441,20 +554,23 @@ "" ], "text/plain": [ - " id username name jobtype_id project_id status_id \\\n", - "0 1 pyiron md 1 1 1 \n", - "1 2 pyiron min 2 1 2 \n", + " id username name jobtype_id project_id status_id \\\n", + "0 3 pyiron murn 3 1 3 \n", + "1 4 pyiron md 1 1 4 \n", + "2 5 pyiron min 2 1 5 \n", "\n", " location status \\\n", "0 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", "1 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", + "2 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", "\n", " type \n", - "0 AseMDTask \n", - "1 AseMinimizeTask " + "0 MurnaghanTask \n", + "1 AseMDTask \n", + "2 AseMinimizeTask " ] }, - "execution_count": 23, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -465,7 +581,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 32, "id": "3fb09d42-f800-46ee-9919-83180863e1ee", "metadata": { "tags": [] @@ -474,7 +590,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "b2e2ef42441543a0b3ad2b46cfec1386", + "model_id": "1331d57c32a7485c8540a76bcc5f0ed0", "version_major": 2, "version_minor": 0 }, @@ -508,7 +624,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 33, "id": "db691097-72c6-45a4-89b1-6ec16018c8b8", "metadata": { "tags": [] @@ -666,14 +782,14 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 34, "id": "23ce6822-b38b-41f3-9269-109dbb152ecf", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "67335e733e9042d3871308302a8348e2", + "model_id": "5b1c290394554c87b5c824cbc463ffed", "version_major": 2, "version_minor": 0 }, @@ -699,7 +815,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 35, "id": "d0f62439-3492-4392-ac2a-2b2545b85527", "metadata": {}, "outputs": [], @@ -709,7 +825,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 37, "id": "5c9ab533-cf97-49a1-8c4b-0e5e2f9758c2", "metadata": {}, "outputs": [], @@ -719,7 +835,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 38, "id": "253237f0-b338-470c-bc54-3c7400a757b7", "metadata": {}, "outputs": [], @@ -730,7 +846,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 39, "id": "c801093b-499e-48a7-8444-77602ed88a96", "metadata": {}, "outputs": [], @@ -740,7 +856,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 40, "id": "1e30b36e-11e6-47d1-836e-cffea7b73cdd", "metadata": {}, "outputs": [], @@ -750,7 +866,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 41, "id": "9920e4b7-8395-4fb9-96c3-792b044c4e3a", "metadata": {}, "outputs": [], @@ -760,25 +876,17 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 42, "id": "18b5305a-8950-44af-bc2e-c9734b059713", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "DEBUG:h5py._conv:Creating converter from 5 to 3\n" - ] - } - ], + "outputs": [], "source": [ "exe = murn.run()" ] }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 43, "id": "836bb2ec-4295-4a3c-b976-7a35d04aad36", "metadata": {}, "outputs": [ diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index cc5423b9c..2702d4668 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -19,8 +19,8 @@ from ase.optimize.gpmin.gpmin import GPMin -AseInput = AbstractInput.from_attributes('AseInput', 'calculator') - +class AseInput(AbstractInput): + calculator = StorageAttribute() class AseStaticInput(AseInput, StructureInput): pass diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index e4b26d8a3..1ccde8a69 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -8,6 +8,7 @@ from pyiron_base.interfaces.object import HasStorage from pyiron_atomistics.atomistics.structure.has_structure import HasStructure +from ase import Atoms import numpy as np import matplotlib.pyplot as plt @@ -137,34 +138,28 @@ class AbstractInput(AbstractContainer, abc.ABC): def check_ready(self): return True -StructureInput = AbstractInput.from_attributes("StructureInput", "structure") +class StructureInput(AbstractInput): + structure = StorageAttribute().type(Atoms) -MDInput = AbstractInput.from_attributes( - "MDInput", - "steps", - "timestep", - "temperature", - "output_steps", -) +class MDInput(AbstractInput): + steps = StorageAttribute().type(int) + timestep = StorageAttribute().type(float) + temperature = StorageAttribute().type(float) + output_steps = StorageAttribute().type(int) class AbstractOutput(AbstractContainer, abc.ABC): pass -EnergyOutput = AbstractOutput.from_attributes( - "EnergyOutput", - "energy_pot", -) +class EnergyOutput(AbstractOutput): + energy_pot = StorageAttribute().type(float) -MDOutputBase = AbstractOutput.from_attributes( - "MDOutputBase", - pot_energies=list, - kin_energies=list, - forces=list, - structures=list, -) +class MDOutput(HasStructure, EnergyOutput): -class MDOutput(HasStructure, MDOutputBase, EnergyOutput): + pot_energies = StorageAttribute().type(list) + kin_energies = StorageAttribute().type(list) + forces = StorageAttribute().type(list) + structures = StorageAttribute().type(list) def plot_energies(self): plt.plot(self.pot_energies - np.min(self.pot_energies), label='pot') From 810ff4c242b9e0c851aa0fa162ac031006bef152 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Thu, 4 May 2023 08:50:10 +0200 Subject: [PATCH 040/756] Add shell based tasks --- notebooks/tinybase/Shell.ipynb | 1129 +++++++++++++++++ .../resources/test/bin/run_hello_1.2.3.sh | 3 + .../resources/test/bin/run_hello_1.2.4.sh | 3 + pyiron_contrib/tinybase/shell.py | 143 +++ 4 files changed, 1278 insertions(+) create mode 100644 notebooks/tinybase/Shell.ipynb create mode 100755 notebooks/tinybase/resources/test/bin/run_hello_1.2.3.sh create mode 100755 notebooks/tinybase/resources/test/bin/run_hello_1.2.4.sh create mode 100644 pyiron_contrib/tinybase/shell.py diff --git a/notebooks/tinybase/Shell.ipynb b/notebooks/tinybase/Shell.ipynb new file mode 100644 index 000000000..f1ef971d2 --- /dev/null +++ b/notebooks/tinybase/Shell.ipynb @@ -0,0 +1,1129 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "9d7fa586-dc24-465a-94df-1c126e743e79", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/poul/pyiron/contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n", + " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d3226fe934d24c2baa541d98e899a823", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from pyiron_contrib.tinybase.shell import ShellTask" + ] + }, + { + "cell_type": "markdown", + "id": "48015102-7d5f-4079-98b8-a1a23937b9da", + "metadata": {}, + "source": [ + "# Echo Task" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f3ed1dd3-0a67-4177-9e3a-808fa93f1810", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh = ShellTask()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "561d682b-60ad-4420-9294-5dfc96af7c87", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh.input.command = 'echo'\n", + "sh.input.arguments = ['foo']" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "aa580b7e-67b0-4afa-8ab9-8ae1aa4baf08", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "ret, out = sh.execute()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e87ee2a1-8851-4e65-b6fd-03ccaa496229", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'foo\\n'" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.stdout" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0ec6cc97-dcdf-48d1-bd61-c0a79f47db24", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "''" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.stderr" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "78f61663-b405-4979-8970-c6d1049998c0", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh = ShellTask()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "a3e52d0a-43ff-4702-8447-f4a00c8d32bd", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh.input.command = 'pwd'\n", + "sh.input.arguments = []\n", + "sh.input.working_directory = '/home/poul'" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "932f18f3-6d39-49d5-b7ed-5a13e5421f1a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "ret, out = sh.execute()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "022a4387-5441-4181-86fa-60ae9baacaee", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'/home/poul\\n'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.stdout" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "431b11c3-4054-412f-ae2f-0c71044e95e1", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "''" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.stderr" + ] + }, + { + "cell_type": "markdown", + "id": "a4e5e274-a86c-41ce-bbaf-8de6b8253b9f", + "metadata": {}, + "source": [ + "# We can check on returncodes and change which trigger an error" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "1d811404-5720-4194-9d09-a5e2acd1eff6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh = ShellTask()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "281e6833-dacd-4622-b87f-8f25d2218a86", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh.input.command = 'sh'\n", + "sh.input.arguments = ['-c', 'echo foo; echo bar 1>&2; exit 2']" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "08ee6439-e617-4db1-a3a5-f7e0df73ac0a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "ret, out = sh.execute()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "f428a199-fb64-4b3b-9b65-f7f58e5e05b0", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ReturnStatus(Code.ABORTED, non-zero error code 2)" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ret" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "1e5ed4a5-cb51-45ff-b541-830476630dac", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.returncode" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "75eafdd9-c25e-4a57-b040-6b460fd5d06d", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'foo\\n'" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.stdout" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "07c7d569-3bf3-44c7-bfba-50ec28286b76", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'bar\\n'" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.stderr" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dd3d8a89-ec3d-4260-b53e-1d4d8df83a15", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "18c5432c-f6b1-49ca-a0ef-9f232102beb4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh = ShellTask()" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "22cb0952-2cd7-40b5-895c-58d41375903f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh.input.command = 'sh'\n", + "sh.input.arguments = ['-c', 'echo foo; echo bar 1>&2; exit 2']" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "5df20abb-b0ba-428d-850e-fc6654a92afa", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh.input.allowed_returncode = [0, 2]" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "b7bae225-5f1a-4f4b-a23d-9e0c84397b8c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "ret, out = sh.execute()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "5419b35e-1bfd-49d2-88d0-b9e58841f7d2", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ReturnStatus(Code.DONE, None)" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ret" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "5f5177ad-cd61-4fcc-96b9-d2ed701f828b", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.returncode" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "9dc082df-fa8e-4ab7-8116-9756e5fd5fe9", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'foo\\n'" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.stdout" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "8b4d800f-e22b-43ae-8f55-2c06ebf30741", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'bar\\n'" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.stderr" + ] + }, + { + "cell_type": "markdown", + "id": "a03726ab-245f-44f1-9b31-3eb7c5c108eb", + "metadata": {}, + "source": [ + "# We can manipulate the environment" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "aade0746-588e-4775-94c3-3f2e1da2ed55", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh = ShellTask()" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "cffaad98-6f8c-47a9-8584-fc038ba455bb", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh.input.command = 'sh'\n", + "sh.input.arguments = ['-c', 'echo $FOO; echo $BAR 1>&2']" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "825f86fb-7055-401f-be3a-d52414129d4f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh.input.environ['FOO'] = 42\n", + "sh.input.environ['BAR'] = 'ERROR!'" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "12bbd896-5f26-4e33-b382-796b7d4312a7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "ret, out = sh.execute()" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "625735ee-df96-4627-91ca-1fe9d389431e", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ReturnStatus(Code.DONE, None)" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ret" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "5eeb62b2-ce78-4864-8cf5-e168d6bf9eb7", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.returncode" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "d5bc1aa1-4579-45ce-ba47-20b731eec066", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'42\\n'" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.stdout" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "53dcc8b0-a8c4-4410-8990-78997dd1f9e9", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'ERROR!\\n'" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.stderr" + ] + }, + { + "cell_type": "markdown", + "id": "1fd2fae0-4dfb-42b2-8840-6dddc3404251", + "metadata": {}, + "source": [ + "# We can use the existing resource setup" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "9feaa502-d16a-4cac-bb36-e681c85a0a63", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.shell import ExecutablePathResolver, state" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "8c340f6c-c687-4461-9c33-f98b768773d6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "state.settings.resource_paths.insert(0, '/home/poul/pyiron/contrib/notebooks/tinybase/resources')" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "ab27d95c-3d7c-4710-9ff7-7de523086c6d", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['/home/poul/pyiron/contrib/notebooks/tinybase/resources',\n", + " '/home/poul/pyiron/contrib/notebooks/tinybase/resources',\n", + " '/home/poul/pyiron/contrib/scratch',\n", + " '/home/poul/micromamba/envs/contrib/share/pyiron']" + ] + }, + "execution_count": 67, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "state.settings.resource_paths" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "7e1bb029-a7b1-488c-8852-f0223c2e477a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "res = ExecutablePathResolver('test', 'hello')" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "9e9b280d-917c-4c11-8eee-140312f3455e", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['/home/poul/pyiron/contrib/notebooks/tinybase/resources/test/bin/run_hello_1.2.3.sh',\n", + " '/home/poul/pyiron/contrib/notebooks/tinybase/resources/test/bin/run_hello_1.2.4.sh',\n", + " '/home/poul/pyiron/contrib/notebooks/tinybase/resources/test/bin/run_hello_1.2.3.sh',\n", + " '/home/poul/pyiron/contrib/notebooks/tinybase/resources/test/bin/run_hello_1.2.4.sh']" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "res.list()" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "03822deb-ea98-4f07-93cf-b8af6028a856", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['1.2.4', '1.2.3']" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "res.list_versions()" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "c22c772c-b7c5-4e9e-9ddf-74da2bba1f87", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh = ShellTask()" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "37c7af44-b517-4f59-8e09-071e597d75cc", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh.input.command = res" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "4fd337b7-9241-4afb-a90f-5e11990f4e41", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "ret, out = sh.execute()" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "ce4eebc6-bad4-46d4-8598-8f706e1b25ba", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ReturnStatus(Code.DONE, None)" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ret" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "ad6f0c64-79bc-4f5c-b86f-fee016a32df8", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.returncode" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "393c3bf1-a41f-45a9-911f-8d7bbaf7d102", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'HelloWorld!\\n'" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.stdout" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "cdc3df94-c4c5-477b-833b-310c95436c56", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "''" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.stderr" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9576fb48-d8db-4430-b4e3-99d9c5d8a58f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "626cd697-f96c-46b2-b7ca-88beab3c1ff7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh = ShellTask()" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "f2cfd062-3b0f-4a48-a52a-305a9ad643e9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh.input.command = ExecutablePathResolver('test', 'hello', version='1.2.4')" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "573a508f-ead5-4177-85a6-19ed5b27b057", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "ret, out = sh.execute()" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "4197e3fd-5e3e-47bb-b49d-bac18d587d6c", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ReturnStatus(Code.DONE, None)" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ret" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "c6ebec7f-c8d7-41cf-8222-7b9829c38e19", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.returncode" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "efd51d34-3dc0-4a34-9dde-8a11261c5a7a", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'Hello World!\\n'" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.stdout" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c093939c-3081-4a41-8946-c6425124e3d4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "8e015724-bbea-4463-a43e-2e0b84c1ca2d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh = ShellTask()" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "7b86e8ee-29c1-4a99-bb7e-0d9564cb8e3a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh.input.command = ExecutablePathResolver('test', 'hello')" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "6d1d8fdb-3ca8-4184-9b55-f82ed9719a37", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['1.2.4', '1.2.3']" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sh.input.command.list_versions()" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "b276fa87-513f-41b9-86fa-39c54dcbb71e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sh.input.command.version = '1.2.4'" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "3a85fcd5-d80f-4197-8e8a-1b32c4f78ba5", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "ret, out = sh.execute()" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "03566ee2-7f64-48d6-9cc7-97be44bc41c8", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'Hello World!\\n'" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.stdout" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/tinybase/resources/test/bin/run_hello_1.2.3.sh b/notebooks/tinybase/resources/test/bin/run_hello_1.2.3.sh new file mode 100755 index 000000000..6330c81d6 --- /dev/null +++ b/notebooks/tinybase/resources/test/bin/run_hello_1.2.3.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo HelloWorld! diff --git a/notebooks/tinybase/resources/test/bin/run_hello_1.2.4.sh b/notebooks/tinybase/resources/test/bin/run_hello_1.2.4.sh new file mode 100755 index 000000000..0d02d92e1 --- /dev/null +++ b/notebooks/tinybase/resources/test/bin/run_hello_1.2.4.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo Hello World! diff --git a/pyiron_contrib/tinybase/shell.py b/pyiron_contrib/tinybase/shell.py new file mode 100644 index 000000000..59e9a4992 --- /dev/null +++ b/pyiron_contrib/tinybase/shell.py @@ -0,0 +1,143 @@ +import os +import subprocess +from glob import glob + +from pyiron_base.state import state + +from pyiron_contrib.tinybase.container import ( + AbstractInput, + AbstractOutput, + StorageAttribute +) + +from pyiron_contrib.tinybase.task import ( + AbstractTask, + ReturnStatus +) + +import os +if os.name == "nt": + EXE_SUFFIX = "bat" +else: + EXE_SUFFIX = "sh" + +class ExecutablePathResolver: + """ + Locates executables in pyiron resource folders. + + This expects executables to be located in folder structures like this + + {resource_folder}/{module}/bin/run_{code}_{version}.sh + + and be executable (on UNIX). + + If multiple executables are found for the same combination of `module`, + `code` and `version`, :meth:`.list()` returns all of them sorted first by + resource path (as in PYIRON_RESOURCE_PATH) and then alphabetically. + + If an executable exists that has `default` in its version and `version` is + `None`, the first such executable is picked for :meth:`.path()`. + + :meth:`.__str__` is overloaded to :meth:`.path()`. + """ + def __init__(self, module, code, version=None): + self._module = module + self._code = code + self._version = version + + def list(self, version=None): + """ + List all possible executables found. + + Returns: list of str + """ + if version is None: + version = self._version or "*" + alternatives = [] + for p in state.settings.resource_paths: + exe_path = f"run_{self._code}_{version}.{EXE_SUFFIX}" + bin_path = os.path.join(p, self._module, "bin", exe_path) + alternatives.extend(sorted(glob(bin_path))) + return alternatives + + def list_versions(self): + """ + List unique version strings found. + """ + exes = self.list(version="*") + def extract(p): + return os.path.splitext( + os.path.basename(p).split(f"run_{self._code}", maxsplit=1)[1] + )[0][1:] + return list(set(map(extract, exes))) + + @property + def version(self): + vers = self.list_versions() + for v in vers: + if 'default' in vers: + return v + return vers[0] + + @version.setter + def version(self, value): + vers = self.list_versions() + if value in vers: + self._version = value + else: + raise ValueError(f"Given version '{value}' not in {vers}!") + + def path(self): + """ + Returns a direct path where a executable has been found. + """ + exes = self.list() + if self._version is not None: + return exes[0] + for p in exes: + if 'default' in p: + return p + return exes[0] + + def __str__(self): + return self.path() + + +class ShellInput(AbstractInput): + command = StorageAttribute() + arguments = StorageAttribute().type(list).default(list) + environ = StorageAttribute().type(dict).default(dict) + working_directory = StorageAttribute().type(str) + allowed_returncode = StorageAttribute().type(list) + +class ShellOutput(AbstractOutput): + stdout = StorageAttribute() + stderr = StorageAttribute() + returncode = StorageAttribute().type(int) + +class ShellTask(AbstractTask): + + def _get_input(self): + return ShellInput() + + def _get_output(self): + return ShellOutput() + + def _execute(self, output): + environ = dict(os.environ) + environ.update({k: str(v) for k, v in self.input.environ.items()}) + proc = subprocess.run( + [str(self.input.command), *map(str, self.input.arguments)], + capture_output=True, + cwd=self.input.working_directory, + encoding='utf8', + env=environ + ) + output.stdout = proc.stdout + output.stderr = proc.stderr + output.returncode = proc.returncode + allowed_returncode = self.input.allowed_returncode + if allowed_returncode is None: + allowed_returncode = [0] + if proc.returncode not in allowed_returncode: + return ReturnStatus('aborted', f'non-zero error code {proc.returncode}') From b9b8dd760374bf7cf92fda979020ea3199f597b1 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Sun, 7 May 2023 21:56:50 +0200 Subject: [PATCH 041/756] Do not try to copy None in AbstractContainer.take/put --- pyiron_contrib/tinybase/container.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index 1ccde8a69..b2a572b98 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -128,7 +128,9 @@ def take(self, other: 'AbstractContainer'): mro_iter = {k: v for c in type(other).__mro__ for k, v in c.__dict__.items()} for name, attr in mro_iter.items(): if isinstance(attr, StorageAttribute): - setattr(self, name, getattr(other, name)) + a = getattr(other, name) + if a is not None: + setattr(self, name, a) def put(self, other: 'AbstractContainer'): other.take(self) From 4850b2cd189ed1fec0517567ed9b135ae34cc52b Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Sun, 7 May 2023 21:57:31 +0200 Subject: [PATCH 042/756] Add convenient methods for ReturnStatus --- pyiron_contrib/tinybase/task.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py index ae1325aaa..eee20d3eb 100644 --- a/pyiron_contrib/tinybase/task.py +++ b/pyiron_contrib/tinybase/task.py @@ -23,6 +23,22 @@ def __init__(self, code, msg=None): self.code = code if not isinstance(code, str) else ReturnStatus.Code(code) self.msg = msg + @classmethod + def done(cls, msg=None): + return cls(code=cls.Code.DONE, msg=msg) + + @classmethod + def aborted(cls, msg=None): + return cls(code=cls.Code.ABORTED, msg=msg) + + @classmethod + def warning(cls, msg=None): + return cls(code=cls.Code.WARNING, msg=msg) + + @classmethod + def not_converged(cls, msg=None): + return cls(code=cls.Code.NOT_CONVERGED, msg=msg) + def __repr__(self): return f"ReturnStatus({self.code}, {self.msg})" def __str__(self): From 44148a34dd27477e8a96cd7979cbfe744c6e96a2 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Sun, 7 May 2023 21:58:01 +0200 Subject: [PATCH 043/756] Make the try/except capture in AbstractTask optional --- pyiron_contrib/tinybase/task.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py index eee20d3eb..55d245e38 100644 --- a/pyiron_contrib/tinybase/task.py +++ b/pyiron_contrib/tinybase/task.py @@ -58,8 +58,9 @@ class AbstractTask(Storable, abc.ABC): their own :class:`.AbstractInput` and :class:`.AbstractOutput`. """ - def __init__(self): + def __init__(self, capture_exceptions=True): self._input = None + self._capture_exceptions=capture_exceptions @abc.abstractmethod def _get_input(self) -> AbstractInput: @@ -104,6 +105,8 @@ def _execute(self, output) -> Optional[ReturnStatus]: pass def execute(self) -> Tuple[ReturnStatus, AbstractOutput]: + if not self.input.check_ready(): + return ReturnStatus.aborted("Input not ready!") output = self._get_output() try: ret = self._execute(output) @@ -111,6 +114,8 @@ def execute(self) -> Tuple[ReturnStatus, AbstractOutput]: ret = ReturnStatus("done") except Exception as e: ret = ReturnStatus("aborted", msg=e) + if not self._capture_exceptions: + raise return ret, output # TaskIterator Impl' From 4d17283e9dbed32254990be214f16ff3b46abb6c Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Sun, 7 May 2023 21:59:00 +0200 Subject: [PATCH 044/756] Add kinetic energy and force outputs --- pyiron_contrib/tinybase/container.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index b2a572b98..2985a289b 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -156,6 +156,15 @@ class AbstractOutput(AbstractContainer, abc.ABC): class EnergyOutput(AbstractOutput): energy_pot = StorageAttribute().type(float) +class EnergyPotOutput(AbstractOutput): + energy_pot = StorageAttribute().type(float) + +class EnergyKinOutput(AbstractOutput): + energy_kin = StorageAttribute().type(float) + +class ForceOutput(AbstractOutput): + forces = StorageAttribute().type(np.ndarray) + class MDOutput(HasStructure, EnergyOutput): pot_energies = StorageAttribute().type(list) From f43186b453c5ba0c740f1eb489fcdaaf030c6806 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Sun, 7 May 2023 22:10:35 +0200 Subject: [PATCH 045/756] Rename EnergyOutput to EnergyPotOutput --- pyiron_contrib/tinybase/ase.py | 4 ++-- pyiron_contrib/tinybase/container.py | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index 2702d4668..77acc8ae6 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -3,7 +3,7 @@ StorageAttribute, StructureInput, MDInput, - EnergyOutput, + EnergyPotOutput, MDOutput ) from pyiron_contrib.tinybase.task import AbstractTask, ReturnStatus @@ -32,7 +32,7 @@ def _get_input(self): return AseStaticInput() def _get_output(self): - return EnergyOutput() + return EnergyPotOutput() def _execute(self, output): structure = self.input.structure diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index 2985a289b..290e087d8 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -153,9 +153,6 @@ class MDInput(AbstractInput): class AbstractOutput(AbstractContainer, abc.ABC): pass -class EnergyOutput(AbstractOutput): - energy_pot = StorageAttribute().type(float) - class EnergyPotOutput(AbstractOutput): energy_pot = StorageAttribute().type(float) @@ -165,7 +162,7 @@ class EnergyKinOutput(AbstractOutput): class ForceOutput(AbstractOutput): forces = StorageAttribute().type(np.ndarray) -class MDOutput(HasStructure, EnergyOutput): +class MDOutput(HasStructure, EnergyPotOutput): pot_energies = StorageAttribute().type(list) kin_energies = StorageAttribute().type(list) From 551248c7d0c652bacc3cbd65e2b2739a5aad468e Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Sun, 7 May 2023 22:33:36 +0200 Subject: [PATCH 046/756] Remove some type confusion in Murn --- notebooks/tinybase/ASE.ipynb | 1286 ++++++++++++++------------ pyiron_contrib/tinybase/container.py | 8 +- pyiron_contrib/tinybase/murn.py | 10 +- pyiron_contrib/tinybase/task.py | 3 - 4 files changed, 690 insertions(+), 617 deletions(-) diff --git a/notebooks/tinybase/ASE.ipynb b/notebooks/tinybase/ASE.ipynb index 10ca9a53e..c62d97cc8 100644 --- a/notebooks/tinybase/ASE.ipynb +++ b/notebooks/tinybase/ASE.ipynb @@ -63,7 +63,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "9ed5730af8334887a97ceca1e75f28c9", + "model_id": "cb10962527db4539a876c77ee57c6d35", "version_major": 2, "version_minor": 0 }, @@ -124,7 +124,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 8, "id": "a6af72cb-989b-46c3-a2b5-4d2b9c5fd1eb", "metadata": { "tags": [] @@ -136,7 +136,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 9, "id": "5b2a9d62-3f74-4acf-acb6-e72dcd984704", "metadata": { "tags": [] @@ -148,7 +148,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 10, "id": "1af70322-897e-487d-ba18-239ba5bfb7ba", "metadata": { "tags": [] @@ -160,7 +160,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 11, "id": "273902ef-03f3-4f68-8668-4e6c6055a302", "metadata": { "tags": [] @@ -170,10 +170,10 @@ "data": { "text/plain": [ "(ReturnStatus(Code.DONE, None),\n", - " )" + " )" ] }, - "execution_count": 42, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -215,7 +215,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 13, "id": "02cfe01b-0b24-4723-a79b-d41ffb146bf9", "metadata": { "tags": [] @@ -227,7 +227,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 14, "id": "466d1f9a-b707-4c05-a8af-5414d76bd8eb", "metadata": { "tags": [] @@ -240,7 +240,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 15, "id": "dfdfc027-1608-43ad-9d15-0c649986eb73", "metadata": { "tags": [] @@ -248,14 +248,14 @@ "outputs": [], "source": [ "md.input.steps = 100\n", - "md.input.timestep = 3\n", - "md.input.temperature = 600\n", + "md.input.timestep = 3.0\n", + "md.input.temperature = 600.0\n", "md.input.output_steps = 20" ] }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 16, "id": "a91f71b6-25f9-4977-b5b7-563a34f30016", "metadata": { "tags": [] @@ -268,7 +268,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 17, "id": "80155255-4dcf-48cb-9825-015da13d6ac0", "metadata": { "tags": [] @@ -280,7 +280,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 18, "id": "6f7aff4e-9e89-459b-843f-46a4d4139bcf", "metadata": { "tags": [] @@ -290,10 +290,10 @@ "data": { "text/plain": [ "{'status': [ReturnStatus(Code.DONE, None)],\n", - " 'output': []}" + " 'output': []}" ] }, - "execution_count": 52, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -304,7 +304,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 19, "id": "62ce8439-bf95-4818-b35c-b4e2ef649bd2", "metadata": { "tags": [] @@ -316,7 +316,7 @@ "" ] }, - "execution_count": 53, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -327,7 +327,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 20, "id": "5bcd1b68-6a48-4a08-92d4-143419071618", "metadata": { "tags": [] @@ -336,10 +336,10 @@ { "data": { "text/plain": [ - "10.619871525006602" + "10.869684777004295" ] }, - "execution_count": 54, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -350,7 +350,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 21, "id": "d21371e0-fa36-44bd-b7bf-a0092177ba17", "metadata": { "tags": [] @@ -359,10 +359,10 @@ { "data": { "text/plain": [ - "0.00010890099656535313" + "3.451299562584609e-05" ] }, - "execution_count": 55, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -373,7 +373,51 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 22, + "id": "412aed30-6adf-4a54-8496-bea647bb520e", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[-303.20813267693006,\n", + " -303.20813267693006,\n", + " -298.21204372431737,\n", + " -299.88503154534726,\n", + " -300.931999137925,\n", + " -299.44502827869934,\n", + " -299.9918148425856,\n", + " -300.1553044042918,\n", + " -301.1881436730182,\n", + " -300.28494441522173,\n", + " -300.3765641855429,\n", + " -299.5227415338809,\n", + " -300.1677455878805,\n", + " -300.5454274254439,\n", + " -299.6345772520824,\n", + " -300.22105101161634,\n", + " -299.54260700848215,\n", + " -299.30378229965436,\n", + " -301.70385283576184,\n", + " -300.2260190182359,\n", + " -300.1547113482648,\n", + " -299.89127173589293]" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exe.output[0].pot_energies" + ] + }, + { + "cell_type": "code", + "execution_count": 23, "id": "9e06cd6d-e0f7-40dd-93f2-777f86ffe2eb", "metadata": { "tags": [] @@ -381,7 +425,7 @@ "outputs": [ { "data": { - "image/png": "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HTp0QN26dbFixQqMGDFCxchubc888wwOHTqEbdu23fQ7T3ptuf2IRY0aNaDT6W7K7JKTk2/KAMm1BAYGolWrVjh9+rTaoVA5rKt3+DpzT2FhYahbty5fayp69tln8ffff2Pjxo2oU6eO7eee+Npy+8TCx8cH0dHRWLt2bbGfr127Ft26dVMpKqoIk8mE48ePIywsTO1QqBxRUVEIDQ0t9jrLzc3F5s2b+TpzA1evXkVcXBxfayqQJAnPPPMMFi1ahA0bNiAqKqrY7z3xteURUyGTJ0/G6NGj0aFDB3Tt2hWzZs1CbGwsJkyYoHZoVMRLL72EoUOHIjIyEsnJyXj//feRlpaGMWPGqB0aAcjIyMCZM2ds358/fx4HDhxAtWrVEBkZiUmTJuGDDz5Ao0aN0KhRI3zwwQcICAjAQw89pGLUt6aynqtq1arh7bffxj333IOwsDBcuHABU6dORY0aNXD33XerGPWtaeLEiViwYAGWLl2KoKAg28iEwWCAv78/NBqN5722VF2TIqOvv/5aqlu3ruTj4yO1b9/etpSHXMf9998vhYWFSd7e3lJ4eLg0YsQI6ejRo2qHRRYbN26UANx0GTNmjCRJYlncW2+9JYWGhkq+vr5Sr169pMOHD6sb9C2qrOcqKytLGjhwoFSzZk3J29tbioyMlMaMGSPFxsaqHfYtqaTnCYA0d+5c22087bXFbdOJiIhINm5fY0FERESug4kFERERyYaJBREREcmGiQURERHJhokFERERyYaJBREREcmGiQURERHJhokFERERyYaJBREREcmGiQURERHJhokFERERyYaJBREREcnm//UUcyxKxOPjAAAAAElFTkSuQmCC\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -396,7 +440,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 24, "id": "bb70a653-6231-4f4e-9bbe-279811acc895", "metadata": { "tags": [] @@ -405,7 +449,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e166420cb3a24a529f16a00f05ffa30d", + "model_id": "ca55e9934a7f4ddfbfcba49067007e5d", "version_major": 2, "version_minor": 0 }, @@ -431,7 +475,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 25, "id": "f816e2af-0455-4e05-9c39-2e9f615d8f34", "metadata": { "tags": [] @@ -443,7 +487,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 26, "id": "22314020-8f48-487b-a765-229a77d79a2f", "metadata": { "tags": [] @@ -455,7 +499,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 27, "id": "ff411a05-82e1-4581-b06e-ab2fd7e0be3b", "metadata": { "tags": [] @@ -467,7 +511,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 28, "id": "5574f0d5-d800-472a-9418-8c6ccc1e555b", "metadata": { "tags": [] @@ -480,7 +524,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 29, "id": "9e02d6dd-0fa6-4dd6-a7ab-3e648958eb20", "metadata": { "tags": [] @@ -489,7 +533,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "adddb5640a9d43e396a2f89877656552", + "model_id": "e14d2befd7114406b7faf407a16b32c8", "version_major": 2, "version_minor": 0 }, @@ -507,7 +551,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 30, "id": "663e4435-1cd0-4ce2-9593-85453f4c846a", "metadata": { "tags": [] @@ -521,7 +565,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 31, "id": "37440e5a-75ff-4601-813a-f5c8df9413ad", "metadata": { "tags": [] @@ -533,7 +577,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 32, "id": "00fbac35-11cd-468b-990e-0b97c3f4dec1", "metadata": { "tags": [] @@ -545,7 +589,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 33, "id": "8506cba9-b045-40f9-8514-2982db2470ca", "metadata": { "tags": [] @@ -556,10 +600,10 @@ "output_type": "stream", "text": [ " Step Time Energy fmax\n", - "GPMin: 0 16:26:11 11.122159 187.2462\n", - "GPMin: 1 16:26:11 -0.278268 1.5338\n", - "GPMin: 2 16:26:11 -0.996055 0.8010\n", - "GPMin: 3 16:26:11 -0.000000 0.0000\n" + "GPMin: 0 22:29:32 11.122159 187.2462\n", + "GPMin: 1 22:29:32 -0.278268 1.5338\n", + "GPMin: 2 22:29:32 -0.996055 0.8010\n", + "GPMin: 3 22:29:32 -0.000000 0.0000\n" ] } ], @@ -569,7 +613,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 34, "id": "5977dd10-c4cf-40c9-944e-5aa52cfa263d", "metadata": { "tags": [] @@ -581,7 +625,7 @@ "(ReturnStatus(Code.DONE, None),)" ] }, - "execution_count": 68, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -592,7 +636,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 35, "id": "dd164778-634c-4785-903a-08a5243999ce", "metadata": { "tags": [] @@ -604,7 +648,7 @@ "2.136147842601888e-07" ] }, - "execution_count": 69, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -615,7 +659,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 36, "id": "515ea06d-9026-4d9e-9df0-b9c249f0758a", "metadata": { "tags": [] @@ -627,7 +671,7 @@ "" ] }, - "execution_count": 70, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -638,7 +682,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 37, "id": "52b7231f-8978-46ec-b698-ea8724a6fea3", "metadata": { "tags": [] @@ -647,10 +691,10 @@ { "data": { "text/plain": [ - "0.07873644600476837" + "0.05034927099768538" ] }, - "execution_count": 71, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } @@ -661,7 +705,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 38, "id": "c845430c-119d-4566-88e1-8465e378fde1", "metadata": { "tags": [] @@ -670,10 +714,10 @@ { "data": { "text/plain": [ - "1.3546996342483908e-05" + "1.0032003046944737e-05" ] }, - "execution_count": 72, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" } @@ -684,7 +728,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 39, "id": "35291d7f-33a9-41ab-9b80-f052c5eb2e55", "metadata": { "tags": [] @@ -707,7 +751,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 40, "id": "1d5b5203-d07f-485b-9553-9150f4a674e7", "metadata": { "tags": [] @@ -723,7 +767,7 @@ " -3.560246436024868e-08]" ] }, - "execution_count": 74, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } @@ -734,7 +778,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 41, "id": "d2cc3b3a-5daa-49bb-9d6d-2994ebc74273", "metadata": { "tags": [] @@ -743,7 +787,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "834b47ef1b224ffc92b0f0392a0a39ac", + "model_id": "39a96fb81e2440b2bd93f54c055787d0", "version_major": 2, "version_minor": 0 }, @@ -779,33 +823,33 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 42, "id": "4acdeafc-90b5-4b3f-9559-c74b9fa221ab", "metadata": { "tags": [] }, "outputs": [], "source": [ - "m = MurnaghanTask()" + "m = MurnaghanTask(capture_exceptions=False)" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 43, "id": "f8cf3136-9b7c-4f1e-b630-962795527946", "metadata": { "tags": [] }, "outputs": [], "source": [ - "m.input.task = AseStaticTask()\n", + "m.input.task = AseStaticTask(capture_exceptions=False)\n", "m.input.task.input.calculator = MorsePotential()\n", "m.input.structure = bulk(\"Fe\", a=1.2)" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 44, "id": "fef21aa4-d9f1-4d4a-8761-af1bc3121e5b", "metadata": { "tags": [] @@ -814,10 +858,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 10, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } @@ -828,7 +872,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 45, "id": "41a68b17-c7c4-4a5f-8f04-11bee18fe55a", "metadata": { "tags": [] @@ -840,7 +884,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 46, "id": "fd107556-99b6-4042-9209-9412b4bbff94", "metadata": { "tags": [] @@ -861,7 +905,7 @@ " 1.12355993, 1.12892306, 1.13423572, 1.13949907, 1.14471424])" ] }, - "execution_count": 12, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } @@ -872,7 +916,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 47, "id": "0715614a-7284-4388-ac6b-c97bfedf7184", "metadata": { "tags": [] @@ -884,7 +928,7 @@ "True" ] }, - "execution_count": 13, + "execution_count": 47, "metadata": {}, "output_type": "execute_result" } @@ -895,7 +939,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 48, "id": "9820e859-f1c2-4b25-b121-feea7843a9c5", "metadata": { "tags": [] @@ -907,7 +951,30 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 49, + "id": "47c6edf6-1ecf-484e-b352-0b2e070e70a3", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ReturnStatus(Code.DONE, None)" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "_" + ] + }, + { + "cell_type": "code", + "execution_count": 50, "id": "98c712a2-ee74-4c28-bce0-cb4824a930e6", "metadata": { "tags": [] @@ -930,7 +997,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 51, "id": "14162f5b-1318-4595-8c8c-d6346a21721d", "metadata": {}, "outputs": [ @@ -940,7 +1007,7 @@ "0.6788586373205143" ] }, - "execution_count": 26, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -951,7 +1018,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 52, "id": "0f5ff296-df33-40d2-851b-02d6ded72dd6", "metadata": {}, "outputs": [ @@ -961,7 +1028,7 @@ "0.6788586373205143" ] }, - "execution_count": 27, + "execution_count": 52, "metadata": {}, "output_type": "execute_result" } @@ -972,7 +1039,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 53, "id": "92b06330-b1fc-41d0-8bd8-bf1b11bf448c", "metadata": {}, "outputs": [ @@ -982,7 +1049,7 @@ "Atoms(symbols='Fe', pbc=True, cell=[[-0.5536557129291797, 0.5536557129291797, 0.5536557129291797], [0.5536557129291797, -0.5536557129291797, 0.5536557129291797], [0.5536557129291797, 0.5536557129291797, -0.5536557129291797]])" ] }, - "execution_count": 28, + "execution_count": 53, "metadata": {}, "output_type": "execute_result" } @@ -1001,7 +1068,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 70, "id": "89169376-be36-4ceb-9f4e-6e1f3247bc62", "metadata": {}, "outputs": [], @@ -1011,7 +1078,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 71, "id": "2ee9f1d4-5b14-4340-98d4-4bd293af89a4", "metadata": {}, "outputs": [], @@ -1023,17 +1090,17 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 72, "id": "3d73a9de-7b4e-476a-b50a-ac6a3957a7ab", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 10, + "execution_count": 72, "metadata": {}, "output_type": "execute_result" } @@ -1044,7 +1111,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 73, "id": "0f075d90-e636-49be-b1a6-741a56363f54", "metadata": {}, "outputs": [], @@ -1062,7 +1129,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 74, "id": "d82a28ab-1a96-4a3a-8f79-5a875ac20788", "metadata": { "scrolled": true, @@ -1075,7 +1142,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 75, "id": "78017969-23fc-46f5-b99f-cd1d2dc74c00", "metadata": {}, "outputs": [ @@ -1096,7 +1163,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 76, "id": "45162eb2-b23d-45c6-8aad-dfe9a6a484d1", "metadata": {}, "outputs": [ @@ -1106,7 +1173,7 @@ "0.6818586500998999" ] }, - "execution_count": 17, + "execution_count": 76, "metadata": {}, "output_type": "execute_result" } @@ -1117,7 +1184,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 77, "id": "f0bcfe59-2168-4e74-9d7a-33d900368907", "metadata": {}, "outputs": [ @@ -1127,7 +1194,7 @@ "0.6818586500999" ] }, - "execution_count": 18, + "execution_count": 77, "metadata": {}, "output_type": "execute_result" } @@ -1139,14 +1206,17 @@ { "cell_type": "markdown", "id": "36b17048-3941-4d86-bd2a-e131371f4bad", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true, + "tags": [] + }, "source": [ "## Again but execute everything in the background." ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 62, "id": "c4758ca5-0760-4fd9-80d6-b02f78da0e5c", "metadata": { "tags": [] @@ -1159,7 +1229,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[19], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28;01mFalse\u001b[39;00m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mbroken in the TaskGenerator formalism\u001b[39m\u001b[38;5;124m\"\u001b[39m\n", + "Cell \u001b[0;32mIn[62], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28;01mFalse\u001b[39;00m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mbroken in the TaskGenerator formalism\u001b[39m\u001b[38;5;124m\"\u001b[39m\n", "\u001b[0;31mAssertionError\u001b[0m: broken in the TaskGenerator formalism" ] } @@ -1321,7 +1391,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 78, "id": "149c52b5-a0ce-4e6b-ba55-d94d33aa2f8a", "metadata": { "tags": [] @@ -1333,7 +1403,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 79, "id": "aca24005-ea49-4389-bc26-f292fd0a75a2", "metadata": { "tags": [] @@ -1352,7 +1422,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 80, "id": "4ae990bd-af18-4dae-8500-779c9509f3f6", "metadata": { "tags": [] @@ -1364,7 +1434,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 81, "id": "0925864e-4dd1-4f4e-ace4-aac09c55e787", "metadata": { "scrolled": true, @@ -1376,1005 +1446,1005 @@ "output_type": "stream", "text": [ " Step Time Energy fmax\n", + "LBFGS: 0 22:31:34 3.488292 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 4.251945 0.0000\n", - "LBFGS: 0 17:04:52 3.244546 0.0000\n", - "LBFGS: 0 17:04:52 4.517693 0.0000\n", + "LBFGS: 0 22:31:34 3.737364 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 3.488292 0.0000\n", + "LBFGS: 0 22:31:34 3.244546 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:34 4.517693 0.0000\n", + "LBFGS: 0 22:31:34 4.789242 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 3.006013 0.0000\n", - "LBFGS: 0 17:04:52 4.789242 0.0000\n", + "LBFGS: 0 22:31:34 3.991875 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 3.737364 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 3.991875 0.0000\n", + "LBFGS: 0 22:31:34 4.251945 0.0000\n", + "LBFGS: 0 22:31:34 3.006013 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:34 2.320604 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 1.473327 0.0000\n", + "LBFGS: 0 22:31:34 2.772582 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 2.544148 0.0000\n", - "LBFGS: 0 17:04:52 1.887783 0.0000\n", + "LBFGS: 0 22:31:34 2.101849 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 2.101849 0.0000\n", + "LBFGS: 0 22:31:35 1.887783 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 2.772582 0.0000\n", + "LBFGS: 0 22:31:35 2.544148 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 2.320604 0.0000\n", + "LBFGS: 0 22:31:35 1.473327 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 1.678307 0.0000\n", + "LBFGS: 0 22:31:35 1.678307 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 1.272749 0.0000\n", + "LBFGS: 0 22:31:35 1.272749 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 1.076481 0.0000\n", + "LBFGS: 0 22:31:35 1.076481 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 0.884435 0.0000\n", + "LBFGS: 0 22:31:35 0.884435 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 0.696523 0.0000\n", + "LBFGS: 0 22:31:35 0.696523 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 -0.015462 0.0000\n", + "LBFGS: 0 22:31:35 0.512659 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 0.332761 0.0000\n", + "LBFGS: 0 22:31:35 0.156747 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 -0.348779 0.0000\n", + "LBFGS: 0 22:31:35 -0.015462 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 0.156747 0.0000\n", + "LBFGS: 0 22:31:35 0.332761 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 0.512659 0.0000\n", + "LBFGS: 0 22:31:35 -0.183946 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 -0.183946 0.0000\n", + "LBFGS: 0 22:31:35 -0.822116 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 -0.510037 0.0000\n", + "LBFGS: 0 22:31:35 -0.348779 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 -0.822116 0.0000\n", + "LBFGS: 0 22:31:35 -0.667792 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 -0.667792 0.0000\n", + "LBFGS: 0 22:31:35 -0.973078 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 -0.973078 0.0000\n", + "LBFGS: 0 22:31:35 -0.510037 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 -1.120747 0.0000\n", + "LBFGS: 0 22:31:35 -1.120747 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 -1.265189 0.0000\n", + "LBFGS: 0 22:31:35 -1.406469 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 -1.406469 0.0000\n", + "LBFGS: 0 22:31:35 -1.265189 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 -1.544652 0.0000\n", + "LBFGS: 0 22:31:35 -1.941232 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 -2.191241 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:52 -1.679799 0.0000\n", + "LBFGS: 0 22:31:35 -1.811973 0.0000\n", + "LBFGS: 0 22:31:35 -1.679799 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:35 -1.544652 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -1.941232 0.0000\n", - "LBFGS: 0 17:04:53 -2.312104 0.0000\n", + "LBFGS: 0 22:31:35 -2.191241 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -1.811973 0.0000\n", + "LBFGS: 0 22:31:35 -2.430279 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -2.067636 0.0000\n", + "LBFGS: 0 22:31:35 -2.067636 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -2.430279 0.0000\n", + "LBFGS: 0 22:31:35 -2.312104 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -2.658780 0.0000\n", + "LBFGS: 0 22:31:35 -2.545820 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -2.982680 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -2.545820 0.0000\n", + "LBFGS: 0 22:31:35 -2.982680 0.0000\n", + "LBFGS: 0 22:31:35 -2.769210 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -2.769210 0.0000\n", + "LBFGS: 0 22:31:35 -2.658780 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -3.085817 0.0000\n", + "LBFGS: 0 22:31:35 -2.877160 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:35 -3.186620 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -3.285134 0.0000\n", - "LBFGS: 0 17:04:53 -2.877160 0.0000\n", + "LBFGS: 0 22:31:35 -3.085817 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -3.186620 0.0000\n", + "LBFGS: 0 22:31:35 -3.285134 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -3.475475 0.0000\n", + "LBFGS: 0 22:31:35 -3.744922 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -3.567390 0.0000\n", + "LBFGS: 0 22:31:35 -3.475475 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -3.914328 0.0000\n", + "LBFGS: 0 22:31:35 -3.830620 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -3.381404 0.0000\n", + "LBFGS: 0 22:31:35 -3.381404 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -3.830620 0.0000\n", + "LBFGS: 0 22:31:35 -3.567390 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:35 -3.914328 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -3.744922 0.0000\n", - "LBFGS: 0 17:04:53 -3.657192 0.0000\n", + "LBFGS: 0 22:31:35 -3.657192 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -3.996084 0.0000\n", + "LBFGS: 0 22:31:35 -3.996084 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -4.075925 0.0000\n", + "LBFGS: 0 22:31:35 -4.153891 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -4.376892 0.0000\n", + "LBFGS: 0 22:31:35 -4.075925 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -4.153891 0.0000\n", + "LBFGS: 0 22:31:35 -4.230016 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -4.230016 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -4.304338 0.0000\n", + "LBFGS: 0 22:31:35 -4.516830 0.0000\n", + "LBFGS: 0 22:31:35 -4.304338 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -4.584282 0.0000\n", + "LBFGS: 0 22:31:35 -4.447711 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -4.516830 0.0000\n", + "LBFGS: 0 22:31:35 -4.376892 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -4.447711 0.0000\n", + "LBFGS: 0 22:31:35 -4.650099 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -4.650099 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -4.714313 0.0000\n", + "LBFGS: 0 22:31:35 -4.584282 0.0000\n", + "LBFGS: 0 22:31:35 -4.714313 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -4.776956 0.0000\n", + "LBFGS: 0 22:31:35 -4.776956 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -4.838057 0.0000\n", + "LBFGS: 0 22:31:35 -4.838057 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.012409 0.0000\n", + "LBFGS: 0 22:31:35 -4.955755 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -4.897647 0.0000\n", + "LBFGS: 0 22:31:35 -4.897647 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -4.955755 0.0000\n", + "LBFGS: 0 22:31:35 -5.067638 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:35 -5.121469 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.067638 0.0000\n", - "LBFGS: 0 17:04:53 -5.225046 0.0000\n", + "LBFGS: 0 22:31:35 -5.012409 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.173930 0.0000\n", + "LBFGS: 0 22:31:35 -5.173930 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.121469 0.0000\n", + "LBFGS: 0 22:31:35 -5.225046 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.323350 0.0000\n", + "LBFGS: 0 22:31:35 -5.323350 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.274844 0.0000\n", + "LBFGS: 0 22:31:35 -5.274844 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.461356 0.0000\n", + "LBFGS: 0 22:31:35 -5.461356 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.370587 0.0000\n", + "LBFGS: 0 22:31:35 -5.416581 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.416581 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.504934 0.0000\n", + "LBFGS: 0 22:31:35 -5.547340 0.0000\n", + "LBFGS: 0 22:31:35 -5.370587 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.547340 0.0000\n", + "LBFGS: 0 22:31:36 -5.504934 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.588595 0.0000\n", + "LBFGS: 0 22:31:36 -5.667742 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.628722 0.0000\n", + "LBFGS: 0 22:31:36 -5.588595 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.705677 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.813177 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.742548 0.0000\n", + "LBFGS: 0 22:31:36 -5.705677 0.0000\n", + "LBFGS: 0 22:31:36 -5.846976 0.0000\n", + "LBFGS: 0 22:31:36 -5.628722 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.778375 0.0000\n", + "LBFGS: 0 22:31:36 -5.742548 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.879789 0.0000\n", + "LBFGS: 0 22:31:36 -5.813177 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.667742 0.0000\n", + "LBFGS: 0 22:31:36 -5.778375 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.846976 0.0000\n", + "LBFGS: 0 22:31:36 -5.879789 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:36 -5.911636 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.109029 0.0000\n", - "LBFGS: 0 17:04:53 -5.911636 0.0000\n", + "LBFGS: 0 22:31:36 -6.057017 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.083445 0.0000\n", + "LBFGS: 0 22:31:36 -5.942536 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.942536 0.0000\n", + "LBFGS: 0 22:31:36 -6.083445 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.057017 0.0000\n", + "LBFGS: 0 22:31:36 -6.029730 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -5.972506 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.029730 0.0000\n", + "LBFGS: 0 22:31:36 -6.001565 0.0000\n", + "LBFGS: 0 22:31:36 -5.972506 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.001565 0.0000\n", + "LBFGS: 0 22:31:36 -6.157731 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.157731 0.0000\n", + "LBFGS: 0 22:31:36 -6.180881 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.203250 0.0000\n", + "LBFGS: 0 22:31:36 -6.109029 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.133786 0.0000\n", + "LBFGS: 0 22:31:36 -6.133786 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.180881 0.0000\n", + "LBFGS: 0 22:31:36 -6.224853 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.245705 0.0000\n", + "LBFGS: 0 22:31:36 -6.265820 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.224853 0.0000\n", + "LBFGS: 0 22:31:36 -6.203250 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.303898 0.0000\n", + "LBFGS: 0 22:31:36 -6.245705 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.265820 0.0000\n", + "LBFGS: 0 22:31:36 -6.303898 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.285213 0.0000\n", + "LBFGS: 0 22:31:36 -6.321888 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.321888 0.0000\n", + "LBFGS: 0 22:31:36 -6.339196 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.339196 0.0000\n", + "LBFGS: 0 22:31:36 -6.285213 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.355836 0.0000\n", + "LBFGS: 0 22:31:36 -6.355836 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.387162 0.0000\n", + "LBFGS: 0 22:31:36 -6.401872 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.371820 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.401872 0.0000\n", + "LBFGS: 0 22:31:36 -6.371820 0.0000\n", + "LBFGS: 0 22:31:36 -6.387162 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.429451 0.0000\n", + "LBFGS: 0 22:31:36 -6.415965 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.415965 0.0000\n", + "LBFGS: 0 22:31:36 -6.429451 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.442342 0.0000\n", + "LBFGS: 0 22:31:36 -6.454650 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.454650 0.0000\n", + "LBFGS: 0 22:31:36 -6.442342 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.498271 0.0000\n", + "LBFGS: 0 22:31:36 -6.466386 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:36 -6.498271 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.466386 0.0000\n", - "LBFGS: 0 17:04:53 -6.488186 0.0000\n", + "LBFGS: 0 22:31:36 -6.488186 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:36 -6.477561 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.477561 0.0000\n", - "LBFGS: 0 17:04:53 -6.507828 0.0000\n", + "LBFGS: 0 22:31:36 -6.507828 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.516865 0.0000\n", + "LBFGS: 0 22:31:36 -6.533425 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.548028 0.0000\n", + "LBFGS: 0 22:31:36 -6.516865 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.533425 0.0000\n", + "LBFGS: 0 22:31:36 -6.540966 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:36 -6.525395 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:53 -6.525395 0.0000\n", - "LBFGS: 0 17:04:53 -6.560750 0.0000\n", + "LBFGS: 0 22:31:36 -6.548028 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.540966 0.0000\n", + "LBFGS: 0 22:31:36 -6.554620 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.571664 0.0000\n", + "LBFGS: 0 22:31:36 -6.560750 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.554620 0.0000\n", + "LBFGS: 0 22:31:36 -6.584797 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.580840 0.0000\n", + "LBFGS: 0 22:31:36 -6.566429 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.566429 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:36 -6.571664 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.588345 0.0000\n", - "LBFGS: 0 17:04:54 -6.576465 0.0000\n", + "LBFGS: 0 22:31:36 -6.580840 0.0000\n", + "LBFGS: 0 22:31:36 -6.576465 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.591492 0.0000\n", + "LBFGS: 0 22:31:36 -6.591492 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.584797 0.0000\n", + "LBFGS: 0 22:31:36 -6.588345 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.598601 0.0000\n", + "LBFGS: 0 22:31:36 -6.594245 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.594245 0.0000\n", - "LBFGS: 0 17:04:54 -6.596612 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.600220 0.0000\n", + "LBFGS: 0 22:31:36 -6.596612 0.0000\n", + "LBFGS: 0 22:31:36 -6.598601 0.0000\n", + "LBFGS: 0 22:31:36 -6.602374 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.602374 0.0000\n", + "LBFGS: 0 22:31:36 -6.602924 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.601475 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.603005 0.0000\n", + "LBFGS: 0 22:31:36 -6.600220 0.0000\n", + "LBFGS: 0 22:31:36 -6.603132 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.603132 0.0000\n", + "LBFGS: 0 22:31:36 -6.601475 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.601771 0.0000\n", + "LBFGS: 0 22:31:36 -6.603005 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.602924 0.0000\n", + "LBFGS: 0 22:31:36 -6.601771 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.602549 0.0000\n", + "LBFGS: 0 22:31:36 -6.599275 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.597570 0.0000\n", + "LBFGS: 0 22:31:36 -6.597570 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:36 -6.602549 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.599275 0.0000\n", - "LBFGS: 0 17:04:54 -6.600678 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.595568 0.0000\n", + "LBFGS: 0 22:31:36 -6.595568 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.590697 0.0000\n", + "LBFGS: 0 22:31:36 -6.600678 0.0000\n", + "LBFGS: 0 22:31:36 -6.593275 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.593275 0.0000\n", + "LBFGS: 0 22:31:36 -6.587840 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.587840 0.0000\n", + "LBFGS: 0 22:31:36 -6.590697 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.584710 0.0000\n", + "LBFGS: 0 22:31:36 -6.573734 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:36 -6.581312 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.577651 0.0000\n", - "LBFGS: 0 17:04:54 -6.581312 0.0000\n", + "LBFGS: 0 22:31:36 -6.577651 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.565149 0.0000\n", + "LBFGS: 0 22:31:36 -6.584710 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.573734 0.0000\n", + "LBFGS: 0 22:31:36 -6.565149 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.569565 0.0000\n", + "LBFGS: 0 22:31:36 -6.569565 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.555599 0.0000\n", + "LBFGS: 0 22:31:36 -6.560493 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.560493 0.0000\n", + "LBFGS: 0 22:31:36 -6.555599 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.545124 0.0000\n", + "LBFGS: 0 22:31:36 -6.550475 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.533761 0.0000\n", + "LBFGS: 0 22:31:36 -6.545124 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.539551 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:36 -6.539551 0.0000\n", + "LBFGS: 0 22:31:36 -6.533761 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.521548 0.0000\n", - "LBFGS: 0 17:04:54 -6.550475 0.0000\n", + "LBFGS: 0 22:31:36 -6.527759 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.527759 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:36 -6.521548 0.0000\n", + "LBFGS: 0 22:31:36 -6.515134 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.515134 0.0000\n", - "LBFGS: 0 17:04:54 -6.501712 0.0000\n", + "LBFGS: 0 22:31:36 -6.480158 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.508521 0.0000\n", + "LBFGS: 0 22:31:36 -6.508521 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.494713 0.0000\n", + "LBFGS: 0 22:31:36 -6.501712 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:36 -6.494713 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.480158 0.0000\n", - "LBFGS: 0 17:04:54 -6.464889 0.0000\n", + "LBFGS: 0 22:31:36 -6.472611 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.487527 0.0000\n", + "LBFGS: 0 22:31:36 -6.487527 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.472611 0.0000\n", + "LBFGS: 0 22:31:36 -6.432329 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.440712 0.0000\n", + "LBFGS: 0 22:31:36 -6.464889 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.456996 0.0000\n", + "LBFGS: 0 22:31:36 -6.448936 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.432329 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.448936 0.0000\n", + "LBFGS: 0 22:31:36 -6.440712 0.0000\n", + "LBFGS: 0 22:31:36 -6.456996 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.423789 0.0000\n", + "LBFGS: 0 22:31:36 -6.423789 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.415096 0.0000\n", + "LBFGS: 0 22:31:36 -6.406254 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.397266 0.0000\n", + "LBFGS: 0 22:31:36 -6.397266 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.406254 0.0000\n", + "LBFGS: 0 22:31:36 -6.415096 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.378866 0.0000\n", + "LBFGS: 0 22:31:36 -6.378866 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.388136 0.0000\n", + "LBFGS: 0 22:31:37 -6.369460 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.350251 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:37 -6.388136 0.0000\n", + "LBFGS: 0 22:31:37 -6.350251 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.359921 0.0000\n", - "LBFGS: 0 17:04:54 -6.340455 0.0000\n", + "LBFGS: 0 22:31:37 -6.359921 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.369460 0.0000\n", + "LBFGS: 0 22:31:37 -6.330535 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.330535 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.310333 0.0000\n", + "LBFGS: 0 22:31:37 -6.340455 0.0000\n", + "LBFGS: 0 22:31:37 -6.310333 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.320493 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.300058 0.0000\n", + "LBFGS: 0 22:31:37 -6.320493 0.0000\n", + "LBFGS: 0 22:31:37 -6.300058 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.289669 0.0000\n", + "LBFGS: 0 22:31:37 -6.289669 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.268565 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.257855 0.0000\n", + "LBFGS: 0 22:31:37 -6.225120 0.0000\n", + "LBFGS: 0 22:31:37 -6.279171 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.279171 0.0000\n", + "LBFGS: 0 22:31:37 -6.268565 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.247042 0.0000\n", + "LBFGS: 0 22:31:37 -6.257855 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.236130 0.0000\n", + "LBFGS: 0 22:31:37 -6.247042 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:37 -6.236130 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.202819 0.0000\n", - "LBFGS: 0 17:04:54 -6.191532 0.0000\n", + "LBFGS: 0 22:31:37 -6.214016 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.225120 0.0000\n", + "LBFGS: 0 22:31:37 -6.180158 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.214016 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.168698 0.0000\n", + "LBFGS: 0 22:31:37 -6.191532 0.0000\n", + "LBFGS: 0 22:31:37 -6.168698 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.180158 0.0000\n", + "LBFGS: 0 22:31:37 -6.145530 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.157154 0.0000\n", + "LBFGS: 0 22:31:37 -6.202819 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.145530 0.0000\n", + "LBFGS: 0 22:31:37 -6.157154 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.098266 0.0000\n", + "LBFGS: 0 22:31:37 -6.110193 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.133827 0.0000\n", + "LBFGS: 0 22:31:37 -6.122047 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.122047 0.0000\n", + "LBFGS: 0 22:31:37 -6.133827 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.110193 0.0000\n", + "LBFGS: 0 22:31:37 -6.098266 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.086268 0.0000\n", + "LBFGS: 0 22:31:37 -6.086268 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.074202 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.062068 0.0000\n", + "LBFGS: 0 22:31:37 -6.049871 0.0000\n", + "LBFGS: 0 22:31:37 -6.074202 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.049871 0.0000\n", + "LBFGS: 0 22:31:37 -6.062068 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:37 -6.025288 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.012907 0.0000\n", - "LBFGS: 0 17:04:54 -6.037610 0.0000\n", + "LBFGS: 0 22:31:37 -6.012907 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.000468 0.0000\n", + "LBFGS: 0 22:31:37 -6.037610 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.987974 0.0000\n", + "LBFGS: 0 22:31:37 -5.950174 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -6.025288 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.975426 0.0000\n", + "LBFGS: 0 22:31:37 -6.000468 0.0000\n", + "LBFGS: 0 22:31:37 -5.987974 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.962825 0.0000\n", + "LBFGS: 0 22:31:37 -5.962825 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.937473 0.0000\n", + "LBFGS: 0 22:31:37 -5.975426 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.950174 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.911931 0.0000\n", + "LBFGS: 0 22:31:37 -5.924725 0.0000\n", + "LBFGS: 0 22:31:37 -5.911931 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.924725 0.0000\n", + "LBFGS: 0 22:31:37 -5.937473 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.886212 0.0000\n", + "LBFGS: 0 22:31:37 -5.899093 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:37 -5.860327 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.899093 0.0000\n", - "LBFGS: 0 17:04:54 -5.873290 0.0000\n", + "LBFGS: 0 22:31:37 -5.886212 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.860327 0.0000\n", + "LBFGS: 0 22:31:37 -5.873290 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.808107 0.0000\n", + "LBFGS: 0 22:31:37 -5.834289 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.821215 0.0000\n", + "LBFGS: 0 22:31:37 -5.821215 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.794966 0.0000\n", + "LBFGS: 0 22:31:37 -5.847326 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:37 -5.808107 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.847326 0.0000\n", - "LBFGS: 0 17:04:54 -5.834289 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.781794 0.0000\n", + "LBFGS: 0 22:31:37 -5.755359 0.0000\n", + "LBFGS: 0 22:31:37 -5.794966 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.755359 0.0000\n", + "LBFGS: 0 22:31:37 -5.715501 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.768591 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.728813 0.0000\n", + "LBFGS: 0 22:31:37 -5.781794 0.0000\n", + "LBFGS: 0 22:31:37 -5.728813 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.742099 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.702165 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.662022 0.0000\n", + "LBFGS: 0 22:31:37 -5.742099 0.0000\n", + "LBFGS: 0 22:31:37 -5.768591 0.0000\n", + "LBFGS: 0 22:31:37 -5.702165 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.715501 0.0000\n", + "LBFGS: 0 22:31:37 -5.648600 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.688805 0.0000\n", + "LBFGS: 0 22:31:37 -5.688805 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.675424 0.0000\n", + "LBFGS: 0 22:31:37 -5.675424 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.648600 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.608226 0.0000\n", + "LBFGS: 0 22:31:37 -5.662022 0.0000\n", + "LBFGS: 0 22:31:37 -5.621701 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.581230 0.0000\n", + "LBFGS: 0 22:31:37 -5.608226 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.594735 0.0000\n", + "LBFGS: 0 22:31:37 -5.567712 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.635159 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:54 -5.621701 0.0000\n", + "LBFGS: 0 22:31:37 -5.635159 0.0000\n", + "LBFGS: 0 22:31:37 -5.594735 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:37 -5.581230 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.540637 0.0000\n", - "LBFGS: 0 17:04:54 -5.567712 0.0000\n", + "LBFGS: 0 22:31:37 -5.527083 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.554180 0.0000\n", + "LBFGS: 0 22:31:37 -5.486366 0.0000\n", + "LBFGS: 0 22:31:37 -5.513519 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.486366 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.527083 0.0000\n", + "LBFGS: 0 22:31:37 -5.540637 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:37 -5.554180 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.499947 0.0000\n", - "LBFGS: 0 17:04:55 -5.472778 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.513519 0.0000\n", + "LBFGS: 0 22:31:37 -5.499947 0.0000\n", + "LBFGS: 0 22:31:37 -5.459183 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:37 -5.472778 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.431978 0.0000\n", - "LBFGS: 0 17:04:55 -5.445583 0.0000\n", + "LBFGS: 0 22:31:37 -5.431978 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.459183 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.418369 0.0000\n", + "LBFGS: 0 22:31:37 -5.418369 0.0000\n", + "LBFGS: 0 22:31:37 -5.404757 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.363910 0.0000\n", + "LBFGS: 0 22:31:37 -5.445583 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.404757 0.0000\n", + "LBFGS: 0 22:31:37 -5.391143 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.377527 0.0000\n", + "LBFGS: 0 22:31:37 -5.295837 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.391143 0.0000\n", + "LBFGS: 0 22:31:37 -5.363910 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.323061 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.350293 0.0000\n", + "LBFGS: 0 22:31:37 -5.336676 0.0000\n", + "LBFGS: 0 22:31:37 -5.377527 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.336676 0.0000\n", + "LBFGS: 0 22:31:37 -5.350293 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.268625 0.0000\n", - "LBFGS: 0 17:04:55 -5.309448 0.0000\n", + "LBFGS: 0 22:31:37 -5.323061 0.0000\n", + "LBFGS: 0 22:31:37 -5.309448 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.295837 0.0000\n", + "LBFGS: 0 22:31:37 -5.268625 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.282229 0.0000\n", + "LBFGS: 0 22:31:37 -5.282229 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:37 -5.227844 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.241432 0.0000\n", - "LBFGS: 0 17:04:55 -5.255026 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.227844 0.0000\n", + "LBFGS: 0 22:31:37 -5.173560 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.214262 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.200687 0.0000\n", + "LBFGS: 0 22:31:37 -5.255026 0.0000\n", + "LBFGS: 0 22:31:37 -5.214262 0.0000\n", + "LBFGS: 0 22:31:37 -5.241432 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:37 -5.187119 0.0000\n", + "LBFGS: 0 22:31:37 -5.160009 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.160009 0.0000\n", - "LBFGS: 0 17:04:55 -5.187119 0.0000\n", - "LBFGS: 0 17:04:55 -5.173560 0.0000\n", + "LBFGS: 0 22:31:37 -5.200687 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.119414 0.0000\n", + "LBFGS: 0 22:31:37 -5.146468 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.146468 0.0000\n", + "LBFGS: 0 22:31:37 -5.092403 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.132936 0.0000\n", + "LBFGS: 0 22:31:37 -5.132936 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:37 -5.105903 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.105903 0.0000\n", - "LBFGS: 0 17:04:55 -5.078915 0.0000\n", + "LBFGS: 0 22:31:37 -5.051977 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.092403 0.0000\n", + "LBFGS: 0 22:31:37 -5.119414 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.051977 0.0000\n", + "LBFGS: 0 22:31:37 -5.078915 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:37 -5.065440 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.065440 0.0000\n", - "LBFGS: 0 17:04:55 -5.038527 0.0000\n", + "LBFGS: 0 22:31:37 -5.011668 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.025090 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -5.011668 0.0000\n", + "LBFGS: 0 22:31:37 -5.025090 0.0000\n", + "LBFGS: 0 22:31:37 -4.998260 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.984867 0.0000\n", + "LBFGS: 0 22:31:37 -5.038527 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.971490 0.0000\n", - "LBFGS: 0 17:04:55 -4.998260 0.0000\n", + "LBFGS: 0 22:31:37 -4.984867 0.0000\n", + "LBFGS: 0 22:31:37 -4.971490 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.944782 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.958128 0.0000\n", + "LBFGS: 0 22:31:37 -4.944782 0.0000\n", + "LBFGS: 0 22:31:37 -4.891567 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.931453 0.0000\n", + "LBFGS: 0 22:31:37 -4.958128 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.918140 0.0000\n", + "LBFGS: 0 22:31:38 -4.904845 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.904845 0.0000\n", + "LBFGS: 0 22:31:38 -4.878308 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.851844 0.0000\n", + "LBFGS: 0 22:31:38 -4.931453 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -4.918140 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.878308 0.0000\n", - "LBFGS: 0 17:04:55 -4.891567 0.0000\n", + "LBFGS: 0 22:31:38 -4.825456 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -4.865067 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.838641 0.0000\n", - "LBFGS: 0 17:04:55 -4.865067 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -4.851844 0.0000\n", + "LBFGS: 0 22:31:38 -4.838641 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.825456 0.0000\n", - "LBFGS: 0 17:04:55 -4.812292 0.0000\n", + "LBFGS: 0 22:31:38 -4.812292 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.799147 0.0000\n", + "LBFGS: 0 22:31:38 -4.772919 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.746775 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.786023 0.0000\n", + "LBFGS: 0 22:31:38 -4.799147 0.0000\n", + "LBFGS: 0 22:31:38 -4.786023 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.772919 0.0000\n", + "LBFGS: 0 22:31:38 -4.759837 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.759837 0.0000\n", + "LBFGS: 0 22:31:38 -4.720716 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.733735 0.0000\n", + "LBFGS: 0 22:31:38 -4.746775 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.720716 0.0000\n", + "LBFGS: 0 22:31:38 -4.707720 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.707720 0.0000\n", + "LBFGS: 0 22:31:38 -4.733735 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.643076 0.0000\n", + "LBFGS: 0 22:31:38 -4.668865 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.668865 0.0000\n", + "LBFGS: 0 22:31:38 -4.681794 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.655959 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.694745 0.0000\n", + "LBFGS: 0 22:31:38 -4.694745 0.0000\n", + "LBFGS: 0 22:31:38 -4.655959 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.604569 0.0000\n", + "LBFGS: 0 22:31:38 -4.630216 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.681794 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.630216 0.0000\n", + "LBFGS: 0 22:31:38 -4.604569 0.0000\n", + "LBFGS: 0 22:31:38 -4.617381 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.617381 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.540876 0.0000\n", + "LBFGS: 0 22:31:38 -4.643076 0.0000\n", + "LBFGS: 0 22:31:38 -4.591781 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.591781 0.0000\n", + "LBFGS: 0 22:31:38 -4.579018 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -4.553565 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.579018 0.0000\n", - "LBFGS: 0 17:04:55 -4.553565 0.0000\n", + "LBFGS: 0 22:31:38 -4.566279 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -4.477811 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.515574 0.0000\n", - "LBFGS: 0 17:04:55 -4.566279 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.502960 0.0000\n", + "LBFGS: 0 22:31:38 -4.540876 0.0000\n", + "LBFGS: 0 22:31:38 -4.528212 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.528212 0.0000\n", + "LBFGS: 0 22:31:38 -4.490373 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -4.515574 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -4.502960 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.440283 0.0000\n", - "LBFGS: 0 17:04:55 -4.490373 0.0000\n", - "LBFGS: 0 17:04:55 -4.452766 0.0000\n", + "LBFGS: 0 22:31:38 -4.465275 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.427826 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.477811 0.0000\n", + "LBFGS: 0 22:31:38 -4.415396 0.0000\n", + "LBFGS: 0 22:31:38 -4.402992 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.465275 0.0000\n", + "LBFGS: 0 22:31:38 -4.427826 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.402992 0.0000\n", + "LBFGS: 0 22:31:38 -4.440283 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.415396 0.0000\n", + "LBFGS: 0 22:31:38 -4.452766 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -4.390616 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.353649 0.0000\n", - "LBFGS: 0 17:04:55 -4.390616 0.0000\n", + "LBFGS: 0 22:31:38 -4.378267 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.365944 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.378267 0.0000\n", + "LBFGS: 0 22:31:38 -4.341382 0.0000\n", + "LBFGS: 0 22:31:38 -4.365944 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -4.316929 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.341382 0.0000\n", - "LBFGS: 0 17:04:55 -4.316929 0.0000\n", + "LBFGS: 0 22:31:38 -4.292588 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.304745 0.0000\n", + "LBFGS: 0 22:31:38 -4.353649 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.329142 0.0000\n", + "LBFGS: 0 22:31:38 -4.329142 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.292588 0.0000\n", + "LBFGS: 0 22:31:38 -4.304745 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.256286 0.0000\n", + "LBFGS: 0 22:31:38 -4.280459 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.244242 0.0000\n", + "LBFGS: 0 22:31:38 -4.256286 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.280459 0.0000\n", - "LBFGS: 0 17:04:55 -4.268359 0.0000\n", + "LBFGS: 0 22:31:38 -4.268359 0.0000\n", + "LBFGS: 0 22:31:38 -4.244242 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.232226 0.0000\n", + "LBFGS: 0 22:31:38 -4.232226 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.208280 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.220239 0.0000\n", + "LBFGS: 0 22:31:38 -4.208280 0.0000\n", + "LBFGS: 0 22:31:38 -4.184448 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.196350 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -4.220239 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.172575 0.0000\n", - "LBFGS: 0 17:04:55 -4.125374 0.0000\n", + "LBFGS: 0 22:31:38 -4.196350 0.0000\n", + "LBFGS: 0 22:31:38 -4.172575 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.184448 0.0000\n", + "LBFGS: 0 22:31:38 -4.160732 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.148917 0.0000\n", + "LBFGS: 0 22:31:38 -4.125374 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.160732 0.0000\n", + "LBFGS: 0 22:31:38 -4.148917 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.137131 0.0000\n", + "LBFGS: 0 22:31:38 -4.137131 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.113646 0.0000\n", + "LBFGS: 0 22:31:38 -4.090278 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.090278 0.0000\n", + "LBFGS: 0 22:31:38 -4.078638 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.078638 0.0000\n", + "LBFGS: 0 22:31:38 -4.101948 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.067028 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.101948 0.0000\n", + "LBFGS: 0 22:31:38 -4.067028 0.0000\n", + "LBFGS: 0 22:31:38 -4.113646 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -4.055446 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.032372 0.0000\n", - "LBFGS: 0 17:04:55 -4.043894 0.0000\n", + "LBFGS: 0 22:31:38 -4.032372 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.020879 0.0000\n", + "LBFGS: 0 22:31:38 -4.043894 0.0000\n", + "LBFGS: 0 22:31:38 -4.009416 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -4.009416 0.0000\n", - "LBFGS: 0 17:04:55 -4.055446 0.0000\n", + "LBFGS: 0 22:31:38 -3.997982 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -3.997982 0.0000\n", + "LBFGS: 0 22:31:38 -4.020879 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -3.963859 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -3.975204 0.0000\n", + "LBFGS: 0 22:31:38 -3.986578 0.0000\n", + "LBFGS: 0 22:31:38 -3.975204 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -3.986578 0.0000\n", + "LBFGS: 0 22:31:38 -3.963859 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -3.930004 0.0000\n", + "LBFGS: 0 22:31:38 -3.918778 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -3.941259 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -3.952544 0.0000\n", - "LBFGS: 0 17:04:55 -3.907582 0.0000\n", + "LBFGS: 0 22:31:38 -3.952544 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -3.930004 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:55 -3.918778 0.0000\n", - "LBFGS: 0 17:04:55 -3.941259 0.0000\n", + "LBFGS: 0 22:31:38 -3.907582 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.863097 0.0000\n", + "LBFGS: 0 22:31:38 -3.885280 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.874174 0.0000\n", + "LBFGS: 0 22:31:38 -3.896416 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.841034 0.0000\n", + "LBFGS: 0 22:31:38 -3.874174 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.896416 0.0000\n", + "LBFGS: 0 22:31:38 -3.863097 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.885280 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -3.819091 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -3.841034 0.0000\n", + "LBFGS: 0 22:31:38 -3.852051 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.797267 0.0000\n", - "LBFGS: 0 17:04:56 -3.852051 0.0000\n", - "LBFGS: 0 17:04:56 -3.819091 0.0000\n", + "LBFGS: 0 22:31:38 -3.830047 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.830047 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -3.808164 0.0000\n", + "LBFGS: 0 22:31:38 -3.797267 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.786400 0.0000\n", - "LBFGS: 0 17:04:56 -3.775562 0.0000\n", + "LBFGS: 0 22:31:38 -3.743230 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.808164 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.764755 0.0000\n", + "LBFGS: 0 22:31:38 -3.764755 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.721825 0.0000\n", + "LBFGS: 0 22:31:38 -3.753978 0.0000\n", + "LBFGS: 0 22:31:38 -3.786400 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.700539 0.0000\n", + "LBFGS: 0 22:31:38 -3.775562 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.711167 0.0000\n", - "LBFGS: 0 17:04:56 -3.753978 0.0000\n", + "LBFGS: 0 22:31:38 -3.721825 0.0000\n", + "LBFGS: 0 22:31:38 -3.711167 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.743230 0.0000\n", + "LBFGS: 0 22:31:38 -3.732513 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -3.700539 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.732513 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.679373 0.0000\n", - "LBFGS: 0 17:04:56 -3.689941 0.0000\n", + "LBFGS: 0 22:31:38 -3.679373 0.0000\n", + "LBFGS: 0 22:31:38 -3.689941 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.668834 0.0000\n", + "LBFGS: 0 22:31:38 -3.658325 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.595897 0.0000\n", + "LBFGS: 0 22:31:38 -3.626978 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.626978 0.0000\n", + "LBFGS: 0 22:31:38 -3.668834 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -3.647846 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.658325 0.0000\n", - "LBFGS: 0 17:04:56 -3.637397 0.0000\n", - "LBFGS: 0 17:04:56 -3.647846 0.0000\n", + "LBFGS: 0 22:31:38 -3.585597 0.0000\n", + "LBFGS: 0 22:31:38 -3.637397 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.616588 0.0000\n", + "LBFGS: 0 22:31:38 -3.595897 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.606228 0.0000\n", + "LBFGS: 0 22:31:38 -3.616588 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.575325 0.0000\n", + "LBFGS: 0 22:31:38 -3.575325 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.585597 0.0000\n", + "LBFGS: 0 22:31:38 -3.606228 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.565084 0.0000\n", + "LBFGS: 0 22:31:38 -3.534536 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.554872 0.0000\n", + "LBFGS: 0 22:31:38 -3.524412 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:38 -3.565084 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.504253 0.0000\n", - "LBFGS: 0 17:04:56 -3.514318 0.0000\n", + "LBFGS: 0 22:31:38 -3.554872 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.524412 0.0000\n", + "LBFGS: 0 22:31:39 -3.514318 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:39 -3.544689 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.534536 0.0000\n", - "LBFGS: 0 17:04:56 -3.544689 0.0000\n", + "LBFGS: 0 22:31:39 -3.494218 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.494218 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.484212 0.0000\n", + "LBFGS: 0 22:31:39 -3.504253 0.0000\n", + "LBFGS: 0 22:31:39 -3.474235 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:39 -3.454369 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.444480 0.0000\n", - "LBFGS: 0 17:04:56 -3.474235 0.0000\n", + "LBFGS: 0 22:31:39 -3.484212 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.464287 0.0000\n", + "LBFGS: 0 22:31:39 -3.464287 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:39 -3.424788 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:39 -3.434619 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.424788 0.0000\n", - "LBFGS: 0 17:04:56 -3.454369 0.0000\n", - "LBFGS: 0 17:04:56 -3.434619 0.0000\n", + "LBFGS: 0 22:31:39 -3.405213 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.414986 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.395469 0.0000\n", + "LBFGS: 0 22:31:39 -3.414986 0.0000\n", + "LBFGS: 0 22:31:39 -3.444480 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.347181 0.0000\n", + "LBFGS: 0 22:31:39 -3.395469 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.385754 0.0000\n", + "LBFGS: 0 22:31:39 -3.385754 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:39 -3.376068 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.376068 0.0000\n", - "LBFGS: 0 17:04:56 -3.405213 0.0000\n", + "LBFGS: 0 22:31:39 -3.356781 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.366410 0.0000\n", + "LBFGS: 0 22:31:39 -3.328067 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.356781 0.0000\n", + "LBFGS: 0 22:31:39 -3.366410 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.328067 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.318553 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.337610 0.0000\n", + "LBFGS: 0 22:31:39 -3.337610 0.0000\n", + "LBFGS: 0 22:31:39 -3.347181 0.0000\n", + "LBFGS: 0 22:31:39 -3.299610 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.299610 0.0000\n", + "LBFGS: 0 22:31:39 -3.318553 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.290181 0.0000\n", + "LBFGS: 0 22:31:39 -3.309067 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.309067 0.0000\n", + "LBFGS: 0 22:31:39 -3.280781 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:39 -3.271409 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.280781 0.0000\n", - "LBFGS: 0 17:04:56 -3.262065 0.0000\n", + "LBFGS: 0 22:31:39 -3.290181 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.271409 0.0000\n", + "LBFGS: 0 22:31:39 -3.262065 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.252749 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.243462 0.0000\n", + "LBFGS: 0 22:31:39 -3.252749 0.0000\n", + "LBFGS: 0 22:31:39 -3.215768 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.234202 0.0000\n", + "LBFGS: 0 22:31:39 -3.234202 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.206592 0.0000\n", + "LBFGS: 0 22:31:39 -3.243462 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.215768 0.0000\n", + "LBFGS: 0 22:31:39 -3.224971 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.197445 0.0000\n", + "LBFGS: 0 22:31:39 -3.206592 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.224971 0.0000\n", + "LBFGS: 0 22:31:39 -3.197445 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.188325 0.0000\n", + "LBFGS: 0 22:31:39 -3.188325 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.179233 0.0000\n", + "LBFGS: 0 22:31:39 -3.170169 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.161132 0.0000\n", + "LBFGS: 0 22:31:39 -3.179233 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.170169 0.0000\n", + "LBFGS: 0 22:31:39 -3.161132 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.134187 0.0000\n", + "LBFGS: 0 22:31:39 -3.152123 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.143141 0.0000\n", - "LBFGS: 0 17:04:56 -3.125260 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.152123 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.116361 0.0000\n", + "LBFGS: 0 22:31:39 -3.107488 0.0000\n", + "LBFGS: 0 22:31:39 -3.134187 0.0000\n", + "LBFGS: 0 22:31:39 -3.125260 0.0000\n", + "LBFGS: 0 22:31:39 -3.143141 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.107488 0.0000\n", + "LBFGS: 0 22:31:39 -3.116361 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.089825 0.0000\n", + "LBFGS: 0 22:31:39 -3.098643 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:39 -3.081034 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.054823 0.0000\n", - "LBFGS: 0 17:04:56 -3.072271 0.0000\n", + "LBFGS: 0 22:31:39 -3.046140 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.098643 0.0000\n", + "LBFGS: 0 22:31:39 -3.089825 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.081034 0.0000\n", + "LBFGS: 0 22:31:39 -3.072271 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.063534 0.0000\n", + "LBFGS: 0 22:31:39 -3.063534 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.037483 0.0000\n", + "LBFGS: 0 22:31:39 -3.037483 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.046140 0.0000\n", + "LBFGS: 0 22:31:39 -3.054823 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.028853 0.0000\n", + "LBFGS: 0 22:31:39 -3.020250 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.020250 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.994599 0.0000\n", + "LBFGS: 0 22:31:39 -3.028853 0.0000\n", + "LBFGS: 0 22:31:39 -3.011673 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.986101 0.0000\n", + "LBFGS: 0 22:31:39 -2.994599 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.003123 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.977630 0.0000\n", + "LBFGS: 0 22:31:39 -2.986101 0.0000\n", + "LBFGS: 0 22:31:39 -3.003123 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -3.011673 0.0000\n", + "LBFGS: 0 22:31:39 -2.977630 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.960766 0.0000\n", + "LBFGS: 0 22:31:39 -2.969185 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.969185 0.0000\n", + "LBFGS: 0 22:31:39 -2.952373 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.952373 0.0000\n", + "LBFGS: 0 22:31:39 -2.944006 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.910796 0.0000\n", + "LBFGS: 0 22:31:39 -2.960766 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:39 -2.919060 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.935665 0.0000\n", - "LBFGS: 0 17:04:56 -2.944006 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:39 -2.927350 0.0000\n", + "LBFGS: 0 22:31:39 -2.935665 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.894346 0.0000\n", - "LBFGS: 0 17:04:56 -2.902558 0.0000\n", + "LBFGS: 0 22:31:39 -2.910796 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:39 -2.902558 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.919060 0.0000\n", - "LBFGS: 0 17:04:56 -2.927350 0.0000\n", + "LBFGS: 0 22:31:39 -2.894346 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.886159 0.0000\n", + "LBFGS: 0 22:31:39 -2.877998 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.837570 0.0000\n", - "LBFGS: 0 17:04:56 -2.861751 0.0000\n", + "LBFGS: 0 22:31:39 -2.886159 0.0000\n", + "LBFGS: 0 22:31:39 -2.869862 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:39 -2.845605 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.845605 0.0000\n", - "LBFGS: 0 17:04:56 -2.877998 0.0000\n", + "LBFGS: 0 22:31:39 -2.861751 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 22:31:39 -2.853666 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.853666 0.0000\n", - "LBFGS: 0 17:04:56 -2.869862 0.0000\n", + "LBFGS: 0 22:31:39 -2.829560 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.821575 0.0000\n", + "LBFGS: 0 22:31:39 -2.837570 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.829560 0.0000\n", + "LBFGS: 0 22:31:39 -2.821575 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.813615 0.0000\n", + "LBFGS: 0 22:31:39 -2.805679 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 17:04:56 -2.805679 0.0000\n" + "LBFGS: 0 22:31:39 -2.813615 0.0000\n" ] } ], @@ -2384,7 +2454,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 82, "id": "71bbb913-7d7a-4bb6-b775-3fbc8e7e1f35", "metadata": { "tags": [] @@ -2396,7 +2466,7 @@ "ReturnStatus(Code.DONE, None)" ] }, - "execution_count": 24, + "execution_count": 82, "metadata": {}, "output_type": "execute_result" } @@ -2407,7 +2477,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 83, "id": "4bf2df15-31dc-474c-b3df-f7c32b0fdaf2", "metadata": { "tags": [] @@ -2420,7 +2490,7 @@ " 3.48829227, 3.244546 , 3.00601254, 2.77258214, 2.54414756])" ] }, - "execution_count": 25, + "execution_count": 83, "metadata": {}, "output_type": "execute_result" } @@ -2431,7 +2501,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 84, "id": "eb0a2daf-9dab-4174-bfee-0cd1ef8c474e", "metadata": {}, "outputs": [ @@ -2449,6 +2519,14 @@ "source": [ "output.plot()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aceeb6a1-f673-43fa-9487-24b0153ccb9e", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index 290e087d8..d3e892df2 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -164,10 +164,10 @@ class ForceOutput(AbstractOutput): class MDOutput(HasStructure, EnergyPotOutput): - pot_energies = StorageAttribute().type(list) - kin_energies = StorageAttribute().type(list) - forces = StorageAttribute().type(list) - structures = StorageAttribute().type(list) + pot_energies = StorageAttribute().type(list).default(list) + kin_energies = StorageAttribute().type(list).default(list) + forces = StorageAttribute().type(list).default(list) + structures = StorageAttribute().type(list).default(list) def plot_energies(self): plt.plot(self.pot_energies - np.min(self.pot_energies), label='pot') diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index 554129fa9..85f97f20d 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -45,8 +45,8 @@ def _create_tasks(self): class MurnaghanOutput(AbstractOutput, HasStructure): base_structures = StorageAttribute() - volumes = StorageAttribute().type(list) - energies = StorageAttribute().type(list) + volumes = StorageAttribute().type(np.ndarray) + energies = StorageAttribute().type(np.ndarray) def plot(self): plt.plot(self.volumes, self.energies) @@ -71,14 +71,12 @@ def _get_input(self): def _get_output(self): out = MurnaghanOutput() + out.energies = np.zeros(len(self.input.strains)) + out.volumes = np.zeros(len(self.input.strains)) out.base_structure = self.input.structure return out def _extract_output(self, output, step, task, ret, task_output): - if len(output.energies) == 0: - output.energies = np.zeros(len(self.input.strains)) - if len(output.volumes) == 0: - output.volumes = np.zeros(len(self.input.strains)) if ret.is_done(): output.energies[step] = task_output.energy_pot output.volumes[step] = task.input.structure.get_volume() diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py index 55d245e38..2114eb13c 100644 --- a/pyiron_contrib/tinybase/task.py +++ b/pyiron_contrib/tinybase/task.py @@ -222,9 +222,6 @@ class ListTaskGenerator(TaskGenerator, abc.ABC): To use it overload :meth:`._extract_output` here and subclass :class:`.ListInput` as well. """ - def __init__(self): - super().__init__() - def _get_input(self): return ListInput() From a05400f8aff7f2551d1b726faa9af1b9b0049c39 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Tue, 9 May 2023 00:45:41 +0200 Subject: [PATCH 047/756] Fix typo and add per_atom in Murn --- pyiron_contrib/tinybase/murn.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index 85f97f20d..1854ff249 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -44,12 +44,13 @@ def _create_tasks(self): return tasks class MurnaghanOutput(AbstractOutput, HasStructure): - base_structures = StorageAttribute() + base_structure = StorageAttribute() volumes = StorageAttribute().type(np.ndarray) energies = StorageAttribute().type(np.ndarray) - def plot(self): - plt.plot(self.volumes, self.energies) + def plot(self, per_atom=True): + N = len(self.base_structure) if per_atom else 1 + plt.plot(self.volumes/N, self.energies/N) @property def equilibrium_volume(self): From ffdffd7424e19fbdc5865ed1868447d18c890bc0 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Tue, 9 May 2023 00:46:26 +0200 Subject: [PATCH 048/756] Add simple lammps implementation --- notebooks/tinybase/Lammps.ipynb | 521 ++++++++++++++++++ .../lammps/bin/run_lammps_default.sh | 3 + pyiron_contrib/tinybase/lammps.py | 189 +++++++ 3 files changed, 713 insertions(+) create mode 100644 notebooks/tinybase/Lammps.ipynb create mode 100755 notebooks/tinybase/resources/lammps/bin/run_lammps_default.sh create mode 100644 pyiron_contrib/tinybase/lammps.py diff --git a/notebooks/tinybase/Lammps.ipynb b/notebooks/tinybase/Lammps.ipynb new file mode 100644 index 000000000..064e0bc8a --- /dev/null +++ b/notebooks/tinybase/Lammps.ipynb @@ -0,0 +1,521 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "1697a637-eb3d-4b63-9cf8-8bc1f56abe22", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/poul/pyiron/contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n", + " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e70d37ad42d44537b74635b5097c08a8", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from pyiron_contrib.tinybase.lammps import LammpsStaticTask" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0c91a3c3-1d02-49f2-b390-4a9ac5ae4d00", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from ase.build import bulk" + ] + }, + { + "cell_type": "markdown", + "id": "a7a8f22f-9df9-4f50-9106-f6aa583444ce", + "metadata": {}, + "source": [ + "A small hack to load resources defined in the notebook folder." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "9feaa502-d16a-4cac-bb36-e681c85a0a63", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.shell import state" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8c340f6c-c687-4461-9c33-f98b768773d6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "state.settings.resource_paths.insert(0, '/home/poul/pyiron/contrib/notebooks/tinybase/resources')" + ] + }, + { + "cell_type": "markdown", + "id": "59cf9aae-1640-4ed4-901e-06544a68e8fd", + "metadata": {}, + "source": [ + "# Simple Static Calculation with Lammps" + ] + }, + { + "cell_type": "code", + "execution_count": 156, + "id": "64c9b3d4-e6e1-4e21-86fc-318940c4c8e8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "lmp = LammpsStaticTask()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "df4de500-24f5-4160-933d-57cf3d0f15a6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "lmp.input.structure = bulk('Fe', cubic=True).repeat(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "4e5c69d6-fee8-4166-8e06-ba8a2e58e707", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "lmp.input.structure.rattle(1e-2)" + ] + }, + { + "cell_type": "markdown", + "id": "bc766dba-b88d-4b6b-997a-fbc6e37529b1", + "metadata": {}, + "source": [ + "List potentials works as before (using the same code), but can't set custom potentials yet." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "1056c4b6-603c-4603-8021-a89c64f84255", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['1997--Ackland-G-J--Fe--LAMMPS--ipr1',\n", + " '1998--Meyer-R--Fe--LAMMPS--ipr1',\n", + " '2001--Lee-B-J--Fe--LAMMPS--ipr1',\n", + " '2001--Lee-B-J--Fe-Cr--LAMMPS--ipr1',\n", + " '2003--Mendelev-M-I--Fe-2--LAMMPS--ipr3']" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lmp.input.list_potentials()[:5]" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "a6af7f64-5435-40b1-b44c-960528fc0cc5", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "lmp.input.potential = '1997--Ackland-G-J--Fe--LAMMPS--ipr1'" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "8a699e68-ad89-4d52-bdff-3b86d9624a4b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "ret, out = lmp.execute()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "8b837b3d-7bd0-4930-817b-d497e8ae1ead", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "ReturnStatus(Code.DONE, None)" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ret" + ] + }, + { + "cell_type": "markdown", + "id": "c4a2020c-6fe8-4ec4-a5b4-0b55ff296d29", + "metadata": {}, + "source": [ + "Current lammps output definition only supports generic attributes, but more can be added." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "c4793e9e-b7f1-476f-807c-c313718ed49b", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "-69.0339463780543" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.energy_pot" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "afe5c4a1-08ae-4e3a-bb0a-f501dacf4be4", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0.0" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.energy_kin" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "6b48188a-b84c-482a-9add-26d61a4e65f2", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[-0.02987269, -0.00282932, -0.12843858],\n", + " [-0.15820356, 0.0453781 , -0.03744197],\n", + " [-0.18450471, -0.08204103, 0.07020649],\n", + " [-0.06351491, 0.03401585, 0.00719245],\n", + " [-0.0037588 , 0.1333546 , 0.1740541 ],\n", + " [ 0.05387634, -0.00118085, -0.09480989],\n", + " [ 0.07405356, 0.11763504, -0.20276011],\n", + " [ 0.02384882, -0.05766377, 0.10891183],\n", + " [ 0.04153901, -0.08675459, 0.09708118],\n", + " [ 0.00966064, 0.03839169, -0.03484355],\n", + " [ 0.11122156, -0.23955637, -0.05193921],\n", + " [ 0.10710042, -0.19785464, 0.08232861],\n", + " [-0.03485634, 0.2108871 , 0.10971198],\n", + " [-0.01956278, -0.10410639, -0.02254802],\n", + " [ 0.0187732 , 0.08581792, 0.07211624],\n", + " [ 0.05420023, 0.10650667, -0.14882155]])" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out.forces" + ] + }, + { + "cell_type": "markdown", + "id": "a1a01129-e7ff-4e6f-addf-5ca6a8592fe0", + "metadata": {}, + "source": [ + "## Murnaghan\n", + "\n", + "Note that except for the input tasks definition, this works exactly the same way as for the ASE nodes and also works for multiple executors without the task implementation needing to know about any of these details." + ] + }, + { + "cell_type": "code", + "execution_count": 157, + "id": "cc50f529-e3bc-4f36-af63-f496f6c1405f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.executor import ProcessExecutor, BackgroundExecutor" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "1fb9a556-d882-4b21-99fa-0fd30023d3f4", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.murn import MurnaghanTask" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "cab52837-17eb-4f33-8182-cb5e917d02ec", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "m = MurnaghanTask()" + ] + }, + { + "cell_type": "code", + "execution_count": 151, + "id": "c95da0c4-fbdb-48d7-8086-f890f82c7725", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "m.input.task = LammpsStaticTask()\n", + "m.input.task.input.potential = '1997--Ackland-G-J--Fe--LAMMPS--ipr1'\n", + "m.input.structure = bulk(\"Fe\", cubic=False).repeat(20)" + ] + }, + { + "cell_type": "code", + "execution_count": 155, + "id": "edb93bf2-9e70-4717-9929-101a3101599b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "m.input.set_strain_range(.5, 50)" + ] + }, + { + "cell_type": "code", + "execution_count": 153, + "id": "9eab17d8-380a-4199-88b0-50910b5e5296", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 49.1 ms, sys: 102 ms, total: 151 ms\n", + "Wall time: 3.67 s\n" + ] + } + ], + "source": [ + "%%time\n", + "ret, output = ProcessExecutor(max_processes=4).run(m)" + ] + }, + { + "cell_type": "code", + "execution_count": 154, + "id": "02659463-8be4-4f76-b67c-3986eff09dd0", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 5.04 s, sys: 11.6 s, total: 16.7 s\n", + "Wall time: 11.4 s\n" + ] + } + ], + "source": [ + "%%time\n", + "ret, output = BackgroundExecutor(max_threads=10).run(m)" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "44ffaf29-f35c-451f-a3fd-9f342035569b", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "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nnvuOY/l8zbncr52796tRYsWKSYmRlFRUbrgggtUWFjo+bBe4mznrLGxUffdd58yMjIUFhamMWPG6KWXXvpGxwzoIlJbW6sZM2YoODhYq1at0q5du/TrX/9asbGxZkfzSk899ZRefvllvfDCC9q9e7d++ctf6le/+pV++9vfmh3NKzQ1NSknJ0cvvPDCKV//5S9/qWeeeUYvvPCCvvrqK6WkpOiSSy7puadSIDrTOWtubtaWLVv0s5/9TFu2bNGbb76pffv2adGiRSYk9Q5n+xnrtnz5cn355ZdKS0vzUDLvdLbzdfDgQc2cOVOjR4/Wxx9/rPz8fP3sZz9TaGioh5N6j7Ods8WLF+v999/X66+/rt27d2vx4sX6/ve/r7fffrv/BzUC2AMPPGDMnDnT7Bg+44orrjDuuuuuXs9de+21xm233WZSIu8lyXjrrbd6vna5XEZKSorx5JNP9jzX0tJixMTEGC+//LIJCb3P18/ZqWzcuNGQZBw5csQzobzY6c5XcXGxkZ6ebuzYscMYPHiw8eyzz3o8mzc61fm66aab+PfrDE51zsaNG2c89thjvZ6bPHmy8fDDD/f7OAE9IrJixQpNnTpVN9xwg5KSkpSbm6s//OEPZsfyWjNnztS//vUv7du3T5KUn5+vTz/9VJdffrnJybzf4cOHVV5ervnz5/c8Z7fbNWfOHG3YsMHEZL6lvr5eFouFUcvTcLlcuv3227VkyRKNGzfO7DhezeVy6d1339XIkSO1YMECJSUladq0aWe83IXOz4EVK1aopKREhmFo7dq12rdvnxYsWNDv7xnQReTQoUN66aWXNGLECH3wwQe655579IMf/EB//etfzY7mlR544AHdcsstGj16tIKDg5Wbm6v7779ft9xyi9nRvF55ebkkKTk5udfzycnJPa/hzFpaWvTggw/q1ltv5SZlp/HUU08pKChIP/jBD8yO4vUqKyvV2NioJ598UpdeeqlWr16ta665Rtdee63WrVtndjyv9fzzz2vs2LHKyMhQSEiILr30Ur344ouaOXNmv7+nV999191cLpemTp2qJ554QpKUm5urnTt36qWXXtIdd9xhcjrv88Ybb+j111/X3/72N40bN055eXm6//77lZaWpu985ztmx/MJFoul19eGYZz0HE7W1tamm2++WS6XSy+++KLZcbzS5s2b9Zvf/EZbtmzhZ+ocdE+yv+qqq7R48WJJ0qRJk7Rhwwa9/PLLmjNnjpnxvNbzzz+vL774QitWrNDgwYP1ySef6N5771Vqaqouvvjifn3PgC4iqampGjt2bK/nxowZo2XLlpmUyLstWbJEDz74oG6++WZJ0oQJE3TkyBEtXbqUInIWKSkpkjpHRlJTU3uer6ysPGmUBL21tbXpxhtv1OHDh/XRRx8xGnIa69evV2VlpbKysnqe6+jo0H/8x3/oueeeU0FBgXnhvFBCQoKCgoJO+Rnw6aefmpTKux07dkw/+clP9NZbb+mKK66QJE2cOFF5eXl6+umn+11EAvrSzIwZM7R3795ez+3bt0+DBw82KZF3a25ultXa+0fGZrOxfPccZGdnKyUlRWvWrOl5rrW1VevWrdP06dNNTObdukvI/v379eGHHyo+Pt7sSF7r9ttv17Zt25SXl9fzSEtL05IlS/TBBx+YHc/rhISE6LzzzuMzoA/a2trU1tY24J8DAT0isnjxYk2fPl1PPPGEbrzxRm3cuFGvvPKKXnnlFbOjeaWFCxfq8ccfV1ZWlsaNG6etW7fqmWee0V133WV2NK/Q2NioAwcO9Hx9+PBh5eXlKS4uTllZWbr//vv1xBNPaMSIERoxYoSeeOIJhYeH69ZbbzUxtbnOdM7S0tJ0/fXXa8uWLXrnnXfU0dHRM58mLi5OISEhZsU2zdl+xr5e1IKDg5WSkqJRo0Z5OqpXONv5WrJkiW666SbNnj1b8+bN0/vvv6+VK1fq448/Ni+0yc52zubMmaMlS5YoLCxMgwcP1rp16/TXv/5VzzzzTP8P2u/1Nn5i5cqVxvjx4w273W6MHj3aeOWVV8yO5LUcDofxwx/+0MjKyjJCQ0ONoUOHGj/96U8Np9NpdjSvsHbtWkPSSY/vfOc7hmF0LuF95JFHjJSUFMNutxuzZ882tm/fbm5ok53pnB0+fPiUr0ky1q5da3Z0U5ztZ+zrAn357rmcrz/96U/G8OHDjdDQUCMnJ8dYvny5eYG9wNnOWVlZmXHnnXcaaWlpRmhoqDFq1Cjj17/+teFyufp9TIthGEb/awwAAED/BfQcEQAAYC6KCAAAMA1FBAAAmIYiAgAATEMRAQAApqGIAAAA01BEAACAaSgiAADANBQRAABgGooIAAAwDUUEAACYhiICAABM8/8DV4nmheaYlRMAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "output.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": 94, + "id": "c6d3f82b-7acb-47f1-ab0c-c0380382e7a7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "20216.45910790539" + ] + }, + "execution_count": 94, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "output.equilibrium_volume" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "f8911ebd-bcc6-4696-89f4-498c60aad544", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "20216.459107905404" + ] + }, + "execution_count": 95, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "output.get_structure().get_volume()" + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "id": "620c128d-fcd4-4842-8f49-cd6f7052b4c3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Atoms(symbols='Fe1728', pbc=True, cell=[[-17.16122821739448, 17.16122821739448, 17.16122821739448], [17.16122821739448, -17.16122821739448, 17.16122821739448], [17.16122821739448, 17.16122821739448, -17.16122821739448]])" + ] + }, + "execution_count": 96, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "output.get_structure()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/tinybase/resources/lammps/bin/run_lammps_default.sh b/notebooks/tinybase/resources/lammps/bin/run_lammps_default.sh new file mode 100755 index 000000000..6fb8ec1c4 --- /dev/null +++ b/notebooks/tinybase/resources/lammps/bin/run_lammps_default.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +lmp_serial -in control.inp diff --git a/pyiron_contrib/tinybase/lammps.py b/pyiron_contrib/tinybase/lammps.py new file mode 100644 index 000000000..5a9f55618 --- /dev/null +++ b/pyiron_contrib/tinybase/lammps.py @@ -0,0 +1,189 @@ +import os +from tempfile import TemporaryDirectory + +from ase import Atoms +from pymatgen.io.lammps.outputs import ( + parse_lammps_dumps, + parse_lammps_log, +) + +from pyiron_atomistics.lammps.potential import ( + LammpsPotential, + LammpsPotentialFile, + list_potentials +) +from pyiron_atomistics.lammps.control import LammpsControl + +from pyiron_contrib.tinybase.container import ( + AbstractInput, + AbstractOutput, + StorageAttribute, + StructureInput, + EnergyPotOutput, + EnergyKinOutput, + ForceOutput, + MDOutput, +) +from pyiron_contrib.tinybase.task import ( + AbstractTask, + ReturnStatus, +) +from pyiron_contrib.tinybase.shell import ( + ShellTask, + ExecutablePathResolver +) + + +class LammpsInputInput(AbstractInput): + working_directory = StorageAttribute().type(str) + + calc_type = StorageAttribute() + + def calc_static(self): + self.calc_type = 'static' + + def check_ready(self): + return self.working_directory is not None \ + and self.calc_type == "static" \ + and super().check_ready() + +class LammpsInputOutput(AbstractOutput): + working_directory = StorageAttribute().type(str) + +class LammpsInputTask(AbstractTask): + """ + Write a set of input files for lammps calculations. + + The potential input is written to `potential.inp` together with any + additional files needed by the potential. + + The structure is written to `structure.inp`. + + The lammps input script is to `control.inp`. + """ + + def _get_input(self): + return LammpsInputInput() + + def _get_output(self): + return LammpsInputOutput() + + def _execute(self, output): + with open( + os.path.join(self.input.working_directory, "structure.inp"), + "w" + ) as f: + self.input.structure.write(f, format="lammps-data") + + potential = LammpsPotential() + potential.df = LammpsPotentialFile().find_by_name(self.input.potential) + potential.write_file( + file_name="potential.inp", cwd=self.input.working_directory + ) + potential.copy_pot_files(self.input.working_directory) + + control = LammpsControl() + if self.input.calc_type == "static": + control.calc_static() + else: + assert "Cannot happen" + control.write_file( + file_name="control.inp", cwd=self.input.working_directory + ) + + output.working_directory = self.input.working_directory + + +class LammpsStaticParserInput(AbstractInput): + working_directory = StorageAttribute().type(str) + + def check_ready(self): + return self.working_directory is not None \ + and super().check_ready() + +class LammpsStaticOutput(EnergyPotOutput, EnergyKinOutput, ForceOutput): + pass + +class LammpsStaticParserTask(AbstractTask): + """ + Parse a static lammps calculation. + + In practice return the last step of lammps dump and log file. + """ + + def _get_input(self): + return LammpsStaticParserInput() + + def _get_output(self): + return LammpsStaticOutput() + + def _execute(self, output): + log = parse_lammps_log( + os.path.join(self.input.working_directory, "log.lammps") + )[-1] + output.energy_pot = energy_pot = log["PotEng"].iloc[-1] + output.energy_kin = log["TotEng"].iloc[-1] - energy_pot + dump = list(parse_lammps_dumps( + os.path.join(self.input.working_directory, "dump.out") + ))[-1] + output.forces = dump.data[['fx','fy','fz']].to_numpy() + + +class LammpsInput(StructureInput): + potential = StorageAttribute().type(str) + + def list_potentials(self): + """ + List available potentials compatible with a set structure. + + If no structure is set, return an empty list. + """ + if self.structure is not None: + return list_potentials(self.structure) + else: + return [] + + def check_ready(self): + return self.potential is not None and super().check_ready() + +class LammpsStaticTask(AbstractTask): + """ + A static calculation with lammps. + """ + + def _get_input(self): + return LammpsInput() + + def _get_output(self): + return LammpsStaticOutput() + + def _execute(self, output): + with TemporaryDirectory() as dir: + inp = LammpsInputTask( + capture_exceptions=self._capture_exceptions + ) + inp.input.working_directory = dir + inp.input.structure = self.input.structure + inp.input.potential = self.input.potential + inp.input.calc_static() + ret, out = inp.execute() + if not ret.is_done(): + return ReturnStatus.aborted(f"Writing input failed: {ret.msg}") + + lmp = ShellTask( + capture_exceptions=self._capture_exceptions + ) + lmp.input.command = ExecutablePathResolver("lammps", "lammps") + lmp.input.working_directory = dir + ret, out = lmp.execute() + if not ret.is_done(): + return ReturnStatus.aborted(f"Running lammps failed: {ret.msg}") + + psr = LammpsStaticParserTask( + capture_exceptions=self._capture_exceptions + ) + psr.input.working_directory = dir + ret, out = psr.execute() + if not ret.is_done(): + return ReturnStatus.aborted(f"Parsing failed: {ret.msg}") + output.take(out) From 2ea892a96d7d0f70f18a75d24e4b620a115816c8 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Tue, 9 May 2023 00:47:42 +0200 Subject: [PATCH 049/756] Add base example of a TaskGenerator implementation --- notebooks/tinybase/Basic.ipynb | 184 +++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/notebooks/tinybase/Basic.ipynb b/notebooks/tinybase/Basic.ipynb index c0e4b62c2..a4eedf43d 100644 --- a/notebooks/tinybase/Basic.ipynb +++ b/notebooks/tinybase/Basic.ipynb @@ -1059,6 +1059,190 @@ "source": [ "l.execute()" ] + }, + { + "cell_type": "markdown", + "id": "840944df-098e-4318-90c1-a66ec31dd513", + "metadata": {}, + "source": [ + "# Implementation Examples" + ] + }, + { + "cell_type": "code", + "execution_count": 136, + "id": "8ead2987-116c-4bba-a09a-4b28a71660f1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.task import TaskGenerator, FunctionTask, ReturnStatus\n", + "from pyiron_contrib.tinybase.container import AbstractInput, AbstractOutput, StorageAttribute\n", + "import time\n", + "\n", + "class WaitInput(AbstractInput):\n", + " time = StorageAttribute().type(float).default(lambda: 10.0)\n", + " n = StorageAttribute().type(int).default(lambda: 10)\n", + "\n", + "class WaitOutput(AbstractOutput):\n", + " pass\n", + "\n", + "class WaitGenerator(TaskGenerator):\n", + " def _get_input(self):\n", + " return WaitInput()\n", + " def _get_output(self):\n", + " return WaitOutput()\n", + " def __iter__(self):\n", + " tasks = []\n", + " for _ in range(self.input.n):\n", + " tasks.append(t := FunctionTask(time.sleep))\n", + " t.input.args = [self.input.time]\n", + " ret, out = zip(*(yield tasks))\n", + " return ReturnStatus.done(), self._get_output()" + ] + }, + { + "cell_type": "code", + "execution_count": 139, + "id": "c4170017-0825-4e2c-87b2-ea4ddc14499e", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 2.1 ms, sys: 12.2 ms, total: 14.3 ms\n", + "Wall time: 20 s\n" + ] + }, + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None), <__main__.WaitOutput at 0x7f90c3da05b0>)" + ] + }, + "execution_count": 139, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "wait = WaitGenerator(capture_exceptions=False)\n", + "wait.input.time = 2.0\n", + "wait.execute()" + ] + }, + { + "cell_type": "code", + "execution_count": 133, + "id": "0ad95218-1e00-408e-8db3-858852d88e8f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from pyiron_contrib.tinybase.executor import BackgroundExecutor" + ] + }, + { + "cell_type": "code", + "execution_count": 137, + "id": "dc30851f-ed76-4bde-979f-9b42286b1645", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 8.73 ms, sys: 29.4 ms, total: 38.1 ms\n", + "Wall time: 20.2 s\n" + ] + }, + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None), <__main__.WaitOutput at 0x7f90c3da0190>)" + ] + }, + "execution_count": 137, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "ProcessExecutor(max_processes=1).run(wait)" + ] + }, + { + "cell_type": "code", + "execution_count": 141, + "id": "6b9393cf-3112-4055-a5cb-ebdb3b17aec6", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 12 ms, sys: 39.1 ms, total: 51.2 ms\n", + "Wall time: 6.06 s\n" + ] + }, + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None), <__main__.WaitOutput at 0x7f90c3da2d10>)" + ] + }, + "execution_count": 141, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "ProcessExecutor(max_processes=4).run(wait)" + ] + }, + { + "cell_type": "code", + "execution_count": 140, + "id": "2bf18743-4760-4491-968c-49a7968ef6cf", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 7.62 ms, sys: 2.35 ms, total: 9.97 ms\n", + "Wall time: 6.02 s\n" + ] + }, + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None), <__main__.WaitOutput at 0x7f90c3da11b0>)" + ] + }, + "execution_count": 140, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "BackgroundExecutor(max_threads=4).run(wait)" + ] } ], "metadata": { From ed29052b3620a0bf2663e791007dac260e9aeaa7 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Tue, 9 May 2023 13:52:15 +0200 Subject: [PATCH 050/756] Move MinimizeInput to container and delete from_attributes --- pyiron_contrib/tinybase/ase.py | 8 +------- pyiron_contrib/tinybase/container.py | 28 +++++----------------------- 2 files changed, 6 insertions(+), 30 deletions(-) diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index 77acc8ae6..d272c8d84 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -3,6 +3,7 @@ StorageAttribute, StructureInput, MDInput, + MinimizeInput, EnergyPotOutput, MDOutput ) @@ -75,13 +76,6 @@ def parse(): dyn.attach(parse, interval=self.input.steps // self.input.output_steps) dyn.run(self.input.steps) -MinimizeInput = AbstractInput.from_attributes( - 'MinimizeInput', - ionic_force_tolerance=float, - max_steps=int, - output_steps=int, -) - class AseMinimizeInput(AseInput, StructureInput, MinimizeInput): algo = StorageAttribute().type(str).default('LBFGS') diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index d3e892df2..99c068c81 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -96,29 +96,6 @@ def doc(self, text): self.__doc__ = text class AbstractContainer(HasStorage, HasHDFAdapaterMixin, abc.ABC): - # TODO: this should go into HasStorage, exists here only to give one location to define from_ methods - @classmethod - def from_attributes(cls, name, *attrs, module=None, bases=(), **default_attrs): - """ - Create a new sub class with given attributes. - - Args: - name (str): name of the new class - *attrs (str): names of the new attributes - module (str, optional): the module path where this class is defined; on CPython this is automagically filled - in, in other python implementations you need to manually provide this value as __name__ when you - call this method for the resulting class to be picklable. - bases (list of type): additional base classes - **default_attrs (str): names and defaults of new attributes - """ - body = {a: StorageAttribute() for a in attrs} - body.update({a: StorageAttribute().default(d) for a, d in default_attrs.items()}) - T = type(name, bases + (cls,), body) - if module is None: - # this is also how cpython does it for namedtuple - module = sys._getframe(1).f_globals['__name__'] - T.__module__ = module - return T def take(self, other: 'AbstractContainer'): # TODO: think hard about variance of types @@ -149,6 +126,11 @@ class MDInput(AbstractInput): temperature = StorageAttribute().type(float) output_steps = StorageAttribute().type(int) +class MinimizeInput(AbstractInput): + ionic_force_tolerance = StorageAttribute().type(float) + max_steps = StorageAttribute().type(int) + output_steps = StorageAttribute().type(int) + class AbstractOutput(AbstractContainer, abc.ABC): pass From 1796f7bbed07637b3455e50dba1a5c22615b8423 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 10 May 2023 13:28:27 -0700 Subject: [PATCH 051/756] Add a doc example for workflow inputs and outputs --- pyiron_contrib/workflow/workflow.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index cd41f379a..87fa10e52 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -126,6 +126,14 @@ class Workflow(HasToDict): ... y=wf.calc.outputs.temperature ... ) + The unconnected inputs and outputs of nodes belonging to a workflow can be + accessed directly via the node, or right from the workflow by combining the + node and channel labels thanks to a convenience-wrapper. Continuing the above + example, we could write... + >>> wf.structure.inputs.element = "Ni" + >>> print(type(wf.outputs.plot_fig.value)) + + TODO: Workflows can be serialized. From f09ed535fc3770078948dec24019cf5fd2a5eb1f Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 23 May 2023 15:34:20 +0000 Subject: [PATCH 052/756] add potentials.py --- .../atomistics/lammps/potentials.py | 589 ++++++++++++++++++ 1 file changed, 589 insertions(+) create mode 100644 pyiron_contrib/atomistics/lammps/potentials.py diff --git a/pyiron_contrib/atomistics/lammps/potentials.py b/pyiron_contrib/atomistics/lammps/potentials.py new file mode 100644 index 000000000..7fd929561 --- /dev/null +++ b/pyiron_contrib/atomistics/lammps/potentials.py @@ -0,0 +1,589 @@ +# coding: utf-8 +# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department +# Distributed under the terms of "New BSD License", see the LICENSE file. + +__author__ = "Sam Waseda" +__copyright__ = ( + "Copyright 2023, Max-Planck-Institut für Eisenforschung GmbH - " + "Computational Materials Design (CM) Department" +) +__version__ = "1.0" +__maintainer__ = "Sam Waseda" +__email__ = "waseda@mpie.de" +__status__ = "production" +__date__ = "April 18, 2023" + +import pandas as pd +from pyiron_atomistics.lammps.potential import LammpsPotentialFile +import numpy as np +import warnings + + +general_doc = """ +How to combine potentials: + +Example I: Hybrid potential for a single element + +>>> from pyiron_atomistics.lammps.potentials import Library, Morse +>>> eam = Library("Al") +>>> morse = Morse("Al", D_0=0.5, alpha=1.1, r_0=2.1, cutoff=6) +>>> lammps_job.potential = eam + morse + +Example II: Hybrid potential for multiple elements + +>>> from pyiron_atomistics.lammps.potentials import Library, Morse +>>> eam = Library("Al") +>>> morse_Al_Ni = Morse("Al", "Ni", D_0=0.2, alpha=1.05, r_0=2.2, cutoff=6) +>>> morse_Ni = Morse("Ni", D_0=0.7, alpha=1.15, r_0=2.15, cutoff=6) +>>> lammps_job.potential = eam + morse_Al_Ni + morse_Ni # hybrid/overlay +>>> lammps_job.potential = eam * morse_Al_Ni * morse_Ni # hybrid +>>> lammps_job.potential = 0.4 * eam + 0.1 * morse_Al_Ni + morse_Ni # hybrid/scaled + +""" + + +doc_pyiron_df = """ +- "Config": Lammps commands in a list. Lines are separated by list items and + each entry must finish with a new line +- "Filename": Potential file name in either absolute path or relative to + the pyiron-resources path (optional) +- "Model": Model name (optional) +- "Name": Name of the potential (optional) +- "Species": Order of species as defined in pair_coeff +- "Citations": Citations (optional) + +Example + +>>> import pandas as pd +>>> return pd.DataFrame( +... { +... "Config": [[ +... 'pair_style my_potential 3.2\n', +... 'pair_coeff 2 1 1.1 2.3 3.2\n' +... ]], +... "Filename": [""], +... "Model": ["my_model"], +... "Name": ["my_potential"], +... "Species": [["Fe", "Al"]], +... "Citations": [], +... } +... ) +""" + + +issue_page = "https://github.com/pyiron/pyiron_atomistics/issues" + + +class LammpsPotentials: + def __new__(cls, *args, **kwargs): + obj = super().__new__(cls) + cls._df = None + return obj + + @staticmethod + def _harmonize_species(species_symbols) -> list: + """ + Check whether species are set for the pairwise interactions. If only + one chemical species is given, duplicate the species. + """ + if len(species_symbols) == 0: + raise ValueError("Chemical elements not specified") + if len(species_symbols) == 1: + species_symbols *= 2 + return list(species_symbols) + + def _initialize_df( + self, + pair_style, + interacting_species, + pair_coeff, + preset_species=None, + model=None, + citations=None, + filename=None, + potential_name=None, + scale=None, + cutoff=None, + ): + def check_none_n_length(variable, default, length=len(pair_coeff)): + if variable is None: + variable = default + if isinstance(variable, list) and len(variable) == 1 < length: + variable = length * variable + return variable + + arg_dict = { + "pair_style": pair_style, + "interacting_species": interacting_species, + "pair_coeff": pair_coeff, + "preset_species": check_none_n_length(preset_species, [[]]), + "cutoff": check_none_n_length(cutoff, 0), + "model": check_none_n_length(model, pair_style), + "citations": check_none_n_length(citations, [[]]), + "filename": check_none_n_length(filename, [""]), + "potential_name": check_none_n_length(potential_name, pair_style), + } + if scale is not None: + arg_dict["scale"] = scale + try: + self.set_df(pd.DataFrame(arg_dict)) + except ValueError: + raise ValueError( + f"Initialization failed - inconsistency in data: {arg_dict}" + ) + + def copy(self): + new_pot = LammpsPotentials() + new_pot.set_df(self.get_df()) + return new_pot + + @staticmethod + def _unique(args): + labels, indices = np.unique(args, return_index=True) + return labels[np.argsort(indices)] + + @property + def model(self) -> str: + """Model name (required in pyiron df)""" + return "_and_".join(self._unique(self.df.model)) + + @property + def potential_name(self) -> str: + """Potential name (required in pyiron df)""" + return "_and_".join(self._unique(self.df.potential_name)) + + @property + def species(self): + """Species defined in the potential""" + species = self._unique([ss for s in self.df.interacting_species for ss in s]) + preset = self._unique( + ["___".join(s) for s in self.df.preset_species if len(s) > 0] + ) + if len(preset) == 0: + return list(species) + elif len(preset) > 1: + raise NotImplementedError( + "Currently not possible to have multiple file-based potentials" + ) + preset = list(preset)[0].split("___") + comp_lst = [s for s in species if s not in self._unique(preset)] + return [p for p in preset + comp_lst if p != "*"] + + @property + def filename(self) -> list: + """LAMMPS potential files""" + return [f for f in self._unique(self.df.filename) if len(f) > 0] + + @property + def citations(self) -> str: + """Citations to be included""" + return "".join(np.unique([c for c in self.df.citations if len(c) > 0])) + + @property + def is_scaled(self) -> bool: + """Scaling in pair_style hybrid/scaled and hybrid/overlay (which is scale=1)""" + return "scale" in self.df + + @property + def pair_style(self) -> str: + """LAMMPS pair_style""" + if len(set(self.df.pair_style)) == 1: + pair_style = "pair_style " + list(set(self.df.pair_style))[0] + if np.max(self.df.cutoff) > 0: + pair_style += f" {np.max(self.df.cutoff)}" + return pair_style + "\n" + elif "scale" not in self.df: + pair_style = "pair_style hybrid" + elif all(self.df.scale == 1): + pair_style = "pair_style hybrid/overlay" + else: + pair_style = "pair_style hybrid/scaled" + for ii, s in enumerate(self.df[["pair_style", "cutoff"]].values): + if pair_style.startswith("pair_style hybrid/scaled"): + pair_style += f" {self.df.iloc[ii].scale}" + pair_style += f" {s[0]}" + if s[1] > 0: + pair_style += f" {s[1]}" + return pair_style + "\n" + + class _PairCoeff: + def __init__( + self, + is_hybrid, + pair_style, + interacting_species, + pair_coeff, + species, + preset_species, + ): + self.is_hybrid = is_hybrid + self._interacting_species = interacting_species + self._pair_coeff = pair_coeff + self._species = species + self._preset_species = preset_species + self._pair_style = pair_style + + @property + def counter(self): + """ + Enumeration of potentials if a potential is used multiple + times in hybrid (which is a requirement from LAMMPS) + """ + key, count = np.unique(self._pair_style, return_counts=True) + counter = {kk: 1 for kk in key[count > 1]} + results = [] + for coeff in self._pair_style: + if coeff in counter and self.is_hybrid: + results.append(str(counter[coeff])) + counter[coeff] += 1 + else: + results.append("") + return results + + @property + def pair_style(self): + """pair_style to be output only in hybrid""" + if self.is_hybrid: + return self._pair_style + else: + return len(self._pair_style) * [""] + + @property + def results(self): + """pair_coeff lines to be used in pyiron df""" + return [ + " ".join((" ".join(("pair_coeff",) + c)).split()) + "\n" + for c in zip( + self.interacting_species, + self.pair_style, + self.counter, + self.pair_coeff, + ) + ] + + @property + def interacting_species(self) -> list: + """ + Species in LAMMPS notation (i.e. in numbers instead of chemical + symbols) + """ + s_dict = dict( + zip(self._species, (np.arange(len(self._species)) + 1).astype(str)) + ) + s_dict.update({"*": "*"}) + return [ + " ".join([s_dict[cc] for cc in c]) for c in self._interacting_species + ] + + @property + def pair_coeff(self) -> list: + """ + Args for pair_coeff. Elements defined in EAM files are + complemented with the ones defined in other potentials in the + case of hybrid (filled with NULL) + """ + if not self.is_hybrid: + return self._pair_coeff + results = [] + for pc, ps in zip(self._pair_coeff, self._preset_species): + if len(ps) > 0 and "eam" in pc: + s = " ".join(ps + (len(self._species) - len(ps)) * ["NULL"]) + pc = pc.replace(" ".join(ps), s) + results.append(pc) + return results + + @property + def pair_coeff(self) -> list: + """LAMMPS pair_coeff""" + + return self._PairCoeff( + is_hybrid="hybrid" in self.pair_style, + pair_style=self.df.pair_style, + interacting_species=self.df.interacting_species, + pair_coeff=self.df.pair_coeff, + species=self.species, + preset_species=self.df.preset_species, + ).results + + def get_df(self): + """df used in pyiron potential""" + return pd.DataFrame( + { + "Config": [[self.pair_style] + self.pair_coeff], + "Filename": [self.filename], + "Model": [self.model], + "Name": [self.potential_name], + "Species": [self.species], + "Citations": [self.citations], + } + ) + + def __repr__(self): + return self.df.__repr__() + + def _repr_html_(self): + return self.df._repr_html_() + + def set_df(self, df): + for key in [ + "pair_style", + "interacting_species", + "pair_coeff", + "preset_species", + ]: + if key not in df: + raise ValueError(f"{key} missing") + self._df = df + + @property + def df(self): + """DataFrame containing all info for each pairwise interactions""" + return self._df + + def get_df(self, default_scale=None): + if default_scale is None or "scale" in self.df: + return self.df.copy() + df = self.df.copy() + df["scale"] = default_scale + return df + + def __mul__(self, scale_or_potential): + if isinstance(scale_or_potential, LammpsPotentials): + if self.is_scaled or scale_or_potential.is_scaled: + raise ValueError("You cannot mix hybrid types") + new_pot = LammpsPotentials() + new_pot.set_df( + pd.concat( + (self.get_df(), scale_or_potential.get_df()), ignore_index=True + ) + ) + return new_pot + if self.is_scaled: + raise NotImplementedError("Currently you cannot scale twice") + new_pot = self.copy() + new_pot.df["scale"] = scale_or_potential + return new_pot + + __rmul__ = __mul__ + + def __add__(self, potential): + new_pot = LammpsPotentials() + new_pot.set_df( + pd.concat( + (self.get_df(default_scale=1), potential.get_df(default_scale=1)), + ignore_index=True, + ) + ) + return new_pot + + +class Library(LammpsPotentials): + """ + Potential class to choose a file based potential from an existing library + (e.g. EAM). + You can either specify the chemical species and/or the name of the + potential. + + Example I: Via chemical species + + >>> eam = Library("Al") + + Example II: Via potential name + + >>> eam = Library(name="1995--Angelo-J-E--Ni-Al-H--LAMMPS--ipr1") + + If the variable `eam` is used without specifying the potential name (i.e. + in Example I), the first potential in the database corresponding with the + specified chemical species will be selected. In order to see the list of + potentials, you can also execute + + >>> eam = Library("Al") + >>> eam.list_potentials() # See list of potential names + >>> eam.view_potentials() # See potential names and metadata + + """ + + def __init__(self, *chemical_elements, name=None): + """ + Args: + chemical_elements (str): chemical elements/species + name (str): potential name in the database + """ + if name is not None: + self._df_candidates = LammpsPotentialFile().find_by_name(name) + else: + self._df_candidates = LammpsPotentialFile().find(list(chemical_elements)) + + @staticmethod + def _get_pair_style(config): + if any(["hybrid" in c for c in config]): + return [c.split()[3] for c in config if "pair_coeff" in c] + for c in config: + if "pair_style" in c: + return [" ".join(c.replace("\n", "").split()[1:])] * sum( + ["pair_coeff" in c for c in config] + ) + raise ValueError( + f""" + pair_style could not determined: {config}. + + The reason why you are seeing this error is most likely because + the potential you chose had a corrupt config. It is + supposed to have at least one item which starts with "pair_style". + If you are using the standard pyiron database, feel free to + submit an issue on {issue_page} + Typically you can get a reply within 24h. + """ + ) + + @staticmethod + def _get_pair_coeff(config): + try: + if any(["hybrid" in c for c in config]): + return [" ".join(c.split()[4:]) for c in config if "pair_coeff" in c] + return [" ".join(c.split()[3:]) for c in config if "pair_coeff" in c] + except IndexError: + raise AssertionError( + f"{config} does not follow the format 'pair_coeff element_1 element_2 args'" + ) + + @staticmethod + def _get_interacting_species(config, species): + def _convert(c, s): + if c == "*": + return c + return s[int(c) - 1] + + return [ + [_convert(cc, species) for cc in c.split()[1:3]] + for c in config + if c.startswith("pair_coeff") + ] + + @staticmethod + def _get_scale(config): + for c in config: + if not c.startswith("pair_style"): + continue + if "hybrid/overlay" in c: + return 1 + elif "hybrid/scaled" in c: + raise NotImplementedError( + "Too much work for something inexistent in pyiron database for now" + ) + return + + def list_potentials(self): + return self._df_candidates.Name + + def view_potentials(self): + return self._df_candidates + + @property + def df(self): + if self._df is None: + df = self._df_candidates.iloc[0] + if len(self._df_candidates) > 1: + warnings.warn( + f"Potential not uniquely specified - use default {df.Name}" + ) + self._initialize_df( + pair_style=self._get_pair_style(df.Config), + interacting_species=self._get_interacting_species( + df.Config, df.Species + ), + pair_coeff=self._get_pair_coeff(df.Config), + preset_species=[df.Species], + model=df.Model, + citations=df.Citations, + filename=[df.Filename], + potential_name=df.Name, + scale=self._get_scale(df.Config), + ) + return self._df + + +def check_cutoff(f): + def wrapper(*args, **kwargs): + if "cutoff" not in kwargs or kwargs["cutoff"] == 0: + raise ValueError( + f""" + It is not possible to set cutoff=0 for parameter-based + potentials. If you think this should be possible, you have the + following options: + + - Open an issue on our GitHub page: {issue_page} + + - Write your own potential in pyiron format. Here's how: + + {doc_pyiron_df} + """ + ) + return f(*args, **kwargs) + + return wrapper + + +class Morse(LammpsPotentials): + """ + Morse potential defined by: + + E = D_0*[exp(-2*alpha*(r-r_0))-2*exp(-alpha*(r-r_0))] + """ + + @check_cutoff + def __init__(self, *chemical_elements, D_0, alpha, r_0, cutoff, pair_style="morse"): + """ + Args: + chemical_elements (str): Chemical elements + D_0 (float): parameter (s. eq. above) + alpha (float): parameter (s. eq. above) + r_0 (float): parameter (s. eq. above) + cutoff (float): cutoff length + pair_style (str): pair_style name (default: "morse") + + Example: + + >>> morse = Morse("Al", "Ni", D_0=1, alpha=0.5, r_0=2, cutoff=6) + """ + self._initialize_df( + pair_style=[pair_style], + interacting_species=[self._harmonize_species(chemical_elements)], + pair_coeff=[" ".join([str(cc) for cc in [D_0, alpha, r_0, cutoff]])], + cutoff=cutoff, + ) + + +Morse.__doc__ += general_doc + + +class CustomPotential(LammpsPotentials): + """ + Custom potential class to define LAMMPS potential not implemented in + pyiron + """ + + @check_cutoff + def __init__(self, pair_style, *chemical_elements, cutoff, **kwargs): + """ + Args: + pair_style (str): pair_style name (default: "morse") + chemical_elements (str): Chemical elements + cutoff (float): cutoff length + + Example: + + >>> custom_pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) + + Important: the order of parameters is conserved in the LAMMPS input + (except for `cutoff`, which is always the last argument). + """ + self._initialize_df( + pair_style=[pair_style], + interacting_species=[self._harmonize_species(chemical_elements)], + pair_coeff=[" ".join([str(cc) for cc in kwargs.values()]) + f" {cutoff}"], + cutoff=cutoff, + ) + + +CustomPotential.__doc__ += general_doc From 02822576f0f35bee58014e765a24278f571ef2a9 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 24 May 2023 08:37:14 -0700 Subject: [PATCH 053/756] Update workflow docs for most recent functionality of the Workflow --- pyiron_contrib/workflow/workflow.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 84775b14d..fc79c3bc2 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -86,13 +86,19 @@ class Workflow(HasToDict): 0, 1, 2 We can also use pre-built nodes, e.g. - >>> from pyiron_contrib.workflow.node_library import atomistics, standard - >>> >>> wf = Workflow("with_prebuilt") >>> - >>> wf.structure = atomistics.BulkStructure(repeat=3, cubic=True, element="Al") + >>> wf.structure = wf.add.atomistics.BulkStructure( + ... repeat=3, + ... cubic=True, + ... element="Al" + ... ) >>> wf.engine = atomistics.Lammps(structure=wf.structure) - >>> wf.calc = atomistics.CalcMD(job=wf.engine) + >>> wf.calc = atomistics.CalcMd( + ... job=wf.engine, + ... run_on_updates=True, + ... update_on_instantiation=True, + ... ) >>> wf.plot = standard.Scatter( ... x=wf.calc.outputs.steps, ... y=wf.calc.outputs.temperature From 60254151692cbd40bbba4469d30e6999dac6d62c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 24 May 2023 10:38:20 -0700 Subject: [PATCH 054/756] Slide the IO example up --- pyiron_contrib/workflow/workflow.py | 34 ++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index fc79c3bc2..bac6d293f 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -85,6 +85,32 @@ class Workflow(HasToDict): >>> print(wf.my_node.inputs.x, wf.my_node0.inputs.x, wf.my_node1.inputs.x) 0, 1, 2 + The `Workflow` class is designed as a single point of entry for workflows, so + you can also access decorators to define new node classes right from the + workflow (cf. the `Node` docs for more detail on the node types). + Let's use these to explore a workflow's input and output, which are dynamically + generated from the unconnected IO of its nodes: + >>> @Workflow.wrap_as.fast_node("y") + >>> def plus_one(x: int = 0): + ... return x + 1 + >>> + >>> wf = Workflow("io_workflow") + >>> wf.first = plus_one() + >>> wf.second = plus_one() + >>> print(len(wf.inputs), len(wf.outputs)) + 2 2 + + If we connect the output of one node to the input of the other, there are fewer + dangling channels for the workflow IO to find: + >>> wf.second.inputs.x = wf.first.outputs.y + >>> print(len(wf.inputs), len(wf.outputs)) + 1 1 + + The workflow joins node lavels and channel labels with a `_` character to + provide direct access to the output: + >>> print(wf.outputs.second_y.value) + 2 + We can also use pre-built nodes, e.g. >>> wf = Workflow("with_prebuilt") >>> @@ -104,14 +130,6 @@ class Workflow(HasToDict): ... y=wf.calc.outputs.temperature ... ) - The unconnected inputs and outputs of nodes belonging to a workflow can be - accessed directly via the node, or right from the workflow by combining the - node and channel labels thanks to a convenience-wrapper. Continuing the above - example, we could write... - >>> wf.structure.inputs.element = "Ni" - >>> print(type(wf.outputs.plot_fig.value)) - - TODO: Workflows can be serialized. From 7962a25e67d729efecba704538b9e79d925c0419 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 24 May 2023 10:39:07 -0700 Subject: [PATCH 055/756] Smooth transition --- pyiron_contrib/workflow/workflow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index bac6d293f..9ac75044d 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -111,7 +111,8 @@ class Workflow(HasToDict): >>> print(wf.outputs.second_y.value) 2 - We can also use pre-built nodes, e.g. + Workflows also give access to packages of pre-built nodes under different + namespaces, e.g. >>> wf = Workflow("with_prebuilt") >>> >>> wf.structure = wf.add.atomistics.BulkStructure( From 312338122916ef45825815def1e465edce43ebe2 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 24 May 2023 10:39:25 -0700 Subject: [PATCH 056/756] Add TODO --- pyiron_contrib/workflow/workflow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 9ac75044d..ba460416f 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -131,6 +131,7 @@ class Workflow(HasToDict): ... y=wf.calc.outputs.temperature ... ) + TODO: Registration of new node packages TODO: Workflows can be serialized. From bf54863eeb1a3c521e27b607cef5c59095fe305e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 11:57:06 +0000 Subject: [PATCH 057/756] Bump boto3 from 1.26.137 to 1.26.142 Bumps [boto3](https://github.com/boto/boto3) from 1.26.137 to 1.26.142. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.26.137...1.26.142) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4ad40350e..8bbf5b364 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ ], 'image': ['scikit-image==0.19.3'], 'generic': [ - 'boto3==1.26.137', + 'boto3==1.26.142', 'moto==4.1.10' ], 'workflow': [ From 50954df057a60c7942de4d2a4d0653c9004d0e17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 11:57:10 +0000 Subject: [PATCH 058/756] Bump pyiron-base from 0.5.38 to 0.5.39 Bumps [pyiron-base](https://github.com/pyiron/pyiron_base) from 0.5.38 to 0.5.39. - [Release notes](https://github.com/pyiron/pyiron_base/releases) - [Changelog](https://github.com/pyiron/pyiron_base/blob/main/CHANGELOG.md) - [Commits](https://github.com/pyiron/pyiron_base/compare/pyiron_base-0.5.38...pyiron_base-0.5.39) --- updated-dependencies: - dependency-name: pyiron-base dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4ad40350e..1c1b2140c 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ install_requires=[ 'matplotlib==3.7.1', 'numpy==1.24.3', - 'pyiron_base==0.5.38', + 'pyiron_base==0.5.39', 'scipy==1.10.1', 'seaborn==0.12.2', 'pyparsing==3.0.9' From 78bcda8773caac782ab567a91d9c06ded811405e Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Mon, 29 May 2023 21:19:47 -0600 Subject: [PATCH 059/756] Update environment.yml --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 4ebfd03a7..f883a1c4e 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.137 +- boto3 =1.26.142 - moto =4.1.10 - pycp2k =0.2.2 - typeguard =4.0.0 From a878c5fc0f8bcf17b31f976bc7a72c0837ad8bc9 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Mon, 29 May 2023 21:20:12 -0600 Subject: [PATCH 060/756] Update environment.yml --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 4ebfd03a7..f17c8b726 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -8,7 +8,7 @@ dependencies: - ipython - matplotlib =3.7.1 - numpy =1.24.3 -- pyiron_base =0.5.38 +- pyiron_base =0.5.39 - pyiron_atomistics =0.2.67 - pyparsing =3.0.9 - scipy =1.10.1 From 80f653dbc9b62611d00a36b56733a08cf81cb052 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 30 May 2023 03:21:53 +0000 Subject: [PATCH 061/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index c3beeda8e..d1287ff94 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.137 +- boto3 =1.26.142 - moto =4.1.10 - pycp2k =0.2.2 - typeguard =4.0.0 diff --git a/docs/environment.yml b/docs/environment.yml index f1f4242c2..2a305791c 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -17,7 +17,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.137 +- boto3 =1.26.142 - moto =4.1.10 - pycp2k =0.2.2 - typeguard =4.0.0 From e8ba41100d9a6fb6553e2f4b7ccb6b6bd2c03e76 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 30 May 2023 03:22:09 +0000 Subject: [PATCH 062/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index c3beeda8e..294b4a79b 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -8,7 +8,7 @@ dependencies: - ipython - matplotlib =3.7.1 - numpy =1.24.3 -- pyiron_base =0.5.38 +- pyiron_base =0.5.39 - pyiron_atomistics =0.2.67 - pyparsing =3.0.9 - scipy =1.10.1 diff --git a/docs/environment.yml b/docs/environment.yml index f1f4242c2..18248ebff 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -10,7 +10,7 @@ dependencies: - ipython - matplotlib =3.7.1 - numpy =1.24.3 -- pyiron_base =0.5.38 +- pyiron_base =0.5.39 - pyiron_atomistics =0.2.67 - pyparsing =3.0.9 - scipy =1.10.1 From ad0819814773fd2ebdf600b2b1979d138df600ee Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 30 May 2023 06:42:25 +0000 Subject: [PATCH 063/756] add tests --- tests/unit/atomistics/test_potentials.py | 167 +++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 tests/unit/atomistics/test_potentials.py diff --git a/tests/unit/atomistics/test_potentials.py b/tests/unit/atomistics/test_potentials.py new file mode 100644 index 000000000..158f96ee1 --- /dev/null +++ b/tests/unit/atomistics/test_potentials.py @@ -0,0 +1,167 @@ +# coding: utf-8 +# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department +# Distributed under the terms of "New BSD License", see the LICENSE file. + +from pyiron_atomistics.lammps.potentials import Library, Morse, CustomPotential, LammpsPotentials +import unittest +import pandas as pd +import numpy as np + + +class TestPotentials(unittest.TestCase): + def test_harmonize_species(self): + pot = LammpsPotentials() + self.assertEqual(pot._harmonize_species(("Al",)), ["Al", "Al"]) + for i in [2, 3, 4]: + self.assertEqual(pot._harmonize_species(i * ("Al",)), i * ["Al"]) + self.assertRaises(ValueError, pot._harmonize_species, tuple()) + + def test_set_df(self): + pot = LammpsPotentials() + self.assertEqual(pot.df, None) + required_keys = [ + "pair_style", + "interacting_species", + "pair_coeff", + "preset_species", + ] + arg_dict = {k: [] for k in required_keys} + pot.set_df(pd.DataFrame(arg_dict)) + self.assertIsInstance(pot.df, pd.DataFrame) + for key in required_keys: + arg_dict = {k: [] for k in required_keys if k != key} + self.assertRaises(ValueError, pot.set_df, pd.DataFrame(arg_dict)) + + def test_initialize_df(self): + pot = LammpsPotentials() + pot._initialize_df( + pair_style=["some_potential"], + interacting_species=[["Al", "Al"]], + pair_coeff=["something"], + ) + self.assertIsInstance(pot.df, pd.DataFrame) + self.assertEqual(len(pot.df), 1) + self.assertEqual(pot.df.iloc[0].pair_style, "some_potential") + self.assertEqual(pot.df.iloc[0].interacting_species, ["Al", "Al"]) + self.assertEqual(pot.df.iloc[0].pair_coeff, "something") + self.assertEqual(pot.df.iloc[0].preset_species, []) + self.assertEqual(pot.df.iloc[0].cutoff, 0) + self.assertEqual(pot.df.iloc[0].model, "some_potential") + self.assertEqual(pot.df.iloc[0].citations, []) + self.assertEqual(pot.df.iloc[0].filename, "") + self.assertEqual(pot.df.iloc[0].potential_name, "some_potential") + with self.assertRaises(ValueError): + pot._initialize_df( + pair_style=["some_potential", "one_too_many"], + interacting_species=[["Al", "Al"]], + pair_coeff=["something"], + ) + + def test_custom_potential(self): + pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) + self.assertEqual(pot.df.iloc[0].pair_coeff, "0.5 1 3") + + def test_copy(self): + pot_1 = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) + pot_2 = pot_1.copy() + self.assertTrue(np.all(pot_1.df == pot_2.df)) + pot_2.df.cutoff = 1 + self.assertFalse(np.all(pot_1.df == pot_2.df)) + + def test_model(self): + pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) + self.assertEqual(pot.model, "lj/cut") + pot = LammpsPotentials() + pot._initialize_df( + pair_style=["a", "b"], + interacting_species=[["Al"], ["Ni"]], + pair_coeff=["one", "two"], + model=["first", "second"] + ) + self.assertEqual(pot.model, "first_and_second") + + def test_potential_name(self): + pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) + self.assertEqual(pot.potential_name, "lj/cut") + pot = LammpsPotentials() + pot._initialize_df( + pair_style=["a", "b"], + interacting_species=[["Al"], ["Ni"]], + pair_coeff=["one", "two"], + potential_name=["first", "second"] + ) + self.assertEqual(pot.potential_name, "first_and_second") + + def test_is_scaled(self): + pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) + self.assertFalse(pot.is_scaled) + + def test_unique(self): + pot = LammpsPotentials() + self.assertEqual(pot._unique([1, 0, 2, 1, 3]).tolist(), [1, 0, 2, 3]) + + def test_pair_style(self): + pot = LammpsPotentials() + pot._initialize_df( + pair_style=["a"], + interacting_species=[["Al"]], + pair_coeff=["one"], + potential_name=["first"] + ) + self.assertEqual(pot.pair_style, "pair_style a\n") + pot._initialize_df( + pair_style=["a", "b"], + interacting_species=[["Al"], ["Ni"]], + pair_coeff=["one", "two"], + potential_name=["first", "second"] + ) + self.assertEqual(pot.pair_style, "pair_style hybrid a b\n") + pot._initialize_df( + pair_style=["a", "b"], + interacting_species=[["Al"], ["Ni"]], + pair_coeff=["one", "two"], + potential_name=["first", "second"], + cutoff=[1, 1], + ) + self.assertEqual(pot.pair_style, "pair_style hybrid a 1 b 1\n") + pot._initialize_df( + pair_style=["a", "b"], + interacting_species=[["Al"], ["Ni"]], + pair_coeff=["one", "two"], + potential_name=["first", "second"], + scale=[1, 1], + cutoff=[1, 1], + ) + self.assertEqual(pot.pair_style, "pair_style hybrid/overlay a 1 b 1\n") + pot._initialize_df( + pair_style=["a", "b"], + interacting_species=[["Al"], ["Ni"]], + pair_coeff=["one", "two"], + potential_name=["first", "second"], + scale=[1, 0.5], + cutoff=[1, 1], + ) + self.assertEqual(pot.pair_style, "pair_style hybrid/scaled 1.0 a 1 0.5 b 1\n") + pot._initialize_df( + pair_style=["a", "a"], + interacting_species=[["Al"], ["Ni"]], + pair_coeff=["one", "two"], + potential_name=["first", "second"], + ) + self.assertEqual(pot.pair_style, "pair_style a\n") + + def test_PairCoeff(self): + pot = LammpsPotentials() + pc = pot._PairCoeff( + is_hybrid=False, + pair_style=["my_style"], + interacting_species=[["Al", "Fe"]], + pair_coeff=["some arguments"], + species=["Al", "Fe"], + preset_species=[], + ) + self.assertEqual(pc.counter, [""]) + + +if __name__ == "__main__": + unittest.main() From e6f59c32166344016a7d625692db2f3e1df11c6b Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 31 May 2023 07:16:13 +0000 Subject: [PATCH 064/756] replace only the last occurrence of elements --- .../atomistics/lammps/potentials.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pyiron_contrib/atomistics/lammps/potentials.py b/pyiron_contrib/atomistics/lammps/potentials.py index 7fd929561..0a413549a 100644 --- a/pyiron_contrib/atomistics/lammps/potentials.py +++ b/pyiron_contrib/atomistics/lammps/potentials.py @@ -126,7 +126,7 @@ def check_none_n_length(variable, default, length=len(pair_coeff)): if scale is not None: arg_dict["scale"] = scale try: - self.set_df(pd.DataFrame(arg_dict)) + self.set_data(pd.DataFrame(arg_dict)) except ValueError: raise ValueError( f"Initialization failed - inconsistency in data: {arg_dict}" @@ -134,7 +134,7 @@ def check_none_n_length(variable, default, length=len(pair_coeff)): def copy(self): new_pot = LammpsPotentials() - new_pot.set_df(self.get_df()) + new_pot.set_data(self.get_all_data()) return new_pot @staticmethod @@ -288,7 +288,7 @@ def pair_coeff(self) -> list: for pc, ps in zip(self._pair_coeff, self._preset_species): if len(ps) > 0 and "eam" in pc: s = " ".join(ps + (len(self._species) - len(ps)) * ["NULL"]) - pc = pc.replace(" ".join(ps), s) + pc = s.join(pc.rsplit(" ".join(ps), 1)) results.append(pc) return results @@ -324,7 +324,7 @@ def __repr__(self): def _repr_html_(self): return self.df._repr_html_() - def set_df(self, df): + def set_data(self, df): for key in [ "pair_style", "interacting_species", @@ -340,7 +340,7 @@ def df(self): """DataFrame containing all info for each pairwise interactions""" return self._df - def get_df(self, default_scale=None): + def get_all_data(self, default_scale=None): if default_scale is None or "scale" in self.df: return self.df.copy() df = self.df.copy() @@ -352,9 +352,10 @@ def __mul__(self, scale_or_potential): if self.is_scaled or scale_or_potential.is_scaled: raise ValueError("You cannot mix hybrid types") new_pot = LammpsPotentials() - new_pot.set_df( + new_pot.set_data( pd.concat( - (self.get_df(), scale_or_potential.get_df()), ignore_index=True + (self.get_all_data(), scale_or_potential.get_all_data()), + ignore_index=True ) ) return new_pot @@ -368,9 +369,9 @@ def __mul__(self, scale_or_potential): def __add__(self, potential): new_pot = LammpsPotentials() - new_pot.set_df( + new_pot.set_data( pd.concat( - (self.get_df(default_scale=1), potential.get_df(default_scale=1)), + (self.get_all_data(default_scale=1), potential.get_all_data(default_scale=1)), ignore_index=True, ) ) @@ -496,7 +497,7 @@ def df(self): preset_species=[df.Species], model=df.Model, citations=df.Citations, - filename=[df.Filename], + filename=df.Filename, potential_name=df.Name, scale=self._get_scale(df.Config), ) From c11757c80d5fc8f3e6d7dda2d084240a59bcb7d7 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 31 May 2023 07:37:27 +0000 Subject: [PATCH 065/756] update tets --- tests/unit/atomistics/{ => lammps}/test_potentials.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename tests/unit/atomistics/{ => lammps}/test_potentials.py (96%) diff --git a/tests/unit/atomistics/test_potentials.py b/tests/unit/atomistics/lammps/test_potentials.py similarity index 96% rename from tests/unit/atomistics/test_potentials.py rename to tests/unit/atomistics/lammps/test_potentials.py index 158f96ee1..a3b6702a9 100644 --- a/tests/unit/atomistics/test_potentials.py +++ b/tests/unit/atomistics/lammps/test_potentials.py @@ -2,7 +2,7 @@ # Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department # Distributed under the terms of "New BSD License", see the LICENSE file. -from pyiron_atomistics.lammps.potentials import Library, Morse, CustomPotential, LammpsPotentials +from pyiron_contrib.atomistics.lammps.potentials import Library, Morse, CustomPotential, LammpsPotentials import unittest import pandas as pd import numpy as np @@ -26,11 +26,11 @@ def test_set_df(self): "preset_species", ] arg_dict = {k: [] for k in required_keys} - pot.set_df(pd.DataFrame(arg_dict)) + pot.set_data(pd.DataFrame(arg_dict)) self.assertIsInstance(pot.df, pd.DataFrame) for key in required_keys: arg_dict = {k: [] for k in required_keys if k != key} - self.assertRaises(ValueError, pot.set_df, pd.DataFrame(arg_dict)) + self.assertRaises(ValueError, pot.set_data, pd.DataFrame(arg_dict)) def test_initialize_df(self): pot = LammpsPotentials() From b869d7d21a41d67fc29029743bd5088eef313e8c Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 31 May 2023 13:38:35 +0000 Subject: [PATCH 066/756] Format black --- pyiron_contrib/atomistics/lammps/potentials.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/atomistics/lammps/potentials.py b/pyiron_contrib/atomistics/lammps/potentials.py index 0a413549a..45993be36 100644 --- a/pyiron_contrib/atomistics/lammps/potentials.py +++ b/pyiron_contrib/atomistics/lammps/potentials.py @@ -355,7 +355,7 @@ def __mul__(self, scale_or_potential): new_pot.set_data( pd.concat( (self.get_all_data(), scale_or_potential.get_all_data()), - ignore_index=True + ignore_index=True, ) ) return new_pot @@ -371,7 +371,10 @@ def __add__(self, potential): new_pot = LammpsPotentials() new_pot.set_data( pd.concat( - (self.get_all_data(default_scale=1), potential.get_all_data(default_scale=1)), + ( + self.get_all_data(default_scale=1), + potential.get_all_data(default_scale=1), + ), ignore_index=True, ) ) From 1796554f3f8c9f9c48681908740401336b0d2388 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 31 May 2023 15:10:25 +0000 Subject: [PATCH 067/756] remove redundant is_hybrid --- pyiron_contrib/atomistics/lammps/potentials.py | 7 ++++--- tests/unit/atomistics/lammps/test_potentials.py | 10 ++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pyiron_contrib/atomistics/lammps/potentials.py b/pyiron_contrib/atomistics/lammps/potentials.py index 0a413549a..d470748a2 100644 --- a/pyiron_contrib/atomistics/lammps/potentials.py +++ b/pyiron_contrib/atomistics/lammps/potentials.py @@ -209,20 +209,22 @@ def pair_style(self) -> str: class _PairCoeff: def __init__( self, - is_hybrid, pair_style, interacting_species, pair_coeff, species, preset_species, ): - self.is_hybrid = is_hybrid self._interacting_species = interacting_species self._pair_coeff = pair_coeff self._species = species self._preset_species = preset_species self._pair_style = pair_style + @property + def is_hybrid(self): + return len(set(self._pair_style)) > 1 + @property def counter(self): """ @@ -297,7 +299,6 @@ def pair_coeff(self) -> list: """LAMMPS pair_coeff""" return self._PairCoeff( - is_hybrid="hybrid" in self.pair_style, pair_style=self.df.pair_style, interacting_species=self.df.interacting_species, pair_coeff=self.df.pair_coeff, diff --git a/tests/unit/atomistics/lammps/test_potentials.py b/tests/unit/atomistics/lammps/test_potentials.py index a3b6702a9..e51b4822f 100644 --- a/tests/unit/atomistics/lammps/test_potentials.py +++ b/tests/unit/atomistics/lammps/test_potentials.py @@ -150,10 +150,12 @@ def test_pair_style(self): ) self.assertEqual(pot.pair_style, "pair_style a\n") - def test_PairCoeff(self): - pot = LammpsPotentials() - pc = pot._PairCoeff( - is_hybrid=False, +class TestPairCoeff(unittest.TestCase): + def setUp(cls): + cls.pot = LammpsPotentials() + + def test_counter(self): + pc = self.pot._PairCoeff( pair_style=["my_style"], interacting_species=[["Al", "Fe"]], pair_coeff=["some arguments"], From 933e75030e54ead96e36eb7a86730130e0c88556 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 1 Jun 2023 06:05:39 +0000 Subject: [PATCH 068/756] define defined_pairs --- .../atomistics/lammps/potentials.py | 48 +++++++++++++++++-- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/atomistics/lammps/potentials.py b/pyiron_contrib/atomistics/lammps/potentials.py index d470748a2..7d6f7494b 100644 --- a/pyiron_contrib/atomistics/lammps/potentials.py +++ b/pyiron_contrib/atomistics/lammps/potentials.py @@ -17,6 +17,7 @@ from pyiron_atomistics.lammps.potential import LammpsPotentialFile import numpy as np import warnings +import itertools general_doc = """ @@ -220,6 +221,7 @@ def __init__( self._species = species self._preset_species = preset_species self._pair_style = pair_style + self._s_dict = None @property def is_hybrid(self): @@ -263,18 +265,54 @@ def results(self): ) ] + @property + def defined_pairs(self): + all_pair = [] + for int_s, pre_s in zip( + self._interacting_species, self._preset_species + ): + pair = [] + for ss in int_s: + if ss == "*": + pair.append(pre_s) + else: + pair.append([ss]) + all_pair.extend( + np.unique( + [sorted([p1, p2]) for p1 in pair[0] for p2 in pair[1]], + axis=0 + ).tolist() + ) + return all_pair + + @property + def undefined_pairs(self): + all_pairs = [ + sorted(s) + for s in itertools.combinations_with_replacement( + self._species, 2 + ) + ] + return [p for p in all_pairs if p not in self.defined_pairs] + + @property + def s_dict(self): + if self._s_dict is None: + self._s_dict = dict( + zip(self._species, (np.arange(len(self._species)) + 1).astype(str)) + ) + self._s_dict.update({"*": "*"}) + return self._s_dict + @property def interacting_species(self) -> list: """ Species in LAMMPS notation (i.e. in numbers instead of chemical symbols) """ - s_dict = dict( - zip(self._species, (np.arange(len(self._species)) + 1).astype(str)) - ) - s_dict.update({"*": "*"}) return [ - " ".join([s_dict[cc] for cc in c]) for c in self._interacting_species + " ".join([self.s_dict[cc] for cc in c]) + for c in self._interacting_species ] @property From 3f1bedf4a577b8bd692cb9a422b936d76a83799b Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 1 Jun 2023 12:50:23 +0000 Subject: [PATCH 069/756] add a few tests --- .../unit/atomistics/lammps/test_potentials.py | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/tests/unit/atomistics/lammps/test_potentials.py b/tests/unit/atomistics/lammps/test_potentials.py index e51b4822f..0e3520194 100644 --- a/tests/unit/atomistics/lammps/test_potentials.py +++ b/tests/unit/atomistics/lammps/test_potentials.py @@ -154,15 +154,50 @@ class TestPairCoeff(unittest.TestCase): def setUp(cls): cls.pot = LammpsPotentials() - def test_counter(self): + def test_counter_and_hybrid(self): pc = self.pot._PairCoeff( pair_style=["my_style"], interacting_species=[["Al", "Fe"]], pair_coeff=["some arguments"], species=["Al", "Fe"], - preset_species=[], + preset_species=[[]], ) self.assertEqual(pc.counter, [""]) + self.assertFalse(pc.is_hybrid) + pc = self.pot._PairCoeff( + pair_style=2 * ["my_style"], + interacting_species=2 * [["Al", "Fe"]], + pair_coeff=2 * ["some arguments"], + species=["Al", "Fe"], + preset_species=2 * [[]], + ) + self.assertEqual(pc.counter, ["", ""]) + self.assertFalse(pc.is_hybrid) + pc = self.pot._PairCoeff( + pair_style=2 * ["my_style"] + ["another_style"], + interacting_species=3 * [["Al", "Fe"]], + pair_coeff=3 * ["some arguments"], + species=["Al", "Fe"], + preset_species=3 * [[]], + ) + self.assertEqual(pc.counter, ["1", "2", ""]) + self.assertTrue(pc.is_hybrid) + + def test_results(self): + pc = self.pot._PairCoeff( + pair_style=["style_one", "style_two"], + interacting_species=[["Al", "Fe"], ["Al", "Al"]], + pair_coeff=["arg_one", "arg_two"], + species=["Al", "Fe"], + preset_species=2 * [[]], + ) + self.assertEqual( + pc.results, + [ + "pair_coeff 1 2 style_one arg_one\n", + "pair_coeff 1 1 style_two arg_two\n" + ] + ) if __name__ == "__main__": From 95e2a863a5f0e42348463d0b142c6abb3ab40360 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 1 Jun 2023 13:23:23 +0000 Subject: [PATCH 070/756] add a few tests --- tests/unit/atomistics/lammps/test_potentials.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/unit/atomistics/lammps/test_potentials.py b/tests/unit/atomistics/lammps/test_potentials.py index 0e3520194..b6dd0ce65 100644 --- a/tests/unit/atomistics/lammps/test_potentials.py +++ b/tests/unit/atomistics/lammps/test_potentials.py @@ -154,7 +154,7 @@ class TestPairCoeff(unittest.TestCase): def setUp(cls): cls.pot = LammpsPotentials() - def test_counter_and_hybrid(self): + def test_hybrid(self): pc = self.pot._PairCoeff( pair_style=["my_style"], interacting_species=[["Al", "Fe"]], @@ -164,6 +164,7 @@ def test_counter_and_hybrid(self): ) self.assertEqual(pc.counter, [""]) self.assertFalse(pc.is_hybrid) + self.assertEqual(pc.pair_style, [""]) pc = self.pot._PairCoeff( pair_style=2 * ["my_style"], interacting_species=2 * [["Al", "Fe"]], @@ -173,6 +174,7 @@ def test_counter_and_hybrid(self): ) self.assertEqual(pc.counter, ["", ""]) self.assertFalse(pc.is_hybrid) + self.assertEqual(pc.pair_style, ["", ""]) pc = self.pot._PairCoeff( pair_style=2 * ["my_style"] + ["another_style"], interacting_species=3 * [["Al", "Fe"]], @@ -182,6 +184,7 @@ def test_counter_and_hybrid(self): ) self.assertEqual(pc.counter, ["1", "2", ""]) self.assertTrue(pc.is_hybrid) + self.assertEqual(pc.pair_style, ["my_style", "my_style", "another_style"]) def test_results(self): pc = self.pot._PairCoeff( From 8e3d6f41c62a786957bec256ecbf8bcee6635c23 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 1 Jun 2023 14:03:12 +0000 Subject: [PATCH 071/756] more tests --- tests/unit/atomistics/lammps/test_potentials.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/unit/atomistics/lammps/test_potentials.py b/tests/unit/atomistics/lammps/test_potentials.py index b6dd0ce65..a626c17d0 100644 --- a/tests/unit/atomistics/lammps/test_potentials.py +++ b/tests/unit/atomistics/lammps/test_potentials.py @@ -202,6 +202,14 @@ def test_results(self): ] ) + def test_pairs(self): + pc = self.pot._PairCoeff( + pair_style=["style_one", "style_two"], + interacting_species=[["Al", "Fe"], ["Al", "Al"]], + pair_coeff=["arg_one", "arg_two"], + species=["Al", "Fe"], + preset_species=2 * [[]], + ) if __name__ == "__main__": unittest.main() From 5042f12cc2ae0f0531c60617a65cb1342f09a6b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 11:57:30 +0000 Subject: [PATCH 072/756] Bump moto from 4.1.10 to 4.1.11 Bumps [moto](https://github.com/getmoto/moto) from 4.1.10 to 4.1.11. - [Changelog](https://github.com/getmoto/moto/blob/master/CHANGELOG.md) - [Commits](https://github.com/getmoto/moto/compare/4.1.10...4.1.11) --- updated-dependencies: - dependency-name: moto dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 13dbab09d..12a4ade28 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ 'image': ['scikit-image==0.19.3'], 'generic': [ 'boto3==1.26.142', - 'moto==4.1.10' + 'moto==4.1.11' ], 'workflow': [ 'python>=3.10', From 7eca3297f0e84c1d2f44f02b85897f8b2d2fb69e Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Mon, 5 Jun 2023 07:13:43 -0600 Subject: [PATCH 073/756] Update environment.yml --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 11f62871e..5879ff2da 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -16,7 +16,7 @@ dependencies: - scikit-image =0.19.3 - randspg =0.0.1 - boto3 =1.26.142 -- moto =4.1.10 +- moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.65.0 From 8ef3b71c028bb180db9365b1cd94690321a5d2d7 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 5 Jun 2023 13:14:05 +0000 Subject: [PATCH 074/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 4310597f0..595da9e13 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -16,7 +16,7 @@ dependencies: - scikit-image =0.19.3 - randspg =0.0.1 - boto3 =1.26.142 -- moto =4.1.10 +- moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.65.0 diff --git a/docs/environment.yml b/docs/environment.yml index 057a26f60..3f1be7f7d 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -18,7 +18,7 @@ dependencies: - scikit-image =0.19.3 - randspg =0.0.1 - boto3 =1.26.142 -- moto =4.1.10 +- moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.65.0 From dff538f48b1a2f231eb6772ade6ea06974f4fa02 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Mon, 5 Jun 2023 08:35:41 -0600 Subject: [PATCH 075/756] Update environment.yml --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 5879ff2da..570be9e60 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -19,4 +19,4 @@ dependencies: - moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 -- aws-sam-translator =1.65.0 +- aws-sam-translator =1.68.0 From 8b81cc28b32089e4092f3eefb9327f30537e9d5a Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 5 Jun 2023 14:36:07 +0000 Subject: [PATCH 076/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 595da9e13..0b402d154 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -19,6 +19,6 @@ dependencies: - moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 -- aws-sam-translator =1.65.0 +- aws-sam-translator =1.68.0 - python >= 3.10 - lammps diff --git a/docs/environment.yml b/docs/environment.yml index 3f1be7f7d..c367dbe7a 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -21,4 +21,4 @@ dependencies: - moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 -- aws-sam-translator =1.65.0 +- aws-sam-translator =1.68.0 From 7b8bd4dedd64b69983a234d5e26fcfae8ecc9651 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 15:10:13 +0000 Subject: [PATCH 077/756] Bump boto3 from 1.26.142 to 1.26.146 Bumps [boto3](https://github.com/boto/boto3) from 1.26.142 to 1.26.146. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.26.142...1.26.146) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 12a4ade28..4f3f37ecd 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ ], 'image': ['scikit-image==0.19.3'], 'generic': [ - 'boto3==1.26.142', + 'boto3==1.26.146', 'moto==4.1.11' ], 'workflow': [ From de7d268a68e18e89bb9914bf72718b5f17e2e8ba Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Mon, 5 Jun 2023 09:16:00 -0600 Subject: [PATCH 078/756] Update environment.yml --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 570be9e60..8f6c1c6fc 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.142 +- boto3 =1.26.146 - moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 From 7ce7361c15e36b84cb10cb7a2a07f7dc402cf037 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 5 Jun 2023 15:16:21 +0000 Subject: [PATCH 079/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 0b402d154..1fb9c8033 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.142 +- boto3 =1.26.146 - moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 diff --git a/docs/environment.yml b/docs/environment.yml index c367dbe7a..29c2560e8 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -17,7 +17,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.142 +- boto3 =1.26.146 - moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 From 13c8ab42375528b2578597276dc474d6b63e027b Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 5 Jun 2023 11:59:37 -0700 Subject: [PATCH 080/756] Extract processing output So it can be registered as a callback --- pyiron_contrib/workflow/node.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index e0d5cb99d..bd6b25265 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -485,6 +485,18 @@ def run(self) -> None: self.failed = True raise e + self.process_output(function_output) + + def process_output(self, function_output): + """ + Take the results of the node function, and use them to update the node. + + By extracting this as a separate method, we allow the node to pass the actual + execution off to another entity and release the python process to do other + things. In such a case, this function should be registered as a callback + so that the node can finishing "running" and push its data forward when that + execution is finished. + """ if len(self.outputs) == 1: function_output = (function_output,) From 22d6c9f3c19abc2f94933fe0a21f961fb7dd6e35 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 5 Jun 2023 12:02:19 -0700 Subject: [PATCH 081/756] Pre-emptively add a server attribute We can settle on a name and exact behaviour later --- pyiron_contrib/workflow/node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index bd6b25265..a494c91d0 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -328,6 +328,7 @@ def __init__( self.running = False self.failed = False + self.server = None # Or "task_manager" or "executor" -- we'll see what's best self.node_function = node_function self.label = label if label is not None else node_function.__name__ From 2543756af6f335460c11f8a71ce74e9cf4e9b386 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 5 Jun 2023 12:04:53 -0700 Subject: [PATCH 082/756] Wrap execution in a server check --- pyiron_contrib/workflow/node.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index a494c91d0..e0fb93c54 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -479,14 +479,20 @@ def run(self) -> None: self.running = True self.failed = False - try: - function_output = self.node_function(**self.inputs.to_value_dict()) - except Exception as e: - self.running = False - self.failed = True - raise e - - self.process_output(function_output) + if self.server is None: + try: + function_output = self.node_function(**self.inputs.to_value_dict()) + except Exception as e: + self.running = False + self.failed = True + raise e + self.process_output(function_output) + else: + raise NotImplementedError( + "We currently only support executing the node functionality right on " + "the main python process that the node instance lives on. Come back " + "later for cool new features." + ) def process_output(self, function_output): """ From b19b8af7ec30603adec288006d9ebd2aa66471e9 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 6 Jun 2023 19:19:51 +0000 Subject: [PATCH 083/756] add self --- pyiron_contrib/workflow/node.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index e0fb93c54..78d1c3d7d 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -364,12 +364,17 @@ def __init__( if update_on_instantiation: self.update() + @property + def _input_args(self): + return inspect.signature(self.node_function).parameters + def _build_input_channels(self, storage_priority: dict[str:int]): channels = [] type_hints = get_type_hints(self.node_function) - parameters = inspect.signature(self.node_function).parameters - for label, value in parameters.items(): + for label, value in self._input_args.items(): + if label == "self": + continue if label in self._init_keywords: # We allow users to parse arbitrary kwargs as channel initialization # So don't let them choose bad channel names @@ -481,7 +486,10 @@ def run(self) -> None: if self.server is None: try: - function_output = self.node_function(**self.inputs.to_value_dict()) + if "self" in self._input_args: + function_output = self.node_function(self=self, **self.inputs.to_value_dict()) + else: + function_output = self.node_function(**self.inputs.to_value_dict()) except Exception as e: self.running = False self.failed = True From d9159833484489a1561b4120cdd39bd7a068ed65 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 6 Jun 2023 19:36:09 +0000 Subject: [PATCH 084/756] add tests --- tests/unit/workflow/test_node.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/unit/workflow/test_node.py b/tests/unit/workflow/test_node.py index 4e051e8a0..987f073da 100644 --- a/tests/unit/workflow/test_node.py +++ b/tests/unit/workflow/test_node.py @@ -19,6 +19,10 @@ def no_default(x, y): return x + y + 1 +def with_self(self, x: float) -> float: + return x + 0.1 + + @skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestNode(TestCase): def test_defaults(self): @@ -156,6 +160,11 @@ def test_statuses(self): # self.assertFalse(n.running) self.assertFalse(n.failed, msg="Re-running should reset failed status") + def test_with_self(self): + node = Node(with_self, "output") + self.assertTrue("x" in node.inputs.labels) + self.assertFalse("x" in node.inputs.labels) + @skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestFastNode(TestCase): From 49c928d47de143d340c981f79fdaf266dc2d639a Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Wed, 7 Jun 2023 15:30:08 +0200 Subject: [PATCH 085/756] Make run optional in TrainingContainer.sample --- .../atomistics/atomistics/job/trainingcontainer.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/atomistics/atomistics/job/trainingcontainer.py b/pyiron_contrib/atomistics/atomistics/job/trainingcontainer.py index f708c7faa..5d22fbdaf 100644 --- a/pyiron_contrib/atomistics/atomistics/job/trainingcontainer.py +++ b/pyiron_contrib/atomistics/atomistics/job/trainingcontainer.py @@ -246,18 +246,20 @@ def sample( name: str, selector: Callable[[StructureStorage, int], bool], delete_existing_job: bool = False, + run: bool = True ) -> "TrainingContainer": """ Create a new TrainingContainer with structures filtered by selector. `self` must have status `finished`. `selector` is passed the underlying :class:`StructureStorage` of this container and the index of the structure and return a boolean whether to include the structure in the new - container or not. The new container is saved and run. + container or not. By default the new container is saved and run. Args: name (str): name of the new TrainingContainer selector (Callable[[StructureStorage, int], bool]): callable that selects structure to include delete_existing_job (bool): if job with name exist, remove it first + run (bool): if True, immediately run and save the job. Returns: :class:`.TrainingContainer`: new container with selected structures @@ -273,7 +275,8 @@ def sample( if not cont.status.initialized: raise ValueError(f"Job '{name}' already exists with status: {cont.status}!") cont._container = self._container.sample(selector) - cont.run() + if run: + cont.run() return cont @property From 0eb50b8705a0733adf2d6cbf94f58a70d3c4f1d3 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 7 Jun 2023 15:02:42 +0000 Subject: [PATCH 086/756] Format black --- pyiron_contrib/atomistics/atomistics/job/trainingcontainer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/atomistics/atomistics/job/trainingcontainer.py b/pyiron_contrib/atomistics/atomistics/job/trainingcontainer.py index 5d22fbdaf..1bea25243 100644 --- a/pyiron_contrib/atomistics/atomistics/job/trainingcontainer.py +++ b/pyiron_contrib/atomistics/atomistics/job/trainingcontainer.py @@ -246,7 +246,7 @@ def sample( name: str, selector: Callable[[StructureStorage, int], bool], delete_existing_job: bool = False, - run: bool = True + run: bool = True, ) -> "TrainingContainer": """ Create a new TrainingContainer with structures filtered by selector. From 720474085c544f26c74fa3fc8d3f482d96008e60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 11:57:33 +0000 Subject: [PATCH 087/756] Bump boto3 from 1.26.146 to 1.26.151 Bumps [boto3](https://github.com/boto/boto3) from 1.26.146 to 1.26.151. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.26.146...1.26.151) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4f3f37ecd..73ee76ce9 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ ], 'image': ['scikit-image==0.19.3'], 'generic': [ - 'boto3==1.26.146', + 'boto3==1.26.151', 'moto==4.1.11' ], 'workflow': [ From c685cc4505c02bf56bdcf249c64cd785a32197b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 11:57:41 +0000 Subject: [PATCH 088/756] Bump pyiron-base from 0.5.39 to 0.6.1 Bumps [pyiron-base](https://github.com/pyiron/pyiron_base) from 0.5.39 to 0.6.1. - [Release notes](https://github.com/pyiron/pyiron_base/releases) - [Changelog](https://github.com/pyiron/pyiron_base/blob/main/CHANGELOG.md) - [Commits](https://github.com/pyiron/pyiron_base/compare/pyiron_base-0.5.39...pyiron_base-0.6.1) --- updated-dependencies: - dependency-name: pyiron-base dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4f3f37ecd..4b6b06453 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ install_requires=[ 'matplotlib==3.7.1', 'numpy==1.24.3', - 'pyiron_base==0.5.39', + 'pyiron_base==0.6.1', 'scipy==1.10.1', 'seaborn==0.12.2', 'pyparsing==3.0.9' From 7c265eb5fdceeade459895f8b3c9722bc7811213 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 12 Jun 2023 11:57:54 +0000 Subject: [PATCH 089/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 8f6c1c6fc..8e919fc30 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.146 +- boto3 =1.26.151 - moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 From 4921b203185d51cf27a00b860a5585a9abeda671 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 12 Jun 2023 11:58:02 +0000 Subject: [PATCH 090/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 8f6c1c6fc..50925ab0a 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -8,7 +8,7 @@ dependencies: - ipython - matplotlib =3.7.1 - numpy =1.24.3 -- pyiron_base =0.5.39 +- pyiron_base =0.6.1 - pyiron_atomistics =0.2.67 - pyparsing =3.0.9 - scipy =1.10.1 From d4821d8d6299eab31f4dbfa2d1de042cfcdfc6d5 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 12 Jun 2023 11:58:18 +0000 Subject: [PATCH 091/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 1fb9c8033..f846bf01e 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.146 +- boto3 =1.26.151 - moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 diff --git a/docs/environment.yml b/docs/environment.yml index 29c2560e8..0e2446899 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -17,7 +17,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.146 +- boto3 =1.26.151 - moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 From 4d3ca21b51696cc163634d3c01f92cb269b5f32a Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 12 Jun 2023 11:58:25 +0000 Subject: [PATCH 092/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 1fb9c8033..e2603537a 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -8,7 +8,7 @@ dependencies: - ipython - matplotlib =3.7.1 - numpy =1.24.3 -- pyiron_base =0.5.39 +- pyiron_base =0.6.1 - pyiron_atomistics =0.2.67 - pyparsing =3.0.9 - scipy =1.10.1 diff --git a/docs/environment.yml b/docs/environment.yml index 29c2560e8..b0329ba9d 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -10,7 +10,7 @@ dependencies: - ipython - matplotlib =3.7.1 - numpy =1.24.3 -- pyiron_base =0.5.39 +- pyiron_base =0.6.1 - pyiron_atomistics =0.2.67 - pyparsing =3.0.9 - scipy =1.10.1 From 34019777908f73d41ba0f78e88739677e672a5d3 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 12 Jun 2023 15:10:05 +0000 Subject: [PATCH 093/756] add more tests --- tests/unit/workflow/test_node.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/unit/workflow/test_node.py b/tests/unit/workflow/test_node.py index 987f073da..d5392e54c 100644 --- a/tests/unit/workflow/test_node.py +++ b/tests/unit/workflow/test_node.py @@ -1,4 +1,4 @@ -from unittest import TestCase, skipUnless +import unittest from sys import version_info from typing import Optional, Union @@ -23,8 +23,8 @@ def with_self(self, x: float) -> float: return x + 0.1 -@skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestNode(TestCase): +@unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") +class TestNode(unittest.TestCase): def test_defaults(self): Node(plus_one, "y") @@ -163,11 +163,14 @@ def test_statuses(self): def test_with_self(self): node = Node(with_self, "output") self.assertTrue("x" in node.inputs.labels) - self.assertFalse("x" in node.inputs.labels) + self.assertFalse("self" in node.inputs.labels) + node.inputs.x = 1 + node.run() + self.assertEqual(node.outputs.output.value, 1.1) -@skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestFastNode(TestCase): +@unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") +class TestFastNode(unittest.TestCase): def test_instantiation(self): has_defaults_is_ok = FastNode(plus_one, "y") @@ -175,8 +178,8 @@ def test_instantiation(self): missing_defaults_should_fail = FastNode(no_default, "z") -@skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestSingleValueNode(TestCase): +@unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") +class TestSingleValueNode(unittest.TestCase): def test_instantiation(self): has_defaults_and_one_return = SingleValueNode(plus_one, "y") @@ -319,3 +322,7 @@ def my_node(x: int = 0, y: int = 0, z: int = 0): n.inputs.z.waiting_for_update, msg="After the run, all three should now be waiting for updates again" ) + + +if __name__ == '__main__': + unittest.main() From 06a889ebe86f8c7a43906c24c4fde67c4f456df0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 17:35:29 +0000 Subject: [PATCH 094/756] Bump pyiron-atomistics from 0.2.67 to 0.3.0 Bumps [pyiron-atomistics](https://github.com/pyiron/pyiron_atomistics) from 0.2.67 to 0.3.0. - [Release notes](https://github.com/pyiron/pyiron_atomistics/releases) - [Changelog](https://github.com/pyiron/pyiron_atomistics/blob/main/CHANGELOG.md) - [Commits](https://github.com/pyiron/pyiron_atomistics/compare/pyiron_atomistics-0.2.67...pyiron_atomistics-0.3.0) --- updated-dependencies: - dependency-name: pyiron-atomistics dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b5fb8524b..450512b78 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ extras_require={ 'atomistic': [ 'ase==3.22.1', - 'pyiron_atomistics==0.2.67', + 'pyiron_atomistics==0.3.0', 'pycp2k==0.2.2', ], 'fenics': [ From e4d101e95a11497d382a619dd1f11ef9a42d7442 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 12 Jun 2023 17:35:53 +0000 Subject: [PATCH 095/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index c3b73e962..af5f36407 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -9,7 +9,7 @@ dependencies: - matplotlib =3.7.1 - numpy =1.24.3 - pyiron_base =0.6.1 -- pyiron_atomistics =0.2.67 +- pyiron_atomistics =0.3.0 - pyparsing =3.0.9 - scipy =1.10.1 - seaborn =0.12.2 From 593fa1267b105b90e4b2e8a3ea9db8f957799977 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 12 Jun 2023 17:36:29 +0000 Subject: [PATCH 096/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index f5655ded6..c566205e6 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -9,7 +9,7 @@ dependencies: - matplotlib =3.7.1 - numpy =1.24.3 - pyiron_base =0.6.1 -- pyiron_atomistics =0.2.67 +- pyiron_atomistics =0.3.0 - pyparsing =3.0.9 - scipy =1.10.1 - seaborn =0.12.2 diff --git a/docs/environment.yml b/docs/environment.yml index 6e9278405..7963ebbf6 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -11,7 +11,7 @@ dependencies: - matplotlib =3.7.1 - numpy =1.24.3 - pyiron_base =0.6.1 -- pyiron_atomistics =0.2.67 +- pyiron_atomistics =0.3.0 - pyparsing =3.0.9 - scipy =1.10.1 - seaborn =0.12.2 From 424a437b5353e926125f6baebdfc3a14e784065f Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 13 Jun 2023 07:55:46 +0000 Subject: [PATCH 097/756] check positions of self --- pyiron_contrib/workflow/node.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 78d1c3d7d..22267c9ff 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -372,9 +372,17 @@ def _build_input_channels(self, storage_priority: dict[str:int]): channels = [] type_hints = get_type_hints(self.node_function) - for label, value in self._input_args.items(): + for ii, (label, value) in enumerate(self._input_args.items()): if label == "self": - continue + if ii == 0: + continue + else: + warnings.warn( + "`self` is used as an argument but not in the first" + " position, so it is treated as a normal function" + " argument. If it is to be treated as the node object," + " use it as a first argument" + ) if label in self._init_keywords: # We allow users to parse arbitrary kwargs as channel initialization # So don't let them choose bad channel names From 77d7a7bcfe81be18d3b7338b972e6209d1437f2d Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 13 Jun 2023 08:14:19 +0000 Subject: [PATCH 098/756] add tests --- tests/unit/workflow/test_node.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/unit/workflow/test_node.py b/tests/unit/workflow/test_node.py index d5392e54c..8933d2de1 100644 --- a/tests/unit/workflow/test_node.py +++ b/tests/unit/workflow/test_node.py @@ -1,6 +1,7 @@ import unittest from sys import version_info from typing import Optional, Union +import warnings from pyiron_contrib.workflow.node import ( FastNode, Node, SingleValueNode, node, single_value_node @@ -19,10 +20,6 @@ def no_default(x, y): return x + y + 1 -def with_self(self, x: float) -> float: - return x + 0.1 - - @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestNode(unittest.TestCase): def test_defaults(self): @@ -161,12 +158,21 @@ def test_statuses(self): self.assertFalse(n.failed, msg="Re-running should reset failed status") def test_with_self(self): + def with_self(self, x: float) -> float: + return x + 0.1 node = Node(with_self, "output") self.assertTrue("x" in node.inputs.labels) self.assertFalse("self" in node.inputs.labels) node.inputs.x = 1 node.run() self.assertEqual(node.outputs.output.value, 1.1) + def with_messed_self(x: float, self) -> float: + return x + 0.1 + with warnings.catch_warnings(record=True) as warning_list: + node = Node(with_messed_self, "output") + self.assertTrue("self" in node.inputs.labels) + self.assertEqual(len(warning_list), 1) + @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") From 1443ab7b00c32e932c6fb8b727c448a7c2e57113 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 13 Jun 2023 10:10:31 -0700 Subject: [PATCH 099/756] Refactor: extract method --- .../workflow/node_library/atomistics.py | 96 ++++++++++++------- 1 file changed, 64 insertions(+), 32 deletions(-) diff --git a/pyiron_contrib/workflow/node_library/atomistics.py b/pyiron_contrib/workflow/node_library/atomistics.py index 12663edf1..e8dda8e4d 100644 --- a/pyiron_contrib/workflow/node_library/atomistics.py +++ b/pyiron_contrib/workflow/node_library/atomistics.py @@ -24,42 +24,27 @@ def lammps(structure: Optional[Atoms] = None) -> LammpsJob: return job -@node( - "cells", - "displacements", - "energy_pot", - "energy_tot", - "force_max", - "forces", - "indices", - "positions", - "pressures", - "steps", - "temperature", - "total_displacements", - "unwrapped_positions", - "volume", -) -def calc_md( - job: AtomisticGenericJob, - n_ionic_steps: int = 1000, - n_print: int = 100, - temperature: int | float = 300.0, - pressure: float - | tuple[float, float, float] - | tuple[float, float, float, float, float, float] - | None = None, -): +def _run_and_remove_job(job, modifier: Optional[callable] = None, **modifier_kwargs): + """ + Extracts the commonalities for all the "calc" methods for running a Lammps engine. + Will need to be extended/updated once we support other engines so that more output + can be parsed. Output may wind up more concretely packaged, e.g. as `CalcOutput` or + `MDOutput`, etc., ala Joerg's suggestion later, so for the time being we don't put + too much effort into this. + + Warning: + Jobs are created in a dummy project with a dummy name and are all removed at the + end; this works fine for serial workflows, but will need to be revisited -- + probably with naming based on the parantage of node/workflow labels -- once + other non-serial execution is introduced. + """ job_name = "JUSTAJOBNAME" pr = Project("WORKFLOWNAMEPROJECT") job = job.copy_to(project=pr, new_job_name=job_name, delete_existing_job=True) - job.calc_md( - n_ionic_steps=n_ionic_steps, - n_print=n_print, - temperature=temperature, - pressure=pressure, - ) + if modifier is not None: + job = modifier(job, **modifier_kwargs) job.run() + cells = job.output.cells displacements = job.output.displacements energy_pot = job.output.energy_pot @@ -74,8 +59,10 @@ def calc_md( total_displacements = job.output.total_displacements unwrapped_positions = job.output.unwrapped_positions volume = job.output.volume + job.remove() pr.remove(enable=True) + return ( cells, displacements, @@ -94,6 +81,51 @@ def calc_md( ) +@node( + "cells", + "displacements", + "energy_pot", + "energy_tot", + "force_max", + "forces", + "indices", + "positions", + "pressures", + "steps", + "temperature", + "total_displacements", + "unwrapped_positions", + "volume", +) +def calc_md( + job: AtomisticGenericJob, + n_ionic_steps: int = 1000, + n_print: int = 100, + temperature: int | float = 300.0, + pressure: float + | tuple[float, float, float] + | tuple[float, float, float, float, float, float] + | None = None, +): + def calc_md(job, n_ionic_steps, n_print, temperature, pressure): + job.calc_md( + n_ionic_steps=n_ionic_steps, + n_print=n_print, + temperature=temperature, + pressure=pressure, + ) + return job + + return _run_and_remove_job( + job=job, + modifier=calc_md, + n_ionic_steps=n_ionic_steps, + n_print=n_print, + temperature=temperature, + pressure=pressure + ) + + nodes = [ bulk_structure, calc_md, From b6435faa31537eeba6045bd19f95113118367710 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 13 Jun 2023 10:15:48 -0700 Subject: [PATCH 100/756] add calc static --- .../workflow/node_library/atomistics.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/pyiron_contrib/workflow/node_library/atomistics.py b/pyiron_contrib/workflow/node_library/atomistics.py index e8dda8e4d..12bf492d0 100644 --- a/pyiron_contrib/workflow/node_library/atomistics.py +++ b/pyiron_contrib/workflow/node_library/atomistics.py @@ -81,6 +81,42 @@ def _run_and_remove_job(job, modifier: Optional[callable] = None, **modifier_kwa ) +@node( + "cells", + "displacements", + "energy_pot", + "energy_tot", + "force_max", + "forces", + "indices", + "positions", + "pressures", + "steps", + "temperature", + "total_displacements", + "unwrapped_positions", + "volume", +) +def calc_static( + job: AtomisticGenericJob, + n_ionic_steps: int = 1000, + n_print: int = 100, + temperature: int | float = 300.0, + pressure: float + | tuple[float, float, float] + | tuple[float, float, float, float, float, float] + | None = None, +): + return _run_and_remove_job( + job=job, + modifier=calc_md, + n_ionic_steps=n_ionic_steps, + n_print=n_print, + temperature=temperature, + pressure=pressure + ) + + @node( "cells", "displacements", From 114101169a7ab2f1e150d04065b9442e3c5ab790 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 13 Jun 2023 10:16:05 -0700 Subject: [PATCH 101/756] Make the node adder return the new node on call --- pyiron_contrib/workflow/workflow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index ba460416f..b9af9bf63 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -31,7 +31,7 @@ def __getattribute__(self, key): return value def __call__(self, node: Node): - self._workflow.add_node(node) + return self._workflow.add_node(node) class _NodeDecoratorAccess: @@ -180,6 +180,7 @@ def add_node(self, node: Node, label: str = None) -> None: self.nodes[label] = node node.label = label node.workflow = self + return node def _ensure_node_belongs_to_at_most_this_workflow(self, node: Node, label: str): if ( From 738e1ab9663917cdc6a1fe1d5e967062db278af5 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 13 Jun 2023 10:27:52 -0700 Subject: [PATCH 102/756] Include calc_static in the returned library --- pyiron_contrib/workflow/node_library/atomistics.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyiron_contrib/workflow/node_library/atomistics.py b/pyiron_contrib/workflow/node_library/atomistics.py index 12bf492d0..5d7cb3e7e 100644 --- a/pyiron_contrib/workflow/node_library/atomistics.py +++ b/pyiron_contrib/workflow/node_library/atomistics.py @@ -165,5 +165,6 @@ def calc_md(job, n_ionic_steps, n_print, temperature, pressure): nodes = [ bulk_structure, calc_md, + calc_static, lammps, ] From 78f109ee519a9aa1a67194d268e009e78f5be43d Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 13 Jun 2023 10:30:41 -0700 Subject: [PATCH 103/756] Fix calc_static signature and function --- .../workflow/node_library/atomistics.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/pyiron_contrib/workflow/node_library/atomistics.py b/pyiron_contrib/workflow/node_library/atomistics.py index 5d7cb3e7e..94ab35878 100644 --- a/pyiron_contrib/workflow/node_library/atomistics.py +++ b/pyiron_contrib/workflow/node_library/atomistics.py @@ -99,22 +99,8 @@ def _run_and_remove_job(job, modifier: Optional[callable] = None, **modifier_kwa ) def calc_static( job: AtomisticGenericJob, - n_ionic_steps: int = 1000, - n_print: int = 100, - temperature: int | float = 300.0, - pressure: float - | tuple[float, float, float] - | tuple[float, float, float, float, float, float] - | None = None, ): - return _run_and_remove_job( - job=job, - modifier=calc_md, - n_ionic_steps=n_ionic_steps, - n_print=n_print, - temperature=temperature, - pressure=pressure - ) + return _run_and_remove_job(job=job) @node( From 99878695b77b9e212f69bf866c8b1fe9aecf8de8 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 16 Jun 2023 11:34:07 +0200 Subject: [PATCH 104/756] Fix typo --- pyiron_contrib/tinybase/task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py index 2114eb13c..cbf1f5c17 100644 --- a/pyiron_contrib/tinybase/task.py +++ b/pyiron_contrib/tinybase/task.py @@ -270,7 +270,7 @@ class SeriesInput(AbstractInput): connections = StorageAttribute().type(list) def check_ready(self): - return len(self.tasks) == len(connections) + 1 + return len(self.tasks) == len(self.connections) + 1 def first(self, task): """ From 5fdd122822433487fe9923cd55fbb67fe9fb7980 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 16 Jun 2023 12:23:03 +0200 Subject: [PATCH 105/756] Return correct task in SeriesTask --- pyiron_contrib/tinybase/task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py index cbf1f5c17..efe9c9cfa 100644 --- a/pyiron_contrib/tinybase/task.py +++ b/pyiron_contrib/tinybase/task.py @@ -324,7 +324,7 @@ def __iter__(self): for task, connection in zip(self.input.tasks[1:], self.input.connections): connection(task.input, out) - (ret, out), *_ = yield [self.input.tasks[0]] + (ret, out), *_ = yield [task] if not ret.is_done(): return ReturnStatus("aborted", ret), None From ad0d7389e56a1d1d15e0aef3affee52bc29b1443 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 16 Jun 2023 11:14:32 +0200 Subject: [PATCH 106/756] Add support for task generators to executor contexts Previously executor contexts would execute task generators as if they were a task, i.e. in serial. With this the futures based context can peek inside a generator and create sub contexts for each list of parallel tasks yielded by a generator. --- pyiron_contrib/tinybase/executor.py | 40 +++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index 7352cff60..e52f653d7 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -1,6 +1,7 @@ import abc import enum from collections import defaultdict +from functools import partial from typing import Union, List import time import logging @@ -167,6 +168,8 @@ def __init__(self, pool, tasks): # self._max_tasks = max_tasks if max_tasks is not None else 4 self._done = 0 self._futures = {} + self._subcontexts = {} + self._generators = {} self._status = {} self._output = {} self._index = {} @@ -176,12 +179,35 @@ def _process_future(self, future): task = self._futures[future] status, output = future.result(timeout=0) - self._status[task] = status - self._output[task] = output with self._lock: + self._status[task] = status + self._output[task] = output self._done += 1 self._check_finish() + def _prepare_subcontext(self, task, sub_tasks): + sub = self._subcontexts[task] = type(self)(self._pool, sub_tasks) + sub._run_machine.observe( + "finished", + partial(self._process_generator, task) + ) + sub.run() + return sub + + def _process_generator(self, task, _data): + gen = self._generators[task] + sub = self._subcontexts[task] + try: + tasks = gen.send(list(zip(sub.status, sub.output))) + self._prepare_subcontext(task, tasks) + except StopIteration as stop: + with self._lock: + self._status[task], self._output[task] = stop.args[0] + self._done += 1 + del self._subcontexts[task] + del self._generators[task] + self._check_finish() + def _check_finish(self, log=False): with self._lock: if self._done == len(self.tasks): @@ -195,10 +221,14 @@ def _check_finish(self, log=False): def _run_running(self): if len(self._futures) == 0: for i, task in enumerate(self.tasks): - future = self._pool.submit(run_task, task) - self._futures[future] = task self._index[task] = i - future.add_done_callback(self._process_future) + if isinstance(task, TaskGenerator): + gen = self._generators[task] = iter(task) + self._prepare_subcontext(task, next(gen)) + else: + future = self._pool.submit(run_task, task) + self._futures[future] = task + future.add_done_callback(self._process_future) else: logging.info("Some tasks are still executing!") From e0f5a0e9b47fb9edb2e0cc227124229cfa9c9150 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 16 Jun 2023 15:30:25 +0200 Subject: [PATCH 107/756] Remove Executor.run It was an ugly hack only because the futures context did not handle TaskGenerator well. This is fixed now, so there is no need for it anymore. Updated notebook examples accordingly. --- notebooks/tinybase/ASE.ipynb | 1406 ++++++++++++--------------- notebooks/tinybase/Basic.ipynb | 380 ++++---- notebooks/tinybase/Shell.ipynb | 52 +- pyiron_contrib/tinybase/executor.py | 14 - 4 files changed, 891 insertions(+), 961 deletions(-) diff --git a/notebooks/tinybase/ASE.ipynb b/notebooks/tinybase/ASE.ipynb index c62d97cc8..eb9c7a483 100644 --- a/notebooks/tinybase/ASE.ipynb +++ b/notebooks/tinybase/ASE.ipynb @@ -60,10 +60,30 @@ " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" ] }, + { + "data": { + "text/html": [ + "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "cb10962527db4539a876c77ee57c6d35",
+       "model_id": "0f2d12d00fbe46c4ba16c668713b921c",
        "version_major": 2,
        "version_minor": 0
       },
@@ -170,7 +190,7 @@
      "data": {
       "text/plain": [
        "(ReturnStatus(Code.DONE, None),\n",
-       " )"
+       " )"
       ]
      },
      "execution_count": 11,
@@ -290,7 +310,7 @@
      "data": {
       "text/plain": [
        "{'status': [ReturnStatus(Code.DONE, None)],\n",
-       " 'output': []}"
+       " 'output': []}"
       ]
      },
      "execution_count": 18,
@@ -336,7 +356,7 @@
     {
      "data": {
       "text/plain": [
-       "10.869684777004295"
+       "10.69577894699978"
       ]
      },
      "execution_count": 20,
@@ -359,7 +379,7 @@
     {
      "data": {
       "text/plain": [
-       "3.451299562584609e-05"
+       "1.5048000932438299e-05"
       ]
      },
      "execution_count": 21,
@@ -384,26 +404,26 @@
       "text/plain": [
        "[-303.20813267693006,\n",
        " -303.20813267693006,\n",
-       " -298.21204372431737,\n",
-       " -299.88503154534726,\n",
-       " -300.931999137925,\n",
-       " -299.44502827869934,\n",
-       " -299.9918148425856,\n",
-       " -300.1553044042918,\n",
-       " -301.1881436730182,\n",
-       " -300.28494441522173,\n",
-       " -300.3765641855429,\n",
-       " -299.5227415338809,\n",
-       " -300.1677455878805,\n",
-       " -300.5454274254439,\n",
-       " -299.6345772520824,\n",
-       " -300.22105101161634,\n",
-       " -299.54260700848215,\n",
-       " -299.30378229965436,\n",
-       " -301.70385283576184,\n",
-       " -300.2260190182359,\n",
-       " -300.1547113482648,\n",
-       " -299.89127173589293]"
+       " -299.82357747361414,\n",
+       " -300.6835289315925,\n",
+       " -300.43053461438194,\n",
+       " -299.5151491098048,\n",
+       " -299.54357181795604,\n",
+       " -300.3822199456922,\n",
+       " -300.3933038182373,\n",
+       " -300.5594238753886,\n",
+       " -300.3492771122286,\n",
+       " -299.94782851220407,\n",
+       " -300.34637783367367,\n",
+       " -300.07711314103085,\n",
+       " -300.39471385080583,\n",
+       " -300.22032558954106,\n",
+       " -299.5895737832703,\n",
+       " -300.31533838531095,\n",
+       " -299.59517640853767,\n",
+       " -300.11269823052316,\n",
+       " -300.3264067673155,\n",
+       " -299.99353276482634]"
       ]
      },
      "execution_count": 22,
@@ -425,7 +445,7 @@
    "outputs": [
     {
      "data": {
-      "image/png": "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\n",
+      "image/png": "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\n",
       "text/plain": [
        "
" ] @@ -449,7 +469,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "ca55e9934a7f4ddfbfcba49067007e5d", + "model_id": "ba0fa6e6b6f34818a1a1eea7b99067f9", "version_major": 2, "version_minor": 0 }, @@ -533,7 +553,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e14d2befd7114406b7faf407a16b32c8", + "model_id": "c9536e6d518f4c5faaaae8bb83ad3ba3", "version_major": 2, "version_minor": 0 }, @@ -600,10 +620,10 @@ "output_type": "stream", "text": [ " Step Time Energy fmax\n", - "GPMin: 0 22:29:32 11.122159 187.2462\n", - "GPMin: 1 22:29:32 -0.278268 1.5338\n", - "GPMin: 2 22:29:32 -0.996055 0.8010\n", - "GPMin: 3 22:29:32 -0.000000 0.0000\n" + "GPMin: 0 15:23:56 11.122159 187.2462\n", + "GPMin: 1 15:23:56 -0.278268 1.5338\n", + "GPMin: 2 15:23:56 -0.996055 0.8010\n", + "GPMin: 3 15:23:56 -0.000000 0.0000\n" ] } ], @@ -691,7 +711,7 @@ { "data": { "text/plain": [ - "0.05034927099768538" + "0.05866858300214517" ] }, "execution_count": 37, @@ -714,7 +734,7 @@ { "data": { "text/plain": [ - "1.0032003046944737e-05" + "1.803600025596097e-05" ] }, "execution_count": 38, @@ -736,7 +756,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -787,7 +807,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "39a96fb81e2440b2bd93f54c055787d0", + "model_id": "5c718a62abef46bab0002035afd1860f", "version_major": 2, "version_minor": 0 }, @@ -858,7 +878,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 44, @@ -982,7 +1002,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1063,12 +1083,12 @@ "id": "26bade63-559f-4c93-be24-5561f5c8190f", "metadata": {}, "source": [ - "## Again but execute children as background processes, but keep the task itself blocking" + "## With an Executor" ] }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 54, "id": "89169376-be36-4ceb-9f4e-6e1f3247bc62", "metadata": {}, "outputs": [], @@ -1078,7 +1098,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 55, "id": "2ee9f1d4-5b14-4340-98d4-4bd293af89a4", "metadata": {}, "outputs": [], @@ -1090,17 +1110,17 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 56, "id": "3d73a9de-7b4e-476a-b50a-ac6a3957a7ab", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 72, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -1111,7 +1131,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 57, "id": "0f075d90-e636-49be-b1a6-741a56363f54", "metadata": {}, "outputs": [], @@ -1121,34 +1141,72 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "2b2f703c-745b-49f9-a81f-a780248e9cd3", - "metadata": {}, - "outputs": [], - "source": [] + "execution_count": 58, + "id": "e93ef33e-11a9-4f8a-a7c1-f9679654e4f1", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 35.7 s, sys: 1.25 s, total: 37 s\n", + "Wall time: 36 s\n" + ] + }, + { + "data": { + "text/plain": [ + "(ReturnStatus(Code.DONE, None),\n", + " )" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "m.execute()" + ] }, { "cell_type": "code", - "execution_count": 74, - "id": "d82a28ab-1a96-4a3a-8f79-5a875ac20788", + "execution_count": 59, + "id": "79c89012-5b28-4124-9681-2507e0690b49", "metadata": { - "scrolled": true, "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 1.25 s, sys: 607 ms, total: 1.85 s\n", + "Wall time: 11 s\n" + ] + } + ], "source": [ - "ret, output = ProcessExecutor(max_processes=4).run(m)" + "%%time\n", + "exe = ProcessExecutor(max_processes=8).submit([m])\n", + "exe.run()\n", + "exe.wait()" ] }, { "cell_type": "code", - "execution_count": 75, - "id": "78017969-23fc-46f5-b99f-cd1d2dc74c00", - "metadata": {}, + "execution_count": 60, + "id": "4df7bd02-4a74-4c0b-b618-926274f96560", + "metadata": { + "tags": [] + }, "outputs": [ { "data": { - "image/png": "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uu+++1RcXCyHw6GxY8fqiSeeiFK18WWg1/aZZ57R5MmTlZaWJrfbrX/8x39UTU1NlKqNHxs2bND8+fNVUFAgi8WiV1555bTPWb9+vaZMmaKUlBSNGTNG//Ef/xH5QuPQQK/tSy+9pDlz5ignJ0eZmZmaPn261qxZE51i40wof2+7hfO9jCASglWrVmnRokW67777tHXrVl1yySWaN2+eDh482OfzGhoadP311+trX/talCqNP6Fc22uuuUZ/+9vf9Pjjj2vXrl167rnnNGHChChWHR8Gem3fffddXX/99brxxhu1fft2vfDCC9q0aZNuuummKFce+3w+nyZPnqwVK1b06/FlZWW6/PLLdckll2jr1q2699579eMf/1gvvvhihCuNPwO9ths2bNCcOXO0evVqbdmyRbNnz9b8+fO1devWCFcafwZ6bbuF/b3MwIBdcMEFxq233trj3IQJE4yf//znfT5vwYIFxj//8z8b999/vzF58uQIVhi/BnptX3/9dcPlchk1NTXRKC+uDfTaPvTQQ8aYMWN6nPvNb35jFBYWRqzGoUCS8fLLL/f5mHvuuceYMGFCj3O33HKLceGFF0awsvjXn2t7KhMnTjQeeOCB8Bc0hAzk2ob7vYwRkQFqbW3Vli1bNHfu3B7n586dq/fff7/X5z355JPau3ev7r///kiXGLdCubavvvqqpk6dqgcffFAjR47U+PHjdffdd6u5uTkaJceNUK7tjBkzdOjQIa1evVqGYejo0aP685//rCuuuCIaJQ9pH3zwwUl/Fl//+te1efNmtbW1mVTV0NTZ2Smv16usrCyzSxkSIvFeFtM3vYtF1dXV6ujoUF5eXo/zeXl5qqysPOVzdu/erZ///Od65513ZLNxyXsTyrXdt2+f3n33XaWkpOjll19WdXW1brvtNtXW1tIncoJQru2MGTP0zDPPaMGCBWppaVF7e7u++c1v6re//W00Sh7SKisrT/ln0d7erurqarndbpMqG3p+9atfyefz6ZprrjG7lLgXqfcyRkRCZLFYenxtGMZJ5ySpo6ND1157rR544AGNHz8+WuXFtf5eWynwrx2LxaJnnnlGF1xwgS6//HI9/PDD+uMf/8ioyCkM5Nru2LFDP/7xj/Uv//Iv2rJli9544w2VlZXp1ltvjUapQ96p/ixOdR6he+6557R06VKtWrVKubm5ZpcT1yL5XsY/zwcoOztbSUlJJ/0rsqqq6qR/4UiS1+vV5s2btXXrVt1xxx2SAm+ehmHIZrPpzTff1KWXXhqV2mPdQK+tJLndbo0cObLH7abPPPNMGYahQ4cOqbS0NKI1x4tQru3y5ct10UUX6ac//akk6ZxzzlF6erouueQS/eIXv+Bf7YOQn59/yj8Lm82mESNGmFTV0LJq1SrdeOONeuGFF3TZZZeZXU7ci+R7GSMiA2S32zVlyhStXbu2x/m1a9dqxowZJz0+MzNTn376qbZt2xY8br31Vp1xxhnatm2bpk2bFq3SY95Ar60kXXTRRTpy5IgaGxuD57744gtZrVYVFhZGtN54Esq1bWpqktXa838RSUlJko7/6x2hmT59+kl/Fm+++aamTp2q5ORkk6oaOp577jndcMMNevbZZ+lpCpOIvpcNut01AT3//PNGcnKy8fjjjxs7duwwFi1aZKSnpxv79+83DMMwfv7znxvXXXddr89n1UzvBnptvV6vUVhYaFx99dXG9u3bjfXr1xulpaXGTTfdZNavELMGem2ffPJJw2azGY899pixd+9e49133zWmTp1qXHDBBWb9CjHL6/UaW7duNbZu3WpIMh5++GFj69atxoEDBwzDOPna7tu3z0hLSzPuuusuY8eOHcbjjz9uJCcnG3/+85/N+hVi1kCv7bPPPmvYbDbj0UcfNSoqKoJHfX29Wb9CzBrotf2ycL2XEURC9OijjxrFxcWG3W43zjvvPGP9+vXB7y1cuNCYOXNmr88liPRtoNd2586dxmWXXWakpqYahYWFxuLFi42mpqYoVx0fBnptf/Ob3xgTJ040UlNTDbfbbfzgBz8wDh06FOWqY9/bb79tSDrpWLhwoWEYp76269atM77yla8YdrvdGD16tLFy5croFx4HBnptZ86c2efjcVwof29PFK73MothMMYKAADMQY8IAAAwDUEEAACYhiACAABMQxABAACmIYgAAADTEEQAAIBpCCIAAMA0BBEAAGAagggAADANQQQAAJiGIAIAAExDEAEAAKb5v1E7qt2HJCfYAAAAAElFTkSuQmCC\n", + "image/png": "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uu+++1RcXCyHw6GxY8fqiSeeiFK18WWg1/aZZ57R5MmTlZaWJrfbrX/8x39UTU1NlKqNHxs2bND8+fNVUFAgi8WiV1555bTPWb9+vaZMmaKUlBSNGTNG//Ef/xH5QuPQQK/tSy+9pDlz5ignJ0eZmZmaPn261qxZE51i40wof2+7hfO9jCASglWrVmnRokW67777tHXrVl1yySWaN2+eDh482OfzGhoadP311+trX/talCqNP6Fc22uuuUZ/+9vf9Pjjj2vXrl167rnnNGHChChWHR8Gem3fffddXX/99brxxhu1fft2vfDCC9q0aZNuuummKFce+3w+nyZPnqwVK1b06/FlZWW6/PLLdckll2jr1q2699579eMf/1gvvvhihCuNPwO9ths2bNCcOXO0evVqbdmyRbNnz9b8+fO1devWCFcafwZ6bbuF/b3MwIBdcMEFxq233trj3IQJE4yf//znfT5vwYIFxj//8z8b999/vzF58uQIVhi/BnptX3/9dcPlchk1NTXRKC+uDfTaPvTQQ8aYMWN6nPvNb35jFBYWRqzGoUCS8fLLL/f5mHvuuceYMGFCj3O33HKLceGFF0awsvjXn2t7KhMnTjQeeOCB8Bc0hAzk2ob7vYwRkQFqbW3Vli1bNHfu3B7n586dq/fff7/X5z355JPau3ev7r///kiXGLdCubavvvqqpk6dqgcffFAjR47U+PHjdffdd6u5uTkaJceNUK7tjBkzdOjQIa1evVqGYejo0aP685//rCuuuCIaJQ9pH3zwwUl/Fl//+te1efNmtbW1mVTV0NTZ2Smv16usrCyzSxkSIvFeFtM3vYtF1dXV6ujoUF5eXo/zeXl5qqysPOVzdu/erZ///Od65513ZLNxyXsTyrXdt2+f3n33XaWkpOjll19WdXW1brvtNtXW1tIncoJQru2MGTP0zDPPaMGCBWppaVF7e7u++c1v6re//W00Sh7SKisrT/ln0d7erurqarndbpMqG3p+9atfyefz6ZprrjG7lLgXqfcyRkRCZLFYenxtGMZJ5ySpo6ND1157rR544AGNHz8+WuXFtf5eWynwrx2LxaJnnnlGF1xwgS6//HI9/PDD+uMf/8ioyCkM5Nru2LFDP/7xj/Uv//Iv2rJli9544w2VlZXp1ltvjUapQ96p/ixOdR6he+6557R06VKtWrVKubm5ZpcT1yL5XsY/zwcoOztbSUlJJ/0rsqqq6qR/4UiS1+vV5s2btXXrVt1xxx2SAm+ehmHIZrPpzTff1KWXXhqV2mPdQK+tJLndbo0cObLH7abPPPNMGYahQ4cOqbS0NKI1x4tQru3y5ct10UUX6ac//akk6ZxzzlF6erouueQS/eIXv+Bf7YOQn59/yj8Lm82mESNGmFTV0LJq1SrdeOONeuGFF3TZZZeZXU7ci+R7GSMiA2S32zVlyhStXbu2x/m1a9dqxowZJz0+MzNTn376qbZt2xY8br31Vp1xxhnatm2bpk2bFq3SY95Ar60kXXTRRTpy5IgaGxuD57744gtZrVYVFhZGtN54Esq1bWpqktXa838RSUlJko7/6x2hmT59+kl/Fm+++aamTp2q5ORkk6oaOp577jndcMMNevbZZ+lpCpOIvpcNut01AT3//PNGcnKy8fjjjxs7duwwFi1aZKSnpxv79+83DMMwfv7znxvXXXddr89n1UzvBnptvV6vUVhYaFx99dXG9u3bjfXr1xulpaXGTTfdZNavELMGem2ffPJJw2azGY899pixd+9e49133zWmTp1qXHDBBWb9CjHL6/UaW7duNbZu3WpIMh5++GFj69atxoEDBwzDOPna7tu3z0hLSzPuuusuY8eOHcbjjz9uJCcnG3/+85/N+hVi1kCv7bPPPmvYbDbj0UcfNSoqKoJHfX29Wb9CzBrotf2ycL2XEURC9OijjxrFxcWG3W43zjvvPGP9+vXB7y1cuNCYOXNmr88liPRtoNd2586dxmWXXWakpqYahYWFxuLFi42mpqYoVx0fBnptf/Ob3xgTJ040UlNTDbfbbfzgBz8wDh06FOWqY9/bb79tSDrpWLhwoWEYp76269atM77yla8YdrvdGD16tLFy5croFx4HBnptZ86c2efjcVwof29PFK73MothMMYKAADMQY8IAAAwDUEEAACYhiACAABMQxABAACmIYgAAADTEEQAAIBpCCIAAMA0BBEAAGAagggAADANQQQAAJiGIAIAAExDEAEAAKb5v1E7qt2HJCfYAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] @@ -1158,22 +1216,22 @@ } ], "source": [ - "output.plot()" + "exe.output[0].plot()" ] }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 61, "id": "45162eb2-b23d-45c6-8aad-dfe9a6a484d1", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0.6818586500998999" + "0.6788586373205143" ] }, - "execution_count": 76, + "execution_count": 61, "metadata": {}, "output_type": "execute_result" } @@ -1184,17 +1242,17 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 62, "id": "f0bcfe59-2168-4e74-9d7a-33d900368907", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0.6818586500999" + "0.6788586373205143" ] }, - "execution_count": 77, + "execution_count": 62, "metadata": {}, "output_type": "execute_result" } @@ -1203,184 +1261,6 @@ "output.equilibrium_volume" ] }, - { - "cell_type": "markdown", - "id": "36b17048-3941-4d86-bd2a-e131371f4bad", - "metadata": { - "jp-MarkdownHeadingCollapsed": true, - "tags": [] - }, - "source": [ - "## Again but execute everything in the background." - ] - }, - { - "cell_type": "code", - "execution_count": 62, - "id": "c4758ca5-0760-4fd9-80d6-b02f78da0e5c", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "ename": "AssertionError", - "evalue": "broken in the TaskGenerator formalism", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[62], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28;01mFalse\u001b[39;00m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mbroken in the TaskGenerator formalism\u001b[39m\u001b[38;5;124m\"\u001b[39m\n", - "\u001b[0;31mAssertionError\u001b[0m: broken in the TaskGenerator formalism" - ] - } - ], - "source": [ - "assert False, \"broken in the TaskGenerator formalism\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "10f6c113-1e35-48f0-8878-291129bd8a60", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "m = MurnaghanTask()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "70832c31-040e-49be-b0f7-172f930cf31b", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "m.input.task = AseStaticTask()\n", - "m.input.task.input.calculator = MorsePotential()\n", - "m.input.structure = bulk(\"Fe\", a=1.2)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "94f4c51d-b69b-4477-a9db-d0ee7627cee6", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "m.input.task.input" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9a000824-0a9e-4395-8e07-00e484bc7937", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "m.input.set_strain_range(.5, 100)" - ] - }, - { - "cell_type": "markdown", - "id": "82c9d3a2-d93c-41aa-a1af-52bfab8cd8df", - "metadata": {}, - "source": [ - "Use the threading backend just to show off." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ca50857c-1c2f-4fad-a58c-16b399b8721d", - "metadata": {}, - "outputs": [], - "source": [ - "from pyiron_contrib.tinybase.executor import BackgroundExecutor\n", - "\n", - "m.input.child_executor = BackgroundExecutor" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "12f20f5c-27a3-4533-ad19-8ec06e1c8a90", - "metadata": { - "scrolled": true, - "tags": [] - }, - "outputs": [], - "source": [ - "exe = m.run(how='background')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2c5d8bea-49ec-4004-8a54-3ded7a3f413d", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "exe.wait()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ea7bcb58-0890-487e-bef5-bd3cb36143c1", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "exe._run_machine.state" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0b7c2912-6847-4262-a62d-7233ca398643", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "exe._run_time" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fd5ca921-2062-4f85-b014-382561e9893a", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "exe._collect_time" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4d1223f7-2d72-413e-b20b-cf42781780bb", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "exe.output[0].plot()" - ] - }, { "cell_type": "markdown", "id": "e21f6582-e7ec-43be-80ec-e9ad53aabc43", @@ -1391,7 +1271,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 63, "id": "149c52b5-a0ce-4e6b-ba55-d94d33aa2f8a", "metadata": { "tags": [] @@ -1403,7 +1283,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 64, "id": "aca24005-ea49-4389-bc26-f292fd0a75a2", "metadata": { "tags": [] @@ -1422,19 +1302,20 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 65, "id": "4ae990bd-af18-4dae-8500-779c9509f3f6", "metadata": { "tags": [] }, "outputs": [], "source": [ + "\n", "m.input.set_strain_range(.5, 500)" ] }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 66, "id": "0925864e-4dd1-4f4e-ace4-aac09c55e787", "metadata": { "scrolled": true, @@ -1446,1015 +1327,1022 @@ "output_type": "stream", "text": [ " Step Time Energy fmax\n", - "LBFGS: 0 22:31:34 3.488292 0.0000\n", + "LBFGS: 0 15:24:47 3.991875 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:47 4.517693 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:34 3.737364 0.0000\n", + "LBFGS: 0 15:24:47 4.789242 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:34 3.244546 0.0000\n", + "LBFGS: 0 15:24:47 4.251945 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:34 4.517693 0.0000\n", - "LBFGS: 0 22:31:34 4.789242 0.0000\n", + "LBFGS: 0 15:24:47 3.488292 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:34 3.991875 0.0000\n", + "LBFGS: 0 15:24:47 3.244546 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:47 3.006013 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:34 4.251945 0.0000\n", - "LBFGS: 0 22:31:34 3.006013 0.0000\n", + "LBFGS: 0 15:24:47 3.737364 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:34 2.320604 0.0000\n", + "LBFGS: 0 15:24:47 2.772582 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:34 2.772582 0.0000\n", + "LBFGS: 0 15:24:47 2.544148 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:34 2.101849 0.0000\n", + "LBFGS: 0 15:24:47 2.320604 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 1.887783 0.0000\n", + "LBFGS: 0 15:24:47 2.101849 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 2.544148 0.0000\n", + "LBFGS: 0 15:24:47 1.887783 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 1.473327 0.0000\n", + "LBFGS: 0 15:24:47 1.678307 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 1.678307 0.0000\n", + "LBFGS: 0 15:24:47 1.473327 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 1.272749 0.0000\n", + "LBFGS: 0 15:24:47 1.272749 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 1.076481 0.0000\n", + "LBFGS: 0 15:24:47 1.076481 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 0.884435 0.0000\n", + "LBFGS: 0 15:24:47 0.884435 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 0.696523 0.0000\n", + "LBFGS: 0 15:24:47 0.512659 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 0.512659 0.0000\n", + "LBFGS: 0 15:24:47 0.696523 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 0.156747 0.0000\n", + "LBFGS: 0 15:24:47 0.332761 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -0.015462 0.0000\n", + "LBFGS: 0 15:24:47 0.156747 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 0.332761 0.0000\n", + "LBFGS: 0 15:24:47 -0.183946 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -0.183946 0.0000\n", + "LBFGS: 0 15:24:48 -0.015462 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -0.822116 0.0000\n", + "LBFGS: 0 15:24:48 -0.348779 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -0.348779 0.0000\n", + "LBFGS: 0 15:24:48 -0.822116 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -0.667792 0.0000\n", + "LBFGS: 0 15:24:48 -0.510037 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -0.973078 0.0000\n", + "LBFGS: 0 15:24:48 -0.973078 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -0.510037 0.0000\n", + "LBFGS: 0 15:24:48 -0.667792 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -1.120747 0.0000\n", + "LBFGS: 0 15:24:48 -1.406469 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -1.406469 0.0000\n", + "LBFGS: 0 15:24:48 -1.120747 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -1.265189 0.0000\n", + "LBFGS: 0 15:24:48 -1.544652 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -1.941232 0.0000\n", + "LBFGS: 0 15:24:48 -1.265189 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:48 -1.679799 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -1.811973 0.0000\n", - "LBFGS: 0 22:31:35 -1.679799 0.0000\n", + "LBFGS: 0 15:24:48 -1.811973 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -1.544652 0.0000\n", + "LBFGS: 0 15:24:48 -2.067636 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -2.191241 0.0000\n", + "LBFGS: 0 15:24:48 -1.941232 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -2.430279 0.0000\n", + "LBFGS: 0 15:24:48 -2.312104 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -2.067636 0.0000\n", + "LBFGS: 0 15:24:48 -2.191241 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -2.312104 0.0000\n", + "LBFGS: 0 15:24:48 -2.430279 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -2.545820 0.0000\n", + "LBFGS: 0 15:24:48 -2.545820 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:48 -2.658780 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -2.982680 0.0000\n", - "LBFGS: 0 22:31:35 -2.769210 0.0000\n", + "LBFGS: 0 15:24:48 -2.769210 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -2.658780 0.0000\n", + "LBFGS: 0 15:24:48 -2.877160 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -2.877160 0.0000\n", + "LBFGS: 0 15:24:48 -2.982680 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -3.186620 0.0000\n", + "LBFGS: 0 15:24:48 -3.186620 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -3.085817 0.0000\n", + "LBFGS: 0 15:24:48 -3.085817 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -3.285134 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -3.744922 0.0000\n", + "LBFGS: 0 15:24:48 -3.285134 0.0000\n", + "LBFGS: 0 15:24:48 -3.381404 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -3.475475 0.0000\n", + "LBFGS: 0 15:24:48 -3.475475 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -3.830620 0.0000\n", + "LBFGS: 0 15:24:48 -3.567390 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -3.381404 0.0000\n", + "LBFGS: 0 15:24:48 -3.744922 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -3.567390 0.0000\n", + "LBFGS: 0 15:24:48 -3.830620 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -3.914328 0.0000\n", + "LBFGS: 0 15:24:48 -3.657192 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -3.657192 0.0000\n", + "LBFGS: 0 15:24:48 -3.914328 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -3.996084 0.0000\n", + "LBFGS: 0 15:24:48 -3.996084 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -4.153891 0.0000\n", + "LBFGS: 0 15:24:48 -4.075925 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -4.075925 0.0000\n", + "LBFGS: 0 15:24:49 -4.153891 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -4.230016 0.0000\n", + "LBFGS: 0 15:24:49 -4.230016 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:49 -4.304338 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -4.516830 0.0000\n", - "LBFGS: 0 22:31:35 -4.304338 0.0000\n", + "LBFGS: 0 15:24:49 -4.447711 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -4.447711 0.0000\n", + "LBFGS: 0 15:24:49 -4.516830 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -4.376892 0.0000\n", + "LBFGS: 0 15:24:49 -4.376892 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -4.650099 0.0000\n", + "LBFGS: 0 15:24:49 -4.584282 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:49 -4.650099 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -4.584282 0.0000\n", - "LBFGS: 0 22:31:35 -4.714313 0.0000\n", + "LBFGS: 0 15:24:49 -4.776956 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -4.776956 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -4.838057 0.0000\n", + "LBFGS: 0 15:24:49 -4.714313 0.0000\n", + "LBFGS: 0 15:24:49 -4.838057 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -4.955755 0.0000\n", + "LBFGS: 0 15:24:49 -4.955755 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -4.897647 0.0000\n", + "LBFGS: 0 15:24:49 -4.897647 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -5.067638 0.0000\n", + "LBFGS: 0 15:24:49 -5.012409 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -5.121469 0.0000\n", + "LBFGS: 0 15:24:49 -5.067638 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -5.012409 0.0000\n", + "LBFGS: 0 15:24:49 -5.173930 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -5.173930 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -5.225046 0.0000\n", + "LBFGS: 0 15:24:49 -5.121469 0.0000\n", + "LBFGS: 0 15:24:49 -5.225046 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -5.323350 0.0000\n", + "LBFGS: 0 15:24:49 -5.323350 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -5.274844 0.0000\n", + "LBFGS: 0 15:24:49 -5.274844 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -5.461356 0.0000\n", + "LBFGS: 0 15:24:49 -5.416581 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -5.416581 0.0000\n", + "LBFGS: 0 15:24:49 -5.370587 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:49 -5.461356 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:35 -5.547340 0.0000\n", - "LBFGS: 0 22:31:35 -5.370587 0.0000\n", + "LBFGS: 0 15:24:49 -5.504934 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -5.504934 0.0000\n", + "LBFGS: 0 15:24:49 -5.547340 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -5.667742 0.0000\n", + "LBFGS: 0 15:24:49 -5.588595 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -5.588595 0.0000\n", + "LBFGS: 0 15:24:49 -5.667742 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:49 -5.628722 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:49 -5.705677 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -5.705677 0.0000\n", - "LBFGS: 0 22:31:36 -5.846976 0.0000\n", - "LBFGS: 0 22:31:36 -5.628722 0.0000\n", + "LBFGS: 0 15:24:49 -5.742548 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -5.742548 0.0000\n", + "LBFGS: 0 15:24:49 -5.813177 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -5.813177 0.0000\n", + "LBFGS: 0 15:24:49 -5.778375 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -5.778375 0.0000\n", + "LBFGS: 0 15:24:50 -5.911636 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -5.879789 0.0000\n", + "LBFGS: 0 15:24:50 -5.879789 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -5.911636 0.0000\n", + "LBFGS: 0 15:24:50 -5.846976 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.057017 0.0000\n", + "LBFGS: 0 15:24:50 -5.942536 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -5.942536 0.0000\n", + "LBFGS: 0 15:24:50 -5.972506 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.083445 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.029730 0.0000\n", + "LBFGS: 0 15:24:50 -6.057017 0.0000\n", + "LBFGS: 0 15:24:50 -6.001565 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:50 -6.029730 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.001565 0.0000\n", - "LBFGS: 0 22:31:36 -5.972506 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.157731 0.0000\n", + "LBFGS: 0 15:24:50 -6.109029 0.0000\n", + "LBFGS: 0 15:24:50 -6.133786 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.180881 0.0000\n", + "LBFGS: 0 15:24:50 -6.083445 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.109029 0.0000\n", + "LBFGS: 0 15:24:50 -6.180881 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.133786 0.0000\n", + "LBFGS: 0 15:24:50 -6.157731 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.224853 0.0000\n", + "LBFGS: 0 15:24:50 -6.203250 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.265820 0.0000\n", + "LBFGS: 0 15:24:50 -6.224853 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.203250 0.0000\n", + "LBFGS: 0 15:24:50 -6.265820 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.245705 0.0000\n", + "LBFGS: 0 15:24:50 -6.245705 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.303898 0.0000\n", + "LBFGS: 0 15:24:50 -6.303898 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.321888 0.0000\n", + "LBFGS: 0 15:24:50 -6.339196 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.339196 0.0000\n", + "LBFGS: 0 15:24:50 -6.321888 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.285213 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.355836 0.0000\n", + "LBFGS: 0 15:24:50 -6.355836 0.0000\n", + "LBFGS: 0 15:24:50 -6.285213 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.401872 0.0000\n", + "LBFGS: 0 15:24:50 -6.371820 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:50 -6.415965 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.371820 0.0000\n", - "LBFGS: 0 22:31:36 -6.387162 0.0000\n", + "LBFGS: 0 15:24:50 -6.401872 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.415965 0.0000\n", + "LBFGS: 0 15:24:50 -6.429451 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.429451 0.0000\n", + "LBFGS: 0 15:24:50 -6.387162 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.454650 0.0000\n", + "LBFGS: 0 15:24:50 -6.454650 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.442342 0.0000\n", + "LBFGS: 0 15:24:50 -6.442342 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.466386 0.0000\n", + "LBFGS: 0 15:24:50 -6.477561 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.498271 0.0000\n", + "LBFGS: 0 15:24:50 -6.488186 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.488186 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.477561 0.0000\n", + "LBFGS: 0 15:24:50 -6.498271 0.0000\n", + "LBFGS: 0 15:24:50 -6.466386 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.507828 0.0000\n", + "LBFGS: 0 15:24:50 -6.507828 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.533425 0.0000\n", + "LBFGS: 0 15:24:50 -6.516865 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.516865 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.540966 0.0000\n", + "LBFGS: 0 15:24:50 -6.533425 0.0000\n", + "LBFGS: 0 15:24:50 -6.525395 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.525395 0.0000\n", + "LBFGS: 0 15:24:50 -6.540966 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.548028 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.554620 0.0000\n", + "LBFGS: 0 15:24:50 -6.548028 0.0000\n", + "LBFGS: 0 15:24:50 -6.560750 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.560750 0.0000\n", + "LBFGS: 0 15:24:50 -6.566429 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.584797 0.0000\n", + "LBFGS: 0 15:24:50 -6.554620 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.566429 0.0000\n", + "LBFGS: 0 15:24:50 -6.571664 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:50 -6.576465 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.571664 0.0000\n", + "LBFGS: 0 15:24:50 -6.580840 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.580840 0.0000\n", - "LBFGS: 0 22:31:36 -6.576465 0.0000\n", + "LBFGS: 0 15:24:50 -6.584797 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.591492 0.0000\n", + "LBFGS: 0 15:24:50 -6.588345 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.588345 0.0000\n", + "LBFGS: 0 15:24:50 -6.591492 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.594245 0.0000\n", + "LBFGS: 0 15:24:50 -6.594245 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:50 -6.596612 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:50 -6.598601 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.596612 0.0000\n", - "LBFGS: 0 22:31:36 -6.598601 0.0000\n", - "LBFGS: 0 22:31:36 -6.602374 0.0000\n", + "LBFGS: 0 15:24:50 -6.602374 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.602924 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:50 -6.600220 0.0000\n", + "LBFGS: 0 15:24:50 -6.602924 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.600220 0.0000\n", - "LBFGS: 0 22:31:36 -6.603132 0.0000\n", + "LBFGS: 0 15:24:50 -6.601475 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.601475 0.0000\n", + "LBFGS: 0 15:24:51 -6.603132 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.603005 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.601771 0.0000\n", + "LBFGS: 0 15:24:51 -6.602549 0.0000\n", + "LBFGS: 0 15:24:51 -6.603005 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.599275 0.0000\n", + "LBFGS: 0 15:24:51 -6.601771 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.597570 0.0000\n", + "LBFGS: 0 15:24:51 -6.599275 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.602549 0.0000\n", + "LBFGS: 0 15:24:51 -6.600678 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:51 -6.597570 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.595568 0.0000\n", + "LBFGS: 0 15:24:51 -6.595568 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.600678 0.0000\n", - "LBFGS: 0 22:31:36 -6.593275 0.0000\n", + "LBFGS: 0 15:24:51 -6.593275 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.587840 0.0000\n", + "LBFGS: 0 15:24:51 -6.590697 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.590697 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.573734 0.0000\n", + "LBFGS: 0 15:24:51 -6.587840 0.0000\n", + "LBFGS: 0 15:24:51 -6.581312 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.581312 0.0000\n", + "LBFGS: 0 15:24:51 -6.584710 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.577651 0.0000\n", + "LBFGS: 0 15:24:51 -6.577651 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.584710 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.565149 0.0000\n", + "LBFGS: 0 15:24:51 -6.569565 0.0000\n", + "LBFGS: 0 15:24:51 -6.565149 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.569565 0.0000\n", + "LBFGS: 0 15:24:51 -6.573734 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.560493 0.0000\n", + "LBFGS: 0 15:24:51 -6.555599 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.555599 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.550475 0.0000\n", + "LBFGS: 0 15:24:51 -6.560493 0.0000\n", + "LBFGS: 0 15:24:51 -6.550475 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.545124 0.0000\n", + "LBFGS: 0 15:24:51 -6.545124 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:51 -6.539551 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.539551 0.0000\n", - "LBFGS: 0 22:31:36 -6.533761 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.527759 0.0000\n", + "LBFGS: 0 15:24:51 -6.533761 0.0000\n", + "LBFGS: 0 15:24:51 -6.527759 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:51 -6.521548 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.521548 0.0000\n", - "LBFGS: 0 22:31:36 -6.515134 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.480158 0.0000\n", + "LBFGS: 0 15:24:51 -6.494713 0.0000\n", + "LBFGS: 0 15:24:51 -6.501712 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.508521 0.0000\n", + "LBFGS: 0 15:24:51 -6.515134 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.501712 0.0000\n", + "LBFGS: 0 15:24:51 -6.508521 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.494713 0.0000\n", + "LBFGS: 0 15:24:51 -6.480158 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.472611 0.0000\n", + "LBFGS: 0 15:24:51 -6.487527 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.487527 0.0000\n", + "LBFGS: 0 15:24:51 -6.472611 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.432329 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.464889 0.0000\n", + "LBFGS: 0 15:24:51 -6.464889 0.0000\n", + "LBFGS: 0 15:24:51 -6.456996 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.448936 0.0000\n", + "LBFGS: 0 15:24:51 -6.448936 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:51 -6.440712 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.440712 0.0000\n", - "LBFGS: 0 22:31:36 -6.456996 0.0000\n", + "LBFGS: 0 15:24:51 -6.432329 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.423789 0.0000\n", + "LBFGS: 0 15:24:51 -6.415096 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.406254 0.0000\n", + "LBFGS: 0 15:24:51 -6.423789 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.397266 0.0000\n", + "LBFGS: 0 15:24:51 -6.397266 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.415096 0.0000\n", + "LBFGS: 0 15:24:51 -6.388136 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:36 -6.378866 0.0000\n", + "LBFGS: 0 15:24:51 -6.406254 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.369460 0.0000\n", + "LBFGS: 0 15:24:51 -6.378866 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:51 -6.359921 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.388136 0.0000\n", - "LBFGS: 0 22:31:37 -6.350251 0.0000\n", + "LBFGS: 0 15:24:51 -6.369460 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.359921 0.0000\n", + "LBFGS: 0 15:24:51 -6.350251 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.330535 0.0000\n", + "LBFGS: 0 15:24:51 -6.340455 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:51 -6.330535 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.340455 0.0000\n", - "LBFGS: 0 22:31:37 -6.310333 0.0000\n", + "LBFGS: 0 15:24:51 -6.320493 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:51 -6.300058 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.320493 0.0000\n", - "LBFGS: 0 22:31:37 -6.300058 0.0000\n", + "LBFGS: 0 15:24:51 -6.310333 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.289669 0.0000\n", + "LBFGS: 0 15:24:51 -6.289669 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:51 -6.279171 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.225120 0.0000\n", - "LBFGS: 0 22:31:37 -6.279171 0.0000\n", + "LBFGS: 0 15:24:51 -6.257855 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.268565 0.0000\n", + "LBFGS: 0 15:24:51 -6.268565 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.257855 0.0000\n", + "LBFGS: 0 15:24:51 -6.247042 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.247042 0.0000\n", + "LBFGS: 0 15:24:51 -6.214016 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.236130 0.0000\n", + "LBFGS: 0 15:24:51 -6.236130 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.214016 0.0000\n", + "LBFGS: 0 15:24:51 -6.225120 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.180158 0.0000\n", + "LBFGS: 0 15:24:51 -6.202819 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:51 -6.180158 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.191532 0.0000\n", - "LBFGS: 0 22:31:37 -6.168698 0.0000\n", + "LBFGS: 0 15:24:51 -6.191532 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.145530 0.0000\n", + "LBFGS: 0 15:24:51 -6.168698 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.202819 0.0000\n", + "LBFGS: 0 15:24:51 -6.157154 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.157154 0.0000\n", + "LBFGS: 0 15:24:51 -6.145530 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.110193 0.0000\n", + "LBFGS: 0 15:24:51 -6.133827 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.122047 0.0000\n", + "LBFGS: 0 15:24:51 -6.122047 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.133827 0.0000\n", + "LBFGS: 0 15:24:51 -6.110193 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.098266 0.0000\n", + "LBFGS: 0 15:24:51 -6.098266 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.086268 0.0000\n", + "LBFGS: 0 15:24:51 -6.074202 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.049871 0.0000\n", - "LBFGS: 0 22:31:37 -6.074202 0.0000\n", + "LBFGS: 0 15:24:51 -6.086268 0.0000\n", + "LBFGS: 0 15:24:51 -6.062068 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.062068 0.0000\n", + "LBFGS: 0 15:24:51 -6.025288 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.025288 0.0000\n", + "LBFGS: 0 15:24:51 -6.049871 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.012907 0.0000\n", + "LBFGS: 0 15:24:52 -6.012907 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.037610 0.0000\n", + "LBFGS: 0 15:24:52 -6.037610 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.950174 0.0000\n", + "LBFGS: 0 15:24:52 -5.987974 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -6.000468 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -6.000468 0.0000\n", - "LBFGS: 0 22:31:37 -5.987974 0.0000\n", + "LBFGS: 0 15:24:52 -5.962825 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.962825 0.0000\n", + "LBFGS: 0 15:24:52 -5.975426 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.975426 0.0000\n", + "LBFGS: 0 15:24:52 -5.950174 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -5.937473 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.924725 0.0000\n", - "LBFGS: 0 22:31:37 -5.911931 0.0000\n", + "LBFGS: 0 15:24:52 -5.924725 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.937473 0.0000\n", + "LBFGS: 0 15:24:52 -5.899093 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.899093 0.0000\n", + "LBFGS: 0 15:24:52 -5.911931 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.860327 0.0000\n", + "LBFGS: 0 15:24:52 -5.886212 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.886212 0.0000\n", + "LBFGS: 0 15:24:52 -5.873290 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.873290 0.0000\n", + "LBFGS: 0 15:24:52 -5.860327 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.834289 0.0000\n", + "LBFGS: 0 15:24:52 -5.847326 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.821215 0.0000\n", + "LBFGS: 0 15:24:52 -5.834289 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.847326 0.0000\n", + "LBFGS: 0 15:24:52 -5.821215 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.808107 0.0000\n", + "LBFGS: 0 15:24:52 -5.808107 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.755359 0.0000\n", - "LBFGS: 0 22:31:37 -5.794966 0.0000\n", + "LBFGS: 0 15:24:52 -5.781794 0.0000\n", + "LBFGS: 0 15:24:52 -5.794966 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.715501 0.0000\n", + "LBFGS: 0 15:24:52 -5.768591 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -5.742099 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.781794 0.0000\n", - "LBFGS: 0 22:31:37 -5.728813 0.0000\n", + "LBFGS: 0 15:24:52 -5.755359 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -5.728813 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -5.715501 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.742099 0.0000\n", - "LBFGS: 0 22:31:37 -5.768591 0.0000\n", - "LBFGS: 0 22:31:37 -5.702165 0.0000\n", + "LBFGS: 0 15:24:52 -5.688805 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.648600 0.0000\n", + "LBFGS: 0 15:24:52 -5.702165 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.688805 0.0000\n", + "LBFGS: 0 15:24:52 -5.675424 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.675424 0.0000\n", + "LBFGS: 0 15:24:52 -5.662022 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -5.635159 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.662022 0.0000\n", - "LBFGS: 0 22:31:37 -5.621701 0.0000\n", + "LBFGS: 0 15:24:52 -5.621701 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.608226 0.0000\n", + "LBFGS: 0 15:24:52 -5.648600 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.567712 0.0000\n", + "LBFGS: 0 15:24:52 -5.608226 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -5.594735 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.635159 0.0000\n", - "LBFGS: 0 22:31:37 -5.594735 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.581230 0.0000\n", + "LBFGS: 0 15:24:52 -5.581230 0.0000\n", + "LBFGS: 0 15:24:52 -5.567712 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.527083 0.0000\n", + "LBFGS: 0 15:24:52 -5.554180 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.486366 0.0000\n", - "LBFGS: 0 22:31:37 -5.513519 0.0000\n", + "LBFGS: 0 15:24:52 -5.540637 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -5.513519 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.540637 0.0000\n", + "LBFGS: 0 15:24:52 -5.499947 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.554180 0.0000\n", + "LBFGS: 0 15:24:52 -5.527083 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -5.486366 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.499947 0.0000\n", - "LBFGS: 0 22:31:37 -5.459183 0.0000\n", + "LBFGS: 0 15:24:52 -5.459183 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.472778 0.0000\n", + "LBFGS: 0 15:24:52 -5.472778 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.431978 0.0000\n", + "LBFGS: 0 15:24:52 -5.445583 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -5.431978 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.418369 0.0000\n", - "LBFGS: 0 22:31:37 -5.404757 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.445583 0.0000\n", + "LBFGS: 0 15:24:52 -5.391143 0.0000\n", + "LBFGS: 0 15:24:52 -5.404757 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.391143 0.0000\n", + "LBFGS: 0 15:24:52 -5.418369 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.295837 0.0000\n", + "LBFGS: 0 15:24:52 -5.377527 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.363910 0.0000\n", + "LBFGS: 0 15:24:52 -5.350293 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -5.363910 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.336676 0.0000\n", - "LBFGS: 0 22:31:37 -5.377527 0.0000\n", + "LBFGS: 0 15:24:52 -5.323061 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.350293 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -5.295837 0.0000\n", + "LBFGS: 0 15:24:52 -5.336676 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.323061 0.0000\n", - "LBFGS: 0 22:31:37 -5.309448 0.0000\n", + "LBFGS: 0 15:24:52 -5.309448 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.268625 0.0000\n", + "LBFGS: 0 15:24:52 -5.268625 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.282229 0.0000\n", + "LBFGS: 0 15:24:52 -5.282229 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.227844 0.0000\n", + "LBFGS: 0 15:24:52 -5.255026 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -5.241432 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.173560 0.0000\n", + "LBFGS: 0 15:24:52 -5.227844 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -5.214262 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.255026 0.0000\n", - "LBFGS: 0 22:31:37 -5.214262 0.0000\n", - "LBFGS: 0 22:31:37 -5.241432 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -5.173560 0.0000\n", + "LBFGS: 0 15:24:52 -5.187119 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.187119 0.0000\n", - "LBFGS: 0 22:31:37 -5.160009 0.0000\n", + "LBFGS: 0 15:24:52 -5.200687 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.200687 0.0000\n", + "LBFGS: 0 15:24:52 -5.160009 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.146468 0.0000\n", + "LBFGS: 0 15:24:52 -5.105903 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.092403 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.132936 0.0000\n", + "LBFGS: 0 15:24:52 -5.132936 0.0000\n", + "LBFGS: 0 15:24:52 -5.119414 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.105903 0.0000\n", + "LBFGS: 0 15:24:52 -5.146468 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.051977 0.0000\n", + "LBFGS: 0 15:24:52 -5.092403 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.119414 0.0000\n", + "LBFGS: 0 15:24:52 -5.065440 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.078915 0.0000\n", + "LBFGS: 0 15:24:52 -5.078915 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.065440 0.0000\n", + "LBFGS: 0 15:24:52 -5.051977 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.011668 0.0000\n", + "LBFGS: 0 15:24:52 -5.038527 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -5.011668 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.025090 0.0000\n", - "LBFGS: 0 22:31:37 -4.998260 0.0000\n", + "LBFGS: 0 15:24:52 -5.025090 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -5.038527 0.0000\n", + "LBFGS: 0 15:24:52 -4.984867 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:52 -4.998260 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -4.984867 0.0000\n", - "LBFGS: 0 22:31:37 -4.971490 0.0000\n", + "LBFGS: 0 15:24:53 -4.958128 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:53 -4.971490 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -4.944782 0.0000\n", - "LBFGS: 0 22:31:37 -4.891567 0.0000\n", + "LBFGS: 0 15:24:53 -4.944782 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:37 -4.958128 0.0000\n", + "LBFGS: 0 15:24:53 -4.931453 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.904845 0.0000\n", + "LBFGS: 0 15:24:53 -4.918140 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.878308 0.0000\n", + "LBFGS: 0 15:24:53 -4.904845 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.931453 0.0000\n", + "LBFGS: 0 15:24:53 -4.891567 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.918140 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.825456 0.0000\n", + "LBFGS: 0 15:24:53 -4.851844 0.0000\n", + "LBFGS: 0 15:24:53 -4.878308 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.865067 0.0000\n", + "LBFGS: 0 15:24:53 -4.865067 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:53 -4.838641 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.851844 0.0000\n", - "LBFGS: 0 22:31:38 -4.838641 0.0000\n", + "LBFGS: 0 15:24:53 -4.812292 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.812292 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.772919 0.0000\n", + "LBFGS: 0 15:24:53 -4.799147 0.0000\n", + "LBFGS: 0 15:24:53 -4.825456 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.799147 0.0000\n", - "LBFGS: 0 22:31:38 -4.786023 0.0000\n", + "LBFGS: 0 15:24:53 -4.772919 0.0000\n", + "LBFGS: 0 15:24:53 -4.786023 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.759837 0.0000\n", + "LBFGS: 0 15:24:53 -4.759837 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.720716 0.0000\n", + "LBFGS: 0 15:24:53 -4.746775 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.746775 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.707720 0.0000\n", + "LBFGS: 0 15:24:53 -4.720716 0.0000\n", + "LBFGS: 0 15:24:53 -4.733735 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.733735 0.0000\n", + "LBFGS: 0 15:24:53 -4.707720 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.668865 0.0000\n", + "LBFGS: 0 15:24:53 -4.694745 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.681794 0.0000\n", + "LBFGS: 0 15:24:53 -4.655959 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:53 -4.668865 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.694745 0.0000\n", - "LBFGS: 0 22:31:38 -4.655959 0.0000\n", + "LBFGS: 0 15:24:53 -4.681794 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.630216 0.0000\n", + "LBFGS: 0 15:24:53 -4.643076 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:53 -4.617381 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.604569 0.0000\n", - "LBFGS: 0 22:31:38 -4.617381 0.0000\n", + "LBFGS: 0 15:24:53 -4.630216 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:53 -4.604569 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.643076 0.0000\n", - "LBFGS: 0 22:31:38 -4.591781 0.0000\n", + "LBFGS: 0 15:24:53 -4.591781 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.579018 0.0000\n", + "LBFGS: 0 15:24:53 -4.579018 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.553565 0.0000\n", + "LBFGS: 0 15:24:53 -4.553565 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.566279 0.0000\n", + "LBFGS: 0 15:24:53 -4.566279 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.477811 0.0000\n", + "LBFGS: 0 15:24:53 -4.528212 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:53 -4.502960 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.540876 0.0000\n", - "LBFGS: 0 22:31:38 -4.528212 0.0000\n", + "LBFGS: 0 15:24:53 -4.540876 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.490373 0.0000\n", + "LBFGS: 0 15:24:53 -4.515574 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.515574 0.0000\n", + "LBFGS: 0 15:24:53 -4.490373 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.502960 0.0000\n", + "LBFGS: 0 15:24:53 -4.477811 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.465275 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:53 -4.440283 0.0000\n", + "LBFGS: 0 15:24:53 -4.465275 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.415396 0.0000\n", - "LBFGS: 0 22:31:38 -4.402992 0.0000\n", + "LBFGS: 0 15:24:53 -4.427826 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.427826 0.0000\n", + "LBFGS: 0 15:24:53 -4.415396 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.440283 0.0000\n", + "LBFGS: 0 15:24:53 -4.452766 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.452766 0.0000\n", + "LBFGS: 0 15:24:53 -4.402992 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.390616 0.0000\n", + "LBFGS: 0 15:24:53 -4.365944 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.378267 0.0000\n", + "LBFGS: 0 15:24:53 -4.378267 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:53 -4.390616 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.341382 0.0000\n", - "LBFGS: 0 22:31:38 -4.365944 0.0000\n", + "LBFGS: 0 15:24:53 -4.329142 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.316929 0.0000\n", + "LBFGS: 0 15:24:53 -4.353649 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.292588 0.0000\n", + "LBFGS: 0 15:24:53 -4.341382 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.353649 0.0000\n", + "LBFGS: 0 15:24:53 -4.316929 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.329142 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.304745 0.0000\n", + "LBFGS: 0 15:24:53 -4.304745 0.0000\n", + "LBFGS: 0 15:24:53 -4.292588 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.280459 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.256286 0.0000\n", + "LBFGS: 0 15:24:53 -4.268359 0.0000\n", + "LBFGS: 0 15:24:53 -4.280459 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:53 -4.256286 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.268359 0.0000\n", - "LBFGS: 0 22:31:38 -4.244242 0.0000\n", + "LBFGS: 0 15:24:54 -4.232226 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.232226 0.0000\n", + "LBFGS: 0 15:24:54 -4.244242 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:54 -4.220239 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.208280 0.0000\n", - "LBFGS: 0 22:31:38 -4.184448 0.0000\n", + "LBFGS: 0 15:24:54 -4.208280 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:54 -4.196350 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.220239 0.0000\n", + "LBFGS: 0 15:24:54 -4.184448 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.196350 0.0000\n", - "LBFGS: 0 22:31:38 -4.172575 0.0000\n", + "LBFGS: 0 15:24:54 -4.172575 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.160732 0.0000\n", + "LBFGS: 0 15:24:54 -4.148917 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.125374 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.148917 0.0000\n", + "LBFGS: 0 15:24:54 -4.137131 0.0000\n", + "LBFGS: 0 15:24:54 -4.125374 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.137131 0.0000\n", + "LBFGS: 0 15:24:54 -4.160732 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.090278 0.0000\n", + "LBFGS: 0 15:24:54 -4.113646 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.078638 0.0000\n", + "LBFGS: 0 15:24:54 -4.101948 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.101948 0.0000\n", + "LBFGS: 0 15:24:54 -4.090278 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:54 -4.078638 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.067028 0.0000\n", - "LBFGS: 0 22:31:38 -4.113646 0.0000\n", + "LBFGS: 0 15:24:54 -4.067028 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.055446 0.0000\n", + "LBFGS: 0 15:24:54 -4.055446 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.032372 0.0000\n", + "LBFGS: 0 15:24:54 -4.032372 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:54 -4.043894 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.043894 0.0000\n", - "LBFGS: 0 22:31:38 -4.009416 0.0000\n", + "LBFGS: 0 15:24:54 -4.020879 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.997982 0.0000\n", + "LBFGS: 0 15:24:54 -3.986578 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -4.020879 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:54 -3.997982 0.0000\n", + "LBFGS: 0 15:24:54 -4.009416 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.986578 0.0000\n", - "LBFGS: 0 22:31:38 -3.975204 0.0000\n", + "LBFGS: 0 15:24:54 -3.975204 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.963859 0.0000\n", + "LBFGS: 0 15:24:54 -3.941259 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.918778 0.0000\n", + "LBFGS: 0 15:24:54 -3.930004 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.941259 0.0000\n", + "LBFGS: 0 15:24:54 -3.963859 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.952544 0.0000\n", + "LBFGS: 0 15:24:54 -3.952544 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.930004 0.0000\n", + "LBFGS: 0 15:24:54 -3.918778 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.907582 0.0000\n", + "LBFGS: 0 15:24:54 -3.896416 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.885280 0.0000\n", + "LBFGS: 0 15:24:54 -3.907582 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.896416 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.874174 0.0000\n", + "LBFGS: 0 15:24:54 -3.885280 0.0000\n", + "LBFGS: 0 15:24:54 -3.874174 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.863097 0.0000\n", + "LBFGS: 0 15:24:54 -3.852051 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:54 -3.863097 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.819091 0.0000\n", + "LBFGS: 0 15:24:54 -3.830047 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.841034 0.0000\n", - "LBFGS: 0 22:31:38 -3.852051 0.0000\n", + "LBFGS: 0 15:24:54 -3.841034 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.830047 0.0000\n", + "LBFGS: 0 15:24:54 -3.819091 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:54 -3.786400 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.808164 0.0000\n", - "LBFGS: 0 22:31:38 -3.797267 0.0000\n", + "LBFGS: 0 15:24:54 -3.797267 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.743230 0.0000\n", + "LBFGS: 0 15:24:54 -3.775562 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:54 -3.808164 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.764755 0.0000\n", + "LBFGS: 0 15:24:54 -3.764755 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.753978 0.0000\n", - "LBFGS: 0 22:31:38 -3.786400 0.0000\n", + "LBFGS: 0 15:24:54 -3.721825 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.775562 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:54 -3.753978 0.0000\n", + "LBFGS: 0 15:24:54 -3.732513 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.721825 0.0000\n", - "LBFGS: 0 22:31:38 -3.711167 0.0000\n", + "LBFGS: 0 15:24:54 -3.743230 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.732513 0.0000\n", + "LBFGS: 0 15:24:54 -3.679373 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.700539 0.0000\n", + "LBFGS: 0 15:24:54 -3.689941 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:54 -3.700539 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.679373 0.0000\n", - "LBFGS: 0 22:31:38 -3.689941 0.0000\n", + "LBFGS: 0 15:24:54 -3.711167 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.658325 0.0000\n", + "LBFGS: 0 15:24:54 -3.658325 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.626978 0.0000\n", + "LBFGS: 0 15:24:54 -3.668834 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.668834 0.0000\n", + "LBFGS: 0 15:24:54 -3.647846 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:55 -3.637397 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.647846 0.0000\n", + "LBFGS: 0 15:24:55 -3.626978 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.585597 0.0000\n", - "LBFGS: 0 22:31:38 -3.637397 0.0000\n", + "LBFGS: 0 15:24:55 -3.606228 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.595897 0.0000\n", + "LBFGS: 0 15:24:55 -3.595897 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.616588 0.0000\n", + "LBFGS: 0 15:24:55 -3.616588 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.575325 0.0000\n", + "LBFGS: 0 15:24:55 -3.585597 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.606228 0.0000\n", + "LBFGS: 0 15:24:55 -3.575325 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.534536 0.0000\n", + "LBFGS: 0 15:24:55 -3.565084 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.524412 0.0000\n", + "LBFGS: 0 15:24:55 -3.554872 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.565084 0.0000\n", + "LBFGS: 0 15:24:55 -3.534536 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:38 -3.554872 0.0000\n", + "LBFGS: 0 15:24:55 -3.544689 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.514318 0.0000\n", + "LBFGS: 0 15:24:55 -3.514318 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.544689 0.0000\n", + "LBFGS: 0 15:24:55 -3.524412 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.494218 0.0000\n", + "LBFGS: 0 15:24:55 -3.504253 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:55 -3.494218 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.504253 0.0000\n", - "LBFGS: 0 22:31:39 -3.474235 0.0000\n", + "LBFGS: 0 15:24:55 -3.474235 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.454369 0.0000\n", + "LBFGS: 0 15:24:55 -3.464287 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.484212 0.0000\n", + "LBFGS: 0 15:24:55 -3.484212 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.464287 0.0000\n", + "LBFGS: 0 15:24:55 -3.424788 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.424788 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.434619 0.0000\n", + "LBFGS: 0 15:24:55 -3.454369 0.0000\n", + "LBFGS: 0 15:24:55 -3.444480 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.405213 0.0000\n", + "LBFGS: 0 15:24:55 -3.434619 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:55 -3.405213 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.414986 0.0000\n", - "LBFGS: 0 22:31:39 -3.444480 0.0000\n", + "LBFGS: 0 15:24:55 -3.414986 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.395469 0.0000\n", + "LBFGS: 0 15:24:55 -3.376068 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.385754 0.0000\n", + "LBFGS: 0 15:24:55 -3.385754 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.376068 0.0000\n", + "LBFGS: 0 15:24:55 -3.395469 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.356781 0.0000\n", + "LBFGS: 0 15:24:55 -3.366410 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.328067 0.0000\n", + "LBFGS: 0 15:24:55 -3.356781 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.366410 0.0000\n", + "LBFGS: 0 15:24:55 -3.337610 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:55 -3.328067 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:55 -3.318553 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.337610 0.0000\n", - "LBFGS: 0 22:31:39 -3.347181 0.0000\n", - "LBFGS: 0 22:31:39 -3.299610 0.0000\n", + "LBFGS: 0 15:24:55 -3.347181 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.318553 0.0000\n", + "LBFGS: 0 15:24:55 -3.290181 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.309067 0.0000\n", + "LBFGS: 0 15:24:55 -3.309067 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.280781 0.0000\n", + "LBFGS: 0 15:24:55 -3.299610 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.271409 0.0000\n", + "LBFGS: 0 15:24:55 -3.280781 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.290181 0.0000\n", + "LBFGS: 0 15:24:55 -3.271409 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.262065 0.0000\n", + "LBFGS: 0 15:24:55 -3.262065 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:55 -3.252749 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.252749 0.0000\n", - "LBFGS: 0 22:31:39 -3.215768 0.0000\n", + "LBFGS: 0 15:24:55 -3.243462 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.234202 0.0000\n", + "LBFGS: 0 15:24:55 -3.224971 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.243462 0.0000\n", + "LBFGS: 0 15:24:55 -3.234202 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.224971 0.0000\n", + "LBFGS: 0 15:24:55 -3.215768 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.206592 0.0000\n", + "LBFGS: 0 15:24:55 -3.206592 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.197445 0.0000\n", + "LBFGS: 0 15:24:55 -3.197445 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.188325 0.0000\n", + "LBFGS: 0 15:24:55 -3.188325 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.170169 0.0000\n", + "LBFGS: 0 15:24:55 -3.179233 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.179233 0.0000\n", + "LBFGS: 0 15:24:55 -3.170169 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.161132 0.0000\n", + "LBFGS: 0 15:24:55 -3.152123 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.152123 0.0000\n", + "LBFGS: 0 15:24:55 -3.161132 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:55 -3.143141 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:55 -3.125260 0.0000\n", + "LBFGS: 0 15:24:55 -3.134187 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.107488 0.0000\n", - "LBFGS: 0 22:31:39 -3.134187 0.0000\n", - "LBFGS: 0 22:31:39 -3.125260 0.0000\n", - "LBFGS: 0 22:31:39 -3.143141 0.0000\n", + "LBFGS: 0 15:24:56 -3.116361 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.116361 0.0000\n", + "LBFGS: 0 15:24:56 -3.107488 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.098643 0.0000\n", + "LBFGS: 0 15:24:56 -3.089825 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.081034 0.0000\n", + "LBFGS: 0 15:24:56 -3.081034 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.046140 0.0000\n", + "LBFGS: 0 15:24:56 -3.098643 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.089825 0.0000\n", + "LBFGS: 0 15:24:56 -3.072271 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.072271 0.0000\n", + "LBFGS: 0 15:24:56 -3.046140 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.063534 0.0000\n", + "LBFGS: 0 15:24:56 -3.037483 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.037483 0.0000\n", + "LBFGS: 0 15:24:56 -3.054823 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.054823 0.0000\n", + "LBFGS: 0 15:24:56 -3.063534 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.020250 0.0000\n", + "LBFGS: 0 15:24:56 -3.028853 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:56 -3.011673 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -3.028853 0.0000\n", - "LBFGS: 0 22:31:39 -3.011673 0.0000\n", + "LBFGS: 0 15:24:56 -2.994599 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.994599 0.0000\n", + "LBFGS: 0 15:24:56 -3.003123 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:56 -3.020250 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.986101 0.0000\n", - "LBFGS: 0 22:31:39 -3.003123 0.0000\n", + "LBFGS: 0 15:24:56 -2.969185 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.977630 0.0000\n", + "LBFGS: 0 15:24:56 -2.977630 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.969185 0.0000\n", + "LBFGS: 0 15:24:56 -2.986101 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.952373 0.0000\n", + "LBFGS: 0 15:24:56 -2.960766 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.944006 0.0000\n", + "LBFGS: 0 15:24:56 -2.952373 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.960766 0.0000\n", + "LBFGS: 0 15:24:56 -2.944006 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.919060 0.0000\n", + "LBFGS: 0 15:24:56 -2.935665 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:56 -2.927350 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.927350 0.0000\n", - "LBFGS: 0 22:31:39 -2.935665 0.0000\n", + "LBFGS: 0 15:24:56 -2.919060 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.910796 0.0000\n", + "LBFGS: 0 15:24:56 -2.910796 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.902558 0.0000\n", + "LBFGS: 0 15:24:56 -2.894346 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.894346 0.0000\n", + "LBFGS: 0 15:24:56 -2.886159 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.877998 0.0000\n", + "LBFGS: 0 15:24:56 -2.902558 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 15:24:56 -2.877998 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.886159 0.0000\n", - "LBFGS: 0 22:31:39 -2.869862 0.0000\n", + "LBFGS: 0 15:24:56 -2.869862 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.845605 0.0000\n", + "LBFGS: 0 15:24:56 -2.861751 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.861751 0.0000\n", + "LBFGS: 0 15:24:56 -2.837570 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.853666 0.0000\n", + "LBFGS: 0 15:24:56 -2.845605 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.829560 0.0000\n", + "LBFGS: 0 15:24:56 -2.853666 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.837570 0.0000\n", + "LBFGS: 0 15:24:56 -2.829560 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.821575 0.0000\n", + "LBFGS: 0 15:24:56 -2.821575 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.805679 0.0000\n", + "LBFGS: 0 15:24:56 -2.805679 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 22:31:39 -2.813615 0.0000\n" + "LBFGS: 0 15:24:56 -2.813615 0.0000\n", + "CPU times: user 1.65 s, sys: 1.22 s, total: 2.87 s\n", + "Wall time: 9.77 s\n" ] } ], "source": [ - "ret, output = ProcessExecutor(max_processes=8).run(m)" + "%%time\n", + "exe = ProcessExecutor(max_processes=4).submit([m])\n", + "exe.run()\n", + "exe.wait()\n", + "output = exe.output[0]\n", + "ret = exe.status[0]" ] }, { "cell_type": "code", - "execution_count": 82, + "execution_count": 67, "id": "71bbb913-7d7a-4bb6-b775-3fbc8e7e1f35", "metadata": { "tags": [] @@ -2466,7 +2354,7 @@ "ReturnStatus(Code.DONE, None)" ] }, - "execution_count": 82, + "execution_count": 67, "metadata": {}, "output_type": "execute_result" } @@ -2477,7 +2365,7 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 68, "id": "4bf2df15-31dc-474c-b3df-f7c32b0fdaf2", "metadata": { "tags": [] @@ -2490,7 +2378,7 @@ " 3.48829227, 3.244546 , 3.00601254, 2.77258214, 2.54414756])" ] }, - "execution_count": 83, + "execution_count": 68, "metadata": {}, "output_type": "execute_result" } @@ -2501,13 +2389,13 @@ }, { "cell_type": "code", - "execution_count": 84, + "execution_count": 69, "id": "eb0a2daf-9dab-4174-bfee-0cd1ef8c474e", "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -2519,14 +2407,6 @@ "source": [ "output.plot()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "aceeb6a1-f673-43fa-9487-24b0153ccb9e", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/notebooks/tinybase/Basic.ipynb b/notebooks/tinybase/Basic.ipynb index a4eedf43d..6fb82c671 100644 --- a/notebooks/tinybase/Basic.ipynb +++ b/notebooks/tinybase/Basic.ipynb @@ -22,10 +22,30 @@ " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" ] }, + { + "data": { + "text/html": [ + "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "cfc617d25c58458ab02e487bed85e341",
+       "model_id": "47d3ef097a6e4dbdbb96ab21eb3b51a4",
        "version_major": 2,
        "version_minor": 0
       },
@@ -88,7 +108,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 5,
    "id": "9f2f3102-d15c-470a-b38c-f8084c9535ec",
    "metadata": {},
    "outputs": [],
@@ -106,7 +126,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 6,
    "id": "e125f49c-257b-4a24-bc81-83fe345d1dcf",
    "metadata": {},
    "outputs": [],
@@ -116,7 +136,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": 7,
    "id": "324f3c10-385e-4577-b089-c305f8203ca5",
    "metadata": {},
    "outputs": [
@@ -130,7 +150,7 @@
        "DataContainer([])"
       ]
      },
-     "execution_count": 11,
+     "execution_count": 7,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -141,7 +161,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 8,
    "id": "6c1f5af7-f5e9-41d9-a849-bab0ebc7dd9f",
    "metadata": {},
    "outputs": [
@@ -151,7 +171,7 @@
        "[]"
       ]
      },
-     "execution_count": 12,
+     "execution_count": 8,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -162,7 +182,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
+   "execution_count": 9,
    "id": "e0afb76d-d1b7-4b42-925f-fb117d58025e",
    "metadata": {},
    "outputs": [
@@ -172,7 +192,7 @@
        "{}"
       ]
      },
-     "execution_count": 13,
+     "execution_count": 9,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -183,7 +203,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": 10,
    "id": "6a5c3235-9c6b-481f-b316-db7420d1ad43",
    "metadata": {},
    "outputs": [],
@@ -193,7 +213,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": 11,
    "id": "4ade8d6a-6ce2-4f3a-b43d-71e1f87125bf",
    "metadata": {},
    "outputs": [
@@ -203,7 +223,7 @@
        "{'n': 10}"
       ]
      },
-     "execution_count": 15,
+     "execution_count": 11,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -214,7 +234,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 16,
+   "execution_count": 12,
    "id": "da69a4cc-409a-4f51-b329-06a69ce8e7f5",
    "metadata": {
     "tags": []
@@ -224,10 +244,10 @@
      "data": {
       "text/plain": [
        "(ReturnStatus(Code.DONE, None),\n",
-       " )"
+       " )"
       ]
      },
-     "execution_count": 16,
+     "execution_count": 12,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -254,7 +274,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 18,
+   "execution_count": 13,
    "id": "9bf053ed-14a1-4d05-80df-d5e135f2722f",
    "metadata": {
     "tags": []
@@ -266,7 +286,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 19,
+   "execution_count": 14,
    "id": "ab2584f3-4c66-4573-b3ab-265af626f5a5",
    "metadata": {
     "tags": []
@@ -278,7 +298,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
+   "execution_count": 15,
    "id": "9b995df8-09a5-45a0-b03e-ffa2706db25c",
    "metadata": {
     "tags": []
@@ -290,7 +310,7 @@
        "ReturnStatus(Code.DONE, None)"
       ]
      },
-     "execution_count": 20,
+     "execution_count": 15,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -301,7 +321,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
+   "execution_count": 16,
    "id": "b7ee90e1-7d99-46eb-bc69-138b986e6ebd",
    "metadata": {
     "tags": []
@@ -313,7 +333,7 @@
        "144"
       ]
      },
-     "execution_count": 21,
+     "execution_count": 16,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -332,7 +352,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 22,
+   "execution_count": 17,
    "id": "1e1b986e-9e00-41f2-86c2-945ff7818580",
    "metadata": {},
    "outputs": [],
@@ -342,7 +362,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 23,
+   "execution_count": 18,
    "id": "0b612150-f654-4995-8910-e46e766fdce2",
    "metadata": {},
    "outputs": [],
@@ -352,7 +372,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 57,
+   "execution_count": 19,
    "id": "097b7515-4875-4e22-8b7a-07594ce16204",
    "metadata": {
     "tags": []
@@ -364,7 +384,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 58,
+   "execution_count": 20,
    "id": "b334ac7e-35ae-4160-b6cf-96fa8672975a",
    "metadata": {
     "tags": []
@@ -376,7 +396,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 59,
+   "execution_count": 21,
    "id": "0d2f427a-21e1-449e-a8cc-c2296bff6c10",
    "metadata": {},
    "outputs": [
@@ -386,7 +406,7 @@
        ""
       ]
      },
-     "execution_count": 59,
+     "execution_count": 21,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -397,7 +417,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 60,
+   "execution_count": 22,
    "id": "a9631d5e-d46a-419c-a929-68ddd77487bb",
    "metadata": {},
    "outputs": [],
@@ -407,7 +427,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 61,
+   "execution_count": 23,
    "id": "408ffab0-70a1-4d08-9007-4d9f0513935d",
    "metadata": {},
    "outputs": [
@@ -417,7 +437,7 @@
        "927372692193078999176"
       ]
      },
-     "execution_count": 61,
+     "execution_count": 23,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -436,7 +456,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 36,
+   "execution_count": 24,
    "id": "ef72a65d-9020-46f6-b9f2-6cc57d7d016b",
    "metadata": {},
    "outputs": [],
@@ -446,7 +466,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 37,
+   "execution_count": 25,
    "id": "18607afd-8c43-4c88-8b40-5f758b1afab8",
    "metadata": {},
    "outputs": [],
@@ -456,7 +476,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 40,
+   "execution_count": 26,
    "id": "418a3d1b-abba-4609-881c-109e1e73fcff",
    "metadata": {
     "tags": []
@@ -468,7 +488,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 41,
+   "execution_count": 27,
    "id": "71a470dd-f25a-484b-9fbf-c758968ffb83",
    "metadata": {
     "tags": []
@@ -480,7 +500,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 43,
+   "execution_count": 28,
    "id": "0c5bf27b-31da-48ce-9344-4b24638f237a",
    "metadata": {},
    "outputs": [
@@ -490,7 +510,7 @@
        ""
       ]
      },
-     "execution_count": 43,
+     "execution_count": 28,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -501,7 +521,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 44,
+   "execution_count": 29,
    "id": "9765f4fe-262d-43fd-a122-a0ed1f97bc29",
    "metadata": {},
    "outputs": [],
@@ -511,7 +531,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 45,
+   "execution_count": 30,
    "id": "21f5ae38-f3e7-4f79-a38e-ef2531d537a1",
    "metadata": {},
    "outputs": [
@@ -521,7 +541,7 @@
        "927372692193078999176"
       ]
      },
-     "execution_count": 45,
+     "execution_count": 30,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -532,7 +552,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 46,
+   "execution_count": 31,
    "id": "760de692-42d0-4827-abf5-8f3afaf1a2b2",
    "metadata": {},
    "outputs": [
@@ -542,7 +562,7 @@
        ""
       ]
      },
-     "execution_count": 46,
+     "execution_count": 31,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -561,7 +581,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 47,
+   "execution_count": 32,
    "id": "e2fed9f1-590b-4ab5-9922-a126444e6169",
    "metadata": {},
    "outputs": [],
@@ -571,7 +591,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 48,
+   "execution_count": 33,
    "id": "fdfc8943-8c0b-4bc6-98f0-71a64b3fae27",
    "metadata": {},
    "outputs": [],
@@ -590,7 +610,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 49,
+   "execution_count": 34,
    "id": "dd709cfa-775f-41c1-a015-7e0647ec3d27",
    "metadata": {
     "scrolled": true,
@@ -604,26 +624,26 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 50,
+   "execution_count": 35,
    "id": "0ac1b35a-b130-4330-bf20-a1222bdc6103",
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "(,\n",
-       " ,\n",
-       " ,\n",
-       " ,\n",
-       " ,\n",
-       " ,\n",
-       " ,\n",
-       " ,\n",
-       " ,\n",
-       " )"
+       "(,\n",
+       " ,\n",
+       " ,\n",
+       " ,\n",
+       " ,\n",
+       " ,\n",
+       " ,\n",
+       " ,\n",
+       " ,\n",
+       " )"
       ]
      },
-     "execution_count": 50,
+     "execution_count": 35,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -634,7 +654,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 51,
+   "execution_count": 36,
    "id": "1ef8d9d6-e5dc-4db1-9e20-7181321f07ce",
    "metadata": {},
    "outputs": [
@@ -644,7 +664,7 @@
        "8"
       ]
      },
-     "execution_count": 51,
+     "execution_count": 36,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -663,7 +683,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 62,
+   "execution_count": 37,
    "id": "25fe617c-ae8e-4b83-bf58-b790441a1126",
    "metadata": {
     "scrolled": true,
@@ -677,7 +697,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 63,
+   "execution_count": 38,
    "id": "19e5d3e8-6779-4c36-a636-2d8cd549e99c",
    "metadata": {},
    "outputs": [],
@@ -687,7 +707,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 64,
+   "execution_count": 39,
    "id": "66feb98b-3f99-4bfb-9bb5-cccaf26d009b",
    "metadata": {},
    "outputs": [
@@ -706,7 +726,7 @@
        " ReturnStatus(Code.DONE, None)]"
       ]
      },
-     "execution_count": 64,
+     "execution_count": 39,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -717,26 +737,26 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 65,
+   "execution_count": 40,
    "id": "fbb40611-9f53-479e-854c-82c8c99a8070",
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "[,\n",
-       " ,\n",
-       " ,\n",
-       " ,\n",
-       " ,\n",
-       " ,\n",
-       " ,\n",
-       " ,\n",
-       " ,\n",
-       " ]"
+       "[,\n",
+       " ,\n",
+       " ,\n",
+       " ,\n",
+       " ,\n",
+       " ,\n",
+       " ,\n",
+       " ,\n",
+       " ,\n",
+       " ]"
       ]
      },
-     "execution_count": 65,
+     "execution_count": 40,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -747,7 +767,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 66,
+   "execution_count": 41,
    "id": "250f9c2d-5c71-4ddb-a94e-fd42f42cbeff",
    "metadata": {},
    "outputs": [
@@ -757,7 +777,7 @@
        "55"
       ]
      },
-     "execution_count": 66,
+     "execution_count": 41,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -776,7 +796,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 67,
+   "execution_count": 42,
    "id": "3dba0814-6a50-41f9-a78f-040014fdc140",
    "metadata": {},
    "outputs": [],
@@ -786,7 +806,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 68,
+   "execution_count": 43,
    "id": "52aae339-ebad-4621-b2e0-c55d4fea3d1b",
    "metadata": {},
    "outputs": [],
@@ -796,7 +816,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 69,
+   "execution_count": 44,
    "id": "e10f7ee9-98db-48c7-affd-465c2011f7b1",
    "metadata": {},
    "outputs": [],
@@ -806,7 +826,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 70,
+   "execution_count": 45,
    "id": "b7e58b55-b4f5-4e2a-aef5-f4e080e4d50c",
    "metadata": {},
    "outputs": [],
@@ -817,17 +837,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 71,
+   "execution_count": 46,
    "id": "b4b2212a-64df-4284-834d-8836c9a59b70",
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       ""
+       ""
       ]
      },
-     "execution_count": 71,
+     "execution_count": 46,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -838,7 +858,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 72,
+   "execution_count": 47,
    "id": "af337125-c4fe-497d-9374-b2d9301abe08",
    "metadata": {},
    "outputs": [],
@@ -848,7 +868,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 73,
+   "execution_count": 48,
    "id": "810a17bb-9f5d-4c50-9665-fa2f93070d60",
    "metadata": {},
    "outputs": [],
@@ -858,7 +878,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 74,
+   "execution_count": 49,
    "id": "4af47287-ab42-4cb4-8e65-c6efb7982ab4",
    "metadata": {},
    "outputs": [
@@ -868,7 +888,7 @@
        "ReturnStatus(Code.DONE, None)"
       ]
      },
-     "execution_count": 74,
+     "execution_count": 49,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -879,17 +899,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 75,
+   "execution_count": 50,
    "id": "705637d8-8da7-4429-ae6f-5401fc15cc9e",
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "144"
+       "12.0"
       ]
      },
-     "execution_count": 75,
+     "execution_count": 50,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -916,7 +936,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 76,
+   "execution_count": 56,
    "id": "b9807c98-6df8-450f-a8dd-1a53cb4ded35",
    "metadata": {},
    "outputs": [],
@@ -926,7 +946,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 77,
+   "execution_count": 57,
    "id": "ac2b9aa8-c118-4a1a-bf8b-96d6853b9be6",
    "metadata": {},
    "outputs": [],
@@ -936,7 +956,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 78,
+   "execution_count": 58,
    "id": "ef092015-5756-409a-bd1a-a31793c0b2b8",
    "metadata": {},
    "outputs": [],
@@ -946,7 +966,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 80,
+   "execution_count": 59,
    "id": "10b67618-f56e-4348-9fdc-35514d0e83a4",
    "metadata": {
     "tags": []
@@ -956,25 +976,25 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "0.6362656980328528\n",
-      "0.9334281883854404\n",
-      "0.6924270609164432\n",
-      "0.6860250441744892\n",
-      "0.022525309153174855\n",
-      "0.7312372220390315\n",
-      "0.48944745224752595\n",
-      "0.4136240195901667\n",
-      "0.2259678116613234\n"
+      "0.9662652321813043\n",
+      "0.27235911125432555\n",
+      "0.11494861653393462\n",
+      "0.8032152774735889\n",
+      "0.3227518840778105\n",
+      "0.6684733014540742\n",
+      "0.39643556152196313\n",
+      "0.8518645269264987\n",
+      "0.2961018352625848\n"
      ]
     },
     {
      "data": {
       "text/plain": [
        "(ReturnStatus(Code.DONE, None),\n",
-       " )"
+       " )"
       ]
      },
-     "execution_count": 80,
+     "execution_count": 59,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -983,6 +1003,43 @@
     "l.execute()"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": 66,
+   "id": "1162c965-93c8-40d4-b8da-2fa4eedb8b3e",
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "exe = BackgroundExecutor(max_threads=1).submit([l])\n",
+    "exe.run()\n",
+    "exe.wait()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 61,
+   "id": "a0db86f9-d974-44d5-893b-9c3a0a1c3ecb",
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "0.385781341996812"
+      ]
+     },
+     "execution_count": 61,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "exe.output[0].result"
+   ]
+  },
   {
    "cell_type": "markdown",
    "id": "1be0a463-f003-4a43-80a3-3e70df03a0bc",
@@ -993,7 +1050,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 89,
+   "execution_count": 62,
    "id": "6c251bfa-e8cf-4e1a-990d-451ebb53f713",
    "metadata": {},
    "outputs": [],
@@ -1003,7 +1060,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 90,
+   "execution_count": 63,
    "id": "563c7fe1-b96f-463c-8903-50f054c831f6",
    "metadata": {},
    "outputs": [],
@@ -1013,7 +1070,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 102,
+   "execution_count": 64,
    "id": "10130bfd-636f-4771-b30b-4648a8822f04",
    "metadata": {},
    "outputs": [],
@@ -1026,7 +1083,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 107,
+   "execution_count": 65,
    "id": "e65a16c1-40b4-4aa6-b382-c38405edd41e",
    "metadata": {
     "tags": []
@@ -1036,22 +1093,37 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "0.4416017514257269\n",
-      "0.3160638768343853\n",
-      "0.20690423045422135\n",
-      "0.5952022105132233\n",
-      "0.3844701289093476\n",
-      "0.996852574386064\n"
+      "0.3274925046984414\n",
+      "0.9161078074778493\n",
+      "0.5297212077927368\n",
+      "0.9890799661513604\n",
+      "0.42191837857926595\n",
+      "0.8488251952343272\n",
+      "0.9693005918735547\n",
+      "0.44102348613413955\n",
+      "0.18869549585034684\n",
+      "0.3953730135658532\n",
+      "0.15772497922300788\n",
+      "0.8254814730759052\n",
+      "0.6498952850955698\n",
+      "0.588999434645353\n",
+      "0.6858310499583608\n",
+      "0.6020208768884394\n",
+      "0.39978751512947674\n",
+      "0.7660632855985916\n",
+      "0.18473064026263886\n",
+      "0.2630763951239491\n",
+      "0.6478896023670242\n"
      ]
     },
     {
      "data": {
       "text/plain": [
        "(ReturnStatus(Code.DONE, None),\n",
-       " )"
+       " )"
       ]
      },
-     "execution_count": 107,
+     "execution_count": 65,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -1070,7 +1142,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 136,
+   "execution_count": 67,
    "id": "8ead2987-116c-4bba-a09a-4b28a71660f1",
    "metadata": {
     "tags": []
@@ -1104,7 +1176,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 139,
+   "execution_count": 68,
    "id": "c4170017-0825-4e2c-87b2-ea4ddc14499e",
    "metadata": {
     "tags": []
@@ -1114,17 +1186,17 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "CPU times: user 2.1 ms, sys: 12.2 ms, total: 14.3 ms\n",
+      "CPU times: user 6.81 ms, sys: 1.68 ms, total: 8.49 ms\n",
       "Wall time: 20 s\n"
      ]
     },
     {
      "data": {
       "text/plain": [
-       "(ReturnStatus(Code.DONE, None), <__main__.WaitOutput at 0x7f90c3da05b0>)"
+       "(ReturnStatus(Code.DONE, None), <__main__.WaitOutput at 0x7fa8474c2dd0>)"
       ]
      },
-     "execution_count": 139,
+     "execution_count": 68,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -1138,7 +1210,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 133,
+   "execution_count": 69,
    "id": "0ad95218-1e00-408e-8db3-858852d88e8f",
    "metadata": {
     "tags": []
@@ -1150,7 +1222,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 137,
+   "execution_count": 73,
    "id": "dc30851f-ed76-4bde-979f-9b42286b1645",
    "metadata": {
     "tags": []
@@ -1160,30 +1232,22 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "CPU times: user 8.73 ms, sys: 29.4 ms, total: 38.1 ms\n",
-      "Wall time: 20.2 s\n"
+      "CPU times: user 24 ms, sys: 25.8 ms, total: 49.8 ms\n",
+      "Wall time: 20.1 s\n"
      ]
-    },
-    {
-     "data": {
-      "text/plain": [
-       "(ReturnStatus(Code.DONE, None), <__main__.WaitOutput at 0x7f90c3da0190>)"
-      ]
-     },
-     "execution_count": 137,
-     "metadata": {},
-     "output_type": "execute_result"
     }
    ],
    "source": [
     "%%time\n",
-    "ProcessExecutor(max_processes=1).run(wait)"
+    "exe = ProcessExecutor(max_processes=1).submit([wait])\n",
+    "exe.run()\n",
+    "exe.wait()"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 141,
-   "id": "6b9393cf-3112-4055-a5cb-ebdb3b17aec6",
+   "execution_count": 76,
+   "id": "d2c0e09b-bd43-4bd5-8c94-6ced8a12fa1a",
    "metadata": {
     "tags": []
    },
@@ -1192,29 +1256,21 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "CPU times: user 12 ms, sys: 39.1 ms, total: 51.2 ms\n",
-      "Wall time: 6.06 s\n"
+      "CPU times: user 28.2 ms, sys: 41.8 ms, total: 69.9 ms\n",
+      "Wall time: 6.08 s\n"
      ]
-    },
-    {
-     "data": {
-      "text/plain": [
-       "(ReturnStatus(Code.DONE, None), <__main__.WaitOutput at 0x7f90c3da2d10>)"
-      ]
-     },
-     "execution_count": 141,
-     "metadata": {},
-     "output_type": "execute_result"
     }
    ],
    "source": [
     "%%time\n",
-    "ProcessExecutor(max_processes=4).run(wait)"
+    "exe = ProcessExecutor(max_processes=4).submit([wait])\n",
+    "exe.run()\n",
+    "exe.wait()"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 140,
+   "execution_count": 75,
    "id": "2bf18743-4760-4491-968c-49a7968ef6cf",
    "metadata": {
     "tags": []
@@ -1224,24 +1280,16 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "CPU times: user 7.62 ms, sys: 2.35 ms, total: 9.97 ms\n",
-      "Wall time: 6.02 s\n"
+      "CPU times: user 11.7 ms, sys: 3.65 ms, total: 15.3 ms\n",
+      "Wall time: 6.03 s\n"
      ]
-    },
-    {
-     "data": {
-      "text/plain": [
-       "(ReturnStatus(Code.DONE, None), <__main__.WaitOutput at 0x7f90c3da11b0>)"
-      ]
-     },
-     "execution_count": 140,
-     "metadata": {},
-     "output_type": "execute_result"
     }
    ],
    "source": [
     "%%time\n",
-    "BackgroundExecutor(max_threads=4).run(wait)"
+    "exe = BackgroundExecutor(max_threads=4).submit([wait])\n",
+    "exe.run()\n",
+    "exe.wait()"
    ]
   }
  ],
diff --git a/notebooks/tinybase/Shell.ipynb b/notebooks/tinybase/Shell.ipynb
index f1ef971d2..0a263ba9f 100644
--- a/notebooks/tinybase/Shell.ipynb
+++ b/notebooks/tinybase/Shell.ipynb
@@ -16,10 +16,30 @@
       "  warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n"
      ]
     },
+    {
+     "data": {
+      "text/html": [
+       "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "d3226fe934d24c2baa541d98e899a823",
+       "model_id": "68874afc69984bf092e93365122bb323",
        "version_major": 2,
        "version_minor": 0
       },
@@ -654,7 +674,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 65,
+   "execution_count": 35,
    "id": "9feaa502-d16a-4cac-bb36-e681c85a0a63",
    "metadata": {},
    "outputs": [],
@@ -664,7 +684,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 66,
+   "execution_count": 36,
    "id": "8c340f6c-c687-4461-9c33-f98b768773d6",
    "metadata": {
     "tags": []
@@ -676,7 +696,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 67,
+   "execution_count": 37,
    "id": "ab27d95c-3d7c-4710-9ff7-7de523086c6d",
    "metadata": {
     "tags": []
@@ -686,12 +706,10 @@
      "data": {
       "text/plain": [
        "['/home/poul/pyiron/contrib/notebooks/tinybase/resources',\n",
-       " '/home/poul/pyiron/contrib/notebooks/tinybase/resources',\n",
-       " '/home/poul/pyiron/contrib/scratch',\n",
        " '/home/poul/micromamba/envs/contrib/share/pyiron']"
       ]
      },
-     "execution_count": 67,
+     "execution_count": 37,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -702,7 +720,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 68,
+   "execution_count": 38,
    "id": "7e1bb029-a7b1-488c-8852-f0223c2e477a",
    "metadata": {
     "tags": []
@@ -714,7 +732,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 69,
+   "execution_count": 39,
    "id": "9e9b280d-917c-4c11-8eee-140312f3455e",
    "metadata": {
     "tags": []
@@ -724,12 +742,10 @@
      "data": {
       "text/plain": [
        "['/home/poul/pyiron/contrib/notebooks/tinybase/resources/test/bin/run_hello_1.2.3.sh',\n",
-       " '/home/poul/pyiron/contrib/notebooks/tinybase/resources/test/bin/run_hello_1.2.4.sh',\n",
-       " '/home/poul/pyiron/contrib/notebooks/tinybase/resources/test/bin/run_hello_1.2.3.sh',\n",
        " '/home/poul/pyiron/contrib/notebooks/tinybase/resources/test/bin/run_hello_1.2.4.sh']"
       ]
      },
-     "execution_count": 69,
+     "execution_count": 39,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -1036,7 +1052,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 60,
+   "execution_count": 56,
    "id": "6d1d8fdb-3ca8-4184-9b55-f82ed9719a37",
    "metadata": {
     "tags": []
@@ -1048,7 +1064,7 @@
        "['1.2.4', '1.2.3']"
       ]
      },
-     "execution_count": 60,
+     "execution_count": 56,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -1059,7 +1075,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 61,
+   "execution_count": 57,
    "id": "b276fa87-513f-41b9-86fa-39c54dcbb71e",
    "metadata": {
     "tags": []
@@ -1071,7 +1087,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 62,
+   "execution_count": 58,
    "id": "3a85fcd5-d80f-4197-8e8a-1b32c4f78ba5",
    "metadata": {
     "tags": []
@@ -1083,7 +1099,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 63,
+   "execution_count": 59,
    "id": "03566ee2-7f64-48d6-9cc7-97be44bc41c8",
    "metadata": {
     "tags": []
@@ -1095,7 +1111,7 @@
        "'Hello World!\\n'"
       ]
      },
-     "execution_count": 63,
+     "execution_count": 59,
      "metadata": {},
      "output_type": "execute_result"
     }
diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py
index e52f653d7..139b59fa8 100644
--- a/pyiron_contrib/tinybase/executor.py
+++ b/pyiron_contrib/tinybase/executor.py
@@ -136,20 +136,6 @@ class Executor:
     def submit(self, tasks: List[AbstractTask]) -> ExecutionContext:
         return ExecutionContext(tasks)
 
-    def run(self, gen: TaskGenerator):
-        gen = iter(gen)
-        tasks = next(gen)
-        while True:
-            exe = self.submit(tasks)
-            exe.run()
-            exe.wait()
-            try:
-                tasks = gen.send(list(zip(exe.status, exe.output)))
-            except StopIteration as stop:
-                ret, output = stop.args[0]
-                break
-        return ret, output
-
 from concurrent.futures import (
         ThreadPoolExecutor,
         ProcessPoolExecutor,

From 8fb2bcedc2a72f7f7730b90b2a81e8ea37f48f6f Mon Sep 17 00:00:00 2001
From: Marvin Poul 
Date: Fri, 16 Jun 2023 15:55:06 +0200
Subject: [PATCH 108/756] Allow constant value as defaults

Co-authored: samwaseda 

Rename the original `default` as `constructor` and added a new `default`
that takes (and copies) constant values.
---
 pyiron_contrib/tinybase/ase.py       |  2 +-
 pyiron_contrib/tinybase/container.py | 38 ++++++++++++++++++++--------
 pyiron_contrib/tinybase/shell.py     |  4 +--
 pyiron_contrib/tinybase/task.py      |  6 ++---
 4 files changed, 34 insertions(+), 16 deletions(-)

diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py
index d272c8d84..7c970e636 100644
--- a/pyiron_contrib/tinybase/ase.py
+++ b/pyiron_contrib/tinybase/ase.py
@@ -79,7 +79,7 @@ def parse():
 class AseMinimizeInput(AseInput, StructureInput, MinimizeInput):
 
     algo = StorageAttribute().type(str).default('LBFGS')
-    minimizer_kwargs = StorageAttribute().type(dict).default(dict)
+    minimizer_kwargs = StorageAttribute().type(dict).constructor(dict)
 
     def lbfgs(self, damping=None, alpha=None):
         self.algo = 'LBFGS'
diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py
index 99c068c81..1215710b0 100644
--- a/pyiron_contrib/tinybase/container.py
+++ b/pyiron_contrib/tinybase/container.py
@@ -1,6 +1,7 @@
 """Generic Input Base Clases"""
 
 import abc
+from copy import deepcopy
 import sys
 
 from pyiron_contrib.tinybase.storage import HasHDFAdapaterMixin
@@ -20,14 +21,14 @@ class StorageAttribute:
     writes and reads values to the underlying :attr:`.HasStorage.storage`. DataContainer of the class.  When accessing
     this property before setting it, `None` is returned.
 
-    It's possible to modify the default value and the accepted type of values by using the builder-style :meth:`.type`
-    and :meth:`.default` methods.
+    It's possible to modify the default value and the accepted type of values by using the builder-style :meth:`.type`,
+    :meth:`.default` and :meth:`.constructor` methods.
 
     >>> class MyType(HasStorage):
     ...     a = StorageAttribute()
     ...     b = StorageAttribute().type(int)
-    ...     c = StorageAttribute().default(list)
-    ...     d = StorageAttribute().default(lambda: 42).type(int)
+    ...     c = StorageAttribute().constructor(list)
+    ...     d = StorageAttribute().default(42).type(int)
 
     >>> t = MyType()
     >>> t.a # return None
@@ -35,7 +36,7 @@ class StorageAttribute:
     >>> t.b
     3
     >>> t.b = 'asdf'
-    TypeError(f"{value} is not of type {self.value_type}")
+    TypeError("'asdf' is not of type ")
     >>> t.c
     []
     >>> t.d
@@ -79,7 +80,23 @@ def type(self, value_type):
         self.value_type = value_type
         return self
 
-    def default(self, default_constructor):
+    def default(self, value):
+        """
+        Set a default value, if the attribute is accessed without being set.
+
+        The given value is deep copied before being set.  If your type does not
+        support this or it is inefficient, use :meth:`.constructor`.
+
+        Args:
+            value: any value to be used as default
+
+        Returns:
+            self: the object it is called on
+        """
+        self.default_constructor = lambda: deepcopy(value)
+        return self
+
+    def constructor(self, default_constructor):
         """
         Set a function to create a default value, if the attribute is accessed without being set.
 
@@ -94,6 +111,7 @@ def default(self, default_constructor):
 
     def doc(self, text):
         self.__doc__ = text
+        return self
 
 class AbstractContainer(HasStorage, HasHDFAdapaterMixin, abc.ABC):
 
@@ -146,10 +164,10 @@ class ForceOutput(AbstractOutput):
 
 class MDOutput(HasStructure, EnergyPotOutput):
 
-    pot_energies = StorageAttribute().type(list).default(list)
-    kin_energies = StorageAttribute().type(list).default(list)
-    forces = StorageAttribute().type(list).default(list)
-    structures = StorageAttribute().type(list).default(list)
+    pot_energies = StorageAttribute().type(list).constructor(list)
+    kin_energies = StorageAttribute().type(list).constructor(list)
+    forces = StorageAttribute().type(list).constructor(list)
+    structures = StorageAttribute().type(list).constructor(list)
 
     def plot_energies(self):
         plt.plot(self.pot_energies - np.min(self.pot_energies), label='pot')
diff --git a/pyiron_contrib/tinybase/shell.py b/pyiron_contrib/tinybase/shell.py
index 59e9a4992..6b1870cef 100644
--- a/pyiron_contrib/tinybase/shell.py
+++ b/pyiron_contrib/tinybase/shell.py
@@ -105,8 +105,8 @@ def __str__(self):
 
 class ShellInput(AbstractInput):
     command = StorageAttribute()
-    arguments = StorageAttribute().type(list).default(list)
-    environ = StorageAttribute().type(dict).default(dict)
+    arguments = StorageAttribute().type(list).constructor(list)
+    environ = StorageAttribute().type(dict).constructor(dict)
     working_directory = StorageAttribute().type(str)
     allowed_returncode = StorageAttribute().type(list)
 
diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py
index efe9c9cfa..b70568140 100644
--- a/pyiron_contrib/tinybase/task.py
+++ b/pyiron_contrib/tinybase/task.py
@@ -172,8 +172,8 @@ def _execute(self, output):
 # assert 
 
 class FunctionInput(AbstractInput):
-    args = StorageAttribute().type(list).default(list)
-    kwargs = StorageAttribute().type(dict).default(dict)
+    args = StorageAttribute().type(list).constructor(list)
+    kwargs = StorageAttribute().type(dict).constructor(dict)
 
 class FunctionOutput(AbstractOutput):
     result = StorageAttribute()
@@ -337,7 +337,7 @@ def __init__(self, condition, restart):
         self._condition = condition
         self._restart = restart
 
-    scratch = StorageAttribute().default(dict)
+    scratch = StorageAttribute().constructor(dict)
 
     def condition(self, task: AbstractTask, output: AbstractTask):
         """

From 1c891ab4a8a230c83f47711f3ce7cb3f54da0f0d Mon Sep 17 00:00:00 2001
From: Marvin Poul 
Date: Fri, 16 Jun 2023 16:48:24 +0200
Subject: [PATCH 109/756] Make assert clearer

---
 pyiron_contrib/tinybase/lammps.py | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/pyiron_contrib/tinybase/lammps.py b/pyiron_contrib/tinybase/lammps.py
index 5a9f55618..3ed8e73bb 100644
--- a/pyiron_contrib/tinybase/lammps.py
+++ b/pyiron_contrib/tinybase/lammps.py
@@ -83,10 +83,8 @@ def _execute(self, output):
         potential.copy_pot_files(self.input.working_directory)
 
         control = LammpsControl()
-        if self.input.calc_type == "static":
-            control.calc_static()
-        else:
-            assert "Cannot happen"
+        assert self.input.calc_type == "static", "Cannot happen"
+        control.calc_static()
         control.write_file(
             file_name="control.inp", cwd=self.input.working_directory
         )

From cf8631f5a9a4b90d1b3f0eb1cb932feb858b1e1c Mon Sep 17 00:00:00 2001
From: Marvin Poul 
Date: Fri, 16 Jun 2023 18:40:51 +0200
Subject: [PATCH 110/756] Add Dask executor

---
 pyiron_contrib/tinybase/executor.py | 25 ++++++++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py
index 139b59fa8..163b44a2b 100644
--- a/pyiron_contrib/tinybase/executor.py
+++ b/pyiron_contrib/tinybase/executor.py
@@ -164,7 +164,13 @@ def __init__(self, pool, tasks):
     def _process_future(self, future):
         task = self._futures[future]
 
-        status, output = future.result(timeout=0)
+        # do not specify a timeout even though we already know that the future
+        # has finished.  For executors with high latency, low number of workers
+        # and large number of tasks transfering the results back to this thread
+        # can take longer than one would naively assume (>1ms).  If this is the
+        # case we might trip the timeout while waiting for the result, botching
+        # the calculation
+        status, output = future.result()
         with self._lock:
             self._status[task] = status
             self._output[task] = output
@@ -237,3 +243,20 @@ def submit(self, tasks):
         if self._pool is None:
             self._pool = ProcessPoolExecutor(max_workers=self._max_processes)
         return FuturesExecutionContext(self._pool, tasks)
+
+from dask.distributed import Client, LocalCluster
+
+class DaskExecutor(Executor):
+    def __init__(self, client):
+        self._client = client
+
+    @classmethod
+    def from_cluster(cls, cluster):
+        return cls(Client(cluster))
+
+    @classmethod
+    def from_localcluster(cls, max_processes=None, **kwargs):
+        return cls(Client(LocalCluster(n_workers=max_processes, **kwargs)))
+
+    def submit(self, tasks):
+        return FuturesExecutionContext(self._client, tasks)

From aefcd2a087fdd0fb141a60583f30fc362bf80cdd Mon Sep 17 00:00:00 2001
From: Marvin Poul 
Date: Fri, 16 Jun 2023 18:41:28 +0200
Subject: [PATCH 111/756] Update ASE notebook with dask example

---
 notebooks/tinybase/ASE.ipynb | 130 +++++++++++++++++++++--------------
 1 file changed, 78 insertions(+), 52 deletions(-)

diff --git a/notebooks/tinybase/ASE.ipynb b/notebooks/tinybase/ASE.ipynb
index eb9c7a483..71216810b 100644
--- a/notebooks/tinybase/ASE.ipynb
+++ b/notebooks/tinybase/ASE.ipynb
@@ -83,7 +83,7 @@
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "0f2d12d00fbe46c4ba16c668713b921c",
+       "model_id": "f87e85a1403249f78fb6cbcbb0b58e70",
        "version_major": 2,
        "version_minor": 0
       },
@@ -1088,7 +1088,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 54,
+   "execution_count": 8,
    "id": "89169376-be36-4ceb-9f4e-6e1f3247bc62",
    "metadata": {},
    "outputs": [],
@@ -1098,7 +1098,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 55,
+   "execution_count": 9,
    "id": "2ee9f1d4-5b14-4340-98d4-4bd293af89a4",
    "metadata": {},
    "outputs": [],
@@ -1110,17 +1110,17 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 56,
+   "execution_count": 10,
    "id": "3d73a9de-7b4e-476a-b50a-ac6a3957a7ab",
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       ""
+       ""
       ]
      },
-     "execution_count": 56,
+     "execution_count": 10,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -1131,18 +1131,18 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 57,
+   "execution_count": 11,
    "id": "0f075d90-e636-49be-b1a6-741a56363f54",
    "metadata": {},
    "outputs": [],
    "source": [
-    "m.input.set_strain_range(.6, 1000)"
+    "m.input.set_strain_range(.6, 250)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 58,
-   "id": "e93ef33e-11a9-4f8a-a7c1-f9679654e4f1",
+   "execution_count": 12,
+   "id": "79c89012-5b28-4124-9681-2507e0690b49",
    "metadata": {
     "tags": []
    },
@@ -1151,112 +1151,138 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "CPU times: user 35.7 s, sys: 1.25 s, total: 37 s\n",
-      "Wall time: 36 s\n"
+      "CPU times: user 243 ms, sys: 146 ms, total: 389 ms\n",
+      "Wall time: 2.32 s\n"
      ]
-    },
-    {
-     "data": {
-      "text/plain": [
-       "(ReturnStatus(Code.DONE, None),\n",
-       " )"
-      ]
-     },
-     "execution_count": 58,
-     "metadata": {},
-     "output_type": "execute_result"
     }
    ],
    "source": [
     "%%time\n",
-    "m.execute()"
+    "exe = ProcessExecutor(max_processes=8).submit([m])\n",
+    "exe.run()\n",
+    "exe.wait()"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 59,
-   "id": "79c89012-5b28-4124-9681-2507e0690b49",
+   "execution_count": 13,
+   "id": "004502ce-2022-476a-b634-4237a6009f43",
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "from pyiron_contrib.tinybase.executor import DaskExecutor"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "id": "4c55e74b-5e7a-4c46-9eb1-1acf5dff2322",
    "metadata": {
     "tags": []
    },
    "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/home/poul/pyiron/contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n",
+      "  warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n",
+      "/home/poul/pyiron/contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n",
+      "  warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n"
+     ]
+    },
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "CPU times: user 1.25 s, sys: 607 ms, total: 1.85 s\n",
-      "Wall time: 11 s\n"
+      "CPU times: user 3.38 s, sys: 1.8 s, total: 5.18 s\n",
+      "Wall time: 45.8 s\n"
      ]
     }
    ],
    "source": [
     "%%time\n",
-    "exe = ProcessExecutor(max_processes=8).submit([m])\n",
+    "exe = DaskExecutor.from_localcluster(2).submit([m])\n",
     "exe.run()\n",
     "exe.wait()"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 60,
-   "id": "4df7bd02-4a74-4c0b-b618-926274f96560",
+   "execution_count": 15,
+   "id": "7fe57ead-1175-4a29-a398-9e0adf52973a",
    "metadata": {
     "tags": []
    },
    "outputs": [
     {
      "data": {
-      "image/png": "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uu+++1RcXCyHw6GxY8fqiSeeiFK18WWg1/aZZ57R5MmTlZaWJrfbrX/8x39UTU1NlKqNHxs2bND8+fNVUFAgi8WiV1555bTPWb9+vaZMmaKUlBSNGTNG//Ef/xH5QuPQQK/tSy+9pDlz5ignJ0eZmZmaPn261qxZE51i40wof2+7hfO9jCASglWrVmnRokW67777tHXrVl1yySWaN2+eDh482OfzGhoadP311+trX/talCqNP6Fc22uuuUZ/+9vf9Pjjj2vXrl167rnnNGHChChWHR8Gem3fffddXX/99brxxhu1fft2vfDCC9q0aZNuuummKFce+3w+nyZPnqwVK1b06/FlZWW6/PLLdckll2jr1q2699579eMf/1gvvvhihCuNPwO9ths2bNCcOXO0evVqbdmyRbNnz9b8+fO1devWCFcafwZ6bbuF/b3MwIBdcMEFxq233trj3IQJE4yf//znfT5vwYIFxj//8z8b999/vzF58uQIVhi/BnptX3/9dcPlchk1NTXRKC+uDfTaPvTQQ8aYMWN6nPvNb35jFBYWRqzGoUCS8fLLL/f5mHvuuceYMGFCj3O33HKLceGFF0awsvjXn2t7KhMnTjQeeOCB8Bc0hAzk2ob7vYwRkQFqbW3Vli1bNHfu3B7n586dq/fff7/X5z355JPau3ev7r///kiXGLdCubavvvqqpk6dqgcffFAjR47U+PHjdffdd6u5uTkaJceNUK7tjBkzdOjQIa1evVqGYejo0aP685//rCuuuCIaJQ9pH3zwwUl/Fl//+te1efNmtbW1mVTV0NTZ2Smv16usrCyzSxkSIvFeFtM3vYtF1dXV6ujoUF5eXo/zeXl5qqysPOVzdu/erZ///Od65513ZLNxyXsTyrXdt2+f3n33XaWkpOjll19WdXW1brvtNtXW1tIncoJQru2MGTP0zDPPaMGCBWppaVF7e7u++c1v6re//W00Sh7SKisrT/ln0d7erurqarndbpMqG3p+9atfyefz6ZprrjG7lLgXqfcyRkRCZLFYenxtGMZJ5ySpo6ND1157rR544AGNHz8+WuXFtf5eWynwrx2LxaJnnnlGF1xwgS6//HI9/PDD+uMf/8ioyCkM5Nru2LFDP/7xj/Uv//Iv2rJli9544w2VlZXp1ltvjUapQ96p/ixOdR6he+6557R06VKtWrVKubm5ZpcT1yL5XsY/zwcoOztbSUlJJ/0rsqqq6qR/4UiS1+vV5s2btXXrVt1xxx2SAm+ehmHIZrPpzTff1KWXXhqV2mPdQK+tJLndbo0cObLH7abPPPNMGYahQ4cOqbS0NKI1x4tQru3y5ct10UUX6ac//akk6ZxzzlF6erouueQS/eIXv+Bf7YOQn59/yj8Lm82mESNGmFTV0LJq1SrdeOONeuGFF3TZZZeZXU7ci+R7GSMiA2S32zVlyhStXbu2x/m1a9dqxowZJz0+MzNTn376qbZt2xY8br31Vp1xxhnatm2bpk2bFq3SY95Ar60kXXTRRTpy5IgaGxuD57744gtZrVYVFhZGtN54Esq1bWpqktXa838RSUlJko7/6x2hmT59+kl/Fm+++aamTp2q5ORkk6oaOp577jndcMMNevbZZ+lpCpOIvpcNut01AT3//PNGcnKy8fjjjxs7duwwFi1aZKSnpxv79+83DMMwfv7znxvXXXddr89n1UzvBnptvV6vUVhYaFx99dXG9u3bjfXr1xulpaXGTTfdZNavELMGem2ffPJJw2azGY899pixd+9e49133zWmTp1qXHDBBWb9CjHL6/UaW7duNbZu3WpIMh5++GFj69atxoEDBwzDOPna7tu3z0hLSzPuuusuY8eOHcbjjz9uJCcnG3/+85/N+hVi1kCv7bPPPmvYbDbj0UcfNSoqKoJHfX29Wb9CzBrotf2ycL2XEURC9OijjxrFxcWG3W43zjvvPGP9+vXB7y1cuNCYOXNmr88liPRtoNd2586dxmWXXWakpqYahYWFxuLFi42mpqYoVx0fBnptf/Ob3xgTJ040UlNTDbfbbfzgBz8wDh06FOWqY9/bb79tSDrpWLhwoWEYp76269atM77yla8YdrvdGD16tLFy5croFx4HBnptZ86c2efjcVwof29PFK73MothMMYKAADMQY8IAAAwDUEEAACYhiACAABMQxABAACmIYgAAADTEEQAAIBpCCIAAMA0BBEAAGAagggAADANQQQAAJiGIAIAAExDEAEAAKb5v1E7qt2HJCfYAAAAAElFTkSuQmCC\n",
       "text/plain": [
-       "
" + "[ReturnStatus(Code.DONE, None)]" ] }, + "execution_count": 15, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "exe.output[0].plot()" + "exe.status" ] }, { "cell_type": "code", - "execution_count": 61, - "id": "45162eb2-b23d-45c6-8aad-dfe9a6a484d1", - "metadata": {}, + "execution_count": 16, + "id": "0d999302-6f1d-48b6-865e-4af578d35cf7", + "metadata": { + "tags": [] + }, "outputs": [ { "data": { + "image/png": "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lZXJZrNp3Lhx+s1vfhOlauPLUM/ts88+q8mTJystLU1FRUX6p3/6JzU1NUWp2vjx1ltvaf78+SouLpbFYtFLL710xtesX79eU6ZMkd1u19ixY/Uf//EfkS80Dg313P7xj3/UnDlzlJeXp8zMTE2fPl2vvfZadIqNM6H8ve0Tzu8ygkgIVq1apYULF+r+++/Xtm3bNHPmTM2bN08HDhw47evcbre++c1v6qqrropSpfEnlHP75S9/WX/5y1/05JNP6uOPP9Zzzz2niRMnRrHq+DDUc/v222/rm9/8pm655Rbt2LFDL7zwgjZv3qxbb701ypXHvra2Nk2ePFnLly8f1PHV1dW65pprNHPmTG3btk333XefvvOd7+gPf/hDhCuNP0M9t2+99ZbmzJmj1atXa+vWrZo9e7bmz5+vbdu2RbjS+DPUc9sn7N9lBobskksuMW6//fZ++yZOnGjce++9p33djTfeaPzwhz80HnjgAWPy5MkRrDB+DfXcvvrqq4bL5TKampqiUV5cG+q5/elPf2qMHTu2376f//znRklJScRqHAkkGS+++OJpj1m8eLExceLEfvtuu+0249JLL41gZfFvMOf2VM455xzjwQcfDH9BI8hQzm24v8toERmizs5Obd26VXPnzu23f+7cudq4ceOAr3vqqae0Z88ePfDAA5EuMW6Fcm5ffvllTZ06VY888ohGjx6tCRMm6Hvf+568Xm80So4boZzbGTNm6ODBg1q9erUMw9Dhw4f1+9//Xp/97GejUfKI9u677570Z/GZz3xGW7ZsUVdXl0lVjUx+v18tLS3Kzs42u5QRIRLfZTF90btY1NjYqJ6eHhUUFPTbX1BQoPr6+lO+pqqqSvfee682bNig5GRO+UBCObd79+7V22+/LbvdrhdffFGNjY264447dPToUcaJnCCUcztjxgw9++yzuvHGG9XR0aHu7m59/vOf1y9+8YtolDyi1dfXn/LPoru7W42NjSoqKjKpspHn0UcfVVtbm7785S+bXUrci9R3GS0iIbJYLP0eG4Zx0j5J6unp0U033aQHH3xQEyZMiFZ5cW2w51YK/LZjsVj07LPP6pJLLtE111yjxx57TE8//TStIqcwlHP74Ycf6jvf+Y5+9KMfaevWrVqzZo2qq6t1++23R6PUEe9Ufxan2o/QPffcc1q6dKlWrVql/Px8s8uJa5H8LuPX8yHKzc1VUlLSSb9FNjQ0nPQbjiS1tLRoy5Yt2rZtm+666y5JgS9PwzCUnJys119/XVdeeWVUao91Qz23klRUVKTRo0f3u9z02WefLcMwdPDgQVVWVka05ngRyrldtmyZPvWpT+n73/++JOn8889Xenq6Zs6cqZ/85Cf81j4MhYWFp/yzSE5OVk5OjklVjSyrVq3SLbfcohdeeEGf/vSnzS4n7kXyu4wWkSFKTU3VlClTtHbt2n77165dqxkzZpx0fGZmprZv3673338/uN1+++0666yz9P7772vatGnRKj3mDfXcStKnPvUp1dbWqrW1Nbhv165dslqtKikpiWi98SSUc9ve3i6rtf9/EUlJSZKO//aO0EyfPv2kP4vXX39dU6dOVUpKiklVjRzPPfecbr75Zq1cuZIxTWES0e+yYQ93TUDPP/+8kZKSYjz55JPGhx9+aCxcuNBIT0839u3bZxiGYdx7773GN77xjQFfz6yZgQ313La0tBglJSXGDTfcYOzYscNYv369UVlZadx6661m/Qgxa6jn9qmnnjKSk5ONJ554wtizZ4/x9ttvG1OnTjUuueQSs36EmNXS0mJs27bN2LZtmyHJeOyxx4xt27YZ+/fvNwzj5HO7d+9eIy0tzbjnnnuMDz/80HjyySeNlJQU4/e//71ZP0LMGuq5XblypZGcnGz88pe/NOrq6oJbc3OzWT9CzBrquf2kcH2XEURC9Mtf/tIoKyszUlNTjYsuushYv3598LkFCxYYV1xxxYCvJYic3lDP7c6dO41Pf/rThsPhMEpKSoxFixYZ7e3tUa46Pgz13P785z83zjnnHMPhcBhFRUXG1772NePgwYNRrjr2vfnmm4akk7YFCxYYhnHqc7tu3TrjwgsvNFJTU43y8nJjxYoV0S88Dgz13F5xxRWnPR7HhfL39kTh+i6zGAZtrAAAwByMEQEAAKYhiAAAANMQRAAAgGkIIgAAwDQEEQAAYBqCCAAAMA1BBAAAmIYgAgAATEMQAQAApiGIAAAA0xBEAACAaQgiAADANP8f+Ziut0B0zN0AAAAASUVORK5CYII=\n", "text/plain": [ - "0.6788586373205143" + "
" ] }, - "execution_count": 61, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "output.get_structure().get_volume()" + "exe.output[0].plot()" ] }, { "cell_type": "code", - "execution_count": 62, - "id": "f0bcfe59-2168-4e74-9d7a-33d900368907", + "execution_count": 17, + "id": "45162eb2-b23d-45c6-8aad-dfe9a6a484d1", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "0.6788586373205143" - ] - }, - "execution_count": 62, - "metadata": {}, - "output_type": "execute_result" + "ename": "NameError", + "evalue": "name 'output' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[17], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43moutput\u001b[49m\u001b[38;5;241m.\u001b[39mget_structure()\u001b[38;5;241m.\u001b[39mget_volume()\n", + "\u001b[0;31mNameError\u001b[0m: name 'output' is not defined" + ] } ], + "source": [ + "output.get_structure().get_volume()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0bcfe59-2168-4e74-9d7a-33d900368907", + "metadata": {}, + "outputs": [], "source": [ "output.equilibrium_volume" ] From 404fa2ba5b455724a90488be7a29108738e711a4 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 19 Jun 2023 07:00:26 +0200 Subject: [PATCH 112/756] Compat for SQLAlchemy 2.0 --- pyiron_contrib/tinybase/database.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pyiron_contrib/tinybase/database.py b/pyiron_contrib/tinybase/database.py index c694064bc..0a6fc380c 100644 --- a/pyiron_contrib/tinybase/database.py +++ b/pyiron_contrib/tinybase/database.py @@ -158,11 +158,11 @@ def update_status(self, job_id, status): def _row_to_entry(self, job_data): return DatabaseEntry( - name=job_data["name"], - project=job_data["location"], - username=job_data["username"], - status=job_data["status"], - jobtype=job_data["type"] + name=job_data.name, + project=job_data.location, + username=job_data.username, + status=job_data.status, + jobtype=job_data.type ) def get_item(self, job_id: int) -> DatabaseEntry: @@ -222,6 +222,4 @@ def job_table(self) -> pd.DataFrame: ).join( JobType, Job.jobtype_id==JobType.id ) - return pd.DataFrame( - list(map(dict, query.all())) - ) + return pd.DataFrame([r._asdict() for r in query.all()]) From a10abbd6589d1e5e7e08d1db89c9a9a047e2c573 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 19 Jun 2023 07:00:37 +0200 Subject: [PATCH 113/756] Update notebooks --- notebooks/tinybase/ASE.ipynb | 32 -- notebooks/tinybase/Basic.ipynb | 160 +++++----- notebooks/tinybase/TinyJob.ipynb | 482 +++++++++---------------------- 3 files changed, 215 insertions(+), 459 deletions(-) diff --git a/notebooks/tinybase/ASE.ipynb b/notebooks/tinybase/ASE.ipynb index 71216810b..2959baafa 100644 --- a/notebooks/tinybase/ASE.ipynb +++ b/notebooks/tinybase/ASE.ipynb @@ -1255,38 +1255,6 @@ "exe.output[0].plot()" ] }, - { - "cell_type": "code", - "execution_count": 17, - "id": "45162eb2-b23d-45c6-8aad-dfe9a6a484d1", - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'output' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[17], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43moutput\u001b[49m\u001b[38;5;241m.\u001b[39mget_structure()\u001b[38;5;241m.\u001b[39mget_volume()\n", - "\u001b[0;31mNameError\u001b[0m: name 'output' is not defined" - ] - } - ], - "source": [ - "output.get_structure().get_volume()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f0bcfe59-2168-4e74-9d7a-33d900368907", - "metadata": {}, - "outputs": [], - "source": [ - "output.equilibrium_volume" - ] - }, { "cell_type": "markdown", "id": "e21f6582-e7ec-43be-80ec-e9ad53aabc43", diff --git a/notebooks/tinybase/Basic.ipynb b/notebooks/tinybase/Basic.ipynb index 6fb82c671..4bf8ce02e 100644 --- a/notebooks/tinybase/Basic.ipynb +++ b/notebooks/tinybase/Basic.ipynb @@ -45,7 +45,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "47d3ef097a6e4dbdbb96ab21eb3b51a4", + "model_id": "f655cbd6f562493b95a9c17636a5bf82", "version_major": 2, "version_minor": 0 }, @@ -244,7 +244,7 @@ "data": { "text/plain": [ "(ReturnStatus(Code.DONE, None),\n", - " )" + " )" ] }, "execution_count": 12, @@ -631,16 +631,16 @@ { "data": { "text/plain": [ - "(,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " )" + "(,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " )" ] }, "execution_count": 35, @@ -744,16 +744,16 @@ { "data": { "text/plain": [ - "[,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ]" + "[,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ]" ] }, "execution_count": 40, @@ -844,7 +844,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 46, @@ -936,7 +936,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 51, "id": "b9807c98-6df8-450f-a8dd-1a53cb4ded35", "metadata": {}, "outputs": [], @@ -946,7 +946,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 52, "id": "ac2b9aa8-c118-4a1a-bf8b-96d6853b9be6", "metadata": {}, "outputs": [], @@ -956,7 +956,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 53, "id": "ef092015-5756-409a-bd1a-a31793c0b2b8", "metadata": {}, "outputs": [], @@ -966,7 +966,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 54, "id": "10b67618-f56e-4348-9fdc-35514d0e83a4", "metadata": { "tags": [] @@ -976,25 +976,25 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.9662652321813043\n", - "0.27235911125432555\n", - "0.11494861653393462\n", - "0.8032152774735889\n", - "0.3227518840778105\n", - "0.6684733014540742\n", - "0.39643556152196313\n", - "0.8518645269264987\n", - "0.2961018352625848\n" + "0.7518040192195612\n", + "0.3729703946467765\n", + "0.7024811641924277\n", + "0.6933476543265069\n", + "0.5159262243479708\n", + "0.1606709747261854\n", + "0.2418212217425253\n", + "0.9048565203370044\n", + "0.8068809589505335\n" ] }, { "data": { "text/plain": [ "(ReturnStatus(Code.DONE, None),\n", - " )" + " )" ] }, - "execution_count": 59, + "execution_count": 54, "metadata": {}, "output_type": "execute_result" } @@ -1005,12 +1005,28 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 55, "id": "1162c965-93c8-40d4-b8da-2fa4eedb8b3e", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.009007595046278127\n", + "0.6981904244112043\n", + "0.09366484339630998\n", + "0.7249243225810421\n", + "0.9726438390471167\n", + "0.9140646976331244\n", + "0.5298676138281572\n", + "0.815698430213116\n", + "0.20660830939989228\n" + ] + } + ], "source": [ "exe = BackgroundExecutor(max_threads=1).submit([l])\n", "exe.run()\n", @@ -1019,7 +1035,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 56, "id": "a0db86f9-d974-44d5-893b-9c3a0a1c3ecb", "metadata": { "tags": [] @@ -1028,10 +1044,10 @@ { "data": { "text/plain": [ - "0.385781341996812" + "0.2947900991153958" ] }, - "execution_count": 61, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -1050,7 +1066,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 57, "id": "6c251bfa-e8cf-4e1a-990d-451ebb53f713", "metadata": {}, "outputs": [], @@ -1060,7 +1076,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 58, "id": "563c7fe1-b96f-463c-8903-50f054c831f6", "metadata": {}, "outputs": [], @@ -1070,7 +1086,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 59, "id": "10130bfd-636f-4771-b30b-4648a8822f04", "metadata": {}, "outputs": [], @@ -1083,7 +1099,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 60, "id": "e65a16c1-40b4-4aa6-b382-c38405edd41e", "metadata": { "tags": [] @@ -1093,37 +1109,19 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.3274925046984414\n", - "0.9161078074778493\n", - "0.5297212077927368\n", - "0.9890799661513604\n", - "0.42191837857926595\n", - "0.8488251952343272\n", - "0.9693005918735547\n", - "0.44102348613413955\n", - "0.18869549585034684\n", - "0.3953730135658532\n", - "0.15772497922300788\n", - "0.8254814730759052\n", - "0.6498952850955698\n", - "0.588999434645353\n", - "0.6858310499583608\n", - "0.6020208768884394\n", - "0.39978751512947674\n", - "0.7660632855985916\n", - "0.18473064026263886\n", - "0.2630763951239491\n", - "0.6478896023670242\n" + "0.22463320545667087\n", + "0.31828116952079233\n", + "0.3147338930435303\n" ] }, { "data": { "text/plain": [ "(ReturnStatus(Code.DONE, None),\n", - " )" + " )" ] }, - "execution_count": 65, + "execution_count": 60, "metadata": {}, "output_type": "execute_result" } @@ -1154,8 +1152,8 @@ "import time\n", "\n", "class WaitInput(AbstractInput):\n", - " time = StorageAttribute().type(float).default(lambda: 10.0)\n", - " n = StorageAttribute().type(int).default(lambda: 10)\n", + " time = StorageAttribute().type(float).default(10.0)\n", + " n = StorageAttribute().type(int).default(10)\n", "\n", "class WaitOutput(AbstractOutput):\n", " pass\n", @@ -1186,14 +1184,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 6.81 ms, sys: 1.68 ms, total: 8.49 ms\n", + "CPU times: user 10.4 ms, sys: 0 ns, total: 10.4 ms\n", "Wall time: 20 s\n" ] }, { "data": { "text/plain": [ - "(ReturnStatus(Code.DONE, None), <__main__.WaitOutput at 0x7fa8474c2dd0>)" + "(ReturnStatus(Code.DONE, None), <__main__.WaitOutput at 0x7fe47ecac3d0>)" ] }, "execution_count": 68, @@ -1222,7 +1220,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 70, "id": "dc30851f-ed76-4bde-979f-9b42286b1645", "metadata": { "tags": [] @@ -1232,7 +1230,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 24 ms, sys: 25.8 ms, total: 49.8 ms\n", + "CPU times: user 37.8 ms, sys: 32.9 ms, total: 70.7 ms\n", "Wall time: 20.1 s\n" ] } @@ -1246,7 +1244,7 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 71, "id": "d2c0e09b-bd43-4bd5-8c94-6ced8a12fa1a", "metadata": { "tags": [] @@ -1256,8 +1254,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 28.2 ms, sys: 41.8 ms, total: 69.9 ms\n", - "Wall time: 6.08 s\n" + "CPU times: user 34.7 ms, sys: 31.9 ms, total: 66.6 ms\n", + "Wall time: 6.17 s\n" ] } ], @@ -1270,7 +1268,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 72, "id": "2bf18743-4760-4491-968c-49a7968ef6cf", "metadata": { "tags": [] @@ -1280,7 +1278,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 11.7 ms, sys: 3.65 ms, total: 15.3 ms\n", + "CPU times: user 17.4 ms, sys: 0 ns, total: 17.4 ms\n", "Wall time: 6.03 s\n" ] } diff --git a/notebooks/tinybase/TinyJob.ipynb b/notebooks/tinybase/TinyJob.ipynb index d87a3148d..d14dd847e 100644 --- a/notebooks/tinybase/TinyJob.ipynb +++ b/notebooks/tinybase/TinyJob.ipynb @@ -24,10 +24,30 @@ " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" ] }, + { + "data": { + "text/html": [ + "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "b404f9ce2d094b63a28e9e902bb1f18c",
+       "model_id": "7257515de92a452395badcf83fb46b03",
        "version_major": 2,
        "version_minor": 0
       },
@@ -139,7 +159,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 29,
    "id": "f2e0123f-f0be-41ef-9733-af547b38d846",
    "metadata": {
     "tags": []
@@ -147,7 +167,7 @@
    "outputs": [],
    "source": [
     "import logging\n",
-    "logging.getLogger().setLevel(0)"
+    "logging.getLogger().setLevel(10)"
    ]
   },
   {
@@ -160,7 +180,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": 14,
    "id": "0f30286b-4434-4569-8f03-fadab46ebe34",
    "metadata": {
     "tags": []
@@ -180,7 +200,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 15,
    "id": "ba190052-7bc1-4383-b3da-c7221c4e38d0",
    "metadata": {
     "scrolled": true,
@@ -193,7 +213,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 19,
+   "execution_count": 16,
    "id": "1e21980e-14a6-4578-b4b9-8195a74a3593",
    "metadata": {
     "tags": []
@@ -205,7 +225,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
+   "execution_count": 17,
    "id": "18e6de26-308c-46ae-9672-b2db43447ea5",
    "metadata": {
     "tags": []
@@ -218,7 +238,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
+   "execution_count": 18,
    "id": "72848cd2-fd51-4ad8-b56e-ca686074bb26",
    "metadata": {
     "tags": []
@@ -226,14 +246,14 @@
    "outputs": [],
    "source": [
     "j.input.steps = 100\n",
-    "j.input.timestep = 3\n",
-    "j.input.temperature = 600\n",
+    "j.input.timestep = 3.0\n",
+    "j.input.temperature = 600.0\n",
     "j.input.output_steps = 20"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 22,
+   "execution_count": 19,
    "id": "56c0e73a-c42b-4814-a25a-e6974fea3d00",
    "metadata": {
     "tags": []
@@ -243,18 +263,8 @@
      "name": "stderr",
      "output_type": "stream",
      "text": [
-      "DEBUG:h5py._conv:Creating converter from 5 to 3\n"
+      "INFO:root:Job already finished!\n"
      ]
-    },
-    {
-     "data": {
-      "text/plain": [
-       ""
-      ]
-     },
-     "execution_count": 22,
-     "metadata": {},
-     "output_type": "execute_result"
     }
    ],
    "source": [
@@ -263,7 +273,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 23,
+   "execution_count": 20,
    "id": "49ccfe01-7b7e-4615-bf43-21c1bbffec66",
    "metadata": {
     "tags": []
@@ -272,7 +282,7 @@
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "e0c1174050024971b55df1bf3f7d89a1",
+       "model_id": "16c628e0bea4418f8c357e500e5ed146",
        "version_major": 2,
        "version_minor": 0
       },
@@ -298,7 +308,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 24,
+   "execution_count": 21,
    "id": "049f056e-47ff-4c2f-9e85-612744af15a8",
    "metadata": {
     "tags": []
@@ -310,7 +320,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 25,
+   "execution_count": 22,
    "id": "1137a899-b00b-4ce4-92df-23a4bbcf7aa8",
    "metadata": {
     "tags": []
@@ -322,7 +332,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 26,
+   "execution_count": 23,
    "id": "3a8cda32-df2e-4884-8cfd-84e438c5be69",
    "metadata": {
     "tags": []
@@ -336,7 +346,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 27,
+   "execution_count": 24,
    "id": "d4b81c3f-4667-4b99-a2b3-08c7ee7e2c82",
    "metadata": {
     "tags": []
@@ -350,7 +360,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 30,
+   "execution_count": 25,
    "id": "e7494fee-d565-45e3-a819-c77ab0d2c7f6",
    "metadata": {
     "scrolled": true,
@@ -358,111 +368,10 @@
    },
    "outputs": [
     {
-     "name": "stdout",
+     "name": "stderr",
      "output_type": "stream",
      "text": [
-      "       Step     Time          Energy         fmax\n",
-      "LBFGS:    0 17:09:11       11.288146      189.5231\n",
-      "LBFGS:    1 17:09:11        1.168671       43.6957\n",
-      "LBFGS:    2 17:09:11        0.860403       38.6924\n",
-      "LBFGS:    3 17:09:11        0.362400       30.3554\n",
-      "LBFGS:    4 17:09:11        0.004806       24.0865\n",
-      "LBFGS:    5 17:09:11       -0.267437       19.0615\n",
-      "LBFGS:    6 17:09:11       -0.471646       15.0628\n",
-      "LBFGS:    7 17:09:11       -0.623506       11.8810\n",
-      "LBFGS:    8 17:09:11       -0.735237        9.3518\n",
-      "LBFGS:    9 17:09:11       -0.816458        7.3435\n",
-      "LBFGS:   10 17:09:11       -0.874705        5.7512\n",
-      "LBFGS:   11 17:09:11       -0.915849        4.4909\n",
-      "LBFGS:   12 17:09:11       -0.944435        3.4955\n",
-      "LBFGS:   13 17:09:11       -0.963943        2.7113\n",
-      "LBFGS:   14 17:09:11       -0.977006        2.0956\n",
-      "LBFGS:   15 17:09:11       -0.985585        1.6137\n",
-      "LBFGS:   16 17:09:11       -0.991109        1.2382\n",
-      "LBFGS:   17 17:09:11       -0.994598        0.9468\n",
-      "LBFGS:   18 17:09:11       -0.996763        0.7216\n",
-      "LBFGS:   19 17:09:11       -0.998083        0.5484\n",
-      "LBFGS:   20 17:09:11       -0.998876        0.4157\n",
-      "LBFGS:   21 17:09:11       -0.999347        0.3144\n",
-      "LBFGS:   22 17:09:11       -0.999623        0.2374\n",
-      "LBFGS:   23 17:09:11       -0.999784        0.1790\n",
-      "LBFGS:   24 17:09:11       -0.999877        0.1348\n",
-      "LBFGS:   25 17:09:11       -0.999930        0.1014\n",
-      "LBFGS:   26 17:09:11       -0.999960        0.0762\n",
-      "LBFGS:   27 17:09:11       -0.999977        0.0573\n",
-      "LBFGS:   28 17:09:11       -0.999987        0.0430\n",
-      "LBFGS:   29 17:09:11       -0.999993        0.0323\n",
-      "LBFGS:   30 17:09:11       -0.999996        0.0242\n",
-      "LBFGS:   31 17:09:11       -0.999998        0.0182\n",
-      "LBFGS:   32 17:09:11       -0.999999        0.0136\n",
-      "LBFGS:   33 17:09:11       -0.999999        0.0102\n",
-      "LBFGS:   34 17:09:11       -1.000000        0.0077\n",
-      "LBFGS:   35 17:09:11       -1.000000        0.0058\n",
-      "LBFGS:   36 17:09:11       -1.000000        0.0043\n",
-      "LBFGS:   37 17:09:11       -1.000000        0.0032\n",
-      "LBFGS:   38 17:09:11       -1.000000        0.0024\n",
-      "LBFGS:   39 17:09:11       -1.000000        0.0018\n",
-      "LBFGS:   40 17:09:11       -1.000000        0.0014\n",
-      "LBFGS:   41 17:09:11       -1.000000        0.0010\n",
-      "LBFGS:   42 17:09:11       -1.000000        0.0008\n",
-      "LBFGS:   43 17:09:11       -1.000000        0.0006\n",
-      "LBFGS:   44 17:09:11       -1.000000        0.0004\n",
-      "LBFGS:   45 17:09:11       -1.000000        0.0003\n",
-      "LBFGS:   46 17:09:11       -1.000000        0.0002\n",
-      "LBFGS:   47 17:09:11       -1.000000        0.0002\n",
-      "LBFGS:   48 17:09:11       -1.000000        0.0001\n",
-      "LBFGS:   49 17:09:11       -1.000000        0.0001\n",
-      "LBFGS:   50 17:09:11       -1.000000        0.0001\n",
-      "LBFGS:   51 17:09:11       -1.000000        0.0001\n",
-      "LBFGS:   52 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   53 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   54 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   55 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   56 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   57 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   58 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   59 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   60 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   61 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   62 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   63 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   64 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   65 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   66 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   67 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   68 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   69 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   70 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   71 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   72 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   73 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   74 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   75 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   76 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   77 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   78 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   79 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   80 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   81 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   82 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   83 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   84 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   85 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   86 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   87 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   88 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   89 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   90 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   91 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   92 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   93 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   94 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   95 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   96 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   97 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   98 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:   99 17:09:11       -1.000000        0.0000\n",
-      "LBFGS:  100 17:09:11       -1.000000        0.0000\n"
+      "INFO:root:Job already finished!\n"
      ]
     }
    ],
@@ -474,7 +383,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 31,
+   "execution_count": 26,
    "id": "7c16a615-0913-4880-9694-2c125285babc",
    "metadata": {
     "tags": []
@@ -515,18 +424,6 @@
        "  \n",
        "    \n",
        "      0\n",
-       "      3\n",
-       "      pyiron\n",
-       "      murn\n",
-       "      3\n",
-       "      1\n",
-       "      3\n",
-       "      /home/poul/pyiron/contrib/notebooks/tinybase/t...\n",
-       "      finished\n",
-       "      MurnaghanTask\n",
-       "    \n",
-       "    \n",
-       "      1\n",
        "      4\n",
        "      pyiron\n",
        "      md\n",
@@ -538,7 +435,7 @@
        "      AseMDTask\n",
        "    \n",
        "    \n",
-       "      2\n",
+       "      1\n",
        "      5\n",
        "      pyiron\n",
        "      min\n",
@@ -549,15 +446,27 @@
        "      finished\n",
        "      AseMinimizeTask\n",
        "    \n",
+       "    \n",
+       "      2\n",
+       "      6\n",
+       "      pyiron\n",
+       "      murn\n",
+       "      3\n",
+       "      1\n",
+       "      6\n",
+       "      /home/poul/pyiron/contrib/notebooks/tinybase/t...\n",
+       "      finished\n",
+       "      MurnaghanTask\n",
+       "    \n",
        "  \n",
        "\n",
        ""
       ],
       "text/plain": [
        "   id username  name  jobtype_id  project_id  status_id  \\\n",
-       "0   3   pyiron  murn           3           1          3   \n",
-       "1   4   pyiron    md           1           1          4   \n",
-       "2   5   pyiron   min           2           1          5   \n",
+       "0   4   pyiron    md           1           1          4   \n",
+       "1   5   pyiron   min           2           1          5   \n",
+       "2   6   pyiron  murn           3           1          6   \n",
        "\n",
        "                                            location    status  \\\n",
        "0  /home/poul/pyiron/contrib/notebooks/tinybase/t...  finished   \n",
@@ -565,12 +474,12 @@
        "2  /home/poul/pyiron/contrib/notebooks/tinybase/t...  finished   \n",
        "\n",
        "              type  \n",
-       "0    MurnaghanTask  \n",
-       "1        AseMDTask  \n",
-       "2  AseMinimizeTask  "
+       "0        AseMDTask  \n",
+       "1  AseMinimizeTask  \n",
+       "2    MurnaghanTask  "
       ]
      },
-     "execution_count": 31,
+     "execution_count": 26,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -581,7 +490,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 32,
+   "execution_count": 27,
    "id": "3fb09d42-f800-46ee-9919-83180863e1ee",
    "metadata": {
     "tags": []
@@ -590,7 +499,7 @@
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "1331d57c32a7485c8540a76bcc5f0ed0",
+       "model_id": "d3db4f34e6854a4f9c2c8d065ce81347",
        "version_major": 2,
        "version_minor": 0
       },
@@ -624,150 +533,15 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 33,
+   "execution_count": 30,
    "id": "db691097-72c6-45a4-89b1-6ec16018c8b8",
    "metadata": {
     "tags": []
    },
    "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "DEBUG:matplotlib.pyplot:Loaded backend module://matplotlib_inline.backend_inline version unknown.\n",
-      "DEBUG:matplotlib.pyplot:Loaded backend module://matplotlib_inline.backend_inline version unknown.\n",
-      "DEBUG:matplotlib.font_manager:findfont: Matching sans\\-serif:style=normal:variant=normal:weight=normal:stretch=normal:size=10.0.\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralItalic.ttf', name='STIXGeneral', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/cmex10.ttf', name='cmex10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Oblique.ttf', name='DejaVu Sans Mono', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/cmtt10.ttf', name='cmtt10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneral.ttf', name='STIXGeneral', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 0.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Bold.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 0.33499999999999996\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBolIta.ttf', name='STIXNonUnicode', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/cmsy10.ttf', name='cmsy10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymReg.ttf', name='STIXSizeOneSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniBol.ttf', name='STIXNonUnicode', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-BoldOblique.ttf', name='DejaVu Sans', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 1.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/cmmi10.ttf', name='cmmi10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBolIta.ttf', name='STIXGeneral', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymReg.ttf', name='STIXSizeThreeSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerifDisplay.ttf', name='DejaVu Serif Display', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUni.ttf', name='STIXNonUnicode', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Italic.ttf', name='DejaVu Serif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Bold.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/cmr10.ttf', name='cmr10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizOneSymBol.ttf', name='STIXSizeOneSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Bold.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/cmss10.ttf', name='cmss10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-BoldOblique.ttf', name='DejaVu Sans Mono', style='oblique', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansDisplay.ttf', name='DejaVu Sans Display', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymBol.ttf', name='STIXSizeThreeSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/cmb10.ttf', name='cmb10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXNonUniIta.ttf', name='STIXNonUnicode', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSerif-BoldItalic.ttf', name='DejaVu Serif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFiveSymReg.ttf', name='STIXSizeFiveSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymBol.ttf', name='STIXSizeTwoSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymBol.ttf', name='STIXSizeFourSym', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans-Oblique.ttf', name='DejaVu Sans', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 1.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSansMono.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymReg.ttf', name='STIXSizeTwoSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXSizFourSymReg.ttf', name='STIXSizeFourSym', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/STIXGeneralBol.ttf', name='STIXGeneral', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/P052-BoldItalic.otf', name='P052', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/UbuntuMono-BI.ttf', name='Ubuntu Mono', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/rsfs10.ttf', name='rsfs10', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSansNarrow-Bold.otf', name='Nimbus Sans Narrow', style='normal', variant='normal', weight=700, stretch='condensed', size='scalable')) = 10.535\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSansNarrow-Oblique.otf', name='Nimbus Sans Narrow', style='oblique', variant='normal', weight=400, stretch='condensed', size='scalable')) = 11.25\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/D050000L.otf', name='D050000L', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-L.ttf', name='Ubuntu', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/UbuntuMono-R.ttf', name='Ubuntu Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusRoman-Bold.otf', name='Nimbus Roman', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSans-Italic.otf', name='Nimbus Sans', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSansNarrow-BoldOblique.otf', name='Nimbus Sans Narrow', style='oblique', variant='normal', weight=700, stretch='condensed', size='scalable')) = 11.535\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/noto/NotoSansMono-Regular.ttf', name='Noto Sans Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSansNarrow-Regular.ttf', name='Liberation Sans Narrow', style='normal', variant='normal', weight=400, stretch='condensed', size='scalable')) = 10.25\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf', name='Liberation Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-R.ttf', name='Ubuntu', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/noto/NotoSansMono-Bold.ttf', name='Noto Sans Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/esint10.ttf', name='esint10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/cmmi10.ttf', name='cmmi10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWBookman-Demi.otf', name='URW Bookman', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/P052-Bold.otf', name='P052', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-MI.ttf', name='Ubuntu', style='italic', variant='normal', weight=500, stretch='normal', size='scalable')) = 11.145\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusMonoPS-BoldItalic.otf', name='Nimbus Mono PS', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSans-Italic.ttf', name='Liberation Sans', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWBookman-DemiItalic.otf', name='URW Bookman', style='italic', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSansNarrow-Bold.ttf', name='Liberation Sans Narrow', style='normal', variant='normal', weight=700, stretch='condensed', size='scalable')) = 10.535\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf', name='Droid Sans Fallback', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf', name='Noto Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/msam10.ttf', name='msam10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-BI.ttf', name='Ubuntu', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-RI.ttf', name='Ubuntu', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/StandardSymbolsPS.otf', name='Standard Symbols PS', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/UbuntuMono-B.ttf', name='Ubuntu Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusMonoPS-Italic.otf', name='Nimbus Mono PS', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSansNarrow-Italic.ttf', name='Liberation Sans Narrow', style='italic', variant='normal', weight=400, stretch='condensed', size='scalable')) = 11.25\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/cmsy10.ttf', name='cmsy10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWGothic-Book.otf', name='URW Gothic', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 0.33499999999999996\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationMono-Bold.ttf', name='Liberation Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSansNarrow-BoldItalic.ttf', name='Liberation Sans Narrow', style='italic', variant='normal', weight=700, stretch='condensed', size='scalable')) = 11.535\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusMonoPS-Regular.otf', name='Nimbus Mono PS', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusRoman-BoldItalic.otf', name='Nimbus Roman', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWGothic-BookOblique.otf', name='URW Gothic', style='oblique', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/msbm10.ttf', name='msbm10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/UbuntuMono-RI.ttf', name='Ubuntu Mono', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/C059-BdIta.otf', name='C059', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/cmex10.ttf', name='cmex10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/C059-Italic.otf', name='C059', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWGothic-DemiOblique.otf', name='URW Gothic', style='oblique', variant='normal', weight=600, stretch='normal', size='scalable')) = 11.24\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusRoman-Regular.otf', name='Nimbus Roman', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationMono-Italic.ttf', name='Liberation Mono', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/Z003-MediumItalic.otf', name='Z003', style='italic', variant='normal', weight=500, stretch='normal', size='scalable')) = 11.145\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSerif-Italic.ttf', name='Liberation Serif', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf', name='Liberation Serif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/C059-Roman.otf', name='C059', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/P052-Roman.otf', name='P052', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationMono-BoldItalic.ttf', name='Liberation Mono', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/cmr10.ttf', name='cmr10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-B.ttf', name='Ubuntu', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-M.ttf', name='Ubuntu', style='normal', variant='normal', weight=500, stretch='normal', size='scalable')) = 10.145\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/eufm10.ttf', name='eufm10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWGothic-Demi.otf', name='URW Gothic', style='normal', variant='normal', weight=600, stretch='normal', size='scalable')) = 10.24\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSerif-BoldItalic.ttf', name='Liberation Serif', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf', name='Liberation Mono', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/stmary10.ttf', name='stmary10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/P052-Italic.otf', name='P052', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSans-BoldItalic.otf', name='Nimbus Sans', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-Th.ttf', name='Ubuntu', style='normal', variant='normal', weight=250, stretch='normal', size='scalable')) = 10.1925\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', name='DejaVu Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 0.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf', name='DejaVu Sans Mono', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/C059-Bold.otf', name='C059', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-C.ttf', name='Ubuntu Condensed', style='normal', variant='normal', weight=400, stretch='condensed', size='scalable')) = 10.25\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSans-BoldItalic.ttf', name='Liberation Sans', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWBookman-LightItalic.otf', name='URW Bookman', style='italic', variant='normal', weight=300, stretch='normal', size='scalable')) = 11.145\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf', name='DejaVu Serif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/ubuntu/Ubuntu-LI.ttf', name='Ubuntu', style='italic', variant='normal', weight=300, stretch='normal', size='scalable')) = 11.145\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/dsrom10.ttf', name='dsrom10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusRoman-Italic.otf', name='Nimbus Roman', style='italic', variant='normal', weight=400, stretch='normal', size='scalable')) = 11.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusMonoPS-Bold.otf', name='Nimbus Mono PS', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSans-Regular.otf', name='Nimbus Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSans-Bold.otf', name='Nimbus Sans', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/lyx/wasy10.ttf', name='wasy10', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf', name='Liberation Sans', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/NimbusSansNarrow-Regular.otf', name='Nimbus Sans Narrow', style='normal', variant='normal', weight=400, stretch='condensed', size='scalable')) = 10.25\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/truetype/liberation/LiberationSerif-Bold.ttf', name='Liberation Serif', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')) = 10.335\n",
-      "DEBUG:matplotlib.font_manager:findfont: score(FontEntry(fname='/usr/share/fonts/opentype/urw-base35/URWBookman-Light.otf', name='URW Bookman', style='normal', variant='normal', weight=300, stretch='normal', size='scalable')) = 10.145\n",
-      "DEBUG:matplotlib.font_manager:findfont: Matching sans\\-serif:style=normal:variant=normal:weight=normal:stretch=normal:size=10.0 to DejaVu Sans ('/home/poul/micromamba/envs/contrib/lib/python3.10/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf') with score of 0.050000.\n"
-     ]
-    },
     {
      "data": {
-      "image/png": "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PaW77rrL6dJSVvS3ZbDfnaNHjzpRUtIZ9+EjyjCMfs9N0xxwDCO3bNkyffzxx9q9e7fTpaSUuro6LV++XG+88YYyMjKcLieldXV1ac6cOVq7dq0k6corr9Snn36qDRs2ED7isHXrVr388svasmWLZs6cqQMHDmjFihUqKSnR4sWLnS4vpfG7M7RxHz4mTpwoj8czYJWjoaFhQCrFyDz88MPavn27du3apbKyMqfLSSn79u1TQ0ODZs+eHTsWiUS0a9cu1dTUKBQKyePxOFhh6iguLtYll1zS79iMGTP02muvOVRRavrZz36mVatW6c4775QkXXrppTp69Kiqq6sJH6NUVFQkqXsFpLi4OHac351e477nw+fzafbs2aqtre13vLa2VnPnznWoqtRkmqaWLVum119/XTt27NC0adOcLinlfPe739Unn3yiAwcOxB5z5szRPffcowMHDhA84jBv3rwBW70PHz6sqVOnOlRRamptbVVaWv+fAo/Hw1bbMZg2bZqKior6/e6Ew2Ht3LmT350e437lQ5Kqqqr0wx/+UHPmzNG1116rjRs36tixY1qyZInTpaWUpUuXasuWLfrDH/6gnJyc2GpSIBBQZmamw9WlhpycnAE9MtnZ2SosLKR3Jk4//elPNXfuXK1du1bf//739cEHH2jjxo3auHGj06WllEWLFumpp57SlClTNHPmTH344Yd67rnn9OCDDzpdWlJraWnRF198EXt+5MgRHThwQAUFBZoyZYpWrFihtWvXqqKiQhUVFVq7dq2ysrJ09913O1h1EnF2s03i/Mu//Is5depU0+fzmd/+9rfZHjoKkgZ9bNq0yenSUhpbbUfvP/7jP8xZs2aZfr/fnD59urlx40anS0o5wWDQXL58uTllyhQzIyPDvOiii8zHHnvMDIVCTpeW1N56661B/3m4ePFi0zS7t9s+8cQTZlFRken3+83rrrvO/OSTT5wtOokYpmmaDuUeAADgQuO+5wMAACQXwgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEio/w9hHopmxnvDfAAAAABJRU5ErkJggg==\n",
+      "image/png": "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PaW77rrL6dJSVvS3ZbDfnaNHjzpRUtIZ9+EjyjCMfs9N0xxwDCO3bNkyffzxx9q9e7fTpaSUuro6LV++XG+88YYyMjKcLieldXV1ac6cOVq7dq0k6corr9Snn36qDRs2ED7isHXrVr388svasmWLZs6cqQMHDmjFihUqKSnR4sWLnS4vpfG7M7RxHz4mTpwoj8czYJWjoaFhQCrFyDz88MPavn27du3apbKyMqfLSSn79u1TQ0ODZs+eHTsWiUS0a9cu1dTUKBQKyePxOFhh6iguLtYll1zS79iMGTP02muvOVRRavrZz36mVatW6c4775QkXXrppTp69Kiqq6sJH6NUVFQkqXsFpLi4OHac351e477nw+fzafbs2aqtre13vLa2VnPnznWoqtRkmqaWLVum119/XTt27NC0adOcLinlfPe739Unn3yiAwcOxB5z5szRPffcowMHDhA84jBv3rwBW70PHz6sqVOnOlRRamptbVVaWv+fAo/Hw1bbMZg2bZqKior6/e6Ew2Ht3LmT350e437lQ5Kqqqr0wx/+UHPmzNG1116rjRs36tixY1qyZInTpaWUpUuXasuWLfrDH/6gnJyc2GpSIBBQZmamw9WlhpycnAE9MtnZ2SosLKR3Jk4//elPNXfuXK1du1bf//739cEHH2jjxo3auHGj06WllEWLFumpp57SlClTNHPmTH344Yd67rnn9OCDDzpdWlJraWnRF198EXt+5MgRHThwQAUFBZoyZYpWrFihtWvXqqKiQhUVFVq7dq2ysrJ09913O1h1EnF2s03i/Mu//Is5depU0+fzmd/+9rfZHjoKkgZ9bNq0yenSUhpbbUfvP/7jP8xZs2aZfr/fnD59urlx40anS0o5wWDQXL58uTllyhQzIyPDvOiii8zHHnvMDIVCTpeW1N56661B/3m4ePFi0zS7t9s+8cQTZlFRken3+83rrrvO/OSTT5wtOokYpmmaDuUeAADgQuO+5wMAACQXwgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEio/w9hHopmxnvDfAAAAABJRU5ErkJggg==\n",
       "text/plain": [
        "
" ] @@ -782,14 +556,14 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 31, "id": "23ce6822-b38b-41f3-9269-109dbb152ecf", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "5b1c290394554c87b5c824cbc463ffed", + "model_id": "759d4d0f319d4ffaa54b959293166c9b", "version_major": 2, "version_minor": 0 }, @@ -815,7 +589,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 41, "id": "d0f62439-3492-4392-ac2a-2b2545b85527", "metadata": {}, "outputs": [], @@ -825,7 +599,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 42, "id": "5c9ab533-cf97-49a1-8c4b-0e5e2f9758c2", "metadata": {}, "outputs": [], @@ -835,7 +609,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 43, "id": "253237f0-b338-470c-bc54-3c7400a757b7", "metadata": {}, "outputs": [], @@ -846,7 +620,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 44, "id": "c801093b-499e-48a7-8444-77602ed88a96", "metadata": {}, "outputs": [], @@ -856,7 +630,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 45, "id": "1e30b36e-11e6-47d1-836e-cffea7b73cdd", "metadata": {}, "outputs": [], @@ -866,7 +640,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 46, "id": "9920e4b7-8395-4fb9-96c3-792b044c4e3a", "metadata": {}, "outputs": [], @@ -876,23 +650,31 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 47, "id": "18b5305a-8950-44af-bc2e-c9734b059713", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "DEBUG:h5py._conv:Creating converter from 5 to 3\n" + ] + } + ], "source": [ "exe = murn.run()" ] }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 48, "id": "836bb2ec-4295-4a3c-b976-7a35d04aad36", "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -917,7 +699,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 49, "id": "79a2bb61-0a5e-4a3a-b195-46d027738a0e", "metadata": {}, "outputs": [], @@ -927,7 +709,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 50, "id": "b4e6e0c9-a2c6-40ab-884e-a46e16c37b04", "metadata": {}, "outputs": [ @@ -965,7 +747,7 @@ "Index: []" ] }, - "execution_count": 36, + "execution_count": 50, "metadata": {}, "output_type": "execute_result" } @@ -976,13 +758,13 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 51, "id": "cef7c46f-551f-401e-96c2-214628e23967", "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1005,7 +787,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 52, "id": "e8a7ee30-d7a6-46fc-bf98-1b52c981470f", "metadata": {}, "outputs": [ @@ -1066,7 +848,7 @@ "0 MurnaghanTask " ] }, - "execution_count": 38, + "execution_count": 52, "metadata": {}, "output_type": "execute_result" } @@ -1077,17 +859,17 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 54, "id": "30871447-3e20-46ee-a58e-853d4f4cb5d9", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 39, + "execution_count": 54, "metadata": {}, "output_type": "execute_result" } @@ -1098,15 +880,15 @@ "j.input.structure = bulk('Fe', a=1.2, cubic=True).repeat(2)\n", "j.input.calculator = MorsePotential()\n", "j.input.steps = 100\n", - "j.input.timestep = 3\n", - "j.input.temperature = 600\n", + "j.input.timestep = 3.0\n", + "j.input.temperature = 600.0\n", "j.input.output_steps = 20\n", "j.run(how='background')" ] }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 55, "id": "e63d43c1-341f-4ec0-b0cf-9cd5ead51926", "metadata": {}, "outputs": [ @@ -1181,7 +963,7 @@ "1 AseMDTask " ] }, - "execution_count": 40, + "execution_count": 55, "metadata": {}, "output_type": "execute_result" } @@ -1200,17 +982,17 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 57, "id": "80da39e2-76d1-42e6-977f-241d2683188d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 41, + "execution_count": 57, "metadata": {}, "output_type": "execute_result" } @@ -1222,15 +1004,15 @@ "j.input.structure = bulk('Fe', a=1.2, cubic=True).repeat(2)\n", "j.input.calculator = MorsePotential()\n", "j.input.steps = 100\n", - "j.input.timestep = 3\n", - "j.input.temperature = 600\n", + "j.input.timestep = 3.0\n", + "j.input.temperature = 600.0\n", "j.input.output_steps = 20\n", "j.run(how='process')" ] }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 58, "id": "a567f96a-cbb3-4d2d-95d1-6dcecee7ddb8", "metadata": {}, "outputs": [ @@ -1288,7 +1070,7 @@ " 1\n", " 2\n", " /\n", - " running\n", + " finished\n", " AseMDTask\n", " \n", " \n", @@ -1310,7 +1092,7 @@ "text/plain": [ " id username name jobtype_id project_id status_id location status \\\n", "0 1 pyiron murn 1 1 1 / finished \n", - "1 2 pyiron md 2 1 2 / running \n", + "1 2 pyiron md 2 1 2 / finished \n", "2 3 pyiron md 2 2 3 /foo running \n", "\n", " type \n", @@ -1319,7 +1101,7 @@ "2 AseMDTask " ] }, - "execution_count": 42, + "execution_count": 58, "metadata": {}, "output_type": "execute_result" } @@ -1338,7 +1120,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 59, "id": "a6fefefb-b09c-4cee-b632-29f88bccfeee", "metadata": {}, "outputs": [], @@ -1348,7 +1130,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 60, "id": "3419f273-b94b-48cf-9373-7441baec2353", "metadata": {}, "outputs": [ @@ -1358,7 +1140,7 @@ "DatabaseEntry(name='murn', username='pyiron', project='/', status='finished', jobtype='MurnaghanTask')" ] }, - "execution_count": 44, + "execution_count": 60, "metadata": {}, "output_type": "execute_result" } @@ -1377,7 +1159,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 61, "id": "cf75b2e8-ec15-4846-bda4-9b6661e8b5fa", "metadata": {}, "outputs": [], @@ -1387,7 +1169,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 62, "id": "1b23728b-1050-47a4-bda0-bd5967ceed2e", "metadata": {}, "outputs": [], @@ -1397,7 +1179,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 63, "id": "4cd73be9-2c6d-4501-8a6c-0afc6083a4b9", "metadata": {}, "outputs": [], @@ -1407,7 +1189,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 64, "id": "e9d9a3e2-5c86-46b4-b6d0-bec3a9f448b0", "metadata": {}, "outputs": [], @@ -1417,7 +1199,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 65, "id": "99059ff6-18bd-40b9-85fc-d76e1828f7ac", "metadata": {}, "outputs": [ @@ -1427,7 +1209,7 @@ "[]" ] }, - "execution_count": 49, + "execution_count": 65, "metadata": {}, "output_type": "execute_result" } @@ -1438,17 +1220,17 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 66, "id": "cb4bb9fe-13bf-4736-aa68-662b980d4f00", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[(2, 'running')]" + "[(2, 'finished')]" ] }, - "execution_count": 50, + "execution_count": 66, "metadata": {}, "output_type": "execute_result" } @@ -1459,17 +1241,17 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 67, "id": "4641b048-b7c7-46a2-b67c-835c08916cb0", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[(1, 'finished'), (2, 'running'), (3, 'running')]" + "[(1, 'finished'), (2, 'finished'), (3, 'finished')]" ] }, - "execution_count": 51, + "execution_count": 67, "metadata": {}, "output_type": "execute_result" } @@ -1480,7 +1262,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 68, "id": "94364e54-b980-48cc-995b-daf977437b1b", "metadata": {}, "outputs": [ @@ -1490,7 +1272,7 @@ "[(1, '/'), (2, '/foo')]" ] }, - "execution_count": 52, + "execution_count": 68, "metadata": {}, "output_type": "execute_result" } @@ -1501,7 +1283,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 69, "id": "f32c43d5-19c8-4505-bf5c-9ac32bca51d0", "metadata": { "tags": [] @@ -1513,7 +1295,7 @@ "[(1, 'MurnaghanTask'), (2, 'AseMDTask')]" ] }, - "execution_count": 53, + "execution_count": 69, "metadata": {}, "output_type": "execute_result" } @@ -1521,6 +1303,14 @@ "source": [ "s.query(JobType.__table__).all()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2aa7afbb-add8-4bee-8151-5ffa8cc3a0b4", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From 8b0b05d728e7cb5a39a6cf7b8fe0d6e9f4e1df64 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jun 2023 11:57:14 +0000 Subject: [PATCH 114/756] Bump pyparsing from 3.0.9 to 3.1.0 Bumps [pyparsing](https://github.com/pyparsing/pyparsing) from 3.0.9 to 3.1.0. - [Release notes](https://github.com/pyparsing/pyparsing/releases) - [Changelog](https://github.com/pyparsing/pyparsing/blob/master/CHANGES) - [Commits](https://github.com/pyparsing/pyparsing/compare/pyparsing_3.0.9...3.1.0) --- updated-dependencies: - dependency-name: pyparsing dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 450512b78..6e01a14ae 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ 'pyiron_base==0.6.1', 'scipy==1.10.1', 'seaborn==0.12.2', - 'pyparsing==3.0.9' + 'pyparsing==3.1.0' ], extras_require={ 'atomistic': [ From b0d1d4bab75155f4e3805f07a868c60a274b7272 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jun 2023 11:57:26 +0000 Subject: [PATCH 115/756] Bump boto3 from 1.26.151 to 1.26.155 Bumps [boto3](https://github.com/boto/boto3) from 1.26.151 to 1.26.155. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.26.151...1.26.155) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 450512b78..f0f28f131 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ ], 'image': ['scikit-image==0.19.3'], 'generic': [ - 'boto3==1.26.151', + 'boto3==1.26.155', 'moto==4.1.11' ], 'workflow': [ From 32cecaf29b211238635c677f77163b55b872934e Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 19 Jun 2023 11:57:37 +0000 Subject: [PATCH 116/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index af5f36407..b37415f40 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -10,7 +10,7 @@ dependencies: - numpy =1.24.3 - pyiron_base =0.6.1 - pyiron_atomistics =0.3.0 -- pyparsing =3.0.9 +- pyparsing =3.1.0 - scipy =1.10.1 - seaborn =0.12.2 - scikit-image =0.19.3 From 459102c6a19cf4b6a999d6096631f240c9fcac24 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 19 Jun 2023 11:57:48 +0000 Subject: [PATCH 117/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index af5f36407..c4cfba615 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.151 +- boto3 =1.26.155 - moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 From 1f92e64eac620f8775164cd5afd986b865381a53 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 19 Jun 2023 11:58:05 +0000 Subject: [PATCH 118/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index c566205e6..465fd4dab 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -10,7 +10,7 @@ dependencies: - numpy =1.24.3 - pyiron_base =0.6.1 - pyiron_atomistics =0.3.0 -- pyparsing =3.0.9 +- pyparsing =3.1.0 - scipy =1.10.1 - seaborn =0.12.2 - scikit-image =0.19.3 diff --git a/docs/environment.yml b/docs/environment.yml index 7963ebbf6..66f3b7e57 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -12,7 +12,7 @@ dependencies: - numpy =1.24.3 - pyiron_base =0.6.1 - pyiron_atomistics =0.3.0 -- pyparsing =3.0.9 +- pyparsing =3.1.0 - scipy =1.10.1 - seaborn =0.12.2 - scikit-image =0.19.3 From 975444cff7869620a3d7adf1ccf1919742c7351c Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 19 Jun 2023 11:58:12 +0000 Subject: [PATCH 119/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index c566205e6..2fd8b68db 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.151 +- boto3 =1.26.155 - moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 diff --git a/docs/environment.yml b/docs/environment.yml index 7963ebbf6..dfb8a2709 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -17,7 +17,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.151 +- boto3 =1.26.155 - moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 From ab6f7afcb0f9798fba51b34555ca090b542815e2 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 19 Jun 2023 14:56:18 +0000 Subject: [PATCH 120/756] add files.py --- pyiron_contrib/workflow/files.py | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 pyiron_contrib/workflow/files.py diff --git a/pyiron_contrib/workflow/files.py b/pyiron_contrib/workflow/files.py new file mode 100644 index 000000000..b0b1eca11 --- /dev/null +++ b/pyiron_contrib/workflow/files.py @@ -0,0 +1,58 @@ +from pathlib import Path + + +def delete_files_and_directories_recursively(path): + if not path.exists(): + return + for item in path.rglob('*'): + if item.is_file(): + item.unlink() + else: + delete_files_and_directories_recursively(item) + path.rmdir() + + +class DirectoryObject: + def __init__(self, directory): + self.path = Path(path) + self.create() + + def create(self): + self.path.mkdir(parents=True, exist_ok=True) + + def delete(self): + delete_files_and_directories_recursively(self.path) + + def list_files(self): + return list(self.path.glob('*')) + + def __len__(self): + return len(self.list_files()) + + def __repr__(self): + return f"DirectoryObject(directory='{self.path}' with {len(self)} files)" + + def write(self, file_name, content, mode="w"): + path = self.path / Path(file_name) + with path.open(mode=mode) as f: + f.write(content) + + +class FileObject: + def __init__(self, file_name: str, directory: DirectoryObject): + self.directory = directory + self._file_name = file_name + + @property + def file_name(self): + return self._file_name + + @property + def path(self): + return self.directory.path / Path(self._file_name) + + def write(self, content, mode='w'): + self.directory.write(file_name=self.file_name, content=content, mode=mode) + + def delete(self): + self.path.unlink() From 5d2cad13c19d4a4515a5ce80d3de8674320e5642 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 19 Jun 2023 16:35:33 +0000 Subject: [PATCH 121/756] add file object --- pyiron_contrib/workflow/files.py | 5 ++++- pyiron_contrib/workflow/node.py | 19 +++++++++++++++++++ pyiron_contrib/workflow/workflow.py | 8 ++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/files.py b/pyiron_contrib/workflow/files.py index b0b1eca11..11be709ad 100644 --- a/pyiron_contrib/workflow/files.py +++ b/pyiron_contrib/workflow/files.py @@ -14,7 +14,7 @@ def delete_files_and_directories_recursively(path): class DirectoryObject: def __init__(self, directory): - self.path = Path(path) + self.path = Path(directory) self.create() def create(self): @@ -37,6 +37,9 @@ def write(self, file_name, content, mode="w"): with path.open(mode=mode) as f: f.write(content) + def create_subdirectory(self, path): + return DirectoryObject(self.path / path) + class FileObject: def __init__(self, file_name: str, directory: DirectoryObject): diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 22267c9ff..57dcb8af4 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -14,6 +14,7 @@ from pyiron_contrib.workflow.has_channel import HasChannel from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Inputs, Outputs, Signals +from pyiron_contrib.workflow.files import FileObject if TYPE_CHECKING: from pyiron_contrib.workflow.workflow import Workflow @@ -364,6 +365,8 @@ def __init__( if update_on_instantiation: self.update() + self._working_directory = None + @property def _input_args(self): return inspect.signature(self.node_function).parameters @@ -572,6 +575,22 @@ def to_dict(self): "signals": self.signals.to_dict(), } + @property + def working_directory(self): + if self._working_directory is None: + if self.workflow is None: + raise ValueError( + "working directory is available only if the node is" + " attached to a workflow" + ) + self._working_directory = self.workflow.working_directory.create_subdirectory( + self.label + ) + return self._working_directory + + def create_file(self, file_name): + return FileObject(file_name, self.working_directory) + class FastNode(Node): """ diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index ba460416f..aab78eae3 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -7,6 +7,7 @@ from pyiron_contrib.workflow.node import Node, node, fast_node, single_value_node from pyiron_contrib.workflow.node_library import atomistics, package, standard from pyiron_contrib.workflow.util import DotDict +from pyiron_contrib.workflow.files import DirectoryObject class _NodeAdder: @@ -153,11 +154,18 @@ def __init__(self, label: str, *nodes: Node, strict_naming=True): self.__dict__["nodes"] = DotDict() self.__dict__["add"] = _NodeAdder(self) self.__dict__["strict_naming"] = strict_naming + self.__dict__["working_directory"] = None # We directly assign using __dict__ because we override the setattr later for node in nodes: self.add_node(node) + @property + def working_directory(self): + if self.__dict__["working_directory"] is None: + self.__dict__["working_directory"] = DirectoryObject(self.label) + return self.__dict__["working_directory"] + def add_node(self, node: Node, label: str = None) -> None: """ Assign a node to the workflow. Optionally provide a new label for that node. From 84687ecde38989d86665d6175502d4721bff5b2e Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 19 Jun 2023 17:05:16 +0000 Subject: [PATCH 122/756] add server object --- pyiron_contrib/workflow/node.py | 2 ++ pyiron_contrib/workflow/workflow.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 22267c9ff..190852326 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -14,6 +14,7 @@ from pyiron_contrib.workflow.has_channel import HasChannel from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Inputs, Outputs, Signals +from pyiron_base.jobs.job.extension.server.generic import Server if TYPE_CHECKING: from pyiron_contrib.workflow.workflow import Workflow @@ -360,6 +361,7 @@ def __init__( elif k not in self._init_keywords: warnings.warn(f"The keyword '{k}' was received but not used.") self.run_on_updates = run_on_updates + self.server = Server() if update_on_instantiation: self.update() diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index ba460416f..9df1a8897 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -7,6 +7,7 @@ from pyiron_contrib.workflow.node import Node, node, fast_node, single_value_node from pyiron_contrib.workflow.node_library import atomistics, package, standard from pyiron_contrib.workflow.util import DotDict +from pyiron_base.jobs.job.extension.server.generic import Server class _NodeAdder: @@ -153,6 +154,7 @@ def __init__(self, label: str, *nodes: Node, strict_naming=True): self.__dict__["nodes"] = DotDict() self.__dict__["add"] = _NodeAdder(self) self.__dict__["strict_naming"] = strict_naming + self.__dict__["server"] = Server() # We directly assign using __dict__ because we override the setattr later for node in nodes: From ddaed6c886ad185363fc501edf738e0796404980 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 19 Jun 2023 17:10:16 +0000 Subject: [PATCH 123/756] Format black --- pyiron_contrib/workflow/files.py | 10 +++++----- pyiron_contrib/workflow/node.py | 8 +++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pyiron_contrib/workflow/files.py b/pyiron_contrib/workflow/files.py index 11be709ad..f3532c54e 100644 --- a/pyiron_contrib/workflow/files.py +++ b/pyiron_contrib/workflow/files.py @@ -4,14 +4,14 @@ def delete_files_and_directories_recursively(path): if not path.exists(): return - for item in path.rglob('*'): + for item in path.rglob("*"): if item.is_file(): item.unlink() else: delete_files_and_directories_recursively(item) path.rmdir() - - + + class DirectoryObject: def __init__(self, directory): self.path = Path(directory) @@ -24,7 +24,7 @@ def delete(self): delete_files_and_directories_recursively(self.path) def list_files(self): - return list(self.path.glob('*')) + return list(self.path.glob("*")) def __len__(self): return len(self.list_files()) @@ -54,7 +54,7 @@ def file_name(self): def path(self): return self.directory.path / Path(self._file_name) - def write(self, content, mode='w'): + def write(self, content, mode="w"): self.directory.write(file_name=self.file_name, content=content, mode=mode) def delete(self): diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 57dcb8af4..c27423a99 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -498,7 +498,9 @@ def run(self) -> None: if self.server is None: try: if "self" in self._input_args: - function_output = self.node_function(self=self, **self.inputs.to_value_dict()) + function_output = self.node_function( + self=self, **self.inputs.to_value_dict() + ) else: function_output = self.node_function(**self.inputs.to_value_dict()) except Exception as e: @@ -583,8 +585,8 @@ def working_directory(self): "working directory is available only if the node is" " attached to a workflow" ) - self._working_directory = self.workflow.working_directory.create_subdirectory( - self.label + self._working_directory = ( + self.workflow.working_directory.create_subdirectory(self.label) ) return self._working_directory From 04d28df11c88dffd5665c3ef0c3812e1f9eab8d8 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 19 Jun 2023 17:10:30 +0000 Subject: [PATCH 124/756] Format black --- pyiron_contrib/workflow/node.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 190852326..52ac3509e 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -497,7 +497,9 @@ def run(self) -> None: if self.server is None: try: if "self" in self._input_args: - function_output = self.node_function(self=self, **self.inputs.to_value_dict()) + function_output = self.node_function( + self=self, **self.inputs.to_value_dict() + ) else: function_output = self.node_function(**self.inputs.to_value_dict()) except Exception as e: From 661410b1197eb61867993a8d320af949575526e6 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 19 Jun 2023 18:21:01 +0000 Subject: [PATCH 125/756] Format black --- pyiron_contrib/workflow/node_library/atomistics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/node_library/atomistics.py b/pyiron_contrib/workflow/node_library/atomistics.py index 94ab35878..781d039b4 100644 --- a/pyiron_contrib/workflow/node_library/atomistics.py +++ b/pyiron_contrib/workflow/node_library/atomistics.py @@ -144,7 +144,7 @@ def calc_md(job, n_ionic_steps, n_print, temperature, pressure): n_ionic_steps=n_ionic_steps, n_print=n_print, temperature=temperature, - pressure=pressure + pressure=pressure, ) From 7227fe41184f6f755c6934f6c220225570bf96db Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 19 Jun 2023 14:30:46 -0700 Subject: [PATCH 126/756] Rename node attribute Workflow to parent, in anticipation of macros being parents --- pyiron_contrib/workflow/node.py | 16 ++++++++-------- pyiron_contrib/workflow/node_library/package.py | 6 +++--- pyiron_contrib/workflow/workflow.py | 14 +++++++------- tests/unit/workflow/test_node_package.py | 4 ++-- tests/unit/workflow/test_workflow.py | 10 +++++----- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index e0fb93c54..761d9a601 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -320,7 +320,7 @@ def __init__( run_on_updates: bool = False, update_on_instantiation: bool = False, channels_requiring_update_after_run: Optional[list[str]] = None, - workflow: Optional[Workflow] = None, + parent: Optional[Workflow] = None, **kwargs, ): if len(output_labels) == 0: @@ -332,9 +332,9 @@ def __init__( self.node_function = node_function self.label = label if label is not None else node_function.__name__ - self.workflow = None - if workflow is not None: - workflow.add(self) + self.parent = None + if parent is not None: + parent.add(self) input_channels = self._build_input_channels(input_storage_priority) self.inputs = Inputs(*input_channels) @@ -573,7 +573,7 @@ def __init__( output_storage_priority: Optional[dict[str, int]] = None, run_on_updates=True, update_on_instantiation=True, - workflow: Optional[Workflow] = None, + parent: Optional[Workflow] = None, **kwargs, ): self.ensure_params_have_defaults(node_function) @@ -585,7 +585,7 @@ def __init__( output_storage_priority=output_storage_priority, run_on_updates=run_on_updates, update_on_instantiation=update_on_instantiation, - workflow=workflow, + parent=parent, **kwargs, ) @@ -619,7 +619,7 @@ def __init__( output_storage_priority: Optional[dict[str, int]] = None, run_on_updates=True, update_on_instantiation=True, - workflow: Optional[Workflow] = None, + parent: Optional[Workflow] = None, **kwargs, ): self.ensure_there_is_only_one_return_value(output_labels) @@ -631,7 +631,7 @@ def __init__( output_storage_priority=output_storage_priority, run_on_updates=run_on_updates, update_on_instantiation=update_on_instantiation, - workflow=workflow, + parent=parent, **kwargs, ) diff --git a/pyiron_contrib/workflow/node_library/package.py b/pyiron_contrib/workflow/node_library/package.py index 3c2633be1..62f94bcfc 100644 --- a/pyiron_contrib/workflow/node_library/package.py +++ b/pyiron_contrib/workflow/node_library/package.py @@ -21,9 +21,9 @@ class NodePackage(DotDict): but to update an existing node the `update` method must be used. """ - def __init__(self, workflow: Workflow, *node_classes: Node): + def __init__(self, parent: Workflow, *node_classes: Node): super().__init__() - self.__dict__["_workflow"] = workflow # Avoid the __setattr__ override + self.__dict__["_parent"] = parent # Avoid the __setattr__ override for node in node_classes: self[node.__name__] = node @@ -45,7 +45,7 @@ def __setitem__(self, key, value): def __getitem__(self, item): value = super().__getitem__(item) if issubclass(value, Node): - return partial(value, workflow=self._workflow) + return partial(value, parent=self._parent) else: return value diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index b9af9bf63..5281f50bd 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -27,7 +27,7 @@ def __init__(self, workflow: Workflow): def __getattribute__(self, key): value = super().__getattribute__(key) if value == Node: - return partial(Node, workflow=self._workflow) + return partial(Node, parent=self._workflow) return value def __call__(self, node: Node): @@ -70,7 +70,7 @@ class Workflow(HasToDict): >>> wf.add.Node(fnc, "y", label="n3") # Instantiating from add >>> wf.n4 = Node(fnc, "y", label="whatever_n4_gets_used") >>> # By attribute assignment - >>> Node(fnc, "x", label="n5", workflow=wf) + >>> Node(fnc, "x", label="n5", parent=wf) >>> # By instantiating the node with a workflow By default, the node naming scheme is strict, so if you try to add a node to a @@ -179,12 +179,12 @@ def add_node(self, node: Node, label: str = None) -> None: self.nodes[label] = node node.label = label - node.workflow = self + node.parent = self return node def _ensure_node_belongs_to_at_most_this_workflow(self, node: Node, label: str): if ( - node.workflow is self # This should guarantee the node is in self.nodes + node.parent is self # This should guarantee the node is in self.nodes and label != node.label ): assert self.nodes[node.label] is node # Should be unreachable by users @@ -193,10 +193,10 @@ def _ensure_node_belongs_to_at_most_this_workflow(self, node: Node, label: str): f"adding it to the workflow {self.label}." ) del self.nodes[node.label] - elif node.workflow is not None: + elif node.parent is not None: raise ValueError( f"The node ({node.label}) already belongs to the workflow " - f"{node.workflow.label}. Please remove it there before trying to " + f"{node.parent.label}. Please remove it there before trying to " f"add it to this workflow ({self.label})." ) @@ -237,7 +237,7 @@ def deactivate_strict_naming(self): def remove(self, node: Node | str): if isinstance(node, Node): - node.workflow = None + node.parent = None node.disconnect() del self.nodes[node.label] else: diff --git a/tests/unit/workflow/test_node_package.py b/tests/unit/workflow/test_node_package.py index f75a3f7ea..f90394eeb 100644 --- a/tests/unit/workflow/test_node_package.py +++ b/tests/unit/workflow/test_node_package.py @@ -26,8 +26,8 @@ def test_access(self): node = self.package.Dummy() self.assertIsInstance(node, dummy) self.assertIs( - node.workflow, - self.package._workflow, + node.parent, + self.package._parent, msg="Package workflow should get assigned to node instances" ) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index b1ee9be3a..ae0848b20 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -19,7 +19,7 @@ def test_node_addition(self): wf.add(Node(fnc, "x", label="foo")) wf.add.Node(fnc, "y", label="bar") wf.baz = Node(fnc, "y", label="whatever_baz_gets_used") - Node(fnc, "x", label="boa", workflow=wf) + Node(fnc, "x", label="boa", parent=wf) self.assertListEqual(list(wf.nodes.keys()), ["foo", "bar", "baz", "boa"]) wf.deactivate_strict_naming() @@ -31,7 +31,7 @@ def test_node_addition(self): "y", label="without_strict_you_can_override_by_assignment" ) - Node(fnc, "x", label="boa", workflow=wf) + Node(fnc, "x", label="boa", parent=wf) self.assertListEqual( list(wf.nodes.keys()), [ @@ -52,7 +52,7 @@ def test_node_addition(self): wf.baz = Node(fnc, "y", label="whatever_baz_gets_used") with self.assertRaises(AttributeError): - Node(fnc, "x", label="boa", workflow=wf) + Node(fnc, "x", label="boa", parent=wf) def test_node_packages(self): wf = Workflow("my_workflow") @@ -72,7 +72,7 @@ def test_node_packages(self): def test_double_workfloage_and_node_removal(self): wf1 = Workflow("one") wf1.add.Node(fnc, "y", label="node1") - node2 = Node(fnc, "y", label="node2", workflow=wf1, x=wf1.node1.outputs.y) + node2 = Node(fnc, "y", label="node2", parent=wf1, x=wf1.node1.outputs.y) self.assertTrue(node2.connected) wf2 = Workflow("two") @@ -81,7 +81,7 @@ def test_double_workfloage_and_node_removal(self): wf2.add(node2) wf1.remove(node2) wf2.add(node2) - self.assertEqual(node2.workflow, wf2) + self.assertEqual(node2.parent, wf2) self.assertFalse(node2.connected) def test_workflow_io(self): From a90326965f531384ebf2d58f7142c78a85a572b4 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 19 Jun 2023 15:16:48 -0700 Subject: [PATCH 127/756] Refactor: extract a base class for owning a graph --- pyiron_contrib/workflow/has_nodes.py | 184 +++++++++++++++++++++++++++ pyiron_contrib/workflow/workflow.py | 148 +-------------------- 2 files changed, 188 insertions(+), 144 deletions(-) create mode 100644 pyiron_contrib/workflow/has_nodes.py diff --git a/pyiron_contrib/workflow/has_nodes.py b/pyiron_contrib/workflow/has_nodes.py new file mode 100644 index 000000000..a2c9d414f --- /dev/null +++ b/pyiron_contrib/workflow/has_nodes.py @@ -0,0 +1,184 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from functools import partial +from typing import Optional, TYPE_CHECKING +from warnings import warn + +from pyiron_contrib.workflow.node import Node +from pyiron_contrib.workflow.node_library import atomistics, standard +from pyiron_contrib.workflow.node_library.package import NodePackage +from pyiron_contrib.workflow.util import DotDict + +if TYPE_CHECKING: + from pyiron_contrib.workflow.io import Inputs, Outputs + + +class HasNodes(ABC): + """ + A mixin class for classes which hold a graph of nodes. + """ + + def __init__(self, *args, strict_naming=True, **kwargs): + self.__dict__["nodes"]: DotDict = DotDict() + self.__dict__["add"]: NodeAdder = NodeAdder(self) + self.__dict__["_strict_naming"]: bool = strict_naming + # We directly assign using __dict__ because we override the setattr later + + @property + @abstractmethod + def inputs(self) -> Inputs: + pass + + @property + @abstractmethod + def outputs(self) -> Outputs: + pass + + def add_node(self, node: Node, label: Optional[str] = None) -> None: + """ + Assign a node to the parent. Optionally provide a new label for that node. + + Args: + node (pyiron_contrib.workflow.node.Node): The node to add. + label (Optional[str]): The label for this node. + + Raises: + TypeError: If the + """ + if not isinstance(node, Node): + raise TypeError( + f"Only new node instances may be added, but got {type(node)}." + ) + + label = self._ensure_label_is_unique(node.label if label is None else label) + self._ensure_node_has_no_other_parent(node, label) + + self.nodes[label] = node + node.label = label + node.parent = self + return node + + def _ensure_label_is_unique(self, label): + if label in self.__dir__(): + if isinstance(getattr(self, label), Node): + if self.strict_naming: + raise AttributeError( + f"{label} is already the label for a node. Please remove it " + f"before assigning another node to this label." + ) + else: + label = self._add_suffix_to_label(label) + else: + raise AttributeError( + f"{label} is an attribute or method of the {self.__class__} class, " + f"and cannot be used as a node label." + ) + return label + + def _add_suffix_to_label(self, label): + i = 0 + new_label = label + while new_label in self.nodes.keys(): + warn( + f"{label} is already a node; appending an index to the " + f"node label instead: {label}{i}" + ) + new_label = f"{label}{i}" + i += 1 + return new_label + + def _ensure_node_has_no_other_parent(self, node: Node, label: str): + if ( + node.parent is self # This should guarantee the node is in self.nodes + and label != node.label + ): + assert self.nodes[node.label] is node # Should be unreachable by users + warn( + f"Reassigning the node {node.label} to the label {label} when " + f"adding it to the parent {self.label}." + ) + del self.nodes[node.label] + elif node.parent is not None: + raise ValueError( + f"The node ({node.label}) already belongs to the parent " + f"{node.parent.label}. Please remove it there before trying to " + f"add it to this parent ({self.label})." + ) + + @property + def strict_naming(self) -> bool: + return self._strict_naming + + def activate_strict_naming(self): + self.__dict__["_strict_naming"] = True + + def deactivate_strict_naming(self): + self.__dict__["_strict_naming"] = False + + def remove(self, node: Node | str): + if isinstance(node, Node): + node.parent = None + node.disconnect() + del self.nodes[node.label] + else: + del self.nodes[node] + + def __setattr__(self, label: str, node: Node): + if not isinstance(node, Node): + raise TypeError( + "Only new node instances may be assigned as attributes. This is " + "syntacic sugar for adding new nodes to the .nodes collection" + ) + self.add_node(node, label=label) + + def __getattr__(self, key): + return self.nodes[key] + + def __getitem__(self, item): + return self.__getattr__(item) + + def __setitem__(self, key, value): + self.__setattr__(key, value) + + def __iter__(self): + return self.nodes.values().__iter__() + + def __len__(self): + return len(self.nodes) + + def __dir__(self): + return set(super().__dir__() + list(self.nodes.keys())) + + +class NodeAdder: + """ + This class provides a layer of misdirection so that `HasNodes` objects can set + themselves as the parent of owned nodes. + + It also provides access to packages of nodes and the ability to register new + packages. + """ + + def __init__(self, parent: HasNodes): + self._parent: HasNodes = parent + self.register_nodes("atomistics", *atomistics.nodes) + self.register_nodes("standard", *standard.nodes) + + Node = Node + + def __getattribute__(self, key): + value = super().__getattribute__(key) + if value == Node: + return partial(Node, parent=self._parent) + return value + + def __call__(self, node: Node): + return self._parent.add_node(node) + + def register_nodes(self, domain: str, *nodes: list[type[Node]]): + """ + Add a list of node classes to be accessible for creation under the provided + domain name. + """ + setattr(self, domain, NodePackage(self._parent, *nodes)) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 5281f50bd..b9d339c2b 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -1,39 +1,12 @@ from __future__ import annotations -from functools import partial -from warnings import warn - +from pyiron_contrib.workflow.has_nodes import HasNodes from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.node import Node, node, fast_node, single_value_node -from pyiron_contrib.workflow.node_library import atomistics, package, standard +from pyiron_contrib.workflow.node_library import atomistics, standard from pyiron_contrib.workflow.util import DotDict -class _NodeAdder: - """ - This class exists to help with the misdirection required for the syntactic sugar - that lets us add nodes to the workflow. - - TODO: Give access to pre-built fixed nodes under various domain names - """ - - def __init__(self, workflow: Workflow): - self._workflow = workflow - self.atomistics = package.NodePackage(self._workflow, *atomistics.nodes) - self.standard = package.NodePackage(self._workflow, *standard.nodes) - - Node = Node - - def __getattribute__(self, key): - value = super().__getattribute__(key) - if value == Node: - return partial(Node, parent=self._workflow) - return value - - def __call__(self, node: Node): - return self._workflow.add_node(node) - - class _NodeDecoratorAccess: """An intermediate container to store node-creating decorators as class methods.""" @@ -42,7 +15,7 @@ class _NodeDecoratorAccess: single_value_node = single_value_node -class Workflow(HasToDict): +class Workflow(HasToDict, HasNodes): """ Workflows are an abstraction for holding a collection of related nodes. @@ -149,123 +122,13 @@ class Workflow(HasToDict): wrap_as = _NodeDecoratorAccess def __init__(self, label: str, *nodes: Node, strict_naming=True): + super().__init__(strict_naming=strict_naming) self.__dict__["label"] = label - self.__dict__["nodes"] = DotDict() - self.__dict__["add"] = _NodeAdder(self) - self.__dict__["strict_naming"] = strict_naming # We directly assign using __dict__ because we override the setattr later for node in nodes: self.add_node(node) - def add_node(self, node: Node, label: str = None) -> None: - """ - Assign a node to the workflow. Optionally provide a new label for that node. - - Args: - node (pyiron_contrib.workflow.node.Node): The node to add. - label (Optional[str]): The label for this node. - - Raises: - - """ - if not isinstance(node, Node): - raise TypeError( - f"Only new node instances may be added, but got {type(node)}." - ) - - label = self._ensure_label_is_unique(node.label if label is None else label) - self._ensure_node_belongs_to_at_most_this_workflow(node, label) - - self.nodes[label] = node - node.label = label - node.parent = self - return node - - def _ensure_node_belongs_to_at_most_this_workflow(self, node: Node, label: str): - if ( - node.parent is self # This should guarantee the node is in self.nodes - and label != node.label - ): - assert self.nodes[node.label] is node # Should be unreachable by users - warn( - f"Reassigning the node {node.label} to the label {label} when " - f"adding it to the workflow {self.label}." - ) - del self.nodes[node.label] - elif node.parent is not None: - raise ValueError( - f"The node ({node.label}) already belongs to the workflow " - f"{node.parent.label}. Please remove it there before trying to " - f"add it to this workflow ({self.label})." - ) - - def _ensure_label_is_unique(self, label): - if label in self.__dir__(): - if isinstance(getattr(self, label), Node): - if self.strict_naming: - raise AttributeError( - f"{label} is already the label for a node. Please remove it " - f"before assigning another node to this label." - ) - else: - label = self._add_suffix_to_label(label) - else: - raise AttributeError( - f"{label} is an attribute or method of the {self.__class__} class, " - f"and cannot be used as a node label." - ) - return label - - def _add_suffix_to_label(self, label): - i = 0 - new_label = label - while new_label in self.nodes.keys(): - warn( - f"{label} is already a node; appending an index to the " - f"node label instead: {label}{i}" - ) - new_label = f"{label}{i}" - i += 1 - return new_label - - def activate_strict_naming(self): - self.__dict__["strict_naming"] = True - - def deactivate_strict_naming(self): - self.__dict__["strict_naming"] = False - - def remove(self, node: Node | str): - if isinstance(node, Node): - node.parent = None - node.disconnect() - del self.nodes[node.label] - else: - del self.nodes[node] - - def __setattr__(self, label: str, node: Node): - if not isinstance(node, Node): - raise TypeError( - "Only new node instances may be assigned as attributes. This is " - "syntacic sugar for adding new nodes to the .nodes collection" - ) - self.add_node(node, label=label) - - def __getattr__(self, key): - return self.nodes[key] - - def __getitem__(self, item): - return self.__getattr__(item) - - def __setitem__(self, key, value): - self.__setattr__(key, value) - - def __iter__(self): - return self.nodes.values().__iter__() - - def __len__(self): - return len(self.nodes) - @property def inputs(self): return DotDict( @@ -316,6 +179,3 @@ def update(self): def run(self): # Maybe we need this if workflows can be used as nodes? raise NotImplementedError - - def __dir__(self): - return set(super().__dir__() + list(self.nodes.keys())) From 4c826fa44d17efffb233474d9cd3699b17e43139 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 19 Jun 2023 15:29:56 -0700 Subject: [PATCH 128/756] Relax attribute assignment So we don't need to mess with __dict__ all the time --- pyiron_contrib/workflow/has_nodes.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pyiron_contrib/workflow/has_nodes.py b/pyiron_contrib/workflow/has_nodes.py index a2c9d414f..6d434f94f 100644 --- a/pyiron_contrib/workflow/has_nodes.py +++ b/pyiron_contrib/workflow/has_nodes.py @@ -17,13 +17,15 @@ class HasNodes(ABC): """ A mixin class for classes which hold a graph of nodes. + + Attribute assignment is overriden such that assignment of a `Node` instance adds + it directly to the collection of nodes. """ def __init__(self, *args, strict_naming=True, **kwargs): - self.__dict__["nodes"]: DotDict = DotDict() - self.__dict__["add"]: NodeAdder = NodeAdder(self) - self.__dict__["_strict_naming"]: bool = strict_naming - # We directly assign using __dict__ because we override the setattr later + self.nodes: DotDict = DotDict() + self.add: NodeAdder = NodeAdder(self) + self._strict_naming: bool = strict_naming @property @abstractmethod @@ -111,10 +113,10 @@ def strict_naming(self) -> bool: return self._strict_naming def activate_strict_naming(self): - self.__dict__["_strict_naming"] = True + self._strict_naming = True def deactivate_strict_naming(self): - self.__dict__["_strict_naming"] = False + self._strict_naming = False def remove(self, node: Node | str): if isinstance(node, Node): @@ -125,12 +127,10 @@ def remove(self, node: Node | str): del self.nodes[node] def __setattr__(self, label: str, node: Node): - if not isinstance(node, Node): - raise TypeError( - "Only new node instances may be assigned as attributes. This is " - "syntacic sugar for adding new nodes to the .nodes collection" - ) - self.add_node(node, label=label) + if isinstance(node, Node): + self.add_node(node, label=label) + else: + super().__setattr__(label, node) def __getattr__(self, key): return self.nodes[key] From ea18344e977d383a2fbc4575e171fd407be5237f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 19 Jun 2023 15:31:40 -0700 Subject: [PATCH 129/756] Remove IO requirement That is better suited to an is_graph_element mixin --- pyiron_contrib/workflow/has_nodes.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/pyiron_contrib/workflow/has_nodes.py b/pyiron_contrib/workflow/has_nodes.py index 6d434f94f..8e7290565 100644 --- a/pyiron_contrib/workflow/has_nodes.py +++ b/pyiron_contrib/workflow/has_nodes.py @@ -1,6 +1,6 @@ from __future__ import annotations -from abc import ABC, abstractmethod +from abc import ABC from functools import partial from typing import Optional, TYPE_CHECKING from warnings import warn @@ -10,9 +10,6 @@ from pyiron_contrib.workflow.node_library.package import NodePackage from pyiron_contrib.workflow.util import DotDict -if TYPE_CHECKING: - from pyiron_contrib.workflow.io import Inputs, Outputs - class HasNodes(ABC): """ @@ -27,16 +24,6 @@ def __init__(self, *args, strict_naming=True, **kwargs): self.add: NodeAdder = NodeAdder(self) self._strict_naming: bool = strict_naming - @property - @abstractmethod - def inputs(self) -> Inputs: - pass - - @property - @abstractmethod - def outputs(self) -> Outputs: - pass - def add_node(self, node: Node, label: Optional[str] = None) -> None: """ Assign a node to the parent. Optionally provide a new label for that node. From caa0b2b173d28cae41109d1f7b3b5e401a594527 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 19 Jun 2023 15:39:17 -0700 Subject: [PATCH 130/756] Update docstrings --- pyiron_contrib/workflow/has_nodes.py | 3 +++ pyiron_contrib/workflow/workflow.py | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/has_nodes.py b/pyiron_contrib/workflow/has_nodes.py index 8e7290565..7c927417a 100644 --- a/pyiron_contrib/workflow/has_nodes.py +++ b/pyiron_contrib/workflow/has_nodes.py @@ -167,5 +167,8 @@ def register_nodes(self, domain: str, *nodes: list[type[Node]]): """ Add a list of node classes to be accessible for creation under the provided domain name. + + TODO: multiple dispatch so we can handle registering something other than a + list, e.g. modules or even urls. """ setattr(self, domain, NodePackage(self._parent, *nodes)) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index b9d339c2b..34a77aab8 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -104,8 +104,6 @@ class Workflow(HasToDict, HasNodes): ... y=wf.calc.outputs.temperature ... ) - TODO: Registration of new node packages - TODO: Workflows can be serialized. TODO: Once you're satisfied with how a workflow is structured, you can export it From 28d51052187b21185c4c28deab748e27c52d90ab Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 19 Jun 2023 15:40:35 -0700 Subject: [PATCH 131/756] Don't use __dict__ anymore --- pyiron_contrib/workflow/workflow.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 34a77aab8..55ceea55b 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -121,8 +121,7 @@ class Workflow(HasToDict, HasNodes): def __init__(self, label: str, *nodes: Node, strict_naming=True): super().__init__(strict_naming=strict_naming) - self.__dict__["label"] = label - # We directly assign using __dict__ because we override the setattr later + self.label = label for node in nodes: self.add_node(node) From 3fa449d31457568128e59ea1c0c24b5c9980682f Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 19 Jun 2023 23:02:21 +0000 Subject: [PATCH 132/756] Format black --- pyiron_contrib/workflow/has_nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/has_nodes.py b/pyiron_contrib/workflow/has_nodes.py index 7c927417a..895274b46 100644 --- a/pyiron_contrib/workflow/has_nodes.py +++ b/pyiron_contrib/workflow/has_nodes.py @@ -168,7 +168,7 @@ def register_nodes(self, domain: str, *nodes: list[type[Node]]): Add a list of node classes to be accessible for creation under the provided domain name. - TODO: multiple dispatch so we can handle registering something other than a + TODO: multiple dispatch so we can handle registering something other than a list, e.g. modules or even urls. """ setattr(self, domain, NodePackage(self._parent, *nodes)) From a9cde70eda0d1ae492c39cf427d42e11a21686cb Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 20 Jun 2023 10:31:47 +0000 Subject: [PATCH 133/756] Format black --- pyiron_contrib/tinybase/__init__.py | 1 - pyiron_contrib/tinybase/ase.py | 59 +++++++------ pyiron_contrib/tinybase/container.py | 19 ++-- pyiron_contrib/tinybase/database.py | 125 +++++++++++++++------------ pyiron_contrib/tinybase/executor.py | 60 +++++++------ pyiron_contrib/tinybase/job.py | 50 ++++++----- pyiron_contrib/tinybase/lammps.py | 82 ++++++++---------- pyiron_contrib/tinybase/murn.py | 28 +++--- pyiron_contrib/tinybase/project.py | 20 +++-- pyiron_contrib/tinybase/shell.py | 35 ++++---- pyiron_contrib/tinybase/storage.py | 8 +- pyiron_contrib/tinybase/task.py | 53 ++++++++---- 12 files changed, 304 insertions(+), 236 deletions(-) diff --git a/pyiron_contrib/tinybase/__init__.py b/pyiron_contrib/tinybase/__init__.py index 36e61c641..3dc1f76bc 100644 --- a/pyiron_contrib/tinybase/__init__.py +++ b/pyiron_contrib/tinybase/__init__.py @@ -1,2 +1 @@ - __version__ = "0.1.0" diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index 7c970e636..f4e33ef68 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -1,11 +1,11 @@ from pyiron_contrib.tinybase.container import ( - AbstractInput, - StorageAttribute, - StructureInput, - MDInput, - MinimizeInput, - EnergyPotOutput, - MDOutput + AbstractInput, + StorageAttribute, + StructureInput, + MDInput, + MinimizeInput, + EnergyPotOutput, + MDOutput, ) from pyiron_contrib.tinybase.task import AbstractTask, ReturnStatus @@ -23,12 +23,12 @@ class AseInput(AbstractInput): calculator = StorageAttribute() + class AseStaticInput(AseInput, StructureInput): pass class AseStaticTask(AbstractTask): - def _get_input(self): return AseStaticInput() @@ -44,8 +44,8 @@ def _execute(self, output): class AseMDInput(AseInput, MDInput): pass -class AseMDTask(AbstractTask): +class AseMDTask(AbstractTask): def _get_input(self): return AseMDInput() @@ -56,14 +56,16 @@ def _execute(self, output): structure = self.input.structure.copy() structure.calc = self.input.calculator - MaxwellBoltzmannDistribution(structure, temperature_K=self.input.temperature * 2) + MaxwellBoltzmannDistribution( + structure, temperature_K=self.input.temperature * 2 + ) dyn = Langevin( - structure, - timestep=self.input.timestep * units.fs, - temperature_K=self.input.temperature, - friction=1e-3, - append_trajectory=True + structure, + timestep=self.input.timestep * units.fs, + temperature_K=self.input.temperature, + friction=1e-3, + append_trajectory=True, ) def parse(): @@ -76,36 +78,33 @@ def parse(): dyn.attach(parse, interval=self.input.steps // self.input.output_steps) dyn.run(self.input.steps) -class AseMinimizeInput(AseInput, StructureInput, MinimizeInput): - algo = StorageAttribute().type(str).default('LBFGS') +class AseMinimizeInput(AseInput, StructureInput, MinimizeInput): + algo = StorageAttribute().type(str).default("LBFGS") minimizer_kwargs = StorageAttribute().type(dict).constructor(dict) def lbfgs(self, damping=None, alpha=None): - self.algo = 'LBFGS' + self.algo = "LBFGS" if damping is not None: - self.minimizer_kwargs['damping'] = damping + self.minimizer_kwargs["damping"] = damping if alpha is not None: - self.minimizer_kwargs['alpha'] = alpha + self.minimizer_kwargs["alpha"] = alpha def fire(self): - self.algo = 'FIRE' + self.algo = "FIRE" self.minimizer_kwargs = {} def gpmin(self): - self.algo = 'GPMIN' + self.algo = "GPMIN" self.minimizer_kwargs = {} def get_ase_optimizer(self, structure): - return { - 'LBFGS': LBFGS, - 'FIRE': FIRE, - 'GPMIN': GPMin - }.get(self.algo)(structure, **self.minimizer_kwargs) + return {"LBFGS": LBFGS, "FIRE": FIRE, "GPMIN": GPMin}.get(self.algo)( + structure, **self.minimizer_kwargs + ) class AseMinimizeTask(AbstractTask): - def _get_input(self): return AseMinimizeInput() @@ -132,6 +131,6 @@ def parse(): force_tolerance = self.input.ionic_force_tolerance if max_force > force_tolerance: return ReturnStatus( - "not_converged", - f"force in last step ({max_force}) is larger than tolerance ({force_tolerance})!" + "not_converged", + f"force in last step ({max_force}) is larger than tolerance ({force_tolerance})!", ) diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index 1215710b0..ed62a9d2c 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -13,6 +13,7 @@ import numpy as np import matplotlib.pyplot as plt + class StorageAttribute: """ Create an attribute that is synced to a storage attribute. @@ -113,9 +114,9 @@ def doc(self, text): self.__doc__ = text return self -class AbstractContainer(HasStorage, HasHDFAdapaterMixin, abc.ABC): - def take(self, other: 'AbstractContainer'): +class AbstractContainer(HasStorage, HasHDFAdapaterMixin, abc.ABC): + def take(self, other: "AbstractContainer"): # TODO: think hard about variance of types if not isinstance(self, type(other)): raise TypeError("Must pass a superclass to transfer from!") @@ -127,7 +128,7 @@ def take(self, other: 'AbstractContainer'): if a is not None: setattr(self, name, a) - def put(self, other: 'AbstractContainer'): + def put(self, other: "AbstractContainer"): other.take(self) @@ -135,15 +136,18 @@ class AbstractInput(AbstractContainer, abc.ABC): def check_ready(self): return True + class StructureInput(AbstractInput): structure = StorageAttribute().type(Atoms) + class MDInput(AbstractInput): steps = StorageAttribute().type(int) timestep = StorageAttribute().type(float) temperature = StorageAttribute().type(float) output_steps = StorageAttribute().type(int) + class MinimizeInput(AbstractInput): ionic_force_tolerance = StorageAttribute().type(float) max_steps = StorageAttribute().type(int) @@ -153,25 +157,28 @@ class MinimizeInput(AbstractInput): class AbstractOutput(AbstractContainer, abc.ABC): pass + class EnergyPotOutput(AbstractOutput): energy_pot = StorageAttribute().type(float) + class EnergyKinOutput(AbstractOutput): energy_kin = StorageAttribute().type(float) + class ForceOutput(AbstractOutput): forces = StorageAttribute().type(np.ndarray) -class MDOutput(HasStructure, EnergyPotOutput): +class MDOutput(HasStructure, EnergyPotOutput): pot_energies = StorageAttribute().type(list).constructor(list) kin_energies = StorageAttribute().type(list).constructor(list) forces = StorageAttribute().type(list).constructor(list) structures = StorageAttribute().type(list).constructor(list) def plot_energies(self): - plt.plot(self.pot_energies - np.min(self.pot_energies), label='pot') - plt.plot(self.kin_energies, label='kin') + plt.plot(self.pot_energies - np.min(self.pot_energies), label="pot") + plt.plot(self.kin_energies, label="kin") plt.legend() def _number_of_structures(self): diff --git a/pyiron_contrib/tinybase/database.py b/pyiron_contrib/tinybase/database.py index 0a6fc380c..a15df67a2 100644 --- a/pyiron_contrib/tinybase/database.py +++ b/pyiron_contrib/tinybase/database.py @@ -3,29 +3,21 @@ import os.path from typing import List from typing import Optional -from sqlalchemy import ( - ForeignKey, - String, - Integer, - Column, - create_engine -) -from sqlalchemy.exc import ( - MultipleResultsFound, - NoResultFound -) +from sqlalchemy import ForeignKey, String, Integer, Column, create_engine +from sqlalchemy.exc import MultipleResultsFound, NoResultFound from sqlalchemy.orm import declarative_base, relationship from sqlalchemy.orm import Session from sqlalchemy.pool import StaticPool import pandas as pd -DatabaseEntry = namedtuple("DatabaseEntry", - ["name", "username", "project", "status", "jobtype"] +DatabaseEntry = namedtuple( + "DatabaseEntry", ["name", "username", "project", "status", "jobtype"] ) Base = declarative_base() + class Project(Base): __tablename__ = "project_table" @@ -35,6 +27,7 @@ class Project(Base): # jobs = relationship("Job", back_populates="project") # jobs = relationship("Job", backref="project") + # FIXME: Can be many-to-many later class JobStatus(Base): __tablename__ = "job_status_table" @@ -42,6 +35,7 @@ class JobStatus(Base): id = Column(Integer, primary_key=True) status = Column(String(250)) + # FIXME: Can be many-to-many later class JobType(Base): __tablename__ = "job_type_table" @@ -49,6 +43,7 @@ class JobType(Base): id = Column(Integer, primary_key=True) type = Column(String(250)) + class Job(Base): __tablename__ = "job_table" @@ -61,6 +56,7 @@ class Job(Base): status_id = Column(Integer, ForeignKey("job_status_table.id")) # project = relationship("Project", back_populates="jobs") + # TODO: this will be pyiron_base.IsDatabase class GenericDatabase(abc.ABC): """ @@ -95,6 +91,7 @@ def remove_item(self, job_id: int) -> DatabaseEntry: def job_table(self) -> pd.DataFrame: pass + class TinyDB(GenericDatabase): """ Minimal database implementation and "reference". Exists mostly to allow easy testing without messing with @@ -109,8 +106,10 @@ def __init__(self, path, echo=False): # this allows to access the same DB from the different threads in one process # it's necessary for an in memory database, otherwise all threads see different dbs kwargs["poolclass"] = StaticPool - kwargs["connect_args"] = {'check_same_thread':False} - self._engine = create_engine(f"sqlite:///{self._path}", echo=self._echo, **kwargs) + kwargs["connect_args"] = {"check_same_thread": False} + self._engine = create_engine( + f"sqlite:///{self._path}", echo=self._echo, **kwargs + ) Base.metadata.create_all(self.engine) Base.metadata.reflect(self.engine, extend_existing=True) @@ -120,11 +119,19 @@ def engine(self): def add_item(self, entry: DatabaseEntry) -> int: with Session(self.engine) as session: - project = session.query(Project).where(Project.location==entry.project).one_or_none() + project = ( + session.query(Project) + .where(Project.location == entry.project) + .one_or_none() + ) if project is None: project = Project(location=entry.project) session.add(project) - jobtype = session.query(JobType).where(JobType.type==entry.jobtype).one_or_none() + jobtype = ( + session.query(JobType) + .where(JobType.type == entry.jobtype) + .one_or_none() + ) if jobtype is None: jobtype = JobType(type=entry.jobtype) session.add(jobtype) @@ -132,11 +139,11 @@ def add_item(self, entry: DatabaseEntry) -> int: session.add(status) session.flush() job = Job( - name=entry.name, - username=entry.username, - project_id=project.id, - status_id=status.id, - jobtype_id=jobtype.id + name=entry.name, + username=entry.username, + project_id=project.id, + status_id=status.id, + jobtype_id=jobtype.id, ) session.add(job) session.flush() @@ -147,10 +154,12 @@ def add_item(self, entry: DatabaseEntry) -> int: def update_status(self, job_id, status): with Session(self.engine) as session: try: - s = session.query(JobStatus).select_from(Job).where( - Job.id == job_id, - JobStatus.id == Job.status_id - ).one() + s = ( + session.query(JobStatus) + .select_from(Job) + .where(Job.id == job_id, JobStatus.id == Job.status_id) + .one() + ) s.status = status session.commit() except Exception as e: @@ -158,44 +167,52 @@ def update_status(self, job_id, status): def _row_to_entry(self, job_data): return DatabaseEntry( - name=job_data.name, - project=job_data.location, - username=job_data.username, - status=job_data.status, - jobtype=job_data.type + name=job_data.name, + project=job_data.location, + username=job_data.username, + status=job_data.status, + jobtype=job_data.type, ) def get_item(self, job_id: int) -> DatabaseEntry: with Session(self.engine) as session: - job_data = session.query( + job_data = ( + session.query( Job.__table__, Project.location, JobStatus.status, JobType.type - ).select_from( - Job - ).where( - Job.id == job_id - ).join( - Project, Job.project_id==Project.id - ).join( - JobStatus, Job.status_id==JobStatus.id - ).join( - JobType, Job.jobtype_id==JobType.id - ).one() + ) + .select_from(Job) + .where(Job.id == job_id) + .join(Project, Job.project_id == Project.id) + .join(JobStatus, Job.status_id == JobStatus.id) + .join(JobType, Job.jobtype_id == JobType.id) + .one() + ) return self._row_to_entry(job_data) def get_item_id(self, job_name: str, project_id: int) -> Optional[int]: with Session(self.engine) as session: try: - return session.query(Job.id).where( + return ( + session.query(Job.id) + .where( Job.name == job_name, Job.project_id == project_id, - ).one().id + ) + .one() + .id + ) except (MultipleResultsFound, NoResultFound): return None def get_project_id(self, location: str) -> Optional[int]: with Session(self.engine) as session: try: - return session.query(Project.id).where(Project.location == location).one().id + return ( + session.query(Project.id) + .where(Project.location == location) + .one() + .id + ) # FIXME: MultipleResultsFound should be reraised because it indicates a broken database except (MultipleResultsFound, NoResultFound): return None @@ -211,15 +228,13 @@ def remove_item(self, job_id: int) -> DatabaseEntry: def job_table(self) -> pd.DataFrame: with Session(self.engine) as session: - query = session.query( + query = ( + session.query( Job.__table__, Project.location, JobStatus.status, JobType.type - ).select_from( - Job - ).join( - Project, Job.project_id==Project.id - ).join( - JobStatus, Job.status_id==JobStatus.id - ).join( - JobType, Job.jobtype_id==JobType.id + ) + .select_from(Job) + .join(Project, Job.project_id == Project.id) + .join(JobStatus, Job.status_id == JobStatus.id) + .join(JobType, Job.jobtype_id == JobType.id) ) return pd.DataFrame([r._asdict() for r in query.all()]) diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index 163b44a2b..8ebba7349 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -8,20 +8,20 @@ from pyiron_contrib.tinybase.task import AbstractTask, TaskGenerator -class RunMachine: +class RunMachine: class Code(enum.Enum): - INIT = 'init' - READY = 'ready' - RUNNING = 'running' - COLLECT = 'collect' - FINISHED = 'finished' + INIT = "init" + READY = "ready" + RUNNING = "running" + COLLECT = "collect" + FINISHED = "finished" def __init__(self, initial_state): self._state = RunMachine.Code(initial_state) self._callbacks = {} self._observers = defaultdict(list) - self._data = {} # state variables associated with each state + self._data = {} # state variables associated with each state @property def state(self): @@ -53,7 +53,6 @@ def step(self, state: Union[str, Code, None] = None, **kwargs): class ExecutionContext: - def __init__(self, tasks): self._tasks = tasks self._run_machine = RunMachine("init") @@ -104,9 +103,10 @@ def _run_running(self): self._run_machine.step("collect", status=status, output=output) def _run_collect(self): - self._run_machine.step("finished", - status=self.status, - output=self.output, + self._run_machine.step( + "finished", + status=self.status, + output=self.output, ) def _run_finished(self): @@ -131,23 +131,25 @@ def status(self): def output(self): return self._run_machine._data["output"] -class Executor: +class Executor: def submit(self, tasks: List[AbstractTask]) -> ExecutionContext: return ExecutionContext(tasks) + from concurrent.futures import ( - ThreadPoolExecutor, - ProcessPoolExecutor, - Executor as FExecutor + ThreadPoolExecutor, + ProcessPoolExecutor, + Executor as FExecutor, ) from threading import Lock + def run_task(task): return task.execute() -class FuturesExecutionContext(ExecutionContext): +class FuturesExecutionContext(ExecutionContext): def __init__(self, pool, tasks): super().__init__(tasks=tasks) self._pool = pool @@ -179,10 +181,7 @@ def _process_future(self, future): def _prepare_subcontext(self, task, sub_tasks): sub = self._subcontexts[task] = type(self)(self._pool, sub_tasks) - sub._run_machine.observe( - "finished", - partial(self._process_generator, task) - ) + sub._run_machine.observe("finished", partial(self._process_generator, task)) sub.run() return sub @@ -203,11 +202,18 @@ def _process_generator(self, task, _data): def _check_finish(self, log=False): with self._lock: if self._done == len(self.tasks): - status = [self._status[n] for n in sorted(self.tasks, key=lambda n: self._index[n])] - output = [self._output[n] for n in sorted(self.tasks, key=lambda n: self._index[n])] - self._run_machine.step("collect", - status=status, - output=output, + status = [ + self._status[n] + for n in sorted(self.tasks, key=lambda n: self._index[n]) + ] + output = [ + self._output[n] + for n in sorted(self.tasks, key=lambda n: self._index[n]) + ] + self._run_machine.step( + "collect", + status=status, + output=output, ) def _run_running(self): @@ -224,6 +230,7 @@ def _run_running(self): else: logging.info("Some tasks are still executing!") + class BackgroundExecutor(Executor): def __init__(self, max_threads): self._max_threads = max_threads @@ -234,6 +241,7 @@ def submit(self, tasks): self._pool = ThreadPoolExecutor(max_workers=self._max_threads) return FuturesExecutionContext(self._pool, tasks) + class ProcessExecutor(Executor): def __init__(self, max_processes): self._max_processes = max_processes @@ -244,8 +252,10 @@ def submit(self, tasks): self._pool = ProcessPoolExecutor(max_workers=self._max_processes) return FuturesExecutionContext(self._pool, tasks) + from dask.distributed import Client, LocalCluster + class DaskExecutor(Executor): def __init__(self, client): self._client = client diff --git a/pyiron_contrib/tinybase/job.py b/pyiron_contrib/tinybase/job.py index f0220fb0f..b4ccf8d43 100644 --- a/pyiron_contrib/tinybase/job.py +++ b/pyiron_contrib/tinybase/job.py @@ -4,19 +4,17 @@ from pyiron_contrib.tinybase.task import AbstractTask from pyiron_contrib.tinybase.storage import ( - Storable, - GenericStorage, - pickle_load, - pickle_dump + Storable, + GenericStorage, + pickle_load, + pickle_dump, ) from pyiron_contrib.tinybase.executor import ( - Executor, - BackgroundExecutor, - ProcessExecutor -) -from pyiron_contrib.tinybase.database import ( - DatabaseEntry + Executor, + BackgroundExecutor, + ProcessExecutor, ) +from pyiron_contrib.tinybase.database import DatabaseEntry from pyiron_contrib.tinybase.project import ProjectInterface, ProjectAdapter from pyiron_base.state import state @@ -46,9 +44,9 @@ class TinyJob(Storable, abc.ABC): """ _executors = { - 'foreground': Executor(), - 'background': BackgroundExecutor(max_threads=4), - 'process': ProcessExecutor(max_processes=4) + "foreground": Executor(), + "background": BackgroundExecutor(max_threads=4), + "process": ProcessExecutor(max_processes=4), } def __init__(self, project: ProjectInterface, job_name: str): @@ -78,7 +76,9 @@ def __init__(self, project: ProjectInterface, job_name: str): try: self.load() except Exception as e: - raise RuntimeError(f"Failed to reload run job from storage: {e}") from None + raise RuntimeError( + f"Failed to reload run job from storage: {e}" + ) from None @property def name(self): @@ -137,14 +137,16 @@ def _set_output(self, data): def _setup_executor_callbacks(self): self._executor._run_machine.observe("ready", lambda _: self.store(self.storage)) self._executor._run_machine.observe("finished", self._set_output) - self._executor._run_machine.observe("finished", lambda _: self.store(self.storage)) + self._executor._run_machine.observe( + "finished", lambda _: self.store(self.storage) + ) self._executor._run_machine.observe("ready", self._add_to_database) self._executor._run_machine.observe("running", self._update_status("running")) self._executor._run_machine.observe("collect", self._update_status("collect")) self._executor._run_machine.observe("finished", self._update_status("finished")) - def run(self, how='foreground') -> Optional[Executor]: + def run(self, how="foreground") -> Optional[Executor]: """ Start execution of the job. @@ -156,7 +158,10 @@ def run(self, how='foreground') -> Optional[Executor]: Returns: :class:`.Executor`: the executor that is running the task or nothing. """ - if self._id is None or self.project.database.get_item(self.id).status == "ready": + if ( + self._id is None + or self.project.database.get_item(self.id).status == "ready" + ): exe = self._executor = self._executors[how].submit(tasks=[self.task]) self._setup_executor_callbacks() exe.run() @@ -179,11 +184,11 @@ def remove(self): def _add_to_database(self, _data): if self._id is None: entry = DatabaseEntry( - name=self.name, - project=self.project.path, - username=state.settings.login_user, - status="ready", - jobtype=self.jobtype, + name=self.name, + project=self.project.path, + username=state.settings.login_user, + status="ready", + jobtype=self.jobtype, ) self._id = self.project.database.add_item(entry) return self.id @@ -241,6 +246,7 @@ class GenericTinyJob(TinyJob): >>> isinstance(job.input, type(MyTask.input)) True """ + def __init__(self, project, job_name): super().__init__(project=project, job_name=job_name) self._task_class = None diff --git a/pyiron_contrib/tinybase/lammps.py b/pyiron_contrib/tinybase/lammps.py index 3ed8e73bb..c61b60644 100644 --- a/pyiron_contrib/tinybase/lammps.py +++ b/pyiron_contrib/tinybase/lammps.py @@ -3,35 +3,32 @@ from ase import Atoms from pymatgen.io.lammps.outputs import ( - parse_lammps_dumps, - parse_lammps_log, + parse_lammps_dumps, + parse_lammps_log, ) from pyiron_atomistics.lammps.potential import ( - LammpsPotential, - LammpsPotentialFile, - list_potentials + LammpsPotential, + LammpsPotentialFile, + list_potentials, ) from pyiron_atomistics.lammps.control import LammpsControl from pyiron_contrib.tinybase.container import ( - AbstractInput, - AbstractOutput, - StorageAttribute, - StructureInput, - EnergyPotOutput, - EnergyKinOutput, - ForceOutput, - MDOutput, + AbstractInput, + AbstractOutput, + StorageAttribute, + StructureInput, + EnergyPotOutput, + EnergyKinOutput, + ForceOutput, + MDOutput, ) from pyiron_contrib.tinybase.task import ( - AbstractTask, - ReturnStatus, -) -from pyiron_contrib.tinybase.shell import ( - ShellTask, - ExecutablePathResolver + AbstractTask, + ReturnStatus, ) +from pyiron_contrib.tinybase.shell import ShellTask, ExecutablePathResolver class LammpsInputInput(AbstractInput): @@ -40,16 +37,20 @@ class LammpsInputInput(AbstractInput): calc_type = StorageAttribute() def calc_static(self): - self.calc_type = 'static' + self.calc_type = "static" def check_ready(self): - return self.working_directory is not None \ - and self.calc_type == "static" \ - and super().check_ready() + return ( + self.working_directory is not None + and self.calc_type == "static" + and super().check_ready() + ) + class LammpsInputOutput(AbstractOutput): working_directory = StorageAttribute().type(str) + class LammpsInputTask(AbstractTask): """ Write a set of input files for lammps calculations. @@ -70,8 +71,7 @@ def _get_output(self): def _execute(self, output): with open( - os.path.join(self.input.working_directory, "structure.inp"), - "w" + os.path.join(self.input.working_directory, "structure.inp"), "w" ) as f: self.input.structure.write(f, format="lammps-data") @@ -85,9 +85,7 @@ def _execute(self, output): control = LammpsControl() assert self.input.calc_type == "static", "Cannot happen" control.calc_static() - control.write_file( - file_name="control.inp", cwd=self.input.working_directory - ) + control.write_file(file_name="control.inp", cwd=self.input.working_directory) output.working_directory = self.input.working_directory @@ -96,12 +94,13 @@ class LammpsStaticParserInput(AbstractInput): working_directory = StorageAttribute().type(str) def check_ready(self): - return self.working_directory is not None \ - and super().check_ready() + return self.working_directory is not None and super().check_ready() + class LammpsStaticOutput(EnergyPotOutput, EnergyKinOutput, ForceOutput): pass + class LammpsStaticParserTask(AbstractTask): """ Parse a static lammps calculation. @@ -117,14 +116,14 @@ def _get_output(self): def _execute(self, output): log = parse_lammps_log( - os.path.join(self.input.working_directory, "log.lammps") + os.path.join(self.input.working_directory, "log.lammps") )[-1] output.energy_pot = energy_pot = log["PotEng"].iloc[-1] output.energy_kin = log["TotEng"].iloc[-1] - energy_pot - dump = list(parse_lammps_dumps( - os.path.join(self.input.working_directory, "dump.out") - ))[-1] - output.forces = dump.data[['fx','fy','fz']].to_numpy() + dump = list( + parse_lammps_dumps(os.path.join(self.input.working_directory, "dump.out")) + )[-1] + output.forces = dump.data[["fx", "fy", "fz"]].to_numpy() class LammpsInput(StructureInput): @@ -144,6 +143,7 @@ def list_potentials(self): def check_ready(self): return self.potential is not None and super().check_ready() + class LammpsStaticTask(AbstractTask): """ A static calculation with lammps. @@ -157,9 +157,7 @@ def _get_output(self): def _execute(self, output): with TemporaryDirectory() as dir: - inp = LammpsInputTask( - capture_exceptions=self._capture_exceptions - ) + inp = LammpsInputTask(capture_exceptions=self._capture_exceptions) inp.input.working_directory = dir inp.input.structure = self.input.structure inp.input.potential = self.input.potential @@ -168,18 +166,14 @@ def _execute(self, output): if not ret.is_done(): return ReturnStatus.aborted(f"Writing input failed: {ret.msg}") - lmp = ShellTask( - capture_exceptions=self._capture_exceptions - ) + lmp = ShellTask(capture_exceptions=self._capture_exceptions) lmp.input.command = ExecutablePathResolver("lammps", "lammps") lmp.input.working_directory = dir ret, out = lmp.execute() if not ret.is_done(): return ReturnStatus.aborted(f"Running lammps failed: {ret.msg}") - psr = LammpsStaticParserTask( - capture_exceptions=self._capture_exceptions - ) + psr = LammpsStaticParserTask(capture_exceptions=self._capture_exceptions) psr.input.working_directory = dir ret, out = psr.execute() if not ret.is_done(): diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index 1854ff249..c8373cd2e 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -1,13 +1,13 @@ from pyiron_contrib.tinybase.container import ( - AbstractOutput, - StructureInput, - StorageAttribute + AbstractOutput, + StructureInput, + StorageAttribute, ) from pyiron_contrib.tinybase.task import ( - AbstractTask, - ListTaskGenerator, - ListInput, - ReturnStatus + AbstractTask, + ListTaskGenerator, + ListInput, + ReturnStatus, ) from copy import deepcopy @@ -19,6 +19,7 @@ from pyiron_atomistics.atomistics.structure.has_structure import HasStructure + class MurnaghanInput(StructureInput, ListInput): strains = StorageAttribute() task = StorageAttribute() @@ -31,7 +32,7 @@ def check_ready(self): return structure_ready and strain_ready and task.input.check_ready() def set_strain_range(self, range, steps): - self.strains = (1 + np.linspace(-range, range, steps))**(1/3) + self.strains = (1 + np.linspace(-range, range, steps)) ** (1 / 3) def _create_tasks(self): cell = self.structure.get_cell() @@ -43,6 +44,7 @@ def _create_tasks(self): tasks.append(n) return tasks + class MurnaghanOutput(AbstractOutput, HasStructure): base_structure = StorageAttribute() volumes = StorageAttribute().type(np.ndarray) @@ -50,23 +52,25 @@ class MurnaghanOutput(AbstractOutput, HasStructure): def plot(self, per_atom=True): N = len(self.base_structure) if per_atom else 1 - plt.plot(self.volumes/N, self.energies/N) + plt.plot(self.volumes / N, self.energies / N) @property def equilibrium_volume(self): inter = si.interp1d(self.volumes, self.energies) - return so.minimize_scalar(inter, bounds=(np.min(self.volumes), np.max(self.volumes))).x + return so.minimize_scalar( + inter, bounds=(np.min(self.volumes), np.max(self.volumes)) + ).x def _number_of_structures(self): return 1 def _get_structure(self, frame, wrap_atoms=True): s = self.base_structure - s.set_cell(s.get_cell() * (self.equilibrium_volume/s.get_volume())**(1/3)) + s.set_cell(s.get_cell() * (self.equilibrium_volume / s.get_volume()) ** (1 / 3)) return s -class MurnaghanTask(ListTaskGenerator): +class MurnaghanTask(ListTaskGenerator): def _get_input(self): return MurnaghanInput() diff --git a/pyiron_contrib/tinybase/project.py b/pyiron_contrib/tinybase/project.py index 5233d3498..0b354bb16 100644 --- a/pyiron_contrib/tinybase/project.py +++ b/pyiron_contrib/tinybase/project.py @@ -2,11 +2,15 @@ import os.path from pyiron_base import Project, DataContainer -from pyiron_contrib.tinybase.storage import GenericStorage, ProjectHDFioStorageAdapter, DataContainerAdapter +from pyiron_contrib.tinybase.storage import ( + GenericStorage, + ProjectHDFioStorageAdapter, + DataContainerAdapter, +) from pyiron_contrib.tinybase.database import TinyDB, GenericDatabase -class ProjectInterface(abc.ABC): +class ProjectInterface(abc.ABC): @classmethod @abc.abstractmethod def open_location(cls, location) -> "ProjectInterface": @@ -68,12 +72,11 @@ def remove(self, job_id): pr = self.open_location(entry.project) pr.remove_storage(entry.name) - #TODO: + # TODO: # def copy_to/move_to across types of ProjectInterface class ProjectAdapter(ProjectInterface): - def __init__(self, project): self._project = project self._database = None @@ -84,8 +87,7 @@ def open_location(cls, location): def create_storage(self, name): return ProjectHDFioStorageAdapter( - self, - self._project.create_hdf(self._project.path, name) + self, self._project.create_hdf(self._project.path, name) ) def exists_storage(self, name) -> bool: @@ -107,8 +109,8 @@ def name(self): def path(self): return self._project.path -class InMemoryProject(ProjectInterface): +class InMemoryProject(ProjectInterface): def __init__(self, location, db=None, storage=None): if db is None: db = TinyDB(":memory:") @@ -124,7 +126,9 @@ def open_location(self, location): return self.__class__(location, db=self.database, storage=self._storage) def create_storage(self, name) -> GenericStorage: - return DataContainerAdapter(self, self._storage[self._location], "/").create_group(name) + return DataContainerAdapter( + self, self._storage[self._location], "/" + ).create_group(name) def exists_storage(self, name) -> bool: return name in self._storage[self._location].list_groups() diff --git a/pyiron_contrib/tinybase/shell.py b/pyiron_contrib/tinybase/shell.py index 6b1870cef..b17ce441f 100644 --- a/pyiron_contrib/tinybase/shell.py +++ b/pyiron_contrib/tinybase/shell.py @@ -5,22 +5,21 @@ from pyiron_base.state import state from pyiron_contrib.tinybase.container import ( - AbstractInput, - AbstractOutput, - StorageAttribute + AbstractInput, + AbstractOutput, + StorageAttribute, ) -from pyiron_contrib.tinybase.task import ( - AbstractTask, - ReturnStatus -) +from pyiron_contrib.tinybase.task import AbstractTask, ReturnStatus import os + if os.name == "nt": EXE_SUFFIX = "bat" else: EXE_SUFFIX = "sh" + class ExecutablePathResolver: """ Locates executables in pyiron resource folders. @@ -40,6 +39,7 @@ class ExecutablePathResolver: :meth:`.__str__` is overloaded to :meth:`.path()`. """ + def __init__(self, module, code, version=None): self._module = module self._code = code @@ -65,17 +65,19 @@ def list_versions(self): List unique version strings found. """ exes = self.list(version="*") + def extract(p): return os.path.splitext( os.path.basename(p).split(f"run_{self._code}", maxsplit=1)[1] )[0][1:] + return list(set(map(extract, exes))) @property def version(self): vers = self.list_versions() for v in vers: - if 'default' in vers: + if "default" in vers: return v return vers[0] @@ -95,7 +97,7 @@ def path(self): if self._version is not None: return exes[0] for p in exes: - if 'default' in p: + if "default" in p: return p return exes[0] @@ -110,13 +112,14 @@ class ShellInput(AbstractInput): working_directory = StorageAttribute().type(str) allowed_returncode = StorageAttribute().type(list) + class ShellOutput(AbstractOutput): stdout = StorageAttribute() stderr = StorageAttribute() returncode = StorageAttribute().type(int) -class ShellTask(AbstractTask): +class ShellTask(AbstractTask): def _get_input(self): return ShellInput() @@ -127,11 +130,11 @@ def _execute(self, output): environ = dict(os.environ) environ.update({k: str(v) for k, v in self.input.environ.items()}) proc = subprocess.run( - [str(self.input.command), *map(str, self.input.arguments)], - capture_output=True, - cwd=self.input.working_directory, - encoding='utf8', - env=environ + [str(self.input.command), *map(str, self.input.arguments)], + capture_output=True, + cwd=self.input.working_directory, + encoding="utf8", + env=environ, ) output.stdout = proc.stdout output.stderr = proc.stderr @@ -140,4 +143,4 @@ def _execute(self, output): if allowed_returncode is None: allowed_returncode = [0] if proc.returncode not in allowed_returncode: - return ReturnStatus('aborted', f'non-zero error code {proc.returncode}') + return ReturnStatus("aborted", f"non-zero error code {proc.returncode}") diff --git a/pyiron_contrib/tinybase/storage.py b/pyiron_contrib/tinybase/storage.py index ca0cab251..768dfdb89 100644 --- a/pyiron_contrib/tinybase/storage.py +++ b/pyiron_contrib/tinybase/storage.py @@ -8,10 +8,12 @@ import pickle import codecs + # utility functions until ASE can be HDF'd def pickle_dump(obj): return codecs.encode(pickle.dumps(obj), "base64").decode() + def pickle_load(buf): return pickle.loads(codecs.decode(buf.encode(), "base64")) @@ -156,6 +158,7 @@ def to_object(self) -> "Storable": raise RuntimeError("Failed to import serialized object!") return cls.restore(self, version=version) + class ProjectHDFioStorageAdapter(GenericStorage): """ Adapter class around ProjectHDFio to let it be used as a GenericStorage. @@ -191,6 +194,7 @@ def project(self): def name(self): return self._hdf.name + class DataContainerAdapter(GenericStorage): """ Provides in memory location to store objects. @@ -218,7 +222,6 @@ def create_group(self, name): d = self._cont[name] return self.__class__(self._project, d, name) - def list_nodes(self): return self._cont.list_nodes() @@ -233,6 +236,7 @@ def project(self): def name(self): return self._name + # DESIGN: equivalent of HasHDF but with generalized language class Storable(abc.ABC): """ @@ -290,6 +294,7 @@ def restore(cls, storage: GenericStorage, version: str) -> "Storable": except Exception as e: raise ValueError(f"Failed to restore object with {e}") + class HasHDFAdapaterMixin(Storable): """ Implements :class:`.Storable` in terms of HasHDF. Make any sub class of it a subclass :class:`.Storable` as well by @@ -305,4 +310,3 @@ def _restore(cls, storage, version): obj = cls(**kw) obj._from_hdf(storage, version) return obj - diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py index b70568140..68ae9c2dd 100644 --- a/pyiron_contrib/tinybase/task.py +++ b/pyiron_contrib/tinybase/task.py @@ -8,6 +8,7 @@ from pyiron_contrib.tinybase.storage import Storable, pickle_dump from .container import AbstractInput, AbstractOutput, StorageAttribute + class ReturnStatus: """ Status of the calculation. @@ -41,6 +42,7 @@ def not_converged(cls, msg=None): def __repr__(self): return f"ReturnStatus({self.code}, {self.msg})" + def __str__(self): return f"{self.code}({self.msg})" @@ -50,6 +52,7 @@ def is_done(self) -> True: """ return self.code == self.Code.DONE + class AbstractTask(Storable, abc.ABC): """ Basic unit of calculations. @@ -60,7 +63,7 @@ class AbstractTask(Storable, abc.ABC): def __init__(self, capture_exceptions=True): self._input = None - self._capture_exceptions=capture_exceptions + self._capture_exceptions = capture_exceptions @abc.abstractmethod def _get_input(self) -> AbstractInput: @@ -119,10 +122,12 @@ def execute(self) -> Tuple[ReturnStatus, AbstractOutput]: return ret, output # TaskIterator Impl' - def __iter__(self) -> Generator[ - List['Task'], - List[Tuple[ReturnStatus, AbstractOutput]], - Tuple[ReturnStatus, AbstractOutput] + def __iter__( + self, + ) -> Generator[ + List["Task"], + List[Tuple[ReturnStatus, AbstractOutput]], + Tuple[ReturnStatus, AbstractOutput], ]: ret, *_ = yield [self] return ret @@ -140,6 +145,7 @@ def _restore(cls, storage, version): task._input = pickle_load(storage["input"]) return task + class TaskGenerator(AbstractTask, abc.ABC): """ A generator that yields collections of tasks that can be executed in @@ -151,9 +157,13 @@ class TaskGenerator(AbstractTask, abc.ABC): """ @abc.abstractmethod - def __iter__(self) -> Generator[List['Task'], - List[Tuple[ReturnStatus, AbstractOutput]], - Tuple[ReturnStatus, AbstractOutput]]: + def __iter__( + self, + ) -> Generator[ + List["Task"], + List[Tuple[ReturnStatus, AbstractOutput]], + Tuple[ReturnStatus, AbstractOutput], + ]: pass def _execute(self, output): @@ -168,16 +178,20 @@ def _execute(self, output): output.take(out) return ret + # TaskGenerator.register(AbstractTask) -# assert +# assert + class FunctionInput(AbstractInput): args = StorageAttribute().type(list).constructor(list) kwargs = StorageAttribute().type(dict).constructor(dict) + class FunctionOutput(AbstractOutput): result = StorageAttribute() + class FunctionTask(AbstractTask): """ A task that wraps a generic function. @@ -199,6 +213,7 @@ def _get_output(self): def _execute(self, output): output.result = self._function(*self.input.args, **self.input.kwargs) + class ListInput(abc.ABC): """ The input of :class:`.ListTaskGenerator`. @@ -215,6 +230,7 @@ def _create_tasks(self): """ pass + class ListTaskGenerator(TaskGenerator, abc.ABC): """ A task that executes other tasks in parallel. @@ -303,6 +319,7 @@ def then(self, next_task, connection): self.connections.append(connection) return self + class SeriesTask(TaskGenerator): """ Executes a series of tasks sequentially. @@ -362,15 +379,16 @@ def restart(self, output: AbstractOutput, input: AbstractInput): """ self._restart(output, input, self.scratch) + class RepeatLoopControl(LoopControl): def __init__(self, steps, restart=lambda *_: None): super().__init__(condition=self._count_steps, restart=restart) self._steps = steps def _count_steps(self, output, input, scratch={}): - c = scratch.get('counter', 0) + c = scratch.get("counter", 0) c += 1 - scratch['counter'] = c + scratch["counter"] = c return c >= self._steps @@ -385,7 +403,11 @@ class LoopInput(AbstractInput): trace = StorageAttribute().type(bool).default(False) - def repeat(self, steps: int, restart: Optional[Callable[[AbstractOutput, AbstractInput, dict], None]] = None): + def repeat( + self, + steps: int, + restart: Optional[Callable[[AbstractOutput, AbstractInput, dict], None]] = None, + ): """ Set up a loop control that loops for steps and calls restart in between. @@ -397,9 +419,9 @@ def repeat(self, steps: int, restart: Optional[Callable[[AbstractOutput, Abstrac self.control = RepeatLoopControl(steps) def control_with( - self, - condition: Callable[[AbstractTask, AbstractOutput, dict], bool], - restart: Callable[[AbstractOutput, AbstractInput, dict], None] + self, + condition: Callable[[AbstractTask, AbstractOutput, dict], bool], + restart: Callable[[AbstractOutput, AbstractInput, dict], None], ): """ Set up a loop control that uses the callables for control flow. @@ -410,6 +432,7 @@ def control_with( """ self.control = LoopControl(condition, restart) + class LoopTask(TaskGenerator): """ Generic task to loop over a given input task. From 9a96a26b631ab7c63e99a65aa2e19c86a4e9ade1 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Tue, 20 Jun 2023 07:47:20 +0200 Subject: [PATCH 134/756] Fix codacy nags --- pyiron_contrib/tinybase/ase.py | 3 --- pyiron_contrib/tinybase/container.py | 1 - pyiron_contrib/tinybase/executor.py | 2 -- pyiron_contrib/tinybase/job.py | 4 ++-- pyiron_contrib/tinybase/lammps.py | 10 ++++------ pyiron_contrib/tinybase/murn.py | 5 ++--- pyiron_contrib/tinybase/shell.py | 2 -- pyiron_contrib/tinybase/task.py | 4 +--- 8 files changed, 9 insertions(+), 22 deletions(-) diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index f4e33ef68..98740c291 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -9,9 +9,6 @@ ) from pyiron_contrib.tinybase.task import AbstractTask, ReturnStatus -import numpy as np -import matplotlib.pyplot as plt - from ase.md.langevin import Langevin from ase.md.velocitydistribution import MaxwellBoltzmannDistribution from ase import units diff --git a/pyiron_contrib/tinybase/container.py b/pyiron_contrib/tinybase/container.py index ed62a9d2c..e44f99a67 100644 --- a/pyiron_contrib/tinybase/container.py +++ b/pyiron_contrib/tinybase/container.py @@ -2,7 +2,6 @@ import abc from copy import deepcopy -import sys from pyiron_contrib.tinybase.storage import HasHDFAdapaterMixin diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index 8ebba7349..b34e2421e 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -1,4 +1,3 @@ -import abc import enum from collections import defaultdict from functools import partial @@ -140,7 +139,6 @@ def submit(self, tasks: List[AbstractTask]) -> ExecutionContext: from concurrent.futures import ( ThreadPoolExecutor, ProcessPoolExecutor, - Executor as FExecutor, ) from threading import Lock diff --git a/pyiron_contrib/tinybase/job.py b/pyiron_contrib/tinybase/job.py index b4ccf8d43..204231d69 100644 --- a/pyiron_contrib/tinybase/job.py +++ b/pyiron_contrib/tinybase/job.py @@ -230,8 +230,8 @@ def _restore(cls, storage, version): return job -# I'm not perfectly happy with this, but three thoughts led to this class: -# 1. I want to be able to set any task on a tiny job with subclassing, to make the prototyping new jobs in the notebook +# I'm not perfectly happy with this, but two thoughts led to this class: +# 1. I want to be able to set any task on a tiny job without subclassing, to make the prototyping new jobs in the notebook # easy # 2. I do *not* want people to accidently change the task instance/class while the job is running class GenericTinyJob(TinyJob): diff --git a/pyiron_contrib/tinybase/lammps.py b/pyiron_contrib/tinybase/lammps.py index c61b60644..a6191e31c 100644 --- a/pyiron_contrib/tinybase/lammps.py +++ b/pyiron_contrib/tinybase/lammps.py @@ -1,7 +1,6 @@ import os from tempfile import TemporaryDirectory -from ase import Atoms from pymatgen.io.lammps.outputs import ( parse_lammps_dumps, parse_lammps_log, @@ -22,7 +21,6 @@ EnergyPotOutput, EnergyKinOutput, ForceOutput, - MDOutput, ) from pyiron_contrib.tinybase.task import ( AbstractTask, @@ -156,9 +154,9 @@ def _get_output(self): return LammpsStaticOutput() def _execute(self, output): - with TemporaryDirectory() as dir: + with TemporaryDirectory() as tmp_dir: inp = LammpsInputTask(capture_exceptions=self._capture_exceptions) - inp.input.working_directory = dir + inp.input.working_directory = tmp_dir inp.input.structure = self.input.structure inp.input.potential = self.input.potential inp.input.calc_static() @@ -168,13 +166,13 @@ def _execute(self, output): lmp = ShellTask(capture_exceptions=self._capture_exceptions) lmp.input.command = ExecutablePathResolver("lammps", "lammps") - lmp.input.working_directory = dir + lmp.input.working_directory = tmp_dir ret, out = lmp.execute() if not ret.is_done(): return ReturnStatus.aborted(f"Running lammps failed: {ret.msg}") psr = LammpsStaticParserTask(capture_exceptions=self._capture_exceptions) - psr.input.working_directory = dir + psr.input.working_directory = tmp_dir ret, out = psr.execute() if not ret.is_done(): return ReturnStatus.aborted(f"Parsing failed: {ret.msg}") diff --git a/pyiron_contrib/tinybase/murn.py b/pyiron_contrib/tinybase/murn.py index c8373cd2e..751154f5b 100644 --- a/pyiron_contrib/tinybase/murn.py +++ b/pyiron_contrib/tinybase/murn.py @@ -4,7 +4,6 @@ StorageAttribute, ) from pyiron_contrib.tinybase.task import ( - AbstractTask, ListTaskGenerator, ListInput, ReturnStatus, @@ -31,8 +30,8 @@ def check_ready(self): task.input.structure = self.structure return structure_ready and strain_ready and task.input.check_ready() - def set_strain_range(self, range, steps): - self.strains = (1 + np.linspace(-range, range, steps)) ** (1 / 3) + def set_strain_range(self, volume_range, steps): + self.strains = (1 + np.linspace(-volume_range, volume_range, steps)) ** (1 / 3) def _create_tasks(self): cell = self.structure.get_cell() diff --git a/pyiron_contrib/tinybase/shell.py b/pyiron_contrib/tinybase/shell.py index b17ce441f..e35172234 100644 --- a/pyiron_contrib/tinybase/shell.py +++ b/pyiron_contrib/tinybase/shell.py @@ -12,8 +12,6 @@ from pyiron_contrib.tinybase.task import AbstractTask, ReturnStatus -import os - if os.name == "nt": EXE_SUFFIX = "bat" else: diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py index 68ae9c2dd..fba4513da 100644 --- a/pyiron_contrib/tinybase/task.py +++ b/pyiron_contrib/tinybase/task.py @@ -6,8 +6,7 @@ from pyiron_base.interfaces.object import HasStorage from pyiron_contrib.tinybase.storage import Storable, pickle_dump -from .container import AbstractInput, AbstractOutput, StorageAttribute - +from pyiron_contrib.tinybase.container import AbstractInput, AbstractOutput, StorageAttribute class ReturnStatus: """ @@ -448,7 +447,6 @@ def __iter__(self): task = deepcopy(self.input.task) control = deepcopy(self.input.control) - scratch = {} while True: (ret, out), *_ = yield [task] if not ret.is_done(): From b5e22e402a3e98c7b4e1903d5194e995747fcd90 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Tue, 20 Jun 2023 08:26:43 +0200 Subject: [PATCH 135/756] Fix undefined name --- pyiron_contrib/tinybase/task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py index fba4513da..a276c1531 100644 --- a/pyiron_contrib/tinybase/task.py +++ b/pyiron_contrib/tinybase/task.py @@ -5,7 +5,7 @@ from pyiron_base.interfaces.object import HasStorage -from pyiron_contrib.tinybase.storage import Storable, pickle_dump +from pyiron_contrib.tinybase.storage import Storable, pickle_dump, pickle_load from pyiron_contrib.tinybase.container import AbstractInput, AbstractOutput, StorageAttribute class ReturnStatus: From 1babf3223fa92f6d1030a391491c02dae2951b1e Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Tue, 20 Jun 2023 08:29:05 +0200 Subject: [PATCH 136/756] Iron out some weirdness in the LoopControls --- notebooks/tinybase/Basic.ipynb | 33 +++++++++------------------------ pyiron_contrib/tinybase/task.py | 13 +++++++------ 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/notebooks/tinybase/Basic.ipynb b/notebooks/tinybase/Basic.ipynb index 4bf8ce02e..b0ce940cf 100644 --- a/notebooks/tinybase/Basic.ipynb +++ b/notebooks/tinybase/Basic.ipynb @@ -45,7 +45,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "f655cbd6f562493b95a9c17636a5bf82", + "model_id": "78241b2f48344bb6852122916cf3b573", "version_major": 2, "version_minor": 0 }, @@ -936,7 +936,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 11, "id": "b9807c98-6df8-450f-a8dd-1a53cb4ded35", "metadata": {}, "outputs": [], @@ -946,7 +946,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 12, "id": "ac2b9aa8-c118-4a1a-bf8b-96d6853b9be6", "metadata": {}, "outputs": [], @@ -956,45 +956,30 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 13, "id": "ef092015-5756-409a-bd1a-a31793c0b2b8", "metadata": {}, "outputs": [], "source": [ - "l.input.repeat(10, restart=lambda output, input, scratch: print(output.result))" + "l.input.repeat(10, restart=lambda output, input: print(output.result))" ] }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 14, "id": "10b67618-f56e-4348-9fdc-35514d0e83a4", "metadata": { "tags": [] }, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.7518040192195612\n", - "0.3729703946467765\n", - "0.7024811641924277\n", - "0.6933476543265069\n", - "0.5159262243479708\n", - "0.1606709747261854\n", - "0.2418212217425253\n", - "0.9048565203370044\n", - "0.8068809589505335\n" - ] - }, { "data": { "text/plain": [ - "(ReturnStatus(Code.DONE, None),\n", - " )" + "(ReturnStatus(Code.ABORTED, RepeatLoopControl._count_steps() takes 3 positional arguments but 4 were given),\n", + " )" ] }, - "execution_count": 54, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py index a276c1531..56b98fdc3 100644 --- a/pyiron_contrib/tinybase/task.py +++ b/pyiron_contrib/tinybase/task.py @@ -382,13 +382,13 @@ def restart(self, output: AbstractOutput, input: AbstractInput): class RepeatLoopControl(LoopControl): def __init__(self, steps, restart=lambda *_: None): super().__init__(condition=self._count_steps, restart=restart) - self._steps = steps + self.storage.steps = steps + self.storage.counter = 0 - def _count_steps(self, output, input, scratch={}): - c = scratch.get("counter", 0) - c += 1 - scratch["counter"] = c - return c >= self._steps + def _count_steps(self, output, input, scratch): + c = self.storage.counter + self.storage.counter += 1 + return c >= self.storage.steps class LoopInput(AbstractInput): @@ -401,6 +401,7 @@ class LoopInput(AbstractInput): """ trace = StorageAttribute().type(bool).default(False) + control = StorageAttribute().type(LoopControl) def repeat( self, From e8f80592e196b794e2d482a3eb7aeb36eb2259dc Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Jun 2023 07:46:19 +0000 Subject: [PATCH 137/756] add read --- pyiron_contrib/workflow/files.py | 4 ++++ pyiron_contrib/workflow/workflow.py | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/files.py b/pyiron_contrib/workflow/files.py index 11be709ad..992141ccf 100644 --- a/pyiron_contrib/workflow/files.py +++ b/pyiron_contrib/workflow/files.py @@ -57,5 +57,9 @@ def path(self): def write(self, content, mode='w'): self.directory.write(file_name=self.file_name, content=content, mode=mode) + def read(self, mode='r'): + with open(self.path, mode=mode) as f: + return f.read() + def delete(self): self.path.unlink() diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 8a11e89ec..09858de98 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -155,11 +155,8 @@ def __init__(self, label: str, *nodes: Node, strict_naming=True): self.__dict__["nodes"] = DotDict() self.__dict__["add"] = _NodeAdder(self) self.__dict__["strict_naming"] = strict_naming -<<<<<<< HEAD self.__dict__["working_directory"] = None -======= self.__dict__["server"] = Server() ->>>>>>> submittable_workflow # We directly assign using __dict__ because we override the setattr later for node in nodes: From e98a624e4b52fb8307997f7a45214eeca70bef6e Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Jun 2023 07:56:52 +0000 Subject: [PATCH 138/756] add warning --- pyiron_contrib/workflow/files.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/files.py b/pyiron_contrib/workflow/files.py index 992141ccf..3f4d3ad96 100644 --- a/pyiron_contrib/workflow/files.py +++ b/pyiron_contrib/workflow/files.py @@ -1,4 +1,5 @@ from pathlib import Path +import warnings def delete_files_and_directories_recursively(path): @@ -32,9 +33,14 @@ def __len__(self): def __repr__(self): return f"DirectoryObject(directory='{self.path}' with {len(self)} files)" + def get_path(self, file_name): + return self.path / file_name + + def file_exists(self, file_name): + return self.get_path(file_name).is_file() + def write(self, file_name, content, mode="w"): - path = self.path / Path(file_name) - with path.open(mode=mode) as f: + with self.get_path(file_name).open(mode=mode) as f: f.write(content) def create_subdirectory(self, path): @@ -55,11 +61,16 @@ def path(self): return self.directory.path / Path(self._file_name) def write(self, content, mode='w'): + if self.is_file(): + warnings.warn(f"{self.file_name} already exists") self.directory.write(file_name=self.file_name, content=content, mode=mode) def read(self, mode='r'): with open(self.path, mode=mode) as f: return f.read() + def is_file(self): + return self.directory.file_exists(self.file_name) + def delete(self): self.path.unlink() From 4b7d62534811059e7be751bd244dec4a8382f819 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Jun 2023 08:42:20 +0000 Subject: [PATCH 139/756] add directory test --- tests/unit/workflow/test_files.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/unit/workflow/test_files.py diff --git a/tests/unit/workflow/test_files.py b/tests/unit/workflow/test_files.py new file mode 100644 index 000000000..986e7a1c4 --- /dev/null +++ b/tests/unit/workflow/test_files.py @@ -0,0 +1,20 @@ +import unittest +from pyiron_contrib.workflow.files import DirectoryObject, FileObject +from pathlib import Path + + +class TestFiles(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.directory = DirectoryObject("test") + + def test_directory_exists(self): + self.assertTrue(Path("test").exists() and Path("test").is_dir()) + + @classmethod + def tearDownClass(cls): + cls.directory.delete() + + +if __name__ == '__main__': + unittest.main() From 8136858258bd60f8e8a8fb0534341f19cac26e31 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Jun 2023 08:51:54 +0000 Subject: [PATCH 140/756] add directory test --- tests/unit/workflow/test_files.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/unit/workflow/test_files.py b/tests/unit/workflow/test_files.py index 986e7a1c4..68ed88429 100644 --- a/tests/unit/workflow/test_files.py +++ b/tests/unit/workflow/test_files.py @@ -11,6 +11,10 @@ def setUpClass(cls): def test_directory_exists(self): self.assertTrue(Path("test").exists() and Path("test").is_dir()) + def test_write(self): + cls.directory.write(file_name="test.txt", content="something") + self.assertEqual(len(cls.directory), 1) + @classmethod def tearDownClass(cls): cls.directory.delete() From cb1dff73a11445c9756c839a7e98e55eb351cab8 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Jun 2023 08:53:10 +0000 Subject: [PATCH 141/756] add test_write --- tests/unit/workflow/test_files.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/workflow/test_files.py b/tests/unit/workflow/test_files.py index 68ed88429..0d7b9e803 100644 --- a/tests/unit/workflow/test_files.py +++ b/tests/unit/workflow/test_files.py @@ -12,8 +12,8 @@ def test_directory_exists(self): self.assertTrue(Path("test").exists() and Path("test").is_dir()) def test_write(self): - cls.directory.write(file_name="test.txt", content="something") - self.assertEqual(len(cls.directory), 1) + self.directory.write(file_name="test.txt", content="something") + self.assertEqual(len(self.directory), 1) @classmethod def tearDownClass(cls): From 04410b1663ddb8e60a0f58f3fab50294fc8488cc Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Jun 2023 16:00:23 +0000 Subject: [PATCH 142/756] categorize files --- pyiron_contrib/workflow/files.py | 26 +++++++++++++++++++++++--- tests/unit/workflow/test_files.py | 10 ++++++++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/files.py b/pyiron_contrib/workflow/files.py index 438f1c3de..9a95e29ff 100644 --- a/pyiron_contrib/workflow/files.py +++ b/pyiron_contrib/workflow/files.py @@ -13,6 +13,26 @@ def delete_files_and_directories_recursively(path): path.rmdir() +def categorize_folder_items(folder_path): + types = [ + 'dir', + 'file', + 'mount', + 'symlink', + 'block_device', + 'char_device', + 'fifo', + 'socket', + ] + results = {t: [] for t in types} + + for item in folder_path.iterdir(): + for tt in types: + if getattr(item, f"is_{tt}")(): + results[tt].append(str(item)) + return results + + class DirectoryObject: def __init__(self, directory): self.path = Path(directory) @@ -24,11 +44,11 @@ def create(self): def delete(self): delete_files_and_directories_recursively(self.path) - def list_files(self): - return list(self.path.glob("*")) + def list_content(self): + return categorize_folder_items(self.path) def __len__(self): - return len(self.list_files()) + return sum([cc for cc in self.list_content()]) def __repr__(self): return f"DirectoryObject(directory='{self.path}' with {len(self)} files)" diff --git a/tests/unit/workflow/test_files.py b/tests/unit/workflow/test_files.py index 0d7b9e803..05208eb9d 100644 --- a/tests/unit/workflow/test_files.py +++ b/tests/unit/workflow/test_files.py @@ -5,7 +5,7 @@ class TestFiles(unittest.TestCase): @classmethod - def setUpClass(cls): + def setUp(cls): cls.directory = DirectoryObject("test") def test_directory_exists(self): @@ -13,10 +13,16 @@ def test_directory_exists(self): def test_write(self): self.directory.write(file_name="test.txt", content="something") + self.assertTrue(self.directory.file_exists("test.txt")) + self.assertTrue("test/test.txt" in self.directory.list_files()) self.assertEqual(len(self.directory), 1) + def test_create_subdirectory(self): + self.directory.create_subdirectory("another_test") + self.assertTrue(Path("test/another_test").exists()) + @classmethod - def tearDownClass(cls): + def tearDown(cls): cls.directory.delete() From 6f39e59a0472fffa27ac5a49bf87dddeb19ec973 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Jun 2023 16:00:53 +0000 Subject: [PATCH 143/756] categorize files --- tests/unit/workflow/test_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/workflow/test_files.py b/tests/unit/workflow/test_files.py index 05208eb9d..deeea5587 100644 --- a/tests/unit/workflow/test_files.py +++ b/tests/unit/workflow/test_files.py @@ -14,7 +14,7 @@ def test_directory_exists(self): def test_write(self): self.directory.write(file_name="test.txt", content="something") self.assertTrue(self.directory.file_exists("test.txt")) - self.assertTrue("test/test.txt" in self.directory.list_files()) + self.assertTrue("test/test.txt" in self.directory.list_content()['file']) self.assertEqual(len(self.directory), 1) def test_create_subdirectory(self): From ac0a56c3250788b46a330a3cd60da86287cfa17d Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Jun 2023 16:25:56 +0000 Subject: [PATCH 144/756] add test_path --- pyiron_contrib/workflow/files.py | 4 ++-- tests/unit/workflow/test_files.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/files.py b/pyiron_contrib/workflow/files.py index 9a95e29ff..092a40b6c 100644 --- a/pyiron_contrib/workflow/files.py +++ b/pyiron_contrib/workflow/files.py @@ -48,10 +48,10 @@ def list_content(self): return categorize_folder_items(self.path) def __len__(self): - return sum([cc for cc in self.list_content()]) + return sum([len(cc) for cc in self.list_content().values()]) def __repr__(self): - return f"DirectoryObject(directory='{self.path}' with {len(self)} files)" + return f"DirectoryObject(directory='{self.path}')\n{self.list_content()}" def get_path(self, file_name): return self.path / file_name diff --git a/tests/unit/workflow/test_files.py b/tests/unit/workflow/test_files.py index deeea5587..2b38ec95f 100644 --- a/tests/unit/workflow/test_files.py +++ b/tests/unit/workflow/test_files.py @@ -21,6 +21,12 @@ def test_create_subdirectory(self): self.directory.create_subdirectory("another_test") self.assertTrue(Path("test/another_test").exists()) + def test_path(self): + f = FileObject("test.txt", self.directory) + self.assertEqual(str(f.path), "test/test.txt") + + # def test_read(self): + @classmethod def tearDown(cls): cls.directory.delete() From c7150e122794aa2c723eaa24799f93b4cb1e3e7b Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Jun 2023 16:26:53 +0000 Subject: [PATCH 145/756] add test_path --- tests/unit/workflow/test_files.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/unit/workflow/test_files.py b/tests/unit/workflow/test_files.py index 2b38ec95f..932eec83b 100644 --- a/tests/unit/workflow/test_files.py +++ b/tests/unit/workflow/test_files.py @@ -25,7 +25,10 @@ def test_path(self): f = FileObject("test.txt", self.directory) self.assertEqual(str(f.path), "test/test.txt") - # def test_read(self): + def test_read_and_write(self): + f = FileObject("test.txt", self.directory) + f.write("something") + self.assertEqual(f.read(), "something") @classmethod def tearDown(cls): From 53e0217c260dd0e7970ce9a3c840e835d7b2912e Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Jun 2023 16:32:25 +0000 Subject: [PATCH 146/756] add tests for is_file and delete --- tests/unit/workflow/test_files.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/unit/workflow/test_files.py b/tests/unit/workflow/test_files.py index 932eec83b..3aa893c7d 100644 --- a/tests/unit/workflow/test_files.py +++ b/tests/unit/workflow/test_files.py @@ -30,6 +30,14 @@ def test_read_and_write(self): f.write("something") self.assertEqual(f.read(), "something") + def test_is_file(self): + f = FileObject("test.txt", self.directory) + self.assertFalse(f.is_file()) + f.write("something") + self.assertTrue(f.is_file()) + f.delete() + self.assertFalse(f.is_file()) + @classmethod def tearDown(cls): cls.directory.delete() From 3a71a5abc579dbfbe1d934fe886c3416999d2d3f Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Jun 2023 10:18:33 -0700 Subject: [PATCH 147/756] Only give the final warning --- pyiron_contrib/workflow/has_nodes.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/has_nodes.py b/pyiron_contrib/workflow/has_nodes.py index 7c927417a..9594233f2 100644 --- a/pyiron_contrib/workflow/has_nodes.py +++ b/pyiron_contrib/workflow/has_nodes.py @@ -69,12 +69,13 @@ def _add_suffix_to_label(self, label): i = 0 new_label = label while new_label in self.nodes.keys(): + new_label = f"{label}{i}" + i += 1 + if new_label != label: warn( f"{label} is already a node; appending an index to the " - f"node label instead: {label}{i}" + f"node label instead: {new_label}" ) - new_label = f"{label}{i}" - i += 1 return new_label def _ensure_node_has_no_other_parent(self, node: Node, label: str): From fc59f88f85455bf6c8072f336b02d352fe6d441f Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Jun 2023 10:19:50 -0700 Subject: [PATCH 148/756] Streamline strict naming flag --- pyiron_contrib/workflow/has_nodes.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pyiron_contrib/workflow/has_nodes.py b/pyiron_contrib/workflow/has_nodes.py index 9594233f2..8de161e51 100644 --- a/pyiron_contrib/workflow/has_nodes.py +++ b/pyiron_contrib/workflow/has_nodes.py @@ -22,7 +22,7 @@ class HasNodes(ABC): def __init__(self, *args, strict_naming=True, **kwargs): self.nodes: DotDict = DotDict() self.add: NodeAdder = NodeAdder(self) - self._strict_naming: bool = strict_naming + self.strict_naming: bool = strict_naming def add_node(self, node: Node, label: Optional[str] = None) -> None: """ @@ -96,16 +96,6 @@ def _ensure_node_has_no_other_parent(self, node: Node, label: str): f"add it to this parent ({self.label})." ) - @property - def strict_naming(self) -> bool: - return self._strict_naming - - def activate_strict_naming(self): - self._strict_naming = True - - def deactivate_strict_naming(self): - self._strict_naming = False - def remove(self, node: Node | str): if isinstance(node, Node): node.parent = None From 160b77c0b915623bd559928cbdec2c1cfc91434f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 20 Jun 2023 10:56:45 -0700 Subject: [PATCH 149/756] Rolled back too far --- pyiron_contrib/workflow/workflow.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 34a77aab8..55ceea55b 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -121,8 +121,7 @@ class Workflow(HasToDict, HasNodes): def __init__(self, label: str, *nodes: Node, strict_naming=True): super().__init__(strict_naming=strict_naming) - self.__dict__["label"] = label - # We directly assign using __dict__ because we override the setattr later + self.label = label for node in nodes: self.add_node(node) From 41d946639f790c3a347962a653df0387f0ea597d Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Jun 2023 10:57:39 -0700 Subject: [PATCH 150/756] Streamline strict naming --- pyiron_contrib/workflow/has_nodes.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pyiron_contrib/workflow/has_nodes.py b/pyiron_contrib/workflow/has_nodes.py index 895274b46..295d897ee 100644 --- a/pyiron_contrib/workflow/has_nodes.py +++ b/pyiron_contrib/workflow/has_nodes.py @@ -22,7 +22,7 @@ class HasNodes(ABC): def __init__(self, *args, strict_naming=True, **kwargs): self.nodes: DotDict = DotDict() self.add: NodeAdder = NodeAdder(self) - self._strict_naming: bool = strict_naming + self.strict_naming: bool = strict_naming def add_node(self, node: Node, label: Optional[str] = None) -> None: """ @@ -95,16 +95,6 @@ def _ensure_node_has_no_other_parent(self, node: Node, label: str): f"add it to this parent ({self.label})." ) - @property - def strict_naming(self) -> bool: - return self._strict_naming - - def activate_strict_naming(self): - self._strict_naming = True - - def deactivate_strict_naming(self): - self._strict_naming = False - def remove(self, node: Node | str): if isinstance(node, Node): node.parent = None From 53ca7ab5f2f1da4a438a460141a2d69c339f97a6 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 20 Jun 2023 10:58:12 -0700 Subject: [PATCH 151/756] Only warn on the last one --- pyiron_contrib/workflow/has_nodes.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/has_nodes.py b/pyiron_contrib/workflow/has_nodes.py index 295d897ee..42df60456 100644 --- a/pyiron_contrib/workflow/has_nodes.py +++ b/pyiron_contrib/workflow/has_nodes.py @@ -69,12 +69,13 @@ def _add_suffix_to_label(self, label): i = 0 new_label = label while new_label in self.nodes.keys(): + new_label = f"{label}{i}" + i += 1 + if new_label != label: warn( f"{label} is already a node; appending an index to the " - f"node label instead: {label}{i}" + f"node label instead: {new_label}" ) - new_label = f"{label}{i}" - i += 1 return new_label def _ensure_node_has_no_other_parent(self, node: Node, label: str): From c89de03cd44881bb8022fdf514abdfb326732a0e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 20 Jun 2023 11:05:34 -0700 Subject: [PATCH 152/756] Fix tests I forgot to stop using the (de)activation method is all --- tests/unit/workflow/test_workflow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index ae0848b20..33699b5c4 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -22,7 +22,7 @@ def test_node_addition(self): Node(fnc, "x", label="boa", parent=wf) self.assertListEqual(list(wf.nodes.keys()), ["foo", "bar", "baz", "boa"]) - wf.deactivate_strict_naming() + wf.strict_naming = False # Validate name incrementation wf.add(Node(fnc, "x", label="foo")) wf.add.Node(fnc, "y", label="bar") @@ -40,7 +40,7 @@ def test_node_addition(self): ] ) - wf.activate_strict_naming() + wf.strict_naming = True # Validate name preservation with self.assertRaises(AttributeError): wf.add(Node(fnc, "x", label="foo")) From 859d1255c7c983d69142e0d70eb5696fac6e3142 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 20 Jun 2023 11:18:48 -0700 Subject: [PATCH 153/756] Manually resolve black nit For some reason the CI was complaining. --- pyiron_contrib/tinybase/task.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py index 56b98fdc3..0748157c5 100644 --- a/pyiron_contrib/tinybase/task.py +++ b/pyiron_contrib/tinybase/task.py @@ -6,7 +6,12 @@ from pyiron_base.interfaces.object import HasStorage from pyiron_contrib.tinybase.storage import Storable, pickle_dump, pickle_load -from pyiron_contrib.tinybase.container import AbstractInput, AbstractOutput, StorageAttribute +from pyiron_contrib.tinybase.container import ( + AbstractInput, + AbstractOutput, + StorageAttribute +) + class ReturnStatus: """ From b46644229a6ecf23a74ef4db759ee6c67971ac0b Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 20 Jun 2023 11:22:03 -0700 Subject: [PATCH 154/756] Black comma --- pyiron_contrib/tinybase/task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py index 0748157c5..65df8250b 100644 --- a/pyiron_contrib/tinybase/task.py +++ b/pyiron_contrib/tinybase/task.py @@ -9,7 +9,7 @@ from pyiron_contrib.tinybase.container import ( AbstractInput, AbstractOutput, - StorageAttribute + StorageAttribute, ) From 09019e4dfb357c1c434872f1c2b3353f4d6f2970 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 20 Jun 2023 12:50:17 -0700 Subject: [PATCH 155/756] Remove unused imports And give correct module link path in the docstring example --- pyiron_contrib/workflow/has_nodes.py | 2 +- pyiron_contrib/workflow/workflow.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/has_nodes.py b/pyiron_contrib/workflow/has_nodes.py index 42df60456..79cc34357 100644 --- a/pyiron_contrib/workflow/has_nodes.py +++ b/pyiron_contrib/workflow/has_nodes.py @@ -2,7 +2,7 @@ from abc import ABC from functools import partial -from typing import Optional, TYPE_CHECKING +from typing import Optional from warnings import warn from pyiron_contrib.workflow.node import Node diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 55ceea55b..eb15b012d 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -3,7 +3,6 @@ from pyiron_contrib.workflow.has_nodes import HasNodes from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.node import Node, node, fast_node, single_value_node -from pyiron_contrib.workflow.node_library import atomistics, standard from pyiron_contrib.workflow.util import DotDict @@ -93,13 +92,13 @@ class Workflow(HasToDict, HasNodes): ... cubic=True, ... element="Al" ... ) - >>> wf.engine = atomistics.Lammps(structure=wf.structure) - >>> wf.calc = atomistics.CalcMd( + >>> wf.engine = wf.add.atomistics.Lammps(structure=wf.structure) + >>> wf.calc = wf.add.atomistics.CalcMd( ... job=wf.engine, ... run_on_updates=True, ... update_on_instantiation=True, ... ) - >>> wf.plot = standard.Scatter( + >>> wf.plot = wf.add.standard.Scatter( ... x=wf.calc.outputs.steps, ... y=wf.calc.outputs.temperature ... ) From e09796397c622f9eb25900793f4920754d6b3b2e Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 21 Jun 2023 06:12:35 +0000 Subject: [PATCH 156/756] move the location of `create_file` --- pyiron_contrib/workflow/files.py | 3 +++ pyiron_contrib/workflow/node.py | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/files.py b/pyiron_contrib/workflow/files.py index 092a40b6c..d88bc6e02 100644 --- a/pyiron_contrib/workflow/files.py +++ b/pyiron_contrib/workflow/files.py @@ -66,6 +66,9 @@ def write(self, file_name, content, mode="w"): def create_subdirectory(self, path): return DirectoryObject(self.path / path) + def create_file(self, file_name): + return FileObject(file_name, self) + class FileObject: def __init__(self, file_name: str, directory: DirectoryObject): diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index f678a98b9..90a1b4e39 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -592,9 +592,6 @@ def working_directory(self): ) return self._working_directory - def create_file(self, file_name): - return FileObject(file_name, self.working_directory) - class FastNode(Node): """ From 5b36df6f7d5ff684b2dddee405b94228e7e40392 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Wed, 21 Jun 2023 15:30:51 +0200 Subject: [PATCH 157/756] Add pympipool executor --- .ci_support/environment.yml | 2 ++ pyiron_contrib/tinybase/executor.py | 12 ++++++++++++ setup.py | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index af5f36407..ca2965cff 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -20,3 +20,5 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.68.0 +- pympipool =0.5.0 +- distributed =2023.6.0 diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index b34e2421e..15407c3de 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -268,3 +268,15 @@ def from_localcluster(cls, max_processes=None, **kwargs): def submit(self, tasks): return FuturesExecutionContext(self._client, tasks) + + +from pympipool import PoolExecutor + + +class PyMPIExecutor(Executor): + def __init__(self, max_workers, **kwargs): + self._max_workers = max_workers + self._pool = PoolExecutor(max_workers=max_workers, **kwargs) + + def submit(self, tasks): + return FuturesExecutionContext(self._pool, tasks) diff --git a/setup.py b/setup.py index 450512b78..b6c904e02 100644 --- a/setup.py +++ b/setup.py @@ -57,6 +57,10 @@ 'python>=3.10', 'ipython', 'typeguard==4.0.0' + ], + 'tinybase': [ + 'distributed==2023.6.0', + 'pympipool==0.5.0' ] }, cmdclass=versioneer.get_cmdclass(), From f14cdd0b1d837924d363d18467818b2447899ff0 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 21 Jun 2023 13:32:16 +0000 Subject: [PATCH 158/756] Update env file --- .binder/environment.yml | 2 ++ docs/environment.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.binder/environment.yml b/.binder/environment.yml index c566205e6..0e590e156 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -20,5 +20,7 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.68.0 +- pympipool =0.5.0 +- distributed =2023.6.0 - python >= 3.10 - lammps diff --git a/docs/environment.yml b/docs/environment.yml index 7963ebbf6..00fd3b588 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -22,3 +22,5 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.68.0 +- pympipool =0.5.0 +- distributed =2023.6.0 From 76df65072caf9931abb55e82bacbc1c69ad96e3b Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 21 Jun 2023 12:26:12 -0700 Subject: [PATCH 159/756] Extract a new parent class for nodal objects and rebase node onto it --- pyiron_contrib/workflow/has_nodes.py | 30 ++++++----- pyiron_contrib/workflow/is_nodal.py | 80 ++++++++++++++++++++++++++++ pyiron_contrib/workflow/node.py | 56 +++++++++---------- tests/unit/workflow/test_workflow.py | 10 +++- 4 files changed, 128 insertions(+), 48 deletions(-) create mode 100644 pyiron_contrib/workflow/is_nodal.py diff --git a/pyiron_contrib/workflow/has_nodes.py b/pyiron_contrib/workflow/has_nodes.py index 79cc34357..cf4bc6be8 100644 --- a/pyiron_contrib/workflow/has_nodes.py +++ b/pyiron_contrib/workflow/has_nodes.py @@ -39,16 +39,16 @@ def add_node(self, node: Node, label: Optional[str] = None) -> None: raise TypeError( f"Only new node instances may be added, but got {type(node)}." ) - - label = self._ensure_label_is_unique(node.label if label is None else label) - self._ensure_node_has_no_other_parent(node, label) + self._ensure_node_has_no_other_parent(node) + label = self._get_unique_label(node.label if label is None else label) + self._ensure_node_is_not_duplicated(node, label) self.nodes[label] = node node.label = label node.parent = self return node - def _ensure_label_is_unique(self, label): + def _get_unique_label(self, label): if label in self.__dir__(): if isinstance(getattr(self, label), Node): if self.strict_naming: @@ -78,23 +78,25 @@ def _add_suffix_to_label(self, label): ) return new_label - def _ensure_node_has_no_other_parent(self, node: Node, label: str): + def _ensure_node_has_no_other_parent(self, node: Node): + if node.parent is not None and node.parent is not self: + raise ValueError( + f"The node ({node.label}) already belongs to the parent " + f"{node.parent.label}. Please remove it there before trying to " + f"add it to this parent ({self.label})." + ) + + def _ensure_node_is_not_duplicated(self, node: Node, label: str): if ( - node.parent is self # This should guarantee the node is in self.nodes - and label != node.label + node.parent is self + and label != node.label + and self.nodes[node.label] is node ): - assert self.nodes[node.label] is node # Should be unreachable by users warn( f"Reassigning the node {node.label} to the label {label} when " f"adding it to the parent {self.label}." ) del self.nodes[node.label] - elif node.parent is not None: - raise ValueError( - f"The node ({node.label}) already belongs to the parent " - f"{node.parent.label}. Please remove it there before trying to " - f"add it to this parent ({self.label})." - ) def remove(self, node: Node | str): if isinstance(node, Node): diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py new file mode 100644 index 000000000..680d1711e --- /dev/null +++ b/pyiron_contrib/workflow/is_nodal.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pyiron_base.jobs.job.extension.server.generic import Server + + from pyiron_contrib.workflow.io import Inputs, Outputs, Signals + + +class IsNodal(ABC): + """ + A mixin class for classes that can represent nodes on a computation graph. + """ + + def __init__( + self, + label: str, + *args, + **kwargs + ): + super().__init__(*args, **kwargs) + self.label: str = label + self.running = False + self.failed = False + # TODO: Replace running and failed with a state object + self._server: Server | None = None # Or "task_manager" or "executor" -- we'll see what's best + + @property + @abstractmethod + def inputs(self) -> Inputs: + pass + + @property + @abstractmethod + def outputs(self) -> Outputs: + pass + + @property + @abstractmethod + def signals(self) -> Signals: + pass + + @abstractmethod + def update(self): + pass + + @abstractmethod + def run(self): + pass + + @property + def server(self) -> Server | None: + return self._server + + @server.setter + def server(self, server: Server | None): + self._server = server + + def disconnect(self): + self.inputs.disconnect() + self.outputs.disconnect() + self.signals.disconnect() + + @property + def ready(self) -> bool: + return not (self.running or self.failed) and self.inputs.ready + + @property + def connected(self) -> bool: + return self.inputs.connected or self.outputs.connected or self.signals.connected + + @property + def fully_connected(self): + return ( + self.inputs.fully_connected + and self.outputs.fully_connected + and self.signals.fully_connected + ) \ No newline at end of file diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 761d9a601..d170bbd6c 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -14,12 +14,13 @@ from pyiron_contrib.workflow.has_channel import HasChannel from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Inputs, Outputs, Signals +from pyiron_contrib.workflow.is_nodal import IsNodal if TYPE_CHECKING: from pyiron_contrib.workflow.workflow import Workflow -class Node(HasToDict): +class Node(IsNodal, HasToDict): """ Nodes have input and output data channels that interface with the outside world, and a callable that determines what they actually compute. After running, their output @@ -323,28 +324,27 @@ def __init__( parent: Optional[Workflow] = None, **kwargs, ): + super().__init__( + label=label if label is not None else node_function.__name__, + # **kwargs, + ) + self.parent = parent + if parent is not None: + parent.add(self) if len(output_labels) == 0: raise ValueError("Nodes must have at least one output label.") - self.running = False - self.failed = False - self.server = None # Or "task_manager" or "executor" -- we'll see what's best self.node_function = node_function - self.label = label if label is not None else node_function.__name__ - - self.parent = None - if parent is not None: - parent.add(self) input_channels = self._build_input_channels(input_storage_priority) - self.inputs = Inputs(*input_channels) + self._inputs = Inputs(*input_channels) output_channels = self._build_output_channels( *output_labels, storage_priority=output_storage_priority ) - self.outputs = Outputs(*output_channels) + self._outputs = Outputs(*output_channels) - self.signals = self._build_signal_channels() + self._signals = self._build_signal_channels() self.channels_requiring_update_after_run = ( [] @@ -364,6 +364,18 @@ def __init__( if update_on_instantiation: self.update() + @property + def inputs(self) -> Inputs: + return self._inputs + + @property + def outputs(self) -> Outputs: + return self._outputs + + @property + def signals(self) -> Signals: + return self._signals + def _build_input_channels(self, storage_priority: dict[str:int]): channels = [] type_hints = get_type_hints(self.node_function) @@ -520,26 +532,6 @@ def process_output(self, function_output): def __call__(self) -> None: self.run() - def disconnect(self): - self.inputs.disconnect() - self.outputs.disconnect() - self.signals.disconnect() - - @property - def ready(self) -> bool: - return not (self.running or self.failed) and self.inputs.ready - - @property - def connected(self) -> bool: - return self.inputs.connected or self.outputs.connected or self.signals.connected - - @property - def fully_connected(self): - return ( - self.inputs.fully_connected - and self.outputs.fully_connected - and self.signals.fully_connected - ) def set_storage_priority(self, priority: int): self.inputs.set_storage_priority(priority) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 33699b5c4..e954b3056 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -19,8 +19,14 @@ def test_node_addition(self): wf.add(Node(fnc, "x", label="foo")) wf.add.Node(fnc, "y", label="bar") wf.baz = Node(fnc, "y", label="whatever_baz_gets_used") - Node(fnc, "x", label="boa", parent=wf) - self.assertListEqual(list(wf.nodes.keys()), ["foo", "bar", "baz", "boa"]) + Node(fnc, "x", label="qux", parent=wf) + self.assertListEqual(list(wf.nodes.keys()), ["foo", "bar", "baz", "qux"]) + wf.boa = wf.qux + self.assertListEqual( + list(wf.nodes.keys()), + ["foo", "bar", "baz", "boa"], + msg="Reassignment should remove the original instance" + ) wf.strict_naming = False # Validate name incrementation From b326d66a05a9d08875a2bcbc1ad42b57abda1abc Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 21 Jun 2023 12:27:24 -0700 Subject: [PATCH 160/756] :bug: call super in has_nodes mixin --- pyiron_contrib/workflow/has_nodes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyiron_contrib/workflow/has_nodes.py b/pyiron_contrib/workflow/has_nodes.py index cf4bc6be8..e560b0c2a 100644 --- a/pyiron_contrib/workflow/has_nodes.py +++ b/pyiron_contrib/workflow/has_nodes.py @@ -20,6 +20,7 @@ class HasNodes(ABC): """ def __init__(self, *args, strict_naming=True, **kwargs): + super().__init__(*args, **kwargs) self.nodes: DotDict = DotDict() self.add: NodeAdder = NodeAdder(self) self.strict_naming: bool = strict_naming From 96a6f74899fff627a70d75d8968bd8f225f7f502 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 21 Jun 2023 12:35:25 -0700 Subject: [PATCH 161/756] Move initialization of signals up into the parent class --- pyiron_contrib/workflow/is_nodal.py | 16 ++++++++++------ pyiron_contrib/workflow/node.py | 12 ------------ 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py index 680d1711e..e0e0078ce 100644 --- a/pyiron_contrib/workflow/is_nodal.py +++ b/pyiron_contrib/workflow/is_nodal.py @@ -3,10 +3,12 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING +from pyiron_contrib.workflow.io import Signals, InputSignal, OutputSignal + if TYPE_CHECKING: from pyiron_base.jobs.job.extension.server.generic import Server - from pyiron_contrib.workflow.io import Inputs, Outputs, Signals + from pyiron_contrib.workflow.io import Inputs, Outputs class IsNodal(ABC): @@ -26,6 +28,7 @@ def __init__( self.failed = False # TODO: Replace running and failed with a state object self._server: Server | None = None # Or "task_manager" or "executor" -- we'll see what's best + self.signals = self._build_signal_channels() @property @abstractmethod @@ -37,11 +40,6 @@ def inputs(self) -> Inputs: def outputs(self) -> Outputs: pass - @property - @abstractmethod - def signals(self) -> Signals: - pass - @abstractmethod def update(self): pass @@ -50,6 +48,12 @@ def update(self): def run(self): pass + def _build_signal_channels(self) -> Signals: + signals = Signals() + signals.input.run = InputSignal("run", self, self.run) + signals.output.ran = OutputSignal("ran", self) + return signals + @property def server(self) -> Server | None: return self._server diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index d170bbd6c..66b99bf9f 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -344,8 +344,6 @@ def __init__( ) self._outputs = Outputs(*output_channels) - self._signals = self._build_signal_channels() - self.channels_requiring_update_after_run = ( [] if channels_requiring_update_after_run is None @@ -372,10 +370,6 @@ def inputs(self) -> Inputs: def outputs(self) -> Outputs: return self._outputs - @property - def signals(self) -> Signals: - return self._signals - def _build_input_channels(self, storage_priority: dict[str:int]): channels = [] type_hints = get_type_hints(self.node_function) @@ -463,12 +457,6 @@ def _build_output_channels( return channels - def _build_signal_channels(self) -> Signals: - signals = Signals() - signals.input.run = InputSignal("run", self, self.run) - signals.output.ran = OutputSignal("ran", self) - return signals - def _verify_that_channels_requiring_update_all_exist(self): if not all( channel_name in self.inputs.labels From 98486ce7af125852f6e989cc3d04ee45e9d97fe6 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 21 Jun 2023 12:35:36 -0700 Subject: [PATCH 162/756] Rebase workflow onto IsNodal mixin --- pyiron_contrib/workflow/workflow.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index eb15b012d..672f8157d 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -2,6 +2,7 @@ from pyiron_contrib.workflow.has_nodes import HasNodes from pyiron_contrib.workflow.has_to_dict import HasToDict +from pyiron_contrib.workflow.is_nodal import IsNodal from pyiron_contrib.workflow.node import Node, node, fast_node, single_value_node from pyiron_contrib.workflow.util import DotDict @@ -14,7 +15,7 @@ class _NodeDecoratorAccess: single_value_node = single_value_node -class Workflow(HasToDict, HasNodes): +class Workflow(IsNodal, HasToDict, HasNodes): """ Workflows are an abstraction for holding a collection of related nodes. @@ -119,8 +120,7 @@ class Workflow(HasToDict, HasNodes): wrap_as = _NodeDecoratorAccess def __init__(self, label: str, *nodes: Node, strict_naming=True): - super().__init__(strict_naming=strict_naming) - self.label = label + super().__init__(label=label, strict_naming=strict_naming) for node in nodes: self.add_node(node) From bbe69889d3c71c334655be55b5c973bb5a352461 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 21 Jun 2023 12:39:03 -0700 Subject: [PATCH 163/756] Remove unused imports --- pyiron_contrib/workflow/node.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 66b99bf9f..93fc2ff10 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -5,12 +5,7 @@ from functools import partialmethod from typing import get_args, get_type_hints, Optional, TYPE_CHECKING -from pyiron_contrib.workflow.channels import ( - InputData, - OutputData, - InputSignal, - OutputSignal, -) +from pyiron_contrib.workflow.channels import InputData, OutputData from pyiron_contrib.workflow.has_channel import HasChannel from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Inputs, Outputs, Signals From 2a4effb0f6d027f0173384641c80c7167b314d17 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 21 Jun 2023 19:42:50 +0000 Subject: [PATCH 164/756] Format black --- pyiron_contrib/workflow/has_nodes.py | 6 +++--- pyiron_contrib/workflow/is_nodal.py | 13 +++++-------- pyiron_contrib/workflow/node.py | 1 - 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/pyiron_contrib/workflow/has_nodes.py b/pyiron_contrib/workflow/has_nodes.py index e560b0c2a..964f0fd30 100644 --- a/pyiron_contrib/workflow/has_nodes.py +++ b/pyiron_contrib/workflow/has_nodes.py @@ -89,9 +89,9 @@ def _ensure_node_has_no_other_parent(self, node: Node): def _ensure_node_is_not_duplicated(self, node: Node, label: str): if ( - node.parent is self - and label != node.label - and self.nodes[node.label] is node + node.parent is self + and label != node.label + and self.nodes[node.label] is node ): warn( f"Reassigning the node {node.label} to the label {label} when " diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py index e0e0078ce..915938cd8 100644 --- a/pyiron_contrib/workflow/is_nodal.py +++ b/pyiron_contrib/workflow/is_nodal.py @@ -16,18 +16,15 @@ class IsNodal(ABC): A mixin class for classes that can represent nodes on a computation graph. """ - def __init__( - self, - label: str, - *args, - **kwargs - ): + def __init__(self, label: str, *args, **kwargs): super().__init__(*args, **kwargs) self.label: str = label self.running = False self.failed = False # TODO: Replace running and failed with a state object - self._server: Server | None = None # Or "task_manager" or "executor" -- we'll see what's best + self._server: Server | None = ( + None # Or "task_manager" or "executor" -- we'll see what's best + ) self.signals = self._build_signal_channels() @property @@ -81,4 +78,4 @@ def fully_connected(self): self.inputs.fully_connected and self.outputs.fully_connected and self.signals.fully_connected - ) \ No newline at end of file + ) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 93fc2ff10..f6351975c 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -515,7 +515,6 @@ def process_output(self, function_output): def __call__(self) -> None: self.run() - def set_storage_priority(self, priority: int): self.inputs.set_storage_priority(priority) self.outputs.set_storage_priority(priority) From 6444b2eed67e131b353720f60ab179e798dc990f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 21 Jun 2023 12:53:47 -0700 Subject: [PATCH 165/756] Update docstring with the goal of the PR --- pyiron_contrib/workflow/io.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py index d4ffca159..93936f772 100644 --- a/pyiron_contrib/workflow/io.py +++ b/pyiron_contrib/workflow/io.py @@ -25,6 +25,9 @@ class IO(HasToDict, ABC): attribute name matches the channel's label and type (i.e. `OutputChannel` for `Outputs` and `InputChannel` for `Inputs`). + New channels can also be added using the `add` method, which must be implemented in + child classes to add channels of the correct type. + When assigning something to an attribute holding an existing channel, if the assigned object is a `Channel`, then it is treated like a `connection`, otherwise it is treated like a value `update`. I.e. From e96037229a8866a6c2b14c736f2fc21cce49c955 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 21 Jun 2023 13:01:39 -0700 Subject: [PATCH 166/756] Remove one if-clause from __setattr__ --- pyiron_contrib/workflow/io.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py index 93936f772..74327274a 100644 --- a/pyiron_contrib/workflow/io.py +++ b/pyiron_contrib/workflow/io.py @@ -44,7 +44,7 @@ class IO(HasToDict, ABC): """ def __init__(self, *channels: Channel): - self.channel_dict = DotDict( + self.__dict__["channel_dict"] = DotDict( { channel.label: channel for channel in channels @@ -65,9 +65,7 @@ def __getattr__(self, item) -> Channel: return self.channel_dict[item] def __setattr__(self, key, value): - if key in ["channel_dict"]: - super().__setattr__(key, value) - elif key in self.channel_dict.keys(): + if key in self.channel_dict.keys(): self._set_existing(key, value) elif isinstance(value, self._channel_class): if key != value.label: From fb4690e57bf8736f6465e053b04e849e1a21c8a5 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 21 Jun 2023 13:02:45 -0700 Subject: [PATCH 167/756] Narrow the scope of an abstract method By always trying to form a connection on the condition that a channel is assigned to an existing channel --- pyiron_contrib/workflow/io.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py index 74327274a..9c8a28898 100644 --- a/pyiron_contrib/workflow/io.py +++ b/pyiron_contrib/workflow/io.py @@ -58,7 +58,8 @@ def _channel_class(self) -> Channel: pass @abstractmethod - def _set_existing(self, key, value): + def _assign_value_to_channel(self, key, value): + """What to do when some non-channel value gets assigned to a channel""" pass def __getattr__(self, item) -> Channel: @@ -66,8 +67,13 @@ def __getattr__(self, item) -> Channel: def __setattr__(self, key, value): if key in self.channel_dict.keys(): - self._set_existing(key, value) + # Assignment to an existing channel + if isinstance(value, HasChannel): + self.channel_dict[key].connect(value.channel) + else: + self._assign_value_to_channel(key, value) elif isinstance(value, self._channel_class): + # Assigning a channel of the correct type to an unused key if key != value.label: raise ValueError( f"Channels can only be assigned to attributes matching their label," @@ -121,11 +127,8 @@ def to_dict(self): class DataIO(IO, ABC): - def _set_existing(self, key, value): - if isinstance(value, HasChannel): - self.channel_dict[key].connect(value.channel) - else: - self.channel_dict[key].update(value) + def _assign_value_to_channel(self, key, value): + self.channel_dict[key].update(value) def to_value_dict(self): return {label: channel.value for label, channel in self.channel_dict.items()} @@ -163,15 +166,12 @@ def _channel_class(self) -> OutputData: class SignalIO(IO, ABC): - def _set_existing(self, key, value): - if isinstance(value, HasChannel): - self.channel_dict[key].connect(value.channel) - else: - raise TypeError( - f"Tried to assign {value} ({type(value)} to the {key}, which is already" - f" a {type(self.channel_dict[key])}. Only other signal channels may be " - f"connected in this way." - ) + def _assign_value_to_channel(self, key, value): + raise TypeError( + f"Tried to assign {value} ({type(value)} to the {key}, which is already" + f" a {type(self.channel_dict[key])}. Only other signal channels may be " + f"connected in this way." + ) class InputSignals(SignalIO): From 1d7fe3438e82295f054791dfd8a0668f97d29c1f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 21 Jun 2023 13:27:56 -0700 Subject: [PATCH 168/756] Refactor: change signature To improve readability, pass the object in question around instead of just its key --- pyiron_contrib/workflow/io.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py index 9c8a28898..390da2e8e 100644 --- a/pyiron_contrib/workflow/io.py +++ b/pyiron_contrib/workflow/io.py @@ -4,8 +4,10 @@ from pyiron_contrib.workflow.channels import ( Channel, + DataChannel, InputData, OutputData, + SignalChannel, InputSignal, OutputSignal, ) @@ -58,7 +60,7 @@ def _channel_class(self) -> Channel: pass @abstractmethod - def _assign_value_to_channel(self, key, value): + def _assign_a_non_channel_value(self, channel: Channel, value) -> None: """What to do when some non-channel value gets assigned to a channel""" pass @@ -67,13 +69,8 @@ def __getattr__(self, item) -> Channel: def __setattr__(self, key, value): if key in self.channel_dict.keys(): - # Assignment to an existing channel - if isinstance(value, HasChannel): - self.channel_dict[key].connect(value.channel) - else: - self._assign_value_to_channel(key, value) + self._assign_value_to_existing_channel(self.channel_dict[key], value) elif isinstance(value, self._channel_class): - # Assigning a channel of the correct type to an unused key if key != value.label: raise ValueError( f"Channels can only be assigned to attributes matching their label," @@ -86,6 +83,12 @@ def __setattr__(self, key, value): f"attribute {key} got assigned {value} of type {type(value)}" ) + def _assign_value_to_existing_channel(self, channel: Channel, value) -> None: + if isinstance(value, HasChannel): + channel.connect(value.channel) + else: + self._assign_a_non_channel_value(channel, value) + def __getitem__(self, item) -> Channel: return self.__getattr__(item) @@ -127,8 +130,8 @@ def to_dict(self): class DataIO(IO, ABC): - def _assign_value_to_channel(self, key, value): - self.channel_dict[key].update(value) + def _assign_a_non_channel_value(self, channel: DataChannel, value) -> None: + channel.update(value) def to_value_dict(self): return {label: channel.value for label, channel in self.channel_dict.items()} @@ -166,11 +169,13 @@ def _channel_class(self) -> OutputData: class SignalIO(IO, ABC): - def _assign_value_to_channel(self, key, value): + def _assign_a_non_channel_value( + self, channel: SignalChannel, value + ) -> None: raise TypeError( - f"Tried to assign {value} ({type(value)} to the {key}, which is already" - f" a {type(self.channel_dict[key])}. Only other signal channels may be " - f"connected in this way." + f"Tried to assign {value} ({type(value)} to the {channel.label}, which is " + f"already a {type(channel)}. Only other signal channels may be connected " + f"in this way." ) From 9fae7dfa51497441a4d93e223fbae1ec2d6d099e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 21 Jun 2023 13:28:42 -0700 Subject: [PATCH 169/756] Fix type hinting We want the class itself, not an instance --- pyiron_contrib/workflow/io.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py index 390da2e8e..fe441e3b5 100644 --- a/pyiron_contrib/workflow/io.py +++ b/pyiron_contrib/workflow/io.py @@ -56,7 +56,7 @@ def __init__(self, *channels: Channel): @property @abstractmethod - def _channel_class(self) -> Channel: + def _channel_class(self) -> type(Channel): pass @abstractmethod @@ -152,7 +152,7 @@ def to_dict(self): class Inputs(DataIO): @property - def _channel_class(self) -> InputData: + def _channel_class(self) -> type(InputData): return InputData def activate_strict_connections(self): @@ -164,7 +164,7 @@ def deactivate_strict_connections(self): class Outputs(DataIO): @property - def _channel_class(self) -> OutputData: + def _channel_class(self) -> type(OutputData): return OutputData @@ -181,13 +181,13 @@ def _assign_a_non_channel_value( class InputSignals(SignalIO): @property - def _channel_class(self) -> InputSignal: + def _channel_class(self) -> type(InputSignal): return InputSignal class OutputSignals(SignalIO): @property - def _channel_class(self) -> OutputSignal: + def _channel_class(self) -> type(OutputSignal): return OutputSignal From 9c4bae95fa08694a3ae05128d01526b9568dd52b Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 21 Jun 2023 22:36:42 +0000 Subject: [PATCH 170/756] Format black --- pyiron_contrib/workflow/io.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py index fe441e3b5..093f31053 100644 --- a/pyiron_contrib/workflow/io.py +++ b/pyiron_contrib/workflow/io.py @@ -169,9 +169,7 @@ def _channel_class(self) -> type(OutputData): class SignalIO(IO, ABC): - def _assign_a_non_channel_value( - self, channel: SignalChannel, value - ) -> None: + def _assign_a_non_channel_value(self, channel: SignalChannel, value) -> None: raise TypeError( f"Tried to assign {value} ({type(value)} to the {channel.label}, which is " f"already a {type(channel)}. Only other signal channels may be connected " From 7c08a9560ec7fd1ed3837f5eb72313e04c7269b1 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 22 Jun 2023 07:25:06 +0000 Subject: [PATCH 171/756] save current changes --- tests/unit/workflow/test_workflow.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index b1ee9be3a..610c64ede 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -1,4 +1,4 @@ -from unittest import TestCase, skipUnless +import unittest from sys import version_info from pyiron_contrib.workflow.node import Node @@ -9,8 +9,8 @@ def fnc(x=0): return x + 1 -@skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestWorkflow(TestCase): +@unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") +class TestWorkflow(unittest.TestCase): def test_node_addition(self): wf = Workflow("my_workflow") @@ -107,3 +107,11 @@ def plus_one(x: int = 0) -> int: return x + 1 self.assertEqual(plus_one().outputs.y.value, 1) + + # def test_working_directory(self): + # wf = Workflow("wf") + # self.assertTrue(wf.__dict__["working_directory"] is None) + + +if __name__ == '__main__': + unittest.main() From 25527a46a4425e0070f89c1d30d1adc130b30af3 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 22 Jun 2023 13:31:26 +0000 Subject: [PATCH 172/756] resolve conflict --- pyiron_contrib/workflow/workflow.py | 108 ---------------------------- 1 file changed, 108 deletions(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index f82da26eb..8a0854e9c 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -135,114 +135,6 @@ def working_directory(self): self._working_directory = DirectoryObject(self.label) return self._working_directory - def add_node(self, node: Node, label: str = None) -> None: - """ - Assign a node to the workflow. Optionally provide a new label for that node. - - Args: - node (pyiron_contrib.workflow.node.Node): The node to add. - label (Optional[str]): The label for this node. - - Raises: - - """ - if not isinstance(node, Node): - raise TypeError( - f"Only new node instances may be added, but got {type(node)}." - ) - - label = self._ensure_label_is_unique(node.label if label is None else label) - self._ensure_node_belongs_to_at_most_this_workflow(node, label) - - self.nodes[label] = node - node.label = label - node.workflow = self - return node - - def _ensure_node_belongs_to_at_most_this_workflow(self, node: Node, label: str): - if ( - node.workflow is self # This should guarantee the node is in self.nodes - and label != node.label - ): - assert self.nodes[node.label] is node # Should be unreachable by users - warn( - f"Reassigning the node {node.label} to the label {label} when " - f"adding it to the workflow {self.label}." - ) - del self.nodes[node.label] - elif node.workflow is not None: - raise ValueError( - f"The node ({node.label}) already belongs to the workflow " - f"{node.workflow.label}. Please remove it there before trying to " - f"add it to this workflow ({self.label})." - ) - - def _ensure_label_is_unique(self, label): - if label in self.__dir__(): - if isinstance(getattr(self, label), Node): - if self.strict_naming: - raise AttributeError( - f"{label} is already the label for a node. Please remove it " - f"before assigning another node to this label." - ) - else: - label = self._add_suffix_to_label(label) - else: - raise AttributeError( - f"{label} is an attribute or method of the {self.__class__} class, " - f"and cannot be used as a node label." - ) - return label - - def _add_suffix_to_label(self, label): - i = 0 - new_label = label - while new_label in self.nodes.keys(): - warn( - f"{label} is already a node; appending an index to the " - f"node label instead: {label}{i}" - ) - new_label = f"{label}{i}" - i += 1 - return new_label - - def activate_strict_naming(self): - self.__dict__["strict_naming"] = True - - def deactivate_strict_naming(self): - self.__dict__["strict_naming"] = False - - def remove(self, node: Node | str): - if isinstance(node, Node): - node.workflow = None - node.disconnect() - del self.nodes[node.label] - else: - del self.nodes[node] - - def __setattr__(self, label: str, node: Node): - if not isinstance(node, Node): - raise TypeError( - "Only new node instances may be assigned as attributes. This is " - "syntacic sugar for adding new nodes to the .nodes collection" - ) - self.add_node(node, label=label) - - def __getattr__(self, key): - return self.nodes[key] - - def __getitem__(self, item): - return self.__getattr__(item) - - def __setitem__(self, key, value): - self.__setattr__(key, value) - - def __iter__(self): - return self.nodes.values().__iter__() - - def __len__(self): - return len(self.nodes) - @property def inputs(self): return DotDict( From 8bb21954dba1e4db6a3c743eceed50aba0c1be9e Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 22 Jun 2023 13:38:55 +0000 Subject: [PATCH 173/756] resolve conflicts --- pyiron_contrib/workflow/node.py | 4 ++-- tests/unit/workflow/test_workflow.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 0dfd4eae2..78ff0357d 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -582,13 +582,13 @@ def to_dict(self): @property def working_directory(self): if self._working_directory is None: - if self.workflow is None: + if self.parent is None: raise ValueError( "working directory is available only if the node is" " attached to a workflow" ) self._working_directory = ( - self.workflow.working_directory.create_subdirectory(self.label) + self.parent.working_directory.create_subdirectory(self.label) ) return self._working_directory diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 4f183bfa4..c1b1d80a0 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -108,9 +108,9 @@ def plus_one(x: int = 0) -> int: self.assertEqual(plus_one().outputs.y.value, 1) - # def test_working_directory(self): - # wf = Workflow("wf") - # self.assertTrue(wf.__dict__["working_directory"] is None) + def test_working_directory(self): + wf = Workflow("wf") + self.assertTrue(wf._working_directory is None) if __name__ == '__main__': From bbd4fe792fded7b41fd3b2795ad12fb1d5e2cdb9 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 22 Jun 2023 13:42:25 +0000 Subject: [PATCH 174/756] change default mode --- pyiron_contrib/workflow/files.py | 6 +++--- tests/unit/workflow/test_files.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyiron_contrib/workflow/files.py b/pyiron_contrib/workflow/files.py index d88bc6e02..38c14fe7d 100644 --- a/pyiron_contrib/workflow/files.py +++ b/pyiron_contrib/workflow/files.py @@ -83,9 +83,9 @@ def file_name(self): def path(self): return self.directory.path / Path(self._file_name) - def write(self, content, mode='w'): - if self.is_file(): - warnings.warn(f"{self.file_name} already exists") + def write(self, content, mode='x'): + #if self.is_file(): + # warnings.warn(f"{self.file_name} already exists") self.directory.write(file_name=self.file_name, content=content, mode=mode) def read(self, mode='r'): diff --git a/tests/unit/workflow/test_files.py b/tests/unit/workflow/test_files.py index 3aa893c7d..9c028e589 100644 --- a/tests/unit/workflow/test_files.py +++ b/tests/unit/workflow/test_files.py @@ -8,6 +8,10 @@ class TestFiles(unittest.TestCase): def setUp(cls): cls.directory = DirectoryObject("test") + @classmethod + def tearDown(cls): + cls.directory.delete() + def test_directory_exists(self): self.assertTrue(Path("test").exists() and Path("test").is_dir()) @@ -38,10 +42,6 @@ def test_is_file(self): f.delete() self.assertFalse(f.is_file()) - @classmethod - def tearDown(cls): - cls.directory.delete() - if __name__ == '__main__': unittest.main() From f8a8d3edbf9ac76ac05bb47afc415788e0811126 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Thu, 22 Jun 2023 16:13:20 +0200 Subject: [PATCH 175/756] Add creators for UX Add creator classes for jobs, tasks, structures and executors so that users don't need to import them. With it there are some changes in TinyJob: 1. it is no longer an abstract class, but requires a task instance to be instantiated. Reloading already run jobs can be done with TinyJob.restore 2. as a consequence GenericTinyJob is no longer needed 3. because Executors are easily created now, the _executors class attribute is also gone; TinyJob.run now takes an argument 'executor', either an executor or a method name of the ExecutorCreator as a short hand The creators are very similar to the ones already existing in pyiron_base. Notable is the ExecutorCreator which as a method `most_recent` that simply returns the last created executor, which is also used by TinyJob.run by default, freeing the user from lugging around executor instances on their own. --- notebooks/tinybase/TinyJob.ipynb | 674 +++++++++++------------------ pyiron_contrib/tinybase/creator.py | 250 +++++++++++ pyiron_contrib/tinybase/job.py | 88 +--- pyiron_contrib/tinybase/project.py | 9 + 4 files changed, 519 insertions(+), 502 deletions(-) create mode 100644 pyiron_contrib/tinybase/creator.py diff --git a/notebooks/tinybase/TinyJob.ipynb b/notebooks/tinybase/TinyJob.ipynb index d14dd847e..e0a62b49c 100644 --- a/notebooks/tinybase/TinyJob.ipynb +++ b/notebooks/tinybase/TinyJob.ipynb @@ -11,7 +11,7 @@ { "cell_type": "code", "execution_count": 1, - "id": "95763abb-4480-4ced-bf73-db72dae9ffe0", + "id": "1e5208d9-d7b8-4ca3-92cf-5d32c30c20f9", "metadata": { "tags": [] }, @@ -23,119 +23,15 @@ "/home/poul/pyiron/contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n", " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" ] - }, - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "7257515de92a452395badcf83fb46b03",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
     }
    ],
-   "source": [
-    "from pyiron_contrib.tinybase.job import GenericTinyJob"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 2,
-   "id": "9730e2a5-9079-4863-905d-f21e47b65816",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "from pyiron_contrib.tinybase.executor import ProcessExecutor"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 3,
-   "id": "09003a07-be8d-4607-b533-54214ae64056",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "from pyiron_contrib.tinybase.murn import MurnaghanTask"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 4,
-   "id": "70c5d3c8-2d81-4539-9453-cd85010e7373",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "from pyiron_contrib.tinybase.ase import AseMDTask, AseMinimizeTask, AseStaticTask"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 5,
-   "id": "1e5208d9-d7b8-4ca3-92cf-5d32c30c20f9",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
    "source": [
     "from pyiron_contrib.tinybase.project import ProjectAdapter, InMemoryProject"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
-   "id": "5606f2b7-ba23-4b0d-b44e-b41cda6e5d4d",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "from ase import Atoms"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 7,
-   "id": "9a28ea42-360a-4e35-9fce-427b661a05c5",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "from ase.build import bulk"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 2,
    "id": "059021a8-6e20-4265-94f4-8fb9bdaebde3",
    "metadata": {
     "tags": []
@@ -147,19 +43,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
-   "id": "f6fb49d0-dbfc-4b33-8b4d-999a2002831e",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "from pyiron_base import Project"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 29,
+   "execution_count": 3,
    "id": "f2e0123f-f0be-41ef-9733-af547b38d846",
    "metadata": {
     "tags": []
@@ -167,7 +51,7 @@
    "outputs": [],
    "source": [
     "import logging\n",
-    "logging.getLogger().setLevel(10)"
+    "logging.getLogger().setLevel(20)"
    ]
   },
   {
@@ -180,65 +64,87 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": 4,
    "id": "0f30286b-4434-4569-8f03-fadab46ebe34",
    "metadata": {
     "tags": []
    },
    "outputs": [],
    "source": [
-    "pr = ProjectAdapter(Project('tinyjob'))"
+    "pr = ProjectAdapter.open_location('tinyjob')"
    ]
   },
   {
    "cell_type": "markdown",
    "id": "450e6b64-9824-4b8a-8854-3999a93fc781",
-   "metadata": {},
-   "source": [
-    "## MD Job"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 15,
-   "id": "ba190052-7bc1-4383-b3da-c7221c4e38d0",
    "metadata": {
-    "scrolled": true,
     "tags": []
    },
-   "outputs": [],
    "source": [
-    "j = GenericTinyJob(pr, 'md')"
+    "## MD Job"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 16,
-   "id": "1e21980e-14a6-4578-b4b9-8195a74a3593",
+   "execution_count": 5,
+   "id": "e31baebd-b2c8-4343-90ad-92d9128d1496",
    "metadata": {
     "tags": []
    },
-   "outputs": [],
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "d365d332f5f44babad1dc4f275dc3284",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
    "source": [
-    "j.task_class = AseMDTask"
+    "j = pr.create.job.AseMD('md')"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
+   "execution_count": 6,
    "id": "18e6de26-308c-46ae-9672-b2db43447ea5",
    "metadata": {
     "tags": []
    },
    "outputs": [],
    "source": [
-    "j.input.structure = bulk('Fe', a=1.2, cubic=True).repeat(2)\n",
+    "j.input.structure = pr.create.structure.bulk('Fe', a=1.2, cubic=True).repeat(2).to_ase()\n",
     "j.input.calculator = MorsePotential()"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 18,
+   "execution_count": 7,
    "id": "72848cd2-fd51-4ad8-b56e-ca686074bb26",
    "metadata": {
     "tags": []
@@ -253,7 +159,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 19,
+   "execution_count": 8,
    "id": "56c0e73a-c42b-4814-a25a-e6974fea3d00",
    "metadata": {
     "tags": []
@@ -268,12 +174,12 @@
     }
    ],
    "source": [
-    "j.run(how='foreground')"
+    "j.run()"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
+   "execution_count": 9,
    "id": "49ccfe01-7b7e-4615-bf43-21c1bbffec66",
    "metadata": {
     "tags": []
@@ -282,7 +188,7 @@
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "16c628e0bea4418f8c357e500e5ed146",
+       "model_id": "1945295762b54d49809da2f8840cf80d",
        "version_major": 2,
        "version_minor": 0
       },
@@ -308,45 +214,37 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
-   "id": "049f056e-47ff-4c2f-9e85-612744af15a8",
-   "metadata": {
-    "tags": []
-   },
-   "outputs": [],
-   "source": [
-    "j = GenericTinyJob(pr, 'min')"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 22,
-   "id": "1137a899-b00b-4ce4-92df-23a4bbcf7aa8",
+   "execution_count": 10,
+   "id": "7b807119-da8d-475c-9aa8-c8e8c9afa115",
    "metadata": {
     "tags": []
    },
    "outputs": [],
    "source": [
-    "j.task_class = AseMinimizeTask"
+    "j = pr.create.job.AseMinimize('min')"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 23,
+   "execution_count": 11,
    "id": "3a8cda32-df2e-4884-8cfd-84e438c5be69",
    "metadata": {
     "tags": []
    },
    "outputs": [],
    "source": [
-    "j.input.structure = Atoms(symbols=['Fe', 'Fe'], positions=[[0,0,0], [0,0, .75]], cell=[10,10,10])\n",
+    "j.input.structure = pr.create.structure.atoms(\n",
+    "    symbols=['Fe', 'Fe'], \n",
+    "    positions=[[0,0,0], [0,0, .75]], \n",
+    "    cell=[10,10,10]\n",
+    ").to_ase()\n",
     "j.input.structure.rattle(1e-3)\n",
     "j.input.calculator = MorsePotential()"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 24,
+   "execution_count": 12,
    "id": "d4b81c3f-4667-4b99-a2b3-08c7ee7e2c82",
    "metadata": {
     "tags": []
@@ -354,13 +252,14 @@
    "outputs": [],
    "source": [
     "j.input.lbfgs(damping=.25)\n",
+    "j.input.ionic_force_tolerance = 1e-3\n",
     "j.input.max_steps = 100\n",
     "j.input.output_steps = 10"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 25,
+   "execution_count": 13,
    "id": "e7494fee-d565-45e3-a819-c77ab0d2c7f6",
    "metadata": {
     "scrolled": true,
@@ -376,14 +275,16 @@
     }
    ],
    "source": [
-    "exe = j.run(how='process')\n",
+    "exe = j.run(\n",
+    "    executor=pr.create.executor.process(4)\n",
+    ")\n",
     "if exe is not None:\n",
     "    exe.wait()"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 26,
+   "execution_count": 14,
    "id": "7c16a615-0913-4880-9694-2c125285babc",
    "metadata": {
     "tags": []
@@ -429,7 +330,7 @@
        "      md\n",
        "      1\n",
        "      1\n",
-       "      4\n",
+       "      8\n",
        "      /home/poul/pyiron/contrib/notebooks/tinybase/t...\n",
        "      finished\n",
        "      AseMDTask\n",
@@ -439,9 +340,9 @@
        "      5\n",
        "      pyiron\n",
        "      min\n",
-       "      2\n",
+       "      3\n",
        "      1\n",
-       "      5\n",
+       "      9\n",
        "      /home/poul/pyiron/contrib/notebooks/tinybase/t...\n",
        "      finished\n",
        "      AseMinimizeTask\n",
@@ -451,9 +352,9 @@
        "      6\n",
        "      pyiron\n",
        "      murn\n",
-       "      3\n",
+       "      2\n",
        "      1\n",
-       "      6\n",
+       "      11\n",
        "      /home/poul/pyiron/contrib/notebooks/tinybase/t...\n",
        "      finished\n",
        "      MurnaghanTask\n",
@@ -464,9 +365,9 @@
       ],
       "text/plain": [
        "   id username  name  jobtype_id  project_id  status_id  \\\n",
-       "0   4   pyiron    md           1           1          4   \n",
-       "1   5   pyiron   min           2           1          5   \n",
-       "2   6   pyiron  murn           3           1          6   \n",
+       "0   4   pyiron    md           1           1          8   \n",
+       "1   5   pyiron   min           3           1          9   \n",
+       "2   6   pyiron  murn           2           1         11   \n",
        "\n",
        "                                            location    status  \\\n",
        "0  /home/poul/pyiron/contrib/notebooks/tinybase/t...  finished   \n",
@@ -479,7 +380,7 @@
        "2    MurnaghanTask  "
       ]
      },
-     "execution_count": 26,
+     "execution_count": 14,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -490,7 +391,30 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 27,
+   "execution_count": 15,
+   "id": "59ea5510-b6ce-4317-90c3-4af77db3d59a",
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [
+    {
+     "data": {
+      "image/png": "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\n",
+      "text/plain": [
+       "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "j.output.plot_energies()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, "id": "3fb09d42-f800-46ee-9919-83180863e1ee", "metadata": { "tags": [] @@ -499,12 +423,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "d3db4f34e6854a4f9c2c8d065ce81347", + "model_id": "fe8ba3dea3e74cf09a80af51a1a8a727", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "NGLWidget(max_frame=11)" + "NGLWidget(max_frame=5)" ] }, "metadata": {}, @@ -517,23 +441,43 @@ }, { "cell_type": "markdown", - "id": "023fbf27-f75c-4177-b35a-a18c33cc2f87", + "id": "858edf07-75ee-4a40-8cff-c26f34d555f5", "metadata": {}, "source": [ - "Escape hatch to old HDF output." + "### Loading from job id or name works" ] }, { - "cell_type": "markdown", - "id": "858edf07-75ee-4a40-8cff-c26f34d555f5", - "metadata": {}, + "cell_type": "code", + "execution_count": 17, + "id": "d32508b9-2854-4076-9109-08ede1b52dc2", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[11.28814567708869,\n", + " -0.8747049787590951,\n", + " -0.9988764739052705,\n", + " -0.9999959367316252,\n", + " -0.9999999870081488,\n", + " -0.999999995888409]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "### Loading from job id or name works" + "j.output.pot_energies" ] }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 18, "id": "db691097-72c6-45a4-89b1-6ec16018c8b8", "metadata": { "tags": [] @@ -541,7 +485,7 @@ "outputs": [ { "data": { - "image/png": "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PaW77rrL6dJSVvS3ZbDfnaNHjzpRUtIZ9+EjyjCMfs9N0xxwDCO3bNkyffzxx9q9e7fTpaSUuro6LV++XG+88YYyMjKcLieldXV1ac6cOVq7dq0k6corr9Snn36qDRs2ED7isHXrVr388svasmWLZs6cqQMHDmjFihUqKSnR4sWLnS4vpfG7M7RxHz4mTpwoj8czYJWjoaFhQCrFyDz88MPavn27du3apbKyMqfLSSn79u1TQ0ODZs+eHTsWiUS0a9cu1dTUKBQKyePxOFhh6iguLtYll1zS79iMGTP02muvOVRRavrZz36mVatW6c4775QkXXrppTp69Kiqq6sJH6NUVFQkqXsFpLi4OHac351e477nw+fzafbs2aqtre13vLa2VnPnznWoqtRkmqaWLVum119/XTt27NC0adOcLinlfPe739Unn3yiAwcOxB5z5szRPffcowMHDhA84jBv3rwBW70PHz6sqVOnOlRRamptbVVaWv+fAo/Hw1bbMZg2bZqKior6/e6Ew2Ht3LmT350e437lQ5Kqqqr0wx/+UHPmzNG1116rjRs36tixY1qyZInTpaWUpUuXasuWLfrDH/6gnJyc2GpSIBBQZmamw9WlhpycnAE9MtnZ2SosLKR3Jk4//elPNXfuXK1du1bf//739cEHH2jjxo3auHGj06WllEWLFumpp57SlClTNHPmTH344Yd67rnn9OCDDzpdWlJraWnRF198EXt+5MgRHThwQAUFBZoyZYpWrFihtWvXqqKiQhUVFVq7dq2ysrJ09913O1h1EnF2s03i/Mu//Is5depU0+fzmd/+9rfZHjoKkgZ9bNq0yenSUhpbbUfvP/7jP8xZs2aZfr/fnD59urlx40anS0o5wWDQXL58uTllyhQzIyPDvOiii8zHHnvMDIVCTpeW1N56661B/3m4ePFi0zS7t9s+8cQTZlFRken3+83rrrvO/OSTT5wtOokYpmmaDuUeAADgQuO+5wMAACQXwgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEgowgcAAEio/w9hHopmxnvDfAAAAABJRU5ErkJggg==\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -556,19 +500,19 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 19, "id": "23ce6822-b38b-41f3-9269-109dbb152ecf", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "759d4d0f319d4ffaa54b959293166c9b", + "model_id": "717e3bf94e2a46c39a07430192eb21fc", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "NGLWidget(max_frame=11)" + "NGLWidget(max_frame=5)" ] }, "metadata": {}, @@ -589,68 +533,50 @@ }, { "cell_type": "code", - "execution_count": 41, - "id": "d0f62439-3492-4392-ac2a-2b2545b85527", - "metadata": {}, - "outputs": [], - "source": [ - "murn = GenericTinyJob(pr, 'murn')" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "5c9ab533-cf97-49a1-8c4b-0e5e2f9758c2", - "metadata": {}, + "execution_count": 20, + "id": "654ce992-b73f-42e3-a32e-2e0dafa7c952", + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "murn.task_class = MurnaghanTask" + "murn = pr.create.job.Murnaghan('murn')" ] }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 21, "id": "253237f0-b338-470c-bc54-3c7400a757b7", "metadata": {}, "outputs": [], "source": [ - "murn.input.task = AseStaticTask()\n", + "murn.input.task = pr.create.task.AseStatic()\n", "murn.input.task.input.calculator = MorsePotential()" ] }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 22, "id": "c801093b-499e-48a7-8444-77602ed88a96", "metadata": {}, "outputs": [], "source": [ - "murn.input.structure = bulk(\"Fe\", a=1.2)" + "murn.input.structure = pr.create.structure.bulk(\"Fe\", a=1.2).to_ase()" ] }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 23, "id": "1e30b36e-11e6-47d1-836e-cffea7b73cdd", "metadata": {}, "outputs": [], "source": [ - "murn.input.set_strain_range(.5, 500)" + "murn.input.set_strain_range(.5, 1000)" ] }, { "cell_type": "code", - "execution_count": 46, - "id": "9920e4b7-8395-4fb9-96c3-792b044c4e3a", - "metadata": {}, - "outputs": [], - "source": [ - "murn.input.child_executor = ProcessExecutor" - ] - }, - { - "cell_type": "code", - "execution_count": 47, + "execution_count": 24, "id": "18b5305a-8950-44af-bc2e-c9734b059713", "metadata": {}, "outputs": [ @@ -658,23 +584,36 @@ "name": "stderr", "output_type": "stream", "text": [ - "DEBUG:h5py._conv:Creating converter from 5 to 3\n" + "INFO:root:Job already finished!\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 1.5 ms, sys: 727 µs, total: 2.23 ms\n", + "Wall time: 2.08 ms\n" ] } ], "source": [ - "exe = murn.run()" + "%%time\n", + "exe = murn.run(\n", + " executor=pr.create.executor.process(4)\n", + ")\n", + "if exe is not None:\n", + " exe.wait()" ] }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 25, "id": "836bb2ec-4295-4a3c-b976-7a35d04aad36", "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -699,7 +638,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 26, "id": "79a2bb61-0a5e-4a3a-b195-46d027738a0e", "metadata": {}, "outputs": [], @@ -709,7 +648,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 27, "id": "b4e6e0c9-a2c6-40ab-884e-a46e16c37b04", "metadata": {}, "outputs": [ @@ -747,7 +686,7 @@ "Index: []" ] }, - "execution_count": 50, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -758,7 +697,30 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 28, + "id": "c81e5148-3da3-428d-bf01-4608f7fdb978", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pr.exists_storage('murn')" + ] + }, + { + "cell_type": "code", + "execution_count": 29, "id": "cef7c46f-551f-401e-96c2-214628e23967", "metadata": {}, "outputs": [ @@ -774,20 +736,20 @@ } ], "source": [ - "murn = GenericTinyJob(pr, 'murn')\n", - "murn.task_class = MurnaghanTask\n", - "murn.input.task = AseStaticTask()\n", + "murn = pr.create.job.Murnaghan(\"murn\")\n", + "murn.input.task = pr.create.task.AseStatic()\n", "murn.input.task.input.calculator = MorsePotential()\n", - "murn.input.structure = bulk(\"Fe\", a=1.2)\n", + "murn.input.structure = pr.create.structure.bulk(\"Fe\", a=1.2).to_ase()\n", "murn.input.set_strain_range(.5, 500)\n", - "murn.input.child_executor = ProcessExecutor\n", - "murn.run()\n", + "exe = murn.run(executor='process')\n", + "if exe is not None:\n", + " exe.wait()\n", "murn.output.plot()" ] }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 30, "id": "e8a7ee30-d7a6-46fc-bf98-1b52c981470f", "metadata": {}, "outputs": [ @@ -848,7 +810,7 @@ "0 MurnaghanTask " ] }, - "execution_count": 52, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -859,36 +821,42 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 31, "id": "30871447-3e20-46ee-a58e-853d4f4cb5d9", "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "DEBUG:pyiron_log:Not supported parameter used!\n" + ] + }, { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 54, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "j = GenericTinyJob(pr, 'md')\n", - "j.task_class = AseMDTask\n", - "j.input.structure = bulk('Fe', a=1.2, cubic=True).repeat(2)\n", + "j = pr.create.job.AseMD('md')\n", + "j.input.structure = pr.create.structure.bulk('Fe', a=1.2, cubic=True).repeat(2).to_ase()\n", "j.input.calculator = MorsePotential()\n", "j.input.steps = 100\n", "j.input.timestep = 3.0\n", "j.input.temperature = 600.0\n", "j.input.output_steps = 20\n", - "j.run(how='background')" + "j.run(executor='background')" ] }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 32, "id": "e63d43c1-341f-4ec0-b0cf-9cd5ead51926", "metadata": {}, "outputs": [ @@ -963,7 +931,7 @@ "1 AseMDTask " ] }, - "execution_count": 55, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -982,25 +950,25 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 33, "id": "80da39e2-76d1-42e6-977f-241d2683188d", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 57, - "metadata": {}, - "output_type": "execute_result" + "ename": "NameError", + "evalue": "name 'bulk' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[33], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m sub \u001b[38;5;241m=\u001b[39m pr\u001b[38;5;241m.\u001b[39mopen_location(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m/foo\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 2\u001b[0m j \u001b[38;5;241m=\u001b[39m sub\u001b[38;5;241m.\u001b[39mcreate\u001b[38;5;241m.\u001b[39mjob\u001b[38;5;241m.\u001b[39mAseMD(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mmd\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[0;32m----> 3\u001b[0m j\u001b[38;5;241m.\u001b[39minput\u001b[38;5;241m.\u001b[39mstructure \u001b[38;5;241m=\u001b[39m \u001b[43mbulk\u001b[49m(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mFe\u001b[39m\u001b[38;5;124m'\u001b[39m, a\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1.2\u001b[39m, cubic\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\u001b[38;5;241m.\u001b[39mrepeat(\u001b[38;5;241m2\u001b[39m)\n\u001b[1;32m 4\u001b[0m j\u001b[38;5;241m.\u001b[39minput\u001b[38;5;241m.\u001b[39mcalculator \u001b[38;5;241m=\u001b[39m MorsePotential()\n\u001b[1;32m 5\u001b[0m j\u001b[38;5;241m.\u001b[39minput\u001b[38;5;241m.\u001b[39msteps \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m100\u001b[39m\n", + "\u001b[0;31mNameError\u001b[0m: name 'bulk' is not defined" + ] } ], "source": [ "sub = pr.open_location(\"/foo\")\n", - "j = GenericTinyJob(sub, 'md')\n", - "j.task_class = AseMDTask\n", + "j = sub.create.job.AseMD('md')\n", "j.input.structure = bulk('Fe', a=1.2, cubic=True).repeat(2)\n", "j.input.calculator = MorsePotential()\n", "j.input.steps = 100\n", @@ -1012,100 +980,10 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": null, "id": "a567f96a-cbb3-4d2d-95d1-6dcecee7ddb8", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
idusernamenamejobtype_idproject_idstatus_idlocationstatustype
01pyironmurn111/finishedMurnaghanTask
12pyironmd212/finishedAseMDTask
23pyironmd223/foorunningAseMDTask
\n", - "
" - ], - "text/plain": [ - " id username name jobtype_id project_id status_id location status \\\n", - "0 1 pyiron murn 1 1 1 / finished \n", - "1 2 pyiron md 2 1 2 / finished \n", - "2 3 pyiron md 2 2 3 /foo running \n", - "\n", - " type \n", - "0 MurnaghanTask \n", - "1 AseMDTask \n", - "2 AseMDTask " - ] - }, - "execution_count": 58, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "pr.job_table()" ] @@ -1120,7 +998,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": null, "id": "a6fefefb-b09c-4cee-b632-29f88bccfeee", "metadata": {}, "outputs": [], @@ -1130,21 +1008,10 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": null, "id": "3419f273-b94b-48cf-9373-7441baec2353", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "DatabaseEntry(name='murn', username='pyiron', project='/', status='finished', jobtype='MurnaghanTask')" - ] - }, - "execution_count": 60, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "db.get_item(1)" ] @@ -1159,7 +1026,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": null, "id": "cf75b2e8-ec15-4846-bda4-9b6661e8b5fa", "metadata": {}, "outputs": [], @@ -1169,7 +1036,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": null, "id": "1b23728b-1050-47a4-bda0-bd5967ceed2e", "metadata": {}, "outputs": [], @@ -1179,7 +1046,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": null, "id": "4cd73be9-2c6d-4501-8a6c-0afc6083a4b9", "metadata": {}, "outputs": [], @@ -1189,7 +1056,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": null, "id": "e9d9a3e2-5c86-46b4-b6d0-bec3a9f448b0", "metadata": {}, "outputs": [], @@ -1199,107 +1066,52 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": null, "id": "99059ff6-18bd-40b9-85fc-d76e1828f7ac", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 65, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "s.query(Job.id).where(Job.name == \"min\", ).all()" ] }, { "cell_type": "code", - "execution_count": 66, + "execution_count": null, "id": "cb4bb9fe-13bf-4736-aa68-662b980d4f00", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[(2, 'finished')]" - ] - }, - "execution_count": 66, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "s.query(JobStatus.__table__).select_from(Job).where(Job.id == 2, Job.status_id == JobStatus.id).all()" ] }, { "cell_type": "code", - "execution_count": 67, + "execution_count": null, "id": "4641b048-b7c7-46a2-b67c-835c08916cb0", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[(1, 'finished'), (2, 'finished'), (3, 'finished')]" - ] - }, - "execution_count": 67, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "s.query(JobStatus.__table__).all()" ] }, { "cell_type": "code", - "execution_count": 68, + "execution_count": null, "id": "94364e54-b980-48cc-995b-daf977437b1b", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[(1, '/'), (2, '/foo')]" - ] - }, - "execution_count": 68, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "s.query(DProject.__table__).all()" ] }, { "cell_type": "code", - "execution_count": 69, + "execution_count": null, "id": "f32c43d5-19c8-4505-bf5c-9ac32bca51d0", "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "text/plain": [ - "[(1, 'MurnaghanTask'), (2, 'AseMDTask')]" - ] - }, - "execution_count": 69, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "s.query(JobType.__table__).all()" ] diff --git a/pyiron_contrib/tinybase/creator.py b/pyiron_contrib/tinybase/creator.py new file mode 100644 index 000000000..9f7981603 --- /dev/null +++ b/pyiron_contrib/tinybase/creator.py @@ -0,0 +1,250 @@ +""" +Creators of various things. + +They serve to give easy access to various classes, so that users do not have to +manually import them. + +Rough spec: + + 1. RootCreator is merely the entry point and delegates to sub creators + based on name + 2. Sub creators should be type homogenous or related by functionality, i.e. + one of jobs, one for tasks, one for structures, etc. + 3. Each project gets its own RootCreator, but should keep that instance + alive so that sub creators may cache certain things +""" + +import abc +import importlib +from typing import Union +from functools import wraps +from os import sched_getaffinity + +import pyiron_contrib.tinybase.job +from pyiron_contrib.tinybase.executor import ( + Executor, + ProcessExecutor, + BackgroundExecutor, + DaskExecutor +) +from pyiron_atomistics import ase_to_pyiron, Atoms + +import ase.build + +class Creator(abc.ABC): + + @abc.abstractmethod + def __init__(self, project, config): + pass + + #TODO add all methods + +def load_class(class_path): + module, klass = class_path.rsplit(".", maxsplit=1) + try: + return getattr(importlib.import_module(module), klass) + except ImportError as e: + raise ValueError(f"Importing task class '{class_path}' failed: {e.args}!") from None + +class JobCreator(Creator): + + def __init__(self, project, job_dict): + self._project = project + self._jobs = job_dict.copy() + + def __dir__(self): + return tuple(self._jobs.keys()) + + def __getattr__(self, task_type): + if task_type not in self._jobs: + raise ValueError(f"Unknown task type {task_type}!") + task = self._jobs[task_type] + if isinstance(task, str): + task = self._jobs[task_type] = load_class(task) + + def create( + name: str, + delete_existing_job: bool = False, + delete_aborted_job: bool = False + ): + """ + Create or load a new job. + + Args: + name (str): name of the job + delete_existing_job (bool): if a job of this name and type + exists, delete it first + delete_existing_job (bool): if a job if this name and type + exists and its status is aborted, delete it first + """ + task = self._jobs[task_type] + if self._project.exists_storage(name): + try: + job = self._project.create_storage(name).to_object() + except Exception as e: + raise RuntimeError( + f"Failed to reload run job from storage: {e}" + ) from None + if not isinstance(job, pyiron_contrib.tinybase.job.TinyJob): + raise ValueError(f"Storage with name {name} exists, but is not a job! {job}") + if not isinstance(job.task, task): + raise ValueError(f"Job with given name already exists, but is of different type!") + return job + else: + return pyiron_contrib.tinybase.job.TinyJob( + task(), self._project, name + ) + + return create + + def register(self, task: Union["AbstractTask", str], name: str): + """ + Register a new task. + + Args: + task (AbstractTask): new task to register + + Returns: + AbstractTask: the newly registered task + """ + if name is None: + name = type(task).__name__ + if name in self._jobs: + if task != self._jobs[name]: + raise ValueError("Refusing to register task: different task of the same name already exists!") + else: + return self._jobs[name] + self._jobs[name] = task + return task + +class TaskCreator(Creator): + + def __init__(self, project, config): + self._tasks = config.copy() + + def __dir__(self): + return tuple(self._tasks.keys()) + + def __getattr__(self, name): + task = self._tasks[name] + if isinstance(task, str): + task = self._tasks[name] = load_class(task) + return task + + def register(self, task: Union["AbstractTask", str], name: str): + """ + Register a new task. + + Args: + task (AbstractTask): new task to register + + Returns: + AbstractTask: the newly registered task + """ + if name is None: + name = type(task).__name__ + if name in self._jobs: + if task != self._jobs[name]: + raise ValueError("Refusing to register task: different task of the same name already exists!") + else: + return self._jobs[name] + self._jobs[name] = task + return task + +class StructureCreator(Creator): + + def __init__(self, project, config): + pass + + @wraps(ase.build.bulk) + def bulk(self, *args, **kwargs): + return ase_to_pyiron(ase.build.bulk(*args, **kwargs)) + + @wraps(Atoms) + def atoms(self, *args, **kwargs): + return Atoms(*args, **kwargs) + + + +class ExecutorCreator(Creator): + + _DEFAULT_CPUS = min(int(0.5 * len(sched_getaffinity(0))), 8) + + def __init__(self, project, config): + self._most_recent = None + + def most_recent(self): + if self._most_recent is None: + self._most_recent = Executor() + return self._most_recent + + def _save(func): + @wraps(func) + def f(self, *args, **kwargs): + self._most_recent = func(self, *args, **kwargs) + return self._most_recent + return f + + @wraps(ProcessExecutor) + @_save + def process(self, max_processes=_DEFAULT_CPUS): + return ProcessExecutor(max_processes=max_processes) + + @wraps(BackgroundExecutor) + @_save + def background(self, max_threads=4): + return BackgroundExecutor(max_threads=max_threads) + + @wraps(DaskExecutor.from_localcluster) + @_save + def dask_local(self, max_workers=_DEFAULT_CPUS, **kwargs): + return DaskExecutor.from_localcluster(max_workers=max_workers, **kwargs) + + @wraps(DaskExecutor.from_cluster) + @_save + def dask_cluster(self, cluster): + return DaskExecutor.from_cluster(cluster) + + del _save + +class RootCreator: + + def __init__(self, project, config): + self._project = project + self._subcreators = {} + for name, (subcreator, subconfig) in config.items(): + self._subcreators[name] = subcreator(project, subconfig) + + def __dir__(self): + return tuple(self._subcreators.keys()) + + def register(self, name, subcreator, force=False): + """ + Add a new creator. + """ + if name not in self._subcreators or force: + self._subcreators[name] = subcreator + else: + raise ValueError(f"Already registered a creator under name {name}!") + + def __getattr__(self, name): + try: + return self._subcreators[name] + except KeyError: + raise AttributeError(f"No creator of name {name} registered!") + +TASK_CONFIG = { + "AseStatic": "pyiron_contrib.tinybase.ase.AseStaticTask", + "AseMinimize": "pyiron_contrib.tinybase.ase.AseMinimizeTask", + "AseMD": "pyiron_contrib.tinybase.ase.AseMDTask", + "Murnaghan": "pyiron_contrib.tinybase.murn.MurnaghanTask", +} + + + +CREATOR_CONFIG = { + "job": (JobCreator, TASK_CONFIG), + "task": (TaskCreator, TASK_CONFIG), + "structure": (StructureCreator, {}), + "executor": (ExecutorCreator, {}) +} diff --git a/pyiron_contrib/tinybase/job.py b/pyiron_contrib/tinybase/job.py index 204231d69..9422285cb 100644 --- a/pyiron_contrib/tinybase/job.py +++ b/pyiron_contrib/tinybase/job.py @@ -1,6 +1,6 @@ import abc import logging -from typing import Optional +from typing import Optional, Union from pyiron_contrib.tinybase.task import AbstractTask from pyiron_contrib.tinybase.storage import ( @@ -11,6 +11,7 @@ ) from pyiron_contrib.tinybase.executor import ( Executor, + ExecutionContext, BackgroundExecutor, ProcessExecutor, ) @@ -19,7 +20,7 @@ from pyiron_base.state import state -class TinyJob(Storable, abc.ABC): +class TinyJob(Storable): """ A tiny job unifies an executor, a task and its output. @@ -43,22 +44,12 @@ class TinyJob(Storable, abc.ABC): You can use :class:`.GenericTinyJob` to dynamically specify which task the job should execute. """ - _executors = { - "foreground": Executor(), - "background": BackgroundExecutor(max_threads=4), - "process": ProcessExecutor(max_processes=4), - } - - def __init__(self, project: ProjectInterface, job_name: str): + def __init__(self, task: AbstractTask, project: ProjectInterface, job_name: str): """ Create a new job. - If the given `job_name` is already present in the `project` it is reloaded. No checks are performed that the - task type of the already present job and the current one match. This is also not always necessary, e.g. when - reloading a :class:`.GenericTinyJob` it will automatically read the - correct task from storage. - Args: + task (:class:`.AbstractTask`): the underlying task to run project (:class:`.ProjectInterface`): the project the job should live in job_name (str): the name of the job. """ @@ -66,19 +57,11 @@ def __init__(self, project: ProjectInterface, job_name: str): project = ProjectAdapter(project) self._project = project self._name = job_name - self._task = None + self._task = task self._output = None self._storage = None self._executor = None self._id = None - # FIXME: this should go into the job creation logic on the project - if project.exists_storage(job_name): - try: - self.load() - except Exception as e: - raise RuntimeError( - f"Failed to reload run job from storage: {e}" - ) from None @property def name(self): @@ -95,19 +78,8 @@ def _update_id(self): def project(self): return self._project - @abc.abstractmethod - def _get_task(self) -> AbstractTask: - """ - Return an instance of the :class:`.AbstractTask`. - - The value return from here is saved automatically in :prop:`.task`. - """ - pass - @property def task(self): - if self._task is None: - self._task = self._get_task() return self._task @property @@ -146,14 +118,16 @@ def _setup_executor_callbacks(self): self._executor._run_machine.observe("collect", self._update_status("collect")) self._executor._run_machine.observe("finished", self._update_status("finished")) - def run(self, how="foreground") -> Optional[Executor]: + def run(self, executor: Union[Executor, str, None] = None) -> Optional[ExecutionContext]: """ Start execution of the job. If the job already has a database id and is not in "ready" state, do nothing. Args: - how (string): specifies which executor to use + executor (:class:`~.Executor`, str): specifies which executor to + use, if `str` must be a method name of :class:`.ExecutorCreator`; + if not given use the last created executor Returns: :class:`.Executor`: the executor that is running the task or nothing. @@ -162,7 +136,11 @@ def run(self, how="foreground") -> Optional[Executor]: self._id is None or self.project.database.get_item(self.id).status == "ready" ): - exe = self._executor = self._executors[how].submit(tasks=[self.task]) + if executor is None: + executor = 'most_recent' + if isinstance(executor, str): + executor = getattr(self.project.create.executor, executor)() + exe = self._executor = executor.submit(tasks=[self.task]) self._setup_executor_callbacks() exe.run() return exe @@ -225,39 +203,7 @@ def load(self, storage: GenericStorage = None): @classmethod def _restore(cls, storage, version): - job = cls(project=storage.project, job_name=storage.name) + task = pickle_load(storage["task"]) + job = cls(task=task, project=storage.project, job_name=storage.name) job.load(storage=storage) return job - - -# I'm not perfectly happy with this, but two thoughts led to this class: -# 1. I want to be able to set any task on a tiny job without subclassing, to make the prototyping new jobs in the notebook -# easy -# 2. I do *not* want people to accidently change the task instance/class while the job is running -class GenericTinyJob(TinyJob): - """ - A generic tiny job is a tiny job that allows to set any task class after instantiating it. - - Set a task class via :attr:`.task_class`, e.g. - - >>> from somewhere import MyTask - >>> job = GenericTinyJob(Project(...), "myjob") - >>> job.task_class = MyTask - >>> isinstance(job.input, type(MyTask.input)) - True - """ - - def __init__(self, project, job_name): - super().__init__(project=project, job_name=job_name) - self._task_class = None - - @property - def task_class(self): - return self._task_class - - @task_class.setter - def task_class(self, cls): - self._task_class = cls - - def _get_task(self): - return self.task_class() diff --git a/pyiron_contrib/tinybase/project.py b/pyiron_contrib/tinybase/project.py index 0b354bb16..b1a360dec 100644 --- a/pyiron_contrib/tinybase/project.py +++ b/pyiron_contrib/tinybase/project.py @@ -36,6 +36,15 @@ def _get_database(self) -> GenericDatabase: def database(self): return self._get_database() + @property + def create(self): + if not hasattr(self, '_creator'): + from pyiron_contrib.tinybase.creator import RootCreator, CREATOR_CONFIG + self._creator = RootCreator( + self, CREATOR_CONFIG + ) + return self._creator + def load(self, name_or_id: int | str) -> "TinyJob": # if a name is given, job must be in the current project if isinstance(name_or_id, str): From 5effad94f89c0a48411d6c2474450cfce8922e1c Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Thu, 22 Jun 2023 17:44:04 +0200 Subject: [PATCH 176/756] Implement delete_*_job on JobCreator Also adds some docstring and more explicit error handling to the relevant methods of the database and project interface. --- notebooks/tinybase/TinyJob.ipynb | 56 +++++++---------------------- pyiron_contrib/tinybase/creator.py | 32 ++++++++--------- pyiron_contrib/tinybase/database.py | 53 ++++++++++++++++++++------- pyiron_contrib/tinybase/job.py | 7 ++++ pyiron_contrib/tinybase/project.py | 28 +++++++++++++-- 5 files changed, 100 insertions(+), 76 deletions(-) diff --git a/notebooks/tinybase/TinyJob.ipynb b/notebooks/tinybase/TinyJob.ipynb index e0a62b49c..ae17ec530 100644 --- a/notebooks/tinybase/TinyJob.ipynb +++ b/notebooks/tinybase/TinyJob.ipynb @@ -86,52 +86,19 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 11, "id": "e31baebd-b2c8-4343-90ad-92d9128d1496", "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "d365d332f5f44babad1dc4f275dc3284",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    }
-   ],
+   "outputs": [],
    "source": [
     "j = pr.create.job.AseMD('md')"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 17,
    "id": "18e6de26-308c-46ae-9672-b2db43447ea5",
    "metadata": {
     "tags": []
@@ -144,7 +111,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 18,
    "id": "72848cd2-fd51-4ad8-b56e-ca686074bb26",
    "metadata": {
     "tags": []
@@ -159,18 +126,21 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 19,
    "id": "56c0e73a-c42b-4814-a25a-e6974fea3d00",
    "metadata": {
     "tags": []
    },
    "outputs": [
     {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "INFO:root:Job already finished!\n"
-     ]
+     "data": {
+      "text/plain": [
+       ""
+      ]
+     },
+     "execution_count": 19,
+     "metadata": {},
+     "output_type": "execute_result"
     }
    ],
    "source": [
diff --git a/pyiron_contrib/tinybase/creator.py b/pyiron_contrib/tinybase/creator.py
index 9f7981603..3b495c3f8 100644
--- a/pyiron_contrib/tinybase/creator.py
+++ b/pyiron_contrib/tinybase/creator.py
@@ -21,6 +21,7 @@
 from os import sched_getaffinity
 
 import pyiron_contrib.tinybase.job
+from pyiron_contrib.tinybase.project import JobNotFoundError
 from pyiron_contrib.tinybase.executor import (
         Executor,
         ProcessExecutor,
@@ -77,23 +78,20 @@ def create(
                 delete_existing_job (bool): if a job if this name and type
                     exists and its status is aborted, delete it first
             """
-            task = self._jobs[task_type]
-            if self._project.exists_storage(name):
-                try:
-                    job = self._project.create_storage(name).to_object()
-                except Exception as e:
-                    raise RuntimeError(
-                        f"Failed to reload run job from storage: {e}"
-                    ) from None
-                if not isinstance(job, pyiron_contrib.tinybase.job.TinyJob):
-                    raise ValueError(f"Storage with name {name} exists, but is not a job! {job}")
-                if not isinstance(job.task, task):
-                    raise ValueError(f"Job with given name already exists, but is of different type!")
-                return job
-            else:
-                return pyiron_contrib.tinybase.job.TinyJob(
-                    task(), self._project, name
-                )
+            try:
+                job = self._project.load(name)
+                if delete_existing_job \
+                        or (delete_aborted_job and job.status == "aborted"):
+                    job.remove()
+                else:
+                    if not isinstance(job.task, task):
+                        raise ValueError(f"Job with given name already exists, but is of different type!")
+                    return job
+            except JobNotFoundError:
+                pass
+            return pyiron_contrib.tinybase.job.TinyJob(
+                task(), self._project, name
+            )
 
         return create
 
diff --git a/pyiron_contrib/tinybase/database.py b/pyiron_contrib/tinybase/database.py
index a15df67a2..312eca762 100644
--- a/pyiron_contrib/tinybase/database.py
+++ b/pyiron_contrib/tinybase/database.py
@@ -60,7 +60,7 @@ class Job(Base):
 # TODO: this will be pyiron_base.IsDatabase
 class GenericDatabase(abc.ABC):
     """
-    Defines the database interface used by the :class:`.ProjectInterface`.
+    Defines the abstract database interface used by all databases.
     """
 
     @abc.abstractmethod
@@ -69,6 +69,18 @@ def add_item(self, entry: DatabaseEntry) -> int:
 
     @abc.abstractmethod
     def get_item(self, job_id: int) -> DatabaseEntry:
+        """
+        Return database entry of the specified job.
+
+        Args:
+            job_id (int): id of the job
+
+        Returns:
+            :class:`.DatabaseEntry`: database entry with the given id
+
+        Raises:
+            ValueError: if no job with the given id exists
+        """
         pass
 
     @abc.abstractmethod
@@ -175,19 +187,34 @@ def _row_to_entry(self, job_data):
         )
 
     def get_item(self, job_id: int) -> DatabaseEntry:
-        with Session(self.engine) as session:
-            job_data = (
-                session.query(
-                    Job.__table__, Project.location, JobStatus.status, JobType.type
+        """
+        Return database entry of the specified job.
+
+        Args:
+            job_id (int): id of the job
+
+        Returns:
+            :class:`.DatabaseEntry`: database entry with the given id
+
+        Raises:
+            ValueError: if no job with the given id exists
+        """
+        try:
+            with Session(self.engine) as session:
+                job_data = (
+                    session.query(
+                        Job.__table__, Project.location, JobStatus.status, JobType.type
+                    )
+                    .select_from(Job)
+                    .where(Job.id == job_id)
+                    .join(Project, Job.project_id == Project.id)
+                    .join(JobStatus, Job.status_id == JobStatus.id)
+                    .join(JobType, Job.jobtype_id == JobType.id)
+                    .one()
                 )
-                .select_from(Job)
-                .where(Job.id == job_id)
-                .join(Project, Job.project_id == Project.id)
-                .join(JobStatus, Job.status_id == JobStatus.id)
-                .join(JobType, Job.jobtype_id == JobType.id)
-                .one()
-            )
-            return self._row_to_entry(job_data)
+                return self._row_to_entry(job_data)
+        except NoResultFound:
+            raise ValueError(f"No job with id {job_id} found!") from None
 
     def get_item_id(self, job_name: str, project_id: int) -> Optional[int]:
         with Session(self.engine) as session:
diff --git a/pyiron_contrib/tinybase/job.py b/pyiron_contrib/tinybase/job.py
index 9422285cb..f63d6ec50 100644
--- a/pyiron_contrib/tinybase/job.py
+++ b/pyiron_contrib/tinybase/job.py
@@ -78,6 +78,13 @@ def _update_id(self):
     def project(self):
         return self._project
 
+    @property
+    def status(self):
+        try:
+            return self._project.database.get_item(self.id).status
+        except ValueError:
+            return "initialized"
+
     @property
     def task(self):
         return self._task
diff --git a/pyiron_contrib/tinybase/project.py b/pyiron_contrib/tinybase/project.py
index b1a360dec..5b2e0f414 100644
--- a/pyiron_contrib/tinybase/project.py
+++ b/pyiron_contrib/tinybase/project.py
@@ -1,5 +1,6 @@
 import abc
 import os.path
+from typing import Union
 
 from pyiron_base import Project, DataContainer
 from pyiron_contrib.tinybase.storage import (
@@ -9,6 +10,8 @@
 )
 from pyiron_contrib.tinybase.database import TinyDB, GenericDatabase
 
+class JobNotFoundError(Exception):
+    pass
 
 class ProjectInterface(abc.ABC):
     @classmethod
@@ -45,13 +48,32 @@ def create(self):
             )
         return self._creator
 
-    def load(self, name_or_id: int | str) -> "TinyJob":
-        # if a name is given, job must be in the current project
+    def load(self, name_or_id: Union[int, str]) -> "TinyJob":
+        """
+        Load a job from storage.
+
+        If the job name is given, it must be a child of this project and not
+        any of its sub projects.
+
+        Args:
+            name_or_id (int, str): either the job name or its id
+
+        Returns:
+            :class:`.TinyJob`: the loaded job
+
+        Raises:
+            :class:`.JobNotFoundError`: if no job of the given name or id exists
+        """
         if isinstance(name_or_id, str):
             pr = self
             name = name_or_id
+            if not pr.exists_storage(name):
+                raise JobNotFoundError(f"No job with name {name} found!")
         else:
-            entry = self.database.get_item(name_or_id)
+            try:
+                entry = self.database.get_item(name_or_id)
+            except ValueError as e:
+                raise JobNotFoundError(*e.args)
             pr = self.open_location(entry.project)
             name = entry.name
         return pr.create_storage(name).to_object()

From f5cfe2c1ab08a1d94e501a08b57ff783aba3d7fa Mon Sep 17 00:00:00 2001
From: pyiron-runner 
Date: Thu, 22 Jun 2023 15:45:39 +0000
Subject: [PATCH 177/756] Format black

---
 pyiron_contrib/tinybase/creator.py | 73 ++++++++++++++++--------------
 pyiron_contrib/tinybase/job.py     |  6 ++-
 pyiron_contrib/tinybase/project.py |  9 ++--
 pyiron_contrib/tinybase/task.py    |  7 ++-
 4 files changed, 55 insertions(+), 40 deletions(-)

diff --git a/pyiron_contrib/tinybase/creator.py b/pyiron_contrib/tinybase/creator.py
index 3b495c3f8..696129f5e 100644
--- a/pyiron_contrib/tinybase/creator.py
+++ b/pyiron_contrib/tinybase/creator.py
@@ -23,32 +23,35 @@
 import pyiron_contrib.tinybase.job
 from pyiron_contrib.tinybase.project import JobNotFoundError
 from pyiron_contrib.tinybase.executor import (
-        Executor,
-        ProcessExecutor,
-        BackgroundExecutor,
-        DaskExecutor
+    Executor,
+    ProcessExecutor,
+    BackgroundExecutor,
+    DaskExecutor,
 )
 from pyiron_atomistics import ase_to_pyiron, Atoms
 
 import ase.build
 
-class Creator(abc.ABC):
 
+class Creator(abc.ABC):
     @abc.abstractmethod
     def __init__(self, project, config):
         pass
 
-    #TODO add all methods
+    # TODO add all methods
+
 
 def load_class(class_path):
     module, klass = class_path.rsplit(".", maxsplit=1)
     try:
         return getattr(importlib.import_module(module), klass)
     except ImportError as e:
-        raise ValueError(f"Importing task class '{class_path}' failed: {e.args}!") from None
+        raise ValueError(
+            f"Importing task class '{class_path}' failed: {e.args}!"
+        ) from None
 
-class JobCreator(Creator):
 
+class JobCreator(Creator):
     def __init__(self, project, job_dict):
         self._project = project
         self._jobs = job_dict.copy()
@@ -64,9 +67,9 @@ def __getattr__(self, task_type):
             task = self._jobs[task_type] = load_class(task)
 
         def create(
-                name: str,
-                delete_existing_job: bool = False,
-                delete_aborted_job: bool = False
+            name: str,
+            delete_existing_job: bool = False,
+            delete_aborted_job: bool = False,
         ):
             """
             Create or load a new job.
@@ -80,18 +83,19 @@ def create(
             """
             try:
                 job = self._project.load(name)
-                if delete_existing_job \
-                        or (delete_aborted_job and job.status == "aborted"):
+                if delete_existing_job or (
+                    delete_aborted_job and job.status == "aborted"
+                ):
                     job.remove()
                 else:
                     if not isinstance(job.task, task):
-                        raise ValueError(f"Job with given name already exists, but is of different type!")
+                        raise ValueError(
+                            f"Job with given name already exists, but is of different type!"
+                        )
                     return job
             except JobNotFoundError:
                 pass
-            return pyiron_contrib.tinybase.job.TinyJob(
-                task(), self._project, name
-            )
+            return pyiron_contrib.tinybase.job.TinyJob(task(), self._project, name)
 
         return create
 
@@ -109,14 +113,16 @@ def register(self, task: Union["AbstractTask", str], name: str):
             name = type(task).__name__
         if name in self._jobs:
             if task != self._jobs[name]:
-                raise ValueError("Refusing to register task: different task of the same name already exists!")
+                raise ValueError(
+                    "Refusing to register task: different task of the same name already exists!"
+                )
             else:
                 return self._jobs[name]
         self._jobs[name] = task
         return task
 
-class TaskCreator(Creator):
 
+class TaskCreator(Creator):
     def __init__(self, project, config):
         self._tasks = config.copy()
 
@@ -143,14 +149,16 @@ def register(self, task: Union["AbstractTask", str], name: str):
             name = type(task).__name__
         if name in self._jobs:
             if task != self._jobs[name]:
-                raise ValueError("Refusing to register task: different task of the same name already exists!")
+                raise ValueError(
+                    "Refusing to register task: different task of the same name already exists!"
+                )
             else:
                 return self._jobs[name]
         self._jobs[name] = task
         return task
 
-class StructureCreator(Creator):
 
+class StructureCreator(Creator):
     def __init__(self, project, config):
         pass
 
@@ -163,9 +171,7 @@ def atoms(self, *args, **kwargs):
         return Atoms(*args, **kwargs)
 
 
-
 class ExecutorCreator(Creator):
-
     _DEFAULT_CPUS = min(int(0.5 * len(sched_getaffinity(0))), 8)
 
     def __init__(self, project, config):
@@ -181,6 +187,7 @@ def _save(func):
         def f(self, *args, **kwargs):
             self._most_recent = func(self, *args, **kwargs)
             return self._most_recent
+
         return f
 
     @wraps(ProcessExecutor)
@@ -205,8 +212,8 @@ def dask_cluster(self, cluster):
 
     del _save
 
-class RootCreator:
 
+class RootCreator:
     def __init__(self, project, config):
         self._project = project
         self._subcreators = {}
@@ -231,18 +238,18 @@ def __getattr__(self, name):
         except KeyError:
             raise AttributeError(f"No creator of name {name} registered!")
 
+
 TASK_CONFIG = {
-        "AseStatic": "pyiron_contrib.tinybase.ase.AseStaticTask",
-        "AseMinimize": "pyiron_contrib.tinybase.ase.AseMinimizeTask",
-        "AseMD": "pyiron_contrib.tinybase.ase.AseMDTask",
-        "Murnaghan": "pyiron_contrib.tinybase.murn.MurnaghanTask",
+    "AseStatic": "pyiron_contrib.tinybase.ase.AseStaticTask",
+    "AseMinimize": "pyiron_contrib.tinybase.ase.AseMinimizeTask",
+    "AseMD": "pyiron_contrib.tinybase.ase.AseMDTask",
+    "Murnaghan": "pyiron_contrib.tinybase.murn.MurnaghanTask",
 }
 
 
-
 CREATOR_CONFIG = {
-        "job": (JobCreator, TASK_CONFIG),
-        "task": (TaskCreator, TASK_CONFIG),
-        "structure": (StructureCreator, {}),
-        "executor": (ExecutorCreator, {})
+    "job": (JobCreator, TASK_CONFIG),
+    "task": (TaskCreator, TASK_CONFIG),
+    "structure": (StructureCreator, {}),
+    "executor": (ExecutorCreator, {}),
 }
diff --git a/pyiron_contrib/tinybase/job.py b/pyiron_contrib/tinybase/job.py
index f63d6ec50..0642ed141 100644
--- a/pyiron_contrib/tinybase/job.py
+++ b/pyiron_contrib/tinybase/job.py
@@ -125,7 +125,9 @@ def _setup_executor_callbacks(self):
         self._executor._run_machine.observe("collect", self._update_status("collect"))
         self._executor._run_machine.observe("finished", self._update_status("finished"))
 
-    def run(self, executor: Union[Executor, str, None] = None) -> Optional[ExecutionContext]:
+    def run(
+        self, executor: Union[Executor, str, None] = None
+    ) -> Optional[ExecutionContext]:
         """
         Start execution of the job.
 
@@ -144,7 +146,7 @@ def run(self, executor: Union[Executor, str, None] = None) -> Optional[Execution
             or self.project.database.get_item(self.id).status == "ready"
         ):
             if executor is None:
-                executor = 'most_recent'
+                executor = "most_recent"
             if isinstance(executor, str):
                 executor = getattr(self.project.create.executor, executor)()
             exe = self._executor = executor.submit(tasks=[self.task])
diff --git a/pyiron_contrib/tinybase/project.py b/pyiron_contrib/tinybase/project.py
index 5b2e0f414..4bade3d81 100644
--- a/pyiron_contrib/tinybase/project.py
+++ b/pyiron_contrib/tinybase/project.py
@@ -10,9 +10,11 @@
 )
 from pyiron_contrib.tinybase.database import TinyDB, GenericDatabase
 
+
 class JobNotFoundError(Exception):
     pass
 
+
 class ProjectInterface(abc.ABC):
     @classmethod
     @abc.abstractmethod
@@ -41,11 +43,10 @@ def database(self):
 
     @property
     def create(self):
-        if not hasattr(self, '_creator'):
+        if not hasattr(self, "_creator"):
             from pyiron_contrib.tinybase.creator import RootCreator, CREATOR_CONFIG
-            self._creator = RootCreator(
-                    self, CREATOR_CONFIG
-            )
+
+            self._creator = RootCreator(self, CREATOR_CONFIG)
         return self._creator
 
     def load(self, name_or_id: Union[int, str]) -> "TinyJob":
diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py
index 56b98fdc3..65df8250b 100644
--- a/pyiron_contrib/tinybase/task.py
+++ b/pyiron_contrib/tinybase/task.py
@@ -6,7 +6,12 @@
 from pyiron_base.interfaces.object import HasStorage
 
 from pyiron_contrib.tinybase.storage import Storable, pickle_dump, pickle_load
-from pyiron_contrib.tinybase.container import AbstractInput, AbstractOutput, StorageAttribute
+from pyiron_contrib.tinybase.container import (
+    AbstractInput,
+    AbstractOutput,
+    StorageAttribute,
+)
+
 
 class ReturnStatus:
     """

From b6d2cd3deee08b59281f198f7be287b1b03b0247 Mon Sep 17 00:00:00 2001
From: liamhuber 
Date: Thu, 22 Jun 2023 10:49:41 -0700
Subject: [PATCH 178/756] Add module summary

---
 pyiron_contrib/workflow/is_nodal.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py
index 915938cd8..4d5bec011 100644
--- a/pyiron_contrib/workflow/is_nodal.py
+++ b/pyiron_contrib/workflow/is_nodal.py
@@ -1,3 +1,8 @@
+"""
+A base class for objects that can form nodes in the graph representation of a
+computational workflow.
+"""
+
 from __future__ import annotations
 
 from abc import ABC, abstractmethod

From 04d748ee55e559a934d08289c987cd880c38cd6c Mon Sep 17 00:00:00 2001
From: liamhuber 
Date: Thu, 22 Jun 2023 11:26:08 -0700
Subject: [PATCH 179/756] Add docstrings

---
 pyiron_contrib/workflow/is_nodal.py | 63 ++++++++++++++++++++++++++++-
 1 file changed, 62 insertions(+), 1 deletion(-)

diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py
index 4d5bec011..266d54c31 100644
--- a/pyiron_contrib/workflow/is_nodal.py
+++ b/pyiron_contrib/workflow/is_nodal.py
@@ -18,10 +18,65 @@
 
 class IsNodal(ABC):
     """
-    A mixin class for classes that can represent nodes on a computation graph.
+    A mixin class for objects that can form nodes in the graph representation of a
+    computational workflow.
+
+    Nodal objects have `inputs` and `outputs` channels for passing data, and `signals`
+    channels for making callbacks on the class (input) and controlling execution flow
+    (output) when connected to other nodal objects.
+
+    Nodal objects can `run` to complete some computational task, or call a softer
+    `update` which will run the task only if it is `ready` -- i.e. it is not currently
+    running, has not previously tried to run and failed, and all of its inputs are ready
+    (i.e. populated with data that passes type requirements, if any).
+
+    Attributes:
+        connected (bool): Whether _any_ of the IO (including signals) are connected.
+        failed (bool): Whether the nodal object raised an error calling `run`. (Default
+            is False.)
+        fully_connected (bool): whether _all_ of the IO (including signals) are
+            connected.
+        inputs (pyiron_contrib.workflow.io.Inputs): **Abstract.** Children must define
+            a property returning an `Inputs` object.
+        label (str): A name for the nodal object.
+        output (pyiron_contrib.workflow.io.Outputs): **Abstract.** Children must define
+            a property returning an `Outputs` object.
+        ready (bool): Whether the inputs are all ready and the nodal object is neither
+            already running nor already failed.
+        running (bool): Whether the nodal object has called `run` and has not yet
+            received output from from this call. (Default is False.)
+        server (Optional[pyiron_base.jobs.job.extension.server.generic.Server]): A
+            server object for computing things somewhere else. Default (and currently
+            _only_) behaviour is to compute things on the main python process owning
+            the nodal object.
+        signals (pyiron_contrib.workflow.io.Signals): A container for input and output
+            signals, which are channels for controlling execution flow. By default, has
+            a `signals.inputs.run` channel which has a callback to the `run` method,
+            and `signals.outputs.ran` which should be called at when the `run` method
+            is finished (TODO: Don't leave this step up to child class developers!).
+            Additional signal channels in derived classes can be added to
+            `signals.inputs` and  `signals.outputs` after this mixin class is
+            initialized.
+
+    Methods:
+        disconnect: Remove all connections, including signals.
+        run: **Abstract.** Do the thing.
+        update: **Abstract.** Do the thing if you're ready and you run on updates.
+            TODO: Once `run_on_updates` is in this class, we can un-abstract this.
     """
 
     def __init__(self, label: str, *args, **kwargs):
+        """
+        A mixin class for objects that can form nodes in the graph representation of a
+        computational workflow.
+
+        Args:
+            label (str): A name for this nodal object.
+            *args: Arguments passed on with `super`.
+            **kwargs: Keyword arguments passed on with `super`.
+
+        TODO: Shouldn't `update_on_instantiation` and `run_on_updates` both live here??
+        """
         super().__init__(*args, **kwargs)
         self.label: str = label
         self.running = False
@@ -30,6 +85,8 @@ def __init__(self, label: str, *args, **kwargs):
         self._server: Server | None = (
             None  # Or "task_manager" or "executor" -- we'll see what's best
         )
+        # TODO: Move from a traditional "sever" to a tinybase "executor"
+        # TODO: Provide support for actually computing stuff with the server/executor
         self.signals = self._build_signal_channels()
 
     @property
@@ -54,6 +111,10 @@ def _build_signal_channels(self) -> Signals:
         signals = Signals()
         signals.input.run = InputSignal("run", self, self.run)
         signals.output.ran = OutputSignal("ran", self)
+        # TODO: Build `run` such that developers inheriting from this class don't need
+        #       to remember to invoke `self.signals.output.ran()`! Probably this will
+        #       involve pulling `run` up into this class and exposing `_on_run` as an
+        #       abstract method to developers (or a similar attack).
         return signals
 
     @property

From ec177f99e154ce311533f9d432de8809555e0f7a Mon Sep 17 00:00:00 2001
From: liamhuber 
Date: Thu, 22 Jun 2023 12:30:41 -0700
Subject: [PATCH 180/756] Fix base class docstring

---
 pyiron_contrib/workflow/io.py | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py
index 093f31053..c77dd871c 100644
--- a/pyiron_contrib/workflow/io.py
+++ b/pyiron_contrib/workflow/io.py
@@ -1,3 +1,7 @@
+"""
+Collections of channel objects.
+"""
+
 from __future__ import annotations
 
 from abc import ABC, abstractmethod
@@ -19,24 +23,24 @@
 class IO(HasToDict, ABC):
     """
     IO is a convenience layer for holding and accessing multiple input/output channels.
-    It allows key and dot-based access to the underlying channels based on their name.
+    It allows key and dot-based access to the underlying channels.
     Channels can also be iterated over, and there are a number of helper functions to
     alter the properties of or check the status of all the channels at once.
 
-    A new channel can be assigned as an attribute of an IO collection, as long as the
-    attribute name matches the channel's label and type (i.e. `OutputChannel` for
-    `Outputs` and `InputChannel` for `Inputs`).
-
-    New channels can also be added using the `add` method, which must be implemented in
-    child classes to add channels of the correct type.
+    A new channel can be assigned as an attribute of an IO collection, as long as it
+    matches the channel's type (e.g. `OutputChannel` for `Outputs`, `InputChannel`
+    for `Inputs`, etc...).
 
     When assigning something to an attribute holding an existing channel, if the
-    assigned object is a `Channel`, then it is treated like a `connection`, otherwise
-    it is treated like a value `update`. I.e.
+    assigned object is a `Channel`, then an attempt is made to make a `connection`
+    between the two channels, otherwise we fall back on a value assignment that must
+    be defined in child classes under `_assign_value_to_existing_channel`, i.e.
     >>> some_io.some_existing_channel = 5
 
     is equivalent to
-    >>> some_io.some_existing_channel.update(5)
+    >>> some_io._assign_value_to_existing_channel(
+    ...     some_io["some_existing_channel"], 5
+    ... )
 
     and
     >>> some_io.some_existing_channel = some_other_channel

From a698291b0fcdc4e05db73abed80fe006804650b1 Mon Sep 17 00:00:00 2001
From: liamhuber 
Date: Thu, 22 Jun 2023 12:33:58 -0700
Subject: [PATCH 181/756] Allow IO panels to store channels under a different
 name than the label

This will allow Workflows to have io with labels that combine the node and channel labels, without modifying the underlying channel label
---
 pyiron_contrib/workflow/io.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py
index c77dd871c..f4a59a65e 100644
--- a/pyiron_contrib/workflow/io.py
+++ b/pyiron_contrib/workflow/io.py
@@ -5,6 +5,7 @@
 from __future__ import annotations
 
 from abc import ABC, abstractmethod
+from warnings import warn
 
 from pyiron_contrib.workflow.channels import (
     Channel,
@@ -76,9 +77,9 @@ def __setattr__(self, key, value):
             self._assign_value_to_existing_channel(self.channel_dict[key], value)
         elif isinstance(value, self._channel_class):
             if key != value.label:
-                raise ValueError(
-                    f"Channels can only be assigned to attributes matching their label,"
-                    f"but just tried to assign the channel {value.label} to {key}"
+                warn(
+                    f"Assigning a channel with the label {value.label} to the io key "
+                    f"{key}"
                 )
             self.channel_dict[key] = value
         else:

From d97b87f5132327abe63071fcb88f657e5a0302ed Mon Sep 17 00:00:00 2001
From: liamhuber 
Date: Thu, 22 Jun 2023 12:38:46 -0700
Subject: [PATCH 182/756] Add docstrings to IO classes

---
 pyiron_contrib/workflow/io.py | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py
index f4a59a65e..8c6e21e02 100644
--- a/pyiron_contrib/workflow/io.py
+++ b/pyiron_contrib/workflow/io.py
@@ -135,6 +135,10 @@ def to_dict(self):
 
 
 class DataIO(IO, ABC):
+    """
+    Extends the base IO class with helper methods relevant to data channels.
+    """
+
     def _assign_a_non_channel_value(self, channel: DataChannel, value) -> None:
         channel.update(value)
 
@@ -195,6 +199,13 @@ def _channel_class(self) -> type(OutputSignal):
 
 
 class Signals:
+    """
+    A meta-container for input and output signal IO containers.
+
+    Attributes:
+        input (InputSignals): An empty input signals IO container.
+        output (OutputSignals): An empty input signals IO container.
+    """
     def __init__(self):
         self.input = InputSignals()
         self.output = OutputSignals()

From 927cc40c566dae132a761c8b72fbb07203468755 Mon Sep 17 00:00:00 2001
From: liamhuber 
Date: Thu, 22 Jun 2023 12:39:07 -0700
Subject: [PATCH 183/756] Remove unused import

---
 pyiron_contrib/workflow/channels.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py
index 1eca94cb3..486948c4d 100644
--- a/pyiron_contrib/workflow/channels.py
+++ b/pyiron_contrib/workflow/channels.py
@@ -2,7 +2,6 @@
 
 import typing
 from abc import ABC, abstractmethod
-from json import dumps
 from warnings import warn
 
 from pyiron_contrib.workflow.has_channel import HasChannel

From e173d21e0b1ab931619ffd5e9f12f8a294a8b334 Mon Sep 17 00:00:00 2001
From: liamhuber 
Date: Thu, 22 Jun 2023 13:18:59 -0700
Subject: [PATCH 184/756] Expand and update channel docs

---
 pyiron_contrib/workflow/channels.py | 173 +++++++++++++++++++++++++---
 1 file changed, 155 insertions(+), 18 deletions(-)

diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py
index 486948c4d..de556917e 100644
--- a/pyiron_contrib/workflow/channels.py
+++ b/pyiron_contrib/workflow/channels.py
@@ -1,3 +1,23 @@
+"""
+Channels are access points for information to flow into and out of nodes.
+
+Data channels carry, unsurprisingly, data.
+Input data channels force an update on their owning node when they are updated with new
+data, and output data channels update all the input data channels to which they are
+connected.
+In this way, data channels facilitate forward propagation of data through a graph.
+They hold data persistently.
+
+Signal channels are tools for procedurally exposing functionality on nodes.
+Input signal channels are connected to a callback function which gets invoked when the
+channel is updated.
+Output signal channels must be accessed by the owning node directly, and then trigger
+all the input signal channels to which they are connected.
+In this way, signal channels can force behaviour (node method calls) to propagate
+forwards through a graph.
+They do not hold any data, but rather fire for an effect.
+"""
+
 from __future__ import annotations
 
 import typing
@@ -19,10 +39,20 @@ class Channel(HasChannel, HasToDict, ABC):
     """
     Channels facilitate the flow of information (data or control signals) into and
     out of nodes.
-    They have a label and belong to a node.
+    They must have a label and belong to a node.
 
     Input/output channels can be (dis)connected from other output/input channels, and
     store all of their current connections in a list.
+    This connection information is duplicated in that it is stored on _both_ channels
+    that form the connection.
+
+    Child classes must define a string representation, `__str__`, and what to do on an
+    attempted connection, `connect`.
+
+    Attributes:
+        label (str): The name of the channel.
+        node (pyiron_contrib.workflow.node.Node): The node to which the channel belongs.
+        connections (list[Channel]): Other channels to which this channel is connected.
     """
 
     def __init__(
@@ -30,9 +60,17 @@ def __init__(
         label: str,
         node: Node,
     ):
-        self.label = label
-        self.node = node
-        self.connections = []
+        """
+        Make a new channel.
+
+        Args:
+            label (str): A name for the channel.
+            node (pyiron_contrib.workflow.node.Node): The node to which the channel
+                belongs.
+        """
+        self.label: str = label
+        self.node: Node = node
+        self.connections: list[Channel] = []
 
     @abstractmethod
     def __str__(self):
@@ -40,19 +78,38 @@ def __str__(self):
 
     @abstractmethod
     def connect(self, *others: Channel):
+        """
+        How to handle connections to other channels.
+
+        Args:
+            *others (Channel): The other channel objects to attempt to connect with.
+        """
         pass
 
     def disconnect(self, *others: Channel):
+        """
+        If currently connected to any others, removes this and the other from eachothers
+        respective connections lists.
+
+        Args:
+            *others (Channel): The other channels to disconnect from.
+        """
         for other in others:
             if other in self.connections:
                 self.connections.remove(other)
                 other.disconnect(self)
 
     def disconnect_all(self):
+        """
+        Disconnect from all other channels currently in the connections list.
+        """
         self.disconnect(*self.connections)
 
     @property
     def connected(self):
+        """
+        Has at least one connection.
+        """
         return len(self.connections) > 0
 
     def _already_connected(self, other: Channel):
@@ -82,16 +139,13 @@ class DataChannel(Channel, ABC):
     They store this data in a `value` attribute.
     They may optionally have a type hint.
     They have a `ready` attribute which tells whether their value matches their type
-    hint.
+    hint (if one is provided, else `True`).
     They may optionally have a storage priority (but this doesn't do anything yet).
     (In the future they may optionally have an ontological type.)
 
     The `value` held by a channel can be manually assigned, but should normally be set
     by the `update` method.
     In neither case is the type hint strictly enforced.
-    Input channels will then propagate their value along to their owning node.
-    Output channels with then propagate their value to all the input channels they're
-    connected to.
 
     Type hinting is strictly enforced in one situation: when making connections to
     other channels and at least one data channel has a non-None value for its type hint.
@@ -121,11 +175,6 @@ class DataChannel(Channel, ABC):
         hint a tuple with a mixture of fixed elements of fixed type, followed by an
         arbitrary elements of arbitrary type. This and other complex scenarios are not
         yet included in our test suite and behaviour is not guaranteed.
-
-    TODO:
-        In direct relation to the above warning, it may be nice to add a flag to
-        channels to turn on/off the strict enforcement of type hints when making
-        connections.
     """
 
     def __init__(
@@ -144,23 +193,53 @@ def __init__(
 
     @property
     def ready(self):
+        """
+        Check if the currently stored value satisfies the channel's type hint.
+
+        Returns:
+            (bool): Whether the value matches the type hint.
+        """
         if self.type_hint is not None:
             return valid_value(self.value, self.type_hint)
         else:
             return True
 
     def update(self, value):
+        """
+        Store a new value and trigger before- and after-update routines.
+
+        Args:
+            value: The value to store.
+        """
         self._before_update()
         self.value = value
         self._after_update()
 
     def _before_update(self):
+        """
+        A tool for child classes to do things before the value changed during an update.
+        """
         pass
 
     def _after_update(self):
+        """
+        A tool for child classes to do things after the value changed during an update.
+        """
         pass
 
     def connect(self, *others: DataChannel):
+        """
+        For all others for which the connection is valid (one input, one output, both
+        data channels), adds this to the other's list of connections and the other to
+        this list of connections.
+        Then the input channel gets updated with the output channel's current value.
+
+        Args:
+            *others (DataChannel):
+
+        Raises:
+            TypeError: When one of others is not a `DataChannel`
+        """
         for other in others:
             if self._valid_connection(other):
                 self.connections.append(other)
@@ -216,12 +295,18 @@ def to_dict(self):
 
 class InputData(DataChannel):
     """
-    `InputData` channels may be set to `wait_for_update()`, and they are only `ready`
-    when they are not `waiting_for_update`. Their parent node can be told to always set
-    them to wait for an update after the node runs using
-    `require_update_after_node_runs()`.
+    On `update`, Input channels will then propagate their value along to their owning
+    node by invoking its `update` method.
 
-    They may also set their `strict_connections` to `False` (`True` -- default) at
+    `InputData` channels may be set to `wait_for_update()`, and they are only `ready`
+    when they are not `waiting_for_update`.
+    Their parent node can be told to always set them to wait for an update after the
+    node runs using `require_update_after_node_runs()`.
+    This allows nodes to complete the update of multiple channels before running again.
+
+    The `strict_connections` parameter controls whether connections are subject to
+    type checking requirements.
+    I.e., they may set `strict_connections` to `False` (`True` -- default) at
     instantiation or later with `(de)activate_strict_connections()` to prevent (enable)
     data type checking when making connections with `OutputData` channels.
     """
@@ -246,10 +331,23 @@ def __init__(
         self.waiting_for_update = False
 
     def wait_for_update(self):
+        """
+        Sets `waiting_for_update` to `True`, which prevents `ready` from returning
+        `True` until `update` is called.
+        """
         self.waiting_for_update = True
 
     @property
     def ready(self):
+        """
+        Extends the parent class check for whether the value matches the type hint with
+        a check for whether the channel has been told to wait for an update (and not
+        been updated since then).
+
+        Returns:
+            (bool): True when the stored value matches the type hint and the channel
+                has not been told to wait for an update.
+        """
         return not self.waiting_for_update and super().ready
 
     def _before_update(self):
@@ -264,6 +362,14 @@ def _after_update(self):
         self.node.update()
 
     def require_update_after_node_runs(self, wait_now=False):
+        """
+        Registers this channel with its owning node as one that should have
+        `wait_for_update()` applied after each time the node runs.
+
+        Args:
+            wait_now (bool): Also call `wait_for_update()` right now. (Default is
+                False.)
+        """
         if self.label not in self.node.channels_requiring_update_after_run:
             self.node.channels_requiring_update_after_run.append(self.label)
         if wait_now:
@@ -277,6 +383,10 @@ def deactivate_strict_connections(self):
 
 
 class OutputData(DataChannel):
+    """
+    On `update`, Output channels propagate their value to all the input channels to
+    which they are connected by invoking their `update` method.
+    """
     def _after_update(self):
         for inp in self.connections:
             inp.update(self.value)
@@ -296,6 +406,17 @@ def __call__(self):
         pass
 
     def connect(self, *others: Channel):
+        """
+        For all others for which the connection is valid (one input, one output, both
+        data channels), adds this to the other's list of connections and the other to
+        this list of connections.
+
+        Args:
+            *others (SignalChannel): The other channels to attempt a connection to
+
+        Raises:
+            TypeError: When one of others is not a `SignalChannel`
+        """
         for other in others:
             if self._valid_connection(other):
                 self.connections.append(other)
@@ -322,12 +443,25 @@ def _is_IO_pair(self, other) -> bool:
 
 
 class InputSignal(SignalChannel):
+    """
+    Invokes a callback when called.
+    """
     def __init__(
         self,
         label: str,
         node: Node,
         callback: callable,
     ):
+        """
+        Make a new input signal channel.
+
+        Args:
+            label (str): A name for the channel.
+            node (pyiron_contrib.workflow.node.Node): The node to which the channel
+                belongs.
+            callback (callable): An argument-free callback to invoke when calling this
+                object.
+        """
         super().__init__(label=label, node=node)
         self.callback: callable = callback
 
@@ -344,6 +478,9 @@ def to_dict(self):
 
 
 class OutputSignal(SignalChannel):
+    """
+    Calls all the input signal objects in its connections list when called.
+    """
     def __call__(self):
         for c in self.connections:
             c()

From 233623e1054a1d18b7c9920c22aebeec567452d5 Mon Sep 17 00:00:00 2001
From: liamhuber 
Date: Thu, 22 Jun 2023 13:28:54 -0700
Subject: [PATCH 185/756] Expand and update type hinting

---
 pyiron_contrib/workflow/channels.py | 56 ++++++++++++++---------------
 1 file changed, 28 insertions(+), 28 deletions(-)

diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py
index de556917e..64d760567 100644
--- a/pyiron_contrib/workflow/channels.py
+++ b/pyiron_contrib/workflow/channels.py
@@ -77,7 +77,7 @@ def __str__(self):
         pass
 
     @abstractmethod
-    def connect(self, *others: Channel):
+    def connect(self, *others: Channel) -> None:
         """
         How to handle connections to other channels.
 
@@ -86,7 +86,7 @@ def connect(self, *others: Channel):
         """
         pass
 
-    def disconnect(self, *others: Channel):
+    def disconnect(self, *others: Channel) -> None:
         """
         If currently connected to any others, removes this and the other from eachothers
         respective connections lists.
@@ -99,20 +99,20 @@ def disconnect(self, *others: Channel):
                 self.connections.remove(other)
                 other.disconnect(self)
 
-    def disconnect_all(self):
+    def disconnect_all(self) -> None:
         """
         Disconnect from all other channels currently in the connections list.
         """
         self.disconnect(*self.connections)
 
     @property
-    def connected(self):
+    def connected(self) -> bool:
         """
         Has at least one connection.
         """
         return len(self.connections) > 0
 
-    def _already_connected(self, other: Channel):
+    def _already_connected(self, other: Channel) -> bool:
         return other in self.connections
 
     def __iter__(self):
@@ -125,7 +125,7 @@ def __len__(self):
     def channel(self) -> Channel:
         return self
 
-    def to_dict(self):
+    def to_dict(self) -> dict:
         return {
             "label": self.label,
             "connected": self.connected,
@@ -192,7 +192,7 @@ def __init__(
         self.storage_priority = storage_priority
 
     @property
-    def ready(self):
+    def ready(self) -> bool:
         """
         Check if the currently stored value satisfies the channel's type hint.
 
@@ -204,7 +204,7 @@ def ready(self):
         else:
             return True
 
-    def update(self, value):
+    def update(self, value) -> None:
         """
         Store a new value and trigger before- and after-update routines.
 
@@ -215,19 +215,19 @@ def update(self, value):
         self.value = value
         self._after_update()
 
-    def _before_update(self):
+    def _before_update(self) -> None:
         """
         A tool for child classes to do things before the value changed during an update.
         """
         pass
 
-    def _after_update(self):
+    def _after_update(self) -> None:
         """
         A tool for child classes to do things after the value changed during an update.
         """
         pass
 
-    def connect(self, *others: DataChannel):
+    def connect(self, *others: DataChannel) -> None:
         """
         For all others for which the connection is valid (one input, one output, both
         data channels), adds this to the other's list of connections and the other to
@@ -258,7 +258,7 @@ def connect(self, *others: DataChannel):
                         f"({self.__class__.__name__}) got a {other} ({type(other)})"
                     )
 
-    def _valid_connection(self, other):
+    def _valid_connection(self, other) -> bool:
         if self._is_IO_pair(other) and not self._already_connected(other):
             if self._both_typed(other):
                 out, inp = self._figure_out_who_is_who(other)
@@ -274,10 +274,10 @@ def _valid_connection(self, other):
         else:
             return False
 
-    def _is_IO_pair(self, other: DataChannel):
+    def _is_IO_pair(self, other: DataChannel) -> bool:
         return isinstance(other, DataChannel) and not isinstance(other, self.__class__)
 
-    def _both_typed(self, other: DataChannel):
+    def _both_typed(self, other: DataChannel) -> bool:
         return self.type_hint is not None and other.type_hint is not None
 
     def _figure_out_who_is_who(self, other: DataChannel) -> (OutputData, InputData):
@@ -286,7 +286,7 @@ def _figure_out_who_is_who(self, other: DataChannel) -> (OutputData, InputData):
     def __str__(self):
         return str(self.value)
 
-    def to_dict(self):
+    def to_dict(self) -> dict:
         d = super().to_dict()
         d["value"] = repr(self.value)
         d["ready"] = self.ready
@@ -330,7 +330,7 @@ def __init__(
         self.strict_connections = strict_connections
         self.waiting_for_update = False
 
-    def wait_for_update(self):
+    def wait_for_update(self) -> None:
         """
         Sets `waiting_for_update` to `True`, which prevents `ready` from returning
         `True` until `update` is called.
@@ -338,7 +338,7 @@ def wait_for_update(self):
         self.waiting_for_update = True
 
     @property
-    def ready(self):
+    def ready(self) -> bool:
         """
         Extends the parent class check for whether the value matches the type hint with
         a check for whether the channel has been told to wait for an update (and not
@@ -350,18 +350,18 @@ def ready(self):
         """
         return not self.waiting_for_update and super().ready
 
-    def _before_update(self):
+    def _before_update(self) -> None:
         if self.node.running:
             raise RuntimeError(
                 f"Parent node {self.node.label} of {self.label} is running, so value "
                 f"cannot be updated."
             )
 
-    def _after_update(self):
+    def _after_update(self) -> None:
         self.waiting_for_update = False
         self.node.update()
 
-    def require_update_after_node_runs(self, wait_now=False):
+    def require_update_after_node_runs(self, wait_now=False) -> None:
         """
         Registers this channel with its owning node as one that should have
         `wait_for_update()` applied after each time the node runs.
@@ -375,10 +375,10 @@ def require_update_after_node_runs(self, wait_now=False):
         if wait_now:
             self.wait_for_update()
 
-    def activate_strict_connections(self):
+    def activate_strict_connections(self) -> None:
         self.strict_connections = True
 
-    def deactivate_strict_connections(self):
+    def deactivate_strict_connections(self) -> None:
         self.strict_connections = False
 
 
@@ -387,7 +387,7 @@ class OutputData(DataChannel):
     On `update`, Output channels propagate their value to all the input channels to
     which they are connected by invoking their `update` method.
     """
-    def _after_update(self):
+    def _after_update(self) -> None:
         for inp in self.connections:
             inp.update(self.value)
 
@@ -402,10 +402,10 @@ class SignalChannel(Channel, ABC):
     """
 
     @abstractmethod
-    def __call__(self):
+    def __call__(self) -> None:
         pass
 
-    def connect(self, *others: Channel):
+    def connect(self, *others: SignalChannel) -> None:
         """
         For all others for which the connection is valid (one input, one output, both
         data channels), adds this to the other's list of connections and the other to
@@ -465,13 +465,13 @@ def __init__(
         super().__init__(label=label, node=node)
         self.callback: callable = callback
 
-    def __call__(self):
+    def __call__(self) -> None:
         self.callback()
 
     def __str__(self):
         return f"{self.label} runs {self.callback.__name__}"
 
-    def to_dict(self):
+    def to_dict(self) -> dict:
         d = super().to_dict()
         d["callback"] = self.callback.__name__
         return d
@@ -481,7 +481,7 @@ class OutputSignal(SignalChannel):
     """
     Calls all the input signal objects in its connections list when called.
     """
-    def __call__(self):
+    def __call__(self) -> None:
         for c in self.connections:
             c()
 

From 77931c8282a9c3bfbbc7d82c5544448e8db7d756 Mon Sep 17 00:00:00 2001
From: liamhuber 
Date: Thu, 22 Jun 2023 13:48:24 -0700
Subject: [PATCH 186/756] Fix tests to match spec

---
 tests/unit/workflow/test_io.py | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/tests/unit/workflow/test_io.py b/tests/unit/workflow/test_io.py
index 7a75df862..950f4d778 100644
--- a/tests/unit/workflow/test_io.py
+++ b/tests/unit/workflow/test_io.py
@@ -40,8 +40,20 @@ def test_assignment(self):
         with self.assertRaises(TypeError):
             self.input.foo = "not an input channel"
 
-        with self.assertRaises(ValueError):
+        with self.subTest("Can assign to a key that is not the label"):
+            label_before_assignment = self.post_facto_output.label
             self.output.not_this_channels_name = self.post_facto_output
+            self.assertIs(
+                self.output.not_this_channels_name,
+                self.post_facto_output,
+                msg="Expected channel to get assigned"
+            )
+            self.assertEqual(
+                self.post_facto_output.label,
+                label_before_assignment,
+                msg="Labels should not get updated on assignment of channels to IO "
+                    "collections"
+            )
 
         with self.assertRaises(TypeError):
             # Right label, and a channel, but wrong type of channel

From cbc8fbce1489b4ef1bb7b8c21d1690200d9a325f Mon Sep 17 00:00:00 2001
From: liamhuber 
Date: Thu, 22 Jun 2023 13:49:03 -0700
Subject: [PATCH 187/756] Refactor: slide

Start by testing things that are forbidden, and move to successful stuff
---
 tests/unit/workflow/test_io.py | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/tests/unit/workflow/test_io.py b/tests/unit/workflow/test_io.py
index 950f4d778..36e15a22f 100644
--- a/tests/unit/workflow/test_io.py
+++ b/tests/unit/workflow/test_io.py
@@ -40,6 +40,13 @@ def test_assignment(self):
         with self.assertRaises(TypeError):
             self.input.foo = "not an input channel"
 
+        with self.assertRaises(TypeError):
+            # Right label, and a channel, but wrong type of channel
+            self.input.b = self.post_facto_output
+
+        with self.subTest("Successful channel assignment"):
+            self.output.b = self.post_facto_output
+
         with self.subTest("Can assign to a key that is not the label"):
             label_before_assignment = self.post_facto_output.label
             self.output.not_this_channels_name = self.post_facto_output
@@ -55,13 +62,6 @@ def test_assignment(self):
                     "collections"
             )
 
-        with self.assertRaises(TypeError):
-            # Right label, and a channel, but wrong type of channel
-            self.input.b = self.post_facto_output
-
-        with self.subTest("Successful channel assignment"):
-            self.output.b = self.post_facto_output
-
     def test_connection(self):
         self.input.x = self.input.y
         self.assertEqual(

From 45048d49c6afb96788d99c2ee8b10b5a3cf751d3 Mon Sep 17 00:00:00 2001
From: pyiron-runner 
Date: Thu, 22 Jun 2023 20:56:42 +0000
Subject: [PATCH 188/756] Format black

---
 pyiron_contrib/workflow/channels.py | 3 +++
 pyiron_contrib/workflow/io.py       | 1 +
 2 files changed, 4 insertions(+)

diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py
index 64d760567..8bf6be53b 100644
--- a/pyiron_contrib/workflow/channels.py
+++ b/pyiron_contrib/workflow/channels.py
@@ -387,6 +387,7 @@ class OutputData(DataChannel):
     On `update`, Output channels propagate their value to all the input channels to
     which they are connected by invoking their `update` method.
     """
+
     def _after_update(self) -> None:
         for inp in self.connections:
             inp.update(self.value)
@@ -446,6 +447,7 @@ class InputSignal(SignalChannel):
     """
     Invokes a callback when called.
     """
+
     def __init__(
         self,
         label: str,
@@ -481,6 +483,7 @@ class OutputSignal(SignalChannel):
     """
     Calls all the input signal objects in its connections list when called.
     """
+
     def __call__(self) -> None:
         for c in self.connections:
             c()
diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py
index 8c6e21e02..c5bc278c0 100644
--- a/pyiron_contrib/workflow/io.py
+++ b/pyiron_contrib/workflow/io.py
@@ -206,6 +206,7 @@ class Signals:
         input (InputSignals): An empty input signals IO container.
         output (OutputSignals): An empty input signals IO container.
     """
+
     def __init__(self):
         self.input = InputSignals()
         self.output = OutputSignals()

From 05ac87570a1ca6f9b5be1311e2651450efda51a6 Mon Sep 17 00:00:00 2001
From: liamhuber 
Date: Thu, 22 Jun 2023 14:17:41 -0700
Subject: [PATCH 189/756] Make Workflow conform to IsNodal type hints for data
 IO

---
 pyiron_contrib/workflow/workflow.py | 34 +++++++++++++----------------
 1 file changed, 15 insertions(+), 19 deletions(-)

diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py
index 672f8157d..bf5fd1ee4 100644
--- a/pyiron_contrib/workflow/workflow.py
+++ b/pyiron_contrib/workflow/workflow.py
@@ -2,9 +2,9 @@
 
 from pyiron_contrib.workflow.has_nodes import HasNodes
 from pyiron_contrib.workflow.has_to_dict import HasToDict
+from pyiron_contrib.workflow.io import Inputs, Outputs
 from pyiron_contrib.workflow.is_nodal import IsNodal
 from pyiron_contrib.workflow.node import Node, node, fast_node, single_value_node
-from pyiron_contrib.workflow.util import DotDict
 
 
 class _NodeDecoratorAccess:
@@ -126,26 +126,22 @@ def __init__(self, label: str, *nodes: Node, strict_naming=True):
             self.add_node(node)
 
     @property
-    def inputs(self):
-        return DotDict(
-            {
-                f"{node.label}_{channel.label}": channel
-                for node in self.nodes.values()
-                for channel in node.inputs
-                if not channel.connected
-            }
-        )
+    def inputs(self) -> Inputs:
+        inputs = Inputs()
+        for node_label, node in self.nodes.items():
+            for channel in node.inputs:
+                if not channel.connected:
+                    inputs[f"{node_label}_{channel.label}"] = channel
+        return inputs
 
     @property
-    def outputs(self):
-        return DotDict(
-            {
-                f"{node.label}_{channel.label}": channel
-                for node in self.nodes.values()
-                for channel in node.outputs
-                if not channel.connected
-            }
-        )
+    def outputs(self) -> Outputs:
+        outputs = Outputs()
+        for node_label, node in self.nodes.items():
+            for channel in node.outputs:
+                if not channel.connected:
+                    outputs[f"{node_label}_{channel.label}"] = channel
+        return outputs
 
     def to_dict(self):
         return {

From b2e5b224f266423a6cfd9db0846db879671fc684 Mon Sep 17 00:00:00 2001
From: liamhuber 
Date: Thu, 22 Jun 2023 14:56:03 -0700
Subject: [PATCH 190/756] Pull run method up to the parent class

---
 pyiron_contrib/workflow/is_nodal.py | 71 ++++++++++++++++++++++++++++-
 pyiron_contrib/workflow/node.py     | 46 +++++--------------
 pyiron_contrib/workflow/workflow.py |  2 +-
 3 files changed, 82 insertions(+), 37 deletions(-)

diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py
index 266d54c31..e7939f76f 100644
--- a/pyiron_contrib/workflow/is_nodal.py
+++ b/pyiron_contrib/workflow/is_nodal.py
@@ -103,10 +103,79 @@ def outputs(self) -> Outputs:
     def update(self):
         pass
 
+    @property
     @abstractmethod
-    def run(self):
+    def on_run(self) -> callable[..., tuple]:
+        """
+        What the nodal object actually does!
+        """
         pass
 
+    @property
+    def run_args(self) -> dict:
+        """
+        Any data needed for `on_run`, will be passed as **kwargs.
+        """
+        return {}
+
+    def process_run_result(self, run_output: tuple) -> None:
+        """
+        What to _do_ with the results of `on_run` once you have them.
+
+        Args:
+            run_output (tuple): The results of a `self.on_run(self.run_args)` call.
+        """
+        pass
+
+    def run(self) -> None:
+        """
+        Executes the functionality of the nodal object defined in `on_run`.
+        Handles the status of the nodal object, and communicating with any remote
+        computing resources.
+        """
+        if self.running:
+            raise RuntimeError(f"{self.label} is already running")
+
+        self.running = True
+        self.failed = False
+
+        if self.server is None:
+            try:
+                run_output = self.on_run(**self.run_args)
+            except Exception as e:
+                self.running = False
+                self.failed = True
+                raise e
+            self.finish_run(run_output)
+        else:
+            raise NotImplementedError(
+                "We currently only support executing the node functionality right on "
+                "the main python process that the node instance lives on. Come back "
+                "later for cool new features."
+            )
+            # TODO: Send the `on_run` callable and the `run_args` data off to remote
+            #       resources and register `finish_run` as a callback.
+
+    def finish_run(self, run_output: tuple):
+        """
+        Process the run result, then wrap up statuses etc.
+
+        By extracting this as a separate method, we allow the node to pass the actual
+        execution off to another entity and release the python process to do other
+        things. In such a case, this function should be registered as a callback
+        so that the node can finish "running" and, e.g. push its data forward when that
+        execution is finished.
+        """
+        try:
+            self.process_run_result(run_output)
+        except Exception as e:
+            self.running = False
+            self.failed = True
+            raise e
+
+        self.signals.output.ran()
+        self.running = False
+
     def _build_signal_channels(self) -> Signals:
         signals = Signals()
         signals.input.run = InputSignal("run", self, self.run)
diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py
index f6351975c..d186f0672 100644
--- a/pyiron_contrib/workflow/node.py
+++ b/pyiron_contrib/workflow/node.py
@@ -467,51 +467,27 @@ def update(self) -> None:
         if self.run_on_updates and self.ready:
             self.run()
 
-    def run(self) -> None:
-        if self.running:
-            raise RuntimeError(f"{self.label} is already running")
-
-        self.running = True
-        self.failed = False
+    @property
+    def on_run(self):
+        return self.node_function
 
-        if self.server is None:
-            try:
-                function_output = self.node_function(**self.inputs.to_value_dict())
-            except Exception as e:
-                self.running = False
-                self.failed = True
-                raise e
-            self.process_output(function_output)
-        else:
-            raise NotImplementedError(
-                "We currently only support executing the node functionality right on "
-                "the main python process that the node instance lives on. Come back "
-                "later for cool new features."
-            )
+    @property
+    def run_args(self) -> dict:
+        return self.inputs.to_value_dict()
 
-    def process_output(self, function_output):
+    def process_run_result(self, function_output):
         """
-        Take the results of the node function, and use them to update the node.
-
-        By extracting this as a separate method, we allow the node to pass the actual
-        execution off to another entity and release the python process to do other
-        things. In such a case, this function should be registered as a callback
-        so that the node can finishing "running" and push its data forward when that
-        execution is finished.
+        Take the results of the node function, and use them to update the node output.
         """
+        for channel_name in self.channels_requiring_update_after_run:
+            self.inputs[channel_name].wait_for_update()
+
         if len(self.outputs) == 1:
             function_output = (function_output,)
 
         for out, value in zip(self.outputs, function_output):
             out.update(value)
 
-        self.signals.output.ran()
-
-        for channel_name in self.channels_requiring_update_after_run:
-            self.inputs[channel_name].wait_for_update()
-
-        self.running = False
-
     def __call__(self) -> None:
         self.run()
 
diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py
index bf5fd1ee4..778a0c66a 100644
--- a/pyiron_contrib/workflow/workflow.py
+++ b/pyiron_contrib/workflow/workflow.py
@@ -168,6 +168,6 @@ def update(self):
             if node.outputs.connected and not node.inputs.connected:
                 node.update()
 
-    def run(self):
+    def on_run(self):
         # Maybe we need this if workflows can be used as nodes?
         raise NotImplementedError

From 05bfa3238c9bd3a7e32af8f3c5af9dcf58c147bc Mon Sep 17 00:00:00 2001
From: liamhuber 
Date: Thu, 22 Jun 2023 15:12:48 -0700
Subject: [PATCH 191/756] Remove storage priority

It didn't actually do anything yet, so let's hold off implementing it until we have serialization for it to work with.
---
 pyiron_contrib/workflow/channels.py |  4 ---
 pyiron_contrib/workflow/io.py       |  4 ---
 pyiron_contrib/workflow/node.py     | 40 +++--------------------------
 3 files changed, 4 insertions(+), 44 deletions(-)

diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py
index 8bf6be53b..e08f4d4a0 100644
--- a/pyiron_contrib/workflow/channels.py
+++ b/pyiron_contrib/workflow/channels.py
@@ -183,13 +183,11 @@ def __init__(
         node: Node,
         default: typing.Optional[typing.Any] = None,
         type_hint: typing.Optional[typing.Any] = None,
-        storage_priority: int = 0,
     ):
         super().__init__(label=label, node=node)
         self.default = default
         self.value = default
         self.type_hint = type_hint
-        self.storage_priority = storage_priority
 
     @property
     def ready(self) -> bool:
@@ -317,7 +315,6 @@ def __init__(
         node: Node,
         default: typing.Optional[typing.Any] = None,
         type_hint: typing.Optional[typing.Any] = None,
-        storage_priority: int = 0,
         strict_connections: bool = True,
     ):
         super().__init__(
@@ -325,7 +322,6 @@ def __init__(
             node=node,
             default=default,
             type_hint=type_hint,
-            storage_priority=storage_priority,
         )
         self.strict_connections = strict_connections
         self.waiting_for_update = False
diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py
index c5bc278c0..243ee42a0 100644
--- a/pyiron_contrib/workflow/io.py
+++ b/pyiron_contrib/workflow/io.py
@@ -149,10 +149,6 @@ def to_value_dict(self):
     def ready(self):
         return all([c.ready for c in self])
 
-    def set_storage_priority(self, priority: int):
-        for c in self:
-            c.storage_priority = priority
-
     def to_dict(self):
         d = super().to_dict()
         d["ready"] = self.ready
diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py
index 761d9a601..8732917f0 100644
--- a/pyiron_contrib/workflow/node.py
+++ b/pyiron_contrib/workflow/node.py
@@ -247,8 +247,6 @@ class with a function and labels for its output, like so:
         ...     def __init__(
         ...         self,
         ...         label: Optional[str] = None,
-        ...         input_storage_priority: Optional[dict[str, int]] = None,
-        ...         output_storage_priority: Optional[dict[str, int]] = None,
         ...         run_on_updates: bool = True,
         ...         update_on_instantiation: bool = False,
         ...         **kwargs
@@ -257,8 +255,6 @@ class with a function and labels for its output, like so:
         ...             self.alphabet_mod_three,
         ...             "letter",
         ...             labe=label,
-        ...             input_storage_priority=input_storage_priority,
-        ...             output_storage_priority=output_storage_priority,
         ...             run_on_updates=run_on_updates,
         ...             update_on_instantiation=update_on_instantiation,
         ...             **kwargs
@@ -336,12 +332,10 @@ def __init__(
         if parent is not None:
             parent.add(self)
 
-        input_channels = self._build_input_channels(input_storage_priority)
+        input_channels = self._build_input_channels()
         self.inputs = Inputs(*input_channels)
 
-        output_channels = self._build_output_channels(
-            *output_labels, storage_priority=output_storage_priority
-        )
+        output_channels = self._build_output_channels(*output_labels)
         self.outputs = Outputs(*output_channels)
 
         self.signals = self._build_signal_channels()
@@ -364,7 +358,7 @@ def __init__(
         if update_on_instantiation:
             self.update()
 
-    def _build_input_channels(self, storage_priority: dict[str:int]):
+    def _build_input_channels(self):
         channels = []
         type_hints = get_type_hints(self.node_function)
         parameters = inspect.signature(self.node_function).parameters
@@ -378,11 +372,6 @@ def _build_input_channels(self, storage_priority: dict[str:int]):
                     f"name _not_ among {self._init_keywords}"
                 )
 
-            try:
-                priority = storage_priority[label]
-            except (KeyError, TypeError):
-                priority = None
-
             try:
                 type_hint = type_hints[label]
             except KeyError:
@@ -399,7 +388,6 @@ def _build_input_channels(self, storage_priority: dict[str:int]):
                     node=self,
                     default=default,
                     type_hint=type_hint,
-                    storage_priority=priority,
                 )
             )
         return channels
@@ -408,9 +396,7 @@ def _build_input_channels(self, storage_priority: dict[str:int]):
     def _init_keywords(self):
         return list(inspect.signature(self.__init__).parameters.keys())
 
-    def _build_output_channels(
-        self, *return_labels: str, storage_priority: dict[str:int] = None
-    ):
+    def _build_output_channels(self, *return_labels: str):
         try:
             type_hints = get_type_hints(self.node_function)["return"]
             if len(return_labels) > 1:
@@ -435,17 +421,11 @@ def _build_output_channels(
 
         channels = []
         for label, hint in zip(return_labels, type_hints):
-            try:
-                priority = storage_priority[label]
-            except (KeyError, TypeError):
-                priority = None
-
             channels.append(
                 OutputData(
                     label=label,
                     node=self,
                     type_hint=hint,
-                    storage_priority=priority,
                 )
             )
 
@@ -541,10 +521,6 @@ def fully_connected(self):
             and self.signals.fully_connected
         )
 
-    def set_storage_priority(self, priority: int):
-        self.inputs.set_storage_priority(priority)
-        self.outputs.set_storage_priority(priority)
-
     def to_dict(self):
         return {
             "label": self.label,
@@ -569,8 +545,6 @@ def __init__(
         node_function: callable,
         *output_labels: str,
         label: Optional[str] = None,
-        input_storage_priority: Optional[dict[str, int]] = None,
-        output_storage_priority: Optional[dict[str, int]] = None,
         run_on_updates=True,
         update_on_instantiation=True,
         parent: Optional[Workflow] = None,
@@ -581,8 +555,6 @@ def __init__(
             node_function,
             *output_labels,
             label=label,
-            input_storage_priority=input_storage_priority,
-            output_storage_priority=output_storage_priority,
             run_on_updates=run_on_updates,
             update_on_instantiation=update_on_instantiation,
             parent=parent,
@@ -615,8 +587,6 @@ def __init__(
         node_function: callable,
         *output_labels: str,
         label: Optional[str] = None,
-        input_storage_priority: Optional[dict[str, int]] = None,
-        output_storage_priority: Optional[dict[str, int]] = None,
         run_on_updates=True,
         update_on_instantiation=True,
         parent: Optional[Workflow] = None,
@@ -627,8 +597,6 @@ def __init__(
             node_function,
             *output_labels,
             label=label,
-            input_storage_priority=input_storage_priority,
-            output_storage_priority=output_storage_priority,
             run_on_updates=run_on_updates,
             update_on_instantiation=update_on_instantiation,
             parent=parent,

From 7da015735698470e3a935a9ee6a145487cf40e9a Mon Sep 17 00:00:00 2001
From: liamhuber 
Date: Thu, 22 Jun 2023 15:16:37 -0700
Subject: [PATCH 192/756] Found two more instances

---
 pyiron_contrib/workflow/node.py | 2 --
 1 file changed, 2 deletions(-)

diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py
index 8732917f0..5113097dd 100644
--- a/pyiron_contrib/workflow/node.py
+++ b/pyiron_contrib/workflow/node.py
@@ -311,8 +311,6 @@ def __init__(
         node_function: callable,
         *output_labels: str,
         label: Optional[str] = None,
-        input_storage_priority: Optional[dict[str, int]] = None,
-        output_storage_priority: Optional[dict[str, int]] = None,
         run_on_updates: bool = False,
         update_on_instantiation: bool = False,
         channels_requiring_update_after_run: Optional[list[str]] = None,

From d5807cd1392bcc51aff7d877f1b04db831838fcb Mon Sep 17 00:00:00 2001
From: liamhuber 
Date: Thu, 22 Jun 2023 15:23:27 -0700
Subject: [PATCH 193/756] Build IO in the property if it doesn't exist already

---
 pyiron_contrib/workflow/node.py | 21 ++++++++++++++++-----
 1 file changed, 16 insertions(+), 5 deletions(-)

diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py
index 5113097dd..6ec80bd3f 100644
--- a/pyiron_contrib/workflow/node.py
+++ b/pyiron_contrib/workflow/node.py
@@ -330,11 +330,10 @@ def __init__(
         if parent is not None:
             parent.add(self)
 
-        input_channels = self._build_input_channels()
-        self.inputs = Inputs(*input_channels)
-
-        output_channels = self._build_output_channels(*output_labels)
-        self.outputs = Outputs(*output_channels)
+        self._inputs = None
+        self._outputs = None
+        self._output_labels = output_labels
+        # TODO: Parse output labels from the node function in case output_labels is None
 
         self.signals = self._build_signal_channels()
 
@@ -356,6 +355,18 @@ def __init__(
         if update_on_instantiation:
             self.update()
 
+    @property
+    def inputs(self) -> Inputs:
+        if self._inputs is None:
+            self._inputs = Inputs(*self._build_input_channels())
+        return self._inputs
+
+    @property
+    def outputs(self) -> Outputs:
+        if self._outputs is None:
+            self._outputs = Outputs(*self._build_output_channels(*self._output_labels))
+        return self._outputs
+
     def _build_input_channels(self):
         channels = []
         type_hints = get_type_hints(self.node_function)

From 41862708540b757b88500e7b414b1eaa2dd9ef4f Mon Sep 17 00:00:00 2001
From: Marvin Poul 
Date: Fri, 23 Jun 2023 11:53:13 +0200
Subject: [PATCH 194/756] Add max_wait argument to ExecutionContext.wait

Also raise an error on run state `init`, because it means nothing is
running and the method would wait indefinitely.
---
 pyiron_contrib/tinybase/executor.py | 21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)

diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py
index b34e2421e..7c36a3e1d 100644
--- a/pyiron_contrib/tinybase/executor.py
+++ b/pyiron_contrib/tinybase/executor.py
@@ -4,6 +4,7 @@
 from typing import Union, List
 import time
 import logging
+from math import inf
 
 from pyiron_contrib.tinybase.task import AbstractTask, TaskGenerator
 
@@ -114,12 +115,28 @@ def _run_finished(self):
     def run(self):
         self._run_machine.step()
 
-    def wait(self, until="finished", sleep=0.1):
+    def wait(self, until="finished", max_wait=inf, sleep=0.1):
         """
         Sleep until specified state of the run state machine is reached.
+
+        Before calling this method, the state of this context must be past
+        `init`, i.e. you have to call :meth:`.run` at least once.
+
+        Args:
+            until (str): wait until the executor has reached this state; must
+                be a valid state name of :class:`.RunMachine.Code`.
+            max_wait (float): maximum amount of seconds to wait; wait
+                indefinitely by default
+            sleep (float): amount of seconds to sleep in between status checks
+
+        Raises:
+            ValueError: if the current state is `init`
         """
+        if self._run_machine.state == RunMachine.Code("init"):
+            raise ValueError("Still in state 'init'! Call run() first!")
         until = RunMachine.Code(until)
-        while until != self._run_machine.state:
+        start = time.monotonic()
+        while until != self._run_machine.state and time.monotonic() - start < max_wait:
             time.sleep(sleep)
 
     @property

From 2c5e2988f1feffb46d04698a0c63fffd6f599a9c Mon Sep 17 00:00:00 2001
From: Marvin Poul 
Date: Fri, 23 Jun 2023 12:16:47 +0200
Subject: [PATCH 195/756] Add wait method to TinyJob

---
 notebooks/tinybase/TinyJob.ipynb    | 279 +++++++++++++++++-----------
 pyiron_contrib/tinybase/executor.py |   8 +-
 pyiron_contrib/tinybase/job.py      |  16 ++
 3 files changed, 191 insertions(+), 112 deletions(-)

diff --git a/notebooks/tinybase/TinyJob.ipynb b/notebooks/tinybase/TinyJob.ipynb
index ae17ec530..cfa47d1ea 100644
--- a/notebooks/tinybase/TinyJob.ipynb
+++ b/notebooks/tinybase/TinyJob.ipynb
@@ -86,19 +86,52 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": 5,
    "id": "e31baebd-b2c8-4343-90ad-92d9128d1496",
    "metadata": {
     "tags": []
    },
-   "outputs": [],
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "9d9d50a24c6348aa986d0949a2750f6f",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
    "source": [
     "j = pr.create.job.AseMD('md')"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
+   "execution_count": 6,
    "id": "18e6de26-308c-46ae-9672-b2db43447ea5",
    "metadata": {
     "tags": []
@@ -111,7 +144,19 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 18,
+   "execution_count": 7,
+   "id": "652989f1-6a38-4901-9c21-4302c85bb0d4",
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "from math import inf"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
    "id": "72848cd2-fd51-4ad8-b56e-ca686074bb26",
    "metadata": {
     "tags": []
@@ -126,21 +171,18 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 19,
+   "execution_count": 9,
    "id": "56c0e73a-c42b-4814-a25a-e6974fea3d00",
    "metadata": {
     "tags": []
    },
    "outputs": [
     {
-     "data": {
-      "text/plain": [
-       ""
-      ]
-     },
-     "execution_count": 19,
-     "metadata": {},
-     "output_type": "execute_result"
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "INFO:root:Job already finished!\n"
+     ]
     }
    ],
    "source": [
@@ -149,7 +191,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 10,
    "id": "49ccfe01-7b7e-4615-bf43-21c1bbffec66",
    "metadata": {
     "tags": []
@@ -158,7 +200,7 @@
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "1945295762b54d49809da2f8840cf80d",
+       "model_id": "6a7d186114ee445abf94c0ab53aa0e90",
        "version_major": 2,
        "version_minor": 0
       },
@@ -184,7 +226,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 11,
    "id": "7b807119-da8d-475c-9aa8-c8e8c9afa115",
    "metadata": {
     "tags": []
@@ -196,7 +238,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": 12,
    "id": "3a8cda32-df2e-4884-8cfd-84e438c5be69",
    "metadata": {
     "tags": []
@@ -214,7 +256,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 13,
    "id": "d4b81c3f-4667-4b99-a2b3-08c7ee7e2c82",
    "metadata": {
     "tags": []
@@ -229,7 +271,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
+   "execution_count": 17,
    "id": "e7494fee-d565-45e3-a819-c77ab0d2c7f6",
    "metadata": {
     "scrolled": true,
@@ -237,24 +279,66 @@
    },
    "outputs": [
     {
-     "name": "stderr",
+     "name": "stdout",
      "output_type": "stream",
      "text": [
-      "INFO:root:Job already finished!\n"
+      "       Step     Time          Energy         fmax\n",
+      "LBFGS:    0 12:07:57       11.288146      189.5231\n",
+      "LBFGS:    1 12:07:57        1.168671       43.6957\n",
+      "LBFGS:    2 12:07:57        0.860403       38.6924\n",
+      "LBFGS:    3 12:07:57        0.362400       30.3554\n",
+      "LBFGS:    4 12:07:57        0.004806       24.0865\n",
+      "LBFGS:    5 12:07:57       -0.267437       19.0615\n",
+      "LBFGS:    6 12:07:57       -0.471646       15.0628\n",
+      "LBFGS:    7 12:07:57       -0.623506       11.8810\n",
+      "LBFGS:    8 12:07:57       -0.735237        9.3518\n",
+      "LBFGS:    9 12:07:57       -0.816458        7.3435\n",
+      "LBFGS:   10 12:07:57       -0.874705        5.7512\n",
+      "LBFGS:   11 12:07:57       -0.915849        4.4909\n",
+      "LBFGS:   12 12:07:57       -0.944435        3.4955\n",
+      "LBFGS:   13 12:07:57       -0.963943        2.7113\n",
+      "LBFGS:   14 12:07:57       -0.977006        2.0956\n",
+      "LBFGS:   15 12:07:57       -0.985585        1.6137\n",
+      "LBFGS:   16 12:07:57       -0.991109        1.2382\n",
+      "LBFGS:   17 12:07:57       -0.994598        0.9468\n",
+      "LBFGS:   18 12:07:57       -0.996763        0.7216\n",
+      "LBFGS:   19 12:07:57       -0.998083        0.5484\n",
+      "LBFGS:   20 12:07:57       -0.998876        0.4157\n",
+      "LBFGS:   21 12:07:57       -0.999347        0.3144\n",
+      "LBFGS:   22 12:07:57       -0.999623        0.2374\n",
+      "LBFGS:   23 12:07:57       -0.999784        0.1790\n",
+      "LBFGS:   24 12:07:57       -0.999877        0.1348\n",
+      "LBFGS:   25 12:07:57       -0.999930        0.1014\n",
+      "LBFGS:   26 12:07:57       -0.999960        0.0762\n",
+      "LBFGS:   27 12:07:57       -0.999977        0.0573\n",
+      "LBFGS:   28 12:07:57       -0.999987        0.0430\n",
+      "LBFGS:   29 12:07:57       -0.999993        0.0323\n",
+      "LBFGS:   30 12:07:57       -0.999996        0.0242\n",
+      "LBFGS:   31 12:07:57       -0.999998        0.0182\n",
+      "LBFGS:   32 12:07:57       -0.999999        0.0136\n",
+      "LBFGS:   33 12:07:57       -0.999999        0.0102\n",
+      "LBFGS:   34 12:07:57       -1.000000        0.0077\n",
+      "LBFGS:   35 12:07:57       -1.000000        0.0058\n",
+      "LBFGS:   36 12:07:57       -1.000000        0.0043\n",
+      "LBFGS:   37 12:07:57       -1.000000        0.0032\n",
+      "LBFGS:   38 12:07:57       -1.000000        0.0024\n",
+      "LBFGS:   39 12:07:57       -1.000000        0.0018\n",
+      "LBFGS:   40 12:07:57       -1.000000        0.0014\n",
+      "LBFGS:   41 12:07:57       -1.000000        0.0010\n",
+      "LBFGS:   42 12:07:57       -1.000000        0.0008\n"
      ]
     }
    ],
    "source": [
-    "exe = j.run(\n",
+    "j.run(\n",
     "    executor=pr.create.executor.process(4)\n",
     ")\n",
-    "if exe is not None:\n",
-    "    exe.wait()"
+    "j.wait()"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": 18,
    "id": "7c16a615-0913-4880-9694-2c125285babc",
    "metadata": {
     "tags": []
@@ -295,39 +379,39 @@
        "  \n",
        "    \n",
        "      0\n",
-       "      4\n",
+       "      6\n",
        "      pyiron\n",
-       "      md\n",
-       "      1\n",
+       "      murn\n",
+       "      2\n",
        "      1\n",
-       "      8\n",
+       "      11\n",
        "      /home/poul/pyiron/contrib/notebooks/tinybase/t...\n",
        "      finished\n",
-       "      AseMDTask\n",
+       "      MurnaghanTask\n",
        "    \n",
        "    \n",
        "      1\n",
-       "      5\n",
+       "      7\n",
        "      pyiron\n",
-       "      min\n",
-       "      3\n",
+       "      md\n",
+       "      1\n",
        "      1\n",
-       "      9\n",
+       "      13\n",
        "      /home/poul/pyiron/contrib/notebooks/tinybase/t...\n",
        "      finished\n",
-       "      AseMinimizeTask\n",
+       "      AseMDTask\n",
        "    \n",
        "    \n",
        "      2\n",
-       "      6\n",
+       "      8\n",
        "      pyiron\n",
-       "      murn\n",
-       "      2\n",
+       "      min\n",
+       "      3\n",
        "      1\n",
-       "      11\n",
+       "      14\n",
        "      /home/poul/pyiron/contrib/notebooks/tinybase/t...\n",
        "      finished\n",
-       "      MurnaghanTask\n",
+       "      AseMinimizeTask\n",
        "    \n",
        "  \n",
        "\n",
@@ -335,9 +419,9 @@
       ],
       "text/plain": [
        "   id username  name  jobtype_id  project_id  status_id  \\\n",
-       "0   4   pyiron    md           1           1          8   \n",
-       "1   5   pyiron   min           3           1          9   \n",
-       "2   6   pyiron  murn           2           1         11   \n",
+       "0   6   pyiron  murn           2           1         11   \n",
+       "1   7   pyiron    md           1           1         13   \n",
+       "2   8   pyiron   min           3           1         14   \n",
        "\n",
        "                                            location    status  \\\n",
        "0  /home/poul/pyiron/contrib/notebooks/tinybase/t...  finished   \n",
@@ -345,12 +429,12 @@
        "2  /home/poul/pyiron/contrib/notebooks/tinybase/t...  finished   \n",
        "\n",
        "              type  \n",
-       "0        AseMDTask  \n",
-       "1  AseMinimizeTask  \n",
-       "2    MurnaghanTask  "
+       "0    MurnaghanTask  \n",
+       "1        AseMDTask  \n",
+       "2  AseMinimizeTask  "
       ]
      },
-     "execution_count": 14,
+     "execution_count": 18,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -503,7 +587,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
+   "execution_count": 26,
    "id": "654ce992-b73f-42e3-a32e-2e0dafa7c952",
    "metadata": {
     "tags": []
@@ -515,7 +599,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
+   "execution_count": 27,
    "id": "253237f0-b338-470c-bc54-3c7400a757b7",
    "metadata": {},
    "outputs": [],
@@ -526,7 +610,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 22,
+   "execution_count": 28,
    "id": "c801093b-499e-48a7-8444-77602ed88a96",
    "metadata": {},
    "outputs": [],
@@ -536,7 +620,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 23,
+   "execution_count": 29,
    "id": "1e30b36e-11e6-47d1-836e-cffea7b73cdd",
    "metadata": {},
    "outputs": [],
@@ -546,44 +630,34 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 24,
+   "execution_count": 30,
    "id": "18b5305a-8950-44af-bc2e-c9734b059713",
    "metadata": {},
    "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "INFO:root:Job already finished!\n"
-     ]
-    },
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "CPU times: user 1.5 ms, sys: 727 µs, total: 2.23 ms\n",
-      "Wall time: 2.08 ms\n"
+      "CPU times: user 1.41 s, sys: 1.23 s, total: 2.64 s\n",
+      "Wall time: 10.7 s\n"
      ]
     }
    ],
    "source": [
     "%%time\n",
-    "exe = murn.run(\n",
-    "    executor=pr.create.executor.process(4)\n",
-    ")\n",
-    "if exe is not None:\n",
-    "    exe.wait()"
+    "murn.run(executor='process')\n",
+    "murn.wait()"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 25,
+   "execution_count": 31,
    "id": "836bb2ec-4295-4a3c-b976-7a35d04aad36",
    "metadata": {},
    "outputs": [
     {
      "data": {
-      "image/png": "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\n",
+      "image/png": "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\n",
       "text/plain": [
        "
" ] @@ -608,7 +682,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 35, "id": "79a2bb61-0a5e-4a3a-b195-46d027738a0e", "metadata": {}, "outputs": [], @@ -618,7 +692,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 36, "id": "b4e6e0c9-a2c6-40ab-884e-a46e16c37b04", "metadata": {}, "outputs": [ @@ -656,7 +730,7 @@ "Index: []" ] }, - "execution_count": 27, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -667,7 +741,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 37, "id": "c81e5148-3da3-428d-bf01-4608f7fdb978", "metadata": { "tags": [] @@ -679,7 +753,7 @@ "False" ] }, - "execution_count": 28, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } @@ -690,30 +764,18 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "id": "cef7c46f-551f-401e-96c2-214628e23967", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "murn = pr.create.job.Murnaghan(\"murn\")\n", "murn.input.task = pr.create.task.AseStatic()\n", "murn.input.task.input.calculator = MorsePotential()\n", "murn.input.structure = pr.create.structure.bulk(\"Fe\", a=1.2).to_ase()\n", "murn.input.set_strain_range(.5, 500)\n", - "exe = murn.run(executor='process')\n", - "if exe is not None:\n", - " exe.wait()\n", + "murn.run(executor='process')\n", + "murn.wait()\n", "murn.output.plot()" ] }, @@ -791,7 +853,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 33, "id": "30871447-3e20-46ee-a58e-853d4f4cb5d9", "metadata": {}, "outputs": [ @@ -799,18 +861,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "DEBUG:pyiron_log:Not supported parameter used!\n" + "DEBUG:pyiron_log:Not supported parameter used!\n", + "INFO:root:Job already finished!\n" ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -821,7 +874,8 @@ "j.input.timestep = 3.0\n", "j.input.temperature = 600.0\n", "j.input.output_steps = 20\n", - "j.run(executor='background')" + "j.run(executor='background')\n", + "j.wait()" ] }, { @@ -920,19 +974,25 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 34, "id": "80da39e2-76d1-42e6-977f-241d2683188d", "metadata": {}, "outputs": [ { - "ename": "NameError", - "evalue": "name 'bulk' is not defined", + "ename": "PermissionError", + "evalue": "[Errno 13] Permission denied: '/foo'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[33], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m sub \u001b[38;5;241m=\u001b[39m pr\u001b[38;5;241m.\u001b[39mopen_location(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m/foo\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 2\u001b[0m j \u001b[38;5;241m=\u001b[39m sub\u001b[38;5;241m.\u001b[39mcreate\u001b[38;5;241m.\u001b[39mjob\u001b[38;5;241m.\u001b[39mAseMD(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mmd\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[0;32m----> 3\u001b[0m j\u001b[38;5;241m.\u001b[39minput\u001b[38;5;241m.\u001b[39mstructure \u001b[38;5;241m=\u001b[39m \u001b[43mbulk\u001b[49m(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mFe\u001b[39m\u001b[38;5;124m'\u001b[39m, a\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1.2\u001b[39m, cubic\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\u001b[38;5;241m.\u001b[39mrepeat(\u001b[38;5;241m2\u001b[39m)\n\u001b[1;32m 4\u001b[0m j\u001b[38;5;241m.\u001b[39minput\u001b[38;5;241m.\u001b[39mcalculator \u001b[38;5;241m=\u001b[39m MorsePotential()\n\u001b[1;32m 5\u001b[0m j\u001b[38;5;241m.\u001b[39minput\u001b[38;5;241m.\u001b[39msteps \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m100\u001b[39m\n", - "\u001b[0;31mNameError\u001b[0m: name 'bulk' is not defined" + "\u001b[0;31mPermissionError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[34], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m sub \u001b[38;5;241m=\u001b[39m \u001b[43mpr\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mopen_location\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m/foo\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2\u001b[0m j \u001b[38;5;241m=\u001b[39m sub\u001b[38;5;241m.\u001b[39mcreate\u001b[38;5;241m.\u001b[39mjob\u001b[38;5;241m.\u001b[39mAseMD(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mmd\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[1;32m 3\u001b[0m j\u001b[38;5;241m.\u001b[39minput\u001b[38;5;241m.\u001b[39mstructure \u001b[38;5;241m=\u001b[39m bulk(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mFe\u001b[39m\u001b[38;5;124m'\u001b[39m, a\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1.2\u001b[39m, cubic\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\u001b[38;5;241m.\u001b[39mrepeat(\u001b[38;5;241m2\u001b[39m)\n", + "File \u001b[0;32m~/pyiron/contrib/pyiron_contrib/tinybase/project.py:117\u001b[0m, in \u001b[0;36mProjectAdapter.open_location\u001b[0;34m(cls, location)\u001b[0m\n\u001b[1;32m 115\u001b[0m \u001b[38;5;129m@classmethod\u001b[39m\n\u001b[1;32m 116\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mopen_location\u001b[39m(\u001b[38;5;28mcls\u001b[39m, location):\n\u001b[0;32m--> 117\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mcls\u001b[39m(\u001b[43mProject\u001b[49m\u001b[43m(\u001b[49m\u001b[43mlocation\u001b[49m\u001b[43m)\u001b[49m)\n", + "File \u001b[0;32m~/micromamba/envs/contrib/lib/python3.10/site-packages/pyiron_base/project/generic.py:117\u001b[0m, in \u001b[0;36mProject.__init__\u001b[0;34m(self, path, user, sql_query, default_working_directory)\u001b[0m\n\u001b[1;32m 114\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 115\u001b[0m path \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m--> 117\u001b[0m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mProject\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[38;5;21;43m__init__\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mpath\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mpath\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 119\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39muser \u001b[38;5;241m=\u001b[39m user\n\u001b[1;32m 120\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msql_query \u001b[38;5;241m=\u001b[39m sql_query\n", + "File \u001b[0;32m~/micromamba/envs/contrib/lib/python3.10/site-packages/pyiron_base/project/path.py:224\u001b[0m, in \u001b[0;36mProjectPath.__init__\u001b[0;34m(self, path)\u001b[0m\n\u001b[1;32m 222\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m path \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m 223\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mProjectPath: path is not allowed to be empty!\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m--> 224\u001b[0m generic_path \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_convert_str_to_generic_path\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpath\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 225\u001b[0m \u001b[38;5;28msuper\u001b[39m(ProjectPath, \u001b[38;5;28mself\u001b[39m)\u001b[38;5;241m.\u001b[39m\u001b[38;5;21m__init__\u001b[39m(\n\u001b[1;32m 226\u001b[0m generic_path\u001b[38;5;241m.\u001b[39mroot_path, generic_path\u001b[38;5;241m.\u001b[39mproject_path\n\u001b[1;32m 227\u001b[0m )\n\u001b[1;32m 228\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_history \u001b[38;5;241m=\u001b[39m []\n", + "File \u001b[0;32m~/micromamba/envs/contrib/lib/python3.10/site-packages/pyiron_base/project/path.py:373\u001b[0m, in \u001b[0;36mProjectPath._convert_str_to_generic_path\u001b[0;34m(self, path)\u001b[0m\n\u001b[1;32m 371\u001b[0m path \u001b[38;5;241m=\u001b[39m posixpath\u001b[38;5;241m.\u001b[39mjoin(path_local, path)\n\u001b[1;32m 372\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mexists(path):\n\u001b[0;32m--> 373\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_create_path\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpath\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 374\u001b[0m \u001b[38;5;66;03m# else:\u001b[39;00m\n\u001b[1;32m 375\u001b[0m \u001b[38;5;66;03m# raise ValueError(path, ' does not exist!')\u001b[39;00m\n\u001b[1;32m 376\u001b[0m path \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_windows_path_to_unix_path(path)\n", + "File \u001b[0;32m~/micromamba/envs/contrib/lib/python3.10/site-packages/pyiron_base/project/path.py:394\u001b[0m, in \u001b[0;36mProjectPath._create_path\u001b[0;34m(self, path, rel_path)\u001b[0m\n\u001b[1;32m 392\u001b[0m rel_path \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_windows_path_to_unix_path(rel_path)\n\u001b[1;32m 393\u001b[0m path \u001b[38;5;241m=\u001b[39m posixpath\u001b[38;5;241m.\u001b[39mjoin(path, rel_path)\n\u001b[0;32m--> 394\u001b[0m \u001b[43mos\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmakedirs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpath\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mexist_ok\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/micromamba/envs/contrib/lib/python3.10/os.py:225\u001b[0m, in \u001b[0;36mmakedirs\u001b[0;34m(name, mode, exist_ok)\u001b[0m\n\u001b[1;32m 223\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[1;32m 224\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 225\u001b[0m \u001b[43mmkdir\u001b[49m\u001b[43m(\u001b[49m\u001b[43mname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmode\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 226\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mOSError\u001b[39;00m:\n\u001b[1;32m 227\u001b[0m \u001b[38;5;66;03m# Cannot rely on checking for EEXIST, since the operating system\u001b[39;00m\n\u001b[1;32m 228\u001b[0m \u001b[38;5;66;03m# could give priority to other errors like EACCES or EROFS\u001b[39;00m\n\u001b[1;32m 229\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m exist_ok \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m path\u001b[38;5;241m.\u001b[39misdir(name):\n", + "\u001b[0;31mPermissionError\u001b[0m: [Errno 13] Permission denied: '/foo'" ] } ], @@ -945,7 +1005,8 @@ "j.input.timestep = 3.0\n", "j.input.temperature = 600.0\n", "j.input.output_steps = 20\n", - "j.run(how='process')" + "j.run(how='process')\n", + "j.wait()" ] }, { diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index 7c36a3e1d..911244012 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -115,7 +115,7 @@ def _run_finished(self): def run(self): self._run_machine.step() - def wait(self, until="finished", max_wait=inf, sleep=0.1): + def wait(self, until="finished", timeout=None, sleep=0.1): """ Sleep until specified state of the run state machine is reached. @@ -125,18 +125,20 @@ def wait(self, until="finished", max_wait=inf, sleep=0.1): Args: until (str): wait until the executor has reached this state; must be a valid state name of :class:`.RunMachine.Code`. - max_wait (float): maximum amount of seconds to wait; wait + timeout (float): maximum amount of seconds to wait; wait indefinitely by default sleep (float): amount of seconds to sleep in between status checks Raises: ValueError: if the current state is `init` """ + if timeout is None: + timeout = inf if self._run_machine.state == RunMachine.Code("init"): raise ValueError("Still in state 'init'! Call run() first!") until = RunMachine.Code(until) start = time.monotonic() - while until != self._run_machine.state and time.monotonic() - start < max_wait: + while until != self._run_machine.state and time.monotonic() - start < timeout: time.sleep(sleep) @property diff --git a/pyiron_contrib/tinybase/job.py b/pyiron_contrib/tinybase/job.py index 0642ed141..2fe521c70 100644 --- a/pyiron_contrib/tinybase/job.py +++ b/pyiron_contrib/tinybase/job.py @@ -156,6 +156,22 @@ def run( else: logging.info("Job already finished!") + def wait(self, timeout: Optional[float] = None): + """ + Wait until job is finished. + + Args: + timeout (float, optional): maximum time to wait in seconds; wait + indefinitely by default + + Raises: + ValueError: if job status is not `finished` or `running` + """ + if self.status == "finished": return + if self.status != "running": + raise ValueError("Job not running!") + self._executor.wait(timeout=timeout) + def remove(self): """ Remove the job from the database and storage. From 6628a0b4fa1504141bd449042dba6e5a8609058d Mon Sep 17 00:00:00 2001 From: samwaseda Date: Fri, 23 Jun 2023 16:01:26 +0000 Subject: [PATCH 196/756] remove NotImplementedError --- pyiron_contrib/workflow/node.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 0c800dceb..0ff24e2cd 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -472,25 +472,18 @@ def run(self) -> None: self.running = True self.failed = False - if self.server is None: - try: - if "self" in self._input_args: - function_output = self.node_function( - self=self, **self.inputs.to_value_dict() - ) - else: - function_output = self.node_function(**self.inputs.to_value_dict()) - except Exception as e: - self.running = False - self.failed = True - raise e - self.process_output(function_output) - else: - raise NotImplementedError( - "We currently only support executing the node functionality right on " - "the main python process that the node instance lives on. Come back " - "later for cool new features." - ) + try: + if "self" in self._input_args: + function_output = self.node_function( + self=self, **self.inputs.to_value_dict() + ) + else: + function_output = self.node_function(**self.inputs.to_value_dict()) + except Exception as e: + self.running = False + self.failed = True + raise e + self.process_output(function_output) def process_output(self, function_output): """ From cde4e3c2a738b915663faedf8001509c98faca4c Mon Sep 17 00:00:00 2001 From: Sam Dareska <37879103+samwaseda@users.noreply.github.com> Date: Fri, 23 Jun 2023 18:58:55 +0200 Subject: [PATCH 197/756] Update pyiron_contrib/workflow/node.py Co-authored-by: Liam Huber --- pyiron_contrib/workflow/node.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 0ff24e2cd..a17036f0e 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -14,7 +14,6 @@ from pyiron_contrib.workflow.has_channel import HasChannel from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Inputs, Outputs, Signals -from pyiron_base.jobs.job.extension.server.generic import Server if TYPE_CHECKING: from pyiron_contrib.workflow.workflow import Workflow From d3f3b0351cd1be17f1e8ef68ebe98c1826b183d6 Mon Sep 17 00:00:00 2001 From: Sam Dareska <37879103+samwaseda@users.noreply.github.com> Date: Fri, 23 Jun 2023 18:59:02 +0200 Subject: [PATCH 198/756] Update pyiron_contrib/workflow/node.py Co-authored-by: Liam Huber --- pyiron_contrib/workflow/node.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index a17036f0e..997f5ed51 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -352,7 +352,6 @@ def __init__( elif k not in self._init_keywords: warnings.warn(f"The keyword '{k}' was received but not used.") self.run_on_updates = run_on_updates - self.server = Server() if update_on_instantiation: self.update() From fbe1096cdd50e90a8fac7fdcbc61e567a5d0459d Mon Sep 17 00:00:00 2001 From: Sam Dareska <37879103+samwaseda@users.noreply.github.com> Date: Fri, 23 Jun 2023 18:59:09 +0200 Subject: [PATCH 199/756] Update pyiron_contrib/workflow/workflow.py Co-authored-by: Liam Huber --- pyiron_contrib/workflow/workflow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 79286b9ef..067ddef6a 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -4,7 +4,6 @@ from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.node import Node, node, fast_node, single_value_node from pyiron_contrib.workflow.util import DotDict -from pyiron_base.jobs.job.extension.server.generic import Server class _NodeDecoratorAccess: From 87d40a11d0703c47371b0e8a2fcdd0f745a1a538 Mon Sep 17 00:00:00 2001 From: Sam Dareska <37879103+samwaseda@users.noreply.github.com> Date: Fri, 23 Jun 2023 18:59:16 +0200 Subject: [PATCH 200/756] Update pyiron_contrib/workflow/workflow.py Co-authored-by: Liam Huber --- pyiron_contrib/workflow/workflow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 067ddef6a..eb15b012d 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -121,7 +121,6 @@ class Workflow(HasToDict, HasNodes): def __init__(self, label: str, *nodes: Node, strict_naming=True): super().__init__(strict_naming=strict_naming) self.label = label - self.server = Server() for node in nodes: self.add_node(node) From ea12ed28ae0b4d51470bd7e009715ea16ef090dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:57:39 +0000 Subject: [PATCH 201/756] Bump moto from 4.1.11 to 4.1.12 Bumps [moto](https://github.com/getmoto/moto) from 4.1.11 to 4.1.12. - [Changelog](https://github.com/getmoto/moto/blob/master/CHANGELOG.md) - [Commits](https://github.com/getmoto/moto/compare/4.1.11...4.1.12) --- updated-dependencies: - dependency-name: moto dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 442b22591..1d2979c73 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ 'image': ['scikit-image==0.19.3'], 'generic': [ 'boto3==1.26.155', - 'moto==4.1.11' + 'moto==4.1.12' ], 'workflow': [ 'python>=3.10', From 79e8bc367b3125bd561d9ef247463e427c3a174e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:57:56 +0000 Subject: [PATCH 202/756] Bump boto3 from 1.26.155 to 1.26.160 Bumps [boto3](https://github.com/boto/boto3) from 1.26.155 to 1.26.160. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.26.155...1.26.160) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 442b22591..ec883ce63 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ ], 'image': ['scikit-image==0.19.3'], 'generic': [ - 'boto3==1.26.155', + 'boto3==1.26.160', 'moto==4.1.11' ], 'workflow': [ From a04012d0ab0b8e2394d2ce5f016cc38d652c1fd3 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 26 Jun 2023 11:57:59 +0000 Subject: [PATCH 203/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index bead156dc..ccad7c6df 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -16,7 +16,7 @@ dependencies: - scikit-image =0.19.3 - randspg =0.0.1 - boto3 =1.26.155 -- moto =4.1.11 +- moto =4.1.12 - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.68.0 From 36b6d333c2b151a8a67e16e40f016bb684f8eb31 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 26 Jun 2023 11:58:19 +0000 Subject: [PATCH 204/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 0e95ee1a9..e8143c25e 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -16,7 +16,7 @@ dependencies: - scikit-image =0.19.3 - randspg =0.0.1 - boto3 =1.26.155 -- moto =4.1.11 +- moto =4.1.12 - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.68.0 diff --git a/docs/environment.yml b/docs/environment.yml index 025c64c8b..a9aa30dd6 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -18,7 +18,7 @@ dependencies: - scikit-image =0.19.3 - randspg =0.0.1 - boto3 =1.26.155 -- moto =4.1.11 +- moto =4.1.12 - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.68.0 From a2245adfbb76d7761606e73927910716919fce29 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 26 Jun 2023 11:58:20 +0000 Subject: [PATCH 205/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index bead156dc..2d26c8706 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.155 +- boto3 =1.26.160 - moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 From 60edb6f6fa52c7dbe1cfea36a197447ab7d90f9c Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 26 Jun 2023 12:00:08 +0000 Subject: [PATCH 206/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 0e95ee1a9..70949f576 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.155 +- boto3 =1.26.160 - moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 diff --git a/docs/environment.yml b/docs/environment.yml index 025c64c8b..bf5be50b5 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -17,7 +17,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.155 +- boto3 =1.26.160 - moto =4.1.11 - pycp2k =0.2.2 - typeguard =4.0.0 From 6526011a9c0b86883d328602df1fddec5797f379 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 26 Jun 2023 16:46:31 +0000 Subject: [PATCH 207/756] add docstring and warnings --- pyiron_contrib/workflow/node.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 997f5ed51..f4540a86b 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -304,6 +304,19 @@ class with a function and labels for its output, like so: To see more details on how to use many nodes together, look at the `Workflow` class. + + Comments: + + If you use the function argument `self` in the first position, the + whole node object is inserted there: + + >>> def with_self(self, x): + >>> ... + >>> return x + + For this function, you don't have a freedom to choose `self`, because + pyiron automatically sets the node object there (which is also the + reason why you do not see `self` in the list of inputs). """ def __init__( @@ -365,9 +378,10 @@ def _build_input_channels(self): type_hints = get_type_hints(self.node_function) for ii, (label, value) in enumerate(self._input_args.items()): - if label == "self": + is_self = False + if label == "self": # `self` is reserved for the node object if ii == 0: - continue + is_self = True else: warnings.warn( "`self` is used as an argument but not in the first" @@ -385,13 +399,17 @@ def _build_input_channels(self): try: type_hint = type_hints[label] + if is_self: + warnings.warn("type hint for self ignored") except KeyError: type_hint = None + default = None if value.default is not inspect.Parameter.empty: - default = value.default - else: - default = None + if is_self: + warnings.warn("default value for self ignored") + else: + default = value.default channels.append( InputData( From 3a552385fb094b14a389f3d0f632fcc80a2019db Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 26 Jun 2023 16:56:52 +0000 Subject: [PATCH 208/756] bugfix --- pyiron_contrib/workflow/node.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index f4540a86b..d4766923f 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -411,14 +411,15 @@ def _build_input_channels(self): else: default = value.default - channels.append( - InputData( - label=label, - node=self, - default=default, - type_hint=type_hint, + if not is_self: + channels.append( + InputData( + label=label, + node=self, + default=default, + type_hint=type_hint, + ) ) - ) return channels @property From 40cee09ec53697524c4f9340f4d47851c3daca4a Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 26 Jun 2023 11:01:33 -0700 Subject: [PATCH 209/756] Add something to self --- tests/unit/workflow/test_node.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/unit/workflow/test_node.py b/tests/unit/workflow/test_node.py index 8933d2de1..7b1f353be 100644 --- a/tests/unit/workflow/test_node.py +++ b/tests/unit/workflow/test_node.py @@ -159,13 +159,23 @@ def test_statuses(self): def test_with_self(self): def with_self(self, x: float) -> float: + if hasattr(self, "some_counter"): + self.some_counter += 1 + else: + self.some_counter = 1 return x + 0.1 + node = Node(with_self, "output") self.assertTrue("x" in node.inputs.labels) self.assertFalse("self" in node.inputs.labels) node.inputs.x = 1 node.run() self.assertEqual(node.outputs.output.value, 1.1) + self.assertEqual( + node.some_counter, + 1, + msg="Node functions should be able to modify attributes on the node object." + ) def with_messed_self(x: float, self) -> float: return x + 0.1 with warnings.catch_warnings(record=True) as warning_list: From 43d2f1fb714db1af5f716201f6debbdf713532a6 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 26 Jun 2023 11:02:52 -0700 Subject: [PATCH 210/756] Extend test messages --- tests/unit/workflow/test_node.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/unit/workflow/test_node.py b/tests/unit/workflow/test_node.py index 7b1f353be..e087bae70 100644 --- a/tests/unit/workflow/test_node.py +++ b/tests/unit/workflow/test_node.py @@ -166,11 +166,23 @@ def with_self(self, x: float) -> float: return x + 0.1 node = Node(with_self, "output") - self.assertTrue("x" in node.inputs.labels) - self.assertFalse("self" in node.inputs.labels) + self.assertTrue( + "x" in node.inputs.labels, + msg=f"Expected to find function input 'x' in the node input but got " + f"{node.inputs.labels}" + ) + self.assertFalse( + "self" in node.inputs.labels, + msg="Expected 'self' to be filtered out of node input, but found it in the " + "input labels" + ) node.inputs.x = 1 node.run() - self.assertEqual(node.outputs.output.value, 1.1) + self.assertEqual( + node.outputs.output.value, + 1.1, + msg="Basic node functionality appears to have failed" + ) self.assertEqual( node.some_counter, 1, From 0ece9561689318445f92b6b4156361a494e5a2be Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 26 Jun 2023 11:03:12 -0700 Subject: [PATCH 211/756] Add newlines for readability --- tests/unit/workflow/test_node.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/workflow/test_node.py b/tests/unit/workflow/test_node.py index e087bae70..c773639b3 100644 --- a/tests/unit/workflow/test_node.py +++ b/tests/unit/workflow/test_node.py @@ -188,11 +188,14 @@ def with_self(self, x: float) -> float: 1, msg="Node functions should be able to modify attributes on the node object." ) + def with_messed_self(x: float, self) -> float: return x + 0.1 + with warnings.catch_warnings(record=True) as warning_list: node = Node(with_messed_self, "output") self.assertTrue("self" in node.inputs.labels) + self.assertEqual(len(warning_list), 1) From d11b0dd5ce6755398376b97f4e419d19b24630d1 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 26 Jun 2023 11:03:41 -0700 Subject: [PATCH 212/756] Black: remove excess newline --- tests/unit/workflow/test_node.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/workflow/test_node.py b/tests/unit/workflow/test_node.py index c773639b3..e0e102a72 100644 --- a/tests/unit/workflow/test_node.py +++ b/tests/unit/workflow/test_node.py @@ -199,7 +199,6 @@ def with_messed_self(x: float, self) -> float: self.assertEqual(len(warning_list), 1) - @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestFastNode(unittest.TestCase): def test_instantiation(self): From 7df9a86a9dd787ef0679739cbc6c00ccad6e044c Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 26 Jun 2023 20:23:18 +0200 Subject: [PATCH 213/756] Update version Co-authored-by: Jan Janssen --- .binder/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 0e590e156..529bd1a28 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -20,7 +20,7 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.68.0 -- pympipool =0.5.0 +- pympipool =0.5.1 - distributed =2023.6.0 - python >= 3.10 - lammps From 8ffbc140aa036f58fe185c914487010d5e617e14 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 26 Jun 2023 20:23:26 +0200 Subject: [PATCH 214/756] Update version Co-authored-by: Jan Janssen --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index ca2965cff..6739fedec 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -20,5 +20,5 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.68.0 -- pympipool =0.5.0 +- pympipool =0.5.1 - distributed =2023.6.0 From aaf8c6953598d367eab227df8e3126fc5a1124da Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 26 Jun 2023 20:23:34 +0200 Subject: [PATCH 215/756] Update version Co-authored-by: Jan Janssen --- docs/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/environment.yml b/docs/environment.yml index 00fd3b588..03eef5b9c 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -22,5 +22,5 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.68.0 -- pympipool =0.5.0 +- pympipool =0.5.1 - distributed =2023.6.0 From b0f5c71f4280fac3b068365e0e1381988de5677b Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 26 Jun 2023 20:23:43 +0200 Subject: [PATCH 216/756] Update version Co-authored-by: Jan Janssen --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b6c904e02..311af7aee 100644 --- a/setup.py +++ b/setup.py @@ -60,7 +60,7 @@ ], 'tinybase': [ 'distributed==2023.6.0', - 'pympipool==0.5.0' + 'pympipool==0.5.1' ] }, cmdclass=versioneer.get_cmdclass(), From 01565450bc34dcd49db649d8be53daa65f3ce435 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 26 Jun 2023 12:04:46 -0700 Subject: [PATCH 217/756] Clarify best practices around use of self --- tests/unit/workflow/test_node.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/unit/workflow/test_node.py b/tests/unit/workflow/test_node.py index e0e102a72..29e4f1cc9 100644 --- a/tests/unit/workflow/test_node.py +++ b/tests/unit/workflow/test_node.py @@ -159,6 +159,12 @@ def test_statuses(self): def test_with_self(self): def with_self(self, x: float) -> float: + # Note: Adding internal state to the node like this goes against the best + # practice of keeping nodes "functional". Following python's paradigm of + # giving users lots of power, we want to guarantee that this behaviour is + # _possible_. + # TODO: update this test with a better-conforming example of this power at + # a future date. if hasattr(self, "some_counter"): self.some_counter += 1 else: From 1deee14b153e5ce2537c8c679f583832e02ac828 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 26 Jun 2023 12:43:51 -0700 Subject: [PATCH 218/756] Finish merging Sam's with-self functionality --- pyiron_contrib/workflow/node.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index a450f2a22..b32cb135a 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -483,7 +483,10 @@ def on_run(self): @property def run_args(self) -> dict: - return self.inputs.to_value_dict() + kwargs = self.inputs.to_value_dict() + if "self" in self._input_args: + kwargs["self"] = self + return kwargs def process_run_result(self, function_output): """ From 1c62b714dd01a4b6eb0e0ecc1feed5fb1153da1e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 26 Jun 2023 13:01:49 -0700 Subject: [PATCH 219/756] Remove todo -- it is done in finish_run --- pyiron_contrib/workflow/is_nodal.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py index e7939f76f..c782a30f4 100644 --- a/pyiron_contrib/workflow/is_nodal.py +++ b/pyiron_contrib/workflow/is_nodal.py @@ -180,10 +180,6 @@ def _build_signal_channels(self) -> Signals: signals = Signals() signals.input.run = InputSignal("run", self, self.run) signals.output.ran = OutputSignal("ran", self) - # TODO: Build `run` such that developers inheriting from this class don't need - # to remember to invoke `self.signals.output.ran()`! Probably this will - # involve pulling `run` up into this class and exposing `_on_run` as an - # abstract method to developers (or a similar attack). return signals @property From fe9b55e38c47a849f91d8e26ef746e9d5e3ecb82 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 26 Jun 2023 13:07:13 -0700 Subject: [PATCH 220/756] Remove todo -- it is done in finish_run --- pyiron_contrib/workflow/is_nodal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py index c782a30f4..437ac635f 100644 --- a/pyiron_contrib/workflow/is_nodal.py +++ b/pyiron_contrib/workflow/is_nodal.py @@ -53,7 +53,7 @@ class IsNodal(ABC): signals, which are channels for controlling execution flow. By default, has a `signals.inputs.run` channel which has a callback to the `run` method, and `signals.outputs.ran` which should be called at when the `run` method - is finished (TODO: Don't leave this step up to child class developers!). + is finished. Additional signal channels in derived classes can be added to `signals.inputs` and `signals.outputs` after this mixin class is initialized. From 67a803ace05dafc50d291bf8e9bc3ca5a1054d4c Mon Sep 17 00:00:00 2001 From: Sam Dareska <37879103+samwaseda@users.noreply.github.com> Date: Tue, 27 Jun 2023 08:14:59 +0200 Subject: [PATCH 221/756] Update tests/unit/workflow/test_workflow.py Co-authored-by: Liam Huber --- tests/unit/workflow/test_workflow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index c1b1d80a0..a3d73a65e 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -111,6 +111,8 @@ def plus_one(x: int = 0) -> int: def test_working_directory(self): wf = Workflow("wf") self.assertTrue(wf._working_directory is None) +self.assertIsInstance(wf.working_directory, DirectoryObject) +self.assertTrue(wf.working_directory.path.absolute().endswith(wf.label) if __name__ == '__main__': From 52e29d09e02db3fd7ba3fc3a9ec6aba00020e5f7 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 27 Jun 2023 06:41:55 +0000 Subject: [PATCH 222/756] update tests --- tests/unit/workflow/test_workflow.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index a3d73a65e..8f79561e2 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -3,6 +3,7 @@ from pyiron_contrib.workflow.node import Node from pyiron_contrib.workflow.workflow import Workflow +from pyiron_contrib.workflow.files import DirectoryObject def fnc(x=0): @@ -111,8 +112,10 @@ def plus_one(x: int = 0) -> int: def test_working_directory(self): wf = Workflow("wf") self.assertTrue(wf._working_directory is None) -self.assertIsInstance(wf.working_directory, DirectoryObject) -self.assertTrue(wf.working_directory.path.absolute().endswith(wf.label) + self.assertIsInstance(wf.working_directory, DirectoryObject) + self.assertTrue( + str(wf.working_directory.path.absolute()).endswith(wf.label) + ) if __name__ == '__main__': From 67801b7b2bee6d3f9f2ce00d93cc57836c3db726 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 27 Jun 2023 06:44:43 +0000 Subject: [PATCH 223/756] update tests --- tests/unit/workflow/test_node.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/unit/workflow/test_node.py b/tests/unit/workflow/test_node.py index 29e4f1cc9..336b8fa9b 100644 --- a/tests/unit/workflow/test_node.py +++ b/tests/unit/workflow/test_node.py @@ -359,6 +359,10 @@ def my_node(x: int = 0, y: int = 0, z: int = 0): msg="After the run, all three should now be waiting for updates again" ) + def test_working_directory(self): + n_f = Node(plus_one, "output") + self.assertRaises(n_f.working_directory, ValueError) + if __name__ == '__main__': unittest.main() From 530c85d864743c172befa5459aa0f1aa49290721 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 27 Jun 2023 06:49:14 +0000 Subject: [PATCH 224/756] add test for node to raise an error for not having a parent workflow --- tests/unit/workflow/test_node.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/workflow/test_node.py b/tests/unit/workflow/test_node.py index 336b8fa9b..b3fe8fac7 100644 --- a/tests/unit/workflow/test_node.py +++ b/tests/unit/workflow/test_node.py @@ -361,7 +361,9 @@ def my_node(x: int = 0, y: int = 0, z: int = 0): def test_working_directory(self): n_f = Node(plus_one, "output") - self.assertRaises(n_f.working_directory, ValueError) + with self.assertRaises(ValueError): + n_f.working_directory + # cf. test_workflow.py for the case that it does notraise an error if __name__ == '__main__': From 8ddb37a27394ab6718e44ba5cc1c43d97135650b Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 27 Jun 2023 06:53:45 +0000 Subject: [PATCH 225/756] add working directory tests for node --- tests/unit/workflow/test_workflow.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 8f79561e2..b87c4ee90 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -113,9 +113,9 @@ def test_working_directory(self): wf = Workflow("wf") self.assertTrue(wf._working_directory is None) self.assertIsInstance(wf.working_directory, DirectoryObject) - self.assertTrue( - str(wf.working_directory.path.absolute()).endswith(wf.label) - ) + self.assertTrue(str(wf.working_directory.path).endswith(wf.label)) + wf.add.Node(fnc, "output") + self.assertTrue(str(wf.fnc.working_directory.path).endswith(wf.fnc.label)) if __name__ == '__main__': From 57d3106d66f359e670ca13476f55fa01a4a46a63 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 27 Jun 2023 06:58:59 +0000 Subject: [PATCH 226/756] Format black --- pyiron_contrib/workflow/files.py | 22 +++++++++++----------- pyiron_contrib/workflow/node.py | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pyiron_contrib/workflow/files.py b/pyiron_contrib/workflow/files.py index 38c14fe7d..001deca07 100644 --- a/pyiron_contrib/workflow/files.py +++ b/pyiron_contrib/workflow/files.py @@ -15,14 +15,14 @@ def delete_files_and_directories_recursively(path): def categorize_folder_items(folder_path): types = [ - 'dir', - 'file', - 'mount', - 'symlink', - 'block_device', - 'char_device', - 'fifo', - 'socket', + "dir", + "file", + "mount", + "symlink", + "block_device", + "char_device", + "fifo", + "socket", ] results = {t: [] for t in types} @@ -83,12 +83,12 @@ def file_name(self): def path(self): return self.directory.path / Path(self._file_name) - def write(self, content, mode='x'): - #if self.is_file(): + def write(self, content, mode="x"): + # if self.is_file(): # warnings.warn(f"{self.file_name} already exists") self.directory.write(file_name=self.file_name, content=content, mode=mode) - def read(self, mode='r'): + def read(self, mode="r"): with open(self.path, mode=mode) as f: return f.read() diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 5723bd615..0fc33a50a 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -572,8 +572,8 @@ def working_directory(self): "working directory is available only if the node is" " attached to a workflow" ) - self._working_directory = ( - self.parent.working_directory.create_subdirectory(self.label) + self._working_directory = self.parent.working_directory.create_subdirectory( + self.label ) return self._working_directory From 8120324de045f98712853c34925c21b0fb116cc8 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 27 Jun 2023 07:01:09 +0000 Subject: [PATCH 227/756] delete working directory afterwards --- tests/unit/workflow/test_workflow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index b87c4ee90..0be254935 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -116,6 +116,7 @@ def test_working_directory(self): self.assertTrue(str(wf.working_directory.path).endswith(wf.label)) wf.add.Node(fnc, "output") self.assertTrue(str(wf.fnc.working_directory.path).endswith(wf.fnc.label)) + wf.working_directory.delete() if __name__ == '__main__': From e9f5f239684c686743c7fa7a492e3d8bbcad7672 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 27 Jun 2023 07:09:25 +0000 Subject: [PATCH 228/756] remove unused warnings --- pyiron_contrib/workflow/files.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyiron_contrib/workflow/files.py b/pyiron_contrib/workflow/files.py index 001deca07..d043c1b82 100644 --- a/pyiron_contrib/workflow/files.py +++ b/pyiron_contrib/workflow/files.py @@ -1,5 +1,4 @@ from pathlib import Path -import warnings def delete_files_and_directories_recursively(path): @@ -84,8 +83,6 @@ def path(self): return self.directory.path / Path(self._file_name) def write(self, content, mode="x"): - # if self.is_file(): - # warnings.warn(f"{self.file_name} already exists") self.directory.write(file_name=self.file_name, content=content, mode=mode) def read(self, mode="r"): From e8e5ca7e16ae7ca79af0149be1bcb10572c0009d Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 27 Jun 2023 07:11:56 +0000 Subject: [PATCH 229/756] remove unused FileObject --- pyiron_contrib/workflow/node.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 0fc33a50a..3e03f657d 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -14,7 +14,6 @@ from pyiron_contrib.workflow.has_channel import HasChannel from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Inputs, Outputs, Signals -from pyiron_contrib.workflow.files import FileObject from pyiron_base.jobs.job.extension.server.generic import Server if TYPE_CHECKING: From 1063ee41b1755cf52dfecdeaf14cff94c2cb1847 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 27 Jun 2023 07:12:18 +0000 Subject: [PATCH 230/756] remove unused Server --- pyiron_contrib/workflow/node.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 3e03f657d..650a371b9 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -14,7 +14,6 @@ from pyiron_contrib.workflow.has_channel import HasChannel from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Inputs, Outputs, Signals -from pyiron_base.jobs.job.extension.server.generic import Server if TYPE_CHECKING: from pyiron_contrib.workflow.workflow import Workflow From 08e5444c332f695b0dddc677efc75af0f82d8585 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 27 Jun 2023 07:15:56 +0000 Subject: [PATCH 231/756] satisfy codacy --- tests/unit/workflow/test_files.py | 2 -- tests/unit/workflow/test_node.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/unit/workflow/test_files.py b/tests/unit/workflow/test_files.py index 9c028e589..97dd5c890 100644 --- a/tests/unit/workflow/test_files.py +++ b/tests/unit/workflow/test_files.py @@ -4,11 +4,9 @@ class TestFiles(unittest.TestCase): - @classmethod def setUp(cls): cls.directory = DirectoryObject("test") - @classmethod def tearDown(cls): cls.directory.delete() diff --git a/tests/unit/workflow/test_node.py b/tests/unit/workflow/test_node.py index b3fe8fac7..df179d276 100644 --- a/tests/unit/workflow/test_node.py +++ b/tests/unit/workflow/test_node.py @@ -362,7 +362,7 @@ def my_node(x: int = 0, y: int = 0, z: int = 0): def test_working_directory(self): n_f = Node(plus_one, "output") with self.assertRaises(ValueError): - n_f.working_directory + _ = n_f.working_directory # cf. test_workflow.py for the case that it does notraise an error From f83e33516149f1c92a996390c72b6d5ac607b5de Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 27 Jun 2023 07:18:01 +0000 Subject: [PATCH 232/756] resolve windows problem --- tests/unit/workflow/test_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/workflow/test_files.py b/tests/unit/workflow/test_files.py index 97dd5c890..bc9389586 100644 --- a/tests/unit/workflow/test_files.py +++ b/tests/unit/workflow/test_files.py @@ -25,7 +25,7 @@ def test_create_subdirectory(self): def test_path(self): f = FileObject("test.txt", self.directory) - self.assertEqual(str(f.path), "test/test.txt") + self.assertEqual(str(f.path).replace("\\", "/"), "test/test.txt") def test_read_and_write(self): f = FileObject("test.txt", self.directory) From 2c892720ac4608c7801759243049b2765ee24d22 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 27 Jun 2023 07:40:20 +0000 Subject: [PATCH 233/756] remove one test --- tests/unit/workflow/test_files.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/workflow/test_files.py b/tests/unit/workflow/test_files.py index bc9389586..843f3cce2 100644 --- a/tests/unit/workflow/test_files.py +++ b/tests/unit/workflow/test_files.py @@ -16,7 +16,6 @@ def test_directory_exists(self): def test_write(self): self.directory.write(file_name="test.txt", content="something") self.assertTrue(self.directory.file_exists("test.txt")) - self.assertTrue("test/test.txt" in self.directory.list_content()['file']) self.assertEqual(len(self.directory), 1) def test_create_subdirectory(self): From 5ce34f7e361ba59eb48cb851b4371a10777d0603 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 27 Jun 2023 08:22:26 +0000 Subject: [PATCH 234/756] add try except --- pyiron_contrib/workflow/files.py | 7 +++++-- tests/unit/workflow/test_files.py | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/files.py b/pyiron_contrib/workflow/files.py index d043c1b82..05c6af038 100644 --- a/pyiron_contrib/workflow/files.py +++ b/pyiron_contrib/workflow/files.py @@ -27,8 +27,11 @@ def categorize_folder_items(folder_path): for item in folder_path.iterdir(): for tt in types: - if getattr(item, f"is_{tt}")(): - results[tt].append(str(item)) + try: + if getattr(item, f"is_{tt}")(): + results[tt].append(str(item)) + except NotImplementedError: + pass return results diff --git a/tests/unit/workflow/test_files.py b/tests/unit/workflow/test_files.py index 843f3cce2..bc9389586 100644 --- a/tests/unit/workflow/test_files.py +++ b/tests/unit/workflow/test_files.py @@ -16,6 +16,7 @@ def test_directory_exists(self): def test_write(self): self.directory.write(file_name="test.txt", content="something") self.assertTrue(self.directory.file_exists("test.txt")) + self.assertTrue("test/test.txt" in self.directory.list_content()['file']) self.assertEqual(len(self.directory), 1) def test_create_subdirectory(self): From 67b6682559f99e1d2a614ac3c404f236e77b7904 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 27 Jun 2023 10:46:28 +0000 Subject: [PATCH 235/756] replace file paths --- tests/unit/workflow/test_files.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/unit/workflow/test_files.py b/tests/unit/workflow/test_files.py index bc9389586..304f6a6cb 100644 --- a/tests/unit/workflow/test_files.py +++ b/tests/unit/workflow/test_files.py @@ -16,7 +16,12 @@ def test_directory_exists(self): def test_write(self): self.directory.write(file_name="test.txt", content="something") self.assertTrue(self.directory.file_exists("test.txt")) - self.assertTrue("test/test.txt" in self.directory.list_content()['file']) + self.assertTrue( + "test/test.txt" in [ + ff.replace("\\", "/") + for ff in self.directory.list_content()['file'] + ] + ) self.assertEqual(len(self.directory), 1) def test_create_subdirectory(self): From e52d0a2714617043545490a810a7f81b1f1cd02b Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 27 Jun 2023 12:25:12 -0700 Subject: [PATCH 236/756] Remove ununsed import --- pyiron_contrib/workflow/workflow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index a427a2b08..120412911 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -5,7 +5,6 @@ from pyiron_contrib.workflow.io import Inputs, Outputs from pyiron_contrib.workflow.is_nodal import IsNodal from pyiron_contrib.workflow.node import Node, node, fast_node, single_value_node -from pyiron_contrib.workflow.util import DotDict from pyiron_contrib.workflow.files import DirectoryObject From 7a1bc23c676df37fd64507170a4ccf37fa02be18 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 27 Jun 2023 12:30:34 -0700 Subject: [PATCH 237/756] Move _working_directory up into the parent class --- pyiron_contrib/workflow/is_nodal.py | 7 +++++++ pyiron_contrib/workflow/node.py | 2 -- pyiron_contrib/workflow/workflow.py | 3 +-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py index 437ac635f..02c8efed0 100644 --- a/pyiron_contrib/workflow/is_nodal.py +++ b/pyiron_contrib/workflow/is_nodal.py @@ -8,6 +8,7 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING +from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.io import Signals, InputSignal, OutputSignal if TYPE_CHECKING: @@ -88,6 +89,7 @@ def __init__(self, label: str, *args, **kwargs): # TODO: Move from a traditional "sever" to a tinybase "executor" # TODO: Provide support for actually computing stuff with the server/executor self.signals = self._build_signal_channels() + self._working_directory = None @property @abstractmethod @@ -103,6 +105,11 @@ def outputs(self) -> Outputs: def update(self): pass + @property + @abstractmethod + def working_directory(self) -> DirectoryObject: + pass + @property @abstractmethod def on_run(self) -> callable[..., tuple]: diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 37dab6778..0809581ac 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -363,8 +363,6 @@ def __init__( if update_on_instantiation: self.update() - self._working_directory = None - @property def _input_args(self): return inspect.signature(self.node_function).parameters diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 120412911..134e7f9ef 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -1,11 +1,11 @@ from __future__ import annotations +from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.has_nodes import HasNodes from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Inputs, Outputs from pyiron_contrib.workflow.is_nodal import IsNodal from pyiron_contrib.workflow.node import Node, node, fast_node, single_value_node -from pyiron_contrib.workflow.files import DirectoryObject class _NodeDecoratorAccess: @@ -122,7 +122,6 @@ class Workflow(IsNodal, HasToDict, HasNodes): def __init__(self, label: str, *nodes: Node, strict_naming=True): super().__init__(label=label, strict_naming=strict_naming) - self._working_directory = None for node in nodes: self.add_node(node) From 91dc898486f1c7cafb97d3f0a779ea2b7524148d Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 27 Jun 2023 12:40:17 -0700 Subject: [PATCH 238/756] Refactor: pull parent attribute into base class --- pyiron_contrib/workflow/is_nodal.py | 16 ++++++++++++++-- pyiron_contrib/workflow/node.py | 4 ---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py index 02c8efed0..ca8170de4 100644 --- a/pyiron_contrib/workflow/is_nodal.py +++ b/pyiron_contrib/workflow/is_nodal.py @@ -6,7 +6,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING +from typing import Optional, TYPE_CHECKING from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.io import Signals, InputSignal, OutputSignal @@ -14,6 +14,7 @@ if TYPE_CHECKING: from pyiron_base.jobs.job.extension.server.generic import Server + from pyiron_contrib.workflow.has_nodes import HasNodes from pyiron_contrib.workflow.io import Inputs, Outputs @@ -42,6 +43,8 @@ class IsNodal(ABC): label (str): A name for the nodal object. output (pyiron_contrib.workflow.io.Outputs): **Abstract.** Children must define a property returning an `Outputs` object. + parent (pyiron_contrib.workflow.has_nodes.HasNodes | None): The parent object + owning this, if any. ready (bool): Whether the inputs are all ready and the nodal object is neither already running nor already failed. running (bool): Whether the nodal object has called `run` and has not yet @@ -66,7 +69,13 @@ class IsNodal(ABC): TODO: Once `run_on_updates` is in this class, we can un-abstract this. """ - def __init__(self, label: str, *args, **kwargs): + def __init__( + self, + label: str, + *args, + parent: Optional[HasNodes] = None, + **kwargs + ): """ A mixin class for objects that can form nodes in the graph representation of a computational workflow. @@ -80,6 +89,9 @@ def __init__(self, label: str, *args, **kwargs): """ super().__init__(*args, **kwargs) self.label: str = label + self.parent = parent + if parent is not None: + parent.add(self) self.running = False self.failed = False # TODO: Replace running and failed with a state object diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 0809581ac..f698e551a 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -323,16 +323,12 @@ def __init__( run_on_updates: bool = False, update_on_instantiation: bool = False, channels_requiring_update_after_run: Optional[list[str]] = None, - parent: Optional[Workflow] = None, **kwargs, ): super().__init__( label=label if label is not None else node_function.__name__, # **kwargs, ) - self.parent = parent - if parent is not None: - parent.add(self) if len(output_labels) == 0: raise ValueError("Nodes must have at least one output label.") From 2959fe79e28edd8b720162a9dc92bde0571ebcba Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 27 Jun 2023 12:44:02 -0700 Subject: [PATCH 239/756] Pull working_directory up into base class and allow nodes to have their own working directory without a parent --- pyiron_contrib/workflow/is_nodal.py | 15 ++++++++++----- pyiron_contrib/workflow/node.py | 13 ------------- pyiron_contrib/workflow/workflow.py | 7 ------- 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py index ca8170de4..7b2d74309 100644 --- a/pyiron_contrib/workflow/is_nodal.py +++ b/pyiron_contrib/workflow/is_nodal.py @@ -117,11 +117,6 @@ def outputs(self) -> Outputs: def update(self): pass - @property - @abstractmethod - def working_directory(self) -> DirectoryObject: - pass - @property @abstractmethod def on_run(self) -> callable[..., tuple]: @@ -201,6 +196,16 @@ def _build_signal_channels(self) -> Signals: signals.output.ran = OutputSignal("ran", self) return signals + @property + def working_directory(self): + if self._working_directory is None: + if self.parent is not None and hasattr(self.parent, "working_directory"): + parent_dir = self.parent.working_directory + self._working_directory = parent_dir.create_subdirectory(self.label) + else: + self._working_directory = DirectoryObject(self.label) + return self._working_directory + @property def server(self) -> Server | None: return self._server diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index f698e551a..67aabdd18 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -522,19 +522,6 @@ def to_dict(self): "signals": self.signals.to_dict(), } - @property - def working_directory(self): - if self._working_directory is None: - if self.parent is None: - raise ValueError( - "working directory is available only if the node is" - " attached to a workflow" - ) - self._working_directory = self.parent.working_directory.create_subdirectory( - self.label - ) - return self._working_directory - class FastNode(Node): """ diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 134e7f9ef..778a0c66a 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -1,6 +1,5 @@ from __future__ import annotations -from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.has_nodes import HasNodes from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Inputs, Outputs @@ -126,12 +125,6 @@ def __init__(self, label: str, *nodes: Node, strict_naming=True): for node in nodes: self.add_node(node) - @property - def working_directory(self): - if self._working_directory is None: - self._working_directory = DirectoryObject(self.label) - return self._working_directory - @property def inputs(self) -> Inputs: inputs = Inputs() From 62d36bc9f89f4b881c7472274c3dded6c69028f1 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 27 Jun 2023 13:21:52 -0700 Subject: [PATCH 240/756] Oops, add the parent kwarg back to nodes --- pyiron_contrib/workflow/node.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 67aabdd18..044bd3e69 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -12,6 +12,7 @@ from pyiron_contrib.workflow.is_nodal import IsNodal if TYPE_CHECKING: + from pyiron_contrib.workflow.has_nodes import HasNodes from pyiron_contrib.workflow.workflow import Workflow @@ -323,10 +324,12 @@ def __init__( run_on_updates: bool = False, update_on_instantiation: bool = False, channels_requiring_update_after_run: Optional[list[str]] = None, + parent: Optional[HasNodes] = None, **kwargs, ): super().__init__( label=label if label is not None else node_function.__name__, + parent=parent, # **kwargs, ) if len(output_labels) == 0: From 5bbf4ddc43fafc748c9ca7e9f85b84e6336dd2a2 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 27 Jun 2023 21:15:36 -0700 Subject: [PATCH 241/756] Conform to and test the spec That workflow's can't have parents. --- pyiron_contrib/workflow/workflow.py | 15 +++++++++++++++ tests/unit/workflow/test_workflow.py | 9 +++++++++ 2 files changed, 24 insertions(+) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 778a0c66a..24052a180 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -120,6 +120,7 @@ class Workflow(IsNodal, HasToDict, HasNodes): wrap_as = _NodeDecoratorAccess def __init__(self, label: str, *nodes: Node, strict_naming=True): + self._parent = None super().__init__(label=label, strict_naming=strict_naming) for node in nodes: @@ -171,3 +172,17 @@ def update(self): def on_run(self): # Maybe we need this if workflows can be used as nodes? raise NotImplementedError + + @property + def parent(self) -> None: + return self._parent + + @parent.setter + def parent(self, new_parent: None): + # Currently workflows are not allowed to have a parent -- maybe we want to + # change our minds on this in the future? + if new_parent is not None: + raise TypeError( + f"{self.__class__} may only take None as a parent but got " + f"{type(new_parent)}" + ) \ No newline at end of file diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 576953fca..12e095d51 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -124,6 +124,15 @@ def test_working_directory(self): self.assertTrue(str(wf.fnc.working_directory.path).endswith(wf.fnc.label)) wf.working_directory.delete() + def test_no_parents(self): + wf = Workflow("wf") + wf2 = Workflow("wf2") + wf2.parent = None # Is already the value and should ignore this + with self.assertRaises(TypeError): + # We currently specify workflows shouldn't get parents, this just verifies + # the spec. If that spec changes, test instead that you _can_ set parents! + wf2.parent = wf + if __name__ == '__main__': unittest.main() From 5a0d3e5548af465a787497fa867b9a3ef6048a4d Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 27 Jun 2023 21:18:30 -0700 Subject: [PATCH 242/756] Modify node tests to conform to new spec That nodes can have working directories --- tests/unit/workflow/test_node.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/unit/workflow/test_node.py b/tests/unit/workflow/test_node.py index df179d276..d3e9c960a 100644 --- a/tests/unit/workflow/test_node.py +++ b/tests/unit/workflow/test_node.py @@ -3,6 +3,7 @@ from typing import Optional, Union import warnings +from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.node import ( FastNode, Node, SingleValueNode, node, single_value_node ) @@ -361,9 +362,10 @@ def my_node(x: int = 0, y: int = 0, z: int = 0): def test_working_directory(self): n_f = Node(plus_one, "output") - with self.assertRaises(ValueError): - _ = n_f.working_directory - # cf. test_workflow.py for the case that it does notraise an error + self.assertTrue(n_f._working_directory is None) + self.assertIsInstance(n_f.working_directory, DirectoryObject) + self.assertTrue(str(n_f.working_directory.path).endswith(n_f.label)) + n_f.working_directory.delete() if __name__ == '__main__': From 738ac1b7a1ef2ffb848d6f0f411c578668feaacd Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 27 Jun 2023 21:18:42 -0700 Subject: [PATCH 243/756] Order imports alphabetically --- tests/unit/workflow/test_workflow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 12e095d51..5a5f9796d 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -1,9 +1,10 @@ import unittest from sys import version_info +from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.node import Node from pyiron_contrib.workflow.workflow import Workflow -from pyiron_contrib.workflow.files import DirectoryObject + def fnc(x=0): From 265406135d836bbeb6dd8aede33bc926c22d7510 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 27 Jun 2023 21:18:48 -0700 Subject: [PATCH 244/756] PEP8 --- tests/unit/workflow/test_workflow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 5a5f9796d..12a8b670c 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -6,7 +6,6 @@ from pyiron_contrib.workflow.workflow import Workflow - def fnc(x=0): return x + 1 From 7e8827d7132c385eeebfd4caedbd03f1bed60ae3 Mon Sep 17 00:00:00 2001 From: Liam Huber Date: Wed, 28 Jun 2023 06:56:53 -0700 Subject: [PATCH 245/756] Update pyiron_contrib/workflow/workflow.py Co-authored-by: Sam Dareska <37879103+samwaseda@users.noreply.github.com> --- pyiron_contrib/workflow/workflow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 24052a180..ff06d35cb 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -175,7 +175,7 @@ def on_run(self): @property def parent(self) -> None: - return self._parent + return None @parent.setter def parent(self, new_parent: None): From f774a135f9770144887a2e7ea47247eb57752384 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 08:25:46 -0700 Subject: [PATCH 246/756] Better clarify intent to future devs --- pyiron_contrib/workflow/workflow.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index ff06d35cb..818dbd6c3 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -120,7 +120,7 @@ class Workflow(IsNodal, HasToDict, HasNodes): wrap_as = _NodeDecoratorAccess def __init__(self, label: str, *nodes: Node, strict_naming=True): - self._parent = None + self._parent = None # Necessary to pre-populate public property/setter var super().__init__(label=label, strict_naming=strict_naming) for node in nodes: @@ -180,7 +180,9 @@ def parent(self) -> None: @parent.setter def parent(self, new_parent: None): # Currently workflows are not allowed to have a parent -- maybe we want to - # change our minds on this in the future? + # change our minds on this in the future? If we do, we can just expose `parent` + # as a kwarg and roll back this private var/property/setter protection and let + # the super call in init handle everything if new_parent is not None: raise TypeError( f"{self.__class__} may only take None as a parent but got " From be0bdfa1fdff591f0f2200c311a0cd2db668fa01 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 10:06:18 -0700 Subject: [PATCH 247/756] PEP8 newline --- pyiron_contrib/workflow/workflow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 818dbd6c3..2ecc80580 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -187,4 +187,4 @@ def parent(self, new_parent: None): raise TypeError( f"{self.__class__} may only take None as a parent but got " f"{type(new_parent)}" - ) \ No newline at end of file + ) From 9c5d2e98b6f6e06640780a7476f4e703a1dfa700 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 10:18:23 -0700 Subject: [PATCH 248/756] Make all nodes have to_dict --- pyiron_contrib/workflow/is_nodal.py | 3 ++- pyiron_contrib/workflow/node.py | 3 +-- pyiron_contrib/workflow/workflow.py | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py index 7b2d74309..8e68c0507 100644 --- a/pyiron_contrib/workflow/is_nodal.py +++ b/pyiron_contrib/workflow/is_nodal.py @@ -9,6 +9,7 @@ from typing import Optional, TYPE_CHECKING from pyiron_contrib.workflow.files import DirectoryObject +from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Signals, InputSignal, OutputSignal if TYPE_CHECKING: @@ -18,7 +19,7 @@ from pyiron_contrib.workflow.io import Inputs, Outputs -class IsNodal(ABC): +class IsNodal(HasToDict, ABC): """ A mixin class for objects that can form nodes in the graph representation of a computational workflow. diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 044bd3e69..3a96b5f70 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -7,7 +7,6 @@ from pyiron_contrib.workflow.channels import InputData, OutputData from pyiron_contrib.workflow.has_channel import HasChannel -from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Inputs, Outputs, Signals from pyiron_contrib.workflow.is_nodal import IsNodal @@ -16,7 +15,7 @@ from pyiron_contrib.workflow.workflow import Workflow -class Node(IsNodal, HasToDict): +class Node(IsNodal): """ Nodes have input and output data channels that interface with the outside world, and a callable that determines what they actually compute. After running, their output diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 2ecc80580..64bf04285 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -1,7 +1,6 @@ from __future__ import annotations from pyiron_contrib.workflow.has_nodes import HasNodes -from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Inputs, Outputs from pyiron_contrib.workflow.is_nodal import IsNodal from pyiron_contrib.workflow.node import Node, node, fast_node, single_value_node @@ -15,7 +14,7 @@ class _NodeDecoratorAccess: single_value_node = single_value_node -class Workflow(IsNodal, HasToDict, HasNodes): +class Workflow(IsNodal, HasNodes): """ Workflows are an abstraction for holding a collection of related nodes. From 07381125418a1bafee82181e948dbfb99776f3b1 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 10:45:20 -0700 Subject: [PATCH 249/756] Pull update up to the parent class --- pyiron_contrib/workflow/is_nodal.py | 10 ++++++---- pyiron_contrib/workflow/node.py | 7 ++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py index 8e68c0507..e022ee603 100644 --- a/pyiron_contrib/workflow/is_nodal.py +++ b/pyiron_contrib/workflow/is_nodal.py @@ -75,6 +75,7 @@ def __init__( label: str, *args, parent: Optional[HasNodes] = None, + run_on_updates: bool = False, **kwargs ): """ @@ -103,6 +104,7 @@ def __init__( # TODO: Provide support for actually computing stuff with the server/executor self.signals = self._build_signal_channels() self._working_directory = None + self.run_on_updates: bool = run_on_updates @property @abstractmethod @@ -114,10 +116,6 @@ def inputs(self) -> Inputs: def outputs(self) -> Outputs: pass - @abstractmethod - def update(self): - pass - @property @abstractmethod def on_run(self) -> callable[..., tuple]: @@ -197,6 +195,10 @@ def _build_signal_channels(self) -> Signals: signals.output.ran = OutputSignal("ran", self) return signals + def update(self) -> None: + if self.run_on_updates and self.ready: + self.run() + @property def working_directory(self): if self._working_directory is None: diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 3a96b5f70..5fa06a68a 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -351,12 +351,13 @@ def __init__( self._verify_that_channels_requiring_update_all_exist() self.run_on_updates = False + # Temporarily disable running on updates to set all initial values at once for k, v in kwargs.items(): if k in self.inputs.labels: self.inputs[k] = v elif k not in self._init_keywords: warnings.warn(f"The keyword '{k}' was received but not used.") - self.run_on_updates = run_on_updates + self.run_on_updates = run_on_updates # Restore provided value if update_on_instantiation: self.update() @@ -476,10 +477,6 @@ def _verify_that_channels_requiring_update_all_exist(self): f"not found among the input channels ({self.inputs.labels})" ) - def update(self) -> None: - if self.run_on_updates and self.ready: - self.run() - @property def on_run(self): return self.node_function From 0068072241963a370bae22f3b1bf11b3ffa71c04 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 10:56:05 -0700 Subject: [PATCH 250/756] Replace HasNodes mix-in behaviour with Composite inheritance behaviour --- .../workflow/{has_nodes.py => composite.py} | 83 ++++++++++++++++--- pyiron_contrib/workflow/is_nodal.py | 6 +- pyiron_contrib/workflow/node.py | 4 +- .../workflow/node_library/package.py | 4 +- pyiron_contrib/workflow/workflow.py | 26 ++---- 5 files changed, 85 insertions(+), 38 deletions(-) rename pyiron_contrib/workflow/{has_nodes.py => composite.py} (66%) diff --git a/pyiron_contrib/workflow/has_nodes.py b/pyiron_contrib/workflow/composite.py similarity index 66% rename from pyiron_contrib/workflow/has_nodes.py rename to pyiron_contrib/workflow/composite.py index 964f0fd30..d1856a4e9 100644 --- a/pyiron_contrib/workflow/has_nodes.py +++ b/pyiron_contrib/workflow/composite.py @@ -1,3 +1,8 @@ +""" +A base class for nodal objects that have internal structure -- i.e. they hold a +sub-graph +""" + from __future__ import annotations from abc import ABC @@ -5,25 +10,79 @@ from typing import Optional from warnings import warn -from pyiron_contrib.workflow.node import Node +from pyiron_contrib.workflow.is_nodal import IsNodal +from pyiron_contrib.workflow.node import Node, node, fast_node, single_value_node from pyiron_contrib.workflow.node_library import atomistics, standard from pyiron_contrib.workflow.node_library.package import NodePackage from pyiron_contrib.workflow.util import DotDict -class HasNodes(ABC): +class _NodeDecoratorAccess: + """An intermediate container to store node-creating decorators as class methods.""" + + node = node + fast_node = fast_node + single_value_node = single_value_node + + +class Composite(IsNodal, ABC): """ - A mixin class for classes which hold a graph of nodes. + A base class for nodes that have internal structure -- i.e. they hold a sub-graph. + + Item and attribute access is modified to give access to owned nodes. + Adding a node with the `add` functionality or by direct attribute assignment sets + this object as the parent of that node. + + Offers a class method (`wrap_as`) to give easy access to the node-creating + decorators. - Attribute assignment is overriden such that assignment of a `Node` instance adds - it directly to the collection of nodes. + Specifies the required `on_run()` to call `run()` on a subset of owned nodes, i.e. + to kick-start computation on the owned sub-graph. + By default, `run()` will be called on all owned nodes who's input has no + connections, but this can be overridden to specify particular nodes to use instead. + + Does not specify `input` and `output` as demanded by the parent class; this + requirement is still passed on to children. + + Attributes: + TBA + + Methods: + TBA """ - def __init__(self, *args, strict_naming=True, **kwargs): - super().__init__(*args, **kwargs) - self.nodes: DotDict = DotDict() + wrap_as = _NodeDecoratorAccess # Class method access to decorators + # Allows users/devs to easily create new nodes when using children of this class + + def __init__( + self, + label: str, + *args, + parent: Optional[Composite] = None, + strict_naming: bool = True, + **kwargs + ): + super().__init__(*args, label=label, parent=parent, **kwargs) + self.stict_naming: bool = strict_naming + self.nodes: DotDict[str: IsNodal] = DotDict() self.add: NodeAdder = NodeAdder(self) - self.strict_naming: bool = strict_naming + + def to_dict(self): + return { + "label": self.label, + "nodes": {n.label: n.to_dict() for n in self.nodes.values()}, + } + + @property + def starting_nodes(self) -> list[IsNodal]: + return [ + node for node in self.nodes.values() + if node.outputs.connected and not node.inputs.connected + ] + + def on_run(self): + for node in self.starting_nodes: + node.run() def add_node(self, node: Node, label: Optional[str] = None) -> None: """ @@ -134,15 +193,15 @@ def __dir__(self): class NodeAdder: """ - This class provides a layer of misdirection so that `HasNodes` objects can set + This class provides a layer of misdirection so that `Composite` objects can set themselves as the parent of owned nodes. It also provides access to packages of nodes and the ability to register new packages. """ - def __init__(self, parent: HasNodes): - self._parent: HasNodes = parent + def __init__(self, parent: Composite): + self._parent: Composite = parent self.register_nodes("atomistics", *atomistics.nodes) self.register_nodes("standard", *standard.nodes) diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py index e022ee603..3451c0d52 100644 --- a/pyiron_contrib/workflow/is_nodal.py +++ b/pyiron_contrib/workflow/is_nodal.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: from pyiron_base.jobs.job.extension.server.generic import Server - from pyiron_contrib.workflow.has_nodes import HasNodes + from pyiron_contrib.workflow.composite import Composite from pyiron_contrib.workflow.io import Inputs, Outputs @@ -44,7 +44,7 @@ class IsNodal(HasToDict, ABC): label (str): A name for the nodal object. output (pyiron_contrib.workflow.io.Outputs): **Abstract.** Children must define a property returning an `Outputs` object. - parent (pyiron_contrib.workflow.has_nodes.HasNodes | None): The parent object + parent (pyiron_contrib.workflow.composite.Composite | None): The parent object owning this, if any. ready (bool): Whether the inputs are all ready and the nodal object is neither already running nor already failed. @@ -74,7 +74,7 @@ def __init__( self, label: str, *args, - parent: Optional[HasNodes] = None, + parent: Optional[Composite] = None, run_on_updates: bool = False, **kwargs ): diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 5fa06a68a..3951def51 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -11,7 +11,7 @@ from pyiron_contrib.workflow.is_nodal import IsNodal if TYPE_CHECKING: - from pyiron_contrib.workflow.has_nodes import HasNodes + from pyiron_contrib.workflow.composite import Composite from pyiron_contrib.workflow.workflow import Workflow @@ -323,7 +323,7 @@ def __init__( run_on_updates: bool = False, update_on_instantiation: bool = False, channels_requiring_update_after_run: Optional[list[str]] = None, - parent: Optional[HasNodes] = None, + parent: Optional[Composite] = None, **kwargs, ): super().__init__( diff --git a/pyiron_contrib/workflow/node_library/package.py b/pyiron_contrib/workflow/node_library/package.py index 62f94bcfc..d0a27009e 100644 --- a/pyiron_contrib/workflow/node_library/package.py +++ b/pyiron_contrib/workflow/node_library/package.py @@ -7,7 +7,7 @@ from pyiron_contrib.workflow.util import DotDict if TYPE_CHECKING: - from pyiron_contrib.workflow.workflow import Workflow + from pyiron_contrib.workflow.composite import Composite class NodePackage(DotDict): @@ -21,7 +21,7 @@ class NodePackage(DotDict): but to update an existing node the `update` method must be used. """ - def __init__(self, parent: Workflow, *node_classes: Node): + def __init__(self, parent: Composite, *node_classes: Node): super().__init__() self.__dict__["_parent"] = parent # Avoid the __setattr__ override for node in node_classes: diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 64bf04285..de299c944 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -1,20 +1,16 @@ from __future__ import annotations -from pyiron_contrib.workflow.has_nodes import HasNodes -from pyiron_contrib.workflow.io import Inputs, Outputs -from pyiron_contrib.workflow.is_nodal import IsNodal -from pyiron_contrib.workflow.node import Node, node, fast_node, single_value_node +from typing import TYPE_CHECKING +from pyiron_contrib.workflow.composite import Composite +from pyiron_contrib.workflow.io import Inputs, Outputs -class _NodeDecoratorAccess: - """An intermediate container to store node-creating decorators as class methods.""" - node = node - fast_node = fast_node - single_value_node = single_value_node +if TYPE_CHECKING: + from pyiron_contrib.workflow.node import Node -class Workflow(IsNodal, HasNodes): +class Workflow(Composite): """ Workflows are an abstraction for holding a collection of related nodes. @@ -116,11 +112,9 @@ class Workflow(IsNodal, HasNodes): integrity of workflows when they're used somewhere else? """ - wrap_as = _NodeDecoratorAccess - def __init__(self, label: str, *nodes: Node, strict_naming=True): self._parent = None # Necessary to pre-populate public property/setter var - super().__init__(label=label, strict_naming=strict_naming) + super().__init__(label=label, parent=None, strict_naming=strict_naming) for node in nodes: self.add_node(node) @@ -143,12 +137,6 @@ def outputs(self) -> Outputs: outputs[f"{node_label}_{channel.label}"] = channel return outputs - def to_dict(self): - return { - "label": self.label, - "nodes": {n.label: n.to_dict() for n in self.nodes.values()}, - } - def to_node(self): """ Export the workflow to a macro node, with the currently exposed IO mapped to From f9b04485a814aa0f6f1b2d6c38a322ce22256b0c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 10:58:02 -0700 Subject: [PATCH 251/756] Allow workflow to inherit on_run and update behaviour from Composite --- pyiron_contrib/workflow/workflow.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index de299c944..d3b634239 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -151,15 +151,6 @@ def serialize(self): def deserialize(self, source): raise NotImplementedError - def update(self): - for node in self.nodes.values(): - if node.outputs.connected and not node.inputs.connected: - node.update() - - def on_run(self): - # Maybe we need this if workflows can be used as nodes? - raise NotImplementedError - @property def parent(self) -> None: return None From f727e74b72b51e54e9f2fee200c67ca1cbc573a0 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 11:08:33 -0700 Subject: [PATCH 252/756] Add run_on_updates docs to parent class --- pyiron_contrib/workflow/is_nodal.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py index 3451c0d52..564d19ee4 100644 --- a/pyiron_contrib/workflow/is_nodal.py +++ b/pyiron_contrib/workflow/is_nodal.py @@ -48,6 +48,8 @@ class IsNodal(HasToDict, ABC): owning this, if any. ready (bool): Whether the inputs are all ready and the nodal object is neither already running nor already failed. + run_on_updates (bool): Whether to run when you are updated and all your input + is ready and your status does not prohibit running. (Default is False). running (bool): Whether the nodal object has called `run` and has not yet received output from from this call. (Default is False.) server (Optional[pyiron_base.jobs.job.extension.server.generic.Server]): A From 856a15255b9ba4186651a314771ef7be7df05ef5 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 11:14:23 -0700 Subject: [PATCH 253/756] Fix typo --- pyiron_contrib/workflow/composite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index d1856a4e9..015c932b5 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -63,7 +63,7 @@ def __init__( **kwargs ): super().__init__(*args, label=label, parent=parent, **kwargs) - self.stict_naming: bool = strict_naming + self.strict_naming: bool = strict_naming self.nodes: DotDict[str: IsNodal] = DotDict() self.add: NodeAdder = NodeAdder(self) From 4cfaa8a4a33ea926ff7830f298be0bb4fe47527c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 11:20:39 -0700 Subject: [PATCH 254/756] Allow composite nodes to specify what nodes should start runs --- pyiron_contrib/workflow/composite.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 015c932b5..e13dc20a5 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -66,6 +66,7 @@ def __init__( self.strict_naming: bool = strict_naming self.nodes: DotDict[str: IsNodal] = DotDict() self.add: NodeAdder = NodeAdder(self) + self.starting_nodes: None | list[Node] = None def to_dict(self): return { @@ -74,14 +75,16 @@ def to_dict(self): } @property - def starting_nodes(self) -> list[IsNodal]: + def upstream_nodes(self) -> list[IsNodal]: return [ node for node in self.nodes.values() if node.outputs.connected and not node.inputs.connected ] def on_run(self): - for node in self.starting_nodes: + starting_nodes = self.upstream_nodes if self.starting_nodes is None \ + else self.starting_nodes + for node in starting_nodes: node.run() def add_node(self, node: Node, label: Optional[str] = None) -> None: From 8bf8ef37357d66316d710c78c4d51e6b8bb15580 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 11:24:02 -0700 Subject: [PATCH 255/756] Update docs --- pyiron_contrib/workflow/composite.py | 22 ++++++++++++++++++---- pyiron_contrib/workflow/workflow.py | 19 ++++++++++++------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index e13dc20a5..3616d7736 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -33,22 +33,36 @@ class Composite(IsNodal, ABC): Adding a node with the `add` functionality or by direct attribute assignment sets this object as the parent of that node. + Guarantees that each owned node is unique, and does not belong to any other parents. + Offers a class method (`wrap_as`) to give easy access to the node-creating decorators. Specifies the required `on_run()` to call `run()` on a subset of owned nodes, i.e. to kick-start computation on the owned sub-graph. - By default, `run()` will be called on all owned nodes who's input has no - connections, but this can be overridden to specify particular nodes to use instead. + 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. Does not specify `input` and `output` as demanded by the parent class; this requirement is still passed on to children. Attributes: - TBA + nodes (DotDict[Node]): The owned nodes that form the composite subgraph. + strict_naming (bool): When true, repeated assignment of a new node to an + existing node label will raise an error, otherwise the label gets appended + with an index and the assignment proceeds. (Default is true: disallow assigning + to existing labels.) + add (NodeAdder): A tool for adding new nodes to this subgraph. + upstream_nodes (list[Node]): All the owned nodes that have output connections + but no input connections, i.e. the upstream-most nodes. + starting_nodes (None | list[Node]): A subset of the owned nodes to be used on + running. (Default is None, running falls back on using the `upstream_nodes`.) Methods: - TBA + add(node: Node): Add the node instance to this subgraph. + remove(node: Node): Break all connections the node has, remove it from this + subgraph, and set its parent to `None`. """ wrap_as = _NodeDecoratorAccess # Class method access to decorators diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index d3b634239..804dbe76b 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -1,3 +1,9 @@ +""" +Provides the main workhorse class for creating and running workflows. + +This class is intended as the single point of entry for users making an import. +""" + from __future__ import annotations from typing import TYPE_CHECKING @@ -12,15 +18,14 @@ class Workflow(Composite): """ - Workflows are an abstraction for holding a collection of related nodes. + Workflows are a dynamic composite node -- i.e. they hold and run a collection of + nodes (a subgraph) which can be dynamically modified (adding and removing nodes, + and modifying their connections). Nodes can be added to the workflow at instantiation or with dot-assignment later on. They are then accessible either under the `nodes` dot-dictionary, or just directly by dot-access on the workflow object itself. - The workflow guarantees that each node it owns has a unique within the scope of this - workflow, and that each node instance appears only once. - Using the `input` and `output` attributes, the workflow gives access to all the IO channels among its nodes which are currently unconnected. @@ -43,9 +48,9 @@ class Workflow(Composite): By default, the node naming scheme is strict, so if you try to add a node to a label that already exists, you will get an error. This behaviour can be changed - at instantiation with the `strict_naming` kwarg, or afterwards with the - `(de)activate_strict_naming()` method(s). When deactivated, repeated assignments - to the same label just get appended with an index: + at instantiation with the `strict_naming` kwarg, or afterwards by assigning a + bool to this property. When deactivated, repeated assignments to the same label + just get appended with an index: >>> wf.deactivate_strict_naming() >>> wf.my_node = Node(fnc, "y", x=0) >>> wf.my_node = Node(fnc, "y", x=1) From 285d13d9cc1e8d8721800820c0f96dc3d8e6115c Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 28 Jun 2023 11:29:54 -0700 Subject: [PATCH 256/756] Remove variable that's not used any more --- pyiron_contrib/workflow/workflow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 64bf04285..445c8f341 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -119,7 +119,6 @@ class Workflow(IsNodal, HasNodes): wrap_as = _NodeDecoratorAccess def __init__(self, label: str, *nodes: Node, strict_naming=True): - self._parent = None # Necessary to pre-populate public property/setter var super().__init__(label=label, strict_naming=strict_naming) for node in nodes: From 506735af4d843f29b4109e35358ceaa100d74959 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 13:50:57 -0700 Subject: [PATCH 257/756] Update type hints to use base class --- pyiron_contrib/workflow/channels.py | 23 ++++++++++--------- pyiron_contrib/workflow/composite.py | 20 ++++++++-------- .../workflow/node_library/package.py | 12 +++++----- pyiron_contrib/workflow/workflow.py | 4 ++-- 4 files changed, 30 insertions(+), 29 deletions(-) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index e08f4d4a0..f106ad29e 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -32,7 +32,7 @@ ) if typing.TYPE_CHECKING: - from pyiron_contrib.workflow.node import Node + from pyiron_contrib.workflow.is_nodal import IsNodal class Channel(HasChannel, HasToDict, ABC): @@ -51,25 +51,26 @@ class Channel(HasChannel, HasToDict, ABC): Attributes: label (str): The name of the channel. - node (pyiron_contrib.workflow.node.Node): The node to which the channel belongs. + node (pyiron_contrib.workflow.is_nodal.IsNodal): The node to which the channel + belongs. connections (list[Channel]): Other channels to which this channel is connected. """ def __init__( self, label: str, - node: Node, + node: IsNodal, ): """ Make a new channel. Args: label (str): A name for the channel. - node (pyiron_contrib.workflow.node.Node): The node to which the channel - belongs. + node (pyiron_contrib.workflow.is_nodal.IsNodal): The node to which the + channel belongs. """ self.label: str = label - self.node: Node = node + self.node: IsNodal = node self.connections: list[Channel] = [] @abstractmethod @@ -180,7 +181,7 @@ class DataChannel(Channel, ABC): def __init__( self, label: str, - node: Node, + node: IsNodal, default: typing.Optional[typing.Any] = None, type_hint: typing.Optional[typing.Any] = None, ): @@ -312,7 +313,7 @@ class InputData(DataChannel): def __init__( self, label: str, - node: Node, + node: IsNodal, default: typing.Optional[typing.Any] = None, type_hint: typing.Optional[typing.Any] = None, strict_connections: bool = True, @@ -447,7 +448,7 @@ class InputSignal(SignalChannel): def __init__( self, label: str, - node: Node, + node: IsNodal, callback: callable, ): """ @@ -455,8 +456,8 @@ def __init__( Args: label (str): A name for the channel. - node (pyiron_contrib.workflow.node.Node): The node to which the channel - belongs. + node (pyiron_contrib.workflow.is_nodal.IsNodal): The node to which the + channel belongs. callback (callable): An argument-free callback to invoke when calling this object. """ diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 3616d7736..a20193fcb 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -80,7 +80,7 @@ def __init__( self.strict_naming: bool = strict_naming self.nodes: DotDict[str: IsNodal] = DotDict() self.add: NodeAdder = NodeAdder(self) - self.starting_nodes: None | list[Node] = None + self.starting_nodes: None | list[IsNodal] = None def to_dict(self): return { @@ -101,7 +101,7 @@ def on_run(self): for node in starting_nodes: node.run() - def add_node(self, node: Node, label: Optional[str] = None) -> None: + def add_node(self, node: IsNodal, label: Optional[str] = None) -> None: """ Assign a node to the parent. Optionally provide a new label for that node. @@ -112,7 +112,7 @@ def add_node(self, node: Node, label: Optional[str] = None) -> None: Raises: TypeError: If the """ - if not isinstance(node, Node): + if not isinstance(node, IsNodal): raise TypeError( f"Only new node instances may be added, but got {type(node)}." ) @@ -127,7 +127,7 @@ def add_node(self, node: Node, label: Optional[str] = None) -> None: def _get_unique_label(self, label): if label in self.__dir__(): - if isinstance(getattr(self, label), Node): + if isinstance(getattr(self, label), IsNodal): if self.strict_naming: raise AttributeError( f"{label} is already the label for a node. Please remove it " @@ -155,7 +155,7 @@ def _add_suffix_to_label(self, label): ) return new_label - def _ensure_node_has_no_other_parent(self, node: Node): + def _ensure_node_has_no_other_parent(self, node: IsNodal): if node.parent is not None and node.parent is not self: raise ValueError( f"The node ({node.label}) already belongs to the parent " @@ -163,7 +163,7 @@ def _ensure_node_has_no_other_parent(self, node: Node): f"add it to this parent ({self.label})." ) - def _ensure_node_is_not_duplicated(self, node: Node, label: str): + def _ensure_node_is_not_duplicated(self, node: IsNodal, label: str): if ( node.parent is self and label != node.label @@ -175,16 +175,16 @@ def _ensure_node_is_not_duplicated(self, node: Node, label: str): ) del self.nodes[node.label] - def remove(self, node: Node | str): - if isinstance(node, Node): + def remove(self, node: IsNodal | str): + if isinstance(node, IsNodal): node.parent = None node.disconnect() del self.nodes[node.label] else: del self.nodes[node] - def __setattr__(self, label: str, node: Node): - if isinstance(node, Node): + def __setattr__(self, label: str, node: IsNodal): + if isinstance(node, IsNodal): self.add_node(node, label=label) else: super().__setattr__(label, node) diff --git a/pyiron_contrib/workflow/node_library/package.py b/pyiron_contrib/workflow/node_library/package.py index d0a27009e..74484242d 100644 --- a/pyiron_contrib/workflow/node_library/package.py +++ b/pyiron_contrib/workflow/node_library/package.py @@ -3,7 +3,7 @@ from functools import partial from typing import TYPE_CHECKING -from pyiron_contrib.workflow.node import Node +from pyiron_contrib.workflow.is_nodal import IsNodal from pyiron_contrib.workflow.util import DotDict if TYPE_CHECKING: @@ -21,7 +21,7 @@ class NodePackage(DotDict): but to update an existing node the `update` method must be used. """ - def __init__(self, parent: Composite, *node_classes: Node): + def __init__(self, parent: Composite, *node_classes: IsNodal): super().__init__() self.__dict__["_parent"] = parent # Avoid the __setattr__ override for node in node_classes: @@ -35,16 +35,16 @@ def __setitem__(self, key, value): f"The name {key} is already an attribute of this " f"{self.__class__.__name__} instance." ) - if not isinstance(value, type) or not issubclass(value, Node): + if not isinstance(value, type) or not issubclass(value, IsNodal): raise TypeError( - f"Can only set members that are (sub)classes of {Node.__name__}, but " - f"got {type(value)}" + f"Can only set members that are (sub)classes of {IsNodal.__name__}, " + f"but got {type(value)}" ) super().__setitem__(key, value) def __getitem__(self, item): value = super().__getitem__(item) - if issubclass(value, Node): + if issubclass(value, IsNodal): return partial(value, parent=self._parent) else: return value diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 16b3e27da..ee6c49b28 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: - from pyiron_contrib.workflow.node import Node + from pyiron_contrib.workflow.is_nodal import IsNodal class Workflow(Composite): @@ -117,7 +117,7 @@ class Workflow(Composite): integrity of workflows when they're used somewhere else? """ - def __init__(self, label: str, *nodes: Node, strict_naming=True): + def __init__(self, label: str, *nodes: IsNodal, strict_naming=True): super().__init__(label=label, parent=None, strict_naming=strict_naming) for node in nodes: From 03f6cef8d40141801f6625f9395ca3d58a911c13 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 13:53:44 -0700 Subject: [PATCH 258/756] Fix workflow tests --- tests/unit/workflow/test_workflow.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 12a8b670c..2e060022b 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -131,6 +131,14 @@ def test_no_parents(self): with self.assertRaises(TypeError): # We currently specify workflows shouldn't get parents, this just verifies # the spec. If that spec changes, test instead that you _can_ set parents! + wf2.parent = "not None" + + with self.assertRaises(AttributeError): + # Setting a non-None value to parent raises the type error above + # If that value is further a nodal object, the __setattr__ definition + # takes over, and we try to add it to the nodes, but there we will run into + # the fact you can't add a node to a taken attribute label + # In both cases, we satisfy the spec that workflow's can't have parents wf2.parent = wf From 6d05ff4883796256f9f69537544f86126fb7c409 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 14:01:58 -0700 Subject: [PATCH 259/756] Fix some more hints --- pyiron_contrib/workflow/composite.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index a20193fcb..8f61bb976 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -48,16 +48,19 @@ class Composite(IsNodal, ABC): requirement is still passed on to children. Attributes: - nodes (DotDict[Node]): The owned nodes that form the composite subgraph. + nodes (DotDict[pyiron_contrib.workflow.is_nodal,IsNodal]): The owned nodes that + form the composite subgraph. strict_naming (bool): When true, repeated assignment of a new node to an existing node label will raise an error, otherwise the label gets appended with an index and the assignment proceeds. (Default is true: disallow assigning to existing labels.) add (NodeAdder): A tool for adding new nodes to this subgraph. - upstream_nodes (list[Node]): All the owned nodes that have output connections - but no input connections, i.e. the upstream-most nodes. - starting_nodes (None | list[Node]): A subset of the owned nodes to be used on - running. (Default is None, running falls back on using the `upstream_nodes`.) + upstream_nodes (list[pyiron_contrib.workflow.is_nodal,IsNodal]): All the owned + nodes that have output connections but no input connections, i.e. the + upstream-most nodes. + starting_nodes (None | list[pyiron_contrib.workflow.is_nodal,IsNodal]): A subset + of the owned nodes to be used on running. (Default is None, running falls back + on using the `upstream_nodes`.) Methods: add(node: Node): Add the node instance to this subgraph. @@ -106,7 +109,7 @@ def add_node(self, node: IsNodal, label: Optional[str] = None) -> None: Assign a node to the parent. Optionally provide a new label for that node. Args: - node (pyiron_contrib.workflow.node.Node): The node to add. + node (pyiron_contrib.workflow.is_nodal.IsNodal): The node to add. label (Optional[str]): The label for this node. Raises: From aad1c1adedaca614696ccd001b2c40728c8018d6 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 14:09:48 -0700 Subject: [PATCH 260/756] Fix some more hints --- pyiron_contrib/workflow/composite.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 8f61bb976..52f4e8efc 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -233,10 +233,10 @@ def __getattribute__(self, key): return partial(Node, parent=self._parent) return value - def __call__(self, node: Node): + def __call__(self, node: IsNodal): return self._parent.add_node(node) - def register_nodes(self, domain: str, *nodes: list[type[Node]]): + def register_nodes(self, domain: str, *nodes: list[type[IsNodal]]): """ Add a list of node classes to be accessible for creation under the provided domain name. From b77952d2cfed6f1914b0952dca95df51b960ed27 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 14:14:11 -0700 Subject: [PATCH 261/756] Rename Node to Function --- pyiron_contrib/workflow/composite.py | 8 ++-- .../workflow/{node.py => function.py} | 28 ++++++------- .../workflow/node_library/atomistics.py | 2 +- .../workflow/node_library/standard.py | 2 +- pyiron_contrib/workflow/workflow.py | 18 ++++---- .../{test_node.py => test_function.py} | 42 +++++++++---------- tests/unit/workflow/test_workflow.py | 38 ++++++++--------- 7 files changed, 69 insertions(+), 69 deletions(-) rename pyiron_contrib/workflow/{node.py => function.py} (97%) rename tests/unit/workflow/{test_node.py => test_function.py} (91%) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 52f4e8efc..77d72f396 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -11,7 +11,7 @@ from warnings import warn from pyiron_contrib.workflow.is_nodal import IsNodal -from pyiron_contrib.workflow.node import Node, node, fast_node, single_value_node +from pyiron_contrib.workflow.function import Function, node, fast_node, single_value_node from pyiron_contrib.workflow.node_library import atomistics, standard from pyiron_contrib.workflow.node_library.package import NodePackage from pyiron_contrib.workflow.util import DotDict @@ -225,12 +225,12 @@ def __init__(self, parent: Composite): self.register_nodes("atomistics", *atomistics.nodes) self.register_nodes("standard", *standard.nodes) - Node = Node + Function = Function def __getattribute__(self, key): value = super().__getattribute__(key) - if value == Node: - return partial(Node, parent=self._parent) + if value == Function: + return partial(Function, parent=self._parent) return value def __call__(self, node: IsNodal): diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/function.py similarity index 97% rename from pyiron_contrib/workflow/node.py rename to pyiron_contrib/workflow/function.py index 3951def51..a38c9c06c 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/function.py @@ -15,7 +15,7 @@ from pyiron_contrib.workflow.workflow import Workflow -class Node(IsNodal): +class Function(IsNodal): """ Nodes have input and output data channels that interface with the outside world, and a callable that determines what they actually compute. After running, their output @@ -96,12 +96,12 @@ class Node(IsNodal): Examples: At the most basic level, to use nodes all we need to do is provide the `Node` class with a function and labels for its output, like so: - >>> from pyiron_contrib.workflow.node import Node + >>> from pyiron_contrib.workflow.node import Function >>> >>> def mwe(x, y): ... return x+1, y-1 >>> - >>> plus_minus_1 = Node(mwe, "p1", "m1") + >>> plus_minus_1 = Function(mwe, "p1", "m1") >>> >>> print(plus_minus_1.outputs.p1) None @@ -131,7 +131,7 @@ class with a function and labels for its output, like so: {'p1': 3, 'm1': 2} We can also, optionally, provide initial values for some or all of the input - >>> plus_minus_1 = Node( + >>> plus_minus_1 = Function( ... mwe, "p1", "m1", ... x=1, ... run_on_updates=True @@ -142,7 +142,7 @@ class with a function and labels for its output, like so: Finally, we might want the node to be ready-to-go right after instantiation. To do this, we need to provide initial values for everything and set two flags: - >>> plus_minus_1 = Node( + >>> plus_minus_1 = Function( ... mwe, "p1", "m1", ... x=0, y=0, ... run_on_updates=True, update_on_instantiation=True @@ -160,7 +160,7 @@ class with a function and labels for its output, like so: Thus, the second solution is to ensure that _all_ the arguments of our function are receiving good enough initial values to facilitate an execution of the node function at the end of instantiation: - >>> plus_minus_1 = Node(mwe, "p1", "m1", x=1, y=2) + >>> plus_minus_1 = Function(mwe, "p1", "m1", x=1, y=2) >>> >>> print(plus_minus_1.outputs.to_value_dict()) {'p1': 2, 'm1': 1} @@ -182,7 +182,7 @@ class with a function and labels for its output, like so: ... ) -> tuple[int, int | float]: ... return x+1, y-1 >>> - >>> plus_minus_1 = Node( + >>> plus_minus_1 = Function( ... hinted_example, "p1", "m1", ... run_on_updates=True, update_on_instantiation=True ... ) @@ -239,7 +239,7 @@ class with a function and labels for its output, like so: The first is to override the `__init__` method directly: >>> from typing import Literal, Optional >>> - >>> class AlphabetModThree(Node): + >>> class AlphabetModThree(Function): ... def __init__( ... self, ... label: Optional[str] = None, @@ -267,13 +267,13 @@ class with a function and labels for its output, like so: afterwards because we were accessing it through self). >>> from functools import partialmethod >>> - >>> class Adder(Node): + >>> class Adder(Function): ... @staticmethod ... def adder(x: int = 0, y: int = 0) -> int: ... return x + y ... ... __init__ = partialmethod( - ... Node.__init__, + ... Function.__init__, ... adder, ... "sum", ... run_on_updates=True, @@ -522,7 +522,7 @@ def to_dict(self): } -class FastNode(Node): +class FastNode(Function): """ Like a regular node, but _all_ input channels _must_ have default values provided, and the initialization signature forces `run_on_updates` and @@ -630,7 +630,7 @@ def node(*output_labels: str, **node_class_kwargs): Decorates a function. Takes an output label for each returned value of the function. - Returns a `Node` subclass whose name is the camel-case version of the function node, + Returns a `Function` subclass whose name is the camel-case version of the function node, and whose signature is modified to exclude the node function and output labels (which are explicitly defined in the process of using the decorator). """ @@ -638,10 +638,10 @@ def node(*output_labels: str, **node_class_kwargs): def as_node(node_function: callable): return type( node_function.__name__.title().replace("_", ""), # fnc_name to CamelCase - (Node,), # Define parentage + (Function,), # Define parentage { "__init__": partialmethod( - Node.__init__, + Function.__init__, node_function, *output_labels, **node_class_kwargs, diff --git a/pyiron_contrib/workflow/node_library/atomistics.py b/pyiron_contrib/workflow/node_library/atomistics.py index 781d039b4..874ffae91 100644 --- a/pyiron_contrib/workflow/node_library/atomistics.py +++ b/pyiron_contrib/workflow/node_library/atomistics.py @@ -7,7 +7,7 @@ from pyiron_atomistics.atomistics.structure.atoms import Atoms from pyiron_atomistics.lammps.lammps import Lammps as LammpsJob -from pyiron_contrib.workflow.node import node, single_value_node +from pyiron_contrib.workflow.function import node, single_value_node @single_value_node("structure") diff --git a/pyiron_contrib/workflow/node_library/standard.py b/pyiron_contrib/workflow/node_library/standard.py index 9020bba0d..a920e2538 100644 --- a/pyiron_contrib/workflow/node_library/standard.py +++ b/pyiron_contrib/workflow/node_library/standard.py @@ -5,7 +5,7 @@ import numpy as np from matplotlib import pyplot as plt -from pyiron_contrib.workflow.node import single_value_node +from pyiron_contrib.workflow.function import single_value_node @single_value_node("fig") diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index ee6c49b28..0b14b01a6 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -32,18 +32,18 @@ class Workflow(Composite): Examples: We allow adding nodes to workflows in five equivalent ways: >>> from pyiron_contrib.workflow.workflow import Workflow - >>> from pyiron_contrib.workflow.node import Node + >>> from pyiron_contrib.workflow.node import Function >>> >>> def fnc(x=0): return x + 1 >>> - >>> n1 = Node(fnc, "x", label="n1") + >>> n1 = Function(fnc, "x", label="n1") >>> >>> wf = Workflow("my_workflow", n1) # As *args at instantiation - >>> wf.add(Node(fnc, "x", label="n2")) # Passing a node to the add caller - >>> wf.add.Node(fnc, "y", label="n3") # Instantiating from add - >>> wf.n4 = Node(fnc, "y", label="whatever_n4_gets_used") + >>> wf.add(Function(fnc, "x", label="n2")) # Passing a node to the add caller + >>> wf.add.Function(fnc, "y", label="n3") # Instantiating from add + >>> wf.n4 = Function(fnc, "y", label="whatever_n4_gets_used") >>> # By attribute assignment - >>> Node(fnc, "x", label="n5", parent=wf) + >>> Function(fnc, "x", label="n5", parent=wf) >>> # By instantiating the node with a workflow By default, the node naming scheme is strict, so if you try to add a node to a @@ -52,9 +52,9 @@ class Workflow(Composite): bool to this property. When deactivated, repeated assignments to the same label just get appended with an index: >>> wf.deactivate_strict_naming() - >>> wf.my_node = Node(fnc, "y", x=0) - >>> wf.my_node = Node(fnc, "y", x=1) - >>> wf.my_node = Node(fnc, "y", x=2) + >>> wf.my_node = Function(fnc, "y", x=0) + >>> wf.my_node = Function(fnc, "y", x=1) + >>> wf.my_node = Function(fnc, "y", x=2) >>> print(wf.my_node.inputs.x, wf.my_node0.inputs.x, wf.my_node1.inputs.x) 0, 1, 2 diff --git a/tests/unit/workflow/test_node.py b/tests/unit/workflow/test_function.py similarity index 91% rename from tests/unit/workflow/test_node.py rename to tests/unit/workflow/test_function.py index d3e9c960a..6963af100 100644 --- a/tests/unit/workflow/test_node.py +++ b/tests/unit/workflow/test_function.py @@ -4,8 +4,8 @@ import warnings from pyiron_contrib.workflow.files import DirectoryObject -from pyiron_contrib.workflow.node import ( - FastNode, Node, SingleValueNode, node, single_value_node +from pyiron_contrib.workflow.function import ( + FastNode, Function, SingleValueNode, node, single_value_node ) @@ -22,19 +22,19 @@ def no_default(x, y): @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestNode(unittest.TestCase): +class TestFunction(unittest.TestCase): def test_defaults(self): - Node(plus_one, "y") + Function(plus_one, "y") def test_failure_without_output_labels(self): with self.assertRaises( ValueError, msg="Instantiated nodes should demand at least one output label" ): - Node(plus_one) + Function(plus_one) def test_instantiation_update(self): - no_update = Node( + no_update = Function( plus_one, "y", run_on_updates=True, @@ -42,7 +42,7 @@ def test_instantiation_update(self): ) self.assertIsNone(no_update.outputs.y.value) - update = Node( + update = Function( plus_one, "y", run_on_updates=True, @@ -51,16 +51,16 @@ def test_instantiation_update(self): self.assertEqual(2, update.outputs.y.value) with self.assertRaises(TypeError): - run_without_value = Node(no_default, "z") + run_without_value = Function(no_default, "z") run_without_value.run() # None + None + 1 -> error with self.assertRaises(TypeError): - run_without_value = Node(no_default, "z", x=1) + run_without_value = Function(no_default, "z", x=1) run_without_value.run() # 1 + None + 1 -> error - deferred_update = Node(no_default, "z", x=1, y=1) + deferred_update = Function(no_default, "z", x=1, y=1) deferred_update.run() self.assertEqual( deferred_update.outputs.z.value, @@ -70,7 +70,7 @@ def test_instantiation_update(self): ) def test_input_kwargs(self): - node = Node( + node = Function( plus_one, "y", x=2, @@ -79,12 +79,12 @@ def test_input_kwargs(self): ) self.assertEqual(3, node.outputs.y.value, msg="Initialize from value") - node2 = Node(plus_one, "y", x=node.outputs.y, run_on_updates=True) + node2 = Function(plus_one, "y", x=node.outputs.y, run_on_updates=True) node.update() self.assertEqual(4, node2.outputs.y.value, msg="Initialize from connection") def test_automatic_updates(self): - node = Node(throw_error, "no_return", run_on_updates=True) + node = Function(throw_error, "no_return", run_on_updates=True) with self.subTest("Shouldn't run for invalid input on update"): node.inputs.x.update("not an int") @@ -120,7 +120,7 @@ def times_two(y): ) def test_statuses(self): - n = Node(plus_one, "p1") + n = Function(plus_one, "p1") self.assertTrue(n.ready) self.assertFalse(n.running) self.assertFalse(n.failed) @@ -172,7 +172,7 @@ def with_self(self, x: float) -> float: self.some_counter = 1 return x + 0.1 - node = Node(with_self, "output") + node = Function(with_self, "output") self.assertTrue( "x" in node.inputs.labels, msg=f"Expected to find function input 'x' in the node input but got " @@ -193,14 +193,14 @@ def with_self(self, x: float) -> float: self.assertEqual( node.some_counter, 1, - msg="Node functions should be able to modify attributes on the node object." + msg="Function functions should be able to modify attributes on the node object." ) def with_messed_self(x: float, self) -> float: return x + 0.1 with warnings.catch_warnings(record=True) as warning_list: - node = Node(with_messed_self, "output") + node = Function(with_messed_self, "output") self.assertTrue("self" in node.inputs.labels) self.assertEqual(len(warning_list), 1) @@ -279,12 +279,12 @@ def test_str(self): str(svn).endswith(str(svn.single_value)), msg="SingleValueNodes should have their output as a string in their string " "representation (e.g., perhaps with a reminder note that this is " - "actually still a Node and not just the value you're seeing.)" + "actually still a Function and not just the value you're seeing.)" ) def test_easy_output_connection(self): svn = SingleValueNode(plus_one, "y") - regular = Node(plus_one, "y") + regular = Function(plus_one, "y") regular.inputs.x = svn @@ -301,7 +301,7 @@ def test_easy_output_connection(self): "case default->plus_one->plus_one = 1 + 1 +1 = 3" ) - at_instantiation = Node(plus_one, "y", x=svn) + at_instantiation = Function(plus_one, "y", x=svn) self.assertIn( svn.outputs.y, at_instantiation.inputs.x.connections, msg="The parsing of SingleValueNode output as a connection should also work" @@ -361,7 +361,7 @@ def my_node(x: int = 0, y: int = 0, z: int = 0): ) def test_working_directory(self): - n_f = Node(plus_one, "output") + n_f = Function(plus_one, "output") self.assertTrue(n_f._working_directory is None) self.assertIsInstance(n_f.working_directory, DirectoryObject) self.assertTrue(str(n_f.working_directory.path).endswith(n_f.label)) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 2e060022b..b1fcb18b1 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -2,7 +2,7 @@ from sys import version_info from pyiron_contrib.workflow.files import DirectoryObject -from pyiron_contrib.workflow.node import Node +from pyiron_contrib.workflow.function import Function from pyiron_contrib.workflow.workflow import Workflow @@ -17,10 +17,10 @@ def test_node_addition(self): wf = Workflow("my_workflow") # Validate the four ways to add a node - wf.add(Node(fnc, "x", label="foo")) - wf.add.Node(fnc, "y", label="bar") - wf.baz = Node(fnc, "y", label="whatever_baz_gets_used") - Node(fnc, "x", label="qux", parent=wf) + wf.add(Function(fnc, "x", label="foo")) + wf.add.Function(fnc, "y", label="bar") + wf.baz = Function(fnc, "y", label="whatever_baz_gets_used") + Function(fnc, "x", label="qux", parent=wf) self.assertListEqual(list(wf.nodes.keys()), ["foo", "bar", "baz", "qux"]) wf.boa = wf.qux self.assertListEqual( @@ -31,14 +31,14 @@ def test_node_addition(self): wf.strict_naming = False # Validate name incrementation - wf.add(Node(fnc, "x", label="foo")) - wf.add.Node(fnc, "y", label="bar") - wf.baz = Node( + wf.add(Function(fnc, "x", label="foo")) + wf.add.Function(fnc, "y", label="bar") + wf.baz = Function( fnc, "y", label="without_strict_you_can_override_by_assignment" ) - Node(fnc, "x", label="boa", parent=wf) + Function(fnc, "x", label="boa", parent=wf) self.assertListEqual( list(wf.nodes.keys()), [ @@ -50,16 +50,16 @@ def test_node_addition(self): wf.strict_naming = True # Validate name preservation with self.assertRaises(AttributeError): - wf.add(Node(fnc, "x", label="foo")) + wf.add(Function(fnc, "x", label="foo")) with self.assertRaises(AttributeError): - wf.add.Node(fnc, "y", label="bar") + wf.add.Function(fnc, "y", label="bar") with self.assertRaises(AttributeError): - wf.baz = Node(fnc, "y", label="whatever_baz_gets_used") + wf.baz = Function(fnc, "y", label="whatever_baz_gets_used") with self.assertRaises(AttributeError): - Node(fnc, "x", label="boa", parent=wf) + Function(fnc, "x", label="boa", parent=wf) def test_node_packages(self): wf = Workflow("my_workflow") @@ -78,8 +78,8 @@ def test_node_packages(self): def test_double_workfloage_and_node_removal(self): wf1 = Workflow("one") - wf1.add.Node(fnc, "y", label="node1") - node2 = Node(fnc, "y", label="node2", parent=wf1, x=wf1.node1.outputs.y) + wf1.add.Function(fnc, "y", label="node1") + node2 = Function(fnc, "y", label="node2", parent=wf1, x=wf1.node1.outputs.y) self.assertTrue(node2.connected) wf2 = Workflow("two") @@ -93,9 +93,9 @@ def test_double_workfloage_and_node_removal(self): def test_workflow_io(self): wf = Workflow("wf") - wf.add.Node(fnc, "y", label="n1") - wf.add.Node(fnc, "y", label="n2") - wf.add.Node(fnc, "y", label="n3") + wf.add.Function(fnc, "y", label="n1") + wf.add.Function(fnc, "y", label="n2") + wf.add.Function(fnc, "y", label="n3") with self.subTest("Workflow IO should be drawn from its nodes"): self.assertEqual(len(wf.inputs), 3) @@ -120,7 +120,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.Node(fnc, "output") + wf.add.Function(fnc, "output") self.assertTrue(str(wf.fnc.working_directory.path).endswith(wf.fnc.label)) wf.working_directory.delete() From cf03a5201016e57b0adb8f0b0da0acd904666256 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 14:16:59 -0700 Subject: [PATCH 262/756] Rename FastNode to Fast --- notebooks/workflow_example.ipynb | 4 ++-- pyiron_contrib/workflow/function.py | 12 ++++++------ tests/unit/workflow/test_function.py | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 9d5febfbe..31c8928ac 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -399,7 +399,7 @@ "source": [ "## Special nodes\n", "\n", - "In addition to the basic `Node` class, for the sake of convenience we also offer `FastNode(Node)` -- which enforces that all the node function inputs are type-hinted and have defaults, then sets `run_on_updates=True` and `update_on_instantiation=True` --, and `SingleValueNode(FastNode)` -- which further enforces that there is only a _single_ return value to the node function (i.e. a single output label), and then lets attribute and item access fall back to looking for attributes and items of this single output value. Of course there are decorators available for both of these.\n", + "In addition to the basic `Node` class, for the sake of convenience we also offer `Fast(Node)` -- which enforces that all the node function inputs are type-hinted and have defaults, then sets `run_on_updates=True` and `update_on_instantiation=True` --, and `SingleValueNode(Fast)` -- which further enforces that there is only a _single_ return value to the node function (i.e. a single output label), and then lets attribute and item access fall back to looking for attributes and items of this single output value. Of course there are decorators available for both of these.\n", "\n", "Let's look at a use case:" ] @@ -736,7 +736,7 @@ "\n", "Currently we have a handfull of pre-build nodes available for import from the `nodes` package. Let's use these to quickly put together a workflow for looking at some MD data.\n", "\n", - "The `calc_md`, node is _not_ at `FastNode`, but we happen to know that the calculation we're doing here is very easy, so we'll set `run_on_updates` and `update_at_instantiation` to `True`.\n", + "The `calc_md`, node is _not_ at `Fast`, but we happen to know that the calculation we're doing here is very easy, so we'll set `run_on_updates` and `update_at_instantiation` to `True`.\n", "\n", "Finally, `SingleValueNode` has one more piece of syntactic sugar: when you're making a connection to the (single!) output channel, you can just pass the node itself!" ] diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index a38c9c06c..528f86595 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -197,7 +197,7 @@ class with a function and labels for its output, like so: >>> plus_minus_1.outputs.to_value_dict() {'p1': 2, 'm1': 0} - Note: the `FastNode(Node)` child class will enforce all function arguments to + Note: the `Fast(Node)` child class will enforce all function arguments to be type-hinted and have defaults, and will automatically set the updating and instantiation flags to `True` for nodes that execute quickly and are meant to _always_ have good output data. @@ -522,7 +522,7 @@ def to_dict(self): } -class FastNode(Function): +class Fast(Function): """ Like a regular node, but _all_ input channels _must_ have default values provided, and the initialization signature forces `run_on_updates` and @@ -564,7 +564,7 @@ def ensure_params_have_defaults(cls, fnc: callable) -> None: ) -class SingleValueNode(FastNode, HasChannel): +class SingleValueNode(Fast, HasChannel): """ A fast node that _must_ return only a single value. @@ -660,13 +660,13 @@ def fast_node(*output_labels: str, **node_class_kwargs): """ def as_fast_node(node_function: callable): - FastNode.ensure_params_have_defaults(node_function) + Fast.ensure_params_have_defaults(node_function) return type( node_function.__name__.title().replace("_", ""), # fnc_name to CamelCase - (FastNode,), # Define parentage + (Fast,), # Define parentage { "__init__": partialmethod( - FastNode.__init__, + Fast.__init__, node_function, *output_labels, **node_class_kwargs, diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 6963af100..98d3580a3 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -5,7 +5,7 @@ from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.function import ( - FastNode, Function, SingleValueNode, node, single_value_node + Fast, Function, SingleValueNode, node, single_value_node ) @@ -209,10 +209,10 @@ def with_messed_self(x: float, self) -> float: @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestFastNode(unittest.TestCase): def test_instantiation(self): - has_defaults_is_ok = FastNode(plus_one, "y") + has_defaults_is_ok = Fast(plus_one, "y") with self.assertRaises(ValueError): - missing_defaults_should_fail = FastNode(no_default, "z") + missing_defaults_should_fail = Fast(no_default, "z") @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") From 8be12636e55dfde56bab507f06a3a14885bc5ddb Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 14:18:26 -0700 Subject: [PATCH 263/756] Rename SingleValueNode to SingleValue --- notebooks/workflow_example.ipynb | 4 ++-- pyiron_contrib/workflow/function.py | 10 +++++----- tests/unit/workflow/test_function.py | 18 +++++++++--------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 31c8928ac..4c7e7093d 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -399,7 +399,7 @@ "source": [ "## Special nodes\n", "\n", - "In addition to the basic `Node` class, for the sake of convenience we also offer `Fast(Node)` -- which enforces that all the node function inputs are type-hinted and have defaults, then sets `run_on_updates=True` and `update_on_instantiation=True` --, and `SingleValueNode(Fast)` -- which further enforces that there is only a _single_ return value to the node function (i.e. a single output label), and then lets attribute and item access fall back to looking for attributes and items of this single output value. Of course there are decorators available for both of these.\n", + "In addition to the basic `Node` class, for the sake of convenience we also offer `Fast(Node)` -- which enforces that all the node function inputs are type-hinted and have defaults, then sets `run_on_updates=True` and `update_on_instantiation=True` --, and `SingleValue(Fast)` -- which further enforces that there is only a _single_ return value to the node function (i.e. a single output label), and then lets attribute and item access fall back to looking for attributes and items of this single output value. Of course there are decorators available for both of these.\n", "\n", "Let's look at a use case:" ] @@ -738,7 +738,7 @@ "\n", "The `calc_md`, node is _not_ at `Fast`, but we happen to know that the calculation we're doing here is very easy, so we'll set `run_on_updates` and `update_at_instantiation` to `True`.\n", "\n", - "Finally, `SingleValueNode` has one more piece of syntactic sugar: when you're making a connection to the (single!) output channel, you can just pass the node itself!" + "Finally, `SingleValue` has one more piece of syntactic sugar: when you're making a connection to the (single!) output channel, you can just pass the node itself!" ] }, { diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 528f86595..b34806e99 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -564,7 +564,7 @@ def ensure_params_have_defaults(cls, fnc: callable) -> None: ) -class SingleValueNode(Fast, HasChannel): +class SingleValue(Fast, HasChannel): """ A fast node that _must_ return only a single value. @@ -685,14 +685,14 @@ def single_value_node(*output_labels: str, **node_class_kwargs): """ def as_single_value_node(node_function: callable): - SingleValueNode.ensure_there_is_only_one_return_value(output_labels) - SingleValueNode.ensure_params_have_defaults(node_function) + SingleValue.ensure_there_is_only_one_return_value(output_labels) + SingleValue.ensure_params_have_defaults(node_function) return type( node_function.__name__.title().replace("_", ""), # fnc_name to CamelCase - (SingleValueNode,), # Define parentage + (SingleValue,), # Define parentage { "__init__": partialmethod( - SingleValueNode.__init__, + SingleValue.__init__, node_function, *output_labels, **node_class_kwargs, diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 98d3580a3..0e438b7eb 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -5,7 +5,7 @@ from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.function import ( - Fast, Function, SingleValueNode, node, single_value_node + Fast, Function, SingleValue, node, single_value_node ) @@ -218,10 +218,10 @@ def test_instantiation(self): @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestSingleValueNode(unittest.TestCase): def test_instantiation(self): - has_defaults_and_one_return = SingleValueNode(plus_one, "y") + has_defaults_and_one_return = SingleValue(plus_one, "y") with self.assertRaises(ValueError): - too_many_labels = SingleValueNode(plus_one, "z", "excess_label") + too_many_labels = SingleValue(plus_one, "z", "excess_label") def test_item_and_attribute_access(self): class Foo: @@ -237,7 +237,7 @@ def __getitem__(self, item): def returns_foo() -> Foo: return Foo() - svn = SingleValueNode(returns_foo, "foo") + svn = SingleValue(returns_foo, "foo") self.assertEqual( svn.some_attribute, @@ -267,14 +267,14 @@ def returns_foo() -> Foo: ) def test_repr(self): - svn = SingleValueNode(plus_one, "y") + svn = SingleValue(plus_one, "y") self.assertEqual( svn.__repr__(), svn.outputs.y.value.__repr__(), msg="SingleValueNodes should have their output as their representation" ) def test_str(self): - svn = SingleValueNode(plus_one, "y") + svn = SingleValue(plus_one, "y") self.assertTrue( str(svn).endswith(str(svn.single_value)), msg="SingleValueNodes should have their output as a string in their string " @@ -283,7 +283,7 @@ def test_str(self): ) def test_easy_output_connection(self): - svn = SingleValueNode(plus_one, "y") + svn = SingleValue(plus_one, "y") regular = Function(plus_one, "y") regular.inputs.x = svn @@ -297,14 +297,14 @@ def test_easy_output_connection(self): regular.run() self.assertEqual( regular.outputs.y.value, 3, - msg="SingleValueNode connections should pass data just like usual; in this " + msg="SingleValue connections should pass data just like usual; in this " "case default->plus_one->plus_one = 1 + 1 +1 = 3" ) at_instantiation = Function(plus_one, "y", x=svn) self.assertIn( svn.outputs.y, at_instantiation.inputs.x.connections, - msg="The parsing of SingleValueNode output as a connection should also work" + msg="The parsing of SingleValue output as a connection should also work" "from assignment at instantiation" ) From 3751e2d1e8d15c602d4174209be20da29513746e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 14:19:23 -0700 Subject: [PATCH 264/756] Update test class names --- tests/unit/workflow/test_function.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 0e438b7eb..571dc3034 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -207,7 +207,7 @@ def with_messed_self(x: float, self) -> float: @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestFastNode(unittest.TestCase): +class TestFast(unittest.TestCase): def test_instantiation(self): has_defaults_is_ok = Fast(plus_one, "y") @@ -216,7 +216,7 @@ def test_instantiation(self): @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestSingleValueNode(unittest.TestCase): +class TestSingleValue(unittest.TestCase): def test_instantiation(self): has_defaults_and_one_return = SingleValue(plus_one, "y") From 1b049f7a6cf34eaed8ede4fb656e696bc00a7f9b Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 14:24:57 -0700 Subject: [PATCH 265/756] Rename the decorator --- pyiron_contrib/workflow/composite.py | 4 ++-- pyiron_contrib/workflow/function.py | 2 +- pyiron_contrib/workflow/node_library/atomistics.py | 6 +++--- tests/unit/workflow/test_function.py | 6 +++--- tests/unit/workflow/test_node_package.py | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 77d72f396..9dae3de4e 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -11,7 +11,7 @@ from warnings import warn from pyiron_contrib.workflow.is_nodal import IsNodal -from pyiron_contrib.workflow.function import Function, node, fast_node, single_value_node +from pyiron_contrib.workflow.function import Function, function_node, fast_node, single_value_node from pyiron_contrib.workflow.node_library import atomistics, standard from pyiron_contrib.workflow.node_library.package import NodePackage from pyiron_contrib.workflow.util import DotDict @@ -20,7 +20,7 @@ class _NodeDecoratorAccess: """An intermediate container to store node-creating decorators as class methods.""" - node = node + function_node = function_node fast_node = fast_node single_value_node = single_value_node diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index b34806e99..3aaa952d5 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -624,7 +624,7 @@ def __str__(self): ) -def node(*output_labels: str, **node_class_kwargs): +def function_node(*output_labels: str, **node_class_kwargs): """ A decorator for dynamically creating node classes from functions. diff --git a/pyiron_contrib/workflow/node_library/atomistics.py b/pyiron_contrib/workflow/node_library/atomistics.py index 874ffae91..e4fdd5a6c 100644 --- a/pyiron_contrib/workflow/node_library/atomistics.py +++ b/pyiron_contrib/workflow/node_library/atomistics.py @@ -7,7 +7,7 @@ from pyiron_atomistics.atomistics.structure.atoms import Atoms from pyiron_atomistics.lammps.lammps import Lammps as LammpsJob -from pyiron_contrib.workflow.function import node, single_value_node +from pyiron_contrib.workflow.function import function_node, single_value_node @single_value_node("structure") @@ -81,7 +81,7 @@ def _run_and_remove_job(job, modifier: Optional[callable] = None, **modifier_kwa ) -@node( +@function_node( "cells", "displacements", "energy_pot", @@ -103,7 +103,7 @@ def calc_static( return _run_and_remove_job(job=job) -@node( +@function_node( "cells", "displacements", "energy_pot", diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 571dc3034..ffe03a964 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -5,7 +5,7 @@ from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.function import ( - Fast, Function, SingleValue, node, single_value_node + Fast, Function, SingleValue, function_node, single_value_node ) @@ -94,11 +94,11 @@ def test_automatic_updates(self): node.inputs.x.update(1) def test_signals(self): - @node("y") + @function_node("y") def linear(x): return x - @node("z") + @function_node("z") def times_two(y): return 2 * y diff --git a/tests/unit/workflow/test_node_package.py b/tests/unit/workflow/test_node_package.py index f90394eeb..5ee86fb75 100644 --- a/tests/unit/workflow/test_node_package.py +++ b/tests/unit/workflow/test_node_package.py @@ -41,7 +41,7 @@ def test_update(self): with self.assertRaises(TypeError): self.package.available_name = "But we can still only assign node classes" - @Workflow.wrap_as.node("y") + @Workflow.wrap_as.function_node("y") def add(x: int = 0): return x + 1 From 0a08a9fd6c8857b3b8dcffee656fcee4a1b25666 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 14:28:17 -0700 Subject: [PATCH 266/756] Update docstrings --- pyiron_contrib/workflow/function.py | 14 +++++++------- pyiron_contrib/workflow/workflow.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 3aaa952d5..fbb32b745 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -17,9 +17,9 @@ class Function(IsNodal): """ - Nodes have input and output data channels that interface with the outside world, and - a callable that determines what they actually compute. After running, their output - channels are updated with the results of the node's computation, which + Function nodes have input and output data channels that interface with the outside + world, and a callable that determines what they actually compute. After running, + their output channels are updated with the results of the node's computation, which triggers downstream node updates if those output channels are connected to other input channels. @@ -39,7 +39,7 @@ class Function(IsNodal): Nodes won't update themselves while setting inputs to initial values, but can optionally update themselves at the end instantiation. - Nodes must be instantiated with a callable to deterimine their function, and an + Nodes must be instantiated with a callable to deterimine their function, and a strings to name each returned value of that callable. (If you really want to return a tuple, just have multiple return values but only one output label -- there is currently no way to mix-and-match, i.e. to have multiple return values at least one @@ -96,7 +96,7 @@ class Function(IsNodal): Examples: At the most basic level, to use nodes all we need to do is provide the `Node` class with a function and labels for its output, like so: - >>> from pyiron_contrib.workflow.node import Function + >>> from pyiron_contrib.workflow.function import Function >>> >>> def mwe(x, y): ... return x+1, y-1 @@ -213,9 +213,9 @@ class with a function and labels for its output, like so: This can be done most easily with the `node` decorator, which takes a function and returns a node class: - >>> from pyiron_contrib.workflow.node import node + >>> from pyiron_contrib.workflow.function import function_node >>> - >>> @node( + >>> @function_node( ... "p1", "m1", ... run_on_updates=True, update_on_instantiation=True ... ) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 0b14b01a6..e3e19f718 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -32,7 +32,7 @@ class Workflow(Composite): Examples: We allow adding nodes to workflows in five equivalent ways: >>> from pyiron_contrib.workflow.workflow import Workflow - >>> from pyiron_contrib.workflow.node import Function + >>> from pyiron_contrib.workflow.function import Function >>> >>> def fnc(x=0): return x + 1 >>> From 2c44a10e6704ddffd9dac0d2f79c7227d5e2271a Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 14:32:49 -0700 Subject: [PATCH 267/756] Rename IsNodal to Node --- pyiron_contrib/workflow/channels.py | 18 ++++----- pyiron_contrib/workflow/composite.py | 40 +++++++++---------- pyiron_contrib/workflow/function.py | 4 +- .../workflow/{is_nodal.py => node.py} | 2 +- .../workflow/node_library/package.py | 10 ++--- pyiron_contrib/workflow/workflow.py | 4 +- 6 files changed, 39 insertions(+), 39 deletions(-) rename pyiron_contrib/workflow/{is_nodal.py => node.py} (99%) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index f106ad29e..754ca4337 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -32,7 +32,7 @@ ) if typing.TYPE_CHECKING: - from pyiron_contrib.workflow.is_nodal import IsNodal + from pyiron_contrib.workflow.node import Node class Channel(HasChannel, HasToDict, ABC): @@ -51,7 +51,7 @@ class Channel(HasChannel, HasToDict, ABC): Attributes: label (str): The name of the channel. - node (pyiron_contrib.workflow.is_nodal.IsNodal): The node to which the channel + node (pyiron_contrib.workflow.node.Node): The node to which the channel belongs. connections (list[Channel]): Other channels to which this channel is connected. """ @@ -59,18 +59,18 @@ class Channel(HasChannel, HasToDict, ABC): def __init__( self, label: str, - node: IsNodal, + node: Node, ): """ Make a new channel. Args: label (str): A name for the channel. - node (pyiron_contrib.workflow.is_nodal.IsNodal): The node to which the + node (pyiron_contrib.workflow.node.Node): The node to which the channel belongs. """ self.label: str = label - self.node: IsNodal = node + self.node: Node = node self.connections: list[Channel] = [] @abstractmethod @@ -181,7 +181,7 @@ class DataChannel(Channel, ABC): def __init__( self, label: str, - node: IsNodal, + node: Node, default: typing.Optional[typing.Any] = None, type_hint: typing.Optional[typing.Any] = None, ): @@ -313,7 +313,7 @@ class InputData(DataChannel): def __init__( self, label: str, - node: IsNodal, + node: Node, default: typing.Optional[typing.Any] = None, type_hint: typing.Optional[typing.Any] = None, strict_connections: bool = True, @@ -448,7 +448,7 @@ class InputSignal(SignalChannel): def __init__( self, label: str, - node: IsNodal, + node: Node, callback: callable, ): """ @@ -456,7 +456,7 @@ def __init__( Args: label (str): A name for the channel. - node (pyiron_contrib.workflow.is_nodal.IsNodal): The node to which the + node (pyiron_contrib.workflow.node.Node): The node to which the channel belongs. callback (callable): An argument-free callback to invoke when calling this object. diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 9dae3de4e..d5b13a897 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -10,7 +10,7 @@ from typing import Optional from warnings import warn -from pyiron_contrib.workflow.is_nodal import IsNodal +from pyiron_contrib.workflow.node import Node from pyiron_contrib.workflow.function import Function, function_node, fast_node, single_value_node from pyiron_contrib.workflow.node_library import atomistics, standard from pyiron_contrib.workflow.node_library.package import NodePackage @@ -25,7 +25,7 @@ class _NodeDecoratorAccess: single_value_node = single_value_node -class Composite(IsNodal, ABC): +class Composite(Node, ABC): """ A base class for nodes that have internal structure -- i.e. they hold a sub-graph. @@ -48,17 +48,17 @@ class Composite(IsNodal, ABC): requirement is still passed on to children. Attributes: - nodes (DotDict[pyiron_contrib.workflow.is_nodal,IsNodal]): The owned nodes that + nodes (DotDict[pyiron_contrib.workflow.node,Node]): The owned nodes that form the composite subgraph. strict_naming (bool): When true, repeated assignment of a new node to an existing node label will raise an error, otherwise the label gets appended with an index and the assignment proceeds. (Default is true: disallow assigning to existing labels.) add (NodeAdder): A tool for adding new nodes to this subgraph. - upstream_nodes (list[pyiron_contrib.workflow.is_nodal,IsNodal]): All the owned + upstream_nodes (list[pyiron_contrib.workflow.node,Node]): All the owned nodes that have output connections but no input connections, i.e. the upstream-most nodes. - starting_nodes (None | list[pyiron_contrib.workflow.is_nodal,IsNodal]): A subset + starting_nodes (None | list[pyiron_contrib.workflow.node,Node]): A subset of the owned nodes to be used on running. (Default is None, running falls back on using the `upstream_nodes`.) @@ -81,9 +81,9 @@ def __init__( ): super().__init__(*args, label=label, parent=parent, **kwargs) self.strict_naming: bool = strict_naming - self.nodes: DotDict[str: IsNodal] = DotDict() + self.nodes: DotDict[str: Node] = DotDict() self.add: NodeAdder = NodeAdder(self) - self.starting_nodes: None | list[IsNodal] = None + self.starting_nodes: None | list[Node] = None def to_dict(self): return { @@ -92,7 +92,7 @@ def to_dict(self): } @property - def upstream_nodes(self) -> list[IsNodal]: + def upstream_nodes(self) -> list[Node]: return [ node for node in self.nodes.values() if node.outputs.connected and not node.inputs.connected @@ -104,18 +104,18 @@ def on_run(self): for node in starting_nodes: node.run() - def add_node(self, node: IsNodal, label: Optional[str] = None) -> None: + def add_node(self, node: Node, label: Optional[str] = None) -> None: """ Assign a node to the parent. Optionally provide a new label for that node. Args: - node (pyiron_contrib.workflow.is_nodal.IsNodal): The node to add. + node (pyiron_contrib.workflow.node.Node): The node to add. label (Optional[str]): The label for this node. Raises: TypeError: If the """ - if not isinstance(node, IsNodal): + if not isinstance(node, Node): raise TypeError( f"Only new node instances may be added, but got {type(node)}." ) @@ -130,7 +130,7 @@ def add_node(self, node: IsNodal, label: Optional[str] = None) -> None: def _get_unique_label(self, label): if label in self.__dir__(): - if isinstance(getattr(self, label), IsNodal): + if isinstance(getattr(self, label), Node): if self.strict_naming: raise AttributeError( f"{label} is already the label for a node. Please remove it " @@ -158,7 +158,7 @@ def _add_suffix_to_label(self, label): ) return new_label - def _ensure_node_has_no_other_parent(self, node: IsNodal): + def _ensure_node_has_no_other_parent(self, node: Node): if node.parent is not None and node.parent is not self: raise ValueError( f"The node ({node.label}) already belongs to the parent " @@ -166,7 +166,7 @@ def _ensure_node_has_no_other_parent(self, node: IsNodal): f"add it to this parent ({self.label})." ) - def _ensure_node_is_not_duplicated(self, node: IsNodal, label: str): + def _ensure_node_is_not_duplicated(self, node: Node, label: str): if ( node.parent is self and label != node.label @@ -178,16 +178,16 @@ def _ensure_node_is_not_duplicated(self, node: IsNodal, label: str): ) del self.nodes[node.label] - def remove(self, node: IsNodal | str): - if isinstance(node, IsNodal): + def remove(self, node: Node | str): + if isinstance(node, Node): node.parent = None node.disconnect() del self.nodes[node.label] else: del self.nodes[node] - def __setattr__(self, label: str, node: IsNodal): - if isinstance(node, IsNodal): + def __setattr__(self, label: str, node: Node): + if isinstance(node, Node): self.add_node(node, label=label) else: super().__setattr__(label, node) @@ -233,10 +233,10 @@ def __getattribute__(self, key): return partial(Function, parent=self._parent) return value - def __call__(self, node: IsNodal): + def __call__(self, node: Node): return self._parent.add_node(node) - def register_nodes(self, domain: str, *nodes: list[type[IsNodal]]): + def register_nodes(self, domain: str, *nodes: list[type[Node]]): """ Add a list of node classes to be accessible for creation under the provided domain name. diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index fbb32b745..62f475c7d 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -8,14 +8,14 @@ from pyiron_contrib.workflow.channels import InputData, OutputData from pyiron_contrib.workflow.has_channel import HasChannel from pyiron_contrib.workflow.io import Inputs, Outputs, Signals -from pyiron_contrib.workflow.is_nodal import IsNodal +from pyiron_contrib.workflow.node import Node if TYPE_CHECKING: from pyiron_contrib.workflow.composite import Composite from pyiron_contrib.workflow.workflow import Workflow -class Function(IsNodal): +class Function(Node): """ Function nodes have input and output data channels that interface with the outside world, and a callable that determines what they actually compute. After running, diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/node.py similarity index 99% rename from pyiron_contrib/workflow/is_nodal.py rename to pyiron_contrib/workflow/node.py index 564d19ee4..1b90f315f 100644 --- a/pyiron_contrib/workflow/is_nodal.py +++ b/pyiron_contrib/workflow/node.py @@ -19,7 +19,7 @@ from pyiron_contrib.workflow.io import Inputs, Outputs -class IsNodal(HasToDict, ABC): +class Node(HasToDict, ABC): """ A mixin class for objects that can form nodes in the graph representation of a computational workflow. diff --git a/pyiron_contrib/workflow/node_library/package.py b/pyiron_contrib/workflow/node_library/package.py index 74484242d..748e4ed75 100644 --- a/pyiron_contrib/workflow/node_library/package.py +++ b/pyiron_contrib/workflow/node_library/package.py @@ -3,7 +3,7 @@ from functools import partial from typing import TYPE_CHECKING -from pyiron_contrib.workflow.is_nodal import IsNodal +from pyiron_contrib.workflow.node import Node from pyiron_contrib.workflow.util import DotDict if TYPE_CHECKING: @@ -21,7 +21,7 @@ class NodePackage(DotDict): but to update an existing node the `update` method must be used. """ - def __init__(self, parent: Composite, *node_classes: IsNodal): + def __init__(self, parent: Composite, *node_classes: Node): super().__init__() self.__dict__["_parent"] = parent # Avoid the __setattr__ override for node in node_classes: @@ -35,16 +35,16 @@ def __setitem__(self, key, value): f"The name {key} is already an attribute of this " f"{self.__class__.__name__} instance." ) - if not isinstance(value, type) or not issubclass(value, IsNodal): + if not isinstance(value, type) or not issubclass(value, Node): raise TypeError( - f"Can only set members that are (sub)classes of {IsNodal.__name__}, " + f"Can only set members that are (sub)classes of {Node.__name__}, " f"but got {type(value)}" ) super().__setitem__(key, value) def __getitem__(self, item): value = super().__getitem__(item) - if issubclass(value, IsNodal): + if issubclass(value, Node): return partial(value, parent=self._parent) else: return value diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index e3e19f718..92c9249f1 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: - from pyiron_contrib.workflow.is_nodal import IsNodal + from pyiron_contrib.workflow.node import Node class Workflow(Composite): @@ -117,7 +117,7 @@ class Workflow(Composite): integrity of workflows when they're used somewhere else? """ - def __init__(self, label: str, *nodes: IsNodal, strict_naming=True): + def __init__(self, label: str, *nodes: Node, strict_naming=True): super().__init__(label=label, parent=None, strict_naming=strict_naming) for node in nodes: From 3fb23b365e10f989158b8c500780cf7b3376b148 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 14:53:43 -0700 Subject: [PATCH 268/756] Update the demo notebook Including a couple of small fixes that are unrelated and needed to be done, but didn't result in errors so hadn't been caught by the CI: Changing the `workflow` kwarg to `parent` in a node instantiation, and updating the description of how things are (or aren't) automatically updated in cell 17. --- notebooks/workflow_example.ipynb | 108 +++++++++---------------------- 1 file changed, 32 insertions(+), 76 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 4c7e7093d..2d7107e30 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -6,30 +6,10 @@ "id": "8dee8129-6b23-4abf-90d2-217d71b8ba7a", "metadata": {}, "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "0116bf172477443ea1eebe5c8c1a8704",
+       "model_id": "ee8645d92ea44a7aa8583a924cd3a804",
        "version_major": 2,
        "version_minor": 0
       },
@@ -37,17 +17,10 @@
      },
      "metadata": {},
      "output_type": "display_data"
-    },
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "* Owlready2 * Warning: optimized Cython parser module 'owlready2_optimized' is not available, defaulting to slower Python implementation\n"
-     ]
     }
    ],
    "source": [
-    "from pyiron_contrib.workflow.node import Node"
+    "from pyiron_contrib.workflow.function import Function"
    ]
   },
   {
@@ -74,7 +47,7 @@
    "source": [
     "## Instantiating a node\n",
     "\n",
-    "Nodes can be defined on-the-fly by passing any callable to the `Node` class, along with a string (tuple of strings) giving names for the output value(s)."
+    "Simple nodes can be defined on-the-fly by passing any callable to the `Function(Node)` class, along with a string (tuple of strings) giving names for the output value(s)."
    ]
   },
   {
@@ -87,7 +60,7 @@
     "def plus_minus_one(x):\n",
     "    return x+1, x-1\n",
     "\n",
-    "pm_node = Node(plus_minus_one, \"p1\", \"m1\")"
+    "pm_node = Function(plus_minus_one, \"p1\", \"m1\")"
    ]
   },
   {
@@ -177,7 +150,7 @@
     "def adder(x: int, y: int = 1) -> int:\n",
     "    return x + y\n",
     "\n",
-    "adder_node = Node(adder, \"sum\", run_on_updates=True, update_on_instantiation=True)\n",
+    "adder_node = Function(adder, \"sum\", run_on_updates=True, update_on_instantiation=True)\n",
     "adder_node.inputs.x = 1\n",
     "adder_node.outputs.sum.value  # We use `value` to see the data the channel holds"
    ]
@@ -251,9 +224,9 @@
    "source": [
     "## Reusable node classes\n",
     "\n",
-    "If we're going to use a node many times, we may want to define a new sub-class of `Node` to handle this.\n",
+    "If we're going to use a node many times, we may want to define a new sub-class of `Function` to handle this.\n",
     "\n",
-    "The can be done directly by inheriting from `Node` and overriding it's `__init__` function so that the core functionality of the node (i.e. the node function and output labels) are set in stone, but even easier is to use the `node` decorator to do this for you!\n",
+    "The can be done directly by inheriting from `Function` and overriding it's `__init__` function so that the core functionality of the node (i.e. the node function and output labels) are set in stone, but even easier is to use the `function_node` decorator to do this for you!\n",
     "\n",
     "The decorator takes the output labels and whatever other class kwargs you want to override, and the function is defined like any other node function:"
    ]
@@ -265,7 +238,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "from pyiron_contrib.workflow.node import node"
+    "from pyiron_contrib.workflow.function import function_node"
    ]
   },
   {
@@ -285,7 +258,7 @@
     }
    ],
    "source": [
-    "@node(\"diff\", run_on_updates=True, update_on_instantiation=True)\n",
+    "@function_node(\"diff\", run_on_updates=True, update_on_instantiation=True)\n",
     "def subtract_node(x: int | float = 2, y: int | float = 1) -> int | float:\n",
     "    return x - y\n",
     "\n",
@@ -351,7 +324,7 @@
     }
    ],
    "source": [
-    "@node(\"sum\", run_on_updates=True, update_on_instantiation=True)\n",
+    "@function_node(\"sum\", run_on_updates=True, update_on_instantiation=True)\n",
     "def add_node(x: int | float = 1, y: int | float = 1) -> int | float:\n",
     "    return x + y\n",
     "\n",
@@ -399,7 +372,7 @@
    "source": [
     "## Special nodes\n",
     "\n",
-    "In addition to the basic `Node` class, for the sake of convenience we also offer `Fast(Node)` -- which enforces that all the node function inputs are type-hinted and have defaults, then sets `run_on_updates=True` and `update_on_instantiation=True` --, and `SingleValue(Fast)` -- which further enforces that there is only a _single_ return value to the node function (i.e. a single output label), and then lets attribute and item access fall back to looking for attributes and items of this single output value. Of course there are decorators available for both of these.\n",
+    "In addition to the basic `Function` class, for the sake of convenience we also offer `Fast(Function)` -- which enforces that all the node function inputs are type-hinted and have defaults, then sets `run_on_updates=True` and `update_on_instantiation=True` --, and `SingleValue(Fast)` -- which further enforces that there is only a _single_ return value to the node function (i.e. a single output label), and then lets attribute and item access fall back to looking for attributes and items of this single output value. Of course there are decorators available for both of these.\n",
     "\n",
     "Let's look at a use case:"
    ]
@@ -412,7 +385,7 @@
    "outputs": [],
    "source": [
     "import numpy as np\n",
-    "from pyiron_contrib.workflow.node import single_value_node"
+    "from pyiron_contrib.workflow.function import single_value_node"
    ]
   },
   {
@@ -453,7 +426,7 @@
     "# Workflows\n",
     "\n",
     "Typically, you will have a group of nodes working together with their connections.\n",
-    "We call these groups workflows, and offer a `Workflow` object as a single point of entry -- i.e. most of the time you shouldn't need the node imports used above, because the decorators are available right on the workflow class."
+    "We call these groups workflows, and offer a `Workflow(Node)` object as a single point of entry -- i.e. most of the time you shouldn't need the node imports used above, because the decorators are available right on the workflow class."
    ]
   },
   {
@@ -492,29 +465,21 @@
      "output_type": "stream",
      "text": [
       "n1 n1 n1 (GreaterThanHalf) output single-value: False\n",
-      "n2 n2 \n",
+      "n2 n2 \n",
       "n3 n3 n3 (GreaterThanHalf) output single-value: False\n",
       "n4 n4 n4 (GreaterThanHalf) output single-value: False\n",
       "n5 n5 n5 (GreaterThanHalf) output single-value: False\n"
      ]
-    },
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/workflow.py:187: UserWarning: Reassigning the node will_get_overwritten_with_n4 to the label n4 when adding it to the workflow my_wf.\n",
-      "  warn(\n"
-     ]
     }
    ],
    "source": [
     "n1 = greater_than_half(label=\"n1\")\n",
     "\n",
     "wf = Workflow(\"my_wf\", n1)  # As args at init\n",
-    "wf.add.Node(lambda: x + 1, \"p1\", label=\"n2\")  # Instantiating from the node adder\n",
+    "wf.add.Function(lambda: x + 1, \"p1\", label=\"n2\")  # Instantiating from the node adder\n",
     "wf.add(greater_than_half(label=\"n3\"))  # Instantiating then passing to node adder\n",
     "wf.n4 = greater_than_half(label=\"will_get_overwritten_with_n4\")  # Set attribute to instance\n",
-    "greater_than_half(label=\"n5\", workflow=wf)  # By passing the workflow to the node\n",
+    "greater_than_half(label=\"n5\", parent=wf)  # By passing the workflow to the node\n",
     "\n",
     "for k, v in wf.nodes.items():\n",
     "    print(k, v.label, v)"
@@ -546,31 +511,22 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "None None\n"
-     ]
-    },
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/node.py:341: UserWarning: The keyword 'run_automatically' was received but not used.\n",
-      "  warnings.warn(f\"The keyword '{k}' was received but not used.\")\n"
+      "1 None\n"
      ]
     }
    ],
    "source": [
-    "@node(\"y\")\n",
+    "@function_node(\"y\")\n",
     "def linear(x):\n",
     "    return x\n",
     "\n",
-    "@node(\"z\")\n",
+    "@function_node(\"z\")\n",
     "def times_two(y):\n",
     "    return 2 * y\n",
     "\n",
     "l = linear(x=1)\n",
-    "t2 = times_two(\n",
-    "    y=l.outputs.y, update_on_instantiation=False, run_automatically=False\n",
-    ")\n",
+    "l.run()\n",
+    "t2 = times_two(y=l.outputs.y)\n",
     "print(t2.inputs.y, t2.outputs.z)"
    ]
   },
@@ -579,7 +535,7 @@
    "id": "37aa4455-9b98-4be5-a365-363e3c490bb6",
    "metadata": {},
    "source": [
-    "Now the input of `t2` got updated when the connection is made, but we told this node not to do any automatic updates, so the output has its uninitialized value of `None`.\n",
+    "Now the input of `t2` got updated when the connection is made, but by default we told this node not to do any automatic updates, so the output has its uninitialized value of `None`.\n",
     "\n",
     "Often, you will probably want to have nodes with data connections to have signal connections, but this is not strictly required. Here, we'll introduce a (not strictly necessary) third node to control starting the workflow, and chain together to signals from our two functional nodes.\n",
     "\n",
@@ -601,7 +557,7 @@
     }
    ],
    "source": [
-    "@node(\"void\")\n",
+    "@function_node(\"void\")\n",
     "def control():\n",
     "    return\n",
     "\n",
@@ -638,7 +594,7 @@
    "outputs": [
     {
      "data": {
-      "image/png": "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BlUwAACILQlOT6irq9PmzZtVXV2toqIi7dy5U6tWrVJLS4vmzJkz6jl33HGHPvjgA+3evVtf/vKX1dnZqU8++WTCxQMAgOjnMsYYJycsXrxYixYtUk1NzXDbggULtGbNGlVVVY3of+TIEX3nO9/RuXPnlJqaGlSRPT098ng88vv9SklJCeo9AABAZI3399vRMk1/f7+am5tVXFwc0F5cXKympqZRz3n11VeVl5enxx9/XNdee61uuOEGPfTQQ/rjH/845uf09fWpp6cn4AAAALHJ0TJNV1eXBgYGlJaWFtCelpamjo6OUc85d+6cjh49KrfbrUOHDqmrq0t///d/rwsXLox53UhVVZUqKyudlAYAAKJUUBewulyugNfGmBFtQwYHB+VyubRv3z7l5+frG9/4hp566im98MILY86OlJeXy+/3Dx/t7e3BlAkAAKKAo5mRmTNnKj4+fsQsSGdn54jZkiFer1fXXnutPB7PcNuCBQtkjNF7772n66+/fsQ5SUlJSkpKclIaAACIUo5mRhITE5Wbm6uGhoaA9oaGBhUWFo56TlFRkd5//3199NFHw21nzpxRXFyc0tPTgygZAADEEsfLNGVlZXruuedUW1urt99+Ww8++KDa2tpUWloq6dISy1133TXc/84779SMGTN0zz33qKWlRW+++aYefvhh/c3f/I2Sk5NDNxIAABCVHO8zUlJSou7ubm3btk0+n085OTmqr69XZmamJMnn86mtrW24/xe+8AU1NDTo+9//vvLy8jRjxgzdcccd+sEPfhC6UQAAgKjleJ8RG9hnBACA6BOWfUYAAABCjTACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArEqwXQAuGRg0OtZ6QZ29FzVrulv5WamKj3PZLgsAgLAjjEwCR077VHm4RT7/xeE2r8etitXZWpnjtVgZAADhxzKNZUdO+3Tv3hMBQUSSOvwXde/eEzpy2mepMgAAIoMwYtHAoFHl4RaZUf5sqK3ycIsGBkfrAQBAbCCMWHSs9cKIGZFPM5J8/os61nohckUBABBhhBGLOnvHDiLB9AMAIBoRRiyaNd0d0n4AAEQjwohF+Vmp8nrcGusGXpcu3VWTn5UaybIAAIgowohF8XEuVazOlqQRgWTodcXqbPYbAQDENMKIZStzvKpZu0izPYFLMbM9btWsXcQ+IwCAmBfUpmfV1dV64okn5PP5tHDhQm3fvl1LliwZte8vfvELfe1rXxvR/vbbb2v+/PnBfHzMWZnj1Yrs2ezACgCYkhyHkbq6Om3evFnV1dUqKirSzp07tWrVKrW0tGjOnDljnvfOO+8oJSVl+PXVV18dXMUxKj7OpYK5M2yXAQBAxDlepnnqqae0YcMGbdy4UQsWLND27duVkZGhmpqay543a9YszZ49e/iIj48PumgAABA7HIWR/v5+NTc3q7i4OKC9uLhYTU1Nlz33lltukdfr1bJly/T6669ftm9fX596enoCDgAAEJschZGuri4NDAwoLS0toD0tLU0dHR2jnuP1erVr1y4dOHBABw8e1Lx587Rs2TK9+eabY35OVVWVPB7P8JGRkeGkTAAAEEWCuoDV5Qq8sNIYM6JtyLx58zRv3rzh1wUFBWpvb9eTTz6pr371q6OeU15errKysuHXPT09BBIAAGKUo5mRmTNnKj4+fsQsSGdn54jZksu59dZb9e67747550lJSUpJSQk4AABAbHIURhITE5Wbm6uGhoaA9oaGBhUWFo77fU6ePCmvl/0zAABAEMs0ZWVlWrdunfLy8lRQUKBdu3apra1NpaWlki4tsZw/f1579uyRJG3fvl1f+tKXtHDhQvX392vv3r06cOCADhw4ENqRAACAqOQ4jJSUlKi7u1vbtm2Tz+dTTk6O6uvrlZmZKUny+Xxqa2sb7t/f36+HHnpI58+fV3JyshYuXKif/exn+sY3vhG6UQAAgKjlMsYY20V8np6eHnk8Hvn9fq4fAQAgSoz395tn0wAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIA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rTUpKSlm69attoYQFKfj/qxovJvG6Ziffvppc+jQIXPmzBlz+vRps2XLFiPJHDhwwNYQHHM65t7eXpOenm6+/e1vm//5n/8xb7zxhrn++uvNxo0bbQ0hKMH+/3vt2rVm8eLFkS43JJyO+fnnnzcJCQmmurranD171hw9etTk5eWZ/Px8W0NwzOmY33nnHfPiiy+aM2fOmF/96lempKTEpKammtbWVksjcGbKhBFjjNmxY4fJzMw0iYmJZtGiReaNN94Y/rO7777bLF26dPj10qVLjaQRx9133x35wifAyZifffZZs3DhQnPllVealJQUc8stt5jq6mozMDBgofKJcTLuz4rGMGKMszE/9thjZu7cucbtdpsvfvGL5s/+7M/Mz372MwtVT4zT7/ntt982y5cvN8nJySY9Pd2UlZWZjz/+OMJVT5zTcX/44YcmOTnZ7Nq1K8KVho7TMT/77LMmOzvbJCcnG6/Xa/76r//avPfeexGuemKcjLmlpcXcfPPNJjk52aSkpJg///M/N7/5zW8sVB0clzFRMGcFAABiVhQsJAEAgFhGGAEAAFYRRgAAgFWEEQAAYBVhBAAAWEUYAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABWEUYAAIBV/w+VpjKACTa2QwAAAABJRU5ErkJggg==",
+      "image/png": "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Fenw4cMTT8cEg8FJe46EQiEdPHhQ3/3ud+emagAAsGA43mfEBvYZAQAg8czLPiMAAABzjTACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALBqVmGkqalJ+fn58ng88vv9amlpOWf/kZER7dixQ3l5eUpPT9eKFSu0d+/eWRUMAAAWllSnAw4cOKCamho1NTVp9erVeuihh7R+/Xp1dXXp0ksvnXLMzTffrHfeeUePPvqoPv7xj6u/v1/vv//+eRcPAAASn8sYY5wMKC0tVXFxsZqbmyfaCgoKtGHDBjU2Nk7qf+TIEX3xi1/UW2+9pSVLlsyqyHA4LK/Xq1AopMzMzFn9DgDA3IqMGbV1n1b/0LCWLfZoVf4SuVNctsv6SIladyKa6fnb0crI6Oio2tvbtX379qj2iooKtba2TjnmmWeeUUlJie677z499thjWrRokT772c/q7//+75WRkeHk7QFgxjjhzK8jnUE1HOpSMDQ80ebzelRfWah1RT6LlZ1bota90DkKIwMDA4pEIsrOzo5qz87OVl9f35Rj3nrrLR09elQej0dPP/20BgYG9LWvfU2nT5+e9r6RkZERjYyMTLwOh8NOygSQ5DjhzK8jnUFt239cH15W7wsNa9v+42reVByX85yodSeDWd3A6nJF/9+FMWZS27ixsTG5XC49/vjjWrVqlT796U9r165d+sEPfqDf/e53U45pbGyU1+udOHJzc2dTJoAkNH7COTuISP93wjnSGbRU2cIQGTNqONQ16YQuaaKt4VCXImOO7gCYd4lad7JwFEaysrLkdrsnrYL09/dPWi0Z5/P5dMkll8jr9U60FRQUyBijt99+e8oxdXV1CoVCE0dvb6+TMgEkKU4486+t+/SkoHc2IykYGlZb9+nYFTUDiVp3snAURtLS0uT3+xUIBKLaA4GA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SUlRTk6O7TLiUmZm5oL9I55LzNPMME8zwzzNDPM0Mwt9ns61IjKOG1gBAIBVhBEAAGAVYSRBpaenq76+Xunp6bZLiWvM08wwTzPDPM0M8zQzzNP/SYgbWAEAwMLFyggAALCKMAIAAKwijAAAAKsIIwAAwCrCSBxrampSfn6+PB6P/H6/Wlpapu0bDAa1ceNGrVy5UikpKaqpqYldoZY5maennnpKN910k/7wD/9QmZmZKisr07/8y7/EsFp7nMzT0aNHtXr1ai1dulQZGRm68sor9cADD8SwWnuczNPZXnrpJaWmpurqq6+e3wLjhJN5ev755+VyuSYdv/rVr2JYcew5/VsaGRnRjh07lJeXp/T0dK1YsUJ79+6NUbWWGcSlH/3oR+aCCy4wDz/8sOnq6jJ33nmnWbRokfnv//7vKft3d3ebO+64w/zwhz80V199tbnzzjtjW7AlTufpzjvvNPfee69pa2szJ06cMHV1deaCCy4wx48fj3HlseV0no4fP26eeOIJ09nZabq7u81jjz1mLrzwQvPQQw/FuPLYcjpP437729+aP/qjPzIVFRXmk5/8ZGyKtcjpPD333HNGknn99ddNMBicON5///0YVx47s/lb+uxnP2tKS0tNIBAw3d3d5t///d/NSy+9FMOq7SGMxKlVq1aZ6urqqLYrr7zSbN++/SPHXn/99UkTRs5nnsYVFhaahoaGuS4trszFPH3uc58zmzZtmuvS4sps56mqqsrcddddpr6+PinCiNN5Gg8jv/nNb2JQXXxwOke/+MUvjNfrNYODg7EoL+5wmSYOjY6Oqr29XRUVFVHtFRUVam1ttVRV/JmLeRobG9PQ0JCWLFkyHyXGhbmYp46ODrW2tur666+fjxLjwmzn6fvf/77efPNN1dfXz3eJceF8/p6uueYa+Xw+rV27Vs8999x8lmnVbObomWeeUUlJie677z5dcskluuKKK/TNb35Tv/vd72JRsnUJ8UV5yWZgYECRSETZ2dlR7dnZ2err67NUVfyZi3n6zne+o//93//VzTffPB8lxoXzmaecnBz9z//8j95//33dfffd2rp163yWatVs5umNN97Q9u3b1dLSotTU5PjP6Wzmyefzac+ePfL7/RoZGdFjjz2mtWvX6vnnn9enPvWpWJQdU7OZo7feektHjx6Vx+PR008/rYGBAX3ta1/T6dOnk+K+keT415OgXC5X1GtjzKQ2zH6ennzySd1999362c9+pmXLls1XeXFjNvPU0tKid999Vy+//LK2b9+uj3/847rlllvms0zrZjpPkUhEGzduVENDg6644opYlRc3nPw9rVy5UitXrpx4XVZWpt7eXt1///0LMoyMczJHY2Njcrlcevzxxye+5XbXrl36whe+oN27dysjI2Pe67WJMBKHsrKy5Ha7JyXo/v7+SUk7mZ3PPB04cEBbtmzRj3/8Y914443zWaZ15zNP+fn5kqRPfOITeuedd3T33Xcv2DDidJ6GhoZ07NgxdXR06Pbbb5d05oRijFFqaqr+9V//VTfccENMao+lufrv07XXXqv9+/fPdXlxYTZz5PP5dMkll0wEEUkqKCiQMUZvv/22Lr/88nmt2TbuGYlDaWlp8vv9CgQCUe2BQEDl5eWWqoo/s52nJ598UrfddpueeOIJfeYzn5nvMq2bq78nY4xGRkbmury44XSeMjMz9ctf/lKvvPLKxFFdXa2VK1fqlVdeUWlpaaxKj6m5+nvq6OiQz+eb6/LiwmzmaPXq1Tp16pTefffdibYTJ04oJSVFOTk581pvXLB26yzOafyxsEcffdR0dXWZmpoas2jRIvPrX//aGGPM9u3bzebNm6PGdHR0mI6ODuP3+83GjRtNR0eHefXVV22UHzNO5+mJJ54wqampZvfu3VGPGP72t7+19RFiwuk8fe973zPPPPOMOXHihDlx4oTZu3evyczMNDt27LD1EWJiNv/uzpYsT9M4nacHHnjAPP300+bEiROms7PTbN++3UgyBw8etPUR5p3TORoaGjI5OTnmC1/4gnn11VfNCy+8YC6//HKzdetWWx8hpggjcWz37t0mLy/PpKWlmeLiYvPCCy9M/OxLX/qSuf7666P6S5p05OXlxbZoC5zM0/XXXz/lPH3pS1+KfeEx5mSe/vEf/9FcddVV5sILLzSZmZnmmmuuMU1NTSYSiVioPLac/rs7W7KEEWOczdO9995rVqxYYTwej/nYxz5mrrvuOvPzn//cQtWx5fRv6bXXXjM33nijycjIMDk5Oaa2tta89957Ma7aDpcxxlhalAEAAOCeEQAAYBdhBAAAWEUYAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFX/H3N6t3HGHIYSAAAAAElFTkSuQmCC",
       "text/plain": [
        "
" ] @@ -652,7 +608,7 @@ "def noise(length: int = 1):\n", " return np.random.rand(length)\n", "\n", - "@node(\"fig\")\n", + "@function_node(\"fig\")\n", "def plot(x, y):\n", " return plt.scatter(x, y)\n", "\n", @@ -705,7 +661,7 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiMAAAGdCAYAAADAAnMpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAkOklEQVR4nO3de2zb1f3/8ZfttDGXxCgtSdyLSqi41ETAkiolZRUao1kAhTFpovuylovan2iBQelgv1adCEFIEUxUXEbD6LgItXQRDBiRukC031ZaypbRphIhaKA2Wy84REmEYy5ph31+f+SbUNdOGzuJj/3x8yH5D58c1+8cxf68es7ncz4uY4wRAACAJW7bBQAAgNxGGAEAAFYRRgAAgFWEEQAAYBVhBAAAWEUYAQAAVhFGAACAVYQRAABgVZ7tAsYjGo3qs88+U0FBgVwul+1yAADAOBhjFA6HNWvWLLndY89/ZEUY+eyzzzR37lzbZQAAgBQcPnxYc+bMGfPnWRFGCgoKJA3/MoWFhZarAQAA4zE4OKi5c+eOHsfHkhVhZGRpprCwkDACAECWOd0pFpzACgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALAqKzY9A2BHJGrU3j2g3vCQigu8qiorksfN/aEATC7CCICEWjuDamjpUjA0NNrm93lVXxdQbbnfYmUAnIZlGgBxWjuDWrN1X0wQkaSe0JDWbN2n1s6gpcoAOBFhBECMSNSooaVLJsHPRtoaWroUiSbqAQDJI4wAiNHePRA3I3IiIykYGlJ790D6igLgaIQRADF6w2MHkVT6AcDpEEYAxCgu8E5qPwA4HcIIgBhVZUXy+7wa6wJel4avqqkqK0pnWQAcjDACIIbH7VJ9XUCS4gLJyPP6ugD7jQCYNIQRAHFqy/1qWl6hUl/sUkypz6um5RXsMwJgUrHpGYCEasv9WhooZQdWAFOOMAJgTB63S9XzZ9guA4DDsUwDAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAq1IKI5s3b1ZZWZm8Xq8qKyu1a9euU/bftm2bLrvsMp155pny+/26/fbb1d/fn1LBAADAWZIOI83NzVq7dq02btyojo4OLVmyRNdee60OHTqUsP/u3bt1yy23aOXKlfroo4/06quv6p///KdWrVo14eIBAED2SzqMbNq0SStXrtSqVau0YMECPfHEE5o7d66ampoS9v/73/+u8847T/fcc4/Kysr0/e9/X3fccYc++OCDCRcPAACyX1Jh5Pjx49q7d69qampi2mtqarRnz56Er1m8eLGOHDmiHTt2yBijzz//XK+99pquv/76Md/n2LFjGhwcjHkAAABnSiqM9PX1KRKJqKSkJKa9pKREPT09CV+zePFibdu2TcuWLdP06dNVWlqqc845R08//fSY79PY2Cifzzf6mDt3bjJlAgCALJLSCawulyvmuTEmrm1EV1eX7rnnHj344IPau3evWltb1d3drdWrV4/572/YsEGhUGj0cfjw4VTKBAAAWSAvmc4zZ86Ux+OJmwXp7e2Nmy0Z0djYqCuvvFIPPPCAJOnSSy/VWWedpSVLluiRRx6R3++Pe01+fr7y8/OTKQ0AAGSppGZGpk+frsrKSrW1tcW0t7W1afHixQlf8/XXX8vtjn0bj8cjaXhGBQAA5Lakl2nWrVun3//+93rhhRf08ccf67777tOhQ4dGl102bNigW265ZbR/XV2dXn/9dTU1NengwYN67733dM8996iqqkqzZs2avN8EAABkpaSWaSRp2bJl6u/v18MPP6xgMKjy8nLt2LFD8+bNkyQFg8GYPUduu+02hcNh/fa3v9Uvf/lLnXPOObr66qv16KOPTt5vAQAAspbLZMFayeDgoHw+n0KhkAoLC22XAwAAxmG8x2/uTQMAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALAq6bv2AtkoEjVq7x5Qb3hIxQVeVZUVyeN22S4LACDCCHJAa2dQDS1dCoaGRtv8Pq/q6wKqLfdbrAwAILFMA4dr7QxqzdZ9MUFEknpCQ1qzdZ9aO4OWKgMAjCCMwLEiUaOGli6ZBD8baWto6VIkmqgHACBd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/1/96lfm4osvjmm74447zBVXXDFlNTpJsuOdSCAQMA0NDZNdmiOlOt7Lli0zv/71r019fT1hZJySHes///nPxufzmf7+/nSU5zjJjvdvfvMbc/7558e0PfXUU2bOnDlTVqMTjSeMpPM46YhlmuPHj2vv3r2qqamJaa+pqdGePXsSvub999+P6/+jH/1IH3zwgf773/9OWa1OkMp4nywajSocDquoqGgqSnSUVMf7xRdf1IEDB1RfXz/VJTpGKmP91ltvaeHChXrsscc0e/ZsXXjhhbr//vv1zTffpKPkrJbKeC9evFhHjhzRjh07ZIzR559/rtdee03XX399OkrOKek8TmbFjfJOp6+vT5FIRCUlJTHtJSUl6unpSfianp6ehP2//fZb9fX1ye/3T1m92S6V8T7Z448/rq+++ko33XTTVJToKKmM96effqr169dr165dystzxMc8LVIZ64MHD2r37t3yer1644031NfXpzvvvFMDAwOcN3IaqYz34sWLtW3bNi1btkxDQ0P69ttvdcMNN+jpp59OR8k5JZ3HSUfMjIxwuVwxz40xcW2n65+oHYklO94jtm/froceekjNzc0qLi6eqvIcZ7zjHYlEdPPNN6uhoUEXXnhhuspzlGT+tqPRqFwul7Zt26aqqipdd9112rRpk1566SVmR8YpmfHu6urSPffcowcffFB79+5Va2ururu7tXr16nSUmnPSdZx0xH+ZZs6cKY/HE5eke3t741LdiNLS0oT98/LyNGPGjCmr1QlSGe8Rzc3NWrlypV599VVdc801U1mmYyQ73uFwWB988IE6Ojp09913Sxo+YBpjlJeXp3feeUdXX311WmrPNqn8bfv9fs2ePTvmNukLFiyQMUZHjhzRBRdcMKU1Z7NUxruxsVFXXnmlHnjgAUnSpZdeqrPOOktLlizRI488wqz2JErncdIRMyPTp09XZWWl2traYtrb2tq0ePHihK+prq6O6//OO+9o4cKFmjZt2pTV6gSpjLc0PCNy22236ZVXXmF9NwnJjndhYaE+/PBD7d+/f/SxevVqXXTRRdq/f78WLVqUrtKzTip/21deeaU+++wzffnll6Ntn3zyidxut+bMmTOl9Wa7VMb766+/ltsde+jyeDySvvtfOyZHWo+Tk35KrCUjl4c9//zzpqury6xdu9acddZZ5t///rcxxpj169ebFStWjPYfuWTpvvvuM11dXeb555/n0t4kJDver7zyisnLyzPPPPOMCQaDo48vvvjC1q+QVZId75NxNc34JTvW4XDYzJkzx/z0pz81H330kdm5c6e54IILzKpVq2z9Clkl2fF+8cUXTV5entm8ebM5cOCA2b17t1m4cKGpqqqy9StkjXA4bDo6OkxHR4eRZDZt2mQ6OjpGL6O2eZx0TBgxxphnnnnGzJs3z0yfPt1UVFSYnTt3jv7s1ltvNVdddVVM/7/97W/me9/7npk+fbo577zzTFNTU5orzm7JjPdVV11lJMU9br311vQXnqWS/fs+EWEkOcmO9ccff2yuueYac8YZZ5g5c+aYdevWma+//jrNVWevZMf7qaeeMoFAwJxxxhnG7/ebn//85+bIkSNprjr7/PWvfz3l97DN46TLGOa1AACAPY44ZwQAAGQvwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACr/j8tzG4eQYg8SwAAAABJRU5ErkJggg==", + "image/png": "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zjlHfr9fd999t/r6+pJUrUsYB/rTn/5kJk2aZF5++WXT0dFhHnroIXPuueeazz77LGb7AwcOmHPOOcc89NBDpqOjw7z88stm0qRJ5q233kpy5c4Sbz889NBD5sknnzS7du0yn3zyiVm9erWZNGmS2b17d5Ird5Z4+yHiiy++MBdffLGpqqoyV111VXKKdbBE+uGWW24xc+fONa2traazs9P861//Mu+//34Sq3aeePuhra3NZGRkmGeffdYcOHDAtLW1mSuuuMLceuutSa7c2RwZRsrLy01tbW3Utcsvv9ysWrUqZvtf/vKX5vLLL4+6du+995prrrnmrNXoBvH2QyxFRUWmvr5+oktzlUT7oaamxvzmN78xdXV1hJEJEG8//PWvfzU+n8/09fUlozzXiLcffve735mLL7446tpzzz1nZs6cedZqdCPHTdMcO3ZM7e3tqqqqirpeVVWlHTt2xHzOzp07R7W/8cYb9cEHH+jbb789a7U6WSL9cLJwOKzBwUFNnTr1bJToCon2wyuvvKL9+/errq7ubJfoCon0w7vvvquysjI99dRTuvDCC3XppZfqkUce0TfffJOMkh0pkX6orKzUoUOH1NzcLGOMPv/8c7311lu6+eabk1Gya6TFjfLi0dvbq1AopLy8vKjreXl56u7ujvmc7u7umO2PHz+u3t5e+f3+s1avUyXSDyf7/e9/r6+++kq333772SjRFRLph08//VSrVq1SW1ubsrIc91+EFYn0w4EDB7R9+3Z5vV6988476u3t1X333af+/n7WjSQokX6orKzU5s2bVVNTo6GhIR0/fly33HKLnn/++WSU7BqOGxmJ8Hg8UV8bY0ZdO137WNcRn3j7IeKNN97Q448/rqamJs2YMeNsleca4+2HUCikxYsXq76+XpdeemmyynONeP49hMNheTwebd68WeXl5brpppv09NNP69VXX2V05AzF0w8dHR168MEH9dhjj6m9vV0tLS3q7OxUbW1tMkp1Dcd97Jk+fboyMzNHpdyenp5RaTgiPz8/ZvusrCxNmzbtrNXqZIn0Q0RTU5OWLVumN998UzfccMPZLNPx4u2HwcFBffDBB9qzZ48eeOABScNvisYYZWVlaevWrbr++uuTUruTJPLvwe/368ILL4y6/fqcOXNkjNGhQ4d0ySWXnNWanSiRfmhoaNC8efP06KOPSpKuvPJKnXvuuZo/f76eeOIJRs4niONGRrKzs1VaWqrW1tao662traqsrIz5nIqKilHtt27dqrKyMk2aNOms1epkifSDNDwictddd+n1119nTnYCxNsPubm5+vDDD7V3796RR21trS677DLt3btXc+fOTVbpjpLIv4d58+bpyJEj+vLLL0euffLJJ8rIyNDMmTPPar1OlUg/fP3118rIiH6rzMzMlPS/EXRMAFsrZ8+myNatDRs2mI6ODrNixQpz7rnnmv/+97/GGGNWrVplli5dOtI+srX34YcfNh0dHWbDhg1s7Z0A8fbD66+/brKyssy6detMIBAYeXzxxRe2/gRHiLcfTsZumokRbz8MDg6amTNnmttuu8189NFHZtu2beaSSy4x99xzj60/wRHi7YdXXnnFZGVlmfXr15v9+/eb7du3m7KyMlNeXm7rT3AkR4YRY4xZt26dmTVrlsnOzjYlJSVm27ZtI9+78847zXXXXRfV/u9//7v5/ve/b7Kzs81FF11kGhsbk1yxM8XTD9ddd52RNOpx5513Jr9wh4n338OJCCMTJ95++Pjjj80NN9xgJk+ebGbOnGlWrlxpvv766yRX7Tzx9sNzzz1nioqKzOTJk43f7zc/+clPzKFDh5JctbN5jGGcCQAA2OO4NSMAACC9EEYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFWEEQAAYBVhBAAAWEUYAQAAVhFGAACAVYQRAABgFWEEAABY9f8Alw4iNhhoKkMAAAAASUVORK5CYII=", "text/plain": [ "
" ] @@ -751,9 +707,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/workflow.py:187: UserWarning: Reassigning the node bulk_structure to the label structure when adding it to the workflow with_prebuilt.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:175: 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/workflow.py:187: UserWarning: Reassigning the node lammps to the label engine when adding it to the workflow with_prebuilt.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:175: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent with_prebuilt.\n", " warn(\n" ] }, @@ -761,16 +717,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "The job JUSTAJOBNAME was saved and received the ID: 7418\n" + "The job JUSTAJOBNAME was saved and received the ID: 9553\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/workflow.py:187: UserWarning: Reassigning the node calc_md to the label calc when adding it to the workflow with_prebuilt.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:175: 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/workflow.py:187: UserWarning: Reassigning the node scatter to the label plot when adding it to the workflow with_prebuilt.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:175: UserWarning: Reassigning the node scatter to the label plot when adding it to the parent with_prebuilt.\n", " warn(\n" ] }, @@ -826,7 +782,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.0" + "version": "3.11.4" } }, "nbformat": 4, From eceb8178ed21024985c3a4cc6166c4c2e196418e Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 28 Jun 2023 21:59:56 +0000 Subject: [PATCH 269/756] Format black --- pyiron_contrib/workflow/is_nodal.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py index e022ee603..94e94e835 100644 --- a/pyiron_contrib/workflow/is_nodal.py +++ b/pyiron_contrib/workflow/is_nodal.py @@ -71,12 +71,12 @@ class IsNodal(HasToDict, ABC): """ def __init__( - self, - label: str, - *args, - parent: Optional[HasNodes] = None, - run_on_updates: bool = False, - **kwargs + self, + label: str, + *args, + parent: Optional[HasNodes] = None, + run_on_updates: bool = False, + **kwargs, ): """ A mixin class for objects that can form nodes in the graph representation of a From ab8de4bc4815e55f319ffe6d137899d7267c7af9 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 15:19:44 -0700 Subject: [PATCH 270/756] Black --- pyiron_contrib/workflow/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 1b90f315f..b227ddded 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -78,7 +78,7 @@ def __init__( *args, parent: Optional[Composite] = None, run_on_updates: bool = False, - **kwargs + **kwargs, ): """ A mixin class for objects that can form nodes in the graph representation of a From a98f4ddc05e652e6c529b57f9d1bcff7bca9c62e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 28 Jun 2023 15:40:19 -0700 Subject: [PATCH 271/756] Update docs It's not exhaustive or finalized, but in general the universal stuff now sits on `Node` and the stuff specific to wrapping functions sits on `Function(Node)` --- pyiron_contrib/workflow/function.py | 61 ++++++++++-------------- pyiron_contrib/workflow/node.py | 72 +++++++++++++++++++---------- 2 files changed, 72 insertions(+), 61 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 62f475c7d..0103b38de 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -17,33 +17,17 @@ class Function(Node): """ - Function nodes have input and output data channels that interface with the outside - world, and a callable that determines what they actually compute. After running, - their output channels are updated with the results of the node's computation, which - triggers downstream node updates if those output channels are connected to other - input channels. - - An "update" is gentle and will only trigger the node to run if its run-on-update - flag is set to true and if its input is all ready -- i.e. having values matching - the type hints, and if it is not either already running or already failed. - - They also have input and output signal channels -- a run input and a ran output, - although these are extensible in child classes. Calling the run input signal - triggers the run method, and after running a signal is sent out on the ran output - signal channel. In this way, execution flow can be managed manually by connecting - signal channels. Be careful as the run input signal bypasses the checks for an - update and really forces a node to run with whatever data it currently has. - - Signal channels cannot be connected to data channels. - - Nodes won't update themselves while setting inputs to initial values, but can - optionally update themselves at the end instantiation. - - Nodes must be instantiated with a callable to deterimine their function, and a - strings to name each returned value of that callable. (If you really want to return - a tuple, just have multiple return values but only one output label -- there is - currently no way to mix-and-match, i.e. to have multiple return values at least one - of which is a tuple.) + Function nodes wrap an arbitrary python function. + Node IO, including type hints, is generated automatically from the provided function + and (in the case of labeling output channels) the provided output labels. + On running, the function node executes this wrapped function with its current input + and uses the results to populate the node output. + + Function nodes must be instantiated with a callable to deterimine their function, + and a string to name each returned value of that callable. (If you really want to + return a tuple, just have multiple return values but only one output label -- there + is currently no way to mix-and-match, i.e. to have multiple return values at least + one of which is a tuple.) The node label (unless otherwise provided), IO types, and input defaults for the node are produced _automatically_ from introspection of the node function. @@ -52,13 +36,18 @@ class Function(Node): keys corresponding to the channel labels (i.e. the node arguments of the node function, or the output labels provided). - Actual node instances can either be instances of the base node class, in which case - the callable node function and output labels *must* be provided, in addition to - other data, OR they can be instances of children of this class. + Actual function node instances can either be instances of the base node class, in + which case the callable node function and output labels *must* be provided, in + addition to other data, OR they can be instances of children of this class. Those children may define some or all of the node behaviour at the class level, and modify their signature accordingly so this is not available for alteration by the user, e.g. the node function and output labels may be hard-wired. + Although not strictly enforced, it is a best-practice that where possible, function + nodes should be both functional (always returning the same output given the same + input) and idempotent (not modifying input data in-place, but creating copies where + necessary and returning new objects as output). + Args: node_function (callable): The function determining the behaviour of the node. *output_labels (str): A name for each return value of the node function. @@ -94,8 +83,8 @@ class Function(Node): disconnect: Disconnect all data and signal IO connections. Examples: - At the most basic level, to use nodes all we need to do is provide the `Node` - class with a function and labels for its output, like so: + At the most basic level, to use nodes all we need to do is provide the + `Function` class with a function and labels for its output, like so: >>> from pyiron_contrib.workflow.function import Function >>> >>> def mwe(x, y): @@ -202,14 +191,14 @@ class with a function and labels for its output, like so: instantiation flags to `True` for nodes that execute quickly and are meant to _always_ have good output data. - In these examples, we've instantiated nodes directly from the base `Node` class, - and populated their input directly with data. + In these examples, we've instantiated nodes directly from the base `Function` + class, and populated their input directly with data. In practice, these nodes are meant to be part of complex workflows; that means both that you are likely to have particular nodes that get heavily re-used, and that you need the nodes to pass data to each other. - For reusable nodes, we want to create a sub-class of `Node` that fixes some of - the node behaviour -- usually the `node_function` and `output_labels`. + For reusable nodes, we want to create a sub-class of `Function` that fixes some + of the node behaviour -- usually the `node_function` and `output_labels`. This can be done most easily with the `node` decorator, which takes a function and returns a node class: diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index b227ddded..1cdc37f92 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -21,41 +21,64 @@ class Node(HasToDict, ABC): """ - A mixin class for objects that can form nodes in the graph representation of a - computational workflow. - - Nodal objects have `inputs` and `outputs` channels for passing data, and `signals` - channels for making callbacks on the class (input) and controlling execution flow - (output) when connected to other nodal objects. - - Nodal objects can `run` to complete some computational task, or call a softer - `update` which will run the task only if it is `ready` -- i.e. it is not currently - running, has not previously tried to run and failed, and all of its inputs are ready - (i.e. populated with data that passes type requirements, if any). + Nodes are elements of a computational graph. + They have input and output data channels that interface with the outside + world, and a callable that determines what they actually compute, and input and + output signal channels that can be used to customize the execution flow of the + graph; + Together these channels represent edges on the computational graph. + + Nodes can be run to force their computation, or more gently updated, which will + trigger a run only if the `run_on_update` flag is set to true and all of the input + is ready (i.e. channel values conform to any type hints provided). + + Nodes may have a `parent` node that owns them as part of a sub-graph. + + Every node must be named with a `label`, and may use this label to attempt to create + a working directory in memory for itself if requested. + These labels also help to identify nodes in the wider context of (potentially + nested) computational graphs. + + 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. + + 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` + methods. + + This is an abstract class. + Children *must* define how `inputs` and `outputs` are constructed, and what will + happen `on_run`. + They may also override the `run_args` property to specify input passed to the + defined `on_run` method, and may add additional signal channels to the signals IO. + + # TODO: Everything with (de)serialization and executors for running on something + # other than the main python process. Attributes: connected (bool): Whether _any_ of the IO (including signals) are connected. - failed (bool): Whether the nodal object raised an error calling `run`. (Default + failed (bool): Whether the node raised an error calling `run`. (Default is False.) fully_connected (bool): whether _all_ of the IO (including signals) are connected. inputs (pyiron_contrib.workflow.io.Inputs): **Abstract.** Children must define a property returning an `Inputs` object. - label (str): A name for the nodal object. - output (pyiron_contrib.workflow.io.Outputs): **Abstract.** Children must define + label (str): A name for the node. + outputs (pyiron_contrib.workflow.io.Outputs): **Abstract.** Children must define a property returning an `Outputs` object. parent (pyiron_contrib.workflow.composite.Composite | None): The parent object owning this, if any. - ready (bool): Whether the inputs are all ready and the nodal object is neither + ready (bool): Whether the inputs are all ready and the node is neither already running nor already failed. run_on_updates (bool): Whether to run when you are updated and all your input is ready and your status does not prohibit running. (Default is False). - running (bool): Whether the nodal object has called `run` and has not yet - received output from from this call. (Default is False.) + running (bool): Whether the node has called `run` and has not yet + received output from this call. (Default is False.) server (Optional[pyiron_base.jobs.job.extension.server.generic.Server]): A server object for computing things somewhere else. Default (and currently _only_) behaviour is to compute things on the main python process owning - the nodal object. + the node. signals (pyiron_contrib.workflow.io.Signals): A container for input and output signals, which are channels for controlling execution flow. By default, has a `signals.inputs.run` channel which has a callback to the `run` method, @@ -67,9 +90,8 @@ class Node(HasToDict, ABC): Methods: disconnect: Remove all connections, including signals. - run: **Abstract.** Do the thing. - update: **Abstract.** Do the thing if you're ready and you run on updates. - TODO: Once `run_on_updates` is in this class, we can un-abstract this. + on_run: **Abstract.** Do the thing. + run: A wrapper to handle all the infrastructure around executing `on_run`. """ def __init__( @@ -85,7 +107,7 @@ def __init__( computational workflow. Args: - label (str): A name for this nodal object. + label (str): A name for this node. *args: Arguments passed on with `super`. **kwargs: Keyword arguments passed on with `super`. @@ -122,7 +144,7 @@ def outputs(self) -> Outputs: @abstractmethod def on_run(self) -> callable[..., tuple]: """ - What the nodal object actually does! + What the node actually does! """ pass @@ -144,8 +166,8 @@ def process_run_result(self, run_output: tuple) -> None: def run(self) -> None: """ - Executes the functionality of the nodal object defined in `on_run`. - Handles the status of the nodal object, and communicating with any remote + Executes the functionality of the node defined in `on_run`. + Handles the status of the node, and communicating with any remote computing resources. """ if self.running: From f85d81a503db0c207c32df8efa603ca09ed26eec Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Thu, 29 Jun 2023 08:24:47 +0200 Subject: [PATCH 272/756] Lower distributed version to temporarily avoid py3.8 issues --- .ci_support/environment.yml | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 6739fedec..7297c6430 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -21,4 +21,4 @@ dependencies: - typeguard =4.0.0 - aws-sam-translator =1.68.0 - pympipool =0.5.1 -- distributed =2023.6.0 +- distributed =2023.5.0 diff --git a/setup.py b/setup.py index 311af7aee..676138c9a 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ 'typeguard==4.0.0' ], 'tinybase': [ - 'distributed==2023.6.0', + 'distributed==2023.5.0', 'pympipool==0.5.1' ] }, From 67671c8688763dffddbf6aeac62c1361c00a40c0 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Thu, 29 Jun 2023 08:02:21 +0000 Subject: [PATCH 273/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 529bd1a28..4d627ed0a 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -21,6 +21,6 @@ dependencies: - typeguard =4.0.0 - aws-sam-translator =1.68.0 - pympipool =0.5.1 -- distributed =2023.6.0 +- distributed =2023.5.0 - python >= 3.10 - lammps diff --git a/docs/environment.yml b/docs/environment.yml index 03eef5b9c..8e918fa20 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -23,4 +23,4 @@ dependencies: - typeguard =4.0.0 - aws-sam-translator =1.68.0 - pympipool =0.5.1 -- distributed =2023.6.0 +- distributed =2023.5.0 From f11424c6a002756c41fc785a4df387063e38c7f6 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 29 Jun 2023 09:47:11 -0700 Subject: [PATCH 274/756] Introduce NotData as a default for data channels And have them fail readiness when this is their value --- pyiron_contrib/workflow/channels.py | 25 +++++++++++++++++++++---- tests/unit/workflow/test_channels.py | 16 +++++++++++++++- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index 754ca4337..a7510a4f7 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -134,6 +134,15 @@ def to_dict(self) -> dict: } +class NotData: + """ + This class exists purely to initialize data channel values where no default value + is provided; it lets the channel know that it has _no data in it_ and thus should + not identify as ready. + """ + pass + + class DataChannel(Channel, ABC): """ Data channels control the flow of data on the graph. @@ -170,6 +179,10 @@ class DataChannel(Channel, ABC): E.g. `Literal[1, 2]` is as or more specific that both `Literal[1, 2]` and `Literal[1, 2, "three"]`. + The data `value` will initialize to an instance of `NotData` by default. + The channel will identify as `ready` when the value is _not_ an instance of + `NotData`, and when the value conforms to type hints (if any). + Warning: Type hinting in python is quite complex, and determining when a hint is "more specific" can be tricky. For instance, in python 3.11 you can now type @@ -182,7 +195,7 @@ def __init__( self, label: str, node: Node, - default: typing.Optional[typing.Any] = None, + default: typing.Optional[typing.Any] = NotData, type_hint: typing.Optional[typing.Any] = None, ): super().__init__(label=label, node=node) @@ -199,9 +212,13 @@ def ready(self) -> bool: (bool): Whether the value matches the type hint. """ if self.type_hint is not None: - return valid_value(self.value, self.type_hint) + return self.value_is_data and valid_value(self.value, self.type_hint) else: - return True + return self.value_is_data + + @property + def value_is_data(self): + return self.value is not NotData def update(self, value) -> None: """ @@ -314,7 +331,7 @@ def __init__( self, label: str, node: Node, - default: typing.Optional[typing.Any] = None, + default: typing.Optional[typing.Any] = NotData, type_hint: typing.Optional[typing.Any] = None, strict_connections: bool = True, ): diff --git a/tests/unit/workflow/test_channels.py b/tests/unit/workflow/test_channels.py index 1b5053aca..48bbd0b3d 100644 --- a/tests/unit/workflow/test_channels.py +++ b/tests/unit/workflow/test_channels.py @@ -2,7 +2,7 @@ from sys import version_info from pyiron_contrib.workflow.channels import ( - InputData, OutputData, InputSignal, OutputSignal + InputData, OutputData, InputSignal, OutputSignal, NotData ) @@ -100,6 +100,20 @@ def test_connection_validity_tests(self): ) def test_ready(self): + with self.subTest("Test defaults and not-data"): + without_default = InputData(label="without_default", node=DummyNode()) + self.assertIs( + without_default.value, + NotData, + msg=f"Without a default, spec is to have a NotData value but got " + f"{type(without_default.value)}" + ) + self.assertFalse( + without_default.ready, + msg="Even without type hints, readiness should be false when the value" + "is NotData" + ) + self.ni1.value = 1 self.assertTrue(self.ni1.ready) From 622a6a5943dfa4651ccb3866a8776924454304b5 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 29 Jun 2023 10:22:37 -0700 Subject: [PATCH 275/756] Update node tests that were expecting a None default --- tests/unit/workflow/test_function.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index ffe03a964..99b61d263 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.workflow.channels import NotData from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.function import ( Fast, Function, SingleValue, function_node, single_value_node @@ -40,7 +41,12 @@ def test_instantiation_update(self): run_on_updates=True, update_on_instantiation=False ) - self.assertIsNone(no_update.outputs.y.value) + self.assertIs( + no_update.outputs.y.value, + NotData, + msg=f"Expected the output to be in its initialized and not-updated NotData " + f"state, but got {no_update.outputs.y.value}" + ) update = Function( plus_one, @@ -106,9 +112,11 @@ def times_two(y): t2 = times_two( update_on_instantiation=False, run_automatically=False, y=l.outputs.y ) - self.assertIsNone( + self.assertIs( t2.outputs.z.value, - msg="Without updates, the output should initially be None" + NotData, + msg=f"Without updates, expected the output to be {NotData} but got " + f"{t2.outputs.z.value}" ) # Nodes should _all_ have the run and ran signals From bb4ff2fc8106fc1d0a41df9c8ac5fd4824a60d3d Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 29 Jun 2023 10:37:48 -0700 Subject: [PATCH 276/756] Don't override the data channel default with None When building input channels in function nodes. And test to make sure it stays this way! --- pyiron_contrib/workflow/function.py | 4 ++-- tests/unit/workflow/test_function.py | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 0103b38de..b55a2f071 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -5,7 +5,7 @@ from functools import partialmethod from typing import get_args, get_type_hints, Optional, TYPE_CHECKING -from pyiron_contrib.workflow.channels import InputData, OutputData +from pyiron_contrib.workflow.channels import InputData, OutputData, NotData from pyiron_contrib.workflow.has_channel import HasChannel from pyiron_contrib.workflow.io import Inputs, Outputs, Signals from pyiron_contrib.workflow.node import Node @@ -398,7 +398,7 @@ def _build_input_channels(self): except KeyError: type_hint = None - default = None + default = NotData # The standard default in DataChannel if value.default is not inspect.Parameter.empty: if is_self: warnings.warn("default value for self ignored") diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 99b61d263..1a37d60dd 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -25,7 +25,20 @@ def no_default(x, y): @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestFunction(unittest.TestCase): def test_defaults(self): - Function(plus_one, "y") + with_defaults = Function(plus_one, "y") + self.assertEqual( + with_defaults.inputs.x.value, + 1, + msg=f"Expected to get the default provided in the underlying function but " + f"got {with_defaults.inputs.x.value}", + ) + without_defaults = Function(no_default, "sum_plus_one") + self.assertIs( + without_defaults.inputs.x.value, + NotData, + msg=f"Expected values with no default specified to start as {NotData} but " + f"got {without_defaults.inputs.x.value}", + ) def test_failure_without_output_labels(self): with self.assertRaises( From 86173f6fec14218cf8853b6f34535efe3cbfb290 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 29 Jun 2023 10:39:14 -0700 Subject: [PATCH 277/756] Test more than just implementation Maybe the implementation test should even be removed... --- tests/unit/workflow/test_function.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 1a37d60dd..be76d878e 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -39,6 +39,11 @@ def test_defaults(self): msg=f"Expected values with no default specified to start as {NotData} but " f"got {without_defaults.inputs.x.value}", ) + self.assertFalse( + without_defaults.ready, + msg="I guess we should test for behaviour and not implementation... Without" + "defaults, the node should not be ready!" + ) def test_failure_without_output_labels(self): with self.assertRaises( From 17f90206d612e1cc03647c8295a3546737159184 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 29 Jun 2023 10:57:38 -0700 Subject: [PATCH 278/756] Update the example notebook --- notebooks/workflow_example.ipynb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 2d7107e30..aca04c84d 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -9,7 +9,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "ee8645d92ea44a7aa8583a924cd3a804", + "model_id": "6a819a64e97a4eb5b91e66cae4689723", "version_major": 2, "version_minor": 0 }, @@ -94,8 +94,9 @@ "id": "22ee2a49-47d1-4cec-bb25-8441ea01faf7", "metadata": {}, "source": [ - "The output is still empty because we haven't `run` the node.\n", - "If we try that now though, we'll just get a type error because the input is not set!\n", + "The output is still empty (`NotData`) because we haven't `run` the node.\n", + "If we try that now though, we'll just get a type error because the input is not set! A softer `update()` will avoid the error because it will see that the node is not `ready` and choose not to `run()`.\n", + "\n", "Let's set the input and run the node:" ] }, @@ -109,7 +110,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'p1': None, 'm1': None}\n", + "{'p1': , 'm1': }\n", "{'p1': 6, 'm1': 4}\n" ] } @@ -184,7 +185,7 @@ "source": [ "adder_node.inputs.x = \"not an integer\"\n", "adder_node.inputs.x.type_hint\n", - "# No error because the update doesn't trigger a run" + "# No error because the update doesn't trigger a run since the type hint is not satisfied" ] }, { @@ -465,7 +466,7 @@ "output_type": "stream", "text": [ "n1 n1 n1 (GreaterThanHalf) output single-value: False\n", - "n2 n2 \n", + "n2 n2 \n", "n3 n3 n3 (GreaterThanHalf) output single-value: False\n", "n4 n4 n4 (GreaterThanHalf) output single-value: False\n", "n5 n5 n5 (GreaterThanHalf) output single-value: False\n" @@ -511,7 +512,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "1 None\n" + "1 \n" ] } ], @@ -594,7 +595,7 @@ "outputs": [ { "data": { - "image/png": "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Fenw4cMTT8cEg8FJe46EQiEdPHhQ3/3ud+emagAAsGA43mfEBvYZAQAg8czLPiMAAABzjTACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALBqVmGkqalJ+fn58ng88vv9amlpOWf/kZER7dixQ3l5eUpPT9eKFSu0d+/eWRUMAAAWllSnAw4cOKCamho1NTVp9erVeuihh7R+/Xp1dXXp0ksvnXLMzTffrHfeeUePPvqoPv7xj6u/v1/vv//+eRcPAAASn8sYY5wMKC0tVXFxsZqbmyfaCgoKtGHDBjU2Nk7qf+TIEX3xi1/UW2+9pSVLlsyqyHA4LK/Xq1AopMzMzFn9DgDA3IqMGbV1n1b/0LCWLfZoVf4SuVNctsv6SIladyKa6fnb0crI6Oio2tvbtX379qj2iooKtba2TjnmmWeeUUlJie677z499thjWrRokT772c/q7//+75WRkeHk7QFgxjjhzK8jnUE1HOpSMDQ80ebzelRfWah1RT6LlZ1bota90DkKIwMDA4pEIsrOzo5qz87OVl9f35Rj3nrrLR09elQej0dPP/20BgYG9LWvfU2nT5+e9r6RkZERjYyMTLwOh8NOygSQ5DjhzK8jnUFt239cH15W7wsNa9v+42reVByX85yodSeDWd3A6nJF/9+FMWZS27ixsTG5XC49/vjjWrVqlT796U9r165d+sEPfqDf/e53U45pbGyU1+udOHJzc2dTJoAkNH7COTuISP93wjnSGbRU2cIQGTNqONQ16YQuaaKt4VCXImOO7gCYd4lad7JwFEaysrLkdrsnrYL09/dPWi0Z5/P5dMkll8jr9U60FRQUyBijt99+e8oxdXV1CoVCE0dvb6+TMgEkKU4486+t+/SkoHc2IykYGlZb9+nYFTUDiVp3snAURtLS0uT3+xUIBKLaA4GA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SUlRTk6O7TLiUmZm5oL9I55LzNPMME8zwzzNDPM0Mwt9ns61IjKOG1gBAIBVhBEAAGAVYSRBpaenq76+Xunp6bZLiWvM08wwTzPDPM0M8zQzzNP/SYgbWAEAwMLFyggAALCKMAIAAKwijAAAAKsIIwAAwCrCSBxrampSfn6+PB6P/H6/Wlpapu0bDAa1ceNGrVy5UikpKaqpqYldoZY5maennnpKN910k/7wD/9QmZmZKisr07/8y7/EsFp7nMzT0aNHtXr1ai1dulQZGRm68sor9cADD8SwWnuczNPZXnrpJaWmpurqq6+e3wLjhJN5ev755+VyuSYdv/rVr2JYcew5/VsaGRnRjh07lJeXp/T0dK1YsUJ79+6NUbWWGcSlH/3oR+aCCy4wDz/8sOnq6jJ33nmnWbRokfnv//7vKft3d3ebO+64w/zwhz80V199tbnzzjtjW7AlTufpzjvvNPfee69pa2szJ06cMHV1deaCCy4wx48fj3HlseV0no4fP26eeOIJ09nZabq7u81jjz1mLrzwQvPQQw/FuPLYcjpP437729+aP/qjPzIVFRXmk5/8ZGyKtcjpPD333HNGknn99ddNMBicON5///0YVx47s/lb+uxnP2tKS0tNIBAw3d3d5t///d/NSy+9FMOq7SGMxKlVq1aZ6urqqLYrr7zSbN++/SPHXn/99UkTRs5nnsYVFhaahoaGuS4trszFPH3uc58zmzZtmuvS4sps56mqqsrcddddpr6+PinCiNN5Gg8jv/nNb2JQXXxwOke/+MUvjNfrNYODg7EoL+5wmSYOjY6Oqr29XRUVFVHtFRUVam1ttVRV/JmLeRobG9PQ0JCWLFkyHyXGhbmYp46ODrW2tur666+fjxLjwmzn6fvf/77efPNN1dfXz3eJceF8/p6uueYa+Xw+rV27Vs8999x8lmnVbObomWeeUUlJie677z5dcskluuKKK/TNb35Tv/vd72JRsnUJ8UV5yWZgYECRSETZ2dlR7dnZ2err67NUVfyZi3n6zne+o//93//VzTffPB8lxoXzmaecnBz9z//8j95//33dfffd2rp163yWatVs5umNN97Q9u3b1dLSotTU5PjP6Wzmyefzac+ePfL7/RoZGdFjjz2mtWvX6vnnn9enPvWpWJQdU7OZo7feektHjx6Vx+PR008/rYGBAX3ta1/T6dOnk+K+keT415OgXC5X1GtjzKQ2zH6ennzySd1999362c9+pmXLls1XeXFjNvPU0tKid999Vy+//LK2b9+uj3/847rlllvms0zrZjpPkUhEGzduVENDg6644opYlRc3nPw9rVy5UitXrpx4XVZWpt7eXt1///0LMoyMczJHY2Njcrlcevzxxye+5XbXrl36whe+oN27dysjI2Pe67WJMBKHsrKy5Ha7JyXo/v7+SUk7mZ3PPB04cEBbtmzRj3/8Y914443zWaZ15zNP+fn5kqRPfOITeuedd3T33Xcv2DDidJ6GhoZ07NgxdXR06Pbbb5d05oRijFFqaqr+9V//VTfccENMao+lufrv07XXXqv9+/fPdXlxYTZz5PP5dMkll0wEEUkqKCiQMUZvv/22Lr/88nmt2TbuGYlDaWlp8vv9CgQCUe2BQEDl5eWWqoo/s52nJ598UrfddpueeOIJfeYzn5nvMq2bq78nY4xGRkbmury44XSeMjMz9ctf/lKvvPLKxFFdXa2VK1fqlVdeUWlpaaxKj6m5+nvq6OiQz+eb6/LiwmzmaPXq1Tp16pTefffdibYTJ04oJSVFOTk581pvXLB26yzOafyxsEcffdR0dXWZmpoas2jRIvPrX//aGGPM9u3bzebNm6PGdHR0mI6ODuP3+83GjRtNR0eHefXVV22UHzNO5+mJJ54wqampZvfu3VGPGP72t7+19RFiwuk8fe973zPPPPOMOXHihDlx4oTZu3evyczMNDt27LD1EWJiNv/uzpYsT9M4nacHHnjAPP300+bEiROms7PTbN++3UgyBw8etPUR5p3TORoaGjI5OTnmC1/4gnn11VfNCy+8YC6//HKzdetWWx8hpggjcWz37t0mLy/PpKWlmeLiYvPCCy9M/OxLX/qSuf7666P6S5p05OXlxbZoC5zM0/XXXz/lPH3pS1+KfeEx5mSe/vEf/9FcddVV5sILLzSZmZnmmmuuMU1NTSYSiVioPLac/rs7W7KEEWOczdO9995rVqxYYTwej/nYxz5mrrvuOvPzn//cQtWx5fRv6bXXXjM33nijycjIMDk5Oaa2tta89957Ma7aDpcxxlhalAEAAOCeEQAAYBdhBAAAWEUYAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFX/H3N6t3HGHIYSAAAAAElFTkSuQmCC", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiMAAAGiCAYAAAA1LsZRAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAnc0lEQVR4nO3dcVBc13328WdZBKuoYl1EtFpbGG9UWQbROAUKBkXNxLaoZJdUzbQmdS1ZrtQJShyHUHteMXKNYTxD7MSqndRQE1t2VckKje1koikh3Zm0NjLTUiHciYIbuxYtSF5EgWaXxAHi5b5/qGy1XpC5a7SHZb+fmfvHPXvu7m/njrQP59x7rsOyLEsAAACGpJkuAAAApDbCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADAqrjDS0tIin88nl8ul4uJidXV1Xbb/U089pfz8fK1cuVKbNm3SkSNH4ioWAAAsP+l2D2hvb1dtba1aWlq0ZcsWPf3009qxY4f6+/t17bXXxvRvbW1VfX29vvWtb+m3f/u31dPToz/7sz/Tr//6r6uqqmpRvgQAAEheDrsPyisrK1NRUZFaW1sjbfn5+dq5c6eam5tj+ldUVGjLli362te+Fmmrra3VqVOndPLkyQ9ROgAAWA5sjYxMT0+rt7dXBw4ciGqvrKxUd3f3nMdMTU3J5XJFta1cuVI9PT361a9+pRUrVsx5zNTUVGR/ZmZG4+PjWrNmjRwOh52SAQCAIZZlaWJiQldffbXS0ua/MsRWGBkdHVU4HJbH44lq93g8Gh4envOY3/3d39UzzzyjnTt3qqioSL29vTp8+LB+9atfaXR0VF6vN+aY5uZmNTY22ikNAAAsUUNDQ1q/fv28r9u+ZkRSzOiEZVnzjlj8xV/8hYaHh3XTTTfJsix5PB7t2bNHjz32mJxO55zH1NfXq66uLrIfDAZ17bXXamhoSFlZWfGUDAAAEiwUCik3N1erV6++bD9bYSQnJ0dOpzNmFGRkZCRmtGTWypUrdfjwYT399NO6cOGCvF6v2tratHr1auXk5Mx5TGZmpjIzM2Pas7KyCCMAACSZD7rEwtatvRkZGSouLpbf749q9/v9qqiouOyxK1as0Pr16+V0OvXtb39bv/d7v3fZ+SMAAJAabE/T1NXVadeuXSopKVF5ebna2to0ODiompoaSRenWM6fPx9ZS+TNN99UT0+PysrK9D//8z86dOiQzpw5o7/5m79Z3G8CAACSku0wUl1drbGxMTU1NSkQCKiwsFAdHR3Ky8uTJAUCAQ0ODkb6h8NhPf744/rpT3+qFStW6NOf/rS6u7t13XXXLdqXAAAAycv2OiMmhEIhud1uBYNBrhkBACBJLPT3m4s2AACAUYQRAABgFGEEAAAYRRgBAABGxbUCKwAAuLLCM5Z6BsY1MjGptatdKvVly5m2PJ/PRhgBAGCJ6TwTUOOJfgWCk5E2r9ulhqoCbS+MfaZbsmOaBgCAJaTzTED7j56OCiKSNByc1P6jp9V5JmCosiuHMAIAwBIRnrHUeKJfcy0ANtvWeKJf4Zklv0SYLYQRAACWiJ6B8ZgRkUtZkgLBSfUMjCeuqAQgjAAAsESMTMwfROLplywIIwAALBFrV7sWtV+yIIwAALBElPqy5XW7NN8NvA5dvKum1JedyLKuOMIIAABLhDPNoYaqAkmKCSSz+w1VBctuvRHCCAAAS8j2Qq9a7yrSOnf0VMw6t0utdxUty3VGWPQMAIAlZnuhV9sK1rECKwAAMMeZ5lD5hjWmy0gIpmkAAIBRhBEAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBRcYWRlpYW+Xw+uVwuFRcXq6ur67L9jx07phtvvFEf+chH5PV6dc8992hsbCyuggEAwPJiO4y0t7ertrZWBw8eVF9fn7Zu3aodO3ZocHBwzv4nT57U7t27tXfvXv3kJz/Rd77zHf3rv/6r9u3b96GLBwAAyc92GDl06JD27t2rffv2KT8/X0888YRyc3PV2to6Z/9//ud/1nXXXaf77rtPPp9Pn/zkJ/X5z39ep06d+tDFAwCA5GcrjExPT6u3t1eVlZVR7ZWVleru7p7zmIqKCp07d04dHR2yLEsXLlzQiy++qNtvv33ez5mamlIoFIraAADA8mQrjIyOjiocDsvj8US1ezweDQ8Pz3lMRUWFjh07purqamVkZGjdunW66qqr9M1vfnPez2lubpbb7Y5subm5dsoEAABJJK4LWB0OR9S+ZVkxbbP6+/t133336aGHHlJvb686Ozs1MDCgmpqaed+/vr5ewWAwsg0NDcVTJgAASALpdjrn5OTI6XTGjIKMjIzEjJbMam5u1pYtW/TAAw9Ikj7+8Y9r1apV2rp1qx555BF5vd6YYzIzM5WZmWmnNAAAkKRsjYxkZGSouLhYfr8/qt3v96uiomLOY959912lpUV/jNPplHRxRAUAAKQ229M0dXV1euaZZ3T48GG98cYb+spXvqLBwcHItEt9fb12794d6V9VVaWXX35Zra2tOnv2rF577TXdd999Ki0t1dVXX7143wQAACQlW9M0klRdXa2xsTE1NTUpEAiosLBQHR0dysvLkyQFAoGoNUf27NmjiYkJ/dVf/ZX+/M//XFdddZVuvvlmPfroo4v3LQAAQNJyWEkwVxIKheR2uxUMBpWVlWW6HAAAsAAL/f3m2TQAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwKt10AUhN4RlLPQPjGpmY1NrVLpX6suVMc5guCwBgAGEECdd5JqDGE/0KBCcjbV63Sw1VBdpe6DVYGQDABKZpkFCdZwLaf/R0VBCRpOHgpPYfPa3OMwFDlQEATCGMIGHCM5YaT/TLmuO12bbGE/0Kz8zVAwCwXBFGkDA9A+MxIyKXsiQFgpPqGRhPXFEAAOMII0iYkYn5g0g8/QAAywNhBAmzdrVrUfsBAJaHuMJIS0uLfD6fXC6XiouL1dXVNW/fPXv2yOFwxGybN2+Ou2gkp1Jftrxul+a7gdehi3fVlPqyE1kWAMAw22Gkvb1dtbW1OnjwoPr6+rR161bt2LFDg4ODc/Z/8sknFQgEItvQ0JCys7P1R3/0Rx+6eCQXZ5pDDVUFkhQTSGb3G6oKWG8EAFKMw7IsW7culJWVqaioSK2trZG2/Px87dy5U83NzR94/Pe+9z199rOf1cDAgPLy8hb0maFQSG63W8FgUFlZWXbKxRLEOiMAkBoW+vtta9Gz6elp9fb26sCBA1HtlZWV6u7uXtB7PPvss7r11lsvG0SmpqY0NTUV2Q+FQnbKxBK3vdCrbQXrWIEVACDJZhgZHR1VOByWx+OJavd4PBoeHv7A4wOBgH7wgx/ohRdeuGy/5uZmNTY22ikNScaZ5lD5hjWmywAALAFxXcDqcET/BWtZVkzbXJ5//nldddVV2rlz52X71dfXKxgMRrahoaF4ygQAAEnA1shITk6OnE5nzCjIyMhIzGjJ+1mWpcOHD2vXrl3KyMi4bN/MzExlZmbaKQ0AACQpWyMjGRkZKi4ult/vj2r3+/2qqKi47LGvvPKK/uM//kN79+61XyUAAFi2bD+1t66uTrt27VJJSYnKy8vV1tamwcFB1dTUSLo4xXL+/HkdOXIk6rhnn31WZWVlKiwsXJzKAQDAsmA7jFRXV2tsbExNTU0KBAIqLCxUR0dH5O6YQCAQs+ZIMBjUSy+9pCeffHJxqgYAAMuG7XVGTGCdEQAAks9Cf795Ng0AADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKPSTReAxArPWOoZGNfIxKTWrnap1JctZ5rDdFkAgBRGGEkhnWcCajzRr0BwMtLmdbvUUFWg7YVeg5XhUgRGAKmGMJIiOs8EtP/oaVnvax8OTmr/0dNqvauIQLIEEBgBpCKuGUkB4RlLjSf6Y4KIpEhb44l+hWfm6oFEmQ2MlwYR6f8CY+eZgKHKAODKIoykgJ6B8ZgfuEtZkgLBSfUMjCeuKEQhMAJIZYSRFDAyMX8QiacfFh+BEUAqI4ykgLWrXYvaD4uPwAgglXEBawoo9WXL63ZpODg55zSAQ9I698W7NmAGgRFLAXdywRTCSApwpjnUUFWg/UdPyyFFBZLZ/2Yaqgr4T8cgAiNM404umMQ0TYrYXuhV611FWueO/st6ndvFbb1LwGxglP4vIM4iMOJK404umOawLGvJX54fCoXkdrsVDAaVlZVlupykxjDs0sZfp0i08IylTz76o3kvoJ4dlTv5/27m/wrYttDfb6ZpUowzzaHyDWtMl4F5bC/0alvBOgIjEsbOnVz834ErhTACLDEERiQSd3JhKeCaEQBIYdzJhaWAMAIAKWz2Tq75JgIdunjdEndy4UoijABACuNOLiwFhBEASHHc+g/TuIAVAMCdXDCKMAIAkMSdXDCHaRoAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYBRhBAAAGEUYAQAARsUVRlpaWuTz+eRyuVRcXKyurq7L9p+amtLBgweVl5enzMxMbdiwQYcPH46rYAAAsLzYXvSsvb1dtbW1amlp0ZYtW/T0009rx44d6u/v17XXXjvnMXfccYcuXLigZ599Vr/xG7+hkZERvffeex+6eAAAkPwclmVZdg4oKytTUVGRWltbI235+fnauXOnmpubY/p3dnbqc5/7nM6ePavs7Pie+hgKheR2uxUMBpWVlRXXewAAgMRa6O+3rWma6elp9fb2qrKyMqq9srJS3d3dcx7z/e9/XyUlJXrsscd0zTXX6Prrr9f999+vX/7yl/N+ztTUlEKhUNQGAACWJ1vTNKOjowqHw/J4PFHtHo9Hw8PDcx5z9uxZnTx5Ui6XS9/97nc1OjqqL3zhCxofH5/3upHm5mY1NjbaKQ0AACSpuC5gdTiin+JoWVZM26yZmRk5HA4dO3ZMpaWluu2223To0CE9//zz846O1NfXKxgMRrahoaF4ygQAAEnA1shITk6OnE5nzCjIyMhIzGjJLK/Xq2uuuUZutzvSlp+fL8uydO7cOW3cuDHmmMzMTGVmZtopDQAAJClbIyMZGRkqLi6W3++Pavf7/aqoqJjzmC1btuidd97Rz3/+80jbm2++qbS0NK1fvz6OkgEAwHJie5qmrq5OzzzzjA4fPqw33nhDX/nKVzQ4OKiamhpJF6dYdu/eHel/5513as2aNbrnnnvU39+vV199VQ888ID+9E//VCtXrly8bwIAAJKS7XVGqqurNTY2pqamJgUCARUWFqqjo0N5eXmSpEAgoMHBwUj/X/u1X5Pf79eXvvQllZSUaM2aNbrjjjv0yCOPLN63AAAAScv2OiMmsM4IAADJ54qsMwIAALDYCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACj0k0XYEp4xlLPwLhGJia1drVLpb5sOdMcpssCACDlpGQY6TwTUOOJfgWCk5E2r9ulhqoCbS/0GqwMAIDUk3LTNJ1nAtp/9HRUEJGk4eCk9h89rc4zAUOVAQCQmlIqjIRnLDWe6Jc1x2uzbY0n+hWemasHAAC4ElIqjPQMjMeMiFzKkhQITqpnYDxxRQEAkOJSKoyMTMwfROLpBwAAPryUCiNrV7sWtR8AAPjwUiqMlPqy5XW7NN8NvA5dvKum1JedyLIAAEhpKRVGnGkONVQVSFJMIJndb6gqYL0RAAASKKXCiCRtL/Sq9a4irXNHT8Wsc7vUelcR64wAAJBgKbno2fZCr7YVrGMFVgAAloCUDCPSxSmb8g1rTJcBAEDKS7lpGgAAsLQQRgAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYBRhBAAAGBVXGGlpaZHP55PL5VJxcbG6urrm7ftP//RPcjgcMdu///u/x100AABYPmyHkfb2dtXW1urgwYPq6+vT1q1btWPHDg0ODl72uJ/+9KcKBAKRbePGjXEXDQAAlg/bYeTQoUPau3ev9u3bp/z8fD3xxBPKzc1Va2vrZY9bu3at1q1bF9mcTmfcRQMAgOXDVhiZnp5Wb2+vKisro9orKyvV3d192WN/67d+S16vV7fccov+8R//8bJ9p6amFAqFojYAALA82Qojo6OjCofD8ng8Ue0ej0fDw8NzHuP1etXW1qaXXnpJL7/8sjZt2qRbbrlFr7766ryf09zcLLfbHdlyc3PtlAkAAJJIejwHORyOqH3LsmLaZm3atEmbNm2K7JeXl2toaEhf//rX9Tu/8ztzHlNfX6+6urrIfigUIpAAALBM2RoZycnJkdPpjBkFGRkZiRktuZybbrpJb7311ryvZ2ZmKisrK2oDAADLk60wkpGRoeLiYvn9/qh2v9+vioqKBb9PX1+fvF6vnY8GAADLlO1pmrq6Ou3atUslJSUqLy9XW1ubBgcHVVNTI+niFMv58+d15MgRSdITTzyh6667Tps3b9b09LSOHj2ql156SS+99NLifhMAAJCUbIeR6upqjY2NqampSYFAQIWFhero6FBeXp4kKRAIRK05Mj09rfvvv1/nz5/XypUrtXnzZv393/+9brvttsX7FgAAIGk5LMuyTBfxQUKhkNxut4LBINePAACQJBb6+82zaQAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGCU7WfTAACA5SE8Y6lnYFwjE5Nau9qlUl+2nGmOhNdBGAEAIAV1ngmo8US/AsHJSJvX7VJDVYG2F3oTWgvTNAAApJjOMwHtP3o6KohI0nBwUvuPnlbnmUBC6yGMAACQQsIzlhpP9Mua47XZtsYT/QrPzNXjyiCMAACQQnoGxmNGRC5lSQoEJ9UzMJ6wmggjAACkkJGJ+YNIPP0WA2EEAIAUsna1a1H7LQbCCAAAKaTUly2v26X5buB16OJdNaW+7ITVRBgBACCFONMcaqgqkKSYQDK731BVkND1RggjAACkmO2FXrXeVaR17uipmHVul1rvKkr4OiMsegYAQAraXujVtoJ1rMAKAADMcaY5VL5hjekymKYBAABmEUYAAIBRhBEAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABhFGAEAAEYRRgAAgFFxhZGWlhb5fD65XC4VFxerq6trQce99tprSk9P1yc+8Yl4PhYAACxDtsNIe3u7amtrdfDgQfX19Wnr1q3asWOHBgcHL3tcMBjU7t27dcstt8RdLAAAWH4clmVZdg4oKytTUVGRWltbI235+fnauXOnmpub5z3uc5/7nDZu3Cin06nvfe97ev311xf8maFQSG63W8FgUFlZWXbKBQAAhiz099vWyMj09LR6e3tVWVkZ1V5ZWanu7u55j3vuuef09ttvq6Ghwc7HAQCAFJBup/Po6KjC4bA8Hk9Uu8fj0fDw8JzHvPXWWzpw4IC6urqUnr6wj5uamtLU1FRkPxQK2SkTAAAkkbguYHU4HFH7lmXFtElSOBzWnXfeqcbGRl1//fULfv/m5ma53e7IlpubG0+ZAAAgCdgKIzk5OXI6nTGjICMjIzGjJZI0MTGhU6dO6d5771V6errS09PV1NSkf/u3f1N6erp+9KMfzfk59fX1CgaDkW1oaMhOmQAAIInYmqbJyMhQcXGx/H6//uAP/iDS7vf79fu///sx/bOysvTjH/84qq2lpUU/+tGP9OKLL8rn8835OZmZmcrMzLRTGgAASFK2wogk1dXVadeuXSopKVF5ebna2to0ODiompoaSRdHNc6fP68jR44oLS1NhYWFUcevXbtWLpcrph0AAKQm22GkurpaY2NjampqUiAQUGFhoTo6OpSXlydJCgQCH7jmCAAAwCzb64yYwDojAAAknyuyzggAAMBiI4wAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKNsPygMAmBGesdQzMK6RiUmtXe1SqS9bzjSH6bKAD40wAgBJoPNMQI0n+hUITkbavG6XGqoKtL3Qa7Ay4MNjmgYAlrjOMwHtP3o6KohI0nBwUvuPnlbnmYChyoDFQRgBgCUsPGOp8US/rDlem21rPNGv8MxcPYDkQBgBgCWsZ2A8ZkTkUpakQHBSPQPjiSsKWGSEEQBYwkYm5g8i8fQDliLCCAAsYWtXuxa1H7AUEUYAYAkr9WXL63Zpvht4Hbp4V02pLzuRZQGLijACAEuYM82hhqoCSYoJJLP7DVUFrDeCpEYYAYAlbnuhV613FWmdO3oqZp3bpda7ilhnBEmPRc8AIAlsL/RqW8E6VmDFskQYAYAk4UxzqHzDGtNlAIuOaRoAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABG8WwaYBkJz1g8SA1A0iGMAMtE55mAGk/0KxCcjLR53S41VBXwiHkASxrTNMAy0HkmoP1HT0cFEUkaDk5q/9HT6jwTMFQZAHwwwgiQ5MIzlhpP9Mua47XZtsYT/QrPzNUDAMwjjABJrmdgPGZE5FKWpEBwUj0D44krCgBsIIwASW5kYv4gEk8/AEg0wgiQ5Naudi1qPwBItLjCSEtLi3w+n1wul4qLi9XV1TVv35MnT2rLli1as2aNVq5cqRtuuEF/+Zd/GXfBAKKV+rLldbs03w28Dl28q6bUl53IsgBgwWyHkfb2dtXW1urgwYPq6+vT1q1btWPHDg0ODs7Zf9WqVbr33nv16quv6o033tCDDz6oBx98UG1tbR+6eACSM82hhqoCSYoJJLP7DVUFrDcCYMlyWJZl6xL7srIyFRUVqbW1NdKWn5+vnTt3qrm5eUHv8dnPflarVq3S3/7t3y6ofygUktvtVjAYVFZWlp1ygZTBOiMAlpqF/n7bWvRsenpavb29OnDgQFR7ZWWluru7F/QefX196u7u1iOPPGLnowF8gO2FXm0rWMcKrACSjq0wMjo6qnA4LI/HE9Xu8Xg0PDx82WPXr1+v//7v/9Z7772nhx9+WPv27Zu379TUlKampiL7oVDITplAynKmOVS+YY3pMgDAlrguYHU4ov/Ssiwrpu39urq6dOrUKf31X/+1nnjiCR0/fnzevs3NzXK73ZEtNzc3njIBAEASsDUykpOTI6fTGTMKMjIyEjNa8n4+n0+S9Ju/+Zu6cOGCHn74Yf3xH//xnH3r6+tVV1cX2Q+FQgQSAACWKVsjIxkZGSouLpbf749q9/v9qqioWPD7WJYVNQ3zfpmZmcrKyoraAADA8mT7qb11dXXatWuXSkpKVF5erra2Ng0ODqqmpkbSxVGN8+fP68iRI5Kkp556Stdee61uuOEGSRfXHfn617+uL33pS4v4NQAAQLKyHUaqq6s1NjampqYmBQIBFRYWqqOjQ3l5eZKkQCAQtebIzMyM6uvrNTAwoPT0dG3YsEFf/epX9fnPf37xvgUAAEhattcZMYF1RgAASD4L/f3m2TQAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwKh00wUAAOYWnrHUMzCukYlJrV3tUqkvW840h+mygEVHGAGAJajzTECNJ/oVCE5G2rxulxqqCrS90GuwMmDxMU0DAEtM55mA9h89HRVEJGk4OKn9R0+r80zAUGXAlUEYAYAlJDxjqfFEv6w5XpttazzRr/DMXD2A5EQYAYAlpGdgPGZE5FKWpEBwUj0D44krCrjCCCMAsISMTMwfROLpByQDwggALCFrV7sWtR+QDAgjALCElPqy5XW7NN8NvA5dvKum1JedyLKAK4owAgBLiDPNoYaqAkmKCSSz+w1VBaw3gmWFMAIAS8z2Qq9a7yrSOnf0VMw6t0utdxWxzgiWHRY9A4AlaHuhV9sK1rECK1ICYQQAlihnmkPlG9aYLgO44uKapmlpaZHP55PL5VJxcbG6urrm7fvyyy9r27Zt+uhHP6qsrCyVl5frhz/8YdwFAwCA5cV2GGlvb1dtba0OHjyovr4+bd26VTt27NDg4OCc/V999VVt27ZNHR0d6u3t1ac//WlVVVWpr6/vQxcPAACSn8OyLFtrCpeVlamoqEitra2Rtvz8fO3cuVPNzc0Leo/NmzerurpaDz300IL6h0Ihud1uBYNBZWVl2SkXAAAYstDfb1sjI9PT0+rt7VVlZWVUe2Vlpbq7uxf0HjMzM5qYmFB2NvfIAwAAmxewjo6OKhwOy+PxRLV7PB4NDw8v6D0ef/xx/eIXv9Add9wxb5+pqSlNTU1F9kOhkJ0yAQBAEonrAlaHI/rWMsuyYtrmcvz4cT388MNqb2/X2rVr5+3X3Nwst9sd2XJzc+MpEwAAJAFbYSQnJ0dOpzNmFGRkZCRmtOT92tvbtXfvXv3d3/2dbr311sv2ra+vVzAYjGxDQ0N2ygQAAEnEVhjJyMhQcXGx/H5/VLvf71dFRcW8xx0/flx79uzRCy+8oNtvv/0DPyczM1NZWVlRGwAAWJ5sL3pWV1enXbt2qaSkROXl5Wpra9Pg4KBqamokXRzVOH/+vI4cOSLpYhDZvXu3nnzySd10002RUZWVK1fK7XYv4lcBAADJyHYYqa6u1tjYmJqamhQIBFRYWKiOjg7l5eVJkgKBQNSaI08//bTee+89ffGLX9QXv/jFSPvdd9+t559/fkGfOXv3MReyAgCQPGZ/tz9oFRHb64yYcO7cOS5iBQAgSQ0NDWn9+vXzvp4UYWRmZkbvvPOOVq9evaC7dlJVKBRSbm6uhoaGuM5mieNcJQ/OVXLhfC0tlmVpYmJCV199tdLS5r9MNSkelJeWlnbZRIVoXPSbPDhXyYNzlVw4X0vHQq4PjWudEQAAgMVCGAEAAEYRRpaRzMxMNTQ0KDMz03Qp+ACcq+TBuUounK/klBQXsAIAgOWLkREAAGAUYQQAABhFGAEAAEYRRgAAgFGEkSTS0tIin88nl8ul4uJidXV1zdv35Zdf1rZt2/TRj35UWVlZKi8v1w9/+MMEVgs75+tSr732mtLT0/WJT3ziyhaICLvnampqSgcPHlReXp4yMzO1YcMGHT58OEHVwu75OnbsmG688UZ95CMfkdfr1T333KOxsbEEVYsFsZAUvv3tb1srVqywvvWtb1n9/f3Wl7/8ZWvVqlXWf/3Xf83Z/8tf/rL16KOPWj09Pdabb75p1dfXWytWrLBOnz6d4MpTk93zNetnP/uZ9bGPfcyqrKy0brzxxsQUm+LiOVef+cxnrLKyMsvv91sDAwPWv/zLv1ivvfZaAqtOXXbPV1dXl5WWlmY9+eST1tmzZ62uri5r8+bN1s6dOxNcOS6HMJIkSktLrZqamqi2G264wTpw4MCC36OgoMBqbGxc7NIwh3jPV3V1tfXggw9aDQ0NhJEEsXuufvCDH1hut9saGxtLRHl4H7vn62tf+5r1sY99LKrtG9/4hrV+/forViPsY5omCUxPT6u3t1eVlZVR7ZWVleru7l7Qe8zMzGhiYkLZ2dlXokRcIt7z9dxzz+ntt99WQ0PDlS4R/yuec/X9739fJSUleuyxx3TNNdfo+uuv1/33369f/vKXiSg5pcVzvioqKnTu3Dl1dHTIsixduHBBL774om6//fZElIwFSooH5aW60dFRhcNheTyeqHaPx6Ph4eEFvcfjjz+uX/ziF7rjjjuuRIm4RDzn66233tKBAwfU1dWl9HT+WSZKPOfq7NmzOnnypFwul7773e9qdHRUX/jCFzQ+Ps51I1dYPOeroqJCx44dU3V1tSYnJ/Xee+/pM5/5jL75zW8momQsECMjScThcETtW5YV0zaX48eP6+GHH1Z7e7vWrl17pcrD+yz0fIXDYd15551qbGzU9ddfn6jycAk7/7ZmZmbkcDh07NgxlZaW6rbbbtOhQ4f0/PPPMzqSIHbOV39/v+677z499NBD6u3tVWdnpwYGBlRTU5OIUrFA/AmWBHJycuR0OmOS/8jISMxfCO/X3t6uvXv36jvf+Y5uvfXWK1km/pfd8zUxMaFTp06pr69P9957r6SLP3iWZSk9PV3/8A//oJtvvjkhtaeaeP5teb1eXXPNNVGPRc/Pz5dlWTp37pw2btx4RWtOZfGcr+bmZm3ZskUPPPCAJOnjH/+4Vq1apa1bt+qRRx6R1+u94nXjgzEykgQyMjJUXFwsv98f1e73+1VRUTHvccePH9eePXv0wgsvMD+aQHbPV1ZWln784x/r9ddfj2w1NTXatGmTXn/9dZWVlSWq9JQTz7+tLVu26J133tHPf/7zSNubb76ptLQ0rV+//orWm+riOV/vvvuu0tKif+qcTqekiyMqWCLMXTsLO2ZvZ3v22Wet/v5+q7a21lq1apX1n//5n5ZlWdaBAwesXbt2Rfq/8MILVnp6uvXUU09ZgUAgsv3sZz8z9RVSit3z9X7cTZM4ds/VxMSEtX79eusP//APrZ/85CfWK6+8Ym3cuNHat2+fqa+QUuyer+eee85KT0+3WlparLfffts6efKkVVJSYpWWlpr6CpgDYSSJPPXUU1ZeXp6VkZFhFRUVWa+88krktbvvvtv61Kc+Fdn/1Kc+ZUmK2e6+++7EF56i7Jyv9yOMJJbdc/XGG29Yt956q7Vy5Upr/fr1Vl1dnfXuu+8muOrUZfd8feMb37AKCgqslStXWl6v1/qTP/kT69y5cwmuGpfjsCzGqQAAgDlcMwIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADDq/wPEt2pAnEfmGQAAAABJRU5ErkJggg==", "text/plain": [ "
" ] @@ -661,7 +662,7 @@ "outputs": [ { "data": { - "image/png": "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zjlHfr9fd999t/r6+pJUrUsYB/rTn/5kJk2aZF5++WXT0dFhHnroIXPuueeazz77LGb7AwcOmHPOOcc89NBDpqOjw7z88stm0qRJ5q233kpy5c4Sbz889NBD5sknnzS7du0yn3zyiVm9erWZNGmS2b17d5Ird5Z4+yHiiy++MBdffLGpqqoyV111VXKKdbBE+uGWW24xc+fONa2traazs9P861//Mu+//34Sq3aeePuhra3NZGRkmGeffdYcOHDAtLW1mSuuuMLceuutSa7c2RwZRsrLy01tbW3Utcsvv9ysWrUqZvtf/vKX5vLLL4+6du+995prrrnmrNXoBvH2QyxFRUWmvr5+oktzlUT7oaamxvzmN78xdXV1hJEJEG8//PWvfzU+n8/09fUlozzXiLcffve735mLL7446tpzzz1nZs6cedZqdCPHTdMcO3ZM7e3tqqqqirpeVVWlHTt2xHzOzp07R7W/8cYb9cEHH+jbb789a7U6WSL9cLJwOKzBwUFNnTr1bJToCon2wyuvvKL9+/errq7ubJfoCon0w7vvvquysjI99dRTuvDCC3XppZfqkUce0TfffJOMkh0pkX6orKzUoUOH1NzcLGOMPv/8c7311lu6+eabk1Gya6TFjfLi0dvbq1AopLy8vKjreXl56u7ujvmc7u7umO2PHz+u3t5e+f3+s1avUyXSDyf7/e9/r6+++kq333772SjRFRLph08//VSrVq1SW1ubsrIc91+EFYn0w4EDB7R9+3Z5vV6988476u3t1X333af+/n7WjSQokX6orKzU5s2bVVNTo6GhIR0/fly33HKLnn/++WSU7BqOGxmJ8Hg8UV8bY0ZdO137WNcRn3j7IeKNN97Q448/rqamJs2YMeNsleca4+2HUCikxYsXq76+XpdeemmyynONeP49hMNheTwebd68WeXl5brpppv09NNP69VXX2V05AzF0w8dHR168MEH9dhjj6m9vV0tLS3q7OxUbW1tMkp1Dcd97Jk+fboyMzNHpdyenp5RaTgiPz8/ZvusrCxNmzbtrNXqZIn0Q0RTU5OWLVumN998UzfccMPZLNPx4u2HwcFBffDBB9qzZ48eeOABScNvisYYZWVlaevWrbr++uuTUruTJPLvwe/368ILL4y6/fqcOXNkjNGhQ4d0ySWXnNWanSiRfmhoaNC8efP06KOPSpKuvPJKnXvuuZo/f76eeOIJRs4niONGRrKzs1VaWqrW1tao662traqsrIz5nIqKilHtt27dqrKyMk2aNOms1epkifSDNDwictddd+n1119nTnYCxNsPubm5+vDDD7V3796RR21trS677DLt3btXc+fOTVbpjpLIv4d58+bpyJEj+vLLL0euffLJJ8rIyNDMmTPPar1OlUg/fP3118rIiH6rzMzMlPS/EXRMAFsrZ8+myNatDRs2mI6ODrNixQpz7rnnmv/+97/GGGNWrVplli5dOtI+srX34YcfNh0dHWbDhg1s7Z0A8fbD66+/brKyssy6detMIBAYeXzxxRe2/gRHiLcfTsZumokRbz8MDg6amTNnmttuu8189NFHZtu2beaSSy4x99xzj60/wRHi7YdXXnnFZGVlmfXr15v9+/eb7du3m7KyMlNeXm7rT3AkR4YRY4xZt26dmTVrlsnOzjYlJSVm27ZtI9+78847zXXXXRfV/u9//7v5/ve/b7Kzs81FF11kGhsbk1yxM8XTD9ddd52RNOpx5513Jr9wh4n338OJCCMTJ95++Pjjj80NN9xgJk+ebGbOnGlWrlxpvv766yRX7Tzx9sNzzz1nioqKzOTJk43f7zc/+clPzKFDh5JctbN5jGGcCQAA2OO4NSMAACC9EEYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFWEEQAAYBVhBAAAWEUYAQAAVhFGAACAVYQRAABgFWEEAABY9f8Alw4iNhhoKkMAAAAASUVORK5CYII=", + "image/png": "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", "text/plain": [ "
" ] From b6d16b8ac49fac82a3f919fd21d558804b4983e1 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 29 Jun 2023 11:10:08 -0700 Subject: [PATCH 279/756] Update docstring --- pyiron_contrib/workflow/function.py | 43 +++++++++++++++++++---------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index b55a2f071..3cb6cdc8a 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -93,32 +93,38 @@ class Function(Node): >>> plus_minus_1 = Function(mwe, "p1", "m1") >>> >>> print(plus_minus_1.outputs.p1) - None + There is no output because we haven't given our function any input, it has - no defaults, and we never ran it! + no defaults, and we never ran it! So it has the channel default value of + `NotData` -- a special non-data class (since `None` is sometimes a meaningful + value in python). We'll run into a hiccup if we try to set only one of the inputs and update >>> plus_minus_1.inputs.x = 1 >>> plus_minus_1.run() TypeError - This is because the second input (y) still has no input value so we can't do the - sum. + This is because the second input (y) still has no input value, so we can't do + the sum. Let's set the node to run automatically when its inputs are updated, then update x and y. >>> plus_minus_1.run_on_updates = True >>> plus_minus_1.inputs.x = 2 - TypeError + >>> print(plus_minus_1.outputs.p1.value) + + + The gentler `update()` call sees that the `y` input is still `NotData`, so it + does not proceed to the `run()` and the output is not yet updated. - What happened here? Well, since we didn't offer any type hints for the function, - when updating the `x` value triggered the node update, it didn't see any - trouble with the other inputs and tried to run! First, let's provide a y-value - as well, then go back and see how to avoid this. + Let's provide a y-value as well: >>> plus_minus_1.inputs.y = 3 >>> plus_minus_1.outputs.to_value_dict() {'p1': 3, 'm1': 2} + Now that both inputs have been provided, the node update triggers a run and we + get the expected output. + We can also, optionally, provide initial values for some or all of the input >>> plus_minus_1 = Function( ... mwe, "p1", "m1", @@ -146,16 +152,24 @@ class Function(Node): We can provide initial values for our node function at instantiation using our kwargs. The node update is deferred until _all_ of these initial values are processed. - Thus, the second solution is to ensure that _all_ the arguments of our function - are receiving good enough initial values to facilitate an execution of the node - function at the end of instantiation: - >>> plus_minus_1 = Function(mwe, "p1", "m1", x=1, y=2) + Thus, if _all_ the arguments of our function are receiving good enough initial + values to facilitate an execution of the node function at the end of + instantiation, the output gets updated right away: + >>> plus_minus_1 = Function( + ... mwe, "p1", "m1", + ... x=1, y=2, + ... run_on_updates=True, update_on_instantiation=True + ... ) >>> >>> print(plus_minus_1.outputs.to_value_dict()) {'p1': 2, 'm1': 1} Second, we could add type hints/defaults to our function so that it knows better than to try to evaluate itself with bad data. + You can always force the node to run with its current input using `run()`, but + `update()` will always check if the node is `ready` -- i.e. if none of its + inputs are `NotData` and all of them obey any type hints that have been + provided. Let's make a new node following the second path. In this example, note the mixture of old-school (`typing.Union`) and new (`|`) @@ -176,7 +190,8 @@ class Function(Node): ... run_on_updates=True, update_on_instantiation=True ... ) >>> plus_minus_1.outputs.to_value_dict() - {'p1': None, 'm1': None} + {'p1': , 'm1': } Here we got an update automatically at the end of instantiation, but because both values are type hinted this didn't result in any errors! From 3608cd6c334b6f9b3bbb873075039d4bf0a4bea4 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 29 Jun 2023 11:10:16 -0700 Subject: [PATCH 280/756] Fix typo --- pyiron_contrib/workflow/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 3cb6cdc8a..b9969e571 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -314,7 +314,7 @@ class Function(Node): >>> ... >>> return x - For this function, you don't have a freedom to choose `self`, because + For this function, you don't have the freedom to choose `self`, because pyiron automatically sets the node object there (which is also the reason why you do not see `self` in the list of inputs). """ From c85afdd8b92ab10308c717b7256ed70cedb4bbb3 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 29 Jun 2023 11:54:14 -0700 Subject: [PATCH 281/756] Make fast the default behaviour for function nodes --- pyiron_contrib/workflow/function.py | 4 ++-- tests/unit/workflow/test_function.py | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index b9969e571..d80dfe622 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -324,8 +324,8 @@ def __init__( node_function: callable, *output_labels: str, label: Optional[str] = None, - run_on_updates: bool = False, - update_on_instantiation: bool = False, + run_on_updates: bool = True, + update_on_instantiation: bool = True, channels_requiring_update_after_run: Optional[list[str]] = None, parent: Optional[Composite] = None, **kwargs, diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index be76d878e..f32347fde 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -74,6 +74,14 @@ def test_instantiation_update(self): ) self.assertEqual(2, update.outputs.y.value) + default = Function(plus_one, "y") + self.assertEqual( + 2, + default.outputs.y.value, + msg="Default behaviour should be to run on updates and update on " + "instantiation", + ) + with self.assertRaises(TypeError): run_without_value = Function(no_default, "z") run_without_value.run() @@ -108,7 +116,7 @@ def test_input_kwargs(self): self.assertEqual(4, node2.outputs.y.value, msg="Initialize from connection") def test_automatic_updates(self): - node = Function(throw_error, "no_return", run_on_updates=True) + node = Function(throw_error, "no_return", update_on_instantiation=False) with self.subTest("Shouldn't run for invalid input on update"): node.inputs.x.update("not an int") @@ -146,7 +154,7 @@ def times_two(y): ) def test_statuses(self): - n = Function(plus_one, "p1") + n = Function(plus_one, "p1", run_on_updates=False) self.assertTrue(n.ready) self.assertFalse(n.running) self.assertFalse(n.failed) From d23a83eb2f8aec8ae43645b77c5847256f8cce85 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 29 Jun 2023 12:21:24 -0700 Subject: [PATCH 282/756] Update the docstring --- pyiron_contrib/workflow/function.py | 136 ++++++++++++---------------- 1 file changed, 59 insertions(+), 77 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index d80dfe622..a227c4be4 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -48,14 +48,18 @@ class Function(Node): input) and idempotent (not modifying input data in-place, but creating copies where necessary and returning new objects as output). + By default, function nodes will attempt to run whenever one or more inputs is + updated, and will attempt to update on initialization (after setting _all_ initial + input values). + Args: node_function (callable): The function determining the behaviour of the node. *output_labels (str): A name for each return value of the node function. label (str): The node's label. (Defaults to the node function's name.) run_on_updates (bool): Whether to run when you are updated and all your - input is ready. (Default is False). + input is ready. (Default is True). update_on_instantiation (bool): Whether to force an update at the end of - instantiation. (Default is False.) + instantiation. (Default is True.) channels_requiring_update_after_run (list[str]): All the input channels named here will be set to `wait_for_update()` at the end of each node run, such that they are not `ready` again until they have had their `.update` method @@ -96,81 +100,63 @@ class Function(Node): There is no output because we haven't given our function any input, it has - no defaults, and we never ran it! So it has the channel default value of + no defaults, and we never ran it! It tried to `update()` on instantiation, but + the update never got to `run()` because the node could see that some its input + had never been specified. So outputs have the channel default value of `NotData` -- a special non-data class (since `None` is sometimes a meaningful value in python). - We'll run into a hiccup if we try to set only one of the inputs and update - >>> plus_minus_1.inputs.x = 1 + We'll run into a hiccup if we try to set only one of the inputs and force the + run: + >>> plus_minus_1.inputs.x = 2 >>> plus_minus_1.run() TypeError - This is because the second input (y) still has no input value, so we can't do + This is because the second input (`y`) still has no input value, so we can't do the sum. - Let's set the node to run automatically when its inputs are updated, then update - x and y. - >>> plus_minus_1.run_on_updates = True - >>> plus_minus_1.inputs.x = 2 - >>> print(plus_minus_1.outputs.p1.value) - - The gentler `update()` call sees that the `y` input is still `NotData`, so it - does not proceed to the `run()` and the output is not yet updated. - - Let's provide a y-value as well: - >>> plus_minus_1.inputs.y = 3 + Once we update `y`, all the input is ready and the automatic `update()` call + will be allowed to proceed to a `run()` call, which succeeds and updates the + output: + >>> plus_minus_1.inputs.x = 3 >>> plus_minus_1.outputs.to_value_dict() {'p1': 3, 'm1': 2} - Now that both inputs have been provided, the node update triggers a run and we - get the expected output. - We can also, optionally, provide initial values for some or all of the input - >>> plus_minus_1 = Function( - ... mwe, "p1", "m1", - ... x=1, - ... run_on_updates=True - ) + >>> plus_minus_1 = Function(mwe, "p1", "m1", x=1) >>> plus_minus_1.inputs.y = 2 # Automatically triggers an update call now >>> plus_minus_1.outputs.to_value_dict() {'p1': 2, 'm1': 1} - Finally, we might want the node to be ready-to-go right after instantiation. - To do this, we need to provide initial values for everything and set two flags: + Finally, we might stop these updates from happening automatically, even when + all the input data is present and available: >>> plus_minus_1 = Function( ... mwe, "p1", "m1", ... x=0, y=0, - ... run_on_updates=True, update_on_instantiation=True + ... run_on_updates=False, update_on_instantiation=False ... ) + >>> plus_minus_1.outputs.p1.value + + + 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} - Another way to stop the node from running with bad input is to provide type - hints (and, optionally, default values) when defining the function the node - wraps. All of these get determined by inspection. - - We can provide initial values for our node function at instantiation using our - kwargs. - The node update is deferred until _all_ of these initial values are processed. - Thus, if _all_ the arguments of our function are receiving good enough initial - values to facilitate an execution of the node function at the end of - instantiation, the output gets updated right away: - >>> plus_minus_1 = Function( - ... mwe, "p1", "m1", - ... x=1, y=2, - ... run_on_updates=True, update_on_instantiation=True - ... ) - >>> - >>> print(plus_minus_1.outputs.to_value_dict()) - {'p1': 2, 'm1': 1} + So function nodes have the most basic level of protection that they won't run + if they haven't seen any input data. + However, we could still get them to raise an error by providing the _wrong_ + data: + >>> plus_minus_1 = Function(mwe, "p1", "m1", x=1, y="can't add to an int") + TypeError - Second, we could add type hints/defaults to our function so that it knows better - than to try to evaluate itself with bad data. - You can always force the node to run with its current input using `run()`, but - `update()` will always check if the node is `ready` -- i.e. if none of its - inputs are `NotData` and all of them obey any type hints that have been - provided. - Let's make a new node following the second path. + Here everything tries to run automatically, but we get an error from adding the + integer and string! + We can make our node even more sensible by adding type + hints (and, optionally, default values) when defining the function that the node + wraps. + The node will automatically figure out defaults and type hints for the IO + channels from inspection of the wrapped function. In this example, note the mixture of old-school (`typing.Union`) and new (`|`) type hints as well as nested hinting with a union-type inside the tuple for the @@ -185,26 +171,23 @@ class Function(Node): ... ) -> tuple[int, int | float]: ... return x+1, y-1 >>> - >>> plus_minus_1 = Function( - ... hinted_example, "p1", "m1", - ... run_on_updates=True, update_on_instantiation=True - ... ) + >>> plus_minus_1 = Function(hinted_example, "p1", "m1", x="not an int") >>> plus_minus_1.outputs.to_value_dict() {'p1': , 'm1': } - Here we got an update automatically at the end of instantiation, but because - both values are type hinted this didn't result in any errors! - Still, we need to provide the rest of the input data in order to get results: - - >>> plus_minus_1.inputs.x = 1 - >>> plus_minus_1.outputs.to_value_dict() - {'p1': 2, 'm1': 0} + Here, even though all the input has data, the node sees that some of it is the + wrong type and so the automatic updates don't proceed all the way to a run. + Note that the type hinting doesn't actually prevent us from assigning bad values + directly to the channel (although it will, by default, prevent connections + _between_ type-hinted channels with incompatible hints), but it _does_ stop the + node from running and throwing an error because it sees that the channel (and + thus node) is not ready + >>> plus_minus_1.inputs.x.value + 'not an int' - Note: the `Fast(Node)` child class will enforce all function arguments to - be type-hinted and have defaults, and will automatically set the updating and - instantiation flags to `True` for nodes that execute quickly and are meant to - _always_ have good output data. + >>> plus_minus_1.ready, plus_minus_1.inputs.x.ready, plus_minus_1.inputs.y.ready + (False, False, True) In these examples, we've instantiated nodes directly from the base `Function` class, and populated their input directly with data. @@ -219,10 +202,7 @@ class Function(Node): and returns a node class: >>> from pyiron_contrib.workflow.function import function_node >>> - >>> @function_node( - ... "p1", "m1", - ... run_on_updates=True, update_on_instantiation=True - ... ) + >>> @function_node("p1", "m1") ... def my_mwe_node( ... x: int | float, y: int | float = 1 ... ) -> tuple[int | float, int | float]: @@ -235,8 +215,7 @@ class Function(Node): Where we've passed the output labels and class arguments to the decorator, and inital values to the newly-created node class (`my_mwe_node`) at instantiation. - Because we told it to run on updates and to update on instantation _and_ we - provided a good initial value for `x`, we get our result right away. + Because we provided a good initial value for `x`, we get our result right away. Using the decorator is the recommended way to create new node classes, but this magic is just equivalent to these two more verbose ways of defining a new class. @@ -254,7 +233,7 @@ class Function(Node): ... super().__init__( ... self.alphabet_mod_three, ... "letter", - ... labe=label, + ... label=label, ... run_on_updates=run_on_updates, ... update_on_instantiation=update_on_instantiation, ... **kwargs @@ -264,6 +243,11 @@ class Function(Node): ... def alphabet_mod_three(i: int) -> Literal["a", "b", "c"]: ... return ["a", "b", "c"][i % 3] + Note that we've overridden the default value for `update_on_instantiation` + above. + We can also provide different defaults for these flags as kwargs in the + decorator. + The second effectively does the same thing, but leverages python's `functools.partialmethod` to do so much more succinctly. In this example, note that the function is declared _before_ `__init__` is set, @@ -280,8 +264,6 @@ class Function(Node): ... Function.__init__, ... adder, ... "sum", - ... run_on_updates=True, - ... update_on_instantiation=True ... ) Finally, let's put it all together by using both of these nodes at once. From 76c5ab554631b46e36c8c8b5cfb18b2f8e355149 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 29 Jun 2023 12:23:54 -0700 Subject: [PATCH 283/756] Remove unnecessary specification of the defaults --- tests/unit/workflow/test_function.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index f32347fde..aca78389f 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -102,16 +102,10 @@ def test_instantiation_update(self): ) def test_input_kwargs(self): - node = Function( - plus_one, - "y", - x=2, - run_on_updates=True, - update_on_instantiation=True - ) + node = Function(plus_one, "y", x=2) self.assertEqual(3, node.outputs.y.value, msg="Initialize from value") - node2 = Function(plus_one, "y", x=node.outputs.y, run_on_updates=True) + node2 = Function(plus_one, "y", x=node.outputs.y) node.update() self.assertEqual(4, node2.outputs.y.value, msg="Initialize from connection") From cdd8cd98916a2cec5d009e7bc591a17ea16b6b21 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 29 Jun 2023 12:33:28 -0700 Subject: [PATCH 284/756] Reparent SingleValue directly onto Function Now that Function is "fast" by default and we don't explode when no defaults are specified --- pyiron_contrib/workflow/function.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index a227c4be4..dc55cd47b 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -550,9 +550,9 @@ def ensure_params_have_defaults(cls, fnc: callable) -> None: ) -class SingleValue(Fast, HasChannel): +class SingleValue(Function, HasChannel): """ - A fast node that _must_ return only a single value. + A node that _must_ return only a single value. Attribute and item access is modified to finally attempt access on the output value. """ @@ -672,7 +672,6 @@ def single_value_node(*output_labels: str, **node_class_kwargs): def as_single_value_node(node_function: callable): SingleValue.ensure_there_is_only_one_return_value(output_labels) - SingleValue.ensure_params_have_defaults(node_function) return type( node_function.__name__.title().replace("_", ""), # fnc_name to CamelCase (SingleValue,), # Define parentage From d36060c8b44106b4b3509ba4e1d3ac96c4b63ce6 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 29 Jun 2023 12:45:08 -0700 Subject: [PATCH 285/756] Replace the Fast node with a Slow node now that fast is default --- pyiron_contrib/workflow/composite.py | 4 +-- pyiron_contrib/workflow/function.py | 44 +++++++++--------------- pyiron_contrib/workflow/workflow.py | 2 +- tests/unit/workflow/test_function.py | 27 +++++++++++---- tests/unit/workflow/test_node_package.py | 4 +-- tests/unit/workflow/test_workflow.py | 2 +- 6 files changed, 43 insertions(+), 40 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index d5b13a897..21e8a8fb5 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -11,7 +11,7 @@ from warnings import warn from pyiron_contrib.workflow.node import Node -from pyiron_contrib.workflow.function import Function, function_node, fast_node, single_value_node +from pyiron_contrib.workflow.function import Function, function_node, slow_node, single_value_node from pyiron_contrib.workflow.node_library import atomistics, standard from pyiron_contrib.workflow.node_library.package import NodePackage from pyiron_contrib.workflow.util import DotDict @@ -21,7 +21,7 @@ class _NodeDecoratorAccess: """An intermediate container to store node-creating decorators as class methods.""" function_node = function_node - fast_node = fast_node + slow_node = slow_node single_value_node = single_value_node diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index dc55cd47b..d9de5d8c6 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -508,11 +508,12 @@ def to_dict(self): } -class Fast(Function): +class Slow(Function): """ - Like a regular node, but _all_ input channels _must_ have default values provided, - and the initialization signature forces `run_on_updates` and - `update_on_instantiation` to be `True`. + Like a regular node, but `run_on_updates` and `update_on_instantiation` default to + `False`. + This is intended for wrapping function which are potentially expensive to call, + where you don't want the output recomputed unless `run()` is _explicitly_ called. """ def __init__( @@ -520,12 +521,11 @@ def __init__( node_function: callable, *output_labels: str, label: Optional[str] = None, - run_on_updates=True, - update_on_instantiation=True, + run_on_updates=False, + update_on_instantiation=False, parent: Optional[Workflow] = None, **kwargs, ): - self.ensure_params_have_defaults(node_function) super().__init__( node_function, *output_labels, @@ -536,19 +536,6 @@ def __init__( **kwargs, ) - @classmethod - def ensure_params_have_defaults(cls, fnc: callable) -> None: - """Raise a `ValueError` if any parameters of the callable lack defaults.""" - if any( - param.default == inspect._empty - for param in inspect.signature(fnc).parameters.values() - ): - raise ValueError( - f"{cls.__name__} requires all function parameters to have defaults, " - f"but {fnc.__name__} has the parameters " - f"{inspect.signature(fnc).parameters.values()}" - ) - class SingleValue(Function, HasChannel): """ @@ -638,21 +625,22 @@ def as_node(node_function: callable): return as_node -def fast_node(*output_labels: str, **node_class_kwargs): +def slow_node(*output_labels: str, **node_class_kwargs): """ - A decorator for dynamically creating fast node classes from functions. + A decorator for dynamically creating slow node classes from functions. - Unlike normal nodes, fast nodes _must_ have default values set for all their inputs. + Unlike normal nodes, slow nodes do update themselves on initialization and do not + run themselves when they get updated -- i.e. they will not run when their input + changes, `run()` must be explicitly called. """ - def as_fast_node(node_function: callable): - Fast.ensure_params_have_defaults(node_function) + def as_slow_node(node_function: callable): return type( node_function.__name__.title().replace("_", ""), # fnc_name to CamelCase - (Fast,), # Define parentage + (Slow,), # Define parentage { "__init__": partialmethod( - Fast.__init__, + Slow.__init__, node_function, *output_labels, **node_class_kwargs, @@ -660,7 +648,7 @@ def as_fast_node(node_function: callable): }, ) - return as_fast_node + return as_slow_node def single_value_node(*output_labels: str, **node_class_kwargs): diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 92c9249f1..9ce81342e 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -63,7 +63,7 @@ class Workflow(Composite): workflow (cf. the `Node` docs for more detail on the node types). Let's use these to explore a workflow's input and output, which are dynamically generated from the unconnected IO of its nodes: - >>> @Workflow.wrap_as.fast_node("y") + >>> @Workflow.wrap_as.function_node("y") >>> def plus_one(x: int = 0): ... return x + 1 >>> diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index aca78389f..5d2d15869 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -6,7 +6,7 @@ from pyiron_contrib.workflow.channels import NotData from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.function import ( - Fast, Function, SingleValue, function_node, single_value_node + Slow, Function, SingleValue, function_node, single_value_node ) @@ -235,12 +235,27 @@ def with_messed_self(x: float, self) -> float: @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestFast(unittest.TestCase): +class TestSlow(unittest.TestCase): def test_instantiation(self): - has_defaults_is_ok = Fast(plus_one, "y") - - with self.assertRaises(ValueError): - missing_defaults_should_fail = Fast(no_default, "z") + slow = Slow(plus_one, "y") + self.assertIs( + slow.outputs.y.value, + NotData, + msg="Slow nodes should not run at instantiation", + ) + slow.inputs.x = 10 + self.assertIs( + slow.outputs.y.value, + NotData, + msg="Slow nodes should not run on updates", + ) + slow.run() + self.assertEqual( + slow.outputs.y.value, + 11, + msg=f"Slow nodes should still run when asked! Expected 11 but got " + f"{slow.outputs.y.value}" + ) @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") diff --git a/tests/unit/workflow/test_node_package.py b/tests/unit/workflow/test_node_package.py index 5ee86fb75..c8492437c 100644 --- a/tests/unit/workflow/test_node_package.py +++ b/tests/unit/workflow/test_node_package.py @@ -5,7 +5,7 @@ from pyiron_contrib.workflow.workflow import Workflow -@Workflow.wrap_as.fast_node("x") +@Workflow.wrap_as.function_node("x") def dummy(x: int = 0): return x @@ -53,7 +53,7 @@ def add(x: int = 0): old_dummy_instance = self.package.Dummy(label="old_dummy_instance") - @Workflow.wrap_as.fast_node("y") + @Workflow.wrap_as.function_node("y") def dummy(x: int = 0): return x + 1 diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index b1fcb18b1..b93708086 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -109,7 +109,7 @@ def test_workflow_io(self): self.assertEqual(len(wf.outputs), 1) def test_node_decorator_access(self): - @Workflow.wrap_as.fast_node("y") + @Workflow.wrap_as.function_node("y") def plus_one(x: int = 0) -> int: return x + 1 From 4158fd3d514dccde0a738852950933a087770954 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 29 Jun 2023 12:46:05 -0700 Subject: [PATCH 286/756] Make atomistic calculation nodes slow --- pyiron_contrib/workflow/node_library/atomistics.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/node_library/atomistics.py b/pyiron_contrib/workflow/node_library/atomistics.py index e4fdd5a6c..1da3bdac6 100644 --- a/pyiron_contrib/workflow/node_library/atomistics.py +++ b/pyiron_contrib/workflow/node_library/atomistics.py @@ -7,7 +7,7 @@ from pyiron_atomistics.atomistics.structure.atoms import Atoms from pyiron_atomistics.lammps.lammps import Lammps as LammpsJob -from pyiron_contrib.workflow.function import function_node, single_value_node +from pyiron_contrib.workflow.function import single_value_node, slow_node @single_value_node("structure") @@ -81,7 +81,7 @@ def _run_and_remove_job(job, modifier: Optional[callable] = None, **modifier_kwa ) -@function_node( +@slow_node( "cells", "displacements", "energy_pot", @@ -103,7 +103,7 @@ def calc_static( return _run_and_remove_job(job=job) -@function_node( +@slow_node( "cells", "displacements", "energy_pot", From 03a011fb6701aced42e97ec6c96fe6aec1d4da9f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 29 Jun 2023 12:59:10 -0700 Subject: [PATCH 287/756] Expose the other functions on the node adder --- pyiron_contrib/workflow/composite.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 21e8a8fb5..612359efa 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -11,7 +11,9 @@ from warnings import warn from pyiron_contrib.workflow.node import Node -from pyiron_contrib.workflow.function import Function, function_node, slow_node, single_value_node +from pyiron_contrib.workflow.function import ( + Function, SingleValue, Slow, function_node, slow_node, single_value_node +) from pyiron_contrib.workflow.node_library import atomistics, standard from pyiron_contrib.workflow.node_library.package import NodePackage from pyiron_contrib.workflow.util import DotDict @@ -226,6 +228,8 @@ def __init__(self, parent: Composite): self.register_nodes("standard", *standard.nodes) Function = Function + Slow = Slow + SingleValue = SingleValue def __getattribute__(self, key): value = super().__getattribute__(key) From 95eb3f5c213ca62248ef14a4f36e8bb4bf7f4989 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 29 Jun 2023 13:04:27 -0700 Subject: [PATCH 288/756] Update the example notebook --- notebooks/workflow_example.ipynb | 200 ++++++++++++++++++++++--------- 1 file changed, 142 insertions(+), 58 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index aca04c84d..50843a9e8 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -9,7 +9,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "6a819a64e97a4eb5b91e66cae4689723", + "model_id": "d57449473dbc42f2997863543b5171c6", "version_major": 2, "version_minor": 0 }, @@ -94,10 +94,8 @@ "id": "22ee2a49-47d1-4cec-bb25-8441ea01faf7", "metadata": {}, "source": [ - "The output is still empty (`NotData`) because we haven't `run` the node.\n", - "If we try that now though, we'll just get a type error because the input is not set! A softer `update()` will avoid the error because it will see that the node is not `ready` and choose not to `run()`.\n", - "\n", - "Let's set the input and run the node:" + "The output is still empty (`NotData`) because we haven't `run()` the node.\n", + "If we try that now though, we'll just get a type error because the input is not set! " ] }, { @@ -110,15 +108,41 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'p1': , 'm1': }\n", + "{'p1': , 'm1': }\n" + ] + } + ], + "source": [ + "print(pm_node.outputs.to_value_dict())\n" + ] + }, + { + "cell_type": "markdown", + "id": "48b0db5a-548e-4195-8361-76763ddf0474", + "metadata": {}, + "source": [ + "By default, a softer `update()` call is made at instantiation and whenever the node input is updated.\n", + "This call checks to make sure the input is `ready` before moving on to `run()`. \n", + "\n", + "If we update the input, we'll give the node enough data to work with and it will automatically update the output" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b1500a40-f4f2-4c06-ad78-aaebcf3e9a50", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ "{'p1': 6, 'm1': 4}\n" ] } ], "source": [ - "print(pm_node.outputs.to_value_dict())\n", "pm_node.inputs.x = 5\n", - "pm_node.run()\n", "print(pm_node.outputs.to_value_dict())" ] }, @@ -127,22 +151,22 @@ "id": "df4520d7-856e-4bc8-817f-5b2e22c1ddce", "metadata": {}, "source": [ - "Nodes also have the option to `run_on_updates` -- i.e. to attempt a `run` command whenever _any_ of their input data gets updated -- and to `update_on_instantiation`." + "We can be stricter and force the node to wait for an explicit `run()` call by modifying the `run_on_updates` and `update_on_instantiation` flags." ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "ab1ac28a-6e69-491f-882f-da4a43162dd7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "2" + "pyiron_contrib.workflow.channels.NotData" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -151,7 +175,7 @@ "def adder(x: int, y: int = 1) -> int:\n", " return x + y\n", "\n", - "adder_node = Function(adder, \"sum\", run_on_updates=True, update_on_instantiation=True)\n", + "adder_node = Function(adder, \"sum\", run_on_updates=False)\n", "adder_node.inputs.x = 1\n", "adder_node.outputs.sum.value # We use `value` to see the data the channel holds" ] @@ -161,33 +185,94 @@ "id": "0929f222-6073-4201-b5a1-723c31c8998a", "metadata": {}, "source": [ - "We see that now the output got populated automatically when we updated `x`. \n", - "We can safely update it back to something silly without causing an error because of our type hints." + "We see that now the output did not get populated automatically when we updated `x`. \n", + "We can still get the output by asking for it though:" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, + "id": "dc41a447-15fd-4df2-b60a-0935d81d469e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "adder_node.run()\n", + "adder_node.outputs.sum.value" + ] + }, + { + "cell_type": "markdown", + "id": "58ed9b25-6dde-488d-9582-d49d405793c6", + "metadata": {}, + "source": [ + "This node also exploits type hinting!\n", + "After turning the automatic updates back on, we can see that we can safely pass incorrect data without running into an error:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, "id": "ac0fe993-6c82-48c8-a780-cbd0c97fc386", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "int" + "(int, str)" ] }, - "execution_count": 6, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ + "adder_node.run_on_updates = True\n", "adder_node.inputs.x = \"not an integer\"\n", - "adder_node.inputs.x.type_hint\n", + "adder_node.inputs.x.type_hint, type(adder_node.inputs.x.value)\n", "# No error because the update doesn't trigger a run since the type hint is not satisfied" ] }, + { + "cell_type": "markdown", + "id": "2737de39-6e75-44e1-b751-6315afe5c676", + "metadata": {}, + "source": [ + "But `run()` never got called, so the output is unchanged" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "bcbd17f1-a3e4-44f0-bde1-cbddc51c5d73", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "adder_node.outputs.sum.value" + ] + }, { "cell_type": "markdown", "id": "263f5b24-113f-45d9-82cc-0475c59da587", @@ -198,7 +283,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 10, "id": "15742a49-4c23-4d4a-84d9-9bf19677544c", "metadata": {}, "outputs": [ @@ -208,7 +293,7 @@ "3" ] }, - "execution_count": 7, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -234,7 +319,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 11, "id": "61b43a9b-8dad-48b7-9194-2045e465793b", "metadata": {}, "outputs": [], @@ -244,7 +329,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 12, "id": "647360a9-c971-4272-995c-aa01e5f5bb83", "metadata": {}, "outputs": [ @@ -259,7 +344,7 @@ } ], "source": [ - "@function_node(\"diff\", run_on_updates=True, update_on_instantiation=True)\n", + "@function_node(\"diff\")\n", "def subtract_node(x: int | float = 2, y: int | float = 1) -> int | float:\n", " return x - y\n", "\n", @@ -281,7 +366,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 13, "id": "8fb0671b-045a-4d71-9d35-f0beadc9cf3a", "metadata": {}, "outputs": [ @@ -291,7 +376,7 @@ "-10" ] }, - "execution_count": 10, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -312,7 +397,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 14, "id": "5ce91f42-7aec-492c-94fb-2320c971cd79", "metadata": {}, "outputs": [ @@ -325,7 +410,7 @@ } ], "source": [ - "@function_node(\"sum\", run_on_updates=True, update_on_instantiation=True)\n", + "@function_node(\"sum\")\n", "def add_node(x: int | float = 1, y: int | float = 1) -> int | float:\n", " return x + y\n", "\n", @@ -347,7 +432,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 15, "id": "20360fe7-b422-4d78-9bd1-de233f28c8df", "metadata": {}, "outputs": [ @@ -373,14 +458,14 @@ "source": [ "## Special nodes\n", "\n", - "In addition to the basic `Function` class, for the sake of convenience we also offer `Fast(Function)` -- which enforces that all the node function inputs are type-hinted and have defaults, then sets `run_on_updates=True` and `update_on_instantiation=True` --, and `SingleValue(Fast)` -- which further enforces that there is only a _single_ return value to the node function (i.e. a single output label), and then lets attribute and item access fall back to looking for attributes and items of this single output value. Of course there are decorators available for both of these.\n", + "In addition to the basic `Function` class, for the sake of convenience we also offer `Slow(Function)` -- which changes the defaults of `run_on_updates` and `update_on_instantiation` to `False` so that `run()` calls are necessary -- this can be helpful for nodes that are computationally expensive; and `SingleValue(Function)` -- which enforces that there is only a _single_ return value to the node function (i.e. a single output label), and then lets attribute and item access fall back to looking for attributes and items of this single output value. Of course there are decorators available for both of these.\n", "\n", "Let's look at a use case:" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 16, "id": "1a4e9693-0980-4435-aecc-3331d8b608dd", "metadata": {}, "outputs": [], @@ -391,7 +476,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 17, "id": "7c4d314b-33bb-4a67-bfb9-ed77fba3949c", "metadata": {}, "outputs": [ @@ -432,7 +517,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 18, "id": "1cd000bd-9b24-4c39-9cac-70a3291d0660", "metadata": {}, "outputs": [], @@ -457,7 +542,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 19, "id": "7964df3c-55af-4c25-afc5-9e07accb606a", "metadata": {}, "outputs": [ @@ -466,7 +551,6 @@ "output_type": "stream", "text": [ "n1 n1 n1 (GreaterThanHalf) output single-value: False\n", - "n2 n2 \n", "n3 n3 n3 (GreaterThanHalf) output single-value: False\n", "n4 n4 n4 (GreaterThanHalf) output single-value: False\n", "n5 n5 n5 (GreaterThanHalf) output single-value: False\n" @@ -474,10 +558,13 @@ } ], "source": [ + "from pyiron_contrib.workflow.function import Slow\n", + "\n", "n1 = greater_than_half(label=\"n1\")\n", "\n", "wf = Workflow(\"my_wf\", n1) # As args at init\n", - "wf.add.Function(lambda: x + 1, \"p1\", label=\"n2\") # Instantiating from the node adder\n", + "wf.add.Slow(lambda: x + 1, \"p1\", label=\"n2\") # Instantiating from the class with a lambda function\n", + "# (Slow since we don't have an x default)\n", "wf.add(greater_than_half(label=\"n3\")) # Instantiating then passing to node adder\n", "wf.n4 = greater_than_half(label=\"will_get_overwritten_with_n4\") # Set attribute to instance\n", "greater_than_half(label=\"n5\", parent=wf) # By passing the workflow to the node\n", @@ -504,7 +591,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 20, "id": "2e418abf-7059-4e1e-9b9f-b3dc0a4b5e35", "metadata": {}, "outputs": [ @@ -521,12 +608,11 @@ "def linear(x):\n", " return x\n", "\n", - "@function_node(\"z\")\n", + "@function_node(\"z\", run_on_updates=False)\n", "def times_two(y):\n", " return 2 * y\n", "\n", "l = linear(x=1)\n", - "l.run()\n", "t2 = times_two(y=l.outputs.y)\n", "print(t2.inputs.y, t2.outputs.z)" ] @@ -536,16 +622,16 @@ "id": "37aa4455-9b98-4be5-a365-363e3c490bb6", "metadata": {}, "source": [ - "Now the input of `t2` got updated when the connection is made, but by default we told this node not to do any automatic updates, so the output has its uninitialized value of `None`.\n", + "Now the input of `t2` got updated when the connection is made, but by we told this node not to do any automatic updates, so the output has its uninitialized value of `NotData`.\n", "\n", - "Often, you will probably want to have nodes with data connections to have signal connections, but this is not strictly required. Here, we'll introduce a (not strictly necessary) third node to control starting the workflow, and chain together to signals from our two functional nodes.\n", + "Often, you will want to have nodes with data connections to have signal connections, but this is not strictly required. Here, we'll introduce a (not strictly necessary) third node to control starting the workflow, and chain together to signals from our two functional nodes.\n", "\n", "Note that we have all the same syntacic sugar from data channels when creating connections between signal channels." ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 21, "id": "3310eac4-04f6-421b-9824-19bb2d680be6", "metadata": {}, "outputs": [ @@ -579,7 +665,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 22, "id": "7a6f2bce-6b5e-4321-9457-0a6790d2202a", "metadata": {}, "outputs": [], @@ -589,13 +675,13 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 23, "id": "6569014a-815b-46dd-8b47-4e1cd4584b3b", "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] @@ -617,9 +703,7 @@ "y = noise(length=10)\n", "f = plot(\n", " x=x, \n", - " y=y, \n", - " run_on_updates=True, \n", - " update_on_instantiation=True,\n", + " y=y,\n", " channels_requiring_update_after_run=[\"x\"],\n", ")\n", "f.inputs.y.require_update_after_node_runs(wait_now=True)" @@ -635,7 +719,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 24, "id": "25f0495a-e85f-43b7-8a70-a2c9cbd51ebb", "metadata": {}, "outputs": [ @@ -645,7 +729,7 @@ "(False, False)" ] }, - "execution_count": 21, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -656,13 +740,13 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 25, "id": "449ce797-be62-4211-b483-c717a3d70583", "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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iooKHT58OORz3nrrLS1cuFBPPvmkZs+erfnz5+vhhx/WN998M+H7DA8Pa2BgIOgBAADC40xxqLqyUNJI8Djf6M/VlYXG9huxFUbOnDkjv9+vnJycoPacnBz19PSEfM6JEyd08OBBdXR06M0339SuXbt04MAB3X///RO+T21trdxu99gjLy/PTpkAAOACy4s8ql9drFx38FRMrtul+tXFRvcZCWvTM4cjODlZljWubVQgEJDD4dC+ffvkdrslSTt37tTtt9+u3bt3a9q0aeOes23bNm3ZsmXs54GBAQIJAACXaXmRR8sKc+N7B9aZM2fK6XSOGwXp7e0dN1oyyuPxaPbs2WNBRBq5x8SyLJ06dUrz5s0b95z09HSlp6fbKQ0AAEyCM8WhsrkzTJcRxNY0TVpamkpKStTS0hLU3tLSovLy8pDPWbx4sU6fPq0vv/xyrO2TTz5RSkqK5syZE0bJAAAgkdjeZ2TLli168cUXtWfPHn388cd66KGH1N3drQ0bNkgamWJZu3btWP8777xTM2bM0D333KPOzk699957euSRR3TvvfeGnKIBAADJxfY9I1VVVerr69OOHTvk9XpVVFSkpqYm5efnS5K8Xq+6u7vH+n/rW99SS0uLfvnLX2rhwoWaMWOGVq1apccff3zq/goAABC3bO8zYgL7jAAAEH8iss8IAADAVCOMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKNSTRcAAIhf/oCltq5+9Q4OKTvDpdKCLDlTHKbLQpwhjAAAwtLc4VVNY6e8vqGxNo/bperKQi0v8hisDPGGaRoAgG3NHV5t3Hs0KIhIUo9vSBv3HlVzh9dQZYhHhBEAgC3+gKWaxk5ZIX432lbT2Cl/IFQPYDzCCADAlrau/nEjIuezJHl9Q2rr6o9eUYhrhBEAgC29gxMHkXD6AYQRAIAt2RmuKe0HEEYAALaUFmTJ43ZpogW8Do2sqiktyIpmWYhjhBEAgC3OFIeqKwslaVwgGf25urKQ/UYwaYSRSfIHLL1/vE//+8Fnev94H3eJA0hqy4s8ql9drFx38FRMrtul+tXF7DMCW9j0bBLY2AcAxlte5NGywlx2YMVlc1iWFfP/xB8YGJDb7ZbP51NmZmZU33t0Y58L/0ca/ajxLwAAAEKb7Pc30zQXwcY+AABEHmHkItjYBwCAyCOMXAQb+wAAEHmEkYtgYx8AACKPMHIRbOwDAEDkEUYugo19AACIPMLIJbCxDwAAkcWmZ5PAxj4AAEQOYWSSnCkOlc2dYboMAAASTljTNHV1dSooKJDL5VJJSYlaW1sn9bxDhw4pNTVVN954YzhvCwAAEpDtMNLQ0KDNmzdr+/btOnbsmJYsWaIVK1aou7v7os/z+Xxau3at/ud//ifsYgEAQOKxfTbNokWLVFxcrPr6+rG2BQsWaOXKlaqtrZ3weXfccYfmzZsnp9Opv/71r/rggw8m/Z4mz6YBAADhicjZNGfPnlV7e7sqKiqC2isqKnT48OEJn/fyyy/r+PHjqq6untT7DA8Pa2BgIOgBAAASk60wcubMGfn9fuXk5AS15+TkqKenJ+RzPv30U23dulX79u1Taurk7petra2V2+0ee+Tl5dkpEwAAxJGwbmB1OIKXtFqWNa5Nkvx+v+68807V1NRo/vz5k379bdu2yefzjT1OnjwZTpkAACAO2FraO3PmTDmdznGjIL29veNGSyRpcHBQR44c0bFjx/TAAw9IkgKBgCzLUmpqqt555x0tXbp03PPS09OVnp5upzQAABCnbIWRtLQ0lZSUqKWlRT/5yU/G2ltaWvTjH/94XP/MzEx9+OGHQW11dXX6+9//rgMHDqigoCDMsgHEGn/AYmNAAGGxvenZli1btGbNGi1cuFBlZWV6/vnn1d3drQ0bNkgamWL57LPP9OqrryolJUVFRUVBz8/OzpbL5RrXDiB+NXd4VdPYKa9vaKzN43apurKQIxMAXJLtMFJVVaW+vj7t2LFDXq9XRUVFampqUn5+viTJ6/Vecs8RAImjucOrjXuP6sI9Anp8Q9q49yhnOAG4JNv7jJjAPiNAbPIHLP3gib8HjYicz6GRQyUP/nopUzZAEorIPiMAcL62rv4Jg4gkWZK8viG1dfVHrygAcYcwAiBsvYMTB5Fw+gFIToQRAGHLznBNaT8AyYkwAiBspQVZ8rhdmuhuEIdGVtWUFmRFsywAcYYwAiBszhSHqisLJWlcIBn9ubqykJtXAVwUYQTAZVle5FH96mLluoOnYnLdLpb1ApgU2/uMAMCFlhd5tKwwlx1YAYSFMAJgSjhTHCqbO8N0GQDiEGEEAIAkFStnShFGAABIQrF0phQ3sAIAkGRGz5S6cAfl0TOlmju8Ua2HMAIAQBLxByzVNHaOO9xS0lhbTWOn/IHoHV1HGAEAIInE4plShBEAAJJILJ4pRRgBACCJxOKZUoQRAACSSCyeKUUYAQAgicTimVKEEQAAkkysnSnFpmcAACShWDpTijACAECSipUzpZimAQAARhFGAACAUYQRAABgFPeMRECsHMkMAEA8IIxMsVg6khkAgHjANM0UirUjmQEAiAeEkSkSi0cyAwAQDwgjUyQWj2QGACAeEEamSCweyQwAQDwgjEyRWDySGQCAeEAYmSKxeCQzAADxgDAyRWLxSGYAAOIBYWQKxdqRzAAAxAM2PZtisXQkMwAA8YAwEgGxciQzAADxgGkaAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYBRhBAAAGJVqugAAycsfsNTW1a/ewSFlZ7hUWpAlZ4rDdFkAoowwAsCI5g6vaho75fUNjbV53C5VVxZqeZHHYGUAoo1pGgBR19zh1ca9R4OCiCT1+Ia0ce9RNXd4DVUGwATCCICo8gcs1TR2ygrxu9G2msZO+QOhegBIRIQRAFHV1tU/bkTkfJYkr29IbV390SsKgFGEEQBR1Ts4cRAJpx+A+EcYARBV2RmuKe0HIP4RRgBEVWlBljxulyZawOvQyKqa0oKsaJYFwCDCCICocqY4VF1ZKEnjAsnoz9WVhew3AiQRwgiAqFte5FH96mLluoOnYnLdLtWvLmafESDJhBVG6urqVFBQIJfLpZKSErW2tk7Y94033tCyZct09dVXKzMzU2VlZXr77bfDLhhAYlhe5NHBXy/V/p9/X0/dcaP2//z7OvjrpQQRIAnZDiMNDQ3avHmztm/frmPHjmnJkiVasWKFuru7Q/Z/7733tGzZMjU1Nam9vV0//OEPVVlZqWPHjl128QDimzPFobK5M/TjG2erbO4MpmaAJOWwLMvWzkKLFi1ScXGx6uvrx9oWLFiglStXqra2dlKvcd1116mqqkqPPvropPoPDAzI7XbL5/MpMzPTTrkAAMCQyX5/2xoZOXv2rNrb21VRURHUXlFRocOHD0/qNQKBgAYHB5WVxZ3yAADA5kF5Z86ckd/vV05OTlB7Tk6Oenp6JvUaf/jDH/TVV19p1apVE/YZHh7W8PDw2M8DAwN2ygQAAHEkrBtYHY7geV3Lssa1hbJ//3499thjamhoUHZ29oT9amtr5Xa7xx55eXnhlAkAAOKArTAyc+ZMOZ3OcaMgvb2940ZLLtTQ0KB169bpz3/+s26++eaL9t22bZt8Pt/Y4+TJk3bKBAAAccRWGElLS1NJSYlaWlqC2ltaWlReXj7h8/bv36+7775br732mm699dZLvk96eroyMzODHgAAIDHZumdEkrZs2aI1a9Zo4cKFKisr0/PPP6/u7m5t2LBB0sioxmeffaZXX31V0kgQWbt2rZ566il9//vfHxtVmTZtmtxu9xT+KQAAIB7ZDiNVVVXq6+vTjh075PV6VVRUpKamJuXn50uSvF5v0J4jzz33nM6dO6f7779f999//1j7XXfdpVdeeeXy/wIAABDXbO8zYgL7jAAAEH8iss8IAADAVCOMAAAAo2zfMwIAAKaeP2CpratfvYNDys5wqbQgK2nOayKMAABgWHOHVzWNnfL6hsbaPG6XqisLk+Ika6ZpAAAwqLnDq417jwYFEUnq8Q1p496jau7wGqoseggjAAAY4g9YqmnsVKhlraNtNY2d8gdifuHrZSGMAABgSFtX/7gRkfNZkry+IbV19UevKAMIIwAAGNI7OHEQCadfvOIGVgAAbJqqlS/ZGa4p7RevCCMAANgwlStfSguy5HG71OMbCnnfiENSrnsk7CQypmkAAJikqV754kxxqLqyUNJI8Djf6M/VlYUJv98IYQQAgEmI1MqX5UUe1a8uVq47eCom1+1S/eripNhnhGkaAAAmwc7Kl7K5M2y99vIij5YV5rIDKwAAmFikV744Uxy2Q0yiYJoGAIBJYOVL5BBGAACYhNGVLxNNnDg0sqom0Ve+RAJhBACASWDlS+QQRgAAmCRWvkQGN7ACAGBDsq98iQTCCAAANiXzypdIYJoGAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYBSbnl2CP2Cxyx4AABFEGLmI5g6vaho75fUNjbV53C5VVxZy/gAAAFOEaZoJNHd4tXHv0aAgIkk9viFt3HtUzR1eQ5UBAJBYCCMh+AOWaho7ZYX43WhbTWOn/IFQPQAAgB2EkRDauvrHjYicz5Lk9Q2pras/ekUBAJCguGckhN7BiYNIOP0uxE2xAAD8f4SRELIzXFPa73zcFAsAQDCmaUIoLciSx+3SRGMVDo0EiNKCLFuvy02xAACMRxgJwZniUHVloSSNCySjP1dXFtqaWuGmWAAAQiOMTGB5kUf1q4uV6w6eisl1u1S/utj2lAo3xQIAEBr3jFzE8iKPlhXmTsnNppG+KRYAgHhFGLkEZ4pDZXNnXPbrRPKmWAAA4hnTNFESqZtiAQCId0kbRvwBS+8f79P/fvCZ3j/eF/EbRyNxUywAAIkgKadpTO31MXpT7IXvncs+IwCAJOawLCvm15IODAzI7XbL5/MpMzPzsl5rdK+PC//o0fGIcFbK2MUOrAAQv/hv+ORN9vs7qUZGLrXXh0Mje30sK8yN6P+xpuqmWABAdLGLdmQk1T0j7PUBAAgXu2hHTlKFEfb6AACEg120Iyupwgh7fQAAwsHIemQlVRhhrw8AQDgYWY+spAoj7PUBAAgHI+uRlVRhRJr6A/AAAImPkfXISqqlvaOm8gA8AEDiGx1Z37j3qBxS0I2sjKxfvqTb9AwAgHCxz4g9bHoGAMAUi5WR9UTbBZYwAgCADaZ30U7E0Zmku4EVAIB4lai7wBJGAACIA4m8CyxhBACAOJDIu8ASRgAAiAOJvAssYQQAgDiQyLvAEkYAAIgDibwLLGEEAIA4kMjnqxFGAACIE4l6vhqbngEAEEdiZRfYqUQYAQAgzpjeBXaqMU0DAACMYmQkDIl2QBEQD/jcAYmLMGJTIh5QBMQ6PndAYgtrmqaurk4FBQVyuVwqKSlRa2vrRfu/++67Kikpkcvl0rXXXqtnn302rGJNS9QDioBYxucOSHy2w0hDQ4M2b96s7du369ixY1qyZIlWrFih7u7ukP27urp0yy23aMmSJTp27Jh+85vfaNOmTfrLX/5y2cVHUyIfUATEKj53QHKwHUZ27typdevWaf369VqwYIF27dqlvLw81dfXh+z/7LPP6pprrtGuXbu0YMECrV+/Xvfee69+//vfX3bx0ZTIBxQBsYrPHZAcbIWRs2fPqr29XRUVFUHtFRUVOnz4cMjnvP/+++P6/+hHP9KRI0f03//+N+RzhoeHNTAwEPQwLZEPKAJiFZ87IDnYCiNnzpyR3+9XTk5OUHtOTo56enpCPqenpydk/3PnzunMmTMhn1NbWyu32z32yMvLs1NmRCTyAUVArOJzBySHsG5gdTiCl9NZljWu7VL9Q7WP2rZtm3w+39jj5MmT4ZQ5pRL5gCIgVvG5A5KDrTAyc+ZMOZ3OcaMgvb2940Y/RuXm5obsn5qaqhkzQu8el56erszMzKCHaYl8QBEQq/jcAcnBVhhJS0tTSUmJWlpagtpbWlpUXl4e8jllZWXj+r/zzjtauHChrrjiCpvlmpWoBxQBsYzPHZD4HNbonMkkNTQ0aM2aNXr22WdVVlam559/Xi+88II++ugj5efna9u2bfrss8/06quvShpZ2ltUVKT77rtPP//5z/X+++9rw4YN2r9/v376059O6j0HBgbkdrvl8/liYpSEnSCB6ONzB8SfyX5/296BtaqqSn19fdqxY4e8Xq+KiorU1NSk/Px8SZLX6w3ac6SgoEBNTU166KGHtHv3bs2aNUtPP/30pINILEq0A4qAeMDnDkhctkdGTIi1kREAAHBpk/3+5tReAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABhFGAEAAEYRRgAAgFG2d2A1YXRftoGBAcOVAACAyRr93r7U/qpxEUYGBwclSXl5eYYrAQAAdg0ODsrtdk/4+7jYDj4QCOj06dPKyMiQw8HBWNEyMDCgvLw8nTx5km34YxjXKfZxjWIf1ygyLMvS4OCgZs2apZSUie8MiYuRkZSUFM2ZM8d0GUkrMzOTD2cc4DrFPq5R7OMaTb2LjYiM4gZWAABgFGEEAAAYRRjBhNLT01VdXa309HTTpeAiuE6xj2sU+7hGZsXFDawAACBxMTICAACMIowAAACjCCMAAMAowggAADCKMJLk6urqVFBQIJfLpZKSErW2tk7Y94033tCyZct09dVXKzMzU2VlZXr77bejWG1ysnONznfo0CGlpqbqxhtvjGyBkGT/Og0PD2v79u3Kz89Xenq65s6dqz179kSp2uRk9xrt27dPN9xwg6688kp5PB7dc8896uvri1K1ScZC0vrTn/5kXXHFFdYLL7xgdXZ2Wg8++KA1ffp06z//+U/I/g8++KD1xBNPWG1tbdYnn3xibdu2zbriiiuso0ePRrny5GH3Go364osvrGuvvdaqqKiwbrjhhugUm8TCuU633XabtWjRIqulpcXq6uqy/vnPf1qHDh2KYtXJxe41am1ttVJSUqynnnrKOnHihNXa2mpdd9111sqVK6NceXIgjCSx0tJSa8OGDUFt3/ve96ytW7dO+jUKCwutmpqaqS4N/yfca1RVVWX99re/taqrqwkjUWD3Ov3tb3+z3G631dfXF43yYNm/Rr/73e+sa6+9Nqjt6aeftubMmROxGpMZ0zRJ6uzZs2pvb1dFRUVQe0VFhQ4fPjyp1wgEAhocHFRWVlYkSkx64V6jl19+WcePH1d1dXWkS4TCu05vvfWWFi5cqCeffFKzZ8/W/Pnz9fDDD+ubb76JRslJJ5xrVF5erlOnTqmpqUmWZenzzz/XgQMHdOutt0aj5KQTFwflYeqdOXNGfr9fOTk5Qe05OTnq6emZ1Gv84Q9/0FdffaVVq1ZFosSkF841+vTTT7V161a1trYqNZWPdzSEc51OnDihgwcPyuVy6c0339SZM2f0i1/8Qv39/dw3EgHhXKPy8nLt27dPVVVVGhoa0rlz53TbbbfpmWeeiUbJSYeRkSTncDiCfrYsa1xbKPv379djjz2mhoYGZWdnR6o8aPLXyO/3684771RNTY3mz58frfLwf+x8lgKBgBwOh/bt26fS0lLdcsst2rlzp1555RVGRyLIzjXq7OzUpk2b9Oijj6q9vV3Nzc3q6urShg0bolFq0uGfTklq5syZcjqd4/5V0NvbO+5fDxdqaGjQunXr9Prrr+vmm2+OZJlJze41Ghwc1JEjR3Ts2DE98MADkka+9CzLUmpqqt555x0tXbo0KrUnk3A+Sx6PR7Nnzw46Wn3BggWyLEunTp3SvHnzIlpzsgnnGtXW1mrx4sV65JFHJEnXX3+9pk+friVLlujxxx+Xx+OJeN3JhJGRJJWWlqaSkhK1tLQEtbe0tKi8vHzC5+3fv1933323XnvtNeZOI8zuNcrMzNSHH36oDz74YOyxYcMGffe739UHH3ygRYsWRav0pBLOZ2nx4sU6ffq0vvzyy7G2Tz75RCkpKZozZ05E601G4Vyjr7/+WikpwV+RTqdT0siICqaYuXtnYdroUreXXnrJ6uzstDZv3mxNnz7d+ve//21ZlmVt3brVWrNmzVj/1157zUpNTbV2795teb3esccXX3xh6k9IeHav0YVYTRMddq/T4OCgNWfOHOv222+3PvroI+vdd9+15s2bZ61fv97Un5Dw7F6jl19+2UpNTbXq6uqs48ePWwcPHrQWLlxolZaWmvoTEhphJMnt3r3bys/Pt9LS0qzi4mLr3XffHfvdXXfdZd10001jP990002WpHGPu+66K/qFJxE71+hChJHosXudPv74Y+vmm2+2pk2bZs2ZM8fasmWL9fXXX0e56uRi9xo9/fTTVmFhoTVt2jTL4/FYP/vZz6xTp05Fuerk4LAsxpsAAIA53DMCAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAw6v8BQzJGIrYe/C8AAAAASUVORK5CYII=", "text/plain": [ "
" ] @@ -693,14 +777,14 @@ "\n", "Currently we have a handfull of pre-build nodes available for import from the `nodes` package. Let's use these to quickly put together a workflow for looking at some MD data.\n", "\n", - "The `calc_md`, node is _not_ at `Fast`, but we happen to know that the calculation we're doing here is very easy, so we'll set `run_on_updates` and `update_at_instantiation` to `True`.\n", + "The `calc_md` node is `Slow`, but we happen to know that the calculation we're doing here is very easy, so we'll manually set `run_on_updates` and `update_at_instantiation` to `True` to get it to behave like a typical `Function` node.\n", "\n", "Finally, `SingleValue` has one more piece of syntactic sugar: when you're making a connection to the (single!) output channel, you can just pass the node itself!" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 26, "id": "ae500d5e-e55b-432c-8b5f-d5892193cdf5", "metadata": {}, "outputs": [ @@ -708,9 +792,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:175: 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:177: 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:175: 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:177: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent with_prebuilt.\n", " warn(\n" ] }, @@ -725,9 +809,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:175: 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:177: 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:175: 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:177: UserWarning: Reassigning the node scatter to the label plot when adding it to the parent with_prebuilt.\n", " warn(\n" ] }, From e44feab3c3e1c7c418664b0e11c8ff0145fe4b43 Mon Sep 17 00:00:00 2001 From: "@liamhuber" <@samwaseda> Date: Fri, 30 Jun 2023 09:24:17 -0700 Subject: [PATCH 289/756] Make NotData checking property private To avoid cluttering the tab completion menu --- pyiron_contrib/workflow/channels.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index a7510a4f7..fd500e03f 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -212,12 +212,12 @@ def ready(self) -> bool: (bool): Whether the value matches the type hint. """ if self.type_hint is not None: - return self.value_is_data and valid_value(self.value, self.type_hint) + return self._value_is_data and valid_value(self.value, self.type_hint) else: - return self.value_is_data + return self._value_is_data @property - def value_is_data(self): + def _value_is_data(self): return self.value is not NotData def update(self, value) -> None: From 82fdcfd2a05a6a5fb5fd4c143a2a1ce8f7e87c20 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Fri, 30 Jun 2023 16:47:14 +0000 Subject: [PATCH 290/756] Format black --- pyiron_contrib/workflow/channels.py | 1 + pyiron_contrib/workflow/composite.py | 29 +++++++++++-------- pyiron_contrib/workflow/node.py | 42 ++++++++++++++-------------- 3 files changed, 40 insertions(+), 32 deletions(-) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index fd500e03f..d76f7406b 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -140,6 +140,7 @@ class NotData: is provided; it lets the channel know that it has _no data in it_ and thus should not identify as ready. """ + pass diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 612359efa..49e7db78e 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -12,7 +12,12 @@ from pyiron_contrib.workflow.node import Node from pyiron_contrib.workflow.function import ( - Function, SingleValue, Slow, function_node, slow_node, single_value_node + Function, + SingleValue, + Slow, + function_node, + slow_node, + single_value_node, ) from pyiron_contrib.workflow.node_library import atomistics, standard from pyiron_contrib.workflow.node_library.package import NodePackage @@ -74,16 +79,16 @@ class Composite(Node, ABC): # Allows users/devs to easily create new nodes when using children of this class def __init__( - self, - label: str, - *args, - parent: Optional[Composite] = None, - strict_naming: bool = True, - **kwargs + self, + label: str, + *args, + parent: Optional[Composite] = None, + strict_naming: bool = True, + **kwargs, ): super().__init__(*args, label=label, parent=parent, **kwargs) self.strict_naming: bool = strict_naming - self.nodes: DotDict[str: Node] = DotDict() + self.nodes: DotDict[str:Node] = DotDict() self.add: NodeAdder = NodeAdder(self) self.starting_nodes: None | list[Node] = None @@ -96,13 +101,15 @@ def to_dict(self): @property def upstream_nodes(self) -> list[Node]: return [ - node for node in self.nodes.values() + node + for node in self.nodes.values() if node.outputs.connected and not node.inputs.connected ] def on_run(self): - starting_nodes = self.upstream_nodes if self.starting_nodes is None \ - else self.starting_nodes + starting_nodes = ( + self.upstream_nodes if self.starting_nodes is None else self.starting_nodes + ) for node in starting_nodes: node.run() diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 1cdc37f92..ed84532d1 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -23,28 +23,28 @@ class Node(HasToDict, ABC): """ Nodes are elements of a computational graph. They have input and output data channels that interface with the outside - world, and a callable that determines what they actually compute, and input and - output signal channels that can be used to customize the execution flow of the - graph; + world, and a callable that determines what they actually compute, and input and + output signal channels that can be used to customize the execution flow of the + graph; Together these channels represent edges on the computational graph. - - Nodes can be run to force their computation, or more gently updated, which will + + Nodes can be run to force their computation, or more gently updated, which will trigger a run only if the `run_on_update` flag is set to true and all of the input is ready (i.e. channel values conform to any type hints provided). - - Nodes may have a `parent` node that owns them as part of a sub-graph. - + + Nodes may have a `parent` node that owns them as part of a sub-graph. + Every node must be named with a `label`, and may use this label to attempt to create a working directory in memory for itself if requested. - These labels also help to identify nodes in the wider context of (potentially + These labels also help to identify nodes in the wider context of (potentially nested) computational graphs. - - 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. - - Nodes have a status, which is currently represented by the `running` and `failed` + + 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. + + 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` + Their value is controlled automatically in the defined `run` and `finish_run` methods. This is an abstract class. @@ -95,12 +95,12 @@ class Node(HasToDict, ABC): """ def __init__( - self, - label: str, - *args, - parent: Optional[Composite] = None, - run_on_updates: bool = False, - **kwargs, + self, + label: str, + *args, + parent: Optional[Composite] = None, + run_on_updates: bool = False, + **kwargs, ): """ A mixin class for objects that can form nodes in the graph representation of a From 3bbd79b551df5af6854a63c50917f080d8433392 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 11:06:28 +0000 Subject: [PATCH 291/756] Bump boto3 from 1.26.160 to 1.26.165 Bumps [boto3](https://github.com/boto/boto3) from 1.26.160 to 1.26.165. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.26.160...1.26.165) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a82f0c7c7..b8400e8f2 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ ], 'image': ['scikit-image==0.19.3'], 'generic': [ - 'boto3==1.26.160', + 'boto3==1.26.165', 'moto==4.1.12' ], 'workflow': [ From 0fac606d1e84ef926dd78277377a9cd0b7541855 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 3 Jul 2023 11:06:50 +0000 Subject: [PATCH 292/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index a8b5fbac5..88a8e7735 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.160 +- boto3 =1.26.165 - moto =4.1.12 - pycp2k =0.2.2 - typeguard =4.0.0 From ece5a07d6336468f996f0a430ec4d61d6432ca0a Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 3 Jul 2023 11:07:13 +0000 Subject: [PATCH 293/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 1da146095..98c3d0c0c 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.160 +- boto3 =1.26.165 - moto =4.1.12 - pycp2k =0.2.2 - typeguard =4.0.0 diff --git a/docs/environment.yml b/docs/environment.yml index 68a5a0f05..fd0ccce63 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -17,7 +17,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.160 +- boto3 =1.26.165 - moto =4.1.12 - pycp2k =0.2.2 - typeguard =4.0.0 From ffd7a2b06de59b03a2c520cbd35274e8ec475bdd Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 5 Jul 2023 15:31:29 -0700 Subject: [PATCH 294/756] Introduce running on an executor --- pyiron_contrib/workflow/node.py | 25 ++++++++++++++++++------- pyiron_contrib/workflow/util.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index ed84532d1..10e90fc34 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -6,11 +6,13 @@ from __future__ import annotations from abc import ABC, abstractmethod +from concurrent.futures import Future from typing import Optional, TYPE_CHECKING from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Signals, InputSignal, OutputSignal +from pyiron_contrib.workflow.util import CloudpickleProcessPoolExecutor if TYPE_CHECKING: from pyiron_base.jobs.job.extension.server.generic import Server @@ -62,6 +64,8 @@ class Node(HasToDict, ABC): is False.) fully_connected (bool): whether _all_ of the IO (including signals) are connected. + future (concurrent.futures.Future | None): A futures object, if the node is + currently running or has already run using an executor. inputs (pyiron_contrib.workflow.io.Inputs): **Abstract.** Children must define a property returning an `Inputs` object. label (str): A name for the node. @@ -129,6 +133,8 @@ def __init__( self.signals = self._build_signal_channels() self._working_directory = None self.run_on_updates: bool = run_on_updates + self.executor: None | CloudpickleProcessPoolExecutor = None + self.future: None | Future = None @property @abstractmethod @@ -176,7 +182,7 @@ def run(self) -> None: self.running = True self.failed = False - if self.server is None: + if self.executor is None: try: run_output = self.on_run(**self.run_args) except Exception as e: @@ -184,16 +190,17 @@ def run(self) -> None: self.failed = True raise e 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) else: raise NotImplementedError( "We currently only support executing the node functionality right on " - "the main python process that the node instance lives on. Come back " - "later for cool new features." + "the main python process or with a " + "pyiron_contrib.workflow.util.CloudpickleProcessPoolExecutor." ) - # TODO: Send the `on_run` callable and the `run_args` data off to remote - # resources and register `finish_run` as a callback. - def finish_run(self, run_output: tuple): + def finish_run(self, run_output: tuple | Future): """ Process the run result, then wrap up statuses etc. @@ -201,8 +208,12 @@ def finish_run(self, run_output: tuple): execution off to another entity and release the python process to do other things. In such a case, this function should be registered as a callback so that the node can finish "running" and, e.g. push its data forward when that - execution is finished. + execution is finished. In such a case, a `concurrent.futures.Future` object is + expected back and must be unpacked. """ + if isinstance(run_output, Future): + run_output = run_output.result() + try: self.process_run_result(run_output) except Exception as e: diff --git a/pyiron_contrib/workflow/util.py b/pyiron_contrib/workflow/util.py index d80bc7ad3..846f8e30c 100644 --- a/pyiron_contrib/workflow/util.py +++ b/pyiron_contrib/workflow/util.py @@ -1,6 +1,35 @@ +from concurrent.futures import ProcessPoolExecutor + +import cloudpickle + + class DotDict(dict): def __getattr__(self, item): return self.__getitem__(item) def __setattr__(self, key, value): self[key] = value + + +def _apply_cloudpickle(fn, /, *args, **kwargs): + fn = cloudpickle.loads(fn) + return fn(*args, **kwargs) + + +class CloudpickleProcessPoolExecutor(ProcessPoolExecutor): + """ + In our workflows, it is common to dynamically create classes from functions using a + decorator; + This makes the underlying function object mismatch with the pickle-findable + "function" (actually a class after wrapping). + The result is that a regular `ProcessPoolExecutor` cannot pickle our node functions. + + An alternative is to force the executor to use pickle under the hood, which _can_ + handle these sort of dynamic objects. + This solution comes from u/mostsquares @ stackoverflow: + https://stackoverflow.com/questions/62830970/submit-dynamically-loaded-functions-to-the-processpoolexecutor + """ + def submit(self, fn, /, *args, **kwargs): + return super().submit( + _apply_cloudpickle, cloudpickle.dumps(fn), *args, **kwargs + ) From 50efd4ac90fd83ead7a72e52ff1abd9ece8a0ed8 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 6 Jul 2023 13:45:18 -0700 Subject: [PATCH 295/756] Reorder when and where the result processing/ran signal happens This was necessary to get cyclic workflows to cycle without failing. --- pyiron_contrib/workflow/node.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index ed84532d1..164152a0f 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -204,15 +204,14 @@ def finish_run(self, run_output: tuple): execution is finished. """ try: + self.running = False self.process_run_result(run_output) + self.signals.output.ran() except Exception as e: self.running = False self.failed = True raise e - self.signals.output.ran() - self.running = False - def _build_signal_channels(self) -> Signals: signals = Signals() signals.input.run = InputSignal("run", self, self.run) From 539eb89ada4cbc0dafbb5792c0ebb8a2e7d681c2 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 6 Jul 2023 14:13:53 -0700 Subject: [PATCH 296/756] Add an integration test for cyclic graphs --- tests/integration/test_workflow.py | 76 ++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/integration/test_workflow.py diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py new file mode 100644 index 000000000..01f3ae78a --- /dev/null +++ b/tests/integration/test_workflow.py @@ -0,0 +1,76 @@ +import time +import unittest + +import numpy as np + +from pyiron_contrib.workflow.channels import OutputSignal +from pyiron_contrib.workflow.function import Function +from pyiron_contrib.workflow.workflow import Workflow + + +class TestNothing(unittest.TestCase): + def test_cyclic_graphs(self): + """ + Check that cyclic graphs run. + + TODO: Update once logical switches are included in the node library + """ + + @Workflow.wrap_as.single_value_node("rand") + def numpy_randint(low=0, high=20): + rand = np.random.randint(low=low, high=high) + print(f"Generating random number between {low} and {high}...{rand}!") + return rand + + class GreaterThanLimitSwitch(Function): + """ + A switch class for sending signal output depending on a '>' check + applied to input + """ + + def __init__(self, **kwargs): + super().__init__(self.greater_than, "value_gt_limit", **kwargs) + self.signals.output.true = OutputSignal("true", self) + self.signals.output.false = OutputSignal("false", self) + + @staticmethod + def greater_than(value, limit=10): + return value > limit + + def process_run_result(self, function_output): + """ + Process the output as usual, then fire signals accordingly. + """ + super().process_run_result(function_output) + + if self.outputs.value_gt_limit.value: + print(f"{self.inputs.value.value} > {self.inputs.limit.value}") + self.signals.output.true() + else: + print(f"{self.inputs.value.value} <= {self.inputs.limit.value}") + self.signals.output.false() + + @Workflow.wrap_as.single_value_node("sqrt") + def numpy_sqrt(value=0): + sqrt = np.sqrt(value) + print(f"sqrt({value}) = {sqrt}") + return sqrt + + wf = Workflow("rand_until_big_then_sqrt") + + wf.rand = numpy_randint() + + wf.gt_switch = GreaterThanLimitSwitch(run_on_updates=False) + wf.gt_switch.inputs.value = wf.rand + + wf.sqrt = numpy_sqrt(run_on_updates=False) + wf.sqrt.inputs.value = wf.rand + + wf.gt_switch.signals.input.run = wf.rand.signals.output.ran + wf.sqrt.signals.input.run = wf.gt_switch.signals.output.true + wf.rand.signals.input.run = wf.gt_switch.signals.output.false + + wf.rand.update() + self.assertAlmostEqual( + np.sqrt(wf.rand.outputs.rand.value), wf.sqrt.outputs.sqrt.value, 6 + ) From 3f02604b020c206c9fabab9a464969f1a5258f23 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 7 Jul 2023 11:58:31 +0200 Subject: [PATCH 297/756] Add logy parameter --- pyiron_contrib/atomistics/ml/potentialfit.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/atomistics/ml/potentialfit.py b/pyiron_contrib/atomistics/ml/potentialfit.py index e4ca09c8e..6ec433b13 100644 --- a/pyiron_contrib/atomistics/ml/potentialfit.py +++ b/pyiron_contrib/atomistics/ml/potentialfit.py @@ -108,9 +108,12 @@ def __init__(self, training_data, predicted_data): self._training_data = training_data self._predicted_data = predicted_data - def energy_scatter_histogram(self): + def energy_scatter_histogram(self, logy=False): """ Plots correlation and (training) error histograms. + + Args: + logy (bool): Use log scale for histogram heights """ energy_train = self._training_data["energy"] / self._training_data["length"] energy_pred = self._predicted_data["energy"] / self._predicted_data["length"] @@ -122,7 +125,7 @@ def energy_scatter_histogram(self): plt.plot() plt.subplot(1, 2, 2) - plt.hist(energy_train - energy_pred) + plt.hist(energy_train - energy_pred, log=logy) plt.xlabel("Training Error [eV / atom]") def energy_log_histogram(self, bins=20, logy=False): @@ -168,13 +171,13 @@ def annotated_vline(x, text, linestyle="--"): annotated_vline(low, f"LOW = {low:.02}", linestyle="-") plt.xlabel("Training Error [eV/atom]") - def force_scatter_histogram(self, axis=None): + def force_scatter_histogram(self, axis=None, logy=False): """ Plots correlation and (training) error histograms. Args: axis (None, int): Whether to plot for an axis or norm - + logy (bool): Use log scale for histogram heights """ force_train = self._training_data["forces"] force_pred = self._predicted_data["forces"] @@ -191,5 +194,5 @@ def force_scatter_histogram(self, axis=None): plt.xlabel("True Forces [eV/$\mathrm{\AA}$]") plt.ylabel("Predicted Forces [eV/$\AA$]") plt.subplot(1, 2, 2) - plt.hist(ft - fp) + plt.hist(ft - fp, log=logy) plt.xlabel("Training Error [eV/$\AA$]") From 7ad5d46c5b48d2632e6451565dd1984da18cf462 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 7 Jul 2023 12:06:31 +0200 Subject: [PATCH 298/756] Add histogram of logarithmic force errors --- pyiron_contrib/atomistics/ml/potentialfit.py | 59 +++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/atomistics/ml/potentialfit.py b/pyiron_contrib/atomistics/ml/potentialfit.py index 6ec433b13..720b5052e 100644 --- a/pyiron_contrib/atomistics/ml/potentialfit.py +++ b/pyiron_contrib/atomistics/ml/potentialfit.py @@ -2,7 +2,7 @@ Abstract base class for fitting interactomic potentials. """ -from typing import List +from typing import List, Optional import abc @@ -196,3 +196,60 @@ def force_scatter_histogram(self, axis=None, logy=False): plt.subplot(1, 2, 2) plt.hist(ft - fp, log=logy) plt.xlabel("Training Error [eV/$\AA$]") + + def force_log_histogram( + self, + bins: int = 20, + logy: bool = False, + axis: Optional[int] = None + ): + """ + Plots a histogram of logarithmic training errors. + + Bins are created automatically using the minimum and maximum absolute + errors with the given number of bins. + + Arguments: + bins (int, optional): number of bins for the histogram + logy (bool, optional): if True use a log scale also for the y-axis + axis (int, optional): which axis of the forces to plot; if not given plot force magnitude + """ + + force_train = self._training_data["forces"] + force_pred = self._predicted_data["forces"] + + if axis is None: + ft = np.linalg.norm(force_train, axis=1) + fp = np.linalg.norm(force_pred, axis=1) + else: + ft = force_train[:, axis] + fp = force_pred[:, axis] + + df = abs(force_train - force_pred) + rmse = np.sqrt((df**2).mean()) + mae = df.mean() + high = df.max() + low = df.min() + + ax = plt.gca() + trafo = ax.get_xaxis_transform() + + def annotated_vline(x, text, linestyle="--"): + plt.axvline(x, color="k", linestyle=linestyle) + plt.text( + x=x, + y=0.5, + s=text, + transform=trafo, + rotation="vertical", + horizontalalignment="center", + path_effects=[withStroke(linewidth=4, foreground="w")], + ) + + plt.hist(df, bins=np.logspace(np.log10(low), np.log10(high), bins), log=logy) + plt.xscale("log") + annotated_vline(rmse, f"RMSE = {rmse:.02}") + annotated_vline(mae, f"MAE = {mae:.02}") + annotated_vline(high, f"HIGH = {high:.02}", linestyle="-") + annotated_vline(low, f"LOW = {low:.02}", linestyle="-") + plt.xlabel("Training Error [eV/$\mathrm{\AA}$]") From 676470092ce86f32a8471aa8fc81b418cd01cd92 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 7 Jul 2023 12:18:15 +0200 Subject: [PATCH 299/756] Add plot to show angle deviations in forces --- pyiron_contrib/atomistics/ml/potentialfit.py | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pyiron_contrib/atomistics/ml/potentialfit.py b/pyiron_contrib/atomistics/ml/potentialfit.py index 720b5052e..451247ed2 100644 --- a/pyiron_contrib/atomistics/ml/potentialfit.py +++ b/pyiron_contrib/atomistics/ml/potentialfit.py @@ -253,3 +253,35 @@ def annotated_vline(x, text, linestyle="--"): annotated_vline(high, f"HIGH = {high:.02}", linestyle="-") annotated_vline(low, f"LOW = {low:.02}", linestyle="-") plt.xlabel("Training Error [eV/$\mathrm{\AA}$]") + + def force_angle( + self, + bins: int = 180, + logy: bool = True, + tol: float = 1e-6, + angle_in_degrees=True + ): + """ + Plot histogram of the angle between training and predicted forces. + + Args: + bins (int): number of bins + logy (bool): Use log scale for histogram heights + tol (float): consider forces smaller than this zero (and obmit them from the histogram) + angle_in_degrees (bool): if True use degrees, otherwise radians + """ + force_train = self._training_data["forces"] + force_pred = self._predicted_data["forces"] + + force_norm_train = np.linalg.norm(force_train, axis=-1).reshape(-1, 1) + force_norm_pred = np.linalg.norm(force_pred, axis=-1).reshape(-1, 1) + + I = ( (force_norm_train > tol) & (force_norm_train > tol) ).reshape(-1) + + force_dir_train = force_train[I]/force_norm_train[I] + force_dir_pred = force_pred[I]/force_norm_pred[I] + + err = np.arccos( (force_dir_train * force_dir_pred).sum(axis=-1).round(8) ) + if angle_in_degrees: + err = np.rad2deg(err) + plt.hist(err, bins=bins, log=logy) From 6bfd4ef90520b550d33f074860c83eb5e75d138a Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 7 Jul 2023 12:41:06 +0200 Subject: [PATCH 300/756] touch ups --- pyiron_contrib/atomistics/ml/potentialfit.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/atomistics/ml/potentialfit.py b/pyiron_contrib/atomistics/ml/potentialfit.py index 451247ed2..d6cf960a8 100644 --- a/pyiron_contrib/atomistics/ml/potentialfit.py +++ b/pyiron_contrib/atomistics/ml/potentialfit.py @@ -225,7 +225,7 @@ def force_log_histogram( ft = force_train[:, axis] fp = force_pred[:, axis] - df = abs(force_train - force_pred) + df = abs(ft - fp) rmse = np.sqrt((df**2).mean()) mae = df.mean() high = df.max() @@ -246,7 +246,7 @@ def annotated_vline(x, text, linestyle="--"): path_effects=[withStroke(linewidth=4, foreground="w")], ) - plt.hist(df, bins=np.logspace(np.log10(low), np.log10(high), bins), log=logy) + plt.hist(df, bins=np.logspace(np.log10(low + 1e-8), np.log10(high), bins), log=logy) plt.xscale("log") annotated_vline(rmse, f"RMSE = {rmse:.02}") annotated_vline(mae, f"MAE = {mae:.02}") @@ -254,12 +254,13 @@ def annotated_vline(x, text, linestyle="--"): annotated_vline(low, f"LOW = {low:.02}", linestyle="-") plt.xlabel("Training Error [eV/$\mathrm{\AA}$]") - def force_angle( + def force_angle_histogram( self, bins: int = 180, logy: bool = True, tol: float = 1e-6, - angle_in_degrees=True + angle_in_degrees=True, + cumulative = False ): """ Plot histogram of the angle between training and predicted forces. @@ -284,4 +285,8 @@ def force_angle( err = np.arccos( (force_dir_train * force_dir_pred).sum(axis=-1).round(8) ) if angle_in_degrees: err = np.rad2deg(err) - plt.hist(err, bins=bins, log=logy) + if cumulative: + logy = False + plt.hist(err, bins=bins, log=logy, cumulative=cumulative) + plt.xlabel("Angular Deviation of Force [" + ["rad", "deg"][angle_in_degrees] + "]") + plt.ylabel("Count") From a1efbd353296fdff3646fdf5e2fd09523346583d Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 7 Jul 2023 08:43:41 -0700 Subject: [PATCH 301/756] Clean the text output in integration test by avoiding 0th rand call --- tests/integration/test_workflow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py index 01f3ae78a..a8f2f4d58 100644 --- a/tests/integration/test_workflow.py +++ b/tests/integration/test_workflow.py @@ -58,7 +58,7 @@ def numpy_sqrt(value=0): wf = Workflow("rand_until_big_then_sqrt") - wf.rand = numpy_randint() + wf.rand = numpy_randint(update_on_instantiation=False) wf.gt_switch = GreaterThanLimitSwitch(run_on_updates=False) wf.gt_switch.inputs.value = wf.rand From 6502bb06565dc16ee4bebfcbcce161d8d5b3e071 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 10 Jul 2023 14:11:10 +0200 Subject: [PATCH 302/756] Do not wrap unnecessary --- pyiron_contrib/tinybase/creator.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyiron_contrib/tinybase/creator.py b/pyiron_contrib/tinybase/creator.py index 696129f5e..b45daed1b 100644 --- a/pyiron_contrib/tinybase/creator.py +++ b/pyiron_contrib/tinybase/creator.py @@ -166,9 +166,7 @@ def __init__(self, project, config): def bulk(self, *args, **kwargs): return ase_to_pyiron(ase.build.bulk(*args, **kwargs)) - @wraps(Atoms) - def atoms(self, *args, **kwargs): - return Atoms(*args, **kwargs) + atoms = Atoms class ExecutorCreator(Creator): From cec9133f5f8aad6a4924ca8ca97fe45151cd3f54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 12:14:44 +0000 Subject: [PATCH 303/756] Bump boto3 from 1.26.165 to 1.28.1 Bumps [boto3](https://github.com/boto/boto3) from 1.26.165 to 1.28.1. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.26.165...1.28.1) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b8400e8f2..f28b0caa0 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ ], 'image': ['scikit-image==0.19.3'], 'generic': [ - 'boto3==1.26.165', + 'boto3==1.28.1', 'moto==4.1.12' ], 'workflow': [ From c0b2d806f5b6f0a1d9f43f63468781ae1c6089a8 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 10 Jul 2023 14:15:42 +0200 Subject: [PATCH 304/756] Remove small typo --- pyiron_contrib/tinybase/creator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/creator.py b/pyiron_contrib/tinybase/creator.py index b45daed1b..cc10c802f 100644 --- a/pyiron_contrib/tinybase/creator.py +++ b/pyiron_contrib/tinybase/creator.py @@ -90,7 +90,7 @@ def create( else: if not isinstance(job.task, task): raise ValueError( - f"Job with given name already exists, but is of different type!" + "Job with given name already exists, but is of different type!" ) return job except JobNotFoundError: From 383b40f88d5013b0a06f53fa12f8f27d7ecedd35 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 10 Jul 2023 12:39:07 +0000 Subject: [PATCH 305/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 88a8e7735..8adc737f5 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.165 +- boto3 =1.28.1 - moto =4.1.12 - pycp2k =0.2.2 - typeguard =4.0.0 From 3c8f59d2b29d5b5f04d6982b213ba8966461ed80 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 10 Jul 2023 12:39:32 +0000 Subject: [PATCH 306/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 98c3d0c0c..855c200c2 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.165 +- boto3 =1.28.1 - moto =4.1.12 - pycp2k =0.2.2 - typeguard =4.0.0 diff --git a/docs/environment.yml b/docs/environment.yml index fd0ccce63..79e6a9c23 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -17,7 +17,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.26.165 +- boto3 =1.28.1 - moto =4.1.12 - pycp2k =0.2.2 - typeguard =4.0.0 From 08d0e8ef73a60aad8126aa254c7f0c5368e6fead Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 10 Jul 2023 13:47:32 +0000 Subject: [PATCH 307/756] Format black --- pyiron_contrib/tinybase/job.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/job.py b/pyiron_contrib/tinybase/job.py index 2fe521c70..0ced3a8b9 100644 --- a/pyiron_contrib/tinybase/job.py +++ b/pyiron_contrib/tinybase/job.py @@ -167,7 +167,8 @@ def wait(self, timeout: Optional[float] = None): Raises: ValueError: if job status is not `finished` or `running` """ - if self.status == "finished": return + if self.status == "finished": + return if self.status != "running": raise ValueError("Job not running!") self._executor.wait(timeout=timeout) From 9f00bf85398cb1835c5116eab6da5c2362d9e0ce Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 10 Jul 2023 11:30:31 -0700 Subject: [PATCH 308/756] Update docs --- pyiron_contrib/workflow/function.py | 4 ++++ pyiron_contrib/workflow/node.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index d9de5d8c6..72a426b5b 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -52,6 +52,10 @@ class Function(Node): updated, and will attempt to update on initialization (after setting _all_ initial input values). + Output is updated in the `process_run_result` inside the parent class `finish_run` + call, such that output data gets pushed after the node stops running but before + then `ran` signal fires. + Args: node_function (callable): The function determining the behaviour of the node. *output_labels (str): A name for each return value of the node function. diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 164152a0f..c94ca19ed 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -195,7 +195,7 @@ def run(self) -> None: def finish_run(self, run_output: tuple): """ - Process the run result, then wrap up statuses etc. + Switch the node status, process the run result, then fire the ran signal. By extracting this as a separate method, we allow the node to pass the actual execution off to another entity and release the python process to do other From e11e5a632822a039e074a9e7806a3bb5cbfd990e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 10 Jul 2023 11:56:28 -0700 Subject: [PATCH 309/756] Make new module --- pyiron_contrib/executors/README.md | 3 +++ pyiron_contrib/executors/__init__.py | 0 2 files changed, 3 insertions(+) create mode 100644 pyiron_contrib/executors/README.md create mode 100644 pyiron_contrib/executors/__init__.py diff --git a/pyiron_contrib/executors/README.md b/pyiron_contrib/executors/README.md new file mode 100644 index 000000000..e96e8b751 --- /dev/null +++ b/pyiron_contrib/executors/README.md @@ -0,0 +1,3 @@ +# Executors + +This sub-module holds custom children of `concurrent.futures.Executor` for use in other parts of pyiron (e.g. `pyiron_contrib.workflow`). \ No newline at end of file diff --git a/pyiron_contrib/executors/__init__.py b/pyiron_contrib/executors/__init__.py new file mode 100644 index 000000000..e69de29bb From 86623f789b38749fbc114ca9483acec1a6a5e945 Mon Sep 17 00:00:00 2001 From: Liam Huber Date: Mon, 10 Jul 2023 12:25:46 -0700 Subject: [PATCH 310/756] Update pyiron_contrib/workflow/node.py Co-authored-by: Sam Dareska <37879103+samwaseda@users.noreply.github.com> --- pyiron_contrib/workflow/node.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index c94ca19ed..ec17b81de 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -208,7 +208,6 @@ def finish_run(self, run_output: tuple): self.process_run_result(run_output) self.signals.output.ran() except Exception as e: - self.running = False self.failed = True raise e From 045cc531a9ca45aff32f0cb43bd86e863a3365a7 Mon Sep 17 00:00:00 2001 From: Liam Huber Date: Mon, 10 Jul 2023 12:25:54 -0700 Subject: [PATCH 311/756] Update pyiron_contrib/workflow/node.py Co-authored-by: Sam Dareska <37879103+samwaseda@users.noreply.github.com> --- pyiron_contrib/workflow/node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index ec17b81de..14286001b 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -203,6 +203,7 @@ def finish_run(self, run_output: tuple): so that the node can finish "running" and, e.g. push its data forward when that execution is finished. """ + self.running = False try: self.running = False self.process_run_result(run_output) From d0dac284e6ee591cc544873dde2eaf73f00b8095 Mon Sep 17 00:00:00 2001 From: Liam Huber Date: Mon, 10 Jul 2023 12:26:02 -0700 Subject: [PATCH 312/756] Update pyiron_contrib/workflow/node.py Co-authored-by: Sam Dareska <37879103+samwaseda@users.noreply.github.com> --- pyiron_contrib/workflow/node.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 14286001b..9db0a95a5 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -205,7 +205,6 @@ def finish_run(self, run_output: tuple): """ self.running = False try: - self.running = False self.process_run_result(run_output) self.signals.output.ran() except Exception as e: From 72b1966206da0a2cd6d3c0ef0aca6024b5eb465b Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 10 Jul 2023 13:02:45 -0700 Subject: [PATCH 313/756] Define and test an extended process pool executor --- pyiron_contrib/executors/executors.py | 25 +++++++++ tests/unit/executor/__init__.py | 0 tests/unit/executor/test_executors.py | 74 +++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 pyiron_contrib/executors/executors.py create mode 100644 tests/unit/executor/__init__.py create mode 100644 tests/unit/executor/test_executors.py diff --git a/pyiron_contrib/executors/executors.py b/pyiron_contrib/executors/executors.py new file mode 100644 index 000000000..35a3a0744 --- /dev/null +++ b/pyiron_contrib/executors/executors.py @@ -0,0 +1,25 @@ +from concurrent.futures import ProcessPoolExecutor + +import cloudpickle + + +def _apply_cloudpickle(fn, /, *args, **kwargs): + fn = cloudpickle.loads(fn) + return fn(*args, **kwargs) + + +class CloudpickleProcessPoolExecutor(ProcessPoolExecutor): + """ + This wrapper class replaces the `pickle` backend of the + `concurrent.futures.ProcessPoolExecutor` with a backend from `cloudpickle`. + In this way, even objects with no canonical import (e.g. those created dynamically + from a decorator) can be submitted to the executor. + + This solution comes from u/mostsquares @ stackoverflow: + https://stackoverflow.com/questions/62830970/submit-dynamically-loaded-functions-to-the-processpoolexecutor + """ + + def submit(self, fn, /, *args, **kwargs): + return super().submit( + _apply_cloudpickle, cloudpickle.dumps(fn), *args, **kwargs + ) diff --git a/tests/unit/executor/__init__.py b/tests/unit/executor/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/executor/test_executors.py b/tests/unit/executor/test_executors.py new file mode 100644 index 000000000..09eb10c32 --- /dev/null +++ b/tests/unit/executor/test_executors.py @@ -0,0 +1,74 @@ +from functools import partialmethod +from time import sleep +import unittest + +from pyiron_contrib.executors.executors import CloudpickleProcessPoolExecutor + + +class Foo: + """ + A base class to be dynamically modified for testing CloudpickleProcessPoolExecutor. + """ + def __init__(self, fnc: callable): + self.fnc = fnc + self.result = None + + @property + def run(self): + return self.fnc + + def process_result(self, future): + self.result = future.result() + + +def dynamic_foo(): + """ + A decorator for dynamically modifying the Foo class to test + CloudpickleProcessPoolExecutor. + + Overrides the `fnc` input of `Foo` with the decorated function. + """ + def as_dynamic_foo(fnc: callable): + return type( + "DynamicFoo", + (Foo,), # Define parentage + { + "__init__": partialmethod( + Foo.__init__, + fnc + ) + }, + ) + + return as_dynamic_foo + + +class TestCloudpickleProcessPoolExecutor(unittest.TestCase): + def test_dynamic_classes(self): + fortytwo = 42 # No magic numbers; we use it in a couple places so give it a var + + @dynamic_foo() + def slowly_returns_42(): + sleep(0.1) + return fortytwo + + dynamic_42 = slowly_returns_42() # Instantiate the dynamically defined class + self.assertIsInstance( + dynamic_42, + Foo, + msg="Just a sanity check that the test is set up right" + ) + self.assertIsNone( + dynamic_42.result, + msg="Just a sanity check that the test is set up right" + ) + executor = CloudpickleProcessPoolExecutor() + fs = executor.submit(dynamic_42.run) + fs.add_done_callback(dynamic_42.process_result) + self.assertFalse(fs.done(), msg="Should be running on the executor") + self.assertEqual(fortytwo, fs.result(), msg="Future must complete") + self.assertEqual(fortytwo, dynamic_42.result, msg="Callback must get called") + + +if __name__ == '__main__': + unittest.main() From b8aed7ea38ede1128896bc98e50acc066f53f72e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 11 Jul 2023 13:54:36 -0700 Subject: [PATCH 314/756] Be more specific about limitations --- pyiron_contrib/executors/executors.py | 12 ++++++---- tests/unit/executor/test_executors.py | 33 ++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/executors/executors.py b/pyiron_contrib/executors/executors.py index 35a3a0744..c85c55897 100644 --- a/pyiron_contrib/executors/executors.py +++ b/pyiron_contrib/executors/executors.py @@ -10,13 +10,17 @@ def _apply_cloudpickle(fn, /, *args, **kwargs): class CloudpickleProcessPoolExecutor(ProcessPoolExecutor): """ - This wrapper class replaces the `pickle` backend of the - `concurrent.futures.ProcessPoolExecutor` with a backend from `cloudpickle`. - In this way, even objects with no canonical import (e.g. those created dynamically - from a decorator) can be submitted to the executor. + This executor behaves like `concurrent.futures.ProcessPoolExecutor`, except that + non-pickleable callables may also be submit (e.g. dynamically defined functions). + + This is accomplished by replacing the `pickle` backend of the + `concurrent.futures.ProcessPoolExecutor` with a backend from `cloudpickle` when + serializing the callable. This solution comes from u/mostsquares @ stackoverflow: https://stackoverflow.com/questions/62830970/submit-dynamically-loaded-functions-to-the-processpoolexecutor + + Note: Arguments and return values must still be regularly pickleable. """ def submit(self, fn, /, *args, **kwargs): diff --git a/tests/unit/executor/test_executors.py b/tests/unit/executor/test_executors.py index 09eb10c32..f1569018e 100644 --- a/tests/unit/executor/test_executors.py +++ b/tests/unit/executor/test_executors.py @@ -1,4 +1,5 @@ from functools import partialmethod +from pickle import PickleError from time import sleep import unittest @@ -44,7 +45,11 @@ def as_dynamic_foo(fnc: callable): class TestCloudpickleProcessPoolExecutor(unittest.TestCase): - def test_dynamic_classes(self): + def test_unpickleable_callable(self): + """ + We should be able to use an unpickleable callable -- in this case, a method of + a dynamically defined class. + """ fortytwo = 42 # No magic numbers; we use it in a couple places so give it a var @dynamic_foo() @@ -69,6 +74,32 @@ def slowly_returns_42(): self.assertEqual(fortytwo, fs.result(), msg="Future must complete") self.assertEqual(fortytwo, dynamic_42.result, msg="Callback must get called") + def test_unpickleable_return(self): + """ + We should _not_ be able to use an unpickleable return value -- in this case, a + method of a dynamically defined class. + """ + + @dynamic_foo() + def does_nothing(): + return + + @dynamic_foo() + def slowly_returns_unpickleable(): + """ + Returns a complex, dynamically defined variable + """ + sleep(0.1) + inside_variable = does_nothing() + inside_variable.result = "it was an inside job!" + return inside_variable + + dynamic_dynamic = slowly_returns_unpickleable() + executor = CloudpickleProcessPoolExecutor() + fs = executor.submit(dynamic_dynamic.run) + with self.assertRaises(PickleError): + print(fs.result()) # Can't (un)pickle the result + if __name__ == '__main__': unittest.main() From a8ee71e78d04883ae2b4381b9d8ab7e2db4a8eb9 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 11 Jul 2023 15:04:00 -0700 Subject: [PATCH 315/756] Draft a universally-cloud-pickling process pool executor --- pyiron_contrib/executors/executors.py | 59 +++++++- .../executor/test_cloudprocesspoolexecutor.py | 136 ++++++++++++++++++ 2 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 tests/unit/executor/test_cloudprocesspoolexecutor.py diff --git a/pyiron_contrib/executors/executors.py b/pyiron_contrib/executors/executors.py index c85c55897..2a3c7b338 100644 --- a/pyiron_contrib/executors/executors.py +++ b/pyiron_contrib/executors/executors.py @@ -1,4 +1,5 @@ -from concurrent.futures import ProcessPoolExecutor +from concurrent.futures import Future, ProcessPoolExecutor +from concurrent.futures.process import _global_shutdown, _WorkItem, BrokenProcessPool import cloudpickle @@ -27,3 +28,59 @@ def submit(self, fn, /, *args, **kwargs): return super().submit( _apply_cloudpickle, cloudpickle.dumps(fn), *args, **kwargs ) + + +class CloudLoadsFuture(Future): + def result(self, timeout=None): + result = super().result(timeout=timeout) + if isinstance(result, bytes): + result = cloudpickle.loads(result) + return result + + +class CloudPickledCallable: + def __init__(self, fnc: callable): + self.fnc_serial = cloudpickle.dumps(fnc) + + def __call__(self, /, dumped_args, dumped_kwargs): + fnc = cloudpickle.loads(self.fnc_serial) + args = cloudpickle.loads(dumped_args) + kwargs = cloudpickle.loads(dumped_kwargs) + return cloudpickle.dumps(fnc(*args, **kwargs)) + + @classmethod + def dumps(cls, stuff): + return cloudpickle.dumps(stuff) + + +class CloudProcessPoolExecutor(ProcessPoolExecutor): + def submit(self, fn, /, *args, **kwargs): + return self._submit( + CloudPickledCallable(fn), + CloudPickledCallable.dumps(args), + CloudPickledCallable.dumps(kwargs) + ) + + def _submit(self, fn, /, *args, **kwargs): + with self._shutdown_lock: + if self._broken: + raise BrokenProcessPool(self._broken) + if self._shutdown_thread: + raise RuntimeError('cannot schedule new futures after shutdown') + if _global_shutdown: + raise RuntimeError('cannot schedule new futures after ' + 'interpreter shutdown') + + f = CloudLoadsFuture() + w = _WorkItem(f, fn, args, kwargs) + + self._pending_work_items[self._queue_count] = w + self._work_ids.put(self._queue_count) + self._queue_count += 1 + # Wake up queue management thread + self._executor_manager_thread_wakeup.wakeup() + + if self._safe_to_dynamically_spawn_children: + self._adjust_process_count() + self._start_executor_manager_thread() + return f diff --git a/tests/unit/executor/test_cloudprocesspoolexecutor.py b/tests/unit/executor/test_cloudprocesspoolexecutor.py new file mode 100644 index 000000000..eb32128af --- /dev/null +++ b/tests/unit/executor/test_cloudprocesspoolexecutor.py @@ -0,0 +1,136 @@ +from functools import partialmethod +from pickle import PickleError +from time import sleep +import unittest + +from pyiron_contrib.executors.executors import CloudProcessPoolExecutor as CloudpickleProcessPoolExecutor + + +class Foo: + """ + A base class to be dynamically modified for testing CloudpickleProcessPoolExecutor. + """ + def __init__(self, fnc: callable): + self.fnc = fnc + self.result = None + + @property + def run(self): + return self.fnc + + def process_result(self, future): + self.result = future.result() + + +def dynamic_foo(): + """ + A decorator for dynamically modifying the Foo class to test + CloudpickleProcessPoolExecutor. + + Overrides the `fnc` input of `Foo` with the decorated function. + """ + def as_dynamic_foo(fnc: callable): + return type( + "DynamicFoo", + (Foo,), # Define parentage + { + "__init__": partialmethod( + Foo.__init__, + fnc + ) + }, + ) + + return as_dynamic_foo + + +class TestCloudpickleProcessPoolExecutor(unittest.TestCase): + def test_unpickleable_callable(self): + """ + We should be able to use an unpickleable callable -- in this case, a method of + a dynamically defined class. + """ + fortytwo = 42 # No magic numbers; we use it in a couple places so give it a var + + @dynamic_foo() + def slowly_returns_42(): + sleep(0.1) + return fortytwo + + dynamic_42 = slowly_returns_42() # Instantiate the dynamically defined class + self.assertIsInstance( + dynamic_42, + Foo, + msg="Just a sanity check that the test is set up right" + ) + self.assertIsNone( + dynamic_42.result, + msg="Just a sanity check that the test is set up right" + ) + executor = CloudpickleProcessPoolExecutor() + fs = executor.submit(dynamic_42.run) + fs.add_done_callback(dynamic_42.process_result) + self.assertFalse(fs.done(), msg="Should be running on the executor") + self.assertEqual(fortytwo, fs.result(), msg="Future must complete") + self.assertEqual(fortytwo, dynamic_42.result, msg="Callback must get called") + + def test_unpickleable_return(self): + """ + We should be able to use an unpickleable return value -- in this case, a + method of a dynamically defined class. + """ + + @dynamic_foo() + def does_nothing(): + return + + @dynamic_foo() + def slowly_returns_unpickleable(): + """ + Returns a complex, dynamically defined variable + """ + sleep(0.1) + inside_variable = does_nothing() + inside_variable.result = "it was an inside job!" + return inside_variable + + dynamic_dynamic = slowly_returns_unpickleable() + executor = CloudpickleProcessPoolExecutor() + fs = executor.submit(dynamic_dynamic.run) + self.assertIsInstance( + fs.result(), + Foo, + msg="The custom future should be unpickling the result" + ) + self.assertEqual(fs.result().result, "it was an inside job!") + + def test_unpickleable_args(self): + """ + We should be able to use an unpickleable return value -- in this case, a + method of a dynamically defined class. + """ + + @dynamic_foo() + def does_nothing(): + return + + @dynamic_foo() + def slowly_returns_unpickleable(unpickleable_arg): + """ + Returns a complex, dynamically defined variable + """ + sleep(0.1) + unpickleable_arg.result = "input updated" + return unpickleable_arg + + dynamic_dynamic = slowly_returns_unpickleable() + executor = CloudpickleProcessPoolExecutor() + unpicklable_object = does_nothing() + fs = executor.submit(dynamic_dynamic.run, unpicklable_object) + self.assertEqual(fs.result().result, "input updated") + + # TODO: Test timeout + + +if __name__ == '__main__': + unittest.main() From 24d07297fe729e6ceaa046fc4f5dffbba08cb114 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 09:21:26 -0700 Subject: [PATCH 316/756] Expand tests --- .../executor/test_cloudprocesspoolexecutor.py | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/tests/unit/executor/test_cloudprocesspoolexecutor.py b/tests/unit/executor/test_cloudprocesspoolexecutor.py index eb32128af..948a5fd10 100644 --- a/tests/unit/executor/test_cloudprocesspoolexecutor.py +++ b/tests/unit/executor/test_cloudprocesspoolexecutor.py @@ -1,5 +1,5 @@ from functools import partialmethod -from pickle import PickleError +from concurrent.futures import TimeoutError from time import sleep import unittest @@ -129,7 +129,37 @@ def slowly_returns_unpickleable(unpickleable_arg): fs = executor.submit(dynamic_dynamic.run, unpicklable_object) self.assertEqual(fs.result().result, "input updated") - # TODO: Test timeout + def test_exception(self): + @dynamic_foo() + def raise_error(): + raise RuntimeError + + re = raise_error() + executor = CloudpickleProcessPoolExecutor() + fs = executor.submit(re.run) + with self.assertRaises(RuntimeError): + fs.result() + + def test_timeout(self): + fortytwo = 42 + + @dynamic_foo() + def slow(): + sleep(0.1) + return fortytwo + + f = slow() + executor = CloudpickleProcessPoolExecutor() + fs = executor.submit(f.run) + self.assertEqual( + fs.result(timeout=30), + fortytwo, + msg="waiting long enough should get the result" + ) + + with self.assertRaises(TimeoutError): + fs = executor.submit(f.run) + fs.result(timeout=0.0001) if __name__ == '__main__': From 4c78c62746aa1e3f5be0418e666574e2cc460484 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 11:02:02 -0700 Subject: [PATCH 317/756] Accommodate py3.8 --- pyiron_contrib/executors/executors.py | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/pyiron_contrib/executors/executors.py b/pyiron_contrib/executors/executors.py index 2a3c7b338..7d3258c7a 100644 --- a/pyiron_contrib/executors/executors.py +++ b/pyiron_contrib/executors/executors.py @@ -1,5 +1,6 @@ from concurrent.futures import Future, ProcessPoolExecutor from concurrent.futures.process import _global_shutdown, _WorkItem, BrokenProcessPool +from sys import version_info import cloudpickle @@ -62,6 +63,31 @@ def submit(self, fn, /, *args, **kwargs): ) def _submit(self, fn, /, *args, **kwargs): + """ + We override the regular `concurrent.futures.ProcessPoolExecutor` to use our + custom future that unpacks cloudpickled results. + + This approach is simple, but the brute-force nature of it means we manually + accommodate different implementations of `ProcessPoolExecutor` in different + python versions. + """ + if version_info.major != 3: + raise RuntimeError( + f"{self.__class__} is only built for python3, but got " + f"{version_info.major}" + ) + + if version_info.minor == 8: + return self._submit_3_8(fn, *args, **kwargs) + elif version_info.minor >= 9: + return self._submit_3_gt9(fn, *args, **kwargs) + else: + raise RuntimeError( + f"{self.__class__} is only built for python 3.8+, but got " + f"{version_info.major}.{version_info.minor}." + ) + + def _submit_3_gt9(self, fn, /, *args, **kwargs): with self._shutdown_lock: if self._broken: raise BrokenProcessPool(self._broken) @@ -84,3 +110,40 @@ def _submit(self, fn, /, *args, **kwargs): self._adjust_process_count() self._start_executor_manager_thread() return f + + def _submit_3_8(self, *args, **kwargs): + if len(args) >= 2: + self, fn, *args = args + elif not args: + raise TypeError("descriptor 'submit' of 'ProcessPoolExecutor' object " + "needs an argument") + elif 'fn' in kwargs: + fn = kwargs.pop('fn') + self, *args = args + import warnings + warnings.warn("Passing 'fn' as keyword argument is deprecated", + DeprecationWarning, stacklevel=2) + else: + raise TypeError('submit expected at least 1 positional argument, ' + 'got %d' % (len(args) - 1)) + + with self._shutdown_lock: + if self._broken: + raise BrokenProcessPool(self._broken) + if self._shutdown_thread: + raise RuntimeError('cannot schedule new futures after shutdown') + if _global_shutdown: + raise RuntimeError('cannot schedule new futures after ' + 'interpreter shutdown') + + f = CloudLoadsFuture() + w = _WorkItem(f, fn, args, kwargs) + + self._pending_work_items[self._queue_count] = w + self._work_ids.put(self._queue_count) + self._queue_count += 1 + # Wake up queue management thread + self._queue_management_thread_wakeup.wakeup() + + self._start_queue_management_thread() + return f From 7d7354653c31ffe53f0259ee5d1ab2cb027a7dab Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 11:10:03 -0700 Subject: [PATCH 318/756] :bug: fix 3.8 signature --- pyiron_contrib/executors/executors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/executors/executors.py b/pyiron_contrib/executors/executors.py index 7d3258c7a..5d796363e 100644 --- a/pyiron_contrib/executors/executors.py +++ b/pyiron_contrib/executors/executors.py @@ -111,7 +111,7 @@ def _submit_3_gt9(self, fn, /, *args, **kwargs): self._start_executor_manager_thread() return f - def _submit_3_8(self, *args, **kwargs): + def _submit_3_8(*args, **kwargs): if len(args) >= 2: self, fn, *args = args elif not args: From c364a805aecff1d85a8e83d957ac54660d5263fa Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 11:24:54 -0700 Subject: [PATCH 319/756] Document the executor --- pyiron_contrib/executors/executors.py | 81 +++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/pyiron_contrib/executors/executors.py b/pyiron_contrib/executors/executors.py index 5d796363e..167b4059a 100644 --- a/pyiron_contrib/executors/executors.py +++ b/pyiron_contrib/executors/executors.py @@ -55,12 +55,93 @@ def dumps(cls, stuff): class CloudProcessPoolExecutor(ProcessPoolExecutor): + """ + This class wraps `concurrent.futures.ProcessPoolExecutor` such that the submitted + callable, its arguments, and its return value are all pickled using `cloudpickle`. + In this way, the executor extends support to all objects which are cloud-pickleable, + e.g. dynamically defined or decorated classes. + + To accomplish this, the underlying `concurrent.futures.Future` class used is + replaced with our `CloudLoadsFuture`, which is identical except that calls to + `result()` will first try to `cloudpickle.loads` and `bytes` results found. + + Examples: + Consider a class created from a function dynamically with a decorator. + These are not normally pickleable, so in this example we should how this class + allows us to submit a method from such a class, that both takes as an argument + and returns such an unpickleable class. + Actions such as registering callbacks and waiting for results behave just like + normal. + >>> from functools import partialmethod + >>> + >>> from pyiron_contrib.executors.executors import CloudProcessPoolExecutor + >>> + >>> class Foo: + ... ''' + ... A base class to be dynamically modified for testing our executor. + ... ''' + ... def __init__(self, fnc: callable): + ... self.fnc = fnc + ... self.result = None + ... + ... @property + ... def run(self): + ... return self.fnc + ... + ... def process_result(self, future): + ... self.result = future.result() + >>> + >>> + >>> def dynamic_foo(): + ... ''' + ... A decorator for dynamically modifying the Foo class. + ... + ... Overrides the `fnc` input of `Foo` with the decorated function. + ... ''' + ... def as_dynamic_foo(fnc: callable): + ... return type( + ... "DynamicFoo", + ... (Foo,), # Define parentage + ... { + ... "__init__": partialmethod( + ... Foo.__init__, + ... fnc + ... ) + ... }, + ... ) + ... + ... return as_dynamic_foo + >>> + >>> @dynamic_foo() + >>> def UnpicklableCallable(unpicklable_arg): + ... unpicklable_arg.result = "This was an arg" + ... return unpicklable_arg + >>> + >>> + >>> instance = UnpicklableCallable() + >>> arg = UnpicklableCallable() + >>> executor = CloudProcessPoolExecutor() + >>> fs = executor.submit(instance.run, arg) + >>> fs.add_done_callback(instance.process_result) + >>> print(fs.done()) + False + + >>> print(fs.result().__class__.__name__) + DynamicFoo + + >>> print(fs.done()) + True + + >>> print(instance.result.result) + This was an arg + """ def submit(self, fn, /, *args, **kwargs): return self._submit( CloudPickledCallable(fn), CloudPickledCallable.dumps(args), CloudPickledCallable.dumps(kwargs) ) + submit.__doc__ = ProcessPoolExecutor.submit.__doc__ def _submit(self, fn, /, *args, **kwargs): """ From 5130c4fbe8cad9717e2a6ae215d82bd068ebaa2c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 11:26:27 -0700 Subject: [PATCH 320/756] Remove the less extensive executor --- pyiron_contrib/executors/executors.py | 26 ------- tests/unit/executor/test_executors.py | 105 -------------------------- 2 files changed, 131 deletions(-) delete mode 100644 tests/unit/executor/test_executors.py diff --git a/pyiron_contrib/executors/executors.py b/pyiron_contrib/executors/executors.py index 167b4059a..5cd5003d6 100644 --- a/pyiron_contrib/executors/executors.py +++ b/pyiron_contrib/executors/executors.py @@ -5,32 +5,6 @@ import cloudpickle -def _apply_cloudpickle(fn, /, *args, **kwargs): - fn = cloudpickle.loads(fn) - return fn(*args, **kwargs) - - -class CloudpickleProcessPoolExecutor(ProcessPoolExecutor): - """ - This executor behaves like `concurrent.futures.ProcessPoolExecutor`, except that - non-pickleable callables may also be submit (e.g. dynamically defined functions). - - This is accomplished by replacing the `pickle` backend of the - `concurrent.futures.ProcessPoolExecutor` with a backend from `cloudpickle` when - serializing the callable. - - This solution comes from u/mostsquares @ stackoverflow: - https://stackoverflow.com/questions/62830970/submit-dynamically-loaded-functions-to-the-processpoolexecutor - - Note: Arguments and return values must still be regularly pickleable. - """ - - def submit(self, fn, /, *args, **kwargs): - return super().submit( - _apply_cloudpickle, cloudpickle.dumps(fn), *args, **kwargs - ) - - class CloudLoadsFuture(Future): def result(self, timeout=None): result = super().result(timeout=timeout) diff --git a/tests/unit/executor/test_executors.py b/tests/unit/executor/test_executors.py deleted file mode 100644 index f1569018e..000000000 --- a/tests/unit/executor/test_executors.py +++ /dev/null @@ -1,105 +0,0 @@ -from functools import partialmethod -from pickle import PickleError -from time import sleep -import unittest - -from pyiron_contrib.executors.executors import CloudpickleProcessPoolExecutor - - -class Foo: - """ - A base class to be dynamically modified for testing CloudpickleProcessPoolExecutor. - """ - def __init__(self, fnc: callable): - self.fnc = fnc - self.result = None - - @property - def run(self): - return self.fnc - - def process_result(self, future): - self.result = future.result() - - -def dynamic_foo(): - """ - A decorator for dynamically modifying the Foo class to test - CloudpickleProcessPoolExecutor. - - Overrides the `fnc` input of `Foo` with the decorated function. - """ - def as_dynamic_foo(fnc: callable): - return type( - "DynamicFoo", - (Foo,), # Define parentage - { - "__init__": partialmethod( - Foo.__init__, - fnc - ) - }, - ) - - return as_dynamic_foo - - -class TestCloudpickleProcessPoolExecutor(unittest.TestCase): - def test_unpickleable_callable(self): - """ - We should be able to use an unpickleable callable -- in this case, a method of - a dynamically defined class. - """ - fortytwo = 42 # No magic numbers; we use it in a couple places so give it a var - - @dynamic_foo() - def slowly_returns_42(): - sleep(0.1) - return fortytwo - - dynamic_42 = slowly_returns_42() # Instantiate the dynamically defined class - self.assertIsInstance( - dynamic_42, - Foo, - msg="Just a sanity check that the test is set up right" - ) - self.assertIsNone( - dynamic_42.result, - msg="Just a sanity check that the test is set up right" - ) - executor = CloudpickleProcessPoolExecutor() - fs = executor.submit(dynamic_42.run) - fs.add_done_callback(dynamic_42.process_result) - self.assertFalse(fs.done(), msg="Should be running on the executor") - self.assertEqual(fortytwo, fs.result(), msg="Future must complete") - self.assertEqual(fortytwo, dynamic_42.result, msg="Callback must get called") - - def test_unpickleable_return(self): - """ - We should _not_ be able to use an unpickleable return value -- in this case, a - method of a dynamically defined class. - """ - - @dynamic_foo() - def does_nothing(): - return - - @dynamic_foo() - def slowly_returns_unpickleable(): - """ - Returns a complex, dynamically defined variable - """ - sleep(0.1) - inside_variable = does_nothing() - inside_variable.result = "it was an inside job!" - return inside_variable - - dynamic_dynamic = slowly_returns_unpickleable() - executor = CloudpickleProcessPoolExecutor() - fs = executor.submit(dynamic_dynamic.run) - with self.assertRaises(PickleError): - print(fs.result()) # Can't (un)pickle the result - - -if __name__ == '__main__': - unittest.main() From dbe10ed6822cd1f756e9825051e143e55a1f0b67 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 11:27:52 -0700 Subject: [PATCH 321/756] Rename the main executor class --- pyiron_contrib/executors/executors.py | 6 +++--- tests/unit/executor/test_cloudprocesspoolexecutor.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pyiron_contrib/executors/executors.py b/pyiron_contrib/executors/executors.py index 5cd5003d6..975669591 100644 --- a/pyiron_contrib/executors/executors.py +++ b/pyiron_contrib/executors/executors.py @@ -28,7 +28,7 @@ def dumps(cls, stuff): return cloudpickle.dumps(stuff) -class CloudProcessPoolExecutor(ProcessPoolExecutor): +class CloudpickleProcessPoolExecutor(ProcessPoolExecutor): """ This class wraps `concurrent.futures.ProcessPoolExecutor` such that the submitted callable, its arguments, and its return value are all pickled using `cloudpickle`. @@ -48,7 +48,7 @@ class CloudProcessPoolExecutor(ProcessPoolExecutor): normal. >>> from functools import partialmethod >>> - >>> from pyiron_contrib.executors.executors import CloudProcessPoolExecutor + >>> from pyiron_contrib.executors.executors import CloudpickleProcessPoolExecutor >>> >>> class Foo: ... ''' @@ -94,7 +94,7 @@ class CloudProcessPoolExecutor(ProcessPoolExecutor): >>> >>> instance = UnpicklableCallable() >>> arg = UnpicklableCallable() - >>> executor = CloudProcessPoolExecutor() + >>> executor = CloudpickleProcessPoolExecutor() >>> fs = executor.submit(instance.run, arg) >>> fs.add_done_callback(instance.process_result) >>> print(fs.done()) diff --git a/tests/unit/executor/test_cloudprocesspoolexecutor.py b/tests/unit/executor/test_cloudprocesspoolexecutor.py index 948a5fd10..2b8357fb4 100644 --- a/tests/unit/executor/test_cloudprocesspoolexecutor.py +++ b/tests/unit/executor/test_cloudprocesspoolexecutor.py @@ -3,7 +3,7 @@ from time import sleep import unittest -from pyiron_contrib.executors.executors import CloudProcessPoolExecutor as CloudpickleProcessPoolExecutor +from pyiron_contrib.executors.executors import CloudpickleProcessPoolExecutor class Foo: @@ -45,6 +45,7 @@ def as_dynamic_foo(fnc: callable): class TestCloudpickleProcessPoolExecutor(unittest.TestCase): + def test_unpickleable_callable(self): """ We should be able to use an unpickleable callable -- in this case, a method of From e8403fa3bb2441f30fb2121945cd9b13520ce43d Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 11:28:43 -0700 Subject: [PATCH 322/756] Rename the test folder to match the code folder --- tests/unit/{executor => executors}/__init__.py | 0 .../unit/{executor => executors}/test_cloudprocesspoolexecutor.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/unit/{executor => executors}/__init__.py (100%) rename tests/unit/{executor => executors}/test_cloudprocesspoolexecutor.py (100%) diff --git a/tests/unit/executor/__init__.py b/tests/unit/executors/__init__.py similarity index 100% rename from tests/unit/executor/__init__.py rename to tests/unit/executors/__init__.py diff --git a/tests/unit/executor/test_cloudprocesspoolexecutor.py b/tests/unit/executors/test_cloudprocesspoolexecutor.py similarity index 100% rename from tests/unit/executor/test_cloudprocesspoolexecutor.py rename to tests/unit/executors/test_cloudprocesspoolexecutor.py From 32e94a22c2f502711cab8094ea074248c7907bc6 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 11:29:37 -0700 Subject: [PATCH 323/756] Rename module --- .../executors/{executors.py => cloudpickleprocesspool.py} | 0 tests/unit/executors/test_cloudprocesspoolexecutor.py | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) rename pyiron_contrib/executors/{executors.py => cloudpickleprocesspool.py} (100%) diff --git a/pyiron_contrib/executors/executors.py b/pyiron_contrib/executors/cloudpickleprocesspool.py similarity index 100% rename from pyiron_contrib/executors/executors.py rename to pyiron_contrib/executors/cloudpickleprocesspool.py diff --git a/tests/unit/executors/test_cloudprocesspoolexecutor.py b/tests/unit/executors/test_cloudprocesspoolexecutor.py index 2b8357fb4..f8df6fd78 100644 --- a/tests/unit/executors/test_cloudprocesspoolexecutor.py +++ b/tests/unit/executors/test_cloudprocesspoolexecutor.py @@ -3,7 +3,9 @@ from time import sleep import unittest -from pyiron_contrib.executors.executors import CloudpickleProcessPoolExecutor +from pyiron_contrib.executors.cloudpickleprocesspool import ( + CloudpickleProcessPoolExecutor +) class Foo: From dcad8171e6916e312c349ad8e00440199faa8e8c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 11:30:03 -0700 Subject: [PATCH 324/756] Expose the new executor at the top module --- pyiron_contrib/executors/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyiron_contrib/executors/__init__.py b/pyiron_contrib/executors/__init__.py index e69de29bb..7f4ed48a5 100644 --- a/pyiron_contrib/executors/__init__.py +++ b/pyiron_contrib/executors/__init__.py @@ -0,0 +1 @@ +from cloudpickleprocesspool import CloudpickleProcessPoolExecutor \ No newline at end of file From aeb8e2cbbe7776b173c3232a4625081965b953cc Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 11:30:39 -0700 Subject: [PATCH 325/756] Add module doc --- pyiron_contrib/executors/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyiron_contrib/executors/__init__.py b/pyiron_contrib/executors/__init__.py index 7f4ed48a5..01ed3da94 100644 --- a/pyiron_contrib/executors/__init__.py +++ b/pyiron_contrib/executors/__init__.py @@ -1 +1,5 @@ +""" +This module holds customized children of `concurrent.futures.Executor`. +""" + from cloudpickleprocesspool import CloudpickleProcessPoolExecutor \ No newline at end of file From 212d571ea82eae33b770de2c848598877e5e0795 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 11:31:17 -0700 Subject: [PATCH 326/756] Make wrapper private --- pyiron_contrib/executors/cloudpickleprocesspool.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyiron_contrib/executors/cloudpickleprocesspool.py b/pyiron_contrib/executors/cloudpickleprocesspool.py index 975669591..5d72321d4 100644 --- a/pyiron_contrib/executors/cloudpickleprocesspool.py +++ b/pyiron_contrib/executors/cloudpickleprocesspool.py @@ -13,7 +13,7 @@ def result(self, timeout=None): return result -class CloudPickledCallable: +class _CloudPickledCallable: def __init__(self, fnc: callable): self.fnc_serial = cloudpickle.dumps(fnc) @@ -111,9 +111,9 @@ class CloudpickleProcessPoolExecutor(ProcessPoolExecutor): """ def submit(self, fn, /, *args, **kwargs): return self._submit( - CloudPickledCallable(fn), - CloudPickledCallable.dumps(args), - CloudPickledCallable.dumps(kwargs) + _CloudPickledCallable(fn), + _CloudPickledCallable.dumps(args), + _CloudPickledCallable.dumps(kwargs) ) submit.__doc__ = ProcessPoolExecutor.submit.__doc__ From 30c0ccd51952b2e4507d8746ca8d7e417cc0f5ca Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 11:31:56 -0700 Subject: [PATCH 327/756] Update example for new file paths --- pyiron_contrib/executors/cloudpickleprocesspool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/executors/cloudpickleprocesspool.py b/pyiron_contrib/executors/cloudpickleprocesspool.py index 5d72321d4..cfa7c69b7 100644 --- a/pyiron_contrib/executors/cloudpickleprocesspool.py +++ b/pyiron_contrib/executors/cloudpickleprocesspool.py @@ -48,7 +48,7 @@ class CloudpickleProcessPoolExecutor(ProcessPoolExecutor): normal. >>> from functools import partialmethod >>> - >>> from pyiron_contrib.executors.executors import CloudpickleProcessPoolExecutor + >>> from pyiron_contrib.executors import CloudpickleProcessPoolExecutor >>> >>> class Foo: ... ''' From fa281a3a4f799bfff60409cc339a517957bc4a54 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 11:32:33 -0700 Subject: [PATCH 328/756] Rename test file to match module --- ...{test_cloudprocesspoolexecutor.py => test_cloudprocesspool.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/unit/executors/{test_cloudprocesspoolexecutor.py => test_cloudprocesspool.py} (100%) diff --git a/tests/unit/executors/test_cloudprocesspoolexecutor.py b/tests/unit/executors/test_cloudprocesspool.py similarity index 100% rename from tests/unit/executors/test_cloudprocesspoolexecutor.py rename to tests/unit/executors/test_cloudprocesspool.py From b4bccaac6a673e33c4a187e9e4f11f563565c940 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 11:34:05 -0700 Subject: [PATCH 329/756] :bug: fix import path in init --- pyiron_contrib/executors/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/executors/__init__.py b/pyiron_contrib/executors/__init__.py index 01ed3da94..39fe717fa 100644 --- a/pyiron_contrib/executors/__init__.py +++ b/pyiron_contrib/executors/__init__.py @@ -2,4 +2,6 @@ This module holds customized children of `concurrent.futures.Executor`. """ -from cloudpickleprocesspool import CloudpickleProcessPoolExecutor \ No newline at end of file +from pyiron_contrib.executors.cloudpickleprocesspool import ( + CloudpickleProcessPoolExecutor +) \ No newline at end of file From a4303c84c34cabe3147dddad012a10c381ca07a8 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 12 Jul 2023 18:36:37 +0000 Subject: [PATCH 330/756] Format black --- pyiron_contrib/executors/__init__.py | 4 +- .../executors/cloudpickleprocesspool.py | 42 ++++++++++++------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/pyiron_contrib/executors/__init__.py b/pyiron_contrib/executors/__init__.py index 39fe717fa..c9a0659bc 100644 --- a/pyiron_contrib/executors/__init__.py +++ b/pyiron_contrib/executors/__init__.py @@ -3,5 +3,5 @@ """ from pyiron_contrib.executors.cloudpickleprocesspool import ( - CloudpickleProcessPoolExecutor -) \ No newline at end of file + CloudpickleProcessPoolExecutor, +) diff --git a/pyiron_contrib/executors/cloudpickleprocesspool.py b/pyiron_contrib/executors/cloudpickleprocesspool.py index cfa7c69b7..7ef13cb7d 100644 --- a/pyiron_contrib/executors/cloudpickleprocesspool.py +++ b/pyiron_contrib/executors/cloudpickleprocesspool.py @@ -109,12 +109,14 @@ class CloudpickleProcessPoolExecutor(ProcessPoolExecutor): >>> print(instance.result.result) This was an arg """ + def submit(self, fn, /, *args, **kwargs): return self._submit( _CloudPickledCallable(fn), _CloudPickledCallable.dumps(args), - _CloudPickledCallable.dumps(kwargs) + _CloudPickledCallable.dumps(kwargs), ) + submit.__doc__ = ProcessPoolExecutor.submit.__doc__ def _submit(self, fn, /, *args, **kwargs): @@ -147,10 +149,11 @@ def _submit_3_gt9(self, fn, /, *args, **kwargs): if self._broken: raise BrokenProcessPool(self._broken) if self._shutdown_thread: - raise RuntimeError('cannot schedule new futures after shutdown') + raise RuntimeError("cannot schedule new futures after shutdown") if _global_shutdown: - raise RuntimeError('cannot schedule new futures after ' - 'interpreter shutdown') + raise RuntimeError( + "cannot schedule new futures after " "interpreter shutdown" + ) f = CloudLoadsFuture() w = _WorkItem(f, fn, args, kwargs) @@ -170,26 +173,35 @@ def _submit_3_8(*args, **kwargs): if len(args) >= 2: self, fn, *args = args elif not args: - raise TypeError("descriptor 'submit' of 'ProcessPoolExecutor' object " - "needs an argument") - elif 'fn' in kwargs: - fn = kwargs.pop('fn') + raise TypeError( + "descriptor 'submit' of 'ProcessPoolExecutor' object " + "needs an argument" + ) + elif "fn" in kwargs: + fn = kwargs.pop("fn") self, *args = args import warnings - warnings.warn("Passing 'fn' as keyword argument is deprecated", - DeprecationWarning, stacklevel=2) + + warnings.warn( + "Passing 'fn' as keyword argument is deprecated", + DeprecationWarning, + stacklevel=2, + ) else: - raise TypeError('submit expected at least 1 positional argument, ' - 'got %d' % (len(args) - 1)) + raise TypeError( + "submit expected at least 1 positional argument, " + "got %d" % (len(args) - 1) + ) with self._shutdown_lock: if self._broken: raise BrokenProcessPool(self._broken) if self._shutdown_thread: - raise RuntimeError('cannot schedule new futures after shutdown') + raise RuntimeError("cannot schedule new futures after shutdown") if _global_shutdown: - raise RuntimeError('cannot schedule new futures after ' - 'interpreter shutdown') + raise RuntimeError( + "cannot schedule new futures after " "interpreter shutdown" + ) f = CloudLoadsFuture() w = _WorkItem(f, fn, args, kwargs) From b488fa5909833f4a663e5bf8bf509b3092e41046 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 12:50:22 -0700 Subject: [PATCH 331/756] Use the cloudpickle executor --- pyiron_contrib/workflow/node.py | 2 +- pyiron_contrib/workflow/util.py | 29 ----------------------------- 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index bf9f4c3e3..3c4fd5774 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -9,10 +9,10 @@ from concurrent.futures import Future from typing import Optional, TYPE_CHECKING +from pyiron_contrib.executors import CloudpickleProcessPoolExecutor from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Signals, InputSignal, OutputSignal -from pyiron_contrib.workflow.util import CloudpickleProcessPoolExecutor if TYPE_CHECKING: from pyiron_base.jobs.job.extension.server.generic import Server diff --git a/pyiron_contrib/workflow/util.py b/pyiron_contrib/workflow/util.py index 846f8e30c..d80bc7ad3 100644 --- a/pyiron_contrib/workflow/util.py +++ b/pyiron_contrib/workflow/util.py @@ -1,35 +1,6 @@ -from concurrent.futures import ProcessPoolExecutor - -import cloudpickle - - class DotDict(dict): def __getattr__(self, item): return self.__getitem__(item) def __setattr__(self, key, value): self[key] = value - - -def _apply_cloudpickle(fn, /, *args, **kwargs): - fn = cloudpickle.loads(fn) - return fn(*args, **kwargs) - - -class CloudpickleProcessPoolExecutor(ProcessPoolExecutor): - """ - In our workflows, it is common to dynamically create classes from functions using a - decorator; - This makes the underlying function object mismatch with the pickle-findable - "function" (actually a class after wrapping). - The result is that a regular `ProcessPoolExecutor` cannot pickle our node functions. - - An alternative is to force the executor to use pickle under the hood, which _can_ - handle these sort of dynamic objects. - This solution comes from u/mostsquares @ stackoverflow: - https://stackoverflow.com/questions/62830970/submit-dynamically-loaded-functions-to-the-processpoolexecutor - """ - def submit(self, fn, /, *args, **kwargs): - return super().submit( - _apply_cloudpickle, cloudpickle.dumps(fn), *args, **kwargs - ) From 44c9ffbd10a43f1d5e99fe6e58a0ea708b69919b Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 13:06:29 -0700 Subject: [PATCH 332/756] Give access to the creator right from composite To maintain Workflow as a single point of import --- pyiron_contrib/workflow/composite.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 49e7db78e..bacc934e8 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -10,6 +10,7 @@ from typing import Optional from warnings import warn +from pyiron_contrib.executors import CloudpickleProcessPoolExecutor from pyiron_contrib.workflow.node import Node from pyiron_contrib.workflow.function import ( Function, @@ -32,6 +33,12 @@ class _NodeDecoratorAccess: single_value_node = single_value_node +class Creator: + """A shortcut interface for creating non-Node objects from the workflow class.""" + + CloudpickleProcessPoolExecutor = CloudpickleProcessPoolExecutor + + class Composite(Node, ABC): """ A base class for nodes that have internal structure -- i.e. they hold a sub-graph. @@ -78,6 +85,8 @@ class Composite(Node, ABC): wrap_as = _NodeDecoratorAccess # Class method access to decorators # Allows users/devs to easily create new nodes when using children of this class + create = Creator + def __init__( self, label: str, From d23f8d142e4af6ca10314d39a4349b557a1dc5db Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 13:45:37 -0700 Subject: [PATCH 333/756] Test parallel process execution --- tests/unit/workflow/test_workflow.py | 47 ++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index b93708086..db35843ed 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -1,6 +1,8 @@ import unittest from sys import version_info +from time import sleep +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.workflow import Workflow @@ -141,6 +143,51 @@ def test_no_parents(self): # In both cases, we satisfy the spec that workflow's can't have parents wf2.parent = wf + def test_parallel_execution(self): + wf = Workflow("wf") + + @Workflow.wrap_as.single_value_node("five", run_on_updates=False) + def five(sleep_time=0.): + sleep(sleep_time) + return 5 + + @Workflow.wrap_as.single_value_node("sum") + def sum(a, b): + return a + b + + wf.slow = five(sleep_time=1) + wf.fast = five() + wf.sum = sum(a=wf.fast, b=wf.slow) + + wf.slow.executor = wf.create.CloudpickleProcessPoolExecutor() + + wf.slow.run() + wf.fast.run() + self.assertTrue( + wf.slow.running, + msg="The slow node should still be running" + ) + self.assertEqual( + wf.fast.outputs.five.value, + 5, + msg="The slow node should not prohibit the completion of the fast node" + ) + self.assertEqual( + wf.sum.outputs.sum.value, + NotData, + msg="The slow node _should_ hold up the downstream node to which it inputs" + ) + + while wf.slow.future.running(): + sleep(0.1) + + self.assertEqual( + wf.sum.outputs.sum.value, + 5 + 5, + msg="After the slow node completes, its output should be updated as a " + "callback, and downstream nodes should proceed" + ) + if __name__ == '__main__': unittest.main() From 60c34f510072fa681b3f8905d2a779322795de5b Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 13:47:56 -0700 Subject: [PATCH 334/756] Update node docs --- pyiron_contrib/workflow/node.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 3c4fd5774..76e67733e 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -49,6 +49,11 @@ class Node(HasToDict, ABC): Their value is controlled automatically in the defined `run` and `finish_run` methods. + Nodes can be run on the main python process that owns them, or by assigning an + appropriate executor to their `executor` attribute. + In case they are run with an executor, their `future` attribute will be populated + with the resulting future object. + This is an abstract class. Children *must* define how `inputs` and `outputs` are constructed, and what will happen `on_run`. From 68a57c985689495a03b601a78713bfc4018c6a0b Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 15:03:40 -0700 Subject: [PATCH 335/756] Make the dot dict contents show up in tab completion --- pyiron_contrib/workflow/util.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyiron_contrib/workflow/util.py b/pyiron_contrib/workflow/util.py index d80bc7ad3..1ed77339e 100644 --- a/pyiron_contrib/workflow/util.py +++ b/pyiron_contrib/workflow/util.py @@ -4,3 +4,6 @@ def __getattr__(self, item): def __setattr__(self, key, value): self[key] = value + + def __dir__(self): + return set(super().__dir__() + list(self.keys())) From 2aba46300e5d1d2c1d46c1f669a34173cbc964a2 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 15:19:53 -0700 Subject: [PATCH 336/756] Refactor: extract method for batch-updating input --- pyiron_contrib/workflow/function.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 72a426b5b..ef4f907ca 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -340,18 +340,28 @@ def __init__( ) self._verify_that_channels_requiring_update_all_exist() - self.run_on_updates = False - # Temporarily disable running on updates to set all initial values at once + self.run_on_updates = run_on_updates + self._batch_update_input(**kwargs) + + if update_on_instantiation: + self.update() + + def _batch_update_input(self, **kwargs): + """ + Temporarily disable running on updates to set all input values at once. + + Args: + **kwargs: input label - input value (including channels for connection) + pairs. + """ + run_on_updates, self.run_on_updates = self.run_on_updates, False for k, v in kwargs.items(): if k in self.inputs.labels: self.inputs[k] = v - elif k not in self._init_keywords: - warnings.warn(f"The keyword '{k}' was received but not used.") + elif k not in self._input_args.keys(): + warnings.warn(f"The keyword '{k}' was not found among input labels.") self.run_on_updates = run_on_updates # Restore provided value - if update_on_instantiation: - self.update() - @property def _input_args(self): return inspect.signature(self.node_function).parameters From bd20e533fb53b064c7e9ceb3595bcb274e77e574 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 15:38:20 -0700 Subject: [PATCH 337/756] Use __call__ to batch-update inputs --- pyiron_contrib/workflow/function.py | 33 ++++++++++++++++++++++++++-- tests/unit/workflow/test_function.py | 29 ++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index ef4f907ca..386b36a5d 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -56,6 +56,11 @@ class Function(Node): call, such that output data gets pushed after the node stops running but before then `ran` signal fires. + After a node is instantiated, its input can be updated as `*args` and/or `**kwargs` + on call. + This invokes an `update()` call, which can in turn invoke `run()` if + `run_on_updates` is set to `True`. + Args: node_function (callable): The function determining the behaviour of the node. *output_labels (str): A name for each return value of the node function. @@ -507,8 +512,32 @@ def process_run_result(self, function_output): for out, value in zip(self.outputs, function_output): out.update(value) - def __call__(self) -> None: - self.run() + def _convert_input_args_and_kwargs_to_input_kwargs(self, *args, **kwargs): + reverse_keys = list(self._input_args.keys())[::-1] + if len(args) > len(reverse_keys): + raise ValueError( + f"Received {len(args)} positional arguments, but the node {self.label}" + f"only accepts {len(reverse_keys)} inputs." + ) + + positional_keywords = reverse_keys[-len(args):] + if len(set(positional_keywords).intersection(kwargs.keys())) > 0: + raise ValueError( + f"Cannot use {set(positional_keywords).intersection(kwargs.keys())} " + f"as both positional _and_ keyword arguments" + ) + + for arg in args: + key = positional_keywords.pop() + kwargs[key] = arg + + return kwargs + + def __call__(self, *args, **kwargs) -> None: + kwargs = self._convert_input_args_and_kwargs_to_input_kwargs(*args, **kwargs) + self._batch_update_input(**kwargs) + if self.run_on_updates: + self.run() def to_dict(self): return { diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 5d2d15869..a088b35e2 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -233,6 +233,35 @@ def with_messed_self(x: float, self) -> float: self.assertEqual(len(warning_list), 1) + def test_call(self): + node = Function(no_default, "output", run_on_updates=False) + + with self.assertRaises(ValueError): + # More input args than there are input channels + node(1, 2, 3) + + with self.assertRaises(ValueError): + # Using input as an arg _and_ a kwarg + node(1, y=2, x=3) + + node(1, y=2) + self.assertEqual( + node.inputs.x.value, 1, msg="__call__ should accept args to update input" + ) + self.assertEqual( + node.inputs.y.value, 2, msg="__call__ should accept kwargs to update input" + ) + self.assertEqual( + node.outputs.output.value, NotData, msg="__call__ should not run things" + ) + node.run_on_updates = True + node(3) # Implicitly test partial update + self.assertEqual( + no_default(3, 2), + node.outputs.output.value, + msg="__call__ should invoke update s.t. run gets called if run_on_updates" + ) + @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestSlow(unittest.TestCase): From 0887a9bd6169e33740cd01097a261e25bc39e144 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 13 Jul 2023 11:28:22 -0700 Subject: [PATCH 338/756] Copy in Sam's output parser from pyiron_contrib issue #717 And use his examples as tests --- pyiron_contrib/workflow/output_parser.py | 65 +++++++++++++++++++++++ tests/unit/workflow/test_output_parser.py | 56 +++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 pyiron_contrib/workflow/output_parser.py create mode 100644 tests/unit/workflow/test_output_parser.py diff --git a/pyiron_contrib/workflow/output_parser.py b/pyiron_contrib/workflow/output_parser.py new file mode 100644 index 000000000..e5405c239 --- /dev/null +++ b/pyiron_contrib/workflow/output_parser.py @@ -0,0 +1,65 @@ +""" +Inspects code to automatically parse return values as strings +""" + +import ast +import inspect +import re + + +def _remove_spaces_until_character(string): + pattern = r'\s+(?=\s)' + modified_string = re.sub(pattern, '', string) + return modified_string + + +class ParseOutput: + def __init__(self, function): + self._func = function + self._source = None + + @property + def func(self): + return self._func + + @property + def node_return(self): + tree = ast.parse(inspect.getsource(self.func)) + for node in ast.walk(tree): + if isinstance(node, ast.Return): + return node + + @property + def source(self): + if self._source is None: + self._source = [ + line.rsplit("\n", 1)[0] for line in inspect.getsourcelines(self.func)[0] + ] + return self._source + + def get_string(self, node): + string = "" + for ll in range(node.lineno - 1, node.end_lineno): + if ll == node.lineno - 1 == node.end_lineno - 1: + string += _remove_spaces_until_character( + self.source[ll][node.col_offset:node.end_col_offset] + ) + elif ll == node.lineno - 1: + string += _remove_spaces_until_character( + self.source[ll][node.col_offset:] + ) + elif ll == node.end_lineno - 1: + string += _remove_spaces_until_character( + self.source[ll][:node.end_col_offset] + ) + else: + string += _remove_spaces_until_character(self.source[ll]) + return string + + @property + def output(self): + if self.node_return is None: + return + if isinstance(self.node_return.value, ast.Tuple): + return [self.get_string(s) for s in self.node_return.value.dims] + return [self.get_string(self.node_return.value)] \ No newline at end of file diff --git a/tests/unit/workflow/test_output_parser.py b/tests/unit/workflow/test_output_parser.py new file mode 100644 index 000000000..87c44be45 --- /dev/null +++ b/tests/unit/workflow/test_output_parser.py @@ -0,0 +1,56 @@ +from sys import version_info +import unittest + +import numpy as np + +from pyiron_contrib.workflow.output_parser import ParseOutput + + +@unittest.skipUnless( + version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+" +) +class TestParseOutput(unittest.TestCase): + def test_parsing(self): + with self.subTest("Single return"): + def identity(x): + return x + self.assertListEqual(ParseOutput(identity).output, ["x"]) + + with self.subTest("Expression return"): + def add(x, y): + return x + y + self.assertListEqual(ParseOutput(add).output, ["x + y"]) + + with self.subTest("Weird whitespace"): + def add(x, y): + return x + y + self.assertListEqual(ParseOutput(add).output, ["x + y"]) + + with self.subTest("Multiple expressions"): + def add_and_subtract(x, y): + return x + y, x - y + self.assertListEqual(ParseOutput(add).output, ["x + y", "x - y"]) + + with self.subTest("Best-practice (well-named return vars)"): + def md(job): + temperature = job.output.temperature + energy = job.output.energy + return temperature, energy + self.assertListEqual(ParseOutput(md).output, ["temperature", "energy"]) + + with self.subTest("Function call returns"): + def function_return(i, j): + return ( + np.arange( + i, dtype=int + ), + np.shape(i, j) + ) + self.assertListEqual( + ParseOutput(function_return).output, + ["np.arange( i, dtype=int )", "np.shape(i, j)"] + ) + + +if __name__ == '__main__': + unittest.main() From a9fd2d71a03110ee3db48efc789e5a9a9559e282 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 13 Jul 2023 12:06:16 -0700 Subject: [PATCH 339/756] :bug: fix typo calling the wrong function --- tests/unit/workflow/test_output_parser.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/unit/workflow/test_output_parser.py b/tests/unit/workflow/test_output_parser.py index 87c44be45..99d87ddd1 100644 --- a/tests/unit/workflow/test_output_parser.py +++ b/tests/unit/workflow/test_output_parser.py @@ -29,7 +29,10 @@ def add(x, y): with self.subTest("Multiple expressions"): def add_and_subtract(x, y): return x + y, x - y - self.assertListEqual(ParseOutput(add).output, ["x + y", "x - y"]) + self.assertListEqual( + ParseOutput(add_and_subtract).output, + ["x + y", "x - y"] + ) with self.subTest("Best-practice (well-named return vars)"): def md(job): From 2d653406e0e7ed8670fe49078c4e1e12e6796870 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 13 Jul 2023 12:06:34 -0700 Subject: [PATCH 340/756] Handle functions that have non-zero indentation --- pyiron_contrib/workflow/output_parser.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pyiron_contrib/workflow/output_parser.py b/pyiron_contrib/workflow/output_parser.py index e5405c239..3caa91cf2 100644 --- a/pyiron_contrib/workflow/output_parser.py +++ b/pyiron_contrib/workflow/output_parser.py @@ -5,6 +5,7 @@ import ast import inspect import re +from textwrap import dedent def _remove_spaces_until_character(string): @@ -22,9 +23,13 @@ def __init__(self, function): def func(self): return self._func + @property + def dedented_source_string(self): + return dedent(inspect.getsource(self.func)) + @property def node_return(self): - tree = ast.parse(inspect.getsource(self.func)) + tree = ast.parse(self.dedented_source_string) for node in ast.walk(tree): if isinstance(node, ast.Return): return node @@ -32,9 +37,7 @@ def node_return(self): @property def source(self): if self._source is None: - self._source = [ - line.rsplit("\n", 1)[0] for line in inspect.getsourcelines(self.func)[0] - ] + self._source = self.dedented_source_string.split("\n")[:-1] return self._source def get_string(self, node): From ea605de1cbd9940b71f80cfe710c903aa67d91c7 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 13 Jul 2023 12:11:09 -0700 Subject: [PATCH 341/756] Add a test for methods --- tests/unit/workflow/test_output_parser.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/unit/workflow/test_output_parser.py b/tests/unit/workflow/test_output_parser.py index 99d87ddd1..b10994ec6 100644 --- a/tests/unit/workflow/test_output_parser.py +++ b/tests/unit/workflow/test_output_parser.py @@ -54,6 +54,12 @@ def function_return(i, j): ["np.arange( i, dtype=int )", "np.shape(i, j)"] ) + with self.subTest("Methods too"): + class Foo: + def add(self, x, y): + return x + y + self.assertListEqual(ParseOutput(Foo.add).output, ["x + y"]) + if __name__ == '__main__': unittest.main() From b472e3fd990883c9fefdc85ca59626e8ac2c5dde Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 13 Jul 2023 12:21:21 -0700 Subject: [PATCH 342/756] Control flow of output attribute more tightly --- pyiron_contrib/workflow/output_parser.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/output_parser.py b/pyiron_contrib/workflow/output_parser.py index 3caa91cf2..e4df57929 100644 --- a/pyiron_contrib/workflow/output_parser.py +++ b/pyiron_contrib/workflow/output_parser.py @@ -63,6 +63,7 @@ def get_string(self, node): def output(self): if self.node_return is None: return - if isinstance(self.node_return.value, ast.Tuple): + elif isinstance(self.node_return.value, ast.Tuple): return [self.get_string(s) for s in self.node_return.value.dims] - return [self.get_string(self.node_return.value)] \ No newline at end of file + else: + return [self.get_string(self.node_return.value)] From 6a1184136ebca2897e5a5e462d8f5c9e3d06797a Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 13 Jul 2023 12:32:28 -0700 Subject: [PATCH 343/756] Handle and test None returns --- pyiron_contrib/workflow/output_parser.py | 8 ++++++-- tests/unit/workflow/test_output_parser.py | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/output_parser.py b/pyiron_contrib/workflow/output_parser.py index e4df57929..2dea2249e 100644 --- a/pyiron_contrib/workflow/output_parser.py +++ b/pyiron_contrib/workflow/output_parser.py @@ -61,9 +61,13 @@ def get_string(self, node): @property def output(self): - if self.node_return is None: + if self.node_return is None or self.node_return.value is None: return elif isinstance(self.node_return.value, ast.Tuple): return [self.get_string(s) for s in self.node_return.value.dims] else: - return [self.get_string(self.node_return.value)] + out = [self.get_string(self.node_return.value)] + if out == ["None"]: + return + else: + return out diff --git a/tests/unit/workflow/test_output_parser.py b/tests/unit/workflow/test_output_parser.py index b10994ec6..f83c96012 100644 --- a/tests/unit/workflow/test_output_parser.py +++ b/tests/unit/workflow/test_output_parser.py @@ -60,6 +60,22 @@ def add(self, x, y): return x + y self.assertListEqual(ParseOutput(Foo.add).output, ["x + y"]) + def test_void(self): + with self.subTest("No return"): + def no_return(): + pass + self.assertIsNone(ParseOutput(no_return).output) + + with self.subTest("Empty return"): + def empty_return(): + return + self.assertIsNone(ParseOutput(empty_return).output) + + with self.subTest("Return None explicitly"): + def none_return(): + return None + self.assertIsNone(ParseOutput(none_return).output) + if __name__ == '__main__': unittest.main() From 31abcfb97bbb9ba0ef89581c4e7b30896e3d585e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 13 Jul 2023 12:59:11 -0700 Subject: [PATCH 344/756] Raise an exception when multiple return values are encountered --- pyiron_contrib/workflow/output_parser.py | 14 +++++++++++++- tests/unit/workflow/test_output_parser.py | 9 +++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/output_parser.py b/pyiron_contrib/workflow/output_parser.py index 2dea2249e..1a166dbd1 100644 --- a/pyiron_contrib/workflow/output_parser.py +++ b/pyiron_contrib/workflow/output_parser.py @@ -30,9 +30,21 @@ def dedented_source_string(self): @property def node_return(self): tree = ast.parse(self.dedented_source_string) + returns = [] for node in ast.walk(tree): if isinstance(node, ast.Return): - return node + returns.append(node) + + if len(returns) > 1: + raise ValueError( + f"{self.__class__.__name__} can only parse callables with at most one " + f"return value, but ast.walk found {len(returns)}." + ) + + try: + return returns[0] + except IndexError: + return None @property def source(self): diff --git a/tests/unit/workflow/test_output_parser.py b/tests/unit/workflow/test_output_parser.py index f83c96012..ad1808cb4 100644 --- a/tests/unit/workflow/test_output_parser.py +++ b/tests/unit/workflow/test_output_parser.py @@ -76,6 +76,15 @@ def none_return(): return None self.assertIsNone(ParseOutput(none_return).output) + def test_multiple_branches(self): + def bifurcating(x): + if x > 5: + return True + else: + return False + with self.assertRaises(ValueError): + ParseOutput(bifurcating).output + if __name__ == '__main__': unittest.main() From 1ed3a6e2bde2f8b5c6280af9d46aa8a65537e6da Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 13 Jul 2023 13:00:35 -0700 Subject: [PATCH 345/756] Parse on instantiation --- pyiron_contrib/workflow/output_parser.py | 4 ++++ tests/unit/workflow/test_output_parser.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/output_parser.py b/pyiron_contrib/workflow/output_parser.py index 1a166dbd1..095471898 100644 --- a/pyiron_contrib/workflow/output_parser.py +++ b/pyiron_contrib/workflow/output_parser.py @@ -18,6 +18,7 @@ class ParseOutput: def __init__(self, function): self._func = function self._source = None + self._output = self.get_parsed_output() @property def func(self): @@ -73,6 +74,9 @@ def get_string(self, node): @property def output(self): + return self._output + + def get_parsed_output(self): if self.node_return is None or self.node_return.value is None: return elif isinstance(self.node_return.value, ast.Tuple): diff --git a/tests/unit/workflow/test_output_parser.py b/tests/unit/workflow/test_output_parser.py index ad1808cb4..e8b5f066f 100644 --- a/tests/unit/workflow/test_output_parser.py +++ b/tests/unit/workflow/test_output_parser.py @@ -83,7 +83,7 @@ def bifurcating(x): else: return False with self.assertRaises(ValueError): - ParseOutput(bifurcating).output + ParseOutput(bifurcating) if __name__ == '__main__': From 1745eef7807dd2d9c0cf2b3684538346105b1382 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 13 Jul 2023 13:04:34 -0700 Subject: [PATCH 346/756] Add docstring --- pyiron_contrib/workflow/output_parser.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyiron_contrib/workflow/output_parser.py b/pyiron_contrib/workflow/output_parser.py index 095471898..36342cc92 100644 --- a/pyiron_contrib/workflow/output_parser.py +++ b/pyiron_contrib/workflow/output_parser.py @@ -15,6 +15,15 @@ def _remove_spaces_until_character(string): class ParseOutput: + """ + Given a function with at most one `return` expression, inspects the source code and + parses a list of strings containing the returned values. + If the function returns `None`, the parsed value is also `None`. + This parsed value is evaluated at instantiation and stored in the `output` + attribute. + In case more than one `return` expression is found, a `ValueError` is raised. + """ + def __init__(self, function): self._func = function self._source = None From d369a8614314ea8498154d227f25c3c29ca9827d Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 13 Jul 2023 14:31:16 -0700 Subject: [PATCH 347/756] _Allow_ not passing output labels --- pyiron_contrib/workflow/function.py | 3 ++- tests/unit/workflow/test_function.py | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 72a426b5b..c8997b6a6 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -9,6 +9,7 @@ from pyiron_contrib.workflow.has_channel import HasChannel from pyiron_contrib.workflow.io import Inputs, Outputs, Signals from pyiron_contrib.workflow.node import Node +from pyiron_contrib.workflow.output_parser import ParseOutput if TYPE_CHECKING: from pyiron_contrib.workflow.composite import Composite @@ -322,7 +323,7 @@ def __init__( # **kwargs, ) if len(output_labels) == 0: - raise ValueError("Nodes must have at least one output label.") + output_labels = ParseOutput(node_function).output self.node_function = node_function diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 5d2d15869..4c7ddc502 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -46,10 +46,7 @@ def test_defaults(self): ) def test_failure_without_output_labels(self): - with self.assertRaises( - ValueError, - msg="Instantiated nodes should demand at least one output label" - ): + with self.subTest("Automatically scrape output labels"): Function(plus_one) def test_instantiation_update(self): From bc203e50d0c5f2f1de5b5727f71706ab84a0ab84 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 13 Jul 2023 14:45:41 -0700 Subject: [PATCH 348/756] Modify docstring to change spec --- pyiron_contrib/workflow/function.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index c8997b6a6..d72b02836 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -59,7 +59,6 @@ class Function(Node): Args: node_function (callable): The function determining the behaviour of the node. - *output_labels (str): A name for each return value of the node function. label (str): The node's label. (Defaults to the node function's name.) run_on_updates (bool): Whether to run when you are updated and all your input is ready. (Default is True). @@ -71,6 +70,15 @@ class Function(Node): called. This can be used to create sets of input data _all_ of which must be updated before the node is ready to produce output again. (Default is None, which makes the list empty.) + output_labels (Optional[str | list[str] | tuple[str]]): A name for each return + value of the node function OR a single label. (Default is None, which + scrapes output labels automatically from the source code of the wrapped + function.) This can be useful when returned values are not well named, e.g. + to make the output channel dot-accessible if it would otherwise have a label + that requires item-string-based access. Additionally, specifying a _single_ + label for a wrapped function that returns a tuple of values ensures that a + _single_ output channel (holding the tuple) is created, instead of one + channel for each return value. **kwargs: Any additional keyword arguments whose keyword matches the label of an input channel will have their value assigned to that channel. From 716c1df6adffe750e15a4dc12bc1dc453fd70617 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 13 Jul 2023 15:13:19 -0700 Subject: [PATCH 349/756] Make output labels optional Breaks other tests, demos, and docs still --- pyiron_contrib/workflow/function.py | 29 +++++++++++-- tests/unit/workflow/test_function.py | 63 ++++++++++++++++++---------- 2 files changed, 66 insertions(+), 26 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index d72b02836..8d24f4722 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -317,12 +317,12 @@ class Function(Node): def __init__( self, node_function: callable, - *output_labels: str, label: Optional[str] = None, run_on_updates: bool = True, update_on_instantiation: bool = True, channels_requiring_update_after_run: Optional[list[str]] = None, parent: Optional[Composite] = None, + output_labels: Optional[str | list[str] | tuple[str]] = None, **kwargs, ): super().__init__( @@ -330,14 +330,12 @@ def __init__( parent=parent, # **kwargs, ) - if len(output_labels) == 0: - output_labels = ParseOutput(node_function).output self.node_function = node_function self._inputs = None self._outputs = None - self._output_labels = output_labels + self._output_labels = self._get_output_labels(output_labels) # TODO: Parse output labels from the node function in case output_labels is None self.signals = self._build_signal_channels() @@ -361,6 +359,29 @@ def __init__( if update_on_instantiation: self.update() + def _get_output_labels(self, output_labels: str | list[str] | tuple[str] | None): + """ + Explicitly passed output labels can be used to rename awkward parsed labels, or + to force the creation of a _single_ output channel when wrapped functions return + a tuple of values. + """ + parsed_labels = ParseOutput(self.node_function).output + if output_labels is None: + return parsed_labels + else: + if isinstance(output_labels, str): + output_labels = (output_labels,) + + if len(output_labels) != 1 and len(output_labels) != len(parsed_labels): + raise ValueError( + f"When output labels are explicitly provided they must either be a " + f"_single_ label, or match the length of the parsed labels. In " + f"this case, {output_labels} were received while {parsed_labels} " + f"were parsed." + ) + + return output_labels + @property def _input_args(self): return inspect.signature(self.node_function).parameters diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 4c7ddc502..8d684cf48 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -15,24 +15,29 @@ def throw_error(x: Optional[int] = None): def plus_one(x=1) -> Union[int, float]: - return x + 1 + y = x + 1 + return y def no_default(x, y): return x + y + 1 +def returns_multiple(x, y): + return x, y, x + y + + @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestFunction(unittest.TestCase): def test_defaults(self): - with_defaults = Function(plus_one, "y") + with_defaults = Function(plus_one) self.assertEqual( with_defaults.inputs.x.value, 1, msg=f"Expected to get the default provided in the underlying function but " f"got {with_defaults.inputs.x.value}", ) - without_defaults = Function(no_default, "sum_plus_one") + without_defaults = Function(no_default) self.assertIs( without_defaults.inputs.x.value, NotData, @@ -45,14 +50,26 @@ def test_defaults(self): "defaults, the node should not be ready!" ) - def test_failure_without_output_labels(self): + def test_label_choices(self): with self.subTest("Automatically scrape output labels"): - Function(plus_one) + n = Function(plus_one) + self.assertListEqual(n.outputs.labels, ["y"]) + + with self.subTest("Allow overriding them"): + n = Function(no_default, output_labels=("sum_plus_one",)) + self.assertListEqual(n.outputs.labels, ["sum_plus_one"]) + + with self.subTest("Allow forcing _one_ output channel"): + n = Function(returns_multiple, output_labels="its_a_tuple") + self.assertListEqual(n.outputs.labels, ["its_a_tuple"]) + + with self.subTest("Force matching lengths"): + with self.assertRaises(ValueError): + Function(returns_multiple, output_labels=["one", "two"]) def test_instantiation_update(self): no_update = Function( plus_one, - "y", run_on_updates=True, update_on_instantiation=False ) @@ -65,13 +82,12 @@ def test_instantiation_update(self): update = Function( plus_one, - "y", run_on_updates=True, update_on_instantiation=True ) self.assertEqual(2, update.outputs.y.value) - default = Function(plus_one, "y") + default = Function(plus_one) self.assertEqual( 2, default.outputs.y.value, @@ -80,19 +96,19 @@ def test_instantiation_update(self): ) with self.assertRaises(TypeError): - run_without_value = Function(no_default, "z") + run_without_value = Function(no_default) run_without_value.run() # None + None + 1 -> error with self.assertRaises(TypeError): - run_without_value = Function(no_default, "z", x=1) + run_without_value = Function(no_default, x=1) run_without_value.run() # 1 + None + 1 -> error - deferred_update = Function(no_default, "z", x=1, y=1) + deferred_update = Function(no_default, x=1, y=1) deferred_update.run() self.assertEqual( - deferred_update.outputs.z.value, + deferred_update.outputs["x + y + 1"].value, 3, msg="By default, all initial values should be parsed before triggering " "an update" @@ -117,35 +133,38 @@ def test_automatic_updates(self): node.inputs.x.update(1) def test_signals(self): - @function_node("y") + @function_node() def linear(x): return x - @function_node("z") + @function_node() def times_two(y): return 2 * y l = linear(x=1) t2 = times_two( - update_on_instantiation=False, run_automatically=False, y=l.outputs.y + update_on_instantiation=False, + run_automatically=False, + output_labels=["double"], + y=l.outputs.x ) self.assertIs( - t2.outputs.z.value, + t2.outputs.double.value, NotData, msg=f"Without updates, expected the output to be {NotData} but got " - f"{t2.outputs.z.value}" + f"{t2.outputs.double.value}" ) # Nodes should _all_ have the run and ran signals t2.signals.input.run = l.signals.output.ran l.run() self.assertEqual( - t2.outputs.z.value, 2, + t2.outputs.double.value, 2, msg="Running the upstream node should trigger a run here" ) def test_statuses(self): - n = Function(plus_one, "p1", run_on_updates=False) + n = Function(plus_one, run_on_updates=False) self.assertTrue(n.ready) self.assertFalse(n.running) self.assertFalse(n.failed) @@ -197,7 +216,7 @@ def with_self(self, x: float) -> float: self.some_counter = 1 return x + 0.1 - node = Function(with_self, "output") + node = Function(with_self, output_labels="output") self.assertTrue( "x" in node.inputs.labels, msg=f"Expected to find function input 'x' in the node input but got " @@ -225,7 +244,7 @@ def with_messed_self(x: float, self) -> float: return x + 0.1 with warnings.catch_warnings(record=True) as warning_list: - node = Function(with_messed_self, "output") + node = Function(with_messed_self) self.assertTrue("self" in node.inputs.labels) self.assertEqual(len(warning_list), 1) @@ -234,7 +253,7 @@ def with_messed_self(x: float, self) -> float: @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestSlow(unittest.TestCase): def test_instantiation(self): - slow = Slow(plus_one, "y") + slow = Slow(plus_one) self.assertIs( slow.outputs.y.value, NotData, From 40cf7e5394fbbaad8d3927da44d27c18bde9aa50 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 13 Jul 2023 15:16:20 -0700 Subject: [PATCH 350/756] Fix the rest of the function tests --- tests/unit/workflow/test_function.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 8d684cf48..96a9b1b20 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -277,10 +277,10 @@ def test_instantiation(self): @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestSingleValue(unittest.TestCase): def test_instantiation(self): - has_defaults_and_one_return = SingleValue(plus_one, "y") + has_defaults_and_one_return = SingleValue(plus_one) with self.assertRaises(ValueError): - too_many_labels = SingleValue(plus_one, "z", "excess_label") + too_many_labels = SingleValue(plus_one, output_labels=["z", "excess_label"]) def test_item_and_attribute_access(self): class Foo: @@ -296,7 +296,7 @@ def __getitem__(self, item): def returns_foo() -> Foo: return Foo() - svn = SingleValue(returns_foo, "foo") + svn = SingleValue(returns_foo, output_labels="foo") self.assertEqual( svn.some_attribute, @@ -326,14 +326,14 @@ def returns_foo() -> Foo: ) def test_repr(self): - svn = SingleValue(plus_one, "y") + svn = SingleValue(plus_one) self.assertEqual( svn.__repr__(), svn.outputs.y.value.__repr__(), msg="SingleValueNodes should have their output as their representation" ) def test_str(self): - svn = SingleValue(plus_one, "y") + svn = SingleValue(plus_one) self.assertTrue( str(svn).endswith(str(svn.single_value)), msg="SingleValueNodes should have their output as a string in their string " @@ -342,8 +342,8 @@ def test_str(self): ) def test_easy_output_connection(self): - svn = SingleValue(plus_one, "y") - regular = Function(plus_one, "y") + svn = SingleValue(plus_one) + regular = Function(plus_one) regular.inputs.x = svn @@ -360,7 +360,7 @@ def test_easy_output_connection(self): "case default->plus_one->plus_one = 1 + 1 +1 = 3" ) - at_instantiation = Function(plus_one, "y", x=svn) + at_instantiation = Function(plus_one, x=svn) self.assertIn( svn.outputs.y, at_instantiation.inputs.x.connections, msg="The parsing of SingleValue output as a connection should also work" @@ -368,7 +368,7 @@ def test_easy_output_connection(self): ) def test_channels_requiring_update_after_run(self): - @single_value_node("sum") + @single_value_node(output_labels="sum") def my_node(x: int = 0, y: int = 0, z: int = 0): return x + y + z @@ -420,7 +420,7 @@ def my_node(x: int = 0, y: int = 0, z: int = 0): ) def test_working_directory(self): - n_f = Function(plus_one, "output") + n_f = Function(plus_one) self.assertTrue(n_f._working_directory is None) self.assertIsInstance(n_f.working_directory, DirectoryObject) self.assertTrue(str(n_f.working_directory.path).endswith(n_f.label)) From 6c9c4894c0223ef7d73e0f4442023b98a54a816e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 13 Jul 2023 15:34:24 -0700 Subject: [PATCH 351/756] Propagate changes to the rest of code and tests No docs or demo yet --- pyiron_contrib/workflow/function.py | 28 ++++---- .../workflow/node_library/atomistics.py | 64 ++++++++++--------- .../workflow/node_library/standard.py | 2 +- tests/unit/workflow/test_node_package.py | 9 +-- tests/unit/workflow/test_workflow.py | 47 +++++++------- 5 files changed, 76 insertions(+), 74 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 8d24f4722..1dda66513 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -553,20 +553,20 @@ class Slow(Function): def __init__( self, node_function: callable, - *output_labels: str, label: Optional[str] = None, run_on_updates=False, update_on_instantiation=False, parent: Optional[Workflow] = None, + output_labels: Optional[str | list[str] | tuple[str]] = None, **kwargs, ): super().__init__( node_function, - *output_labels, label=label, run_on_updates=run_on_updates, update_on_instantiation=update_on_instantiation, parent=parent, + output_labels=output_labels, **kwargs, ) @@ -581,31 +581,31 @@ class SingleValue(Function, HasChannel): def __init__( self, node_function: callable, - *output_labels: str, label: Optional[str] = None, run_on_updates=True, update_on_instantiation=True, parent: Optional[Workflow] = None, + output_labels: Optional[str | list[str] | tuple[str]] = None, **kwargs, ): - self.ensure_there_is_only_one_return_value(output_labels) super().__init__( node_function, - *output_labels, label=label, run_on_updates=run_on_updates, update_on_instantiation=update_on_instantiation, parent=parent, + output_labels=output_labels, **kwargs, ) - @classmethod - def ensure_there_is_only_one_return_value(cls, output_labels): + def _get_output_labels(self, output_labels: str | list[str] | tuple[str] | None): + output_labels = super()._get_output_labels(output_labels) if len(output_labels) > 1: raise ValueError( - f"{cls.__name__} must only have a single return value, but got " - f"multiple output labels: {output_labels}" + f"{self.__class__.__name__} must only have a single return value, but " + f"got multiple output labels: {output_labels}" ) + return output_labels @property def single_value(self): @@ -631,7 +631,7 @@ def __str__(self): ) -def function_node(*output_labels: str, **node_class_kwargs): +def function_node(**node_class_kwargs): """ A decorator for dynamically creating node classes from functions. @@ -650,7 +650,6 @@ def as_node(node_function: callable): "__init__": partialmethod( Function.__init__, node_function, - *output_labels, **node_class_kwargs, ) }, @@ -659,7 +658,7 @@ def as_node(node_function: callable): return as_node -def slow_node(*output_labels: str, **node_class_kwargs): +def slow_node(**node_class_kwargs): """ A decorator for dynamically creating slow node classes from functions. @@ -676,7 +675,6 @@ def as_slow_node(node_function: callable): "__init__": partialmethod( Slow.__init__, node_function, - *output_labels, **node_class_kwargs, ) }, @@ -685,7 +683,7 @@ def as_slow_node(node_function: callable): return as_slow_node -def single_value_node(*output_labels: str, **node_class_kwargs): +def single_value_node(**node_class_kwargs): """ A decorator for dynamically creating fast node classes from functions. @@ -693,7 +691,6 @@ def single_value_node(*output_labels: str, **node_class_kwargs): """ def as_single_value_node(node_function: callable): - SingleValue.ensure_there_is_only_one_return_value(output_labels) return type( node_function.__name__.title().replace("_", ""), # fnc_name to CamelCase (SingleValue,), # Define parentage @@ -701,7 +698,6 @@ def as_single_value_node(node_function: callable): "__init__": partialmethod( SingleValue.__init__, node_function, - *output_labels, **node_class_kwargs, ) }, diff --git a/pyiron_contrib/workflow/node_library/atomistics.py b/pyiron_contrib/workflow/node_library/atomistics.py index 1da3bdac6..fd304570e 100644 --- a/pyiron_contrib/workflow/node_library/atomistics.py +++ b/pyiron_contrib/workflow/node_library/atomistics.py @@ -10,12 +10,12 @@ from pyiron_contrib.workflow.function import single_value_node, slow_node -@single_value_node("structure") +@single_value_node(output_labels="structure") def bulk_structure(element: str = "Fe", cubic: bool = False, repeat: int = 1) -> Atoms: return _StructureFactory().bulk(element, cubic=cubic).repeat(repeat) -@single_value_node("job") +@single_value_node(output_labels="job") def lammps(structure: Optional[Atoms] = None) -> LammpsJob: pr = Project(".") job = pr.atomistics.job.Lammps("NOTAREALNAME") @@ -82,20 +82,22 @@ def _run_and_remove_job(job, modifier: Optional[callable] = None, **modifier_kwa @slow_node( - "cells", - "displacements", - "energy_pot", - "energy_tot", - "force_max", - "forces", - "indices", - "positions", - "pressures", - "steps", - "temperature", - "total_displacements", - "unwrapped_positions", - "volume", + output_labels=[ + "cells", + "displacements", + "energy_pot", + "energy_tot", + "force_max", + "forces", + "indices", + "positions", + "pressures", + "steps", + "temperature", + "total_displacements", + "unwrapped_positions", + "volume", + ] ) def calc_static( job: AtomisticGenericJob, @@ -104,20 +106,22 @@ def calc_static( @slow_node( - "cells", - "displacements", - "energy_pot", - "energy_tot", - "force_max", - "forces", - "indices", - "positions", - "pressures", - "steps", - "temperature", - "total_displacements", - "unwrapped_positions", - "volume", + output_labels= [ + "cells", + "displacements", + "energy_pot", + "energy_tot", + "force_max", + "forces", + "indices", + "positions", + "pressures", + "steps", + "temperature", + "total_displacements", + "unwrapped_positions", + "volume", + ] ) def calc_md( job: AtomisticGenericJob, diff --git a/pyiron_contrib/workflow/node_library/standard.py b/pyiron_contrib/workflow/node_library/standard.py index a920e2538..1a2d11e21 100644 --- a/pyiron_contrib/workflow/node_library/standard.py +++ b/pyiron_contrib/workflow/node_library/standard.py @@ -8,7 +8,7 @@ from pyiron_contrib.workflow.function import single_value_node -@single_value_node("fig") +@single_value_node(output_labels="fig") def scatter( x: Optional[list | np.ndarray] = None, y: Optional[list | np.ndarray] = None ): diff --git a/tests/unit/workflow/test_node_package.py b/tests/unit/workflow/test_node_package.py index c8492437c..4e89db0b4 100644 --- a/tests/unit/workflow/test_node_package.py +++ b/tests/unit/workflow/test_node_package.py @@ -5,7 +5,7 @@ from pyiron_contrib.workflow.workflow import Workflow -@Workflow.wrap_as.function_node("x") +@Workflow.wrap_as.function_node() def dummy(x: int = 0): return x @@ -41,7 +41,7 @@ def test_update(self): with self.assertRaises(TypeError): self.package.available_name = "But we can still only assign node classes" - @Workflow.wrap_as.function_node("y") + @Workflow.wrap_as.function_node(output_label="y") def add(x: int = 0): return x + 1 @@ -53,9 +53,10 @@ def add(x: int = 0): old_dummy_instance = self.package.Dummy(label="old_dummy_instance") - @Workflow.wrap_as.function_node("y") + @Workflow.wrap_as.function_node() def dummy(x: int = 0): - return x + 1 + y = x + 1 + return y self.package.update(dummy) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index db35843ed..b9dd2fd4a 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -9,7 +9,8 @@ def fnc(x=0): - return x + 1 + y = x + 1 + return y @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") @@ -19,10 +20,10 @@ def test_node_addition(self): wf = Workflow("my_workflow") # Validate the four ways to add a node - wf.add(Function(fnc, "x", label="foo")) - wf.add.Function(fnc, "y", label="bar") - wf.baz = Function(fnc, "y", label="whatever_baz_gets_used") - Function(fnc, "x", label="qux", parent=wf) + 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) self.assertListEqual(list(wf.nodes.keys()), ["foo", "bar", "baz", "qux"]) wf.boa = wf.qux self.assertListEqual( @@ -33,14 +34,13 @@ def test_node_addition(self): wf.strict_naming = False # Validate name incrementation - wf.add(Function(fnc, "x", label="foo")) - wf.add.Function(fnc, "y", label="bar") + wf.add(Function(fnc, label="foo")) + wf.add.Function(fnc, label="bar") wf.baz = Function( fnc, - "y", label="without_strict_you_can_override_by_assignment" ) - Function(fnc, "x", label="boa", parent=wf) + Function(fnc, 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, "x", label="foo")) + wf.add(Function(fnc, label="foo")) with self.assertRaises(AttributeError): - wf.add.Function(fnc, "y", label="bar") + wf.add.Function(fnc, label="bar") with self.assertRaises(AttributeError): - wf.baz = Function(fnc, "y", label="whatever_baz_gets_used") + wf.baz = Function(fnc, label="whatever_baz_gets_used") with self.assertRaises(AttributeError): - Function(fnc, "x", label="boa", parent=wf) + Function(fnc, 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, "y", label="node1") - node2 = Function(fnc, "y", label="node2", parent=wf1, x=wf1.node1.outputs.y) + wf1.add.Function(fnc, label="node1") + node2 = Function(fnc, 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, "y", label="n1") - wf.add.Function(fnc, "y", label="n2") - wf.add.Function(fnc, "y", label="n3") + wf.add.Function(fnc, label="n1") + wf.add.Function(fnc, label="n2") + wf.add.Function(fnc, label="n3") with self.subTest("Workflow IO should be drawn from its nodes"): self.assertEqual(len(wf.inputs), 3) @@ -111,7 +111,7 @@ def test_workflow_io(self): self.assertEqual(len(wf.outputs), 1) def test_node_decorator_access(self): - @Workflow.wrap_as.function_node("y") + @Workflow.wrap_as.function_node(output_labels="y") def plus_one(x: int = 0) -> int: return x + 1 @@ -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, "output") + wf.add.Function(fnc) self.assertTrue(str(wf.fnc.working_directory.path).endswith(wf.fnc.label)) wf.working_directory.delete() @@ -146,12 +146,13 @@ def test_no_parents(self): def test_parallel_execution(self): wf = Workflow("wf") - @Workflow.wrap_as.single_value_node("five", run_on_updates=False) + @Workflow.wrap_as.single_value_node(run_on_updates=False) def five(sleep_time=0.): sleep(sleep_time) - return 5 + five = 5 + return five - @Workflow.wrap_as.single_value_node("sum") + @Workflow.wrap_as.single_value_node(output_labels="sum") def sum(a, b): return a + b From b1d4680ce921651a0980adde7a3a5445437c133f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 14 Jul 2023 13:21:30 -0700 Subject: [PATCH 352/756] Handle no data output --- pyiron_contrib/workflow/function.py | 6 ++++-- tests/unit/workflow/test_function.py | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 1dda66513..e799e715f 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -367,7 +367,7 @@ def _get_output_labels(self, output_labels: str | list[str] | tuple[str] | None) """ parsed_labels = ParseOutput(self.node_function).output if output_labels is None: - return parsed_labels + return parsed_labels if parsed_labels is not None else [] else: if isinstance(output_labels, str): output_labels = (output_labels,) @@ -521,7 +521,9 @@ def process_run_result(self, function_output): for channel_name in self.channels_requiring_update_after_run: self.inputs[channel_name].wait_for_update() - if len(self.outputs) == 1: + if len(self.outputs) == 0: + return + elif len(self.outputs) == 1: function_output = (function_output,) for out, value in zip(self.outputs, function_output): diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 96a9b1b20..dac78dd1d 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -26,9 +26,17 @@ def no_default(x, y): def returns_multiple(x, y): return x, y, x + y +def void(): + pass + @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestFunction(unittest.TestCase): + def test_instantiation(self): + with self.subTest("Void function"): + void_node = Function(void) + self.assertEqual(len(void_node.outputs), 0) + def test_defaults(self): with_defaults = Function(plus_one) self.assertEqual( From a2832f4451f961365f2fed9b617b1243938f6572 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 14 Jul 2023 13:46:20 -0700 Subject: [PATCH 353/756] Update example --- notebooks/workflow_example.ipynb | 220 ++++++++++++++++++++----------- 1 file changed, 140 insertions(+), 80 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 50843a9e8..1ef82d6f8 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -9,7 +9,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "d57449473dbc42f2997863543b5171c6", + "model_id": "e23eaad8312941fbbaa0683e71bc8ed6", "version_major": 2, "version_minor": 0 }, @@ -47,7 +47,9 @@ "source": [ "## Instantiating a node\n", "\n", - "Simple nodes can be defined on-the-fly by passing any callable to the `Function(Node)` class, along with a string (tuple of strings) giving names for the output value(s)." + "Simple nodes can be defined on-the-fly by passing any callable to the `Function(Node)` class. This transforms the function into a node instance which has input and output, can be connected to other nodes in a workflow, and can run the function it stores.\n", + "\n", + "Input and output channels are _automatically_ extracted from the signature and return value(s) of the function. (Note: \"Nodized\" functions must have _at most_ one `return` expression!)" ] }, { @@ -60,7 +62,7 @@ "def plus_minus_one(x):\n", " return x+1, x-1\n", "\n", - "pm_node = Function(plus_minus_one, \"p1\", \"m1\")" + "pm_node = Function(plus_minus_one)" ] }, { @@ -81,7 +83,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "['x'] ['p1', 'm1']\n" + "['x'] ['x+1', 'x-1']\n" ] } ], @@ -94,8 +96,7 @@ "id": "22ee2a49-47d1-4cec-bb25-8441ea01faf7", "metadata": {}, "source": [ - "The output is still empty (`NotData`) because we haven't `run()` the node.\n", - "If we try that now though, we'll just get a type error because the input is not set! " + "The output is still empty (`NotData`) because we haven't `run()` the node:" ] }, { @@ -108,12 +109,30 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'p1': , 'm1': }\n" + "{'x+1': , 'x-1': }\n" ] } ], "source": [ - "print(pm_node.outputs.to_value_dict())\n" + "print(pm_node.outputs.to_value_dict())" + ] + }, + { + "cell_type": "markdown", + "id": "0374e277-55ab-45d2-8058-b06365bd07af", + "metadata": {}, + "source": [ + "If we try that now though, we'll just get a type error because the input is not set! " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "05196cd8-97c7-4f08-ae3a-ad6a076512f7", + "metadata": {}, + "outputs": [], + "source": [ + "# pm_node.run()" ] }, { @@ -124,12 +143,12 @@ "By default, a softer `update()` call is made at instantiation and whenever the node input is updated.\n", "This call checks to make sure the input is `ready` before moving on to `run()`. \n", "\n", - "If we update the input, we'll give the node enough data to work with and it will automatically update the output" + "If we update the input, we'll give the node enough data to work with and it will automatically update the output:" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "b1500a40-f4f2-4c06-ad78-aaebcf3e9a50", "metadata": {}, "outputs": [ @@ -137,7 +156,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'p1': 6, 'm1': 4}\n" + "{'x+1': 6, 'x-1': 4}\n" ] } ], @@ -151,12 +170,14 @@ "id": "df4520d7-856e-4bc8-817f-5b2e22c1ddce", "metadata": {}, "source": [ - "We can be stricter and force the node to wait for an explicit `run()` call by modifying the `run_on_updates` and `update_on_instantiation` flags." + "We can be stricter and force the node to wait for an explicit `run()` call by modifying the `run_on_updates` and `update_on_instantiation` flags. \n", + "\n", + "Let's also take the opportunity to give our output channel a better name so we can get it by dot-access." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "ab1ac28a-6e69-491f-882f-da4a43162dd7", "metadata": {}, "outputs": [ @@ -166,18 +187,19 @@ "pyiron_contrib.workflow.channels.NotData" ] }, - "execution_count": 6, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def adder(x: int, y: int = 1) -> int:\n", - " return x + y\n", + " sum_ = x + y\n", + " return sum_\n", "\n", - "adder_node = Function(adder, \"sum\", run_on_updates=False)\n", + "adder_node = Function(adder, run_on_updates=False)\n", "adder_node.inputs.x = 1\n", - "adder_node.outputs.sum.value # We use `value` to see the data the channel holds" + "adder_node.outputs.sum_.value # We use `value` to see the data the channel holds" ] }, { @@ -191,7 +213,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "dc41a447-15fd-4df2-b60a-0935d81d469e", "metadata": {}, "outputs": [ @@ -201,14 +223,14 @@ "2" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "adder_node.run()\n", - "adder_node.outputs.sum.value" + "adder_node.outputs.sum_.value" ] }, { @@ -222,7 +244,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "ac0fe993-6c82-48c8-a780-cbd0c97fc386", "metadata": {}, "outputs": [ @@ -232,7 +254,7 @@ "(int, str)" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -254,7 +276,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "bcbd17f1-a3e4-44f0-bde1-cbddc51c5d73", "metadata": {}, "outputs": [ @@ -264,13 +286,13 @@ "2" ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "adder_node.outputs.sum.value" + "adder_node.outputs.sum_.value" ] }, { @@ -283,7 +305,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "15742a49-4c23-4d4a-84d9-9bf19677544c", "metadata": {}, "outputs": [ @@ -293,14 +315,14 @@ "3" ] }, - "execution_count": 10, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "adder_node.inputs.x.update(2)\n", - "adder_node.outputs.sum.value" + "adder_node.outputs.sum_.value" ] }, { @@ -319,7 +341,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "61b43a9b-8dad-48b7-9194-2045e465793b", "metadata": {}, "outputs": [], @@ -329,7 +351,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "id": "647360a9-c971-4272-995c-aa01e5f5bb83", "metadata": {}, "outputs": [ @@ -344,9 +366,10 @@ } ], "source": [ - "@function_node(\"diff\")\n", + "@function_node()\n", "def subtract_node(x: int | float = 2, y: int | float = 1) -> int | float:\n", - " return x - y\n", + " diff = x - y\n", + " return diff\n", "\n", "sn = subtract_node()\n", "print(\"class name =\", sn.__class__.__name__)\n", @@ -366,7 +389,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "id": "8fb0671b-045a-4d71-9d35-f0beadc9cf3a", "metadata": {}, "outputs": [ @@ -376,7 +399,7 @@ "-10" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -397,7 +420,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "id": "5ce91f42-7aec-492c-94fb-2320c971cd79", "metadata": {}, "outputs": [ @@ -410,15 +433,16 @@ } ], "source": [ - "@function_node(\"sum\")\n", + "@function_node()\n", "def add_node(x: int | float = 1, y: int | float = 1) -> int | float:\n", - " return x + y\n", + " sum_ = x + y\n", + " return sum_\n", "\n", "add1 = add_node()\n", "add2 = add_node(x=2, y=2)\n", - "sub = subtract_node(x=add1.outputs.sum, y=add2.outputs.sum)\n", + "sub = subtract_node(x=add1.outputs.sum_, y=add2.outputs.sum_)\n", "print(\n", - " f\"{add1.outputs.sum.value} - {add2.outputs.sum.value} = {sub.outputs.diff.value}\"\n", + " f\"{add1.outputs.sum_.value} - {add2.outputs.sum_.value} = {sub.outputs.diff.value}\"\n", ")" ] }, @@ -432,7 +456,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "20360fe7-b422-4d78-9bd1-de233f28c8df", "metadata": {}, "outputs": [ @@ -447,7 +471,7 @@ "source": [ "add1.inputs.x = 10\n", "print(\n", - " f\"{add1.outputs.sum.value} - {add2.outputs.sum.value} = {sub.outputs.diff.value}\"\n", + " f\"{add1.outputs.sum_.value} - {add2.outputs.sum_.value} = {sub.outputs.diff.value}\"\n", ")" ] }, @@ -465,18 +489,19 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "1a4e9693-0980-4435-aecc-3331d8b608dd", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", + "\n", "from pyiron_contrib.workflow.function import single_value_node" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "7c4d314b-33bb-4a67-bfb9-ed77fba3949c", "metadata": {}, "outputs": [ @@ -491,17 +516,18 @@ } ], "source": [ - "@single_value_node(\"linspace\")\n", + "@single_value_node()\n", "def linspace_node(\n", " start: int | float = 0, stop: int | float = 1, num: int = 50\n", "):\n", - " return np.linspace(start=start, stop=stop, num=num)\n", + " linspace = np.linspace(start=start, stop=stop, num=num)\n", + " return linspace\n", "\n", "lin = linspace_node()\n", "\n", "print(type(lin.outputs.linspace.value)) # Output is just what we expect\n", "print(lin[1:4]) # Gets items from the output\n", - "print(lin.mean()) # Finds the method on the output" + "print(lin.mean()) # Finds the method on the output -- a special feature of SingleValueNode" ] }, { @@ -512,19 +538,21 @@ "# Workflows\n", "\n", "Typically, you will have a group of nodes working together with their connections.\n", - "We call these groups workflows, and offer a `Workflow(Node)` object as a single point of entry -- i.e. most of the time you shouldn't need the node imports used above, because the decorators are available right on the workflow class." + "We call these groups workflows, and offer a `Workflow(Node)` object as a single point of entry -- i.e. most of the time you shouldn't need the node imports used above, because the decorators are available right on the workflow class.\n", + "\n", + "We can also rename our node output channels using the `output_labels: Optional[str | list[str] | tuple[str]` kwarg, which we'll see here" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "1cd000bd-9b24-4c39-9cac-70a3291d0660", "metadata": {}, "outputs": [], "source": [ "from pyiron_contrib.workflow.workflow import Workflow\n", "\n", - "@Workflow.wrap_as.single_value_node(\"is_greater\")\n", + "@Workflow.wrap_as.single_value_node(output_labels=\"is_greater\")\n", "def greater_than_half(x: int | float | bool = 0) -> bool:\n", " \"\"\"The functionality doesn't matter here, it's just an example\"\"\"\n", " return x > 0.5" @@ -542,7 +570,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "7964df3c-55af-4c25-afc5-9e07accb606a", "metadata": {}, "outputs": [ @@ -563,7 +591,7 @@ "n1 = greater_than_half(label=\"n1\")\n", "\n", "wf = Workflow(\"my_wf\", n1) # As args at init\n", - "wf.add.Slow(lambda: x + 1, \"p1\", label=\"n2\") # Instantiating from the class with a lambda function\n", + "wf.add.Slow(lambda: x + 1, output_labels=\"p1\", label=\"n2\") # Instantiating from the class with a lambda function\n", "# (Slow since we don't have an x default)\n", "wf.add(greater_than_half(label=\"n3\")) # Instantiating then passing to node adder\n", "wf.n4 = greater_than_half(label=\"will_get_overwritten_with_n4\") # Set attribute to instance\n", @@ -591,7 +619,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "id": "2e418abf-7059-4e1e-9b9f-b3dc0a4b5e35", "metadata": {}, "outputs": [ @@ -604,17 +632,18 @@ } ], "source": [ - "@function_node(\"y\")\n", + "@function_node()\n", "def linear(x):\n", " return x\n", "\n", - "@function_node(\"z\", run_on_updates=False)\n", - "def times_two(y):\n", - " return 2 * y\n", + "@function_node(run_on_updates=False)\n", + "def times_two(x):\n", + " double = 2 * x\n", + " return double\n", "\n", "l = linear(x=1)\n", - "t2 = times_two(y=l.outputs.y)\n", - "print(t2.inputs.y, t2.outputs.z)" + "t2 = times_two(x=l.outputs.x)\n", + "print(t2.inputs.x, t2.outputs.double)" ] }, { @@ -631,7 +660,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "id": "3310eac4-04f6-421b-9824-19bb2d680be6", "metadata": {}, "outputs": [ @@ -644,7 +673,7 @@ } ], "source": [ - "@function_node(\"void\")\n", + "@function_node()\n", "def control():\n", " return\n", "\n", @@ -652,7 +681,15 @@ "l.signals.input.run = c.signals.output.ran\n", "t2.signals.input.run = l.signals.output.ran\n", "c.run()\n", - "print(t2.outputs.z.value)" + "print(t2.outputs.double.value)" + ] + }, + { + "cell_type": "markdown", + "id": "003ed16e-c493-4465-9f08-492f9c51f764", + "metadata": {}, + "source": [ + "`Function` and its children always push out data updates _before_ triggering their `ran` signal." ] }, { @@ -665,7 +702,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "id": "7a6f2bce-6b5e-4321-9457-0a6790d2202a", "metadata": {}, "outputs": [], @@ -675,13 +712,13 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 24, "id": "6569014a-815b-46dd-8b47-4e1cd4584b3b", "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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DBs2zDz55JMRnnxgnK67y+HDh83pp59uioqKzHnnnReZYUPI6brfeustI8l89NFHxufzdb+OHTsW4ckHJpjf909/+lMzadIkU11dberr6827775rtm7dGsGpB8bpmg8fPhzwO25sbDRpaWmmtLQ0soMPkNN1b9682cTFxZlHH33U7N2712zevNmcc845ZubMmRGefGCcrruiosKkpKSYF1980XzyySdm9erV5qSTTjLr1q2L8OSRETUxkp+fb0pKSgK2nX322Wbx4sX9/h45OTmmrKws1KOFVSjW/fOf/9zMmjUr1KOFVbDrLi4uNn/4wx9MaWlpVMaI03V3xch///vfCEwXPk7X/frrrxuPx2MOHjwYifHCYqD/bq9du9a4XC7z6aefhmO8sHG67vvvv9+cfvrpAdsee+wxk5mZGbYZw8HpugsKCswdd9wRsO22224zkydPDtuMNkXFZZqjR4+qpqZGRUVFAduLioq0bdu2fn2Pzs5Otba2Ki0tLRwjhkUo1r1r1y5t27ZNF198cThGDItg1/3MM8/ok08+UWlpabhHDIuB/L4vuOACeb1eXXbZZXrrrbfCOWbIBbPudevWaeLEibrvvvs0evRonXXWWbrjjjv09ddfR2LkAQvFv9srVqzQ5ZdfrjFjxoRjxLAIZt2FhYXat2+fNmzYIGOMvvjiC7388su66qqrIjFySASz7vb2drnd7oBtycnJ2r59u7755puwzWpLVMTIgQMH1NHRofT09IDt6enpampq6tf3ePDBB/XVV1/pmmuuCceIYTGQdWdmZiopKUkTJ07UzTffrPnz54dz1JAKZt0ff/yxFi9erFWrVikhISr+Muoeglm31+vVU089pVdeeUWvvvqqxo0bp8suu0xvv/12JEYOiWDWvXfvXm3ZskW1tbVau3atHnnkEb388su6+eabIzHygA30v2k+n0+vv/56VP17LQW37sLCQq1atUrFxcVKTExURkaGTj75ZD3++OORGDkkgln3FVdcoaefflo1NTUyxmjHjh2qqqrSN998owMHDkRi7IiKqv9qu1yugK+NMT229Wb16tW6++679dprr2nkyJHhGi9sgln35s2b9eWXX+qdd97R4sWLdcYZZ+iXv/xlOMcMuf6uu6OjQ9ddd53Kysp01llnRWq8sHHy+x43bpzGjRvX/XVBQYEaGxv1wAMP6H/+53/COmeoOVl3Z2enXC6XVq1a1f3Xkz/00EO6+uqrtXz5ciUnJ4d93lAI9r9pzz77rE4++WTNnDkzTJOFl5N119XV6dZbb9Vdd92lK664Qj6fT3feeadKSkq0YsWKSIwbMk7W/cc//lFNTU268MILZYxRenq65syZo/vuu0/x8fGRGDeiouLMyIgRIxQfH9+jIJubm3uU5netWbNG8+bN01/+8hddfvnl4Rwz5Aay7uzsbP34xz/WjTfeqNtvv1133313GCcNLafrbm1t1Y4dO3TLLbcoISFBCQkJWrZsmf79738rISFBf//73yM1+oAM5Pf9bRdeeKE+/vjjUI8XNsGs2+v1avTo0d0hIknjx4+XMUb79u0L67yhMJDftTFGVVVVuv7665WYmBjOMUMumHWXl5dr8uTJuvPOO3XuuefqiiuuUEVFhaqqquTz+SIx9oAFs+7k5GRVVVXpyJEj+vTTT9XQ0KDTTjtNKSkpGjFiRCTGjqioiJHExETl5eWpuro6YHt1dbUKCwv7fN/q1as1Z84cvfDCC1F1fbFLsOv+LmOM2tvbQz1e2Dhdd2pqqt577z3t3r27+1VSUqJx48Zp9+7dmjRpUqRGH5BQ/b537dolr9cb6vHCJph1T548Wfv379eXX37ZvW3Pnj2Ki4tTZmZmWOcNhYH8rjdt2qT//Oc/mjdvXjhHDItg1n3kyBHFxQUeqrrODJgo+XteB/L7PuGEE5SZman4+Hi9+OKL+slPftLjf48hwcKHZoPSdVvUihUrTF1dnVm4cKE58cQTuz9JvnjxYnP99dd37//CCy+YhIQEs3z58oDb4Q4fPmxrCUFxuu4nnnjCrFu3zuzZs8fs2bPHVFVVmdTUVLN06VJbSwiK03V/V7TeTeN03Q8//LBZu3at2bNnj6mtrTWLFy82kswrr7xiawlBcbru1tZWk5mZaa6++mrz/vvvm02bNpkzzzzTzJ8/39YSHAv2n/FZs2aZSZMmRXrckHG67meeecYkJCSYiooK88knn5gtW7aYiRMnmvz8fFtLCIrTdX/00Ufm+eefN3v27DHvvvuuKS4uNmlpaaa+vt7SCsIramLEGGOWL19uxowZYxITE82ECRPMpk2buv/shhtuMBdffHH31xdffLGR1ON1ww03RH7wAXKy7scee8ycc845ZtiwYSY1NdVccMEFpqKiwnR0dFiYfGCcrPu7ojVGjHG27nvvvdeMHTvWuN1u86Mf/chcdNFF5m9/+5uFqQfO6e/7gw8+MJdffrlJTk42mZmZZtGiRebIkSMRnnpgnK758OHDJjk52Tz11FMRnjS0nK77scceMzk5OSY5Odl4vV7zq1/9yuzbty/CUw+ck3XX1dWZ888/3yQnJ5vU1FTzs5/9zHz44YcWpo4MlzFRcp4LAAAMSUPwwhMAAIgmxAgAALCKGAEAAFYRIwAAwCpiBAAAWEWMAAAAq4gRAABgFTECAACsIkYAAIBVxAgAALCKGAEAAFb9LzGeNuwnas6eAAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -691,13 +728,15 @@ } ], "source": [ - "@single_value_node(\"array\")\n", + "@single_value_node()\n", "def noise(length: int = 1):\n", - " return np.random.rand(length)\n", + " array = np.random.rand(length)\n", + " return array\n", "\n", - "@function_node(\"fig\")\n", + "@function_node()\n", "def plot(x, y):\n", - " return plt.scatter(x, y)\n", + " fig = plt.scatter(x, y)\n", + " return fig\n", "\n", "x = noise(length=10)\n", "y = noise(length=10)\n", @@ -719,7 +758,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "id": "25f0495a-e85f-43b7-8a70-a2c9cbd51ebb", "metadata": {}, "outputs": [ @@ -729,7 +768,7 @@ "(False, False)" ] }, - "execution_count": 24, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -740,13 +779,35 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "id": "449ce797-be62-4211-b483-c717a3d70583", "metadata": {}, "outputs": [ { "data": { - "image/png": "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iooKHT58OORz3nrrLS1cuFBPPvmkZs+erfnz5+vhhx/WN998M+H7DA8Pa2BgIOgBAADC40xxqLqyUNJI8Djf6M/VlYXG9huxFUbOnDkjv9+vnJycoPacnBz19PSEfM6JEyd08OBBdXR06M0339SuXbt04MAB3X///RO+T21trdxu99gjLy/PTpkAAOACy4s8ql9drFx38FRMrtul+tXFRvcZCWvTM4cjODlZljWubVQgEJDD4dC+ffvkdrslSTt37tTtt9+u3bt3a9q0aeOes23bNm3ZsmXs54GBAQIJAACXaXmRR8sKc+N7B9aZM2fK6XSOGwXp7e0dN1oyyuPxaPbs2WNBRBq5x8SyLJ06dUrz5s0b95z09HSlp6fbKQ0AAEyCM8WhsrkzTJcRxNY0TVpamkpKStTS0hLU3tLSovLy8pDPWbx4sU6fPq0vv/xyrO2TTz5RSkqK5syZE0bJAAAgkdjeZ2TLli168cUXtWfPHn388cd66KGH1N3drQ0bNkgamWJZu3btWP8777xTM2bM0D333KPOzk699957euSRR3TvvfeGnKIBAADJxfY9I1VVVerr69OOHTvk9XpVVFSkpqYm5efnS5K8Xq+6u7vH+n/rW99SS0uLfvnLX2rhwoWaMWOGVq1apccff3zq/goAABC3bO8zYgL7jAAAEH8iss8IAADAVCOMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKNSTRcAAIhf/oCltq5+9Q4OKTvDpdKCLDlTHKbLQpwhjAAAwtLc4VVNY6e8vqGxNo/bperKQi0v8hisDPGGaRoAgG3NHV5t3Hs0KIhIUo9vSBv3HlVzh9dQZYhHhBEAgC3+gKWaxk5ZIX432lbT2Cl/IFQPYDzCCADA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", + "text/plain": [ + "(True, False)" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x.inputs.length = 20\n", + "f.inputs.x.ready, f.inputs.y.ready" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "7008b0fc-3644-401c-b49f-9c40f9d89ac4", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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X7jXZtiXX35pZs2aZcrLy82VV14Zn2JTTCTX5pZbbjHz5s0zDQ0NprW11fzzn/80e/fujWPVqSHca7N7926TlpZmnnnmGXPs2DGze/duc/nll5vFixfHuXJnq6urM+vWrTNvvfWWkWTeeeedsx5/7Ngxc8EFF5iHHnrItLS0mJdeesmMGzfOvPnmm2G9L4HFQUpKSkxlZWVA22WXXWYeffTRkF+joKDAVFdXR7u0lBfptVmyZIn57W9/a6qqqggsMRLutfnLX/5iPB6P6erqikd5KS3ca/O73/3OzJo1K6Dt2WefNdOnT49ZjakulMDyq1/9ylx22WUBbffdd5+5+uqrw3ovhoQc4uTJkzp48KDKy8sD2svLy7Vv376QXmNwcFC9vb3Kzs6ORYkpK9Jr88orr+jo0aOqqqqKdYkpK5Jr895772nu3Ll66qmnNG3aNF1yySV6+OGH9e2338aj5JQRybUpKyvTiRMnVFdXJ2OMvvjiC7355pu6+eab41EyxrB///6g63jjjTfqwIED+u6770J+naS4WzPOrbOzUwMDA8rNzQ1oz83NVXt7e0iv8fvf/15ff/21brvttliUmLIiuTaffPKJHn30Ue3evVsZGXxMYyWSa3Ps2DHt2bNHbrdb77zzjjo7O3X//feru7ubeSxRFMm1KSsr09atW7VkyRL19fXp1KlTuuWWW/Tcc8/Fo2SMob29fdTreOrUKXV2dsrr9Yb0OvSwOIzL5Qp4bowJahvNtm3b9Pjjj2v79u2aMmVKrMpLaaFem4GBAd1xxx2qrq7WJZdcEq/yUlo4n5vBwUG5XC5t3bpVJSUluummm/T000/r1VdfpZclBsK5Ni0tLVq5cqUee+wxHTx4UPX19WptbVVlZWU8SsVZjHYdR2s/G351c4jJkycrPT096DePjo6OoGR7pu3bt+uee+7RG2+8oRtuuCGWZaakcK9Nb2+vDhw4oKamJj344IOShr4kjTHKyMjQBx98oOuvvz4utTtdJJ8br9eradOmyePxjLTNmTNHxhidOHFCF198cUxrThWRXJuamhpdc801euSRRyRJV1xxhSZOnKj58+friSeeCPk3eURXXl7eqNcxIyNDOTk5Ib8OPSwOMX78eBUXF6uhoSGgvaGhQWVlZWOet23bNt111116/fXXGeeNkXCvTVZWlj788EM1NzePPCorK3XppZequblZ8+bNi1fpjhfJ5+aaa67R559/rq+++mqk7eOPP1ZaWpqmT58e03pTSSTX5ptvvlFaWuDXWnp6uqT//UaP+CstLQ26jh988IHmzp2rcePGhf5CYU3RhdWGlwBu2rTJtLS0mFWrVpmJEyea//73v8YYYx599FFz5513jhz/+uuvm4yMDLNhwwbj8/lGHl9++WWifgTHCvfanIlVQrET7rXp7e0106dPN7feeqv56KOPzM6dO83FF19s7r333kT9CI4V7rV55ZVXTEZGhtm4caM5evSo2bNnj5k7d64pKSlJ1I/gSL29vaapqck0NTUZSebpp582TU1NI8vNz7wuw8uaV69ebVpaWsymTZtY1gxjNmzYYGbOnGnGjx9vioqKzM6dO0f+bPny5ebaa68deX7ttdcaSUGP5cuXx7/wFBDOtTkTgSW2wr02R44cMTfccIOZMGGCmT59ulmzZo355ptv4lx1agj32jz77LOmoKDATJgwwXi9XvOzn/3MnDhxIs5VO9vf/va3s353jHZd/v73v5sf/vCHZvz48eaiiy4ytbW1Yb+vyxj6yQAAgN2YwwIAAKxHYAEAANYjsAAAAOsRWAAAgPUILAAAwHoEFgAAYD0CCwAAsB6BBQAAWI/AAgAArEdgAQAA1iOwAAAA6/0/dvCXVJIj9t8AAAAASUVORK5CYII=", "text/plain": [ "
" ] @@ -756,7 +817,6 @@ } ], "source": [ - "x.inputs.length = 20\n", "y.inputs.length = 20" ] }, @@ -784,7 +844,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 28, "id": "ae500d5e-e55b-432c-8b5f-d5892193cdf5", "metadata": {}, "outputs": [ @@ -792,9 +852,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:177: 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:193: 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:177: 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:193: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent with_prebuilt.\n", " warn(\n" ] }, @@ -802,22 +862,22 @@ "name": "stdout", "output_type": "stream", "text": [ - "The job JUSTAJOBNAME was saved and received the ID: 9553\n" + "The job JUSTAJOBNAME was saved and received the ID: 9558\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:177: 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:193: 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:177: 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:193: UserWarning: Reassigning the node scatter to the label plot when adding it to the parent with_prebuilt.\n", " warn(\n" ] }, { "data": { - "image/png": "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", + "image/png": "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d7vd8nq9re4jFApp9erVWrdunUaNGiVJKi4uVk5OjrZs2aKxY8dGewwAAKCH6dCbZEOhkCQpLS1y1tytW7cqMzNTl112mYYNG6annnpKmZmZkqTy8nKdOHFCY8aMceqzs7Pl9/u1Y8eOVgNKOBxWOBw+43vr6uo60nwAANCFTp23jTHnrG13QDHG6JFHHtFtt90mv9/vrB8/frzuuusu5ebmqqqqSr/4xS80YsQIlZeXy+12KxgMKjExUX369InYX1ZWloLBYKvfVVRUpGXLlp2xPicnp73NBwAA3aS+vl4ej+esNe0OKA8//LD+/Oc/a/v27RHrp02b5vzZ7/dr4MCBys3N1dtvv60pU6a0uT9jjFyu1ie5W7x4sR555BHn55aWFh09elTp6eltfqa96urqlJOTo+rqaqWmpnbqvvH/6OeuQT93Dfq569DXXeNC9bMxRvX19crOzj5nbbsCyrx58/TWW2/pgw8+0BVXXHHWWp/Pp9zcXB06dEiS5PV61dTUpNra2ohRlJqaGg0dOrTVfbjdbrnd7oh1l112WXuaft5SU1P5x98F6OeuQT93Dfq569DXXeNC9PO5Rk5OieopHmOMHn74Yb355pt6//33lZeXd87PHDlyRNXV1fL5fJKkgoICJSQkqKyszKkJBAKqrKxsM6AAAICLS1QjKHPnztX69ev1+9//XikpKc49Ix6PR7169VJDQ4OWLl2qO++8Uz6fT59++qmWLFmijIwM3XHHHU7t7NmztWDBAqWnpystLU0LFy5Ufn6+81QPAAC4uEUVUFauXClJGj58eMT6V199VbNmzVJcXJwqKiq0du1aHTt2TD6fT7fffrveeOMNpaSkOPUrVqxQfHy8pk6dqsbGRo0cOVJr1qxRXFxcx4+og9xut5544okzLimhc9HPXYN+7hr0c9ehr7uGDf3sMufzrA8AAEAXYi4eAABgHQIKAACwDgEFAABYh4ACAACsQ0D5O//+7/+uvLw8JSUlqaCgQH/84x+7u0kxpaioSDfffLNSUlKUmZmpH//4x/roo48iaowxWrp0qbKzs9WrVy8NHz5cBw4ciKgJh8OaN2+eMjIylJycrMmTJ+vw4cNdeSgxpaioSC6XS4WFhc46+rlzfPHFF5oxY4bS09PVu3dv3XjjjSovL3e2088d99133+lf//VflZeXp169eumqq67SL3/5S7W0tDg19HP7fPDBB5o0aZKys7Plcrn0u9/9LmJ7Z/VrbW2t7rvvPnk8Hnk8Ht133306duxYxw/AwBhjTElJiUlISDCrVq0yBw8eNPPnzzfJycnms88+6+6mxYyxY8eaV1991VRWVpr9+/ebCRMmmCuvvNI0NDQ4NU8//bRJSUkxv/3tb01FRYWZNm2a8fl8pq6uzql58MEHzfe+9z1TVlZm9u7da26//XZzww03mO+++647Dstqu3fvNv/wD/9g+vfvb+bPn++sp5877ujRoyY3N9fMmjXLfPjhh6aqqsps2bLF/PWvf3Vq6OeOe/LJJ016err5j//4D1NVVWV+85vfmEsvvdS8+OKLTg393D4bN240jz/+uPntb39rJJnS0tKI7Z3Vr+PGjTN+v9/s2LHD7Nixw/j9fjNx4sQOt5+A8n9uueUW8+CDD0asu+6668xjjz3WTS2KfTU1NUaS2bZtmzHGmJaWFuP1es3TTz/t1Bw/ftx4PB7z61//2hhjzLFjx0xCQoIpKSlxar744gtzySWXmE2bNnXtAViuvr7e9O3b15SVlZlhw4Y5AYV+7hyPPvqoue2229rcTj93jgkTJpif/vSnEeumTJliZsyYYYyhnzvL6QGls/r14MGDRpLZtWuXU7Nz504jyfz3f/93h9rMJR5JTU1NKi8v15gxYyLWjxkzRjt27OimVsW+UCgkSUpLS5MkVVVVKRgMRvSz2+3WsGHDnH4uLy/XiRMnImqys7Pl9/v5uzjN3LlzNWHChDPewEw/d4633npLAwcO1F133aXMzEwNGDBAq1atcrbTz53jtttu03vvvaePP/5YkvRf//Vf2r59u/7xH/9REv18oXRWv+7cuVMej0eDBg1yagYPHiyPx9Phvm/3bMY9yddff63m5mZlZWVFrM/KynJe54/oGGP0yCOP6LbbbpPf75ckpy9b6+fPPvvMqUlMTIyYSPJUDX8X/6+kpER79+7Vnj17zthGP3eOTz75RCtXrtQjjzyiJUuWaPfu3frZz34mt9utn/zkJ/RzJ3n00UcVCoV03XXXKS4uTs3NzXrqqad0zz33SOLf84XSWf0aDAaVmZl5xv4zMzM73PcElL/jcrkifjbGnLEO5+fhhx/Wn//8Z23fvv2Mbe3pZ/4u/l91dbXmz5+vzZs3Kykpqc06+rljWlpaNHDgQC1fvlySNGDAAB04cEArV67UT37yE6eOfu6YN954Q8XFxVq/fr2uv/567d+/X4WFhcrOztbMmTOdOvr5wuiMfm2tvjP6nks8kjIyMhQXF3dG2qupqTkjXeLc5s2bp7feekt/+MMfdMUVVzjrvV6vJJ21n71er5qamlRbW9tmzcWuvLxcNTU1KigoUHx8vOLj47Vt2za99NJLio+Pd/qJfu4Yn8+nfv36Raz7wQ9+oM8//1wS/547y7/8y7/oscce09133638/Hzdd999+vnPf66ioiJJ9POF0ln96vV69dVXX52x///5n//pcN8TUCQlJiaqoKBAZWVlEevLyso0dOjQbmpV7DHG6OGHH9abb76p999/X3l5eRHb8/Ly5PV6I/q5qalJ27Ztc/q5oKBACQkJETWBQECVlZX8XfyfkSNHqqKiQvv373eWgQMHavr06dq/f7+uuuoq+rkT3HrrrWc8Jv/xxx8rNzdXEv+eO8u3336rSy6JPBXFxcU5jxnTzxdGZ/XrkCFDFAqFtHv3bqfmww8/VCgU6njfd+gW2x7k1GPGq1evNgcPHjSFhYUmOTnZfPrpp93dtJgxZ84c4/F4zNatW00gEHCWb7/91ql5+umnjcfjMW+++aapqKgw99xzT6uPtV1xxRVmy5YtZu/evWbEiBEX/eOC5/L3T/EYQz93ht27d5v4+Hjz1FNPmUOHDpnXX3/d9O7d2xQXFzs19HPHzZw503zve99zHjN+8803TUZGhlm0aJFTQz+3T319vdm3b5/Zt2+fkWReeOEFs2/fPuf1GZ3Vr+PGjTP9+/c3O3fuNDt37jT5+fk8ZtzZ/u3f/s3k5uaaxMREc9NNNzmPx+L8SGp1efXVV52alpYW88QTTxiv12vcbrf54Q9/aCoqKiL209jYaB5++GGTlpZmevXqZSZOnGg+//zzLj6a2HJ6QKGfO8eGDRuM3+83brfbXHfddeaVV16J2E4/d1xdXZ2ZP3++ufLKK01SUpK56qqrzOOPP27C4bBTQz+3zx/+8IdWfyfPnDnTGNN5/XrkyBEzffp0k5KSYlJSUsz06dNNbW1th9vvMsaYjo3BAAAAdC7uQQEAANYhoAAAAOsQUAAAgHUIKAAAwDoEFAAAYB0CCgAAsA4BBQAAWIeAAgAArENAAQAA1iGgAAAA6xBQAACAdQgoAADAOv8L4r+IohWRUVIAAAAASUVORK5CYII=", "text/plain": [ "
" ] From 58801249ab73c00fc2322af58c27a60ef5c2723e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 14 Jul 2023 13:56:03 -0700 Subject: [PATCH 354/756] Let specified labels totally override scraping This way we have a route for handling cases scraping can't (like multiple return branches), but it comes at the user/node designer assuming that risk themself. --- pyiron_contrib/workflow/function.py | 39 ++++++++++++++++------------ tests/unit/workflow/test_function.py | 20 +++++++++++--- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index e799e715f..d6211d948 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -78,7 +78,10 @@ class Function(Node): that requires item-string-based access. Additionally, specifying a _single_ label for a wrapped function that returns a tuple of values ensures that a _single_ output channel (holding the tuple) is created, instead of one - channel for each return value. + channel for each return value. The default approach of extracting labels + from the function source code also requires that the function body contain + _at most_ one `return` expression, so providing explicit labels can be used + to circumvent this (at your own risk). **kwargs: Any additional keyword arguments whose keyword matches the label of an input channel will have their value assigned to that channel. @@ -361,26 +364,30 @@ def __init__( def _get_output_labels(self, output_labels: str | list[str] | tuple[str] | None): """ - Explicitly passed output labels can be used to rename awkward parsed labels, or - to force the creation of a _single_ output channel when wrapped functions return - a tuple of values. + If output labels are provided, turn convert them to a list if passed as a + string and return them, else scrape them from the source channel. + + Note: When the user explicitly provides output channels, they are taking + responsibility that these are correct, e.g. in terms of quantity, order, etc. """ - parsed_labels = ParseOutput(self.node_function).output if output_labels is None: - return parsed_labels if parsed_labels is not None else [] + return self._scrape_output_labels() + elif isinstance(output_labels, str): + return [output_labels] else: - if isinstance(output_labels, str): - output_labels = (output_labels,) + return output_labels - if len(output_labels) != 1 and len(output_labels) != len(parsed_labels): - raise ValueError( - f"When output labels are explicitly provided they must either be a " - f"_single_ label, or match the length of the parsed labels. In " - f"this case, {output_labels} were received while {parsed_labels} " - f"were parsed." - ) + def _scrape_output_labels(self): + """ + Inspect the source code to scrape out strings representing the returned values. + _Only_ works for functions with a single `return` expression in their body. - return output_labels + Will return expressions and function calls just fine, thus best practice is to + create well-named variables and return those so that the output labels stay + dot-accessible. + """ + parsed_outputs = ParseOutput(self.node_function).output + return [] if parsed_outputs is None else parsed_outputs @property def _input_args(self): diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index dac78dd1d..914d4d239 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -26,14 +26,22 @@ def no_default(x, y): def returns_multiple(x, y): return x, y, x + y + def void(): pass +def multiple_branches(x): + if x < 10: + return True + else: + return False + + @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestFunction(unittest.TestCase): def test_instantiation(self): - with self.subTest("Void function"): + with self.subTest("Void function is allowable"): void_node = Function(void) self.assertEqual(len(void_node.outputs), 0) @@ -71,9 +79,15 @@ def test_label_choices(self): n = Function(returns_multiple, output_labels="its_a_tuple") self.assertListEqual(n.outputs.labels, ["its_a_tuple"]) - with self.subTest("Force matching lengths"): + with self.subTest("Fail on multiple return values"): with self.assertRaises(ValueError): - Function(returns_multiple, output_labels=["one", "two"]) + # Can't automatically parse output labels from a function with multiple + # return expressions + Function(multiple_branches) + + with self.subTest("Override output label scraping"): + switch = Function(multiple_branches, output_labels="bool") + self.assertListEqual(switch.outputs.labels, ["bool"]) def test_instantiation_update(self): no_update = Function( From 2c278020e6118b88dc0e0f3a99b6d97bcc9ffbd1 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 14 Jul 2023 14:16:41 -0700 Subject: [PATCH 355/756] Update function docs --- pyiron_contrib/workflow/function.py | 83 ++++++++++++++++++----------- 1 file changed, 53 insertions(+), 30 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index d6211d948..7ff972963 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -19,8 +19,9 @@ class Function(Node): """ Function nodes wrap an arbitrary python function. - Node IO, including type hints, is generated automatically from the provided function - and (in the case of labeling output channels) the provided output labels. + Node IO, including type hints, is generated automatically from the provided + function. + On running, the function node executes this wrapped function with its current input and uses the results to populate the node output. @@ -30,16 +31,20 @@ class Function(Node): is currently no way to mix-and-match, i.e. to have multiple return values at least one of which is a tuple.) - The node label (unless otherwise provided), IO types, and input defaults for the - node are produced _automatically_ from introspection of the node function. - Additional properties like storage priority (present but doesn't do anything yet) - and ontological type (not yet present) can be set using kwarg dictionaries with - keys corresponding to the channel labels (i.e. the node arguments of the node - function, or the output labels provided). + The node label (unless otherwise provided), IO channel names, IO types, and input + defaults for the node are produced _automatically_ from introspection of the node + function. + Explicit output labels can be provided to modify the number of return values (from + $N$ to 1 in case you _want_ a tuple returned) and to dodge constraints on the + automatic scraping routine (namely, that there be _at most_ one `return` + expression). + (Additional properties like storage priority and ontological type are forthcoming + as kwarg dictionaries with keys corresponding to the channel labels (i.e. the node + arguments of the node function, or the output labels provided).) Actual function node instances can either be instances of the base node class, in - which case the callable node function and output labels *must* be provided, in - addition to other data, OR they can be instances of children of this class. + which case the callable node function *must* be provided OR they can be instances + of children of this class. Those children may define some or all of the node behaviour at the class level, and modify their signature accordingly so this is not available for alteration by the user, e.g. the node function and output labels may be hard-wired. @@ -48,6 +53,8 @@ class Function(Node): nodes should be both functional (always returning the same output given the same input) and idempotent (not modifying input data in-place, but creating copies where necessary and returning new objects as output). + Further, functions with multiple return branches that return different types or + numbers of return values may or may not work smoothly, depending on the details. By default, function nodes will attempt to run whenever one or more inputs is updated, and will attempt to update on initialization (after setting _all_ initial @@ -55,7 +62,7 @@ class Function(Node): Output is updated in the `process_run_result` inside the parent class `finish_run` call, such that output data gets pushed after the node stops running but before - then `ran` signal fires. + then `ran` signal fires: run, process and push result, ran. Args: node_function (callable): The function determining the behaviour of the node. @@ -110,9 +117,9 @@ class Function(Node): >>> def mwe(x, y): ... return x+1, y-1 >>> - >>> plus_minus_1 = Function(mwe, "p1", "m1") + >>> plus_minus_1 = Function(mwe) >>> - >>> print(plus_minus_1.outputs.p1) + >>> print(plus_minus_1.outputs["x+1"]) There is no output because we haven't given our function any input, it has @@ -133,13 +140,17 @@ class Function(Node): Once we update `y`, all the input is ready and the automatic `update()` call will be allowed to proceed to a `run()` call, which succeeds and updates the - output: - >>> plus_minus_1.inputs.x = 3 + output. + The final thing we need to do is disable the `failed` status we got from our + last run call + >>> plus_minus_1.failed = False + >>> plus_minus_1.inputs.y = 3 >>> plus_minus_1.outputs.to_value_dict() - {'p1': 3, 'm1': 2} + {'x+1': 3, 'y-1': 2} - We can also, optionally, provide initial values for some or all of the input - >>> plus_minus_1 = Function(mwe, "p1", "m1", x=1) + We can also, optionally, provide initial values for some or all of the input and + labels for the output: + >>> plus_minus_1 = Function(mwe, output_labels=("p1", "m1"), x=1) >>> plus_minus_1.inputs.y = 2 # Automatically triggers an update call now >>> plus_minus_1.outputs.to_value_dict() {'p1': 2, 'm1': 1} @@ -147,7 +158,7 @@ class Function(Node): Finally, we might stop these updates from happening automatically, even when all the input data is present and available: >>> plus_minus_1 = Function( - ... mwe, "p1", "m1", + ... mwe, output_labels=("p1", "m1"), ... x=0, y=0, ... run_on_updates=False, update_on_instantiation=False ... ) @@ -163,7 +174,7 @@ class Function(Node): if they haven't seen any input data. However, we could still get them to raise an error by providing the _wrong_ data: - >>> plus_minus_1 = Function(mwe, "p1", "m1", x=1, y="can't add to an int") + >>> plus_minus_1 = Function(mwe, x=1, y="can't add to an int") TypeError Here everything tries to run automatically, but we get an error from adding the @@ -179,15 +190,19 @@ class Function(Node): return hint. Our treatment of type hints is **not infinitely robust**, but covers a wide variety of common use cases. + Note that getting "good" (i.e. dot-accessible) output labels can be achieved by + using good variable names and returning those variables instead of using + `output_labels`: >>> from typing import Union >>> >>> def hinted_example( ... x: Union[int, float], ... y: int | float = 1 ... ) -> tuple[int, int | float]: - ... return x+1, y-1 + ... p1, m1 = x+1, y-1 + ... return p1, m1 >>> - >>> plus_minus_1 = Function(hinted_example, "p1", "m1", x="not an int") + >>> plus_minus_1 = Function(hinted_example, x="not an int") >>> plus_minus_1.outputs.to_value_dict() {'p1': , 'm1': } @@ -218,7 +233,7 @@ class Function(Node): and returns a node class: >>> from pyiron_contrib.workflow.function import function_node >>> - >>> @function_node("p1", "m1") + >>> @function_node(output_labels=("p1", "m1")) ... def my_mwe_node( ... x: int | float, y: int | float = 1 ... ) -> tuple[int | float, int | float]: @@ -248,7 +263,6 @@ class Function(Node): ... ): ... super().__init__( ... self.alphabet_mod_three, - ... "letter", ... label=label, ... run_on_updates=run_on_updates, ... update_on_instantiation=update_on_instantiation, @@ -257,7 +271,8 @@ class Function(Node): ... ... @staticmethod ... def alphabet_mod_three(i: int) -> Literal["a", "b", "c"]: - ... return ["a", "b", "c"][i % 3] + ... letter = ["a", "b", "c"][i % 3] + ... return letter Note that we've overridden the default value for `update_on_instantiation` above. @@ -274,12 +289,12 @@ class Function(Node): >>> class Adder(Function): ... @staticmethod ... def adder(x: int = 0, y: int = 0) -> int: - ... return x + y + ... sum = x + y + ... return sum ... ... __init__ = partialmethod( ... Function.__init__, ... adder, - ... "sum", ... ) Finally, let's put it all together by using both of these nodes at once. @@ -585,6 +600,9 @@ class SingleValue(Function, HasChannel): A node that _must_ return only a single value. Attribute and item access is modified to finally attempt access on the output value. + Note that this means any attributes/method available on the output value become + available directly at the node level (at least those which don't conflict with the + existing node namespace). """ def __init__( @@ -645,10 +663,11 @@ def function_node(**node_class_kwargs): A decorator for dynamically creating node classes from functions. Decorates a function. - Takes an output label for each returned value of the function. - Returns a `Function` subclass whose name is the camel-case version of the function node, - and whose signature is modified to exclude the node function and output labels + Returns a `Function` subclass whose name is the camel-case version of the function + node, and whose signature is modified to exclude the node function and output labels (which are explicitly defined in the process of using the decorator). + + Optionally takes any keyword arguments of `Function`. """ def as_node(node_function: callable): @@ -674,6 +693,8 @@ def slow_node(**node_class_kwargs): Unlike normal nodes, slow nodes do update themselves on initialization and do not run themselves when they get updated -- i.e. they will not run when their input changes, `run()` must be explicitly called. + + Optionally takes any keyword arguments of `Slow`. """ def as_slow_node(node_function: callable): @@ -697,6 +718,8 @@ def single_value_node(**node_class_kwargs): A decorator for dynamically creating fast node classes from functions. Unlike normal nodes, fast nodes _must_ have default values set for all their inputs. + + Optionally takes any keyword arguments of `SingleValueNode`. """ def as_single_value_node(node_function: callable): From be6485c1cd11088fcd48a3b6c4b0f9e0b96c18d2 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 14 Jul 2023 14:20:39 -0700 Subject: [PATCH 356/756] Update workflow docs --- pyiron_contrib/workflow/workflow.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 9ce81342e..8c9f7936d 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -34,16 +34,17 @@ class Workflow(Composite): >>> from pyiron_contrib.workflow.workflow import Workflow >>> from pyiron_contrib.workflow.function import Function >>> - >>> def fnc(x=0): return x + 1 + >>> def fnc(x=0): + ... return x + 1 >>> - >>> n1 = Function(fnc, "x", label="n1") + >>> n1 = Function(fnc, label="n1") >>> >>> wf = Workflow("my_workflow", n1) # As *args at instantiation - >>> wf.add(Function(fnc, "x", label="n2")) # Passing a node to the add caller - >>> wf.add.Function(fnc, "y", label="n3") # Instantiating from add - >>> wf.n4 = Function(fnc, "y", label="whatever_n4_gets_used") + >>> wf.add(Function(fnc, label="n2")) # Passing a node to the add caller + >>> wf.add.Function(fnc, label="n3") # Instantiating from add + >>> wf.n4 = Function(fnc, label="whatever_n4_gets_used") >>> # By attribute assignment - >>> Function(fnc, "x", label="n5", parent=wf) + >>> Function(fnc, label="n5", parent=wf) >>> # By instantiating the node with a workflow By default, the node naming scheme is strict, so if you try to add a node to a @@ -51,10 +52,10 @@ class Workflow(Composite): at instantiation with the `strict_naming` kwarg, or afterwards by assigning a bool to this property. When deactivated, repeated assignments to the same label just get appended with an index: - >>> wf.deactivate_strict_naming() - >>> wf.my_node = Function(fnc, "y", x=0) - >>> wf.my_node = Function(fnc, "y", x=1) - >>> wf.my_node = Function(fnc, "y", x=2) + >>> wf.strict_naming = False + >>> wf.my_node = Function(fnc, x=0) + >>> wf.my_node = Function(fnc, x=1) + >>> wf.my_node = Function(fnc, x=2) >>> print(wf.my_node.inputs.x, wf.my_node0.inputs.x, wf.my_node1.inputs.x) 0, 1, 2 @@ -63,7 +64,7 @@ class Workflow(Composite): workflow (cf. the `Node` docs for more detail on the node types). Let's use these to explore a workflow's input and output, which are dynamically generated from the unconnected IO of its nodes: - >>> @Workflow.wrap_as.function_node("y") + >>> @Workflow.wrap_as.function_node(output_labels="y") >>> def plus_one(x: int = 0): ... return x + 1 >>> From 0d105f7c3cea14b63d5fcfedf99e84a823d3ac81 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 14 Jul 2023 14:23:07 -0700 Subject: [PATCH 357/756] Patch env files with executor dependence --- .ci_support/environment.yml | 1 + setup.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 8adc737f5..9e6f710a7 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -2,6 +2,7 @@ channels: - conda-forge dependencies: - ase =3.22.1 +- cloudpickle - coveralls - coverage - codacy-coverage diff --git a/setup.py b/setup.py index f28b0caa0..9813be8c5 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,9 @@ 'pyiron_atomistics==0.3.0', 'pycp2k==0.2.2', ], + 'executors': [ + 'cloudpickle', + ], 'fenics': [ 'fenics==2019.1.0', 'mshr==2019.1.0', @@ -54,6 +57,7 @@ 'moto==4.1.12' ], 'workflow': [ + 'cloudpickle', 'python>=3.10', 'ipython', 'typeguard==4.0.0' From dd3e0fe0ac779a19373de6047888c412326ad715 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Fri, 14 Jul 2023 21:23:44 +0000 Subject: [PATCH 358/756] Update env file --- .binder/environment.yml | 1 + docs/environment.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.binder/environment.yml b/.binder/environment.yml index 855c200c2..95475d32e 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -2,6 +2,7 @@ channels: - conda-forge dependencies: - ase =3.22.1 +- cloudpickle - coveralls - coverage - codacy-coverage diff --git a/docs/environment.yml b/docs/environment.yml index 79e6a9c23..9623164f9 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -4,6 +4,7 @@ dependencies: - ipykernel - nbsphinx - ase =3.22.1 +- cloudpickle - coveralls - coverage - codacy-coverage From 760c3c3d950728aecb51ccf5e4037c94d321b0c8 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Fri, 14 Jul 2023 21:24:41 +0000 Subject: [PATCH 359/756] Format black --- pyiron_contrib/workflow/node_library/atomistics.py | 2 +- pyiron_contrib/workflow/output_parser.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyiron_contrib/workflow/node_library/atomistics.py b/pyiron_contrib/workflow/node_library/atomistics.py index fd304570e..7ff060c01 100644 --- a/pyiron_contrib/workflow/node_library/atomistics.py +++ b/pyiron_contrib/workflow/node_library/atomistics.py @@ -106,7 +106,7 @@ def calc_static( @slow_node( - output_labels= [ + output_labels=[ "cells", "displacements", "energy_pot", diff --git a/pyiron_contrib/workflow/output_parser.py b/pyiron_contrib/workflow/output_parser.py index 36342cc92..2f88e71e2 100644 --- a/pyiron_contrib/workflow/output_parser.py +++ b/pyiron_contrib/workflow/output_parser.py @@ -9,8 +9,8 @@ def _remove_spaces_until_character(string): - pattern = r'\s+(?=\s)' - modified_string = re.sub(pattern, '', string) + pattern = r"\s+(?=\s)" + modified_string = re.sub(pattern, "", string) return modified_string @@ -67,15 +67,15 @@ def get_string(self, node): for ll in range(node.lineno - 1, node.end_lineno): if ll == node.lineno - 1 == node.end_lineno - 1: string += _remove_spaces_until_character( - self.source[ll][node.col_offset:node.end_col_offset] + self.source[ll][node.col_offset : node.end_col_offset] ) elif ll == node.lineno - 1: string += _remove_spaces_until_character( - self.source[ll][node.col_offset:] + self.source[ll][node.col_offset :] ) elif ll == node.end_lineno - 1: string += _remove_spaces_until_character( - self.source[ll][:node.end_col_offset] + self.source[ll][: node.end_col_offset] ) else: string += _remove_spaces_until_character(self.source[ll]) From 6d194ffa98bcd241f534d2e504477a08fabff6e8 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 14 Jul 2023 14:54:57 -0700 Subject: [PATCH 360/756] Update integration test --- tests/integration/test_workflow.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py index a8f2f4d58..9b018c1bb 100644 --- a/tests/integration/test_workflow.py +++ b/tests/integration/test_workflow.py @@ -16,7 +16,7 @@ def test_cyclic_graphs(self): TODO: Update once logical switches are included in the node library """ - @Workflow.wrap_as.single_value_node("rand") + @Workflow.wrap_as.single_value_node() def numpy_randint(low=0, high=20): rand = np.random.randint(low=low, high=high) print(f"Generating random number between {low} and {high}...{rand}!") @@ -29,7 +29,11 @@ class GreaterThanLimitSwitch(Function): """ def __init__(self, **kwargs): - super().__init__(self.greater_than, "value_gt_limit", **kwargs) + super().__init__( + self.greater_than, + output_labels="value_gt_limit", + **kwargs + ) self.signals.output.true = OutputSignal("true", self) self.signals.output.false = OutputSignal("false", self) @@ -50,7 +54,7 @@ def process_run_result(self, function_output): print(f"{self.inputs.value.value} <= {self.inputs.limit.value}") self.signals.output.false() - @Workflow.wrap_as.single_value_node("sqrt") + @Workflow.wrap_as.single_value_node() def numpy_sqrt(value=0): sqrt = np.sqrt(value) print(f"sqrt({value}) = {sqrt}") From c7227eba902734d71036021b9933b7be256230c2 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 14 Jul 2023 14:57:45 -0700 Subject: [PATCH 361/756] Satisfy codacy nits --- tests/unit/workflow/test_function.py | 5 +++-- tests/unit/workflow/test_output_parser.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 914d4d239..d441b24f1 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -299,10 +299,11 @@ def test_instantiation(self): @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestSingleValue(unittest.TestCase): def test_instantiation(self): - has_defaults_and_one_return = SingleValue(plus_one) + SingleValue(plus_one) with self.assertRaises(ValueError): - too_many_labels = SingleValue(plus_one, output_labels=["z", "excess_label"]) + # Too many labels + SingleValue(plus_one, output_labels=["z", "excess_label"]) def test_item_and_attribute_access(self): class Foo: diff --git a/tests/unit/workflow/test_output_parser.py b/tests/unit/workflow/test_output_parser.py index e8b5f066f..84b63b3de 100644 --- a/tests/unit/workflow/test_output_parser.py +++ b/tests/unit/workflow/test_output_parser.py @@ -22,9 +22,9 @@ def add(x, y): self.assertListEqual(ParseOutput(add).output, ["x + y"]) with self.subTest("Weird whitespace"): - def add(x, y): + def add_with_whitespace(x, y): return x + y - self.assertListEqual(ParseOutput(add).output, ["x + y"]) + self.assertListEqual(ParseOutput(add_with_whitespace).output, ["x + y"]) with self.subTest("Multiple expressions"): def add_and_subtract(x, y): From 111d9e85967aa4ed66e941cbce7cf61c86dd0332 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:07:58 +0000 Subject: [PATCH 362/756] Bump pympipool from 0.5.1 to 0.5.4 Bumps [pympipool](https://github.com/jan-janssen/pympipool) from 0.5.1 to 0.5.4. - [Release notes](https://github.com/jan-janssen/pympipool/releases) - [Commits](https://github.com/jan-janssen/pympipool/compare/pympipool-0.5.1...pympipool-0.5.4) --- updated-dependencies: - dependency-name: pympipool dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f28b0caa0..b2584441c 100644 --- a/setup.py +++ b/setup.py @@ -60,7 +60,7 @@ ], 'tinybase': [ 'distributed==2023.5.0', - 'pympipool==0.5.1' + 'pympipool==0.5.4' ] }, cmdclass=versioneer.get_cmdclass(), From 3220831218f692c5553a89596433b611f187fca7 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 17 Jul 2023 11:08:19 +0000 Subject: [PATCH 363/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 8adc737f5..0301b9d45 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -20,5 +20,5 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.68.0 -- pympipool =0.5.1 +- pympipool =0.5.4 - distributed =2023.5.0 From 6189db01f389101fedbb289e178f852c4b835c87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:08:29 +0000 Subject: [PATCH 364/756] Bump pyiron-base from 0.6.1 to 0.6.2 Bumps [pyiron-base](https://github.com/pyiron/pyiron_base) from 0.6.1 to 0.6.2. - [Release notes](https://github.com/pyiron/pyiron_base/releases) - [Changelog](https://github.com/pyiron/pyiron_base/blob/main/CHANGELOG.md) - [Commits](https://github.com/pyiron/pyiron_base/compare/pyiron_base-0.6.1...pyiron_base-0.6.2) --- updated-dependencies: - dependency-name: pyiron-base dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f28b0caa0..051058347 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ install_requires=[ 'matplotlib==3.7.1', 'numpy==1.24.3', - 'pyiron_base==0.6.1', + 'pyiron_base==0.6.2', 'scipy==1.10.1', 'seaborn==0.12.2', 'pyparsing==3.1.0' From 04e9b5f291dca3f39a407f062b4bdca9de83f81a Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 17 Jul 2023 11:08:47 +0000 Subject: [PATCH 365/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 855c200c2..409592aa1 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -20,7 +20,7 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.68.0 -- pympipool =0.5.1 +- pympipool =0.5.4 - distributed =2023.5.0 - python >= 3.10 - lammps diff --git a/docs/environment.yml b/docs/environment.yml index 79e6a9c23..1ec5816b8 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -22,5 +22,5 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.68.0 -- pympipool =0.5.1 +- pympipool =0.5.4 - distributed =2023.5.0 From 67e3643beab8b1888a79f61c91e687d936fa3c26 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 17 Jul 2023 11:08:52 +0000 Subject: [PATCH 366/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 8adc737f5..87f2e89c0 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -8,7 +8,7 @@ dependencies: - ipython - matplotlib =3.7.1 - numpy =1.24.3 -- pyiron_base =0.6.1 +- pyiron_base =0.6.2 - pyiron_atomistics =0.3.0 - pyparsing =3.1.0 - scipy =1.10.1 From 7b20edb222cdc18bbf321826943930f68d8d2db4 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 17 Jul 2023 11:09:15 +0000 Subject: [PATCH 367/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 855c200c2..036ca1629 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -8,7 +8,7 @@ dependencies: - ipython - matplotlib =3.7.1 - numpy =1.24.3 -- pyiron_base =0.6.1 +- pyiron_base =0.6.2 - pyiron_atomistics =0.3.0 - pyparsing =3.1.0 - scipy =1.10.1 diff --git a/docs/environment.yml b/docs/environment.yml index 79e6a9c23..ee9121183 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -10,7 +10,7 @@ dependencies: - ipython - matplotlib =3.7.1 - numpy =1.24.3 -- pyiron_base =0.6.1 +- pyiron_base =0.6.2 - pyiron_atomistics =0.3.0 - pyparsing =3.1.0 - scipy =1.10.1 From 4c2ae4c38b60dde1e326fbc37b06b34f64168905 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 17 Jul 2023 13:54:43 +0200 Subject: [PATCH 368/756] Bump aws-sam-translator --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 87f2e89c0..b7ef34db4 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -19,6 +19,6 @@ dependencies: - moto =4.1.12 - pycp2k =0.2.2 - typeguard =4.0.0 -- aws-sam-translator =1.68.0 +- aws-sam-translator =1.70.0 - pympipool =0.5.1 - distributed =2023.5.0 From ec932fc88c12109042b7fd00346f82692193ab01 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 17 Jul 2023 11:55:05 +0000 Subject: [PATCH 369/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 036ca1629..112a2a21d 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -19,7 +19,7 @@ dependencies: - moto =4.1.12 - pycp2k =0.2.2 - typeguard =4.0.0 -- aws-sam-translator =1.68.0 +- aws-sam-translator =1.70.0 - pympipool =0.5.1 - distributed =2023.5.0 - python >= 3.10 diff --git a/docs/environment.yml b/docs/environment.yml index ee9121183..37d1f8484 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -21,6 +21,6 @@ dependencies: - moto =4.1.12 - pycp2k =0.2.2 - typeguard =4.0.0 -- aws-sam-translator =1.68.0 +- aws-sam-translator =1.70.0 - pympipool =0.5.1 - distributed =2023.5.0 From e7b67e44801fb7b46676a870467224d6be21e20f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 15:19:53 -0700 Subject: [PATCH 370/756] Rebasing args as input onto output labels as kwargs --- pyiron_contrib/workflow/function.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 7ff972963..d4a894b64 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -365,18 +365,28 @@ def __init__( ) self._verify_that_channels_requiring_update_all_exist() - self.run_on_updates = False - # Temporarily disable running on updates to set all initial values at once + self.run_on_updates = run_on_updates + self._batch_update_input(**kwargs) + + if update_on_instantiation: + self.update() + + def _batch_update_input(self, **kwargs): + """ + Temporarily disable running on updates to set all input values at once. + + Args: + **kwargs: input label - input value (including channels for connection) + pairs. + """ + run_on_updates, self.run_on_updates = self.run_on_updates, False for k, v in kwargs.items(): if k in self.inputs.labels: self.inputs[k] = v - elif k not in self._init_keywords: - warnings.warn(f"The keyword '{k}' was received but not used.") + elif k not in self._input_args.keys(): + warnings.warn(f"The keyword '{k}' was not found among input labels.") self.run_on_updates = run_on_updates # Restore provided value - if update_on_instantiation: - self.update() - def _get_output_labels(self, output_labels: str | list[str] | tuple[str] | None): """ If output labels are provided, turn convert them to a list if passed as a From 9cab601e2518872f5a38d3a69e63a90d7625c3a3 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 12 Jul 2023 15:38:20 -0700 Subject: [PATCH 371/756] Use __call__ to batch-update inputs --- pyiron_contrib/workflow/function.py | 33 ++++++++++++++++++++++++++-- tests/unit/workflow/test_function.py | 29 ++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index d4a894b64..88749d635 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -64,6 +64,11 @@ class Function(Node): call, such that output data gets pushed after the node stops running but before then `ran` signal fires: run, process and push result, ran. + After a node is instantiated, its input can be updated as `*args` and/or `**kwargs` + on call. + This invokes an `update()` call, which can in turn invoke `run()` if + `run_on_updates` is set to `True`. + Args: node_function (callable): The function determining the behaviour of the node. label (str): The node's label. (Defaults to the node function's name.) @@ -561,8 +566,32 @@ def process_run_result(self, function_output): for out, value in zip(self.outputs, function_output): out.update(value) - def __call__(self) -> None: - self.run() + def _convert_input_args_and_kwargs_to_input_kwargs(self, *args, **kwargs): + reverse_keys = list(self._input_args.keys())[::-1] + if len(args) > len(reverse_keys): + raise ValueError( + f"Received {len(args)} positional arguments, but the node {self.label}" + f"only accepts {len(reverse_keys)} inputs." + ) + + positional_keywords = reverse_keys[-len(args):] + if len(set(positional_keywords).intersection(kwargs.keys())) > 0: + raise ValueError( + f"Cannot use {set(positional_keywords).intersection(kwargs.keys())} " + f"as both positional _and_ keyword arguments" + ) + + for arg in args: + key = positional_keywords.pop() + kwargs[key] = arg + + return kwargs + + def __call__(self, *args, **kwargs) -> None: + kwargs = self._convert_input_args_and_kwargs_to_input_kwargs(*args, **kwargs) + self._batch_update_input(**kwargs) + if self.run_on_updates: + self.run() def to_dict(self): return { diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index d441b24f1..df4cdbf53 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -271,6 +271,35 @@ def with_messed_self(x: float, self) -> float: self.assertEqual(len(warning_list), 1) + def test_call(self): + node = Function(no_default, "output", run_on_updates=False) + + with self.assertRaises(ValueError): + # More input args than there are input channels + node(1, 2, 3) + + with self.assertRaises(ValueError): + # Using input as an arg _and_ a kwarg + node(1, y=2, x=3) + + node(1, y=2) + self.assertEqual( + node.inputs.x.value, 1, msg="__call__ should accept args to update input" + ) + self.assertEqual( + node.inputs.y.value, 2, msg="__call__ should accept kwargs to update input" + ) + self.assertEqual( + node.outputs.output.value, NotData, msg="__call__ should not run things" + ) + node.run_on_updates = True + node(3) # Implicitly test partial update + self.assertEqual( + no_default(3, 2), + node.outputs.output.value, + msg="__call__ should invoke update s.t. run gets called if run_on_updates" + ) + @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestSlow(unittest.TestCase): From b02f81481fa941f6c141c92b032cb36512f5c9fe Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 09:44:39 -0700 Subject: [PATCH 372/756] Make output label a kwarg not arg --- tests/unit/workflow/test_function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index df4cdbf53..b4d8ec0fb 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -272,7 +272,7 @@ def with_messed_self(x: float, self) -> float: self.assertEqual(len(warning_list), 1) def test_call(self): - node = Function(no_default, "output", run_on_updates=False) + node = Function(no_default, output_labels="output", run_on_updates=False) with self.assertRaises(ValueError): # More input args than there are input channels From 3647d9056ce20cded4219f2ac033461d89870b02 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 10:00:54 -0700 Subject: [PATCH 373/756] Split test into subtests --- tests/unit/workflow/test_function.py | 53 +++++++++++++++------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index b4d8ec0fb..f81d63e98 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -274,31 +274,36 @@ def with_messed_self(x: float, self) -> float: def test_call(self): node = Function(no_default, output_labels="output", run_on_updates=False) - with self.assertRaises(ValueError): - # More input args than there are input channels - node(1, 2, 3) - - with self.assertRaises(ValueError): - # Using input as an arg _and_ a kwarg - node(1, y=2, x=3) + with self.subTest("Ensure desired failures occur"): + with self.assertRaises(ValueError): + # More input args than there are input channels + node(1, 2, 3) - node(1, y=2) - self.assertEqual( - node.inputs.x.value, 1, msg="__call__ should accept args to update input" - ) - self.assertEqual( - node.inputs.y.value, 2, msg="__call__ should accept kwargs to update input" - ) - self.assertEqual( - node.outputs.output.value, NotData, msg="__call__ should not run things" - ) - node.run_on_updates = True - node(3) # Implicitly test partial update - self.assertEqual( - no_default(3, 2), - node.outputs.output.value, - msg="__call__ should invoke update s.t. run gets called if run_on_updates" - ) + with self.assertRaises(ValueError): + # Using input as an arg _and_ a kwarg + node(1, y=2, x=3) + + with self.subTest("Make sure data updates work as planned"): + node(1, y=2) + self.assertEqual( + node.inputs.x.value, 1, msg="__call__ should accept args to update input" + ) + self.assertEqual( + node.inputs.y.value, 2, msg="__call__ should accept kwargs to update input" + ) + self.assertEqual( + node.outputs.output.value, NotData, msg="__call__ should not run things" + ) + node.run_on_updates = True + node(3) # Implicitly test partial update + self.assertEqual( + no_default(3, 2), + node.outputs.output.value, + msg="__call__ should invoke update s.t. run gets called if run_on_updates" + ) + + with self.subTest("Check that node kwargs can also be updated"): + pass @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") From 83dfabb0e95f75ead77c1faafe83fd691b8d4725 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 10:06:05 -0700 Subject: [PATCH 374/756] Fail more generally if input not found --- pyiron_contrib/workflow/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 88749d635..649c2efbe 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -388,7 +388,7 @@ def _batch_update_input(self, **kwargs): for k, v in kwargs.items(): if k in self.inputs.labels: self.inputs[k] = v - elif k not in self._input_args.keys(): + else: warnings.warn(f"The keyword '{k}' was not found among input labels.") self.run_on_updates = run_on_updates # Restore provided value From 39734896994393714685602ed1605f6a526ea3b3 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 10:07:32 -0700 Subject: [PATCH 375/756] Make error message more informative --- pyiron_contrib/workflow/function.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 649c2efbe..b7435f3d1 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -389,7 +389,12 @@ def _batch_update_input(self, **kwargs): if k in self.inputs.labels: self.inputs[k] = v else: - warnings.warn(f"The keyword '{k}' was not found among input labels.") + warnings.warn( + f"The keyword '{k}' was not found among input labels. If you are " + f"trying to update a node keyword, please use attribute assignment " + f"directly instead of calling, e.g. " + f"`my_node_instance.run_on_updates = False`." + ) self.run_on_updates = run_on_updates # Restore provided value def _get_output_labels(self, output_labels: str | list[str] | tuple[str] | None): From 8233cb02257ab749b96f2046c06fdcfe28048c0d Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 10:09:44 -0700 Subject: [PATCH 376/756] Refactor: pull method up to parent --- pyiron_contrib/workflow/function.py | 21 --------------------- pyiron_contrib/workflow/node.py | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index b7435f3d1..598edd8e2 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -376,27 +376,6 @@ def __init__( if update_on_instantiation: self.update() - def _batch_update_input(self, **kwargs): - """ - Temporarily disable running on updates to set all input values at once. - - Args: - **kwargs: input label - input value (including channels for connection) - pairs. - """ - run_on_updates, self.run_on_updates = self.run_on_updates, False - for k, v in kwargs.items(): - if k in self.inputs.labels: - self.inputs[k] = v - else: - warnings.warn( - f"The keyword '{k}' was not found among input labels. If you are " - f"trying to update a node keyword, please use attribute assignment " - f"directly instead of calling, e.g. " - f"`my_node_instance.run_on_updates = False`." - ) - self.run_on_updates = run_on_updates # Restore provided value - def _get_output_labels(self, output_labels: str | list[str] | tuple[str] | None): """ If output labels are provided, turn convert them to a list if passed as a diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 76e67733e..be4a0e6f3 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -5,6 +5,7 @@ from __future__ import annotations +import warnings from abc import ABC, abstractmethod from concurrent.futures import Future from typing import Optional, TYPE_CHECKING @@ -275,3 +276,24 @@ def fully_connected(self): and self.outputs.fully_connected and self.signals.fully_connected ) + + def _batch_update_input(self, **kwargs): + """ + Temporarily disable running on updates to set all input values at once. + + Args: + **kwargs: input label - input value (including channels for connection) + pairs. + """ + run_on_updates, self.run_on_updates = self.run_on_updates, False + for k, v in kwargs.items(): + if k in self.inputs.labels: + self.inputs[k] = v + else: + warnings.warn( + f"The keyword '{k}' was not found among input labels. If you are " + f"trying to update a node keyword, please use attribute assignment " + f"directly instead of calling, e.g. " + f"`my_node_instance.run_on_updates = False`." + ) + self.run_on_updates = run_on_updates # Restore provided value From 8b541fde5f29e4e26996228f138af6ce6fffdccd Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 10:11:03 -0700 Subject: [PATCH 377/756] Move call up to Node --- pyiron_contrib/workflow/function.py | 4 +--- pyiron_contrib/workflow/node.py | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 598edd8e2..fc8f38ad8 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -573,9 +573,7 @@ def _convert_input_args_and_kwargs_to_input_kwargs(self, *args, **kwargs): def __call__(self, *args, **kwargs) -> None: kwargs = self._convert_input_args_and_kwargs_to_input_kwargs(*args, **kwargs) - self._batch_update_input(**kwargs) - if self.run_on_updates: - self.run() + return super().__call__(**kwargs) def to_dict(self): return { diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index be4a0e6f3..b9f901c3c 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -297,3 +297,8 @@ def _batch_update_input(self, **kwargs): f"`my_node_instance.run_on_updates = False`." ) self.run_on_updates = run_on_updates # Restore provided value + + def __call__(self, **kwargs) -> None: + self._batch_update_input(**kwargs) + if self.run_on_updates: + self.run() From 75e5cfedeaacc8bc5ce5a8285e0215ff41539c43 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 10:25:58 -0700 Subject: [PATCH 378/756] Test unused kwargs showing up in the call --- tests/unit/workflow/test_function.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index f81d63e98..d5370b66a 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -303,7 +303,19 @@ def test_call(self): ) with self.subTest("Check that node kwargs can also be updated"): - pass + with self.assertWarns(Warning): + node(4, run_on_updates=False, y=5) + + self.assertTupleEqual( + (node.inputs.x.value, node.inputs.y.value), + (4, 5), + msg="The warning should not prevent other data from being parsed" + ) + + with self.assertWarns(Warning): + # It's also fine if you just have a typo in your kwarg or whatever, + # there should just be a warning that the data didn't get updated + node(some_randome_kwaaaaarg="foo") @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") From 24eced0cafdfbd3987b0ec2320f0cd725ee106a3 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 10:28:32 -0700 Subject: [PATCH 379/756] Use update directly Instead of (incorrectly!) reproducing it --- pyiron_contrib/workflow/node.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index b9f901c3c..0e46dbce7 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -300,5 +300,4 @@ def _batch_update_input(self, **kwargs): def __call__(self, **kwargs) -> None: self._batch_update_input(**kwargs) - if self.run_on_updates: - self.run() + self.update() From a14488fe44ba9ae225322620b930b6c838ff7182 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 10:54:27 -0700 Subject: [PATCH 380/756] Hotfix: Give NotData a __repr__ Since we use the class directly instead of instances. Otherwise when we try to __repr__ SingleValueNodes that don't yet have data, the lack of a repr throws a type error --- pyiron_contrib/workflow/channels.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index d76f7406b..8ddaf9bda 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -141,7 +141,11 @@ class NotData: not identify as ready. """ - pass + @classmethod + def __repr__(cls): + # We use the class directly (not instances of it) where there is not yet data + # So give it a decent repr, even as just a class + return cls class DataChannel(Channel, ABC): From 36a3e3f102e0e1fa6b139b705cd5af5288b20c25 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 11:05:12 -0700 Subject: [PATCH 381/756] Hotfix: represeting NotData --- pyiron_contrib/workflow/channels.py | 2 +- tests/unit/workflow/test_function.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index 8ddaf9bda..71a2ebb98 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -145,7 +145,7 @@ class NotData: def __repr__(cls): # We use the class directly (not instances of it) where there is not yet data # So give it a decent repr, even as just a class - return cls + return cls.__name__ class DataChannel(Channel, ABC): diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 5d2d15869..7af6c4243 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -325,6 +325,15 @@ def test_str(self): "actually still a Function and not just the value you're seeing.)" ) + def test_repr(self): + svn = SingleValue(no_default, "output") + self.assertIs(svn.outputs.output.value, NotData) + self.assertTrue( + svn.__repr__().endswith(NotData.__name__), + msg="When the output is still not data, the representation should indicate " + "this" + ) + def test_easy_output_connection(self): svn = SingleValue(plus_one, "y") regular = Function(plus_one, "y") From 33f770cad72fa03ae68f7ae1fa2709e45f865554 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 11:15:34 -0700 Subject: [PATCH 382/756] Update SingleValueNode tests --- tests/unit/workflow/test_function.py | 29 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 082c41045..b7750c33c 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -349,11 +349,21 @@ def returns_foo() -> Foo: ) def test_repr(self): - svn = SingleValue(plus_one) - self.assertEqual( - svn.__repr__(), svn.outputs.y.value.__repr__(), - msg="SingleValueNodes should have their output as their representation" - ) + with self.subTest("Filled data"): + svn = SingleValue(plus_one) + self.assertEqual( + svn.__repr__(), svn.outputs.y.value.__repr__(), + msg="SingleValueNodes should have their output as their representation" + ) + + with self.subTest("Not data"): + svn = SingleValue(no_default, output_labels="output") + self.assertIs(svn.outputs.output.value, NotData) + self.assertTrue( + svn.__repr__().endswith(NotData.__name__), + msg="When the output is still not data, the representation should " + "indicate this" + ) def test_str(self): svn = SingleValue(plus_one) @@ -364,15 +374,6 @@ def test_str(self): "actually still a Function and not just the value you're seeing.)" ) - def test_repr(self): - svn = SingleValue(no_default, "output") - self.assertIs(svn.outputs.output.value, NotData) - self.assertTrue( - svn.__repr__().endswith(NotData.__name__), - msg="When the output is still not data, the representation should indicate " - "this" - ) - def test_easy_output_connection(self): svn = SingleValue(plus_one) regular = Function(plus_one) From 8c4a2d43398f7949f71cf72aadabcb000c0dfff5 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 12:51:06 -0700 Subject: [PATCH 383/756] Also parse args at input --- pyiron_contrib/workflow/function.py | 7 ++++++- tests/unit/workflow/test_function.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index fc8f38ad8..607a6a74c 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -340,6 +340,7 @@ class Function(Node): def __init__( self, node_function: callable, + *args, label: Optional[str] = None, run_on_updates: bool = True, update_on_instantiation: bool = True, @@ -371,7 +372,7 @@ def __init__( self._verify_that_channels_requiring_update_all_exist() self.run_on_updates = run_on_updates - self._batch_update_input(**kwargs) + self._batch_update_input(*args, **kwargs) if update_on_instantiation: self.update() @@ -571,6 +572,10 @@ def _convert_input_args_and_kwargs_to_input_kwargs(self, *args, **kwargs): return kwargs + def _batch_update_input(self, *args, **kwargs): + kwargs = self._convert_input_args_and_kwargs_to_input_kwargs(*args, **kwargs) + return super()._batch_update_input(**kwargs) + def __call__(self, *args, **kwargs) -> None: kwargs = self._convert_input_args_and_kwargs_to_input_kwargs(*args, **kwargs) return super().__call__(**kwargs) diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 8545501f7..5cfffe8b3 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -45,6 +45,23 @@ def test_instantiation(self): void_node = Function(void) self.assertEqual(len(void_node.outputs), 0) + with self.subTest("Args and kwargs at initialization"): + node = Function(returns_multiple, 1, y=2) + self.assertEqual( + node.inputs.x.value, + 1, + msg="Should be able to set function input as args" + ) + self.assertEqual( + node.inputs.y.value, + 2, + msg="Should be able to set function input as kwargs" + ) + + with self.assertRaises(ValueError): + # Can't pass more args than the function takes + Function(returns_multiple, 1, 2, 3) + def test_defaults(self): with_defaults = Function(plus_one) self.assertEqual( From a591b1eadd242802cf49c97d387af1473b24b26f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 13:15:40 -0700 Subject: [PATCH 384/756] :bug: Allow args in children of Function as well --- pyiron_contrib/workflow/function.py | 8 ++++++-- tests/unit/workflow/test_function.py | 22 +++++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 607a6a74c..2edd996d8 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -559,11 +559,11 @@ def _convert_input_args_and_kwargs_to_input_kwargs(self, *args, **kwargs): f"only accepts {len(reverse_keys)} inputs." ) - positional_keywords = reverse_keys[-len(args):] + positional_keywords = reverse_keys[-len(args):] if len(args) > 0 else [] # -0: if len(set(positional_keywords).intersection(kwargs.keys())) > 0: raise ValueError( f"Cannot use {set(positional_keywords).intersection(kwargs.keys())} " - f"as both positional _and_ keyword arguments" + f"as both positional _and_ keyword arguments; args {args}, kwargs {kwargs}, reverse_keys {reverse_keys}, positional_keyworkds {positional_keywords}" ) for arg in args: @@ -603,6 +603,7 @@ class Slow(Function): def __init__( self, node_function: callable, + *args, label: Optional[str] = None, run_on_updates=False, update_on_instantiation=False, @@ -612,6 +613,7 @@ def __init__( ): super().__init__( node_function, + *args, label=label, run_on_updates=run_on_updates, update_on_instantiation=update_on_instantiation, @@ -634,6 +636,7 @@ class SingleValue(Function, HasChannel): def __init__( self, node_function: callable, + *args, label: Optional[str] = None, run_on_updates=True, update_on_instantiation=True, @@ -643,6 +646,7 @@ def __init__( ): super().__init__( node_function, + *args, label=label, run_on_updates=run_on_updates, update_on_instantiation=update_on_instantiation, diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 5cfffe8b3..70aa9f2d6 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -358,11 +358,31 @@ def test_instantiation(self): f"{slow.outputs.y.value}" ) + node = Slow(no_default, 1, y=2, output_labels="output") + node.run() + self.assertEqual( + no_default(1, 2), + node.outputs.output.value, + msg="Slow nodes should allow input initialization by arg and kwarg" + ) + node(2, y=3) + node.run() + self.assertEqual( + no_default(2, 3), + node.outputs.output.value, + msg="Slow nodes should allow input update on call by arg and kwarg" + ) + @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestSingleValue(unittest.TestCase): def test_instantiation(self): - SingleValue(plus_one) + node = SingleValue(no_default, 1, y=2, output_labels="output") + self.assertEqual( + no_default(1, 2), + node.outputs.output.value, + msg="Single value node should allow function input by arg and kwarg" + ) with self.assertRaises(ValueError): # Too many labels From 846c177b160e68f5c67d98f2f8e635e3d9a9ca63 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 13:16:35 -0700 Subject: [PATCH 385/756] :bug: remove output label specification --- tests/unit/workflow/test_function.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 70aa9f2d6..d0251eae4 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -154,10 +154,10 @@ def test_instantiation_update(self): ) def test_input_kwargs(self): - node = Function(plus_one, "y", x=2) + node = Function(plus_one, x=2) self.assertEqual(3, node.outputs.y.value, msg="Initialize from value") - node2 = Function(plus_one, "y", x=node.outputs.y) + node2 = Function(plus_one, x=node.outputs.y) node.update() self.assertEqual(4, node2.outputs.y.value, msg="Initialize from connection") From c86acd958134dc84a31c4a4c9f24de31633247f0 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 13:20:04 -0700 Subject: [PATCH 386/756] Test calling a workflow --- tests/unit/workflow/test_workflow.py | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index b9dd2fd4a..b0bf3751c 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -189,6 +189,36 @@ def sum(a, b): "callback, and downstream nodes should proceed" ) + def test_call(self): + wf = Workflow("wf") + + wf.a = wf.add.SingleValue(fnc) + wf.b = wf.add.SingleValue(fnc) + + @Workflow.wrap_as.single_value_node(output_labels="sum") + def sum_(a, b): + return a + b + + wf.sum = sum_(wf.a, wf.b) + self.assertEqual( + wf.a.outputs.y.value + wf.b.outputs.y.value, + wf.sum.outputs.sum.value, + msg="Sanity check" + ) + wf(a_x=42, b_x=42) + self.assertEqual( + fnc(42) + fnc(42), + wf.sum.outputs.sum.value, + msg="Workflow should accept input channel kwargs and update inputs " + "accordingly" + # Since the nodes run automatically, there is no need for wf.run() here + ) + + with self.assertRaises(TypeError): + # IO is not ordered, so args make no sense for a workflow call + # We _must_ use kwargs + wf(42, 42) + if __name__ == '__main__': unittest.main() From 4f9052182a43b8f62a6878b0fc81c3b1edaa0937 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 13:29:48 -0700 Subject: [PATCH 387/756] Add __call__ stuff to docstrings --- pyiron_contrib/workflow/function.py | 8 ++++++++ pyiron_contrib/workflow/workflow.py | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 2edd996d8..883bae4bf 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -21,6 +21,8 @@ class Function(Node): Function nodes wrap an arbitrary python function. Node IO, including type hints, is generated automatically from the provided function. + Input data for the wrapped function can be provided as any valid combination of + `*arg` and `**kwarg` at both initialization and on calling the node. On running, the function node executes this wrapped function with its current input and uses the results to populate the node output. @@ -160,6 +162,12 @@ class Function(Node): >>> plus_minus_1.outputs.to_value_dict() {'p1': 2, 'm1': 1} + Input data can be provided to both initialization and on call as ordered args + or keyword kwargs, e.g.: + >>> plus_minus_1(2, y=3) + >>> plus_minus_1.outputs.to_value_dict() + {'p1': 3, 'm1': 2} + Finally, we might stop these updates from happening automatically, even when all the input data is present and available: >>> plus_minus_1 = Function( diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 8c9f7936d..73d18f648 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -85,6 +85,13 @@ class Workflow(Composite): >>> print(wf.outputs.second_y.value) 2 + 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 + 12 + Workflows also give access to packages of pre-built nodes under different namespaces, e.g. >>> wf = Workflow("with_prebuilt") From 4fa1d30e4b5916670a85adf7ab82a13237b79355 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 13:47:04 -0700 Subject: [PATCH 388/756] Update the example notebook --- notebooks/workflow_example.ipynb | 315 +++++++++++++++++++++---------- 1 file changed, 220 insertions(+), 95 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 1ef82d6f8..9ed57c66e 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -4,12 +4,14 @@ "cell_type": "code", "execution_count": 1, "id": "8dee8129-6b23-4abf-90d2-217d71b8ba7a", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e23eaad8312941fbbaa0683e71bc8ed6", + "model_id": "88c66e527673496c8f5b7ea75538baa0", "version_major": 2, "version_minor": 0 }, @@ -325,6 +327,36 @@ "adder_node.outputs.sum_.value" ] }, + { + "cell_type": "markdown", + "id": "416ba898-21ee-4638-820f-0f04a98a6706", + "metadata": {}, + "source": [ + "We can also set new input as any valid combination of kwargs and/or args at both instantiation or on call:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "0c8f09a7-67c4-4c6c-a021-e3fea1a16576", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "30" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "adder_node(10, y=20)\n", + "adder_node.outputs.sum_.value" + ] + }, { "cell_type": "markdown", "id": "07a22cee-e340-4551-bb81-07d8be1d152b", @@ -341,7 +373,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "id": "61b43a9b-8dad-48b7-9194-2045e465793b", "metadata": {}, "outputs": [], @@ -351,7 +383,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "id": "647360a9-c971-4272-995c-aa01e5f5bb83", "metadata": {}, "outputs": [ @@ -389,7 +421,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "id": "8fb0671b-045a-4d71-9d35-f0beadc9cf3a", "metadata": {}, "outputs": [ @@ -399,13 +431,13 @@ "-10" ] }, - "execution_count": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "subtract_node(x=10, y=20).outputs.diff.value" + "subtract_node(10, 20).outputs.diff.value" ] }, { @@ -420,7 +452,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "5ce91f42-7aec-492c-94fb-2320c971cd79", "metadata": {}, "outputs": [ @@ -439,7 +471,7 @@ " return sum_\n", "\n", "add1 = add_node()\n", - "add2 = add_node(x=2, y=2)\n", + "add2 = add_node(2, 2)\n", "sub = subtract_node(x=add1.outputs.sum_, y=add2.outputs.sum_)\n", "print(\n", " f\"{add1.outputs.sum_.value} - {add2.outputs.sum_.value} = {sub.outputs.diff.value}\"\n", @@ -456,7 +488,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "20360fe7-b422-4d78-9bd1-de233f28c8df", "metadata": {}, "outputs": [ @@ -489,7 +521,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "1a4e9693-0980-4435-aecc-3331d8b608dd", "metadata": {}, "outputs": [], @@ -501,7 +533,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "7c4d314b-33bb-4a67-bfb9-ed77fba3949c", "metadata": {}, "outputs": [ @@ -530,77 +562,6 @@ "print(lin.mean()) # Finds the method on the output -- a special feature of SingleValueNode" ] }, - { - "cell_type": "markdown", - "id": "a1a9daa5-9c12-4c2f-b8bd-a54a5fc60feb", - "metadata": {}, - "source": [ - "# Workflows\n", - "\n", - "Typically, you will have a group of nodes working together with their connections.\n", - "We call these groups workflows, and offer a `Workflow(Node)` object as a single point of entry -- i.e. most of the time you shouldn't need the node imports used above, because the decorators are available right on the workflow class.\n", - "\n", - "We can also rename our node output channels using the `output_labels: Optional[str | list[str] | tuple[str]` kwarg, which we'll see here" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "1cd000bd-9b24-4c39-9cac-70a3291d0660", - "metadata": {}, - "outputs": [], - "source": [ - "from pyiron_contrib.workflow.workflow import Workflow\n", - "\n", - "@Workflow.wrap_as.single_value_node(output_labels=\"is_greater\")\n", - "def greater_than_half(x: int | float | bool = 0) -> bool:\n", - " \"\"\"The functionality doesn't matter here, it's just an example\"\"\"\n", - " return x > 0.5" - ] - }, - { - "cell_type": "markdown", - "id": "ceef526f-3583-4d87-a69d-1ac3d2e706d2", - "metadata": {}, - "source": [ - "## Adding nodes to a workflow\n", - "\n", - "All five of the following approaches are equivalent ways to add a node to a workflow:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "7964df3c-55af-4c25-afc5-9e07accb606a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "n1 n1 n1 (GreaterThanHalf) output single-value: False\n", - "n3 n3 n3 (GreaterThanHalf) output single-value: False\n", - "n4 n4 n4 (GreaterThanHalf) output single-value: False\n", - "n5 n5 n5 (GreaterThanHalf) output single-value: False\n" - ] - } - ], - "source": [ - "from pyiron_contrib.workflow.function import Slow\n", - "\n", - "n1 = greater_than_half(label=\"n1\")\n", - "\n", - "wf = Workflow(\"my_wf\", n1) # As args at init\n", - "wf.add.Slow(lambda: x + 1, output_labels=\"p1\", label=\"n2\") # Instantiating from the class with a lambda function\n", - "# (Slow since we don't have an x default)\n", - "wf.add(greater_than_half(label=\"n3\")) # Instantiating then passing to node adder\n", - "wf.n4 = greater_than_half(label=\"will_get_overwritten_with_n4\") # Set attribute to instance\n", - "greater_than_half(label=\"n5\", parent=wf) # By passing the workflow to the node\n", - "\n", - "for k, v in wf.nodes.items():\n", - " print(k, v.label, v)" - ] - }, { "cell_type": "markdown", "id": "9b9220b0-833d-4c6a-9929-5dfa60a47d14", @@ -619,7 +580,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "id": "2e418abf-7059-4e1e-9b9f-b3dc0a4b5e35", "metadata": {}, "outputs": [ @@ -660,7 +621,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "id": "3310eac4-04f6-421b-9824-19bb2d680be6", "metadata": {}, "outputs": [ @@ -702,7 +663,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "id": "7a6f2bce-6b5e-4321-9457-0a6790d2202a", "metadata": {}, "outputs": [], @@ -712,13 +673,13 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "id": "6569014a-815b-46dd-8b47-4e1cd4584b3b", "metadata": {}, "outputs": [ { "data": { - "image/png": "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DBs2zDz55JMRnnxgnK67y+HDh83pp59uioqKzHnnnReZYUPI6brfeustI8l89NFHxufzdb+OHTsW4ckHJpjf909/+lMzadIkU11dberr6827775rtm7dGsGpB8bpmg8fPhzwO25sbDRpaWmmtLQ0soMPkNN1b9682cTFxZlHH33U7N2712zevNmcc845ZubMmRGefGCcrruiosKkpKSYF1980XzyySdm9erV5qSTTjLr1q2L8OSRETUxkp+fb0pKSgK2nX322Wbx4sX9/h45OTmmrKws1KOFVSjW/fOf/9zMmjUr1KOFVbDrLi4uNn/4wx9MaWlpVMaI03V3xch///vfCEwXPk7X/frrrxuPx2MOHjwYifHCYqD/bq9du9a4XC7z6aefhmO8sHG67vvvv9+cfvrpAdsee+wxk5mZGbYZw8HpugsKCswdd9wRsO22224zkydPDtuMNkXFZZqjR4+qpqZGRUVFAduLioq0bdu2fn2Pzs5Otba2Ki0tLRwjhkUo1r1r1y5t27ZNF198cThGDItg1/3MM8/ok08+UWlpabhHDIuB/L4vuOACeb1eXXbZZXrrrbfCOWbIBbPudevWaeLEibrvvvs0evRonXXWWbrjjjv09ddfR2LkAQvFv9srVqzQ5ZdfrjFjxoRjxLAIZt2FhYXat2+fNmzYIGOMvvjiC7388su66qqrIjFySASz7vb2drnd7oBtycnJ2r59u7755puwzWpLVMTIgQMH1NHRofT09IDt6enpampq6tf3ePDBB/XVV1/pmmuuCceIYTGQdWdmZiopKUkTJ07UzTffrPnz54dz1JAKZt0ff/yxFi9erFWrVikhISr+Muoeglm31+vVU089pVdeeUWvvvqqxo0bp8suu0xvv/12JEYOiWDWvXfvXm3ZskW1tbVau3atHnnkEb388su6+eabIzHygA30v2k+n0+vv/56VP17LQW37sLCQq1atUrFxcVKTExURkaGTj75ZD3++OORGDkkgln3FVdcoaefflo1NTUyxmjHjh2qqqrSN998owMHDkRi7IiKqv9qu1yugK+NMT229Wb16tW6++679dprr2nkyJHhGi9sgln35s2b9eWXX+qdd97R4sWLdcYZZ+iXv/xlOMcMuf6uu6OjQ9ddd53Kysp01llnRWq8sHHy+x43bpzGjRvX/XVBQYEaGxv1wAMP6H/+53/COmeoOVl3Z2enXC6XVq1a1f3Xkz/00EO6+uqrtXz5ciUnJ4d93lAI9r9pzz77rE4++WTNnDkzTJOFl5N119XV6dZbb9Vdd92lK664Qj6fT3feeadKSkq0YsWKSIwbMk7W/cc//lFNTU268MILZYxRenq65syZo/vuu0/x8fGRGDeiouLMyIgRIxQfH9+jIJubm3uU5netWbNG8+bN01/+8hddfvnl4Rwz5Aay7uzsbP34xz/WjTfeqNtvv1133313GCcNLafrbm1t1Y4dO3TLLbcoISFBCQkJWrZsmf79738rISFBf//73yM1+oAM5Pf9bRdeeKE+/vjjUI8XNsGs2+v1avTo0d0hIknjx4+XMUb79u0L67yhMJDftTFGVVVVuv7665WYmBjOMUMumHWXl5dr8uTJuvPOO3XuuefqiiuuUEVFhaqqquTz+SIx9oAFs+7k5GRVVVXpyJEj+vTTT9XQ0KDTTjtNKSkpGjFiRCTGjqioiJHExETl5eWpuro6YHt1dbUKCwv7fN/q1as1Z84cvfDCC1F1fbFLsOv+LmOM2tvbQz1e2Dhdd2pqqt577z3t3r27+1VSUqJx48Zp9+7dmjRpUqRGH5BQ/b537dolr9cb6vHCJph1T548Wfv379eXX37ZvW3Pnj2Ki4tTZmZmWOcNhYH8rjdt2qT//Oc/mjdvXjhHDItg1n3kyBHFxQUeqrrODJgo+XteB/L7PuGEE5SZman4+Hi9+OKL+slPftLjf48hwcKHZoPSdVvUihUrTF1dnVm4cKE58cQTuz9JvnjxYnP99dd37//CCy+YhIQEs3z58oDb4Q4fPmxrCUFxuu4nnnjCrFu3zuzZs8fs2bPHVFVVmdTUVLN06VJbSwiK03V/V7TeTeN03Q8//LBZu3at2bNnj6mtrTWLFy82kswrr7xiawlBcbru1tZWk5mZaa6++mrz/vvvm02bNpkzzzzTzJ8/39YSHAv2n/FZs2aZSZMmRXrckHG67meeecYkJCSYiooK88knn5gtW7aYiRMnmvz8fFtLCIrTdX/00Ufm+eefN3v27DHvvvuuKS4uNmlpaaa+vt7SCsIramLEGGOWL19uxowZYxITE82ECRPMpk2buv/shhtuMBdffHH31xdffLGR1ON1ww03RH7wAXKy7scee8ycc845ZtiwYSY1NdVccMEFpqKiwnR0dFiYfGCcrPu7ojVGjHG27nvvvdeMHTvWuN1u86Mf/chcdNFF5m9/+5uFqQfO6e/7gw8+MJdffrlJTk42mZmZZtGiRebIkSMRnnpgnK758OHDJjk52Tz11FMRnjS0nK77scceMzk5OSY5Odl4vV7zq1/9yuzbty/CUw+ck3XX1dWZ888/3yQnJ5vU1FTzs5/9zHz44YcWpo4MlzFRcp4LAAAMSUPwwhMAAIgmxAgAALCKGAEAAFYRIwAAwCpiBAAAWEWMAAAAq4gRAABgFTECAACsIkYAAIBVxAgAALCKGAEAAFb9LzGeNuwnas6eAAAAAElFTkSuQmCC", + "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==", "text/plain": [ "
" ] @@ -758,7 +719,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 24, "id": "25f0495a-e85f-43b7-8a70-a2c9cbd51ebb", "metadata": {}, "outputs": [ @@ -768,7 +729,7 @@ "(False, False)" ] }, - "execution_count": 25, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -779,7 +740,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 25, "id": "449ce797-be62-4211-b483-c717a3d70583", "metadata": {}, "outputs": [ @@ -789,7 +750,7 @@ "(True, False)" ] }, - "execution_count": 26, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -801,13 +762,13 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 26, "id": "7008b0fc-3644-401c-b49f-9c40f9d89ac4", "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiwAAAGiCAYAAADEJZ3cAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAkoUlEQVR4nO3de2zV9R3/8ddpCz3I6FlapD1choV4oTbqWlJsHTE6qaCpI5kRfw5Bp79Y1CEw3WQs1hKTRpeZeYE6FTQGZMTrJOmqzbJx3zqgXcSSaaCzoKc2beNpvbRI+/n90V87DqeFcw7n8jnf83wk54/z4fs9512+HM6rn9vXZYwxAgAAsFhaogsAAAA4FwILAACwHoEFAABYj8ACAACsR2ABAADWI7AAAADrEVgAAID1CCwAAMB6BBYAAGA9AgsAALBe2IFl165dqqio0NSpU+VyufTuu++e85ydO3equLhYbrdbs2bN0gsvvBBJrQAAIEWFHVi+/vprXXnllXr++edDOr61tVU33XST5s+fr6amJv3mN7/RypUr9dZbb4VdLAAASE2u87n5ocvl0jvvvKPFixePecyvf/1rvffeezpy5MhIW2Vlpf79739r//79kb41AABIIRmxfoP9+/ervLw8oO3GG2/Upk2b9N1332ncuHFB5/T396u/v3/k+eDgoLq7u5WTkyOXyxXrkgEAQBQYY9Tb26upU6cqLe38ps3GPLC0t7crNzc3oC03N1enTp1SZ2envF5v0Dk1NTWqrq6OdWkAACAOjh8/runTp5/Xa8Q8sEgK6hUZHoUaq7dk7dq1WrNmzchzv9+vH/zgBzp+/LiysrJiVygAAIianp4ezZgxQ5MmTTrv14p5YMnLy1N7e3tAW0dHhzIyMpSTkzPqOZmZmcrMzAxqz8rKIrAAAJBkojGdI+b7sJSWlqqhoSGg7YMPPtDcuXNHnb8CAABwprADy1dffaXm5mY1NzdLGlq23NzcrLa2NklDwznLli0bOb6yslKffvqp1qxZoyNHjmjz5s3atGmTHn744ej8BAAAwPHCHhI6cOCArrvuupHnw3NNli9frldffVU+n28kvEhSfn6+6urqtHr1am3YsEFTp07Vs88+q5/+9KdRKB8AAKSC89qHJV56enrk8Xjk9/uZwwIAQJKI5vc39xICAADWI7AAAADrEVgAAID1CCwAAMB6cdnpFoingUGjxtZudfT2acokt0rys5Wexj2oACCZEVjgKPWHfare0SKfv2+kzetxq6qiQAsLg+9bBWcjvALOQWCBY9Qf9mnFlkM6c51+u79PK7YcUu3SIkJLCiG8As7CHBY4wsCgUfWOlqCwImmkrXpHiwYGrd92CFEwHF5PDyvS/8Jr/WFfgioDECkCCxyhsbU76MvpdEaSz9+nxtbu+BWFhCC8As5EYIEjdPSOHVYiOQ7Ji/AKOBOBBY4wZZI7qscheRFeAWcisMARSvKz5fW4Ndb6D5eGJlyW5GfHsywkAOEVcCYCCxwhPc2lqooCSQoKLcPPqyoKWNKaAgivgDMRWOAYCwu9ql1apDxP4G/OeR43S5pTCOEVcCaXMcb6qfLRvD01nI/NwiCxDwtgg2h+fxNYADgW4RVIrGh+f7PTLQDHSk9zqXR2TqLLABAFzGEBAADWI7AAAADrEVgAAID1CCwAAMB6BBYAAGA9AgsAALAegQUAAFiPwAIAAKxHYAEAANYjsAAAAOsRWAAAgPUILAAAwHoEFgAAYD0CCwAAsB6BBQAAWI/AAgAArEdgAQAA1iOwAAAA6xFYAACA9QgsAADAegQWAABgPQILAACwHoEFAABYLyPRBQAAEImBQaPG1m519PZpyiS3SvKzlZ7mSnRZiBECCwAg6dQf9ql6R4t8/r6RNq/HraqKAi0s9CawMsQKQ0IAgKRSf9inFVsOBYQVSWr392nFlkOqP+xLUGWIJQILACBpDAwaVe9okRnlz4bbqne0aGBwtCOQzAgsAICk0djaHdSzcjojyefvU2Nrd/yKQlwQWAAASaOjd+ywEslxSB4EFgBA0pgyyR3V45A8WCUEAEgaJfnZ8nrcavf3jTqPxSUpzzO0xBnnlkxLwwksAICkkZ7mUlVFgVZsOSSXFBBahr9mqyoKrP3StUmyLQ1nSAgAkFQWFnpVu7RIeZ7AYZ88j1u1S4us/LK1TTIuDaeHBQCQdBYWerWgIC9phjNscq6l4S4NLQ1fUJBn1d8ngQUAkJTS01wqnZ2T6DKSTjhLw236+2VICACAFJKsS8MJLAAApJBkXRpOYAEAIIUMLw0fa3aKS0OrhWxbGk5gAQAghQwvDZcUFFpsXhpOYAEAIMUk49JwVgkBAJCCkm1pOIEFAIAUlUxLwwksIUimey0AAOBEBJZzSLZ7LQAA4ERMuj2LZLzXAgAATkRgGcO57rUgDd1rYWBwtCMAAEA0EVjGEM69FgAAQGwRWMaQrPdaAADAiSIKLBs3blR+fr7cbreKi4u1e/fusx6/detWXXnllbrgggvk9Xp19913q6urK6KC4yVZ77UAAIAThR1Ytm/frlWrVmndunVqamrS/PnztWjRIrW1tY16/J49e7Rs2TLdc889+uijj/TGG2/oX//6l+69997zLj6WkvVeCwAAOFHYgeXpp5/WPffco3vvvVdz5szRH/7wB82YMUO1tbWjHv+Pf/xDF110kVauXKn8/Hz96Ec/0n333acDBw6cd/GxlKz3WgAAwInCCiwnT57UwYMHVV5eHtBeXl6uffv2jXpOWVmZTpw4obq6Ohlj9MUXX+jNN9/UzTffPOb79Pf3q6enJ+CRCMl4rwUAAJworI3jOjs7NTAwoNzc3ID23Nxctbe3j3pOWVmZtm7dqiVLlqivr0+nTp3SLbfcoueee27M96mpqVF1dXU4pcVMst1rAQAAJ4po0q3LFfhlbYwJahvW0tKilStX6rHHHtPBgwdVX1+v1tZWVVZWjvn6a9euld/vH3kcP348kjKjZvheCz+5appKZ+cQVgAAiLOwelgmT56s9PT0oN6Ujo6OoF6XYTU1Nbrmmmv0yCOPSJKuuOIKTZw4UfPnz9cTTzwhrzd4WCUzM1OZmZnhlAYAABwsrB6W8ePHq7i4WA0NDQHtDQ0NKisrG/Wcb775RmlpgW+Tnp4uaahnBgAA4FzCHhJas2aNXn75ZW3evFlHjhzR6tWr1dbWNjLEs3btWi1btmzk+IqKCr399tuqra3VsWPHtHfvXq1cuVIlJSWaOnVq9H4SAADgWGHfrXnJkiXq6urS+vXr5fP5VFhYqLq6Os2cOVOS5PP5AvZkueuuu9Tb26vnn39ev/zlL/X9739f119/vZ588sno/RQAAMDRXCYJxmV6enrk8Xjk9/uVlZWV6HIAAEAIovn9zb2EAACA9QgsAADAegQWAABgPQILAACwHoEFAABYj8ACAACsR2ABAADWI7AAAADrEVgAAID1CCwAAMB6BBYAAGA9AgsAALAegQUAAFiPwAIAAKxHYAEAANYjsAAAAOsRWAAAgPUILAAAwHoEFgAAYD0CCwAAsB6BBQAAWI/AAgAArEdgAQAA1iOwAAAA6xFYAACA9QgsAADAegQWAABgPQILAACwHoEFAABYj8ACAACsR2ABAADWI7AAAADrEVgAAID1CCwAAMB6BBYAAGA9AgsAALAegQUAAFiPwAIAAKxHYAEAANYjsAAAAOsRWAAAgPUILAAAwHoEFgAAYD0CCwAAsB6BBQAAWI/AAgAArEdgAQAA1iOwAAAA6xFYAACA9QgsAADAegQWAABgPQILAACwHoEFAABYj8ACAACsR2ABAADWI7AAAADrEVgAAID1CCwAAMB6GYkuAMD/DAwaNbZ2q6O3T1MmuVWSn630NFeiywKAhCOwAJaoP+xT9Y4W+fx9I21ej1tVFQVaWOhNYGUAkHgMCQEWqD/s04othwLCiiS1+/u0Yssh1R/2JagyIHQDg0b7j3bpz82faf/RLg0MmkSXBAehhwVIsIFBo+odLRrtv3YjySWpekeLFhTkMTwEa9FDiFijhwVIsMbW7qCeldMZST5/nxpbu+NXFBAGeggRDwQWIME6escOK5EcB8TTuXoIpaEeQoaHcL4ILECCTZnkjupxQDzRQ4h4IbAACVaSny2vx62xZqe4NDQXoCQ/O55lASGhhxDxElFg2bhxo/Lz8+V2u1VcXKzdu3ef9fj+/n6tW7dOM2fOVGZmpmbPnq3NmzdHVDDgNOlpLlVVFEhSUGgZfl5VUcCEW1iJHkLES9iBZfv27Vq1apXWrVunpqYmzZ8/X4sWLVJbW9uY59x2223661//qk2bNuk///mPtm3bpssuu+y8CgecZGGhV7VLi5TnCfxPPc/jVu3SIlZZwFr0ECJeXMaYsGZCzZs3T0VFRaqtrR1pmzNnjhYvXqyampqg4+vr63X77bfr2LFjys4O7R9sf3+/+vv7R5739PRoxowZ8vv9ysrKCqdcIKmw0y2S0fAqIUkBk2+H/+USulNXT0+PPB5PVL6/w+phOXnypA4ePKjy8vKA9vLycu3bt2/Uc9577z3NnTtXTz31lKZNm6ZLLrlEDz/8sL799tsx36empkYej2fkMWPGjHDKBJJWeppLpbNz9JOrpql0dg5hBUmBHkLEQ1gbx3V2dmpgYEC5ubkB7bm5uWpvbx/1nGPHjmnPnj1yu91655131NnZqfvvv1/d3d1jzmNZu3at1qxZM/J8uIcFAGCnhYVeLSjIo4cQMRPRTrcuV+A/QGNMUNuwwcFBuVwubd26VR6PR5L09NNP69Zbb9WGDRs0YcKEoHMyMzOVmZkZSWkAgAQZ7iEEYiGsIaHJkycrPT09qDelo6MjqNdlmNfr1bRp00bCijQ058UYoxMnTkRQMgAASDVhBZbx48eruLhYDQ0NAe0NDQ0qKysb9ZxrrrlGn3/+ub766quRto8//lhpaWmaPn16BCUDAIBUE/ay5jVr1ujll1/W5s2bdeTIEa1evVptbW2qrKyUNDT/ZNmyZSPH33HHHcrJydHdd9+tlpYW7dq1S4888oh+/vOfjzocBAAAcKaw57AsWbJEXV1dWr9+vXw+nwoLC1VXV6eZM2dKknw+X8CeLN/73vfU0NCgX/ziF5o7d65ycnJ022236YknnojeTwEAABwt7H1YEiGa67gBAEB8JGwfFgAAgEQgsAAAAOsRWAAAgPUILAAAwHoEFgAAYL2ItuYHgLPhrtMAoo3AAiCq6g/7VL2jRT5/30ib1+NWVUUBd+0FEDGGhABETf1hn1ZsORQQViSp3d+nFVsOqf6wL0GVAUh2BBYAIwYGjfYf7dKfmz/T/qNdGhgMfV/JgUGj6h0tGu2M4bbqHS1hvSYADGNICICk8x/KaWztDupZOZ2R5PP3qbG1W6Wzc6JRMoAUQg8LgKgM5XT0jh1WIjkOAE5HYAFSXLSGcqZMcof0fqEeBwCnI7AAKS6coZyzKcnPltfj1liLl10aGmIqyc+OuFYAqYvAAqS4aA3lpKe5VFVRIElBoWX4eVVFAfuxAIgIgQUI0/mspLFRNIdyFhZ6Vbu0SHmewGPzPG7VLi1iHxYAEWOVEBAGJ26KNjyU0+7vG3Uei0tDgSPUoZyFhV4tKMhjp1sAUUUPCxAip26KFouhnPQ0l0pn5+gnV01T6ewcwgqA80ZgAULg9E3RGMoBYDuGhIAQpMKmaAzlALAZgQUIQapsijY8lAMAtmFICAgBm6IBQGIRWIAQsCkaACQWgQUIAZuiAUBiEViAELGSBgASh0m3QBhYSQMAiUFgAcLEShoAiD+GhAAAgPUILAAAwHoEFgAAYD0CCwAAsB6BBQAAWI/AAgAArEdgAQAA1iOwAAAA6xFYAACA9QgsAADAemzNDyBmBgYN910CEBUEFgAxUX/Yp+odLfL5+0bavB63qioKuLM1gLAxJAQg6uoP+7Riy6GAsCJJ7f4+rdhySPWHfQmqDECyIrAAiKqBQaPqHS0yo/zZcFv1jhYNDI52BACMjsACIKoaW7uDelZOZyT5/H1qbO2OX1EAkh5zWACHStSE147escNKJMcBgERgARwpkRNep0xyR/U4AJAYEgIcJ9ETXkvys+X1uDVWX45LQ+GpJD87pnUAcBYCC+AgNkx4TU9zqaqiQJKCQsvw86qKAvZjARAWAgvgILZMeF1Y6FXt0iLleQKHffI8btUuLWIfFgBhYw4L4CA2TXhdWOjVgoI8droFEBUEFsBBbJvwmp7mUunsnLi8FwBnY0gIcBAmvAJwKgIL4CBMeAXgVAQWwGGY8ArAiZjDAjgQE14BOA2BBXAoJrwCcBKGhAAAgPUILAAAwHoEFgAAYD0CCwAAsB6BBQAAWI/AAgAArEdgAQAA1iOwAAAA6xFYAACA9QgsAADAegQWAABgPQILAACwXkSBZePGjcrPz5fb7VZxcbF2794d0nl79+5VRkaGrrrqqkjeFgAApKiwA8v27du1atUqrVu3Tk1NTZo/f74WLVqktra2s57n9/u1bNky/fjHP464WAAAkJpcxhgTzgnz5s1TUVGRamtrR9rmzJmjxYsXq6amZszzbr/9dl188cVKT0/Xu+++q+bm5pDfs6enRx6PR36/X1lZWeGUCwAAEiSa399h9bCcPHlSBw8eVHl5eUB7eXm59u3bN+Z5r7zyio4ePaqqqqqQ3qe/v189PT0BDwAAkLrCCiydnZ0aGBhQbm5uQHtubq7a29tHPeeTTz7Ro48+qq1btyojIyOk96mpqZHH4xl5zJgxI5wyAQCAw0Q06dblcgU8N8YEtUnSwMCA7rjjDlVXV+uSSy4J+fXXrl0rv98/8jh+/HgkZQIAAIcIrcvj/5s8ebLS09ODelM6OjqCel0kqbe3VwcOHFBTU5MefPBBSdLg4KCMMcrIyNAHH3yg66+/Pui8zMxMZWZmhlMaAABwsLB6WMaPH6/i4mI1NDQEtDc0NKisrCzo+KysLH344Ydqbm4eeVRWVurSSy9Vc3Oz5s2bd37VAwCAlBBWD4skrVmzRnfeeafmzp2r0tJSvfjii2pra1NlZaWkoeGczz77TK+99prS0tJUWFgYcP6UKVPkdruD2gEAAMYSdmBZsmSJurq6tH79evl8PhUWFqqurk4zZ86UJPl8vnPuyQIAABCOsPdhSQT2YQEAIPkkbB8WAACARAh7SAhIRQODRo2t3ero7dOUSW6V5GcrPS14KT+AxOKz6lwEFuAc6g/7VL2jRT5/30ib1+NWVUWBFhZ6E1gZgNPxWXU2hoSAs6g/7NOKLYcC/gOUpHZ/n1ZsOaT6w74EVQbgdHxWnY/AAoxhYNCoekeLRpuVPtxWvaNFA4PWz1sHHI3PamogsABjaGztDvpt7XRGks/fp8bW7vgVBSAIn9XUQGABxtDRO/Z/gJEcByA2+KymBgILMIYpk9xRPQ5AbPBZTQ0EFmAMJfnZ8nrcGmtBpEtDKxBK8rPjWRaAM/BZTQ0EFmAM6WkuVVUUSFLQf4TDz6sqCtjjAUgwPqupgcACnMXCQq9qlxYpzxPYlZzncat2aRF7OwCW4LPqfNxLCAgBu2fGHn/HiAb+Hdklmt/f7HQLhCA9zaXS2TmJLsOx2KEU0cJn1bkYEgKQUOxQCiAUBBYACcMOpQBCRWABkDDsUAogVAQWAAnDDqUAQkVgAZAw7FAKIFQEFgAJww6lAEJFYAGQMOxQCiBUBBYACcUOpQBCkbIbx7EbImCPhYVeLSjI4zMJYEwpGVjYVROwDzuUAjiblBsSYldNAACST0oFFnbVBAAgOaVUYGFXTQBAPA0MGu0/2qU/N3+m/Ue7+IX4PKTUHBZ21QQAxAvzJaMrpXpY2FUTABAPzJeMvpQKLOyqCQCINeZLxkZKBRZ21QTswxg/nIb5krGRUnNYpP/tqnnmuGIe44pA3DHGDydivmRspFxgkdhVE7DB8Bj/mf0pw2P8bMuPZMV8ydhIycAisasmkEjnGuN3aWiMf0FBHr9IIOkMz5ds9/eN+m/cpaFefeZLhiel5rAAsANj/HAy5kvGBoEFQNwxxg+n4y7k0ZeyQ0IAEocxfqQC5ktGF4EFQNwxxo9UwXzJ6GFICEDcMcYPIFwEFgAJwRg/gHAwJAQgYRjjBxAqAguAhGKMH0AoGBICAADWI7AAAADrEVgAAID1CCwAAMB6BBYAAGA9AgsAALAegQUAAFiPwAIAAKxHYAEAANYjsAAAAOsRWAAAgPUILAAAwHoEFgAAYD0CCwAAsB6BBQAAWI/AAgAArEdgAQAA1iOwAAAA6xFYAACA9QgsAADAehmJLgCRGxg0amztVkdvn6ZMcqskP1vpaa5ElwUAQNQRWJJU/WGfqne0yOfvG2nzetyqqijQwkJvAisDACD6GBJKQvWHfVqx5VBAWJGkdn+fVmw5pPrDvgRVBgBAbBBYkszAoFH1jhaZUf5suK16R4sGBkc7AgCA5BRRYNm4caPy8/PldrtVXFys3bt3j3ns22+/rQULFujCCy9UVlaWSktL9f7770dccKprbO0O6lk5nZHk8/epsbU7fkUBABBjYQeW7du3a9WqVVq3bp2ampo0f/58LVq0SG1tbaMev2vXLi1YsEB1dXU6ePCgrrvuOlVUVKipqem8i09FHb1jh5VIjgMAIBm4jDFhjR3MmzdPRUVFqq2tHWmbM2eOFi9erJqampBe4/LLL9eSJUv02GOPhXR8T0+PPB6P/H6/srKywinXcfYf7dL/eekf5zxu2/+9WqWzc+JQEQAAo4vm93dYPSwnT57UwYMHVV5eHtBeXl6uffv2hfQag4OD6u3tVXZ29pjH9Pf3q6enJ+CBISX52fJ63Bpr8bJLQ6uFSvLH/vsFACDZhBVYOjs7NTAwoNzc3ID23Nxctbe3h/Qav//97/X111/rtttuG/OYmpoaeTyekceMGTPCKdPR0tNcqqookKSg0DL8vKqigP1YAACOEtGkW5cr8MvQGBPUNppt27bp8ccf1/bt2zVlypQxj1u7dq38fv/I4/jx45GU6VgLC72qXVqkPI87oD3P41bt0iL2YQEAOE5YG8dNnjxZ6enpQb0pHR0dQb0uZ9q+fbvuuecevfHGG7rhhhvOemxmZqYyMzPDKS3lLCz0akFBHjvdAgBSQlg9LOPHj1dxcbEaGhoC2hsaGlRWVjbmedu2bdNdd92l119/XTfffHNklSJIeppLpbNz9JOrpql0dg5hBQDgWGFvzb9mzRrdeeedmjt3rkpLS/Xiiy+qra1NlZWVkoaGcz777DO99tprkobCyrJly/TMM8/o6quvHumdmTBhgjweTxR/FAAA4FRhB5YlS5aoq6tL69evl8/nU2Fhoerq6jRz5kxJks/nC9iT5Y9//KNOnTqlBx54QA888MBI+/Lly/Xqq6+e/08AAAAcL+x9WBKBfVgAAEg+CduHBQAAIBEILAAAwHoEFgAAYD0CCwAAsB6BBQAAWI/AAgAArEdgAQAA1iOwAAAA6xFYAACA9QgsAADAegQWAABgPQILAACwHoEFAABYj8ACAACsR2ABAADWI7AAAADrEVgAAID1CCwAAMB6BBYAAGA9AgsAALAegQUAAFiPwAIAAKxHYAEAANYjsAAAAOsRWAAAgPUILAAAwHoEFgAAYD0CCwAAsB6BBQAAWI/AAgAArEdgAQAA1iOwAAAA6xFYAACA9QgsAADAegQWAABgvYxEFwAgPAODRo2t3ero7dOUSW6V5GcrPc2V6LIAIKYILEASqT/sU/WOFvn8fSNtXo9bVRUFWljoTWBlABBbDAkBSaL+sE8rthwKCCuS1O7v04oth1R/2JegygAg9ggsQBIYGDSq3tEiM8qfDbdV72jRwOBoRwBA8iOwAEmgsbU7qGfldEaSz9+nxtbu+BUFAHFEYAGSQEfv2GElkuMAINkQWIAkMGWSO6rHAUCyIbAASaAkP1tej1tjLV52aWi1UEl+djzLAoC4IbAASSA9zaWqigJJCgotw8+rKgrYjwWAYxFYgCSxsNCr2qVFyvMEDvvkedyqXVrEPiwAHI2N44AksrDQqwUFeex0CyDlEFiAJJOe5lLp7JxElwEAccWQEAAAsB6BBQAAWI/AAgAArEdgAQAA1iOwAAAA6xFYAACA9QgsAADAegQWAABgPQILAACwXlLsdGuMkST19PQkuBIAABCq4e/t4e/x85EUgaW3t1eSNGPGjARXAgAAwtXb2yuPx3Ner+Ey0Yg9MTY4OKjPP/9ckyZNksvFTd4i1dPToxkzZuj48ePKyspKdDk4DdfGXlwbe3Ft7DV8bdra2uRyuTR16lSlpZ3fLJSk6GFJS0vT9OnTE12GY2RlZfHhthTXxl5cG3txbezl8Xiidm2YdAsAAKxHYAEAANYjsKSQzMxMVVVVKTMzM9Gl4AxcG3txbezFtbFXLK5NUky6BQAAqY0eFgAAYD0CCwAAsB6BBQAAWI/AAgAArEdgAQAA1iOwOMzGjRuVn58vt9ut4uJi7d69e8xj3377bS1YsEAXXnihsrKyVFpaqvfffz+O1aaWcK7N6fbu3auMjAxdddVVsS0whYV7bfr7+7Vu3TrNnDlTmZmZmj17tjZv3hynalNLuNdm69atuvLKK3XBBRfI6/Xq7rvvVldXV5yqTQ27du1SRUWFpk6dKpfLpXffffec5+zcuVPFxcVyu92aNWuWXnjhhfDf2MAx/vSnP5lx48aZl156ybS0tJiHHnrITJw40Xz66aejHv/QQw+ZJ5980jQ2NpqPP/7YrF271owbN84cOnQozpU7X7jXZtiXX35pZs2aZcrLy82VV14Zn2JTTCTX5pZbbjHz5s0zDQ0NprW11fzzn/80e/fujWPVqSHca7N7926TlpZmnnnmGXPs2DGze/duc/nll5vFixfHuXJnq6urM+vWrTNvvfWWkWTeeeedsx5/7Ngxc8EFF5iHHnrItLS0mJdeesmMGzfOvPnmm2G9L4HFQUpKSkxlZWVA22WXXWYeffTRkF+joKDAVFdXR7u0lBfptVmyZIn57W9/a6qqqggsMRLutfnLX/5iPB6P6erqikd5KS3ca/O73/3OzJo1K6Dt2WefNdOnT49ZjakulMDyq1/9ylx22WUBbffdd5+5+uqrw3ovhoQc4uTJkzp48KDKy8sD2svLy7Vv376QXmNwcFC9vb3Kzs6ORYkpK9Jr88orr+jo0aOqqqqKdYkpK5Jr895772nu3Ll66qmnNG3aNF1yySV6+OGH9e2338aj5JQRybUpKyvTiRMnVFdXJ2OMvvjiC7355pu6+eab41EyxrB///6g63jjjTfqwIED+u6770J+naS4WzPOrbOzUwMDA8rNzQ1oz83NVXt7e0iv8fvf/15ff/21brvttliUmLIiuTaffPKJHn30Ue3evVsZGXxMYyWSa3Ps2DHt2bNHbrdb77zzjjo7O3X//feru7ubeSxRFMm1KSsr09atW7VkyRL19fXp1KlTuuWWW/Tcc8/Fo2SMob29fdTreOrUKXV2dsrr9Yb0OvSwOIzL5Qp4bowJahvNtm3b9Pjjj2v79u2aMmVKrMpLaaFem4GBAd1xxx2qrq7WJZdcEq/yUlo4n5vBwUG5XC5t3bpVJSUluummm/T000/r1VdfpZclBsK5Ni0tLVq5cqUee+wxHTx4UPX19WptbVVlZWU8SsVZjHYdR2s/G351c4jJkycrPT096DePjo6OoGR7pu3bt+uee+7RG2+8oRtuuCGWZaakcK9Nb2+vDhw4oKamJj344IOShr4kjTHKyMjQBx98oOuvvz4utTtdJJ8br9eradOmyePxjLTNmTNHxhidOHFCF198cUxrThWRXJuamhpdc801euSRRyRJV1xxhSZOnKj58+friSeeCPk3eURXXl7eqNcxIyNDOTk5Ib8OPSwOMX78eBUXF6uhoSGgvaGhQWVlZWOet23bNt111116/fXXGeeNkXCvTVZWlj788EM1NzePPCorK3XppZequblZ8+bNi1fpjhfJ5+aaa67R559/rq+++mqk7eOPP1ZaWpqmT58e03pTSSTX5ptvvlFaWuDXWnp6uqT//UaP+CstLQ26jh988IHmzp2rcePGhf5CYU3RhdWGlwBu2rTJtLS0mFWrVpmJEyea//73v8YYYx599FFz5513jhz/+uuvm4yMDLNhwwbj8/lGHl9++WWifgTHCvfanIlVQrET7rXp7e0106dPN7feeqv56KOPzM6dO83FF19s7r333kT9CI4V7rV55ZVXTEZGhtm4caM5evSo2bNnj5k7d64pKSlJ1I/gSL29vaapqck0NTUZSebpp582TU1NI8vNz7wuw8uaV69ebVpaWsymTZtY1gxjNmzYYGbOnGnGjx9vioqKzM6dO0f+bPny5ebaa68deX7ttdcaSUGP5cuXx7/wFBDOtTkTgSW2wr02R44cMTfccIOZMGGCmT59ulmzZo355ptv4lx1agj32jz77LOmoKDATJgwwXi9XvOzn/3MnDhxIs5VO9vf/va3s353jHZd/v73v5sf/vCHZvz48eaiiy4ytbW1Yb+vyxj6yQAAgN2YwwIAAKxHYAEAANYjsAAAAOsRWAAAgPUILAAAwHoEFgAAYD0CCwAAsB6BBQAAWI/AAgAArEdgAQAA1iOwAAAA6/0/dvCXVJIj9t8AAAAASUVORK5CYII=", + "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", "text/plain": [ "
" ] @@ -828,6 +789,170 @@ "Note that in the second cell, `f` is trying to update itself as soon as its inputs are ready, so if we _hadn't_ set the `f.inputs.y` channel to wait for an update, we would have gotten an error from the plotting command due to the mis-matched lengths of the x- and y-arrays." ] }, + { + "cell_type": "markdown", + "id": "5dc12164-b663-405b-872f-756996f628bd", + "metadata": {}, + "source": [ + "# Workflows\n", + "\n", + "The case where we have groups of connected nodes working together is our normal, intended use case.\n", + "We offer a formal way to group these objects together as a `Workflow(Node)` object.\n", + "`Workflow` also offers us a single point of entry to the codebase -- i.e. most of the time you shouldn't need the node imports used above, because the decorators are available right on the workflow class.\n", + "\n", + "We will also see here that we can our node output channels using the `output_labels: Optional[str | list[str] | tuple[str]` kwarg, in case they don't have a convenient name to start with.\n", + "This way we can always have convenient dot-based access (and tab completion) instead of having to access things by string-based keys." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "1cd000bd-9b24-4c39-9cac-70a3291d0660", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_contrib.workflow.workflow import Workflow\n", + "\n", + "@Workflow.wrap_as.single_value_node(output_labels=\"is_greater\")\n", + "def greater_than_half(x: int | float | bool = 0) -> bool:\n", + " \"\"\"The functionality doesn't matter here, it's just an example\"\"\"\n", + " return x > 0.5" + ] + }, + { + "cell_type": "markdown", + "id": "8f17751c-f5bf-4b13-8275-0685d8a1629e", + "metadata": {}, + "source": [ + "## Adding nodes to a workflow\n", + "\n", + "All five of the following approaches are equivalent ways to add a node to a workflow:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "7964df3c-55af-4c25-afc5-9e07accb606a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "n1 n1 n1 (GreaterThanHalf) output single-value: False\n", + "n3 n3 n3 (GreaterThanHalf) output single-value: False\n", + "n4 n4 n4 (GreaterThanHalf) output single-value: False\n", + "n5 n5 n5 (GreaterThanHalf) output single-value: False\n" + ] + } + ], + "source": [ + "from pyiron_contrib.workflow.function import Slow\n", + "\n", + "n1 = greater_than_half(label=\"n1\")\n", + "\n", + "wf = Workflow(\"my_wf\", n1) # As args at init\n", + "wf.add.Slow(lambda: x + 1, output_labels=\"p1\", label=\"n2\") # Instantiating from the class with a lambda function\n", + "# (Slow since we don't have an x default)\n", + "wf.add(greater_than_half(label=\"n3\")) # Instantiating then passing to node adder\n", + "wf.n4 = greater_than_half(label=\"will_get_overwritten_with_n4\") # Set attribute to instance\n", + "greater_than_half(label=\"n5\", parent=wf) # By passing the workflow to the node\n", + "\n", + "for k, v in wf.nodes.items():\n", + " print(k, v.label, v)" + ] + }, + { + "cell_type": "markdown", + "id": "dd5768a4-1810-4675-9389-bceb053cddfa", + "metadata": {}, + "source": [ + "Workflows have inputs and outputs just like function nodes, but these are dynamically created to map to all _unconnected_ input and output for their underlying graph:" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "809178a5-2e6b-471d-89ef-0797db47c5ad", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label sum_ to the io key sum_sum_\n", + " warn(\n" + ] + } + ], + "source": [ + "wf = Workflow(\"simple\")\n", + "\n", + "@Workflow.wrap_as.single_value_node()\n", + "def add_one(x):\n", + " y = x + 1\n", + " return y\n", + "\n", + "wf.a = add_one(0)\n", + "wf.b = add_one(0)\n", + "wf.sum = add_node(wf.a, wf.b) \n", + "# Remember, with single value nodes we can pass the whole node instead of an output channel!\n", + "\n", + "print(wf.outputs.sum_sum_.value)" + ] + }, + { + "cell_type": "markdown", + "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:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "52c48d19-10a2-4c48-ae81-eceea4129a60", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "7\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label x to the io key a_x\n", + " warn(\n", + "/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" + ] + } + ], + "source": [ + "wf(a_x=2, b_x=3)\n", + "print(wf.outputs.sum_sum_.value)" + ] + }, + { + "cell_type": "markdown", + "id": "0d6c7e6a-d39d-4c03-9f73-d506d7975fea", + "metadata": {}, + "source": [ + "(Note, you might see warnings from the workflow IO. This is fine, it's just letting us know that its keys don't match up with the channel labels. We don't see it until we call the input because workflows generate their IO panels dynamically on request to account for the fact that connections may change.)" + ] + }, { "cell_type": "markdown", "id": "2671dc36-42a4-466b-848d-067ef7bd1d1d", @@ -844,7 +969,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 31, "id": "ae500d5e-e55b-432c-8b5f-d5892193cdf5", "metadata": {}, "outputs": [ From 2ac6ec6a019506263fb2ae2c91b645abe45e1d62 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 17 Jul 2023 15:58:31 -0700 Subject: [PATCH 389/756] Add graphviz to deps --- .ci_support/environment.yml | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 8adc737f5..288ea0b0d 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -18,6 +18,7 @@ dependencies: - boto3 =1.28.1 - moto =4.1.12 - pycp2k =0.2.2 +- python-graphviz - typeguard =4.0.0 - aws-sam-translator =1.68.0 - pympipool =0.5.1 diff --git a/setup.py b/setup.py index f28b0caa0..4a5b73dfb 100644 --- a/setup.py +++ b/setup.py @@ -55,6 +55,7 @@ ], 'workflow': [ 'python>=3.10', + 'graphviz', 'ipython', 'typeguard==4.0.0' ], From cd78ea7556ca014301db8979fc49c13592779850 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 18 Jul 2023 11:17:04 -0700 Subject: [PATCH 390/756] 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 391/756] 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 392/756] 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 393/756] 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 394/756] 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 395/756] :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 396/756] 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 397/756] 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 e5f84a60539f3390e83015b6e178175987894edc Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Wed, 19 Jul 2023 09:27:28 -0600 Subject: [PATCH 398/756] Update environment.yml --- .binder/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 409592aa1..f3bf7e786 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -19,7 +19,7 @@ dependencies: - moto =4.1.12 - pycp2k =0.2.2 - typeguard =4.0.0 -- aws-sam-translator =1.68.0 +- aws-sam-translator =1.71.0 - pympipool =0.5.4 - distributed =2023.5.0 - python >= 3.10 From 95a6a0983c891a45143f17b0944faa5b38ab8e10 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Wed, 19 Jul 2023 09:27:43 -0600 Subject: [PATCH 399/756] Update environment.yml --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 0301b9d45..f20087373 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -19,6 +19,6 @@ dependencies: - moto =4.1.12 - pycp2k =0.2.2 - typeguard =4.0.0 -- aws-sam-translator =1.68.0 +- aws-sam-translator =1.71.0 - pympipool =0.5.4 - distributed =2023.5.0 From 97e89113b55459f1346175058c34552939cb85eb Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 19 Jul 2023 15:28:08 +0000 Subject: [PATCH 400/756] Update env file --- docs/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/environment.yml b/docs/environment.yml index 1ec5816b8..bcb86d2d4 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -21,6 +21,6 @@ dependencies: - moto =4.1.12 - pycp2k =0.2.2 - typeguard =4.0.0 -- aws-sam-translator =1.68.0 +- aws-sam-translator =1.71.0 - pympipool =0.5.4 - distributed =2023.5.0 From 398821255a18d10796feb27515ae4cde490040d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 15:53:53 +0000 Subject: [PATCH 401/756] Bump pympipool from 0.5.4 to 0.5.5 Bumps [pympipool](https://github.com/jan-janssen/pympipool) from 0.5.4 to 0.5.5. - [Release notes](https://github.com/jan-janssen/pympipool/releases) - [Commits](https://github.com/jan-janssen/pympipool/compare/pympipool-0.5.4...pympipool-0.5.5) --- updated-dependencies: - dependency-name: pympipool dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b2584441c..839fcc3b4 100644 --- a/setup.py +++ b/setup.py @@ -60,7 +60,7 @@ ], 'tinybase': [ 'distributed==2023.5.0', - 'pympipool==0.5.4' + 'pympipool==0.5.5' ] }, cmdclass=versioneer.get_cmdclass(), From 4ec112fc639f1a781e5f55388354512a7c7d293c Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 19 Jul 2023 15:54:23 +0000 Subject: [PATCH 402/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index f20087373..b9c3785cc 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -20,5 +20,5 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.71.0 -- pympipool =0.5.4 +- pympipool =0.5.5 - distributed =2023.5.0 From 1790acd1eae6b3ab13b725a09eaf6da03c79d6df Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Wed, 19 Jul 2023 09:54:35 -0600 Subject: [PATCH 403/756] Update environment.yml --- .binder/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index c42b7afa2..c01188c99 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -8,7 +8,7 @@ dependencies: - ipython - matplotlib =3.7.1 - numpy =1.24.3 -- pyiron_base =0.6.2 +- pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 - pyparsing =3.1.0 - scipy =1.10.1 From 459ade6089ccd90d027eec28f0e652664dd7a05c Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Wed, 19 Jul 2023 09:54:45 -0600 Subject: [PATCH 404/756] Update environment.yml --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 1d4b7c3cc..ee3e65c31 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -8,7 +8,7 @@ dependencies: - ipython - matplotlib =3.7.1 - numpy =1.24.3 -- pyiron_base =0.6.2 +- pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 - pyparsing =3.1.0 - scipy =1.10.1 From c82ea33b753206f3cfa08879d9b5c00bc74ebd9d Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Wed, 19 Jul 2023 09:55:13 -0600 Subject: [PATCH 405/756] Update setup.py --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 0c3c4f04c..6cab821b7 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,9 @@ 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9' + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', ], keywords='pyiron', @@ -33,7 +35,7 @@ install_requires=[ 'matplotlib==3.7.1', 'numpy==1.24.3', - 'pyiron_base==0.6.2', + 'pyiron_base==0.6.3', 'scipy==1.10.1', 'seaborn==0.12.2', 'pyparsing==3.1.0' From 330b82d071298bb8bdd7540a728b5879673a8acc Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Wed, 19 Jul 2023 09:55:26 -0600 Subject: [PATCH 406/756] Update environment.yml --- docs/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/environment.yml b/docs/environment.yml index cd64be874..210a5432b 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -10,7 +10,7 @@ dependencies: - ipython - matplotlib =3.7.1 - numpy =1.24.3 -- pyiron_base =0.6.2 +- pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 - pyparsing =3.1.0 - scipy =1.10.1 From 6fb376213f7cc38a1b9e8022d19b3149a64d01d9 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 19 Jul 2023 16:00:18 +0000 Subject: [PATCH 407/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index f3bf7e786..d57ddf966 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -20,7 +20,7 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.71.0 -- pympipool =0.5.4 +- pympipool =0.5.5 - distributed =2023.5.0 - python >= 3.10 - lammps diff --git a/docs/environment.yml b/docs/environment.yml index bcb86d2d4..dc9780b11 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -22,5 +22,5 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.71.0 -- pympipool =0.5.4 +- pympipool =0.5.5 - distributed =2023.5.0 From eabfa3e9c7e4ed442ef75009af2cef56e4a27e7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 16:14:51 +0000 Subject: [PATCH 408/756] Bump moto from 4.1.12 to 4.1.13 Bumps [moto](https://github.com/getmoto/moto) from 4.1.12 to 4.1.13. - [Changelog](https://github.com/getmoto/moto/blob/master/CHANGELOG.md) - [Commits](https://github.com/getmoto/moto/compare/4.1.12...4.1.13) --- updated-dependencies: - dependency-name: moto dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b2584441c..cc5cf0dd2 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ 'image': ['scikit-image==0.19.3'], 'generic': [ 'boto3==1.28.1', - 'moto==4.1.12' + 'moto==4.1.13' ], 'workflow': [ 'python>=3.10', From 0ce3d35db325652e1db6403dafbc4ed46716bb8f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 19 Jul 2023 09:27:51 -0700 Subject: [PATCH 409/756] 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 f8094f2d0e3319016bfc1bc03b02827b02fa603f Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 19 Jul 2023 16:33:17 +0000 Subject: [PATCH 410/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index f20087373..86e472568 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -16,7 +16,7 @@ dependencies: - scikit-image =0.19.3 - randspg =0.0.1 - boto3 =1.28.1 -- moto =4.1.12 +- moto =4.1.13 - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.71.0 From 1de973f8a3b16961f44e45d052f080457655281e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 19 Jul 2023 09:33:58 -0700 Subject: [PATCH 411/756] 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 97fe9c58f4ede33d21fd613d1da0b32964cb4488 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 19 Jul 2023 16:38:25 +0000 Subject: [PATCH 412/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index f3bf7e786..1d35877ba 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -16,7 +16,7 @@ dependencies: - scikit-image =0.19.3 - randspg =0.0.1 - boto3 =1.28.1 -- moto =4.1.12 +- moto =4.1.13 - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.71.0 diff --git a/docs/environment.yml b/docs/environment.yml index bcb86d2d4..a632037ef 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -18,7 +18,7 @@ dependencies: - scikit-image =0.19.3 - randspg =0.0.1 - boto3 =1.28.1 -- moto =4.1.12 +- moto =4.1.13 - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.71.0 From c6ee0cae60a72011dc6c5f6d9fe63f7cc1963aae Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 19 Jul 2023 09:41:25 -0700 Subject: [PATCH 413/756] 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 414/756] 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 415/756] 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 416/756] 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 417/756] 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": "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xrqA16GzV9UUZKWGrSbCCAAAMSQ+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//ryam5sX9HsuXbqkycnJmAMAAGQnR2FkbGxMkUhEJSUlMedLSko0MjKS8D2//vWvtW/fPh09elS5ubkL+j2tra3y+/1zR1lZmZMyAQBABklqAqvH44l5bYyJOydJkUhE9913n1paWnTbbbct+PP379+vcDg8dwwPDydTJgAAyAALu1Xxv1auXCmv1xt3F2R0dDTubokkTU1N6dSpU+rv79cjjzwiSZqZmZExRrm5uXrjjTd05513xr0vPz9f+fn5TkoDAAAZytGdkby8PFVXV6unpyfmfE9Pj+rq6uLaFxYW6pe//KXOnj07dzQ1NeljH/uYzp49qw0bNnyw6gEAQMZzdGdEkvbu3av7779f69evV21trf7lX/5FQ0NDampqkjQ7xPLb3/5W//qv/6qcnBxVVlbGvL+4uFg+ny/uPAAAcCfHYaSxsVHj4+M6ePCgQqGQKisr1dXVpTVr1kiSQqHQdfccAQAAiHK8z4gN7DMCAEDmSck+IwAAAIuNMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArMq1XQCWnsiMUd/ghEanplVc4FNNeZG8OR7bZQEAshRhBDG6B0Jq6QwqFJ6eOxfw+9TcUKEtlQGLlQEAshXDNJjTPRDSziNnYoKIJI2Ep7XzyBl1D4QsVQYAyGaEEUiaHZpp6QzKJPhZ9FxLZ1CRmUQtAABIHmEEkqS+wYm4OyJXM5JC4Wn1DU6krygAgCsQRiBJGp2aP4gk0w4AgIViAiskScUFvkVtl21YYQQAqUMYgSSpprxIAb9PI+HphPNGPJJK/bMXYbdhhREApBbDNJAkeXM8am6okDQbPK4Wfd3cUOG6uwGsMAKA1COMYM6WyoDat1ep1B87FFPq96l9e5Xr7gKwwggA0oNhGsTYUhnQ5opS5kfI2Qqj2rUr0lcYAGQZwgjieHM8XFzFCiMASBeGaYB5sMIIANKDOyNXYfkmrsYKIwBID8LI/2L5Jt4vusJo55Ez8kgxgcTNK4wAYLExTCOWb2J+rDACgNRz/Z2R6y3f9Gh2+ebmilK+AbsUK4wAILVcH0ZYvomFYIURAKSO64dpWL4JAIBdrg8jLN8EAMAu14eR6PLN+Ub/PZpdVcPyTQAAUsP1YYQHxAEAYJfrw4jE8k0AAGxy/WqaKJZvAgBgB2HkKizfBAAg/RimAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABWEUYAAIBVhBEAAGBVUmGkra1N5eXl8vl8qq6uVm9v77xtX331VW3evFkf+tCHVFhYqNraWv30pz9NumAAAOBcZMbo5Plx/dvZ3+rk+XFFZoztkuY43vSso6NDu3fvVltbmzZu3KjnnntOW7duVTAY1Ic//OG49m+99ZY2b96sf/zHf9RNN92kF154QQ0NDfr5z3+uT33qU4vyRwAAgPl1D4TU0hlUKDw9dy7g96m5oWJJPPLEY4xxFI02bNigqqoqtbe3z51bt26dtm3bptbW1gV9xic+8Qk1Njbq8ccfX1D7yclJ+f1+hcNhFRYWOikXAABX6x4IaeeRM3r/xT76sJNUPoNtoddvR8M0ly9f1unTp1VfXx9zvr6+XidOnFjQZ8zMzGhqakpFRUXztrl06ZImJydjDgAA4ExkxqilMxgXRCTNnWvpDFofsnEURsbGxhSJRFRSUhJzvqSkRCMjIwv6jKeeekp/+MMfdO+9987bprW1VX6/f+4oKytzUiYAAJDUNzgRMzTzfkZSKDytvsGJ9BWVQFITWD2e2CfZGmPiziXy0ksv6Zvf/KY6OjpUXFw8b7v9+/crHA7PHcPDw8mUCQCAq41OzR9EkmmXKo4msK5cuVJerzfuLsjo6Gjc3ZL36+jo0EMPPaSXX35Zd9111zXb5ufnKz8/30lpAADgfYoLfIvaLlUc3RnJy8tTdXW1enp6Ys739PSorq5u3ve99NJLeuCBB/Tiiy/qnnvuSa5SAADgSE15kQJ+n+Ybu/BodlVNTfn88zjTwfEwzd69e/X888/r8OHDOnfunPbs2aOhoSE1NTVJmh1i2bFjx1z7l156STt27NBTTz2lT3/60xoZGdHIyIjC4fDi/RUAACCON8ej5oYKSYoLJNHXzQ0V8uZcf6pFKjkOI42Njfr2t7+tgwcP6o477tBbb72lrq4urVmzRpIUCoU0NDQ01/65557TlStX9Nd//dcKBAJzx6OPPrp4fwUAAEhoS2VA7durVOqPHYop9ftSuqzXCcf7jNjAPiMAAHwwkRmjvsEJjU5Nq7hgdmgm1XdEFnr9drwDKwAAyDzeHI9q166wXUZCPCgPAABYRRgBAABWEUYAAIBVhBEAAGAVE1gBYJHZWLUAZDLCCAAsou6BkFo6gzEPJwv4fWpuqFgS+zkASxHDNACwSLoHQtp55EzcU1JHwtPaeeSMugdClioDljbCCAAsgsiMUUtnUIl2kYyea+kMKjKz5PeZBNKOMAIAi6BvcCLujsjVjKRQeFp9gxPpKwrIEIQRAFgEo1PzB5Fk2gFuQhgBgEVQXOC7fiMH7QA3IYwAwCKoKS9SwO+Le0x7lEezq2pqyovSWRaQEQgjALAIvDkeNTdUSFJcIIm+bm6oYL8RIAHCCAAski2VAbVvr1KpP3YoptTvU/v2KvYZAebBpmcAsIi2VAa0uaKUHVgBBwgjALDIvDke1a5dYbsMIGMwTAMAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAq1ja62KRGcNeCAAA6wgjLtU9EFJLZzDmkecBv0/NDRXsEgkASCuGaVyoeyCknUfOxAQRSRoJT2vnkTPqHghZqgwA4EaEEZeJzBi1dAZlEvwseq6lM6jITKIWAAAsPsKIy/QNTsTdEbmakRQKT6tvcCJ9RQEAXI0w4jKjU/MHkWTaAQDwQRFGXKa4wHf9Rg7aAQDwQRFGXKamvEgBv0/zLeD1aHZVTU15UTrLAgC4GGHEZbw5HjU3VEhSXCCJvm5uqGC/EQBA2hBGXGhLZUDt26tU6o8diin1+9S+vYp9RgAAacWmZy61pTKgzRWl7MAKALCOMOJi3hyPateusF0GAMDlGKYBAABWEUYAAIBVhBEAAGAVc0ayUGTGMDEVAJAxCCNZpnsgpJbOYMzzZwJ+n5obKliyCwBYkhimySLdAyHtPHIm7kF4I+Fp7TxyRt0DIUuVAQAwP8JIlojMGLV0BmUS/Cx6rqUzqMhMohYAANhDGMkSfYMTcXdErmYkhcLT6hucSF9RAAAsAGEkS4xOzR9EkmkHAEC6EEayRHGB7/qNHLQDACBdCCNZoqa8SAG/L+5JvFEeza6qqSkvSmdZAABcF2EkS3hzPGpuqJCkuEASfd3cUMF+IwCAJYcwkkW2VAbUvr1Kpf7YoZhSv0/t26vYZwQAsCSx6VmW2VIZ0OaKUnZgBQBkzI7chJEs5M3xqHbtCttlAAAsyqQduRmmAQAgy2TajtyEEQAAskgm7shNGAEAIItk4o7chBEAALJIJu7ITRgBACCLZOKO3IQRAACySCbuyE0YAQAgi2TijtyEEQAAskym7cjNpmcAAGShTNqRmzACAECWypQduRmmAQAAVhFGAACAVUmFkba2NpWXl8vn86m6ulq9vb3XbP/mm2+qurpaPp9Pt9xyi773ve8lVSwAAMg+jsNIR0eHdu/erQMHDqi/v1+bNm3S1q1bNTQ0lLD94OCg7r77bm3atEn9/f36+7//e+3atUs/+tGPPnDxAAAg83mMMY6elLNhwwZVVVWpvb197ty6deu0bds2tba2xrX/xje+oddff13nzp2bO9fU1KT/+q//0smTJxf0OycnJ+X3+xUOh1VYWOikXAAAYMlCr9+O7oxcvnxZp0+fVn19fcz5+vp6nThxIuF7Tp48Gdf+c5/7nE6dOqU//elPCd9z6dIlTU5OxhwAACA7OQojY2NjikQiKikpiTlfUlKikZGRhO8ZGRlJ2P7KlSsaGxtL+J7W1lb5/f65o6yszEmZAAAggyQ1gdXjid0wxRgTd+567ROdj9q/f7/C4fDcMTw8nEyZAAAgAzja9GzlypXyer1xd0FGR0fj7n5ElZaWJmyfm5urFSsSb8SSn5+v/Px8J6UBAIAM5SiM5OXlqbq6Wj09PfrCF74wd76np0d/8Rd/kfA9tbW16uzsjDn3xhtvaP369Vq2bNmCfm/0TgpzRwAAyBzR6/Z118oYh374wx+aZcuWmUOHDplgMGh2795tli9fbn7zm98YY4zZt2+fuf/+++faX7hwwdx4441mz549JhgMmkOHDplly5aZV155ZcG/c3h42Eji4ODg4ODgyMBjeHj4mtd5x8+maWxs1Pj4uA4ePKhQKKTKykp1dXVpzZo1kqRQKBSz50h5ebm6urq0Z88ePfvss1q1apWeeeYZ/eVf/uWCf+eqVas0PDysgoKCa85NmZycVFlZmYaHh1kCnCb0efrR5+lHn6cffZ5+qehzY4ympqa0atWqa7ZzvM/IUsZ+JOlHn6cffZ5+9Hn60efpZ7PPeTYNAACwijACAACsyqowkp+fr+bmZpYFpxF9nn70efrR5+lHn6efzT7PqjkjAAAg82TVnREAAJB5CCMAAMAqwggAALCKMAIAAKzKqDDS1tam8vJy+Xw+VVdXq7e395rt33zzTVVXV8vn8+mWW27R9773vTRVml2c9Purr76qzZs360Mf+pAKCwtVW1urn/70p2msNjs4/bcedfz4ceXm5uqOO+5IbYFZyGmfX7p0SQcOHNCaNWuUn5+vtWvX6vDhw2mqNjs47fOjR4/q9ttv14033qhAIKAHH3xQ4+Pjaao287311ltqaGjQqlWr5PF49OMf//i670nbddTps2lsiT4T5/vf/74JBoPm0UcfNcuXLzf/8z//k7B99Jk4jz76qAkGg+b73/++42fiwHm/P/roo+bJJ580fX195u233zb79+83y5YtM2fOnElz5ZnLaZ9H/e53vzO33HKLqa+vN7fffnt6is0SyfT55z//ebNhwwbT09NjBgcHzc9//nNz/PjxNFad2Zz2eW9vr8nJyTH//M//bC5cuGB6e3vNJz7xCbNt27Y0V565urq6zIEDB8yPfvQjI8m89tpr12yfzutoxoSRmpoa09TUFHPu4x//uNm3b1/C9n/3d39nPv7xj8ec++pXv2o+/elPp6zGbOS03xOpqKgwLS0ti11a1kq2zxsbG80//MM/mObmZsKIQ077/Cc/+Ynx+/1mfHw8HeVlJad9/k//9E/mlltuiTn3zDPPmNWrV6esxmy2kDCSzutoRgzTXL58WadPn1Z9fX3M+fr6ep04cSLhe06ePBnX/nOf+5xOnTqlP/3pTymrNZsk0+/vNzMzo6mpKRUVFaWixKyTbJ+/8MILOn/+vJqbm1NdYtZJps9ff/11rV+/Xt/61rd0880367bbbtPXv/51/fGPf0xHyRkvmT6vq6vTxYsX1dXVJWOM3n33Xb3yyiu655570lGyK6XzOur4qb02jI2NKRKJqKSkJOZ8SUmJRkZGEr5nZGQkYfsrV65obGxMgUAgZfVmi2T6/f2eeuop/eEPf9C9996bihKzTjJ9/utf/1r79u1Tb2+vcnMz4j/pJSWZPr9w4YKOHTsmn8+n1157TWNjY/ra176miYkJ5o0sQDJ9XldXp6NHj6qxsVHT09O6cuWKPv/5z+s73/lOOkp2pXReRzPizkiUx+OJeW2MiTt3vfaJzuPanPZ71EsvvaRvfvOb6ujoUHFxcarKy0oL7fNIJKL77rtPLS0tuu2229JVXlZy8u98ZmZGHo9HR48eVU1Nje6++249/fTT+sEPfsDdEQec9HkwGNSuXbv0+OOP6/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" ] }, From 964d710d9a69d8d4094c3261744f158574c23f0b Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 19 Jul 2023 09:56:53 -0700 Subject: [PATCH 418/756] Switch workhorse CI to run on _all_ pulls, not just to name --- .github/workflows/push-pull-main.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/push-pull-main.yml b/.github/workflows/push-pull-main.yml index 1b2aaf76b..9c3da7113 100644 --- a/.github/workflows/push-pull-main.yml +++ b/.github/workflows/push-pull-main.yml @@ -1,12 +1,11 @@ # This runs jobs which pyiron modules should run on pushes or PRs to main -name: Push-Pull-main +name: Push-main-Pull-all on: push: branches: [ main ] pull_request: - branches: [ main ] jobs: pyiron: From 3129d701e8f34fd24c38a7a7703315450f5db7fe Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 19 Jul 2023 09:58:02 -0700 Subject: [PATCH 419/756] Refactor: rename file --- .github/workflows/{push-pull-main.yml => push-pull.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{push-pull-main.yml => push-pull.yml} (100%) diff --git a/.github/workflows/push-pull-main.yml b/.github/workflows/push-pull.yml similarity index 100% rename from .github/workflows/push-pull-main.yml rename to .github/workflows/push-pull.yml From 5fc52fe140f8ee4674c229feed3c4c0bf5a97e87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 17:39:09 +0000 Subject: [PATCH 420/756] Bump pyiron-base from 0.6.1 to 0.6.3 Bumps [pyiron-base](https://github.com/pyiron/pyiron_base) from 0.6.1 to 0.6.3. - [Release notes](https://github.com/pyiron/pyiron_base/releases) - [Changelog](https://github.com/pyiron/pyiron_base/blob/main/CHANGELOG.md) - [Commits](https://github.com/pyiron/pyiron_base/compare/pyiron_base-0.6.1...pyiron_base-0.6.3) --- updated-dependencies: - dependency-name: pyiron-base dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6e9effff2..15df4abf2 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ install_requires=[ 'matplotlib==3.7.1', 'numpy==1.24.3', - 'pyiron_base==0.6.1', + 'pyiron_base==0.6.3', 'scipy==1.10.1', 'seaborn==0.12.2', 'pyparsing==3.1.0' From c938ec042d9ca87db74ccb09c6353ecf0ce18494 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 17:39:41 +0000 Subject: [PATCH 421/756] Bump boto3 from 1.28.1 to 1.28.5 Bumps [boto3](https://github.com/boto/boto3) from 1.28.1 to 1.28.5. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.28.1...1.28.5) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 043dafc0a..f1f231189 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ ], 'image': ['scikit-image==0.19.3'], 'generic': [ - 'boto3==1.28.1', + 'boto3==1.28.5', 'moto==4.1.13' ], 'workflow': [ From cee845211aafa4c2de10a51c11b4d92ea663dd5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 17:43:55 +0000 Subject: [PATCH 422/756] Bump scikit-image from 0.19.3 to 0.21.0 Bumps [scikit-image](https://github.com/scikit-image/scikit-image) from 0.19.3 to 0.21.0. - [Release notes](https://github.com/scikit-image/scikit-image/releases) - [Changelog](https://github.com/scikit-image/scikit-image/blob/main/RELEASE.txt) - [Commits](https://github.com/scikit-image/scikit-image/compare/v0.19.3...v0.21.0) --- updated-dependencies: - dependency-name: scikit-image dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 043dafc0a..5b626ea6c 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ 'fenics==2019.1.0', 'mshr==2019.1.0', ], - 'image': ['scikit-image==0.19.3'], + 'image': ['scikit-image==0.21.0'], 'generic': [ 'boto3==1.28.1', 'moto==4.1.13' From a4043563598e8ca30058721904089e49019e0650 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 19 Jul 2023 17:45:04 +0000 Subject: [PATCH 423/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 2a58dc6e7..d506e98bf 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -8,7 +8,7 @@ dependencies: - ipython - matplotlib =3.7.1 - numpy =1.24.3 -- pyiron_base =0.6.1 +- pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 - pyparsing =3.1.0 - scipy =1.10.1 From fca45b8f3cdf0d4b8af56a8f0a6e8d35270b1153 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 19 Jul 2023 17:45:10 +0000 Subject: [PATCH 424/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index d506e98bf..3b1d0a715 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.28.1 +- boto3 =1.28.5 - moto =4.1.13 - pycp2k =0.2.2 - typeguard =4.0.0 From 9e69dbdc0fffa44f815cf08fd68f2ae46d9dbd9a Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 19 Jul 2023 17:45:28 +0000 Subject: [PATCH 425/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index d506e98bf..3d1f1c02b 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -13,7 +13,7 @@ dependencies: - pyparsing =3.1.0 - scipy =1.10.1 - seaborn =0.12.2 -- scikit-image =0.19.3 +- scikit-image =0.21.0 - randspg =0.0.1 - boto3 =1.28.1 - moto =4.1.13 From 06be91970f51d194a1e62a6c90435146eed5f325 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 19 Jul 2023 17:45:34 +0000 Subject: [PATCH 426/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index bc152cd37..d2911ba2b 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.28.1 +- boto3 =1.28.5 - moto =4.1.13 - pycp2k =0.2.2 - typeguard =4.0.0 diff --git a/docs/environment.yml b/docs/environment.yml index 4199d409b..12cf6b1ec 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -17,7 +17,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.19.3 - randspg =0.0.1 -- boto3 =1.28.1 +- boto3 =1.28.5 - moto =4.1.13 - pycp2k =0.2.2 - typeguard =4.0.0 From 7575dd424719ba200752cefd933a37deb4a75e17 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 19 Jul 2023 17:45:34 +0000 Subject: [PATCH 427/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 1198d3124..bc152cd37 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -8,7 +8,7 @@ dependencies: - ipython - matplotlib =3.7.1 - numpy =1.24.3 -- pyiron_base =0.6.1 +- pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 - pyparsing =3.1.0 - scipy =1.10.1 diff --git a/docs/environment.yml b/docs/environment.yml index a2488b610..4199d409b 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -10,7 +10,7 @@ dependencies: - ipython - matplotlib =3.7.1 - numpy =1.24.3 -- pyiron_base =0.6.1 +- pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 - pyparsing =3.1.0 - scipy =1.10.1 From fed98307e0726333b57a6b628707309adfc13b77 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 19 Jul 2023 17:45:51 +0000 Subject: [PATCH 428/756] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index bc152cd37..a621d7c30 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -13,7 +13,7 @@ dependencies: - pyparsing =3.1.0 - scipy =1.10.1 - seaborn =0.12.2 -- scikit-image =0.19.3 +- scikit-image =0.21.0 - randspg =0.0.1 - boto3 =1.28.1 - moto =4.1.13 diff --git a/docs/environment.yml b/docs/environment.yml index 4199d409b..2ccfe2f05 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -15,7 +15,7 @@ dependencies: - pyparsing =3.1.0 - scipy =1.10.1 - seaborn =0.12.2 -- scikit-image =0.19.3 +- scikit-image =0.21.0 - randspg =0.0.1 - boto3 =1.28.1 - moto =4.1.13 From 544ab1440901594d526131a81b58336254d27ec6 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 19 Jul 2023 12:24:49 -0700 Subject: [PATCH 429/756] Draw the node with graphviz Just a first pass --- pyiron_contrib/workflow/draw.py | 80 +++++++++++++++++++++++++++++++++ pyiron_contrib/workflow/node.py | 6 +++ 2 files changed, 86 insertions(+) create mode 100644 pyiron_contrib/workflow/draw.py diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py new file mode 100644 index 000000000..856630ca3 --- /dev/null +++ b/pyiron_contrib/workflow/draw.py @@ -0,0 +1,80 @@ +""" +Functions for drawing the graph. +""" + +from __future__ import annotations + +from typing import Optional, TYPE_CHECKING + +import graphviz + +if TYPE_CHECKING: + from pyiron_contrib.workflow.node import Node + + +def _channel_name(node, channel): + return node.label + channel.label + + +def _channel_label(channel): + label = channel.label + try: + if channel.type_hint is not None: + label += ": " + channel.type_hint.__name__ + except AttributeError: + pass # Signals have no type + return label + + +def _make_channel_node(parent_graph, node, channel, shape="oval"): + parent_graph.node( + _channel_name(node, channel), + _channel_label(channel), + shape=shape + ) + + +def _io_name(node, io): + return "cluster" + node.label + io.__class__.__name__ + + +def _make_io_panel(parent_graph, node, data_io, signals_io): + with parent_graph.subgraph(name=_io_name(node, data_io)) as io_graph: + io_graph.attr(compound="true", label=data_io.__class__.__name__, rankdir="TB") + for data_channel in data_io: + _make_channel_node(io_graph, node, data_channel) + for signal_channel in signals_io: + _make_channel_node(io_graph, node, signal_channel, shape="cds") + return io_graph + + +def _node_name(node): + return "cluster" + node.label + + +def _node_label(node): + return node.label + ": " + node.__class__.__name__ + + +def draw_node(node: Node, parent_graph: Optional[graphviz.graphs.Digraph] = None): + if parent_graph is None: + parent_graph = graphviz.graphs.Digraph(node.label) + parent_graph.attr(compound="true", rankdir="TB") + + with parent_graph.subgraph(name=_node_name(node)) as node_graph: + node_graph.attr(compount="true", label=_node_label(node), rankdir="LR") + + _make_io_panel(node_graph, node, node.inputs, node.signals.input) + _make_io_panel(node_graph, node, node.outputs, node.signals.output) + + # Make inputs and outputs groups ordered by (invisibly) drawing a connection + # Exploit the fact that all nodes have `run` and `ran` signal channels + node_graph.edge( + _channel_name(node, node.signals.input[node.signals.input.labels[0]]), + _channel_name(node, node.signals.output[node.signals.output.labels[0]]), + ltail=_io_name(node, node.inputs), + lhead=_io_name(node, node.outputs), + style="invis" + ) + + return parent_graph diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 76e67733e..fe2551c40 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -10,11 +10,14 @@ from typing import Optional, TYPE_CHECKING from pyiron_contrib.executors import CloudpickleProcessPoolExecutor +from pyiron_contrib.workflow.draw import draw_node from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Signals, InputSignal, OutputSignal if TYPE_CHECKING: + import graphviz + from pyiron_base.jobs.job.extension.server.generic import Server from pyiron_contrib.workflow.composite import Composite @@ -275,3 +278,6 @@ def fully_connected(self): and self.outputs.fully_connected and self.signals.fully_connected ) + + def draw(self, parent_graph: Optional[graphviz.graphs.Digraph] = None): + return draw_node(self, parent_graph) From afb68c40819a7a9e7b9bd1f24f33c5dffa4bffd0 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 19 Jul 2023 19:39:46 +0000 Subject: [PATCH 430/756] [dependabot skip] Update env file --- .binder/environment.yml | 1 + docs/environment.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.binder/environment.yml b/.binder/environment.yml index bc152cd37..f06c5111f 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -18,6 +18,7 @@ dependencies: - boto3 =1.28.1 - moto =4.1.13 - pycp2k =0.2.2 +- python-graphviz - typeguard =4.0.0 - aws-sam-translator =1.71.0 - pympipool =0.5.5 diff --git a/docs/environment.yml b/docs/environment.yml index 4199d409b..3cfd027f8 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -20,6 +20,7 @@ dependencies: - boto3 =1.28.1 - moto =4.1.13 - pycp2k =0.2.2 +- python-graphviz - typeguard =4.0.0 - aws-sam-translator =1.71.0 - pympipool =0.5.5 From 477487c0a76d24a105be9a88fc4aa00286fbbafa Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 20 Jul 2023 09:43:56 -0700 Subject: [PATCH 431/756] Draw children for composite graphs --- pyiron_contrib/workflow/composite.py | 34 +++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index bacc934e8..8b497275f 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -7,10 +7,11 @@ from abc import ABC from functools import partial -from typing import Optional +from typing import Optional, TYPE_CHECKING from warnings import warn from pyiron_contrib.executors import CloudpickleProcessPoolExecutor +from pyiron_contrib.workflow.draw import _node_name from pyiron_contrib.workflow.node import Node from pyiron_contrib.workflow.function import ( Function, @@ -24,6 +25,9 @@ from pyiron_contrib.workflow.node_library.package import NodePackage from pyiron_contrib.workflow.util import DotDict +if TYPE_CHECKING: + import graphviz + class _NodeDecoratorAccess: """An intermediate container to store node-creating decorators as class methods.""" @@ -204,6 +208,34 @@ def remove(self, node: Node | str): else: del self.nodes[node] + def draw( + self, + parent_graph: Optional[graphviz.graphs.Digraph] = None, + granularity: int = 1 + ): + parent_graph = super().draw(parent_graph) + if granularity > 0: + with parent_graph.subgraph(name=_node_name(self)) as workflow_graph: + self._draw_children(workflow_graph, granularity) + return parent_graph + + def _draw_children( + self, + workflow_graph: graphviz.graphs.Digraph, + granularity: int + ): + for node in self.nodes.values(): + try: + workflow_graph = node.draw( + parent_graph=workflow_graph, + granularity=granularity - 1 + ) + except TypeError: + # Non-composite nodes don't take the granularity argument + workflow_graph = node.draw(parent_graph=workflow_graph) + # TODO: Connect child outputs to parent's IO panels + return workflow_graph + def __setattr__(self, label: str, node: Node): if isinstance(node, Node): self.add_node(node, label=label) From c90b93037a08e9c6b4bb2320c9fe796e8e172fae Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 20 Jul 2023 09:56:25 -0700 Subject: [PATCH 432/756] Recursively prepend parent labels to get a totally unique string --- pyiron_contrib/workflow/draw.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index 856630ca3..3b89cb113 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -48,8 +48,13 @@ def _make_io_panel(parent_graph, node, data_io, signals_io): return io_graph -def _node_name(node): - return "cluster" + node.label +def _node_name(node, suffix=""): + if node.parent is not None: + # Recursively prepend parent labels to get a totally unique label string + # (inside the scope of this graph) + return _node_name(node.parent, suffix=suffix + node.label) + else: + return "cluster" + node.label + suffix def _node_label(node): From 0ff2892273e73d8dbf26862ca04f6d06a3e5ec35 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 20 Jul 2023 12:29:36 -0700 Subject: [PATCH 433/756] Replace it all with a class-based system --- pyiron_contrib/workflow/composite.py | 30 +--- pyiron_contrib/workflow/draw.py | 256 +++++++++++++++++++-------- pyiron_contrib/workflow/node.py | 6 +- 3 files changed, 190 insertions(+), 102 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 8b497275f..42598e59c 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -11,7 +11,6 @@ from warnings import warn from pyiron_contrib.executors import CloudpickleProcessPoolExecutor -from pyiron_contrib.workflow.draw import _node_name from pyiron_contrib.workflow.node import Node from pyiron_contrib.workflow.function import ( Function, @@ -208,33 +207,8 @@ def remove(self, node: Node | str): else: del self.nodes[node] - def draw( - self, - parent_graph: Optional[graphviz.graphs.Digraph] = None, - granularity: int = 1 - ): - parent_graph = super().draw(parent_graph) - if granularity > 0: - with parent_graph.subgraph(name=_node_name(self)) as workflow_graph: - self._draw_children(workflow_graph, granularity) - return parent_graph - - def _draw_children( - self, - workflow_graph: graphviz.graphs.Digraph, - granularity: int - ): - for node in self.nodes.values(): - try: - workflow_graph = node.draw( - parent_graph=workflow_graph, - granularity=granularity - 1 - ) - except TypeError: - # Non-composite nodes don't take the granularity argument - workflow_graph = node.draw(parent_graph=workflow_graph) - # TODO: Connect child outputs to parent's IO panels - return workflow_graph + def draw(self, granularity=1) -> graphviz.graphs.Digraph: + return super().draw(granularity=granularity) def __setattr__(self, label: str, node: Node): if isinstance(node, Node): diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index 3b89cb113..df5ccb8f8 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -4,82 +4,196 @@ from __future__ import annotations +from abc import ABC, abstractmethod from typing import Optional, TYPE_CHECKING import graphviz if TYPE_CHECKING: - from pyiron_contrib.workflow.node import Node - - -def _channel_name(node, channel): - return node.label + channel.label - - -def _channel_label(channel): - label = channel.label - try: - if channel.type_hint is not None: - label += ": " + channel.type_hint.__name__ - except AttributeError: - pass # Signals have no type - return label - - -def _make_channel_node(parent_graph, node, channel, shape="oval"): - parent_graph.node( - _channel_name(node, channel), - _channel_label(channel), - shape=shape - ) - - -def _io_name(node, io): - return "cluster" + node.label + io.__class__.__name__ - - -def _make_io_panel(parent_graph, node, data_io, signals_io): - with parent_graph.subgraph(name=_io_name(node, data_io)) as io_graph: - io_graph.attr(compound="true", label=data_io.__class__.__name__, rankdir="TB") - for data_channel in data_io: - _make_channel_node(io_graph, node, data_channel) - for signal_channel in signals_io: - _make_channel_node(io_graph, node, signal_channel, shape="cds") - return io_graph - - -def _node_name(node, suffix=""): - if node.parent is not None: - # Recursively prepend parent labels to get a totally unique label string - # (inside the scope of this graph) - return _node_name(node.parent, suffix=suffix + node.label) - else: - return "cluster" + node.label + suffix - - -def _node_label(node): - return node.label + ": " + node.__class__.__name__ - - -def draw_node(node: Node, parent_graph: Optional[graphviz.graphs.Digraph] = None): - if parent_graph is None: - parent_graph = graphviz.graphs.Digraph(node.label) - parent_graph.attr(compound="true", rankdir="TB") - - with parent_graph.subgraph(name=_node_name(node)) as node_graph: - node_graph.attr(compount="true", label=_node_label(node), rankdir="LR") - - _make_io_panel(node_graph, node, node.inputs, node.signals.input) - _make_io_panel(node_graph, node, node.outputs, node.signals.output) - - # Make inputs and outputs groups ordered by (invisibly) drawing a connection - # Exploit the fact that all nodes have `run` and `ran` signal channels - node_graph.edge( - _channel_name(node, node.signals.input[node.signals.input.labels[0]]), - _channel_name(node, node.signals.output[node.signals.output.labels[0]]), - ltail=_io_name(node, node.inputs), - lhead=_io_name(node, node.outputs), + from pyiron_contrib.workflow.channels import Channel as WorkflowChannel + from pyiron_contrib.workflow.io import DataIO, SignalIO + from pyiron_contrib.workflow.node import Node as WorkflowNode + + +def directed_graph(name, label, rankdir="TB"): + """A shortcut method for instantiating the type of graphviz graph we want""" + digraph = graphviz.graphs.Digraph(name=name) + digraph.attr(label=label, compound="true", rankdir=rankdir) + return digraph + + +class WorkflowGraphvizMap(ABC): + @property + @abstractmethod + def parent(self) -> WorkflowGraphvizMap | None: + pass + + @property + @abstractmethod + def name(self) -> str: + pass + + @property + @abstractmethod + def label(self) -> str: + pass + + @property + @abstractmethod + def graph(self) -> graphviz.graphs.Digraph: + pass + + +class Channel(WorkflowGraphvizMap): + def __init__( + self, + parent: _IO, + channel: WorkflowChannel, + shape: str = "oval", + ): + self.channel = channel + self._parent = parent + self._name = self.parent.name + self.channel.label + self._label = self._build_label() + self.channel: WorkflowChannel = channel + + self.graph.node(name=self.name, label=self.label, shape=shape) + + def _build_label(self): + label = self.channel.label + try: + if self.channel.type_hint is not None: + label += ": " + self.channel.type_hint.__name__ + except AttributeError: + pass # Signals have no type + return label + + @property + def parent(self) -> _IO | None: + return self._parent + + @property + def name(self) -> str: + return self._name + + @property + def label(self) -> str: + return self._label + + @property + def graph(self) -> graphviz.graphs.Digraph: + return self.parent.graph + + +class _IO(WorkflowGraphvizMap, ABC): + def __init__(self, parent: Node): + self._parent = parent + self.node = self.parent.node + self.data_io, self.signals_io = self._get_node_io() + self._name = self.parent.name + self.data_io.__class__.__name__ + self._label = self.data_io.__class__.__name__ + self._graph = directed_graph(self.name, self.label, rankdir="TB") + + self.channels = [ + Channel(self, channel, shape="cds") for channel in self.signals_io + ] + [ + Channel(self, channel, shape="oval") for channel in self.data_io + ] + + self.parent.graph.subgraph(self.graph) + + @abstractmethod + def _get_node_io(self) -> tuple[DataIO, SignalIO]: + pass + + @property + def parent(self) -> Node: + return self._parent + + @property + def name(self) -> str: + return self._name + + @property + def label(self) -> str: + return self._label + + @property + def graph(self) -> graphviz.graphs.Digraph: + return self._graph + + def __len__(self): + return len(self.channels) + + +class Inputs(_IO): + def _get_node_io(self) -> tuple[DataIO, SignalIO]: + return self.node.inputs, self.node.signals.input + + +class Outputs(_IO): + def _get_node_io(self) -> tuple[DataIO, SignalIO]: + return self.node.outputs, self.node.signals.output + + +class Node(WorkflowGraphvizMap): + def __init__( + self, + node: WorkflowNode, + parent: Optional[Node] = None, + granularity: int = 0, + ): + self.node = node + self._parent = parent + self._name = self.build_node_name() + self._label = self.node.label + ": " + self.node.__class__.__name__ + self._graph = directed_graph(self.name, self.label, rankdir="LR") + + self.inputs = Inputs(self) + self.outputs = Outputs(self) + self.graph.edge( + self.inputs.channels[0].name, + self.outputs.channels[0].name, style="invis" ) - return parent_graph + if granularity > 0: + try: + self.nodes = [ + Node(node, self, granularity - 1) + for node in self.node.nodes.values() + ] + except AttributeError: + # Only composite nodes have their own nodes attribute + self.nodes = [] + + # TODO: Connect nodes + # Nodes have channels, channels have channel, channel has connections + # TODO: Map nodes IO to IO + + if self.parent is not None: + self.parent.graph.subgraph(self.graph) + + def build_node_name(self, suffix=""): + if self.parent is not None: + # Recursively prepend parent labels to get a totally unique label string + # (inside the scope of this graph) + return self.parent.build_node_name(suffix=suffix + self.node.label) + else: + return "cluster" + self.node.label + suffix + + @property + def parent(self) -> Node | None: + return self._parent + + @property + def name(self) -> str: + return self._name + + @property + def label(self) -> str: + return self._label + + @property + def graph(self) -> graphviz.graphs.Digraph: + return self._graph diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index fe2551c40..6afef7dde 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -10,7 +10,7 @@ from typing import Optional, TYPE_CHECKING from pyiron_contrib.executors import CloudpickleProcessPoolExecutor -from pyiron_contrib.workflow.draw import draw_node +from pyiron_contrib.workflow.draw import Node as GraphvizNode from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Signals, InputSignal, OutputSignal @@ -279,5 +279,5 @@ def fully_connected(self): and self.signals.fully_connected ) - def draw(self, parent_graph: Optional[graphviz.graphs.Digraph] = None): - return draw_node(self, parent_graph) + def draw(self, granularity=0) -> graphviz.graphs.Digraph: + return GraphvizNode(self, granularity=granularity).graph From e55fdd5cff33fd5d4cff9f4955d526429ba37c22 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 20 Jul 2023 12:39:35 -0700 Subject: [PATCH 434/756] Connect internal nodes --- pyiron_contrib/workflow/draw.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index df5ccb8f8..ecc3cc28e 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -159,21 +159,31 @@ def __init__( if granularity > 0: try: - self.nodes = [ - Node(node, self, granularity - 1) - for node in self.node.nodes.values() - ] + self._connect_owned_nodes(granularity) except AttributeError: # Only composite nodes have their own nodes attribute - self.nodes = [] - - # TODO: Connect nodes - # Nodes have channels, channels have channel, channel has connections - # TODO: Map nodes IO to IO + pass if self.parent is not None: self.parent.graph.subgraph(self.graph) + def _connect_owned_nodes(self, granularity): + nodes = [ + Node(node, self, granularity - 1) + for node in self.node.nodes.values() + ] + for source_node in nodes: + for source_channel in source_node.outputs.channels: + for inp in source_channel.channel.connections: + for destination_node in nodes: + for destination_channel in destination_node.inputs.channels: + if inp is destination_channel.channel: + self.graph.edge( + source_channel.name, + destination_channel.name + ) + # TODO: Map nodes IO to IO + def build_node_name(self, suffix=""): if self.parent is not None: # Recursively prepend parent labels to get a totally unique label string From 1661fcd3eaafe41772fadd7e093a4de3be6d6265 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 20 Jul 2023 12:57:17 -0700 Subject: [PATCH 435/756] Refactor: readability --- pyiron_contrib/workflow/draw.py | 47 +++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index ecc3cc28e..7dcec7221 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -172,17 +172,42 @@ def _connect_owned_nodes(self, granularity): Node(node, self, granularity - 1) for node in self.node.nodes.values() ] - for source_node in nodes: - for source_channel in source_node.outputs.channels: - for inp in source_channel.channel.connections: - for destination_node in nodes: - for destination_channel in destination_node.inputs.channels: - if inp is destination_channel.channel: - self.graph.edge( - source_channel.name, - destination_channel.name - ) - # TODO: Map nodes IO to IO + internal_inputs = [ + channel for node in nodes for channel in node.inputs.channels + ] + internal_outputs = [ + channel for node in nodes for channel in node.outputs.channels + ] + + # Loop to check for internal node output --> internal node input connections + for output_channel in internal_outputs: + for input_channel in internal_inputs: + if input_channel.channel in output_channel.channel.connections: + self.graph.edge( + output_channel.name, + input_channel.name + ) + + # Loop to check for macro input --> internal node input connections + self._connect_matching(self.inputs.channels, internal_inputs) + # Loop to check for macro input --> internal node input connections + self._connect_matching(internal_outputs, self.outputs.channels) + + def _connect_matching( + self, + sources: list[Channel], + destinations: list[Channel] + ): + """ + Draw an edge between two graph channels whose workflow channels are the same + """ + for source in sources: + for destination in destinations: + if source.channel is destination.channel: + self.graph.edge( + source.name, + destination.name + ) def build_node_name(self, suffix=""): if self.parent is not None: From 0cc4b1ff6e3b9897b29c78a2b6c2e9dacb00f755 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 20 Jul 2023 13:05:32 -0700 Subject: [PATCH 436/756] Just pass granularity=1 as default Excessive granularity can't hurt us, so always pass something >0 and don't bother with overriding the method --- pyiron_contrib/workflow/composite.py | 8 +------- pyiron_contrib/workflow/draw.py | 2 +- pyiron_contrib/workflow/node.py | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 42598e59c..bacc934e8 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -7,7 +7,7 @@ from abc import ABC from functools import partial -from typing import Optional, TYPE_CHECKING +from typing import Optional from warnings import warn from pyiron_contrib.executors import CloudpickleProcessPoolExecutor @@ -24,9 +24,6 @@ from pyiron_contrib.workflow.node_library.package import NodePackage from pyiron_contrib.workflow.util import DotDict -if TYPE_CHECKING: - import graphviz - class _NodeDecoratorAccess: """An intermediate container to store node-creating decorators as class methods.""" @@ -207,9 +204,6 @@ def remove(self, node: Node | str): else: del self.nodes[node] - def draw(self, granularity=1) -> graphviz.graphs.Digraph: - return super().draw(granularity=granularity) - def __setattr__(self, label: str, node: Node): if isinstance(node, Node): self.add_node(node, label=label) diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index 7dcec7221..ffddc2931 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -141,7 +141,7 @@ def __init__( self, node: WorkflowNode, parent: Optional[Node] = None, - granularity: int = 0, + granularity: int = 1, ): self.node = node self._parent = parent diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 6afef7dde..8cae84c8a 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -279,5 +279,5 @@ def fully_connected(self): and self.signals.fully_connected ) - def draw(self, granularity=0) -> graphviz.graphs.Digraph: + def draw(self, granularity=1) -> graphviz.graphs.Digraph: return GraphvizNode(self, granularity=granularity).graph From 75e363af805b7a78c5a379da8f9a65865c2605b1 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 21 Jul 2023 09:58:56 +0200 Subject: [PATCH 437/756] Remove read_cgfs because of numpy incompat --- pyiron_contrib/atomistics/mlip/cfgs.py | 15 +++++ pyiron_contrib/atomistics/mlip/mlip.py | 81 ++------------------------ 2 files changed, 19 insertions(+), 77 deletions(-) diff --git a/pyiron_contrib/atomistics/mlip/cfgs.py b/pyiron_contrib/atomistics/mlip/cfgs.py index 5c09f32fa..8f463f87d 100644 --- a/pyiron_contrib/atomistics/mlip/cfgs.py +++ b/pyiron_contrib/atomistics/mlip/cfgs.py @@ -177,3 +177,18 @@ def savecfgs(filename, cfgs, desc=None): for cfg in cfgs: savecfg(file, cfg, desc) print("", file=file) + + +def load_grades_ids_and_timesteps(filename): + ids = [] + timesteps = [] + grades = [] + with open(filename) as f: + for line in f: + if line.startswith("FEATURE MV_GRADE"): + grades.append(float(line.split()[-1])) + elif line.startswith("FEATURE PYIRON"): + i, t = map(int, line.rsplit(maxsplit=1)[-1].split("_")) + ids.append(i) + timesteps.append(t) + return np.array(grades), np.array(ids), np.array(timesteps) diff --git a/pyiron_contrib/atomistics/mlip/mlip.py b/pyiron_contrib/atomistics/mlip/mlip.py index 4c47d915b..054e4cd5f 100644 --- a/pyiron_contrib/atomistics/mlip/mlip.py +++ b/pyiron_contrib/atomistics/mlip/mlip.py @@ -20,7 +20,7 @@ ) from pyiron_atomistics import ase_to_pyiron, Atoms from pyiron_contrib.atomistics.ml.potentialfit import PotentialFit -from pyiron_contrib.atomistics.mlip.cfgs import savecfgs, loadcfgs, Cfg +from pyiron_contrib.atomistics.mlip.cfgs import savecfgs, loadcfgs, Cfg, load_grades_ids_and_timesteps from pyiron_contrib.atomistics.mlip.potential import MtpPotential __author__ = "Jan Janssen" @@ -235,39 +235,17 @@ def collect_logfiles(self): def collect_output(self): file_name = os.path.join(self.working_directory, "diff.cfg") if os.path.exists(file_name): - _, _, _, _, _, _, _, job_id_diff_lst, timestep_diff_lst = read_cgfs( - file_name - ) + _, job_id_diff_lst, timestep_diff_lst = load_grades_ids_and_timesteps(file_name) else: job_id_diff_lst, timestep_diff_lst = [], [] file_name = os.path.join(self.working_directory, "selected.cfg") if os.path.exists(file_name): - ( - _, - _, - _, - _, - _, - _, - _, - job_id_new_training_lst, - timestep_new_training_lst, - ) = read_cgfs(file_name) + _, job_id_new_training_lst, timestep_new_training_lst = load_grades_ids_and_timesteps(file_name) else: job_id_new_training_lst, timestep_new_training_lst = [], [] file_name = os.path.join(self.working_directory, "grades.cfg") if os.path.exists(file_name): - ( - _, - _, - _, - _, - _, - _, - grades_lst, - job_id_grades_lst, - timestep_grades_lst, - ) = read_cgfs(file_name) + grades_lst, job_id_grades_lst, timestep_grades_lst = load_grades_ids_and_timesteps(file_name) else: grades_lst, job_id_grades_lst, timestep_grades_lst = [], [], [] try: @@ -753,54 +731,3 @@ def write_cfg( cfg_object.desc = "pyiron\t" + track_str cfg_lst.append(cfg_object) savecfgs(filename=file_name, cfgs=cfg_lst, desc=None) - - -def read_cgfs(file_name): - cgfs_lst = loadcfgs(filename=file_name, max_cfgs=None) - cell, positions, forces, stress, energy, indicies, grades, jobids, timesteps = ( - [], - [], - [], - [], - [], - [], - [], - [], - [], - ) - for cgf in cgfs_lst: - if cgf.pos is not None: - positions.append(cgf.pos) - if cgf.lat is not None: - cell.append(cgf.lat) - if cgf.types is not None: - indicies.append(cgf.types) - if cgf.energy is not None: - energy.append(cgf.energy) - if cgf.forces is not None: - forces.append(cgf.forces) - if cgf.stresses is not None: - stress.append( - [ - [cgf.stresses[0], cgf.stresses[5], cgf.stresses[4]], - [cgf.stresses[5], cgf.stresses[1], cgf.stresses[3]], - [cgf.stresses[4], cgf.stresses[3], cgf.stresses[2]], - ] - ) - if cgf.grade is not None: - grades.append(cgf.grade) - if cgf.desc is not None: - job_id, timestep = cgf.desc.split("_") - jobids.append(int(job_id)) - timesteps.append(int(timestep)) - return [ - np.array(cell), - np.array(positions), - np.array(forces), - np.array(stress), - np.array(energy), - np.array(indicies), - np.array(grades), - np.array(jobids), - np.array(timesteps), - ] From 44d0cc03ee1bd6679882bd43b2e1e76fb7ac307b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 11:13:25 +0000 Subject: [PATCH 438/756] Bump pympipool from 0.5.5 to 0.5.6 Bumps [pympipool](https://github.com/jan-janssen/pympipool) from 0.5.5 to 0.5.6. - [Release notes](https://github.com/jan-janssen/pympipool/releases) - [Commits](https://github.com/jan-janssen/pympipool/compare/pympipool-0.5.5...pympipool-0.5.6) --- updated-dependencies: - dependency-name: pympipool dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 76810c764..5949e36df 100644 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ ], 'tinybase': [ 'distributed==2023.5.0', - 'pympipool==0.5.5' + 'pympipool==0.5.6' ] }, cmdclass=versioneer.get_cmdclass(), From bc80dfbe5d1a0a347b104c68e967be5d458069a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 11:13:46 +0000 Subject: [PATCH 439/756] Bump boto3 from 1.28.5 to 1.28.9 Bumps [boto3](https://github.com/boto/boto3) from 1.28.5 to 1.28.9. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.28.5...1.28.9) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 76810c764..b1c1b6c39 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ ], 'image': ['scikit-image==0.21.0'], 'generic': [ - 'boto3==1.28.5', + 'boto3==1.28.9', 'moto==4.1.13' ], 'workflow': [ From e34372b15dbff7a16180e92f179765dd092e50d3 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 24 Jul 2023 11:13:56 +0000 Subject: [PATCH 440/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index c881e6768..bd7a63ec1 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -20,5 +20,5 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.71.0 -- pympipool =0.5.5 +- pympipool =0.5.6 - distributed =2023.5.0 From 39199aab7faaddad6a0cc56d027a782a06a6d6a0 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 24 Jul 2023 11:14:04 +0000 Subject: [PATCH 441/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index c881e6768..37090b7a7 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 -- boto3 =1.28.5 +- boto3 =1.28.9 - moto =4.1.13 - pycp2k =0.2.2 - typeguard =4.0.0 From 932ebecfa147e4dd7f8a0b6a86ee25f245f59bd7 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 24 Jul 2023 11:14:20 +0000 Subject: [PATCH 442/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 9cef4f59f..665d795b5 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -20,7 +20,7 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.71.0 -- pympipool =0.5.5 +- pympipool =0.5.6 - distributed =2023.5.0 - python >= 3.10 - lammps diff --git a/docs/environment.yml b/docs/environment.yml index 6f866fdb4..9e49f56f3 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -22,5 +22,5 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.71.0 -- pympipool =0.5.5 +- pympipool =0.5.6 - distributed =2023.5.0 From 778ec18beca38fc88363be7b26b0c308b14c1c34 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 24 Jul 2023 11:14:26 +0000 Subject: [PATCH 443/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 9cef4f59f..2a1cd20ab 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 -- boto3 =1.28.5 +- boto3 =1.28.9 - moto =4.1.13 - pycp2k =0.2.2 - typeguard =4.0.0 diff --git a/docs/environment.yml b/docs/environment.yml index 6f866fdb4..0606ee6d0 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -17,7 +17,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 -- boto3 =1.28.5 +- boto3 =1.28.9 - moto =4.1.13 - pycp2k =0.2.2 - typeguard =4.0.0 From cdf2907ae83079c078bbfd96d457473aa833fd59 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 24 Jul 2023 15:56:12 +0200 Subject: [PATCH 444/756] Use alternative for get_schedaffinity --- pyiron_contrib/tinybase/creator.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/tinybase/creator.py b/pyiron_contrib/tinybase/creator.py index cc10c802f..a4af16cce 100644 --- a/pyiron_contrib/tinybase/creator.py +++ b/pyiron_contrib/tinybase/creator.py @@ -18,7 +18,12 @@ import importlib from typing import Union from functools import wraps -from os import sched_getaffinity + +try: + from os import sched_getaffinity + cpu_count = lambda: len(sched_getaffinity) +except ImportError: + from multiprocessing import cpu_count import pyiron_contrib.tinybase.job from pyiron_contrib.tinybase.project import JobNotFoundError @@ -170,7 +175,7 @@ def bulk(self, *args, **kwargs): class ExecutorCreator(Creator): - _DEFAULT_CPUS = min(int(0.5 * len(sched_getaffinity(0))), 8) + _DEFAULT_CPUS = min(int(0.5 * cpu_count()), 8) def __init__(self, project, config): self._most_recent = None From cec9391c910c171c07f6cb6919e595e2f5478d36 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 24 Jul 2023 13:57:40 +0000 Subject: [PATCH 445/756] Format black --- pyiron_contrib/tinybase/creator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyiron_contrib/tinybase/creator.py b/pyiron_contrib/tinybase/creator.py index a4af16cce..4720682fc 100644 --- a/pyiron_contrib/tinybase/creator.py +++ b/pyiron_contrib/tinybase/creator.py @@ -21,6 +21,7 @@ try: from os import sched_getaffinity + cpu_count = lambda: len(sched_getaffinity) except ImportError: from multiprocessing import cpu_count From 90d6d5414e915abbdeef87b2ec24c93b3de6f035 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Sun, 23 Jul 2023 15:52:43 +0200 Subject: [PATCH 446/756] Replace read_cgfs in LammpsMlip --- pyiron_contrib/atomistics/mlip/lammps.py | 43 ++++++++++++++---------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/pyiron_contrib/atomistics/mlip/lammps.py b/pyiron_contrib/atomistics/mlip/lammps.py index 75a34da09..25ca3a9ca 100644 --- a/pyiron_contrib/atomistics/mlip/lammps.py +++ b/pyiron_contrib/atomistics/mlip/lammps.py @@ -7,7 +7,6 @@ from pyiron_atomistics.lammps.interactive import LammpsInteractive from pyiron_atomistics.atomistics.structure.atoms import Atoms from pyiron_atomistics.atomistics.structure.structurestorage import StructureStorage -from pyiron_contrib.atomistics.mlip.mlip import read_cgfs from pyiron_contrib.atomistics.mlip.cfgs import loadcfgs from pyiron_base import GenericParameters @@ -121,7 +120,26 @@ def collect_output(self): if "select:save-selected" in self.input.mlip._dataset["Parameter"]: file_name = self._get_selection_file() if os.path.exists(file_name): + cell = [] + positions = [] + forces = [] + stress = [] + energy = [] + indicies = [] for cfg in loadcfgs(file_name): + cell.append(cfg.lat) + positions.append(cfg.pos) + forces.append(cfg.forces) + stress.append( + [ + [cgf.stresses[0], cgf.stresses[5], cgf.stresses[4]], + [cgf.stresses[5], cgf.stresses[1], cgf.stresses[3]], + [cgf.stresses[4], cgf.stresses[3], cgf.stresses[2]], + ] + ) + energy.append(cfg.energy) + indicies.append(cfg.types) + species = np.array(self.potential.Species.iloc[0])[ cfg.types.astype(int) ] @@ -134,24 +152,13 @@ def collect_output(self): ), mv_grade=cfg.grade, ) - ( - cell, - positions, - forces, - stress, - energy, - indicies, - grades, - jobids, - timesteps, - ) = read_cgfs(file_name=file_name) with self.project_hdf5.open("output/mlip") as hdf5_output: - hdf5_output["forces"] = forces - hdf5_output["energy_tot"] = energy - hdf5_output["pressures"] = stress - hdf5_output["cells"] = cell - hdf5_output["positions"] = positions - hdf5_output["indicies"] = indicies + hdf5_output["forces"] = np.array(forces) + hdf5_output["energy_tot"] = np.array(energy) + hdf5_output["pressures"] = np.array(stress) + hdf5_output["cells"] = np.array(cell) + hdf5_output["positions"] = np.array(positions) + hdf5_output["indicies"] = np.array(indicies) self.selected_structures.to_hdf( self.project_hdf5.open("output"), "selected" ) From f7d21b2e6ed94cf563a24006c461cc45d76b01c7 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 26 Jul 2023 07:15:20 +0000 Subject: [PATCH 447/756] add drag.py --- pyiron_contrib/atomistics/lammps/drag.py | 391 +++++++++++++++++++++++ 1 file changed, 391 insertions(+) create mode 100644 pyiron_contrib/atomistics/lammps/drag.py diff --git a/pyiron_contrib/atomistics/lammps/drag.py b/pyiron_contrib/atomistics/lammps/drag.py new file mode 100644 index 000000000..a79c7b2eb --- /dev/null +++ b/pyiron_contrib/atomistics/lammps/drag.py @@ -0,0 +1,391 @@ +import numpy as np +from hashlib import sha1 +from tqdm.auto import tqdm +from .base import get_potential, ProjectContainer, Hydrogen +from sklearn.gaussian_process import GaussianProcessRegressor + + +def get_job_name(structure): + return sha1(structure.__repr__().encode()).hexdigest() + + +class Points: + """ + Class to create a meshgrid and sort the points by box symmetry. + + Exp: + >>> points = Points(structure) + >>> unique_data = [do_something(xx) for xx in points.unique_positions] + >>> total_data = points.unravel(unique_data) + """ + def __init__(self, structure, mesh_distance=0.2): + """ + Args: + structure (pyiron_atomistics.atomistics.structure.atoms): structure + for which the meshgrid is to be produced + mesh_distance (float): mesh spacing + """ + self.mesh_distance = mesh_distance + self._labels = None + self._structure = structure + + @property + def _n_points(self): + return np.rint( + self._structure.cell.diagonal() / self.mesh_distance / 2 + ).astype(int) * 2 + + @property + def _lin_space(self): + linspace_lst = [] + for ll, nn in zip(self._structure.cell.diagonal(), self._n_points): + linspace, dx = np.linspace(0, ll, nn, endpoint=False, retstep=True) + linspace_lst.append(linspace + 0.5 * dx) + return linspace_lst + + @property + def _meshgrid(self): + meshgrid = np.meshgrid(*self._lin_space, indexing='ij') + return np.einsum('xijk->ijkx', meshgrid) + + @property + def _meshgrid_flat(self): + return self._meshgrid.reshape(-1, 3) + + @property + def _symmetry(self): + return self._structure.get_symmetry() + + @property + def _all_labels(self): + if self._labels is None: + self._labels = self._symmetry.get_arg_equivalent_sites(self._meshgrid_flat) + return self._labels + + @property + def unique_positions(self): + """ + Unique positions among the meshgrid. + """ + return self._meshgrid_flat[self._unique_indices] + + @property + def min_distance(self): + """ + Minimum distance of a grid point and atoms. This should not be too + small for the simulation to not explode. Currently, there is no + automatic check system implemented, meaning this attribute is not + internally used anywhere. + """ + return self._structure.get_neighborhood( + self.unique_positions, num_neighbors=1 + ).distances.min() + + @property + def _uniques(self): + return np.unique( + self._all_labels, return_index=True, return_counts=True + ) + + @property + def _unique_counts(self): + return self._uniques[2] + + @property + def _unique_labels(self): + return self._uniques[0] + + @property + def _unique_indices(self): + return self._uniques[1] + + def unravel(self, **kwargs): + """ + Args: + kwargs (numpy.ndarray): Data to unravel (must have the same length + as unique_positions) + + Returns: + (numpy.ndarray): unravelled data + """ + results = {"positions": self._lin_space} + for k, v in kwargs.items(): + y_all = np.zeros(len(self._all_labels)) + for ll, yy in zip(self._unique_labels, v): + y_all[self._all_labels==ll] = yy + results[k] = y_all.reshape(*self._n_points) + return results + + def get_position_from_index(self, index): + """ + Args: + index (int): index + + Returns: + (numpy.ndarray): position for the index + """ + unraveled_index = np.unravel_index(index, self._n_points) + return self._meshgrid[unraveled_index] + + +def setup_lmp_input(lmp, n_atoms=None, direction=None, fix_id=-1): + """ + Change input for LAMMPS to run a drag calculation. + + Args: + lmp (pyiron_atomistics.lammps.lammps.Lammps): LAMMPS job + n_atoms (int): number of free atoms (default: None, i.e. it is + determined from the job structure) + direction (None/numpy.ndarray): direction along which the force is + cancelled. None if all forces are to be cancelled (default: None) + fix_id (None/int): id of the atom to be fixed (default: -1, i.e. last + atom) + + Returns: + None (input of lmp is changed in-place) + + In the context of this function, a drag calculation is a constraint energy + minimization, in which one atom is either not allowed to move at all, or + not allowed to move along a given direction. In order for the system to not + fall to the energy minimum, the sum of the remaining forces is set to 0. + + Exp: Hydrogen diffusion + + >>> from pyiron_atomistics import Project + >>> pr = Project("DRAG") + >>> bulk = pr.create.structure.bulk('Ni', cubic=True) + >>> a_0 = bulk.cell[0, 0] + >>> x_octa = np.array([0, 0, 0.5 * a_0]) + >>> x_tetra = np.array(3 * [0.25 * a_0]) + >>> dx = x_tetra - x_octa + >>> transition = np.linspace(0, 1, 101) + >>> x_lst = transition[:, None] * dx + x_octa + >>> structure = bulk.repeat(4) + pr.create.structure.atoms( + ... positions=[x_octa], + ... elements=['H'], + ... cell=structure.cell + ... ) + >>> lmp = pr.create.job.Lammps('lmp') + >>> lmp.structure = structure + >>> lmp.calc_minimize() + >>> lmp.potential = potential_of_your_choice + >>> setup_lmp_input(lmp, direction=dx) + >>> lmp.interactive_open() + >>> for xx in x_lst: + >>> lmp.structure.positions[-1] = xx + >>> lmp.run() + >>> lmp.interactive_close() + """ + if lmp.input.control["minimize"] is None: + raise ValueError("set calc_minimize first") + if n_atoms is None: + try: + n_atoms = len(lmp.structure) - 1 + except TypeError: + raise AssertionError("either `n_atoms` or the structure must be set") + fix_id = np.arange(n_atoms)[fix_id] + 2 + lmp.input.control['atom_modify'] = 'map array' + lmp.input.control["group___fixed"] = f"id {fix_id}" + lmp.input.control["group___free"] = "subtract all fixed" + if direction is None: + for ii, xx in enumerate(['x', 'y', 'z']): + lmp.input.control[f'variable___f{xx}_free'] = f'equal f{xx}[{fix_id}]/{n_atoms}' + lmp.input.control[f'variable___f{xx}_fixed'] = f'equal -f{xx}[{fix_id}]' + else: + direction = np.array(direction) / np.linalg.norm(direction) + direction = np.outer(direction, direction) + direction = np.around(direction, decimals=8) + for grp, ss in zip(["free", "fixed"], [f"1/{n_atoms}*", "-"]): + for ii, xx in enumerate(['x', 'y', 'z']): + txt = "+".join([f"({ss}f{xxx}[{fix_id}]*({direction[ii][iii]}))" for iii, xxx in enumerate(['x', 'y', 'z'])]) + lmp.input.control[f'variable___f{xx}_{grp}'] = f" equal {txt}" + lmp.input.control['variable___energy'] = "atom 0" + for key in ["free", "fixed"]: + txt = " ".join([f"v_f{x}_{key}" for x in ["x", "y", "z"]]) + lmp.input.control[f"fix___f_{key}"] = f"{key} addforce {txt} energy v_energy" + lmp.input.control['min_style'] = 'quickmin' + + +class Drag(ProjectContainer): + """ + Run drag calculation + + Internal routines: + + 1. Create a meshgrid for the box inserted + 2. Create a Lammps job, to which the repeated box is inserted, if the box + length does not exceed `min_length`. + 3. Compute the Voronoi vertices + 4. Run drag calculations for the Voronoi vertices + 5. Suggest a new point new sample point based on the covariance matrix + + The data can be accessed via `drag.field`, in which case the data for the + meshgrid is given, or `drag.data`, which contains the positions of + measurements including their symmetrically equivalent points, as well as + their energy values. + + Exp: + + >>> import matplotlib.pylab as plt + >>> from pyiron_atomistics import Project + >>> pr = Project("DRAG") + >>> bulk = pr.create.structure.bulk('Ni', cubic=True) + >>> drag = Drag(pr, bulk) + >>> drag.run() + >>> layer = 0 + >>> for tag in ['energy', 'error']: + ... plt.contourf(*drag.field['positions'][:2], drag.field[tag].T[layer]); + ... plt.colorbar().set_label(tag) + ... plt.show(); + + This gives the energy landscape in the lowest layer. + """ + def __init__( + self, + pr, + structure, + min_length=10, + mesh_distance=0.2, + buffer=3, + distance_threshold=0.1, + ): + """ + Args: + pr (pyiron_atomistics.project.Project): Project + structure (pyiron_atomistics.atomistics.structure.atoms): structure + min_length (float): minimum box length (default: 10) + mesh_distance (float): mesh spacing + buffer (float): distance up to which the atoms beyond box + boundaries are considered + distance_threshold (float): distance below which the Voronoi + vertices are considered to be one point + """ + super().__init__(pr=pr) + self._min_length = min_length + self._structure = structure + self._buffer = buffer + self._lmp = None + self._data = None + self._field = None + self._gp = None + self._points = Points(structure, mesh_distance=mesh_distance) + self._distance_threshold = distance_threshold + + @property + def lmp(self): + """Job attribute""" + if self._lmp is None: + self._lmp = self.pr.create.job.Lammps( + ('lmp', sha1(self._structure.__repr__().encode()).hexdigest()) + ) + self._lmp.potential = get_potential() + self._lmp.calc_minimize(n_print=1000) + setup_lmp_input(self._lmp, len(self._rep_structure)) + self._lmp.interactive_open() + return self._lmp + + def _initialize(self): + self._data = None + self._field = None + self._gp = None + + @property + def _next_positions(self): + if self.lmp.status.initialized: + return self._unique_voro + return self._points.get_position_from_index(np.argmax(self.field['error'])) + + def run(self, positions=None): + """ + Run command. + + Args: + positions (numpy.ndarray): Positions for which the energy sampling + should take place. None if the new position should be + given either from the Voronoi vertices (at the beginning) or + calculated from the maximum error value given by the Gaussian + process. + + Returns: + None + """ + if positions is None: + positions = self._next_positions + for xx in tqdm(np.atleast_2d(positions)): + self.lmp.structure = self.append_hydrogen(xx) + self.lmp.run() + self._initialize() + + @property + def gaussian_process(self): + """Gaussian process regressor""" + if self._gp is None: + xx, ii = self._structure.get_extended_positions( + self._buffer, return_indices=True, positions=self.data['positions'] + ) + self._gp = GaussianProcessRegressor().fit(xx, self.data['energy'][ii]) + return self._gp + + @property + def field(self): + if self._field is None: + E, err = self.gaussian_process.predict( + self._points.unique_positions, return_std=True + ) + self._field = self._points.unravel(energy=E, error=err) + return self._field + + @property + def data(self): + if self._data is None: + x = self._symmetry.generate_equivalent_points( + self.lmp.output.positions[:, -1], return_unique=False + ) + E = np.tile(self.lmp.output.energy_pot - self._ref_energy, len(x)) + x = x.reshape(-1, 3) + unique_indices = np.unique( + np.round(x, decimals=3), return_index=True, axis=0 + )[1] + self._data = { + "positions": x[unique_indices], + "energy": E[unique_indices], + } + return self._data + + @property + def _repeat(self): + return np.ceil(self._min_length / self._structure.cell.diagonal()).astype(int) + + @property + def _rep_structure(self): + return self._structure.repeat(self._repeat) + + @property + def _symmetry(self): + return self._structure.get_symmetry() + + @property + def _unique_voro(self): + voro = self._structure.analyse.get_voronoi_vertices( + distance_threshold=self._distance_threshold + ) + ids = self._symmetry.get_arg_equivalent_sites(voro) + return voro[np.unique(ids, return_index=True)[1]] + + def append_hydrogen(self, x): + return self._rep_structure + self.pr.create.structure.atoms( + elements=['H'], positions=[x], cell=self._rep_structure.cell + ) + + @property + def _energy_hydrogen(self): + h = Hydrogen(3, self.pr) + return h.E_octa + + @property + def _energy_base(self): + return self.get_minimize(self._structure).output.energy_pot[-1] + + @property + def _ref_energy(self): + return self._energy_hydrogen + np.prod(self._repeat) * self._energy_base From 49189adc87dd015f491bda69f13bbc243e7ee309 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 26 Jul 2023 07:21:17 +0000 Subject: [PATCH 448/756] remove unused lines --- pyiron_contrib/atomistics/lammps/drag.py | 305 +---------------------- 1 file changed, 1 insertion(+), 304 deletions(-) diff --git a/pyiron_contrib/atomistics/lammps/drag.py b/pyiron_contrib/atomistics/lammps/drag.py index a79c7b2eb..a7c116d15 100644 --- a/pyiron_contrib/atomistics/lammps/drag.py +++ b/pyiron_contrib/atomistics/lammps/drag.py @@ -9,125 +9,6 @@ def get_job_name(structure): return sha1(structure.__repr__().encode()).hexdigest() -class Points: - """ - Class to create a meshgrid and sort the points by box symmetry. - - Exp: - >>> points = Points(structure) - >>> unique_data = [do_something(xx) for xx in points.unique_positions] - >>> total_data = points.unravel(unique_data) - """ - def __init__(self, structure, mesh_distance=0.2): - """ - Args: - structure (pyiron_atomistics.atomistics.structure.atoms): structure - for which the meshgrid is to be produced - mesh_distance (float): mesh spacing - """ - self.mesh_distance = mesh_distance - self._labels = None - self._structure = structure - - @property - def _n_points(self): - return np.rint( - self._structure.cell.diagonal() / self.mesh_distance / 2 - ).astype(int) * 2 - - @property - def _lin_space(self): - linspace_lst = [] - for ll, nn in zip(self._structure.cell.diagonal(), self._n_points): - linspace, dx = np.linspace(0, ll, nn, endpoint=False, retstep=True) - linspace_lst.append(linspace + 0.5 * dx) - return linspace_lst - - @property - def _meshgrid(self): - meshgrid = np.meshgrid(*self._lin_space, indexing='ij') - return np.einsum('xijk->ijkx', meshgrid) - - @property - def _meshgrid_flat(self): - return self._meshgrid.reshape(-1, 3) - - @property - def _symmetry(self): - return self._structure.get_symmetry() - - @property - def _all_labels(self): - if self._labels is None: - self._labels = self._symmetry.get_arg_equivalent_sites(self._meshgrid_flat) - return self._labels - - @property - def unique_positions(self): - """ - Unique positions among the meshgrid. - """ - return self._meshgrid_flat[self._unique_indices] - - @property - def min_distance(self): - """ - Minimum distance of a grid point and atoms. This should not be too - small for the simulation to not explode. Currently, there is no - automatic check system implemented, meaning this attribute is not - internally used anywhere. - """ - return self._structure.get_neighborhood( - self.unique_positions, num_neighbors=1 - ).distances.min() - - @property - def _uniques(self): - return np.unique( - self._all_labels, return_index=True, return_counts=True - ) - - @property - def _unique_counts(self): - return self._uniques[2] - - @property - def _unique_labels(self): - return self._uniques[0] - - @property - def _unique_indices(self): - return self._uniques[1] - - def unravel(self, **kwargs): - """ - Args: - kwargs (numpy.ndarray): Data to unravel (must have the same length - as unique_positions) - - Returns: - (numpy.ndarray): unravelled data - """ - results = {"positions": self._lin_space} - for k, v in kwargs.items(): - y_all = np.zeros(len(self._all_labels)) - for ll, yy in zip(self._unique_labels, v): - y_all[self._all_labels==ll] = yy - results[k] = y_all.reshape(*self._n_points) - return results - - def get_position_from_index(self, index): - """ - Args: - index (int): index - - Returns: - (numpy.ndarray): position for the index - """ - unraveled_index = np.unravel_index(index, self._n_points) - return self._meshgrid[unraveled_index] - - def setup_lmp_input(lmp, n_atoms=None, direction=None, fix_id=-1): """ Change input for LAMMPS to run a drag calculation. @@ -183,7 +64,7 @@ def setup_lmp_input(lmp, n_atoms=None, direction=None, fix_id=-1): n_atoms = len(lmp.structure) - 1 except TypeError: raise AssertionError("either `n_atoms` or the structure must be set") - fix_id = np.arange(n_atoms)[fix_id] + 2 + fix_id = np.arange(n_atoms + 1)[fix_id] + 1 lmp.input.control['atom_modify'] = 'map array' lmp.input.control["group___fixed"] = f"id {fix_id}" lmp.input.control["group___free"] = "subtract all fixed" @@ -205,187 +86,3 @@ def setup_lmp_input(lmp, n_atoms=None, direction=None, fix_id=-1): lmp.input.control[f"fix___f_{key}"] = f"{key} addforce {txt} energy v_energy" lmp.input.control['min_style'] = 'quickmin' - -class Drag(ProjectContainer): - """ - Run drag calculation - - Internal routines: - - 1. Create a meshgrid for the box inserted - 2. Create a Lammps job, to which the repeated box is inserted, if the box - length does not exceed `min_length`. - 3. Compute the Voronoi vertices - 4. Run drag calculations for the Voronoi vertices - 5. Suggest a new point new sample point based on the covariance matrix - - The data can be accessed via `drag.field`, in which case the data for the - meshgrid is given, or `drag.data`, which contains the positions of - measurements including their symmetrically equivalent points, as well as - their energy values. - - Exp: - - >>> import matplotlib.pylab as plt - >>> from pyiron_atomistics import Project - >>> pr = Project("DRAG") - >>> bulk = pr.create.structure.bulk('Ni', cubic=True) - >>> drag = Drag(pr, bulk) - >>> drag.run() - >>> layer = 0 - >>> for tag in ['energy', 'error']: - ... plt.contourf(*drag.field['positions'][:2], drag.field[tag].T[layer]); - ... plt.colorbar().set_label(tag) - ... plt.show(); - - This gives the energy landscape in the lowest layer. - """ - def __init__( - self, - pr, - structure, - min_length=10, - mesh_distance=0.2, - buffer=3, - distance_threshold=0.1, - ): - """ - Args: - pr (pyiron_atomistics.project.Project): Project - structure (pyiron_atomistics.atomistics.structure.atoms): structure - min_length (float): minimum box length (default: 10) - mesh_distance (float): mesh spacing - buffer (float): distance up to which the atoms beyond box - boundaries are considered - distance_threshold (float): distance below which the Voronoi - vertices are considered to be one point - """ - super().__init__(pr=pr) - self._min_length = min_length - self._structure = structure - self._buffer = buffer - self._lmp = None - self._data = None - self._field = None - self._gp = None - self._points = Points(structure, mesh_distance=mesh_distance) - self._distance_threshold = distance_threshold - - @property - def lmp(self): - """Job attribute""" - if self._lmp is None: - self._lmp = self.pr.create.job.Lammps( - ('lmp', sha1(self._structure.__repr__().encode()).hexdigest()) - ) - self._lmp.potential = get_potential() - self._lmp.calc_minimize(n_print=1000) - setup_lmp_input(self._lmp, len(self._rep_structure)) - self._lmp.interactive_open() - return self._lmp - - def _initialize(self): - self._data = None - self._field = None - self._gp = None - - @property - def _next_positions(self): - if self.lmp.status.initialized: - return self._unique_voro - return self._points.get_position_from_index(np.argmax(self.field['error'])) - - def run(self, positions=None): - """ - Run command. - - Args: - positions (numpy.ndarray): Positions for which the energy sampling - should take place. None if the new position should be - given either from the Voronoi vertices (at the beginning) or - calculated from the maximum error value given by the Gaussian - process. - - Returns: - None - """ - if positions is None: - positions = self._next_positions - for xx in tqdm(np.atleast_2d(positions)): - self.lmp.structure = self.append_hydrogen(xx) - self.lmp.run() - self._initialize() - - @property - def gaussian_process(self): - """Gaussian process regressor""" - if self._gp is None: - xx, ii = self._structure.get_extended_positions( - self._buffer, return_indices=True, positions=self.data['positions'] - ) - self._gp = GaussianProcessRegressor().fit(xx, self.data['energy'][ii]) - return self._gp - - @property - def field(self): - if self._field is None: - E, err = self.gaussian_process.predict( - self._points.unique_positions, return_std=True - ) - self._field = self._points.unravel(energy=E, error=err) - return self._field - - @property - def data(self): - if self._data is None: - x = self._symmetry.generate_equivalent_points( - self.lmp.output.positions[:, -1], return_unique=False - ) - E = np.tile(self.lmp.output.energy_pot - self._ref_energy, len(x)) - x = x.reshape(-1, 3) - unique_indices = np.unique( - np.round(x, decimals=3), return_index=True, axis=0 - )[1] - self._data = { - "positions": x[unique_indices], - "energy": E[unique_indices], - } - return self._data - - @property - def _repeat(self): - return np.ceil(self._min_length / self._structure.cell.diagonal()).astype(int) - - @property - def _rep_structure(self): - return self._structure.repeat(self._repeat) - - @property - def _symmetry(self): - return self._structure.get_symmetry() - - @property - def _unique_voro(self): - voro = self._structure.analyse.get_voronoi_vertices( - distance_threshold=self._distance_threshold - ) - ids = self._symmetry.get_arg_equivalent_sites(voro) - return voro[np.unique(ids, return_index=True)[1]] - - def append_hydrogen(self, x): - return self._rep_structure + self.pr.create.structure.atoms( - elements=['H'], positions=[x], cell=self._rep_structure.cell - ) - - @property - def _energy_hydrogen(self): - h = Hydrogen(3, self.pr) - return h.E_octa - - @property - def _energy_base(self): - return self.get_minimize(self._structure).output.energy_pot[-1] - - @property - def _ref_energy(self): - return self._energy_hydrogen + np.prod(self._repeat) * self._energy_base From 0a90c4f4d1929d3e1c27125b383e5931830164bb Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 26 Jul 2023 07:25:27 +0000 Subject: [PATCH 449/756] Format black --- pyiron_contrib/atomistics/lammps/drag.py | 26 +++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/pyiron_contrib/atomistics/lammps/drag.py b/pyiron_contrib/atomistics/lammps/drag.py index a7c116d15..83cee9133 100644 --- a/pyiron_contrib/atomistics/lammps/drag.py +++ b/pyiron_contrib/atomistics/lammps/drag.py @@ -65,24 +65,30 @@ def setup_lmp_input(lmp, n_atoms=None, direction=None, fix_id=-1): except TypeError: raise AssertionError("either `n_atoms` or the structure must be set") fix_id = np.arange(n_atoms + 1)[fix_id] + 1 - lmp.input.control['atom_modify'] = 'map array' + lmp.input.control["atom_modify"] = "map array" lmp.input.control["group___fixed"] = f"id {fix_id}" lmp.input.control["group___free"] = "subtract all fixed" if direction is None: - for ii, xx in enumerate(['x', 'y', 'z']): - lmp.input.control[f'variable___f{xx}_free'] = f'equal f{xx}[{fix_id}]/{n_atoms}' - lmp.input.control[f'variable___f{xx}_fixed'] = f'equal -f{xx}[{fix_id}]' + for ii, xx in enumerate(["x", "y", "z"]): + lmp.input.control[ + f"variable___f{xx}_free" + ] = f"equal f{xx}[{fix_id}]/{n_atoms}" + lmp.input.control[f"variable___f{xx}_fixed"] = f"equal -f{xx}[{fix_id}]" else: direction = np.array(direction) / np.linalg.norm(direction) direction = np.outer(direction, direction) direction = np.around(direction, decimals=8) for grp, ss in zip(["free", "fixed"], [f"1/{n_atoms}*", "-"]): - for ii, xx in enumerate(['x', 'y', 'z']): - txt = "+".join([f"({ss}f{xxx}[{fix_id}]*({direction[ii][iii]}))" for iii, xxx in enumerate(['x', 'y', 'z'])]) - lmp.input.control[f'variable___f{xx}_{grp}'] = f" equal {txt}" - lmp.input.control['variable___energy'] = "atom 0" + for ii, xx in enumerate(["x", "y", "z"]): + txt = "+".join( + [ + f"({ss}f{xxx}[{fix_id}]*({direction[ii][iii]}))" + for iii, xxx in enumerate(["x", "y", "z"]) + ] + ) + lmp.input.control[f"variable___f{xx}_{grp}"] = f" equal {txt}" + lmp.input.control["variable___energy"] = "atom 0" for key in ["free", "fixed"]: txt = " ".join([f"v_f{x}_{key}" for x in ["x", "y", "z"]]) lmp.input.control[f"fix___f_{key}"] = f"{key} addforce {txt} energy v_energy" - lmp.input.control['min_style'] = 'quickmin' - + lmp.input.control["min_style"] = "quickmin" From 17e2586024bee2fb2dc3a1675a6d6775a0547b79 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 26 Jul 2023 07:26:34 +0000 Subject: [PATCH 450/756] remove all the unnecessary imports --- pyiron_contrib/atomistics/lammps/drag.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pyiron_contrib/atomistics/lammps/drag.py b/pyiron_contrib/atomistics/lammps/drag.py index a7c116d15..41c93fff3 100644 --- a/pyiron_contrib/atomistics/lammps/drag.py +++ b/pyiron_contrib/atomistics/lammps/drag.py @@ -1,12 +1,4 @@ import numpy as np -from hashlib import sha1 -from tqdm.auto import tqdm -from .base import get_potential, ProjectContainer, Hydrogen -from sklearn.gaussian_process import GaussianProcessRegressor - - -def get_job_name(structure): - return sha1(structure.__repr__().encode()).hexdigest() def setup_lmp_input(lmp, n_atoms=None, direction=None, fix_id=-1): From 0e7dc01b52f5b84f8f878db49d0beb21d0ecd22a Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 26 Jul 2023 07:30:58 +0000 Subject: [PATCH 451/756] update docstring --- pyiron_contrib/atomistics/lammps/drag.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/atomistics/lammps/drag.py b/pyiron_contrib/atomistics/lammps/drag.py index 41c93fff3..c7a75dc1f 100644 --- a/pyiron_contrib/atomistics/lammps/drag.py +++ b/pyiron_contrib/atomistics/lammps/drag.py @@ -25,6 +25,7 @@ def setup_lmp_input(lmp, n_atoms=None, direction=None, fix_id=-1): Exp: Hydrogen diffusion >>> from pyiron_atomistics import Project + >>> import numpy as np >>> pr = Project("DRAG") >>> bulk = pr.create.structure.bulk('Ni', cubic=True) >>> a_0 = bulk.cell[0, 0] @@ -33,7 +34,8 @@ def setup_lmp_input(lmp, n_atoms=None, direction=None, fix_id=-1): >>> dx = x_tetra - x_octa >>> transition = np.linspace(0, 1, 101) >>> x_lst = transition[:, None] * dx + x_octa - >>> structure = bulk.repeat(4) + pr.create.structure.atoms( + >>> structure = bulk.repeat(4) + >>> structure += pr.create.structure.atoms( ... positions=[x_octa], ... elements=['H'], ... cell=structure.cell From 7dc18f496aee4dfa65f24004b3bd6b28d04fd76b Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 26 Jul 2023 07:50:35 +0000 Subject: [PATCH 452/756] add one test --- tests/unit/atomistics/lammps/test_drag.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/unit/atomistics/lammps/test_drag.py diff --git a/tests/unit/atomistics/lammps/test_drag.py b/tests/unit/atomistics/lammps/test_drag.py new file mode 100644 index 000000000..5f49341e8 --- /dev/null +++ b/tests/unit/atomistics/lammps/test_drag.py @@ -0,0 +1,17 @@ +import unittest +from pyiron_atomistics import Project +from pyiron_contrib.atomistics.lammps.drag import setup_lmp_input + +class TestDrag(unittest.TestCase): + def setUp(self): + pr = Project("DRAG") + bulk = pr.create.structure.bulk('Ni', cubic=True) + self.lmp = pr.create.job.Lammps("test") + self.lmp.structure = bulk.repeat(2) + + def test_calc_minimize(self): + self.assertRaises(ValueError, setup_lmp_input, self.lmp) + + +if __name__ == '__main__': + unittest.main() From eb3944ed804d85f0d938328765c39ceeb3c9a243 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 26 Jul 2023 07:52:54 +0000 Subject: [PATCH 453/756] add __init__.py --- pyiron_contrib/atomistics/lammps/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pyiron_contrib/atomistics/lammps/__init__.py diff --git a/pyiron_contrib/atomistics/lammps/__init__.py b/pyiron_contrib/atomistics/lammps/__init__.py new file mode 100644 index 000000000..e69de29bb From 62ebab3cbfdc5d29edd7ffa37d7166565452747f Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 26 Jul 2023 07:57:31 +0000 Subject: [PATCH 454/756] add init from tests --- tests/unit/atomistics/lammps/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/unit/atomistics/lammps/__init__.py diff --git a/tests/unit/atomistics/lammps/__init__.py b/tests/unit/atomistics/lammps/__init__.py new file mode 100644 index 000000000..e69de29bb From 5b50a62d72bbaae6bc3eb4caa44acbd1c34d16ea Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 26 Jul 2023 07:58:05 +0000 Subject: [PATCH 455/756] remove project name --- tests/unit/atomistics/lammps/test_drag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/atomistics/lammps/test_drag.py b/tests/unit/atomistics/lammps/test_drag.py index 5f49341e8..9e53a4e25 100644 --- a/tests/unit/atomistics/lammps/test_drag.py +++ b/tests/unit/atomistics/lammps/test_drag.py @@ -4,7 +4,7 @@ class TestDrag(unittest.TestCase): def setUp(self): - pr = Project("DRAG") + pr = Project(".") bulk = pr.create.structure.bulk('Ni', cubic=True) self.lmp = pr.create.job.Lammps("test") self.lmp.structure = bulk.repeat(2) From 50bb8b07d20660fa7427c92bdd29f2c1eb39eb78 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 26 Jul 2023 08:01:00 +0000 Subject: [PATCH 456/756] add min_style test --- tests/unit/atomistics/lammps/test_drag.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/unit/atomistics/lammps/test_drag.py b/tests/unit/atomistics/lammps/test_drag.py index 9e53a4e25..c2d948273 100644 --- a/tests/unit/atomistics/lammps/test_drag.py +++ b/tests/unit/atomistics/lammps/test_drag.py @@ -5,13 +5,16 @@ class TestDrag(unittest.TestCase): def setUp(self): pr = Project(".") - bulk = pr.create.structure.bulk('Ni', cubic=True) self.lmp = pr.create.job.Lammps("test") - self.lmp.structure = bulk.repeat(2) + self.lmp.structure = pr.create.structure.bulk('Ni', cubic=True) def test_calc_minimize(self): self.assertRaises(ValueError, setup_lmp_input, self.lmp) + def test_min_style(self): + sef.lmp.calc_minimize() + self.assertEqual(self.lmp.input.control["min_style"], "quickmin") + if __name__ == '__main__': unittest.main() From 0dfd795371c618f57bebee8614512383e242cedb Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 26 Jul 2023 08:11:16 +0000 Subject: [PATCH 457/756] do not include zero forces --- pyiron_contrib/atomistics/lammps/drag.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyiron_contrib/atomistics/lammps/drag.py b/pyiron_contrib/atomistics/lammps/drag.py index 9bed067ed..1741be581 100644 --- a/pyiron_contrib/atomistics/lammps/drag.py +++ b/pyiron_contrib/atomistics/lammps/drag.py @@ -78,8 +78,11 @@ def setup_lmp_input(lmp, n_atoms=None, direction=None, fix_id=-1): [ f"({ss}f{xxx}[{fix_id}]*({direction[ii][iii]}))" for iii, xxx in enumerate(["x", "y", "z"]) + if not np.isclose(direction[ii][iii], 0) ] ) + if txt == "": + txt = 0 lmp.input.control[f"variable___f{xx}_{grp}"] = f" equal {txt}" lmp.input.control["variable___energy"] = "atom 0" for key in ["free", "fixed"]: From c31f3c0e685213961ce61c0d4a9eef3c4ce4bfe9 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 26 Jul 2023 08:12:21 +0000 Subject: [PATCH 458/756] stupid typo --- tests/unit/atomistics/lammps/test_drag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/atomistics/lammps/test_drag.py b/tests/unit/atomistics/lammps/test_drag.py index c2d948273..df03a03f2 100644 --- a/tests/unit/atomistics/lammps/test_drag.py +++ b/tests/unit/atomistics/lammps/test_drag.py @@ -12,7 +12,7 @@ def test_calc_minimize(self): self.assertRaises(ValueError, setup_lmp_input, self.lmp) def test_min_style(self): - sef.lmp.calc_minimize() + self.lmp.calc_minimize() self.assertEqual(self.lmp.input.control["min_style"], "quickmin") From 7a44615402201dd998a8655f59db3012136db24a Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 26 Jul 2023 08:13:39 +0000 Subject: [PATCH 459/756] another stupid typo --- tests/unit/atomistics/lammps/test_drag.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/atomistics/lammps/test_drag.py b/tests/unit/atomistics/lammps/test_drag.py index df03a03f2..2e007966a 100644 --- a/tests/unit/atomistics/lammps/test_drag.py +++ b/tests/unit/atomistics/lammps/test_drag.py @@ -13,6 +13,8 @@ def test_calc_minimize(self): def test_min_style(self): self.lmp.calc_minimize() + self.assertEqual(self.lmp.input.control["min_style"], "cg") + setup_lmp_input(self.lmp) self.assertEqual(self.lmp.input.control["min_style"], "quickmin") From fd48770e854304601dd84d42ca6e98849c6f710f Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 26 Jul 2023 09:22:31 +0000 Subject: [PATCH 460/756] add direction --- tests/unit/atomistics/lammps/test_drag.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unit/atomistics/lammps/test_drag.py b/tests/unit/atomistics/lammps/test_drag.py index 2e007966a..643c34ae0 100644 --- a/tests/unit/atomistics/lammps/test_drag.py +++ b/tests/unit/atomistics/lammps/test_drag.py @@ -17,6 +17,11 @@ def test_min_style(self): setup_lmp_input(self.lmp) self.assertEqual(self.lmp.input.control["min_style"], "quickmin") + def test_direction(self): + self.lmp.calc_minimize() + setup_lmp_input(self.lmp, direction=[0, 0, 1]) + self.assertEqual(self.lmp.input.control["variable___fx_free"], " equal 0") + if __name__ == '__main__': unittest.main() From 21d96a52aace9da9f35b2113b82ab36b01bf88be Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Thu, 27 Jul 2023 07:17:36 +0000 Subject: [PATCH 461/756] Format black --- pyiron_contrib/atomistics/lammps/potentials.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/pyiron_contrib/atomistics/lammps/potentials.py b/pyiron_contrib/atomistics/lammps/potentials.py index 5553bf85a..49667c6e7 100644 --- a/pyiron_contrib/atomistics/lammps/potentials.py +++ b/pyiron_contrib/atomistics/lammps/potentials.py @@ -268,9 +268,7 @@ def results(self): @property def defined_pairs(self): all_pair = [] - for int_s, pre_s in zip( - self._interacting_species, self._preset_species - ): + for int_s, pre_s in zip(self._interacting_species, self._preset_species): pair = [] for ss in int_s: if ss == "*": @@ -279,8 +277,7 @@ def defined_pairs(self): pair.append([ss]) all_pair.extend( np.unique( - [sorted([p1, p2]) for p1 in pair[0] for p2 in pair[1]], - axis=0 + [sorted([p1, p2]) for p1 in pair[0] for p2 in pair[1]], axis=0 ).tolist() ) return all_pair @@ -288,10 +285,8 @@ def defined_pairs(self): @property def undefined_pairs(self): all_pairs = [ - sorted(s) - for s in itertools.combinations_with_replacement( - self._species, 2 - ) + sorted(s) + for s in itertools.combinations_with_replacement(self._species, 2) ] return [p for p in all_pairs if p not in self.defined_pairs] From 297ff7a1092437b37b14596f7e57c4485b6bafd6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 11:34:11 +0000 Subject: [PATCH 462/756] Bump moto from 4.1.13 to 4.1.14 Bumps [moto](https://github.com/getmoto/moto) from 4.1.13 to 4.1.14. - [Changelog](https://github.com/getmoto/moto/blob/master/CHANGELOG.md) - [Commits](https://github.com/getmoto/moto/compare/4.1.13...4.1.14) --- updated-dependencies: - dependency-name: moto dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4e74ef6b0..8dba6eec5 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ 'image': ['scikit-image==0.21.0'], 'generic': [ 'boto3==1.28.9', - 'moto==4.1.13' + 'moto==4.1.14' ], 'workflow': [ 'python>=3.10', From 1142d49d404f979b9cb3fc9981b87c24630c2a25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 11:34:27 +0000 Subject: [PATCH 463/756] Bump pyparsing from 3.1.0 to 3.1.1 Bumps [pyparsing](https://github.com/pyparsing/pyparsing) from 3.1.0 to 3.1.1. - [Release notes](https://github.com/pyparsing/pyparsing/releases) - [Changelog](https://github.com/pyparsing/pyparsing/blob/master/CHANGES) - [Commits](https://github.com/pyparsing/pyparsing/compare/3.1.0...3.1.1) --- updated-dependencies: - dependency-name: pyparsing dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4e74ef6b0..dbdbc28fc 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ 'pyiron_base==0.6.3', 'scipy==1.10.1', 'seaborn==0.12.2', - 'pyparsing==3.1.0' + 'pyparsing==3.1.1' ], extras_require={ 'atomistic': [ From ec5fd52d6617fd258a3e85645c6294041dfcc244 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 31 Jul 2023 11:34:33 +0000 Subject: [PATCH 464/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 65b80954b..20f704456 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -16,7 +16,7 @@ dependencies: - scikit-image =0.21.0 - randspg =0.0.1 - boto3 =1.28.9 -- moto =4.1.13 +- moto =4.1.14 - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.71.0 From 4e34ab57047547fc937813b1cd7d88909acec7ae Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 31 Jul 2023 11:34:46 +0000 Subject: [PATCH 465/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 65b80954b..a7c402bc7 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -10,7 +10,7 @@ dependencies: - numpy =1.24.3 - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 -- pyparsing =3.1.0 +- pyparsing =3.1.1 - scipy =1.10.1 - seaborn =0.12.2 - scikit-image =0.21.0 From 5974110ed14ef10185d5ffdf2d3a4d5c979a7f50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 11:34:55 +0000 Subject: [PATCH 466/756] Bump typeguard from 4.0.0 to 4.1.0 Bumps [typeguard](https://github.com/agronholm/typeguard) from 4.0.0 to 4.1.0. - [Changelog](https://github.com/agronholm/typeguard/blob/master/docs/versionhistory.rst) - [Commits](https://github.com/agronholm/typeguard/compare/4.0.0...4.1.0) --- updated-dependencies: - dependency-name: typeguard dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4e74ef6b0..8f3690119 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,7 @@ 'workflow': [ 'python>=3.10', 'ipython', - 'typeguard==4.0.0' + 'typeguard==4.1.0' ], 'tinybase': [ 'distributed==2023.5.0', From 44d597ae390f52c0321f83c15be28c02931b8b37 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 31 Jul 2023 11:34:55 +0000 Subject: [PATCH 467/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 09f537cd9..1f78d0cb6 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -16,7 +16,7 @@ dependencies: - scikit-image =0.21.0 - randspg =0.0.1 - boto3 =1.28.9 -- moto =4.1.13 +- moto =4.1.14 - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.71.0 diff --git a/docs/environment.yml b/docs/environment.yml index 287f1f363..a7a3e4c6b 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -18,7 +18,7 @@ dependencies: - scikit-image =0.21.0 - randspg =0.0.1 - boto3 =1.28.9 -- moto =4.1.13 +- moto =4.1.14 - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.71.0 From f788080b1d662b72f2261c2f8fd51618ff1ba809 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 31 Jul 2023 11:35:09 +0000 Subject: [PATCH 468/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 09f537cd9..1e3b00813 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -10,7 +10,7 @@ dependencies: - numpy =1.24.3 - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 -- pyparsing =3.1.0 +- pyparsing =3.1.1 - scipy =1.10.1 - seaborn =0.12.2 - scikit-image =0.21.0 diff --git a/docs/environment.yml b/docs/environment.yml index 287f1f363..576deb296 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -12,7 +12,7 @@ dependencies: - numpy =1.24.3 - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 -- pyparsing =3.1.0 +- pyparsing =3.1.1 - scipy =1.10.1 - seaborn =0.12.2 - scikit-image =0.21.0 From df4a77dfa8fd2a939de95c4623d5a74d2ea979f2 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 31 Jul 2023 11:35:15 +0000 Subject: [PATCH 469/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 65b80954b..eda639265 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -18,7 +18,7 @@ dependencies: - boto3 =1.28.9 - moto =4.1.13 - pycp2k =0.2.2 -- typeguard =4.0.0 +- typeguard =4.1.0 - aws-sam-translator =1.71.0 - pympipool =0.5.6 - distributed =2023.5.0 From 89034e5d0492922516773f1f9665cf540d1e638a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 11:35:31 +0000 Subject: [PATCH 470/756] Bump pympipool from 0.5.6 to 0.6.1 Bumps [pympipool](https://github.com/jan-janssen/pympipool) from 0.5.6 to 0.6.1. - [Release notes](https://github.com/jan-janssen/pympipool/releases) - [Commits](https://github.com/jan-janssen/pympipool/compare/pympipool-0.5.6...pympipool-0.6.1) --- updated-dependencies: - dependency-name: pympipool dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4e74ef6b0..6ea85fadc 100644 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ ], 'tinybase': [ 'distributed==2023.5.0', - 'pympipool==0.5.6' + 'pympipool==0.6.1' ] }, cmdclass=versioneer.get_cmdclass(), From af61d2dff8e9720435f934248eb5b70f04b96dd3 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 31 Jul 2023 11:39:05 +0000 Subject: [PATCH 471/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 09f537cd9..a0ce52ed9 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -18,7 +18,7 @@ dependencies: - boto3 =1.28.9 - moto =4.1.13 - pycp2k =0.2.2 -- typeguard =4.0.0 +- typeguard =4.1.0 - aws-sam-translator =1.71.0 - pympipool =0.5.6 - distributed =2023.5.0 diff --git a/docs/environment.yml b/docs/environment.yml index 287f1f363..e46962828 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -20,7 +20,7 @@ dependencies: - boto3 =1.28.9 - moto =4.1.13 - pycp2k =0.2.2 -- typeguard =4.0.0 +- typeguard =4.1.0 - aws-sam-translator =1.71.0 - pympipool =0.5.6 - distributed =2023.5.0 From d024250f153d8b2a1498ca3f22f41f6bf971126b Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 31 Jul 2023 11:42:33 +0000 Subject: [PATCH 472/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 65b80954b..3c6958bb8 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -20,5 +20,5 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.71.0 -- pympipool =0.5.6 +- pympipool =0.6.1 - distributed =2023.5.0 From 9d4495cd513d418481f74bc3f066670fa0aeddcf Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 31 Jul 2023 11:51:49 +0000 Subject: [PATCH 473/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 09f537cd9..f0110d2bb 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -20,7 +20,7 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.71.0 -- pympipool =0.5.6 +- pympipool =0.6.1 - distributed =2023.5.0 - python >= 3.10 - lammps diff --git a/docs/environment.yml b/docs/environment.yml index 287f1f363..a528f96ec 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -22,5 +22,5 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.0.0 - aws-sam-translator =1.71.0 -- pympipool =0.5.6 +- pympipool =0.6.1 - distributed =2023.5.0 From 1249f164c29998fa80839ee70fe4b953fcd0ead6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 12:19:38 +0000 Subject: [PATCH 474/756] Bump boto3 from 1.28.9 to 1.28.15 Bumps [boto3](https://github.com/boto/boto3) from 1.28.9 to 1.28.15. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.28.9...1.28.15) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b9f717ec1..baaa30550 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ ], 'image': ['scikit-image==0.21.0'], 'generic': [ - 'boto3==1.28.9', + 'boto3==1.28.15', 'moto==4.1.14' ], 'workflow': [ From f39889c3bcef87c3a01301c720944206e1de2e05 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 31 Jul 2023 12:20:01 +0000 Subject: [PATCH 475/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index c806ba588..8aa57007b 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 -- boto3 =1.28.9 +- boto3 =1.28.15 - moto =4.1.14 - pycp2k =0.2.2 - typeguard =4.1.0 From fd08fe131c8a3169b80e2c08824e33e602e6e5e2 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 31 Jul 2023 12:20:27 +0000 Subject: [PATCH 476/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 02ef2fe70..93fa04061 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 -- boto3 =1.28.9 +- boto3 =1.28.15 - moto =4.1.14 - pycp2k =0.2.2 - typeguard =4.1.0 diff --git a/docs/environment.yml b/docs/environment.yml index dedf928e8..108499ec4 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -17,7 +17,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 -- boto3 =1.28.9 +- boto3 =1.28.15 - moto =4.1.14 - pycp2k =0.2.2 - typeguard =4.1.0 From 0164812d57da8bc446299d0b4297807452c347af Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 1 Aug 2023 17:41:24 +0000 Subject: [PATCH 477/756] remove unused properties --- .../atomistics/lammps/potentials.py | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/pyiron_contrib/atomistics/lammps/potentials.py b/pyiron_contrib/atomistics/lammps/potentials.py index 5553bf85a..b860aa24c 100644 --- a/pyiron_contrib/atomistics/lammps/potentials.py +++ b/pyiron_contrib/atomistics/lammps/potentials.py @@ -265,36 +265,6 @@ def results(self): ) ] - @property - def defined_pairs(self): - all_pair = [] - for int_s, pre_s in zip( - self._interacting_species, self._preset_species - ): - pair = [] - for ss in int_s: - if ss == "*": - pair.append(pre_s) - else: - pair.append([ss]) - all_pair.extend( - np.unique( - [sorted([p1, p2]) for p1 in pair[0] for p2 in pair[1]], - axis=0 - ).tolist() - ) - return all_pair - - @property - def undefined_pairs(self): - all_pairs = [ - sorted(s) - for s in itertools.combinations_with_replacement( - self._species, 2 - ) - ] - return [p for p in all_pairs if p not in self.defined_pairs] - @property def s_dict(self): if self._s_dict is None: From ea984eae2bb2148bfce580e3f2307afd59e9b52e Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 2 Aug 2023 09:00:54 +0000 Subject: [PATCH 478/756] update mainly doc --- .../atomistics/lammps/potentials.py | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/pyiron_contrib/atomistics/lammps/potentials.py b/pyiron_contrib/atomistics/lammps/potentials.py index b860aa24c..ef2b598eb 100644 --- a/pyiron_contrib/atomistics/lammps/potentials.py +++ b/pyiron_contrib/atomistics/lammps/potentials.py @@ -56,7 +56,7 @@ Example >>> import pandas as pd ->>> return pd.DataFrame( +>>> pd.DataFrame( ... { ... "Config": [[ ... 'pair_style my_potential 3.2\n', @@ -437,16 +437,13 @@ def _get_pair_style(config): ["pair_coeff" in c for c in config] ) raise ValueError( - f""" - pair_style could not determined: {config}. - - The reason why you are seeing this error is most likely because - the potential you chose had a corrupt config. It is - supposed to have at least one item which starts with "pair_style". - If you are using the standard pyiron database, feel free to - submit an issue on {issue_page} - Typically you can get a reply within 24h. - """ + f"pair_style could not determined: {config}.\n\n" + "The reason why you are seeing this error is most likely because " + "the potential you chose had a corrupt config. It is " + "supposed to have at least one item which starts with 'pair_style'.\n" + "If you are using the standard pyiron database, feel free to " + f"submit an issue on {issue_page}. " + "Typically you can get a reply within 24h.\n" ) @staticmethod @@ -520,17 +517,12 @@ def check_cutoff(f): def wrapper(*args, **kwargs): if "cutoff" not in kwargs or kwargs["cutoff"] == 0: raise ValueError( - f""" - It is not possible to set cutoff=0 for parameter-based - potentials. If you think this should be possible, you have the - following options: - - - Open an issue on our GitHub page: {issue_page} - - - Write your own potential in pyiron format. Here's how: - - {doc_pyiron_df} - """ + "It is not possible to set cutoff=0 for parameter-based " + "potentials. If you think this should be possible, you have the " + "following options:\n\n" + f"- Open an issue on our GitHub page: {issue_page}\n" + "- Write your own potential in pyiron format. Here's how:\n" + f"{doc_pyiron_df}\n" ) return f(*args, **kwargs) From 107abbca1096b985dd361d0292e7621fa0ca3b66 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 2 Aug 2023 09:12:09 +0000 Subject: [PATCH 479/756] bugfix --- pyiron_contrib/atomistics/lammps/potentials.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyiron_contrib/atomistics/lammps/potentials.py b/pyiron_contrib/atomistics/lammps/potentials.py index 6cb556e3a..ef2b598eb 100644 --- a/pyiron_contrib/atomistics/lammps/potentials.py +++ b/pyiron_contrib/atomistics/lammps/potentials.py @@ -265,6 +265,7 @@ def results(self): ) ] + @property def s_dict(self): if self._s_dict is None: self._s_dict = dict( From 94c43ffd45d25e7813aa62f9d944389aaa74b586 Mon Sep 17 00:00:00 2001 From: Sarath Menon Date: Wed, 2 Aug 2023 15:05:32 +0200 Subject: [PATCH 480/756] update setup --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a73da2d11..196a2b1db 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ 'pyiron_base==0.6.3', 'scipy==1.10.1', 'seaborn==0.12.2', - 'pyparsing==3.1.1' + 'pyparsing<3.1' ], extras_require={ 'atomistic': [ From 050e056122eab67325c437dafaf1c9bbbaf44932 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 2 Aug 2023 13:12:27 +0000 Subject: [PATCH 481/756] append wrapping and add tests --- .../atomistics/lammps/potentials.py | 2 ++ .../unit/atomistics/lammps/test_potentials.py | 21 ++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/pyiron_contrib/atomistics/lammps/potentials.py b/pyiron_contrib/atomistics/lammps/potentials.py index ef2b598eb..655c1c402 100644 --- a/pyiron_contrib/atomistics/lammps/potentials.py +++ b/pyiron_contrib/atomistics/lammps/potentials.py @@ -18,6 +18,7 @@ import numpy as np import warnings import itertools +from functools import wraps general_doc = """ @@ -514,6 +515,7 @@ def df(self): def check_cutoff(f): + @wraps(f) def wrapper(*args, **kwargs): if "cutoff" not in kwargs or kwargs["cutoff"] == 0: raise ValueError( diff --git a/tests/unit/atomistics/lammps/test_potentials.py b/tests/unit/atomistics/lammps/test_potentials.py index a626c17d0..8963f8285 100644 --- a/tests/unit/atomistics/lammps/test_potentials.py +++ b/tests/unit/atomistics/lammps/test_potentials.py @@ -150,6 +150,7 @@ def test_pair_style(self): ) self.assertEqual(pot.pair_style, "pair_style a\n") + class TestPairCoeff(unittest.TestCase): def setUp(cls): cls.pot = LammpsPotentials() @@ -185,6 +186,8 @@ def test_hybrid(self): self.assertEqual(pc.counter, ["1", "2", ""]) self.assertTrue(pc.is_hybrid) self.assertEqual(pc.pair_style, ["my_style", "my_style", "another_style"]) + self.assertEqual(pc.s_dict, {'Al': '1', 'Fe': '2', '*': '*'}) + self.assertEqual(pc.interacting_species, 3 * ["1 2"]) def test_results(self): pc = self.pot._PairCoeff( @@ -202,14 +205,16 @@ def test_results(self): ] ) - def test_pairs(self): - pc = self.pot._PairCoeff( - pair_style=["style_one", "style_two"], - interacting_species=[["Al", "Fe"], ["Al", "Al"]], - pair_coeff=["arg_one", "arg_two"], - species=["Al", "Fe"], - preset_species=2 * [[]], - ) + +class TestMorse(unittest.TestCase): + def test_incomplete_init(self): + self.assertRaises(ValueError, Morse, "Al") + self.assertRaises(TypeError, Morse, "Al", cutoff=1) + + def test_pair_coeff(self): + morse = Morse("Al", D_0=0.5, alpha=1.1, r_0=2.1, cutoff=6) + self.assertEqual(morse.pair_coeff, ['pair_coeff 1 1 0.5 1.1 2.1 6\n']) + if __name__ == "__main__": unittest.main() From b7eb4ddcf714cbe294c93548f66df89864af82c3 Mon Sep 17 00:00:00 2001 From: Sarath Menon Date: Wed, 2 Aug 2023 15:14:13 +0200 Subject: [PATCH 482/756] update env --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 70806fcee..3bbcafb4c 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -10,7 +10,7 @@ dependencies: - numpy =1.24.3 - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 -- pyparsing =3.1.1 +- pyparsing <3.1 - scipy =1.10.1 - seaborn =0.12.2 - scikit-image =0.21.0 From 5a36377160322597873bc63aa74c4cfee4bd04da Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 2 Aug 2023 13:15:13 +0000 Subject: [PATCH 483/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 92a7100eb..8a3848c4d 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -10,7 +10,7 @@ dependencies: - numpy =1.24.3 - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 -- pyparsing =3.1.1 +- pyparsing <3.1 - scipy =1.10.1 - seaborn =0.12.2 - scikit-image =0.21.0 diff --git a/docs/environment.yml b/docs/environment.yml index 84ff5fa85..a14d58a57 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -12,7 +12,7 @@ dependencies: - numpy =1.24.3 - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 -- pyparsing =3.1.1 +- pyparsing <3.1 - scipy =1.10.1 - seaborn =0.12.2 - scikit-image =0.21.0 From 5dcb1d13295f7700d2a2e0e984ae7d0e517463a0 Mon Sep 17 00:00:00 2001 From: Sarath Menon Date: Wed, 2 Aug 2023 17:44:24 +0200 Subject: [PATCH 484/756] Update .binder/environment.yml Co-authored-by: Jan Janssen --- .binder/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 8a3848c4d..6645c1b4f 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -10,7 +10,7 @@ dependencies: - numpy =1.24.3 - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 -- pyparsing <3.1 +- pyparsing =3.0.9 - scipy =1.10.1 - seaborn =0.12.2 - scikit-image =0.21.0 From ef97cac9ea16290ce0337030a251b59de05f111d Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 2 Aug 2023 15:44:49 +0000 Subject: [PATCH 485/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 6645c1b4f..8a3848c4d 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -10,7 +10,7 @@ dependencies: - numpy =1.24.3 - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 -- pyparsing =3.0.9 +- pyparsing <3.1 - scipy =1.10.1 - seaborn =0.12.2 - scikit-image =0.21.0 From dbae39b62576888678e8dcfcfe1ad5912cbec0aa Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Wed, 2 Aug 2023 10:01:36 -0600 Subject: [PATCH 486/756] Update .ci_support/environment.yml --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 3bbcafb4c..2c52bff3d 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -10,7 +10,7 @@ dependencies: - numpy =1.24.3 - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 -- pyparsing <3.1 +- pyparsing =3.0.9 - scipy =1.10.1 - seaborn =0.12.2 - scikit-image =0.21.0 From 0451d1166eefabb28e8cc4234732973e320fb5eb Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Wed, 2 Aug 2023 10:01:44 -0600 Subject: [PATCH 487/756] Update docs/environment.yml --- docs/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/environment.yml b/docs/environment.yml index a14d58a57..8e7f61f66 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -12,7 +12,7 @@ dependencies: - numpy =1.24.3 - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 -- pyparsing <3.1 +- pyparsing =3.0.9 - scipy =1.10.1 - seaborn =0.12.2 - scikit-image =0.21.0 From 0c50a9bf4983d9b6c832920d22456c24d152754d Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Wed, 2 Aug 2023 10:01:52 -0600 Subject: [PATCH 488/756] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 196a2b1db..fe226331d 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ 'pyiron_base==0.6.3', 'scipy==1.10.1', 'seaborn==0.12.2', - 'pyparsing<3.1' + 'pyparsing==3.0.9', ], extras_require={ 'atomistic': [ From 1c11041e20b1da61b6d4d1684f6df2a98c8551b4 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 2 Aug 2023 16:02:05 +0000 Subject: [PATCH 489/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 8a3848c4d..6645c1b4f 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -10,7 +10,7 @@ dependencies: - numpy =1.24.3 - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 -- pyparsing <3.1 +- pyparsing =3.0.9 - scipy =1.10.1 - seaborn =0.12.2 - scikit-image =0.21.0 From 2e34729d849c0d98be224e3439420834fff0986b Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 2 Aug 2023 10:16:10 -0700 Subject: [PATCH 490/756] Drop python <3.9 from setup.py --- setup.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.py b/setup.py index a73da2d11..3a8bdda66 100644 --- a/setup.py +++ b/setup.py @@ -22,9 +22,6 @@ 'License :: OSI Approved :: BSD License', 'Intended Audience :: Science/Research', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', From 0e72e8941ceb90148496c4c46712bc84dc42cd34 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 2 Aug 2023 17:34:45 +0000 Subject: [PATCH 491/756] Format black --- pyiron_contrib/workflow/composite.py | 6 +----- pyiron_contrib/workflow/function.py | 2 +- pyiron_contrib/workflow/workflow.py | 6 +----- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index c27b3210f..77fd539a2 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -100,11 +100,7 @@ def __init__( **kwargs, ): super().__init__( - *args, - label=label, - parent=parent, - run_on_updates=run_on_updates, - **kwargs + *args, label=label, parent=parent, run_on_updates=run_on_updates, **kwargs ) self.strict_naming: bool = strict_naming self.nodes: DotDict[str:Node] = DotDict() diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index decc53a59..01bb04fe9 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -578,7 +578,7 @@ def _convert_input_args_and_kwargs_to_input_kwargs(self, *args, **kwargs): f"only accepts {len(reverse_keys)} inputs." ) - positional_keywords = reverse_keys[-len(args):] if len(args) > 0 else [] # -0: + positional_keywords = reverse_keys[-len(args) :] if len(args) > 0 else [] # -0: if len(set(positional_keywords).intersection(kwargs.keys())) > 0: raise ValueError( f"Cannot use {set(positional_keywords).intersection(kwargs.keys())} " diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index e2b543e80..2856090ff 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -131,11 +131,7 @@ class Workflow(Composite): """ def __init__( - self, - label: str, - *nodes: Node, - run_on_updates: bool = True, - strict_naming=True + self, label: str, *nodes: Node, run_on_updates: bool = True, strict_naming=True ): super().__init__( label=label, From 42f33cb8e72e3048386c73ef2020e1c8a5460f94 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 2 Aug 2023 12:02:59 -0700 Subject: [PATCH 492/756] Color channels and connections --- pyiron_contrib/workflow/draw.py | 69 +++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index ffddc2931..2629b49bc 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -5,7 +5,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Optional, TYPE_CHECKING +from typing import Literal, Optional, TYPE_CHECKING import graphviz @@ -14,6 +14,17 @@ from pyiron_contrib.workflow.io import DataIO, SignalIO from pyiron_contrib.workflow.node import Node as WorkflowNode +IN_ALPHA = "66" +OUT_ALPHA = "aa" +DATA_COLOR_BASE = "#ebba34" +DATA_COLOR = {"in": DATA_COLOR_BASE + IN_ALPHA, "out": DATA_COLOR_BASE + OUT_ALPHA} +SIGNAL_COLOR_BASE = "#3452ed" +SIGNAL_COLOR = { + "in": SIGNAL_COLOR_BASE + IN_ALPHA, "out": SIGNAL_COLOR_BASE + OUT_ALPHA +} +DATA_SHAPE = "oval" +SIGNAL_SHAPE = "cds" + def directed_graph(name, label, rankdir="TB"): """A shortcut method for instantiating the type of graphviz graph we want""" @@ -49,15 +60,23 @@ def __init__( self, parent: _IO, channel: WorkflowChannel, - shape: str = "oval", + shape: str, + color: str = "white", ): self.channel = channel self._parent = parent self._name = self.parent.name + self.channel.label self._label = self._build_label() self.channel: WorkflowChannel = channel - - self.graph.node(name=self.name, label=self.label, shape=shape) + self._color = color + + self.graph.node( + name=self.name, + label=self.label, + shape=shape, + color=self.color, + style="filled" + ) def _build_label(self): label = self.channel.label @@ -84,6 +103,10 @@ def label(self) -> str: def graph(self) -> graphviz.graphs.Digraph: return self.parent.graph + @property + def color(self): + return self._color + class _IO(WorkflowGraphvizMap, ABC): def __init__(self, parent: Node): @@ -95,9 +118,19 @@ def __init__(self, parent: Node): self._graph = directed_graph(self.name, self.label, rankdir="TB") self.channels = [ - Channel(self, channel, shape="cds") for channel in self.signals_io + Channel( + self, + channel, + SIGNAL_SHAPE, + SIGNAL_COLOR[self.in_or_out] + ) for channel in self.signals_io ] + [ - Channel(self, channel, shape="oval") for channel in self.data_io + Channel( + self, + channel, + DATA_SHAPE, + DATA_COLOR[self.in_or_out] + ) for channel in self.data_io ] self.parent.graph.subgraph(self.graph) @@ -106,6 +139,11 @@ def __init__(self, parent: Node): def _get_node_io(self) -> tuple[DataIO, SignalIO]: pass + @property + @abstractmethod + def in_or_out(self) -> Literal["in", "out"]: + pass + @property def parent(self) -> Node: return self._parent @@ -130,11 +168,19 @@ class Inputs(_IO): def _get_node_io(self) -> tuple[DataIO, SignalIO]: return self.node.inputs, self.node.signals.input + @property + def in_or_out(self) -> Literal["in"]: + return "in" + class Outputs(_IO): def _get_node_io(self) -> tuple[DataIO, SignalIO]: return self.node.outputs, self.node.signals.output + @property + def in_or_out(self) -> Literal["out"]: + return "out" + class Node(WorkflowGraphvizMap): def __init__( @@ -167,6 +213,9 @@ def __init__( if self.parent is not None: self.parent.graph.subgraph(self.graph) + def _gradient_channel_color(self, start_channel, end_channel): + return f"{start_channel.color};0.5:{end_channel.color};0.5" + def _connect_owned_nodes(self, granularity): nodes = [ Node(node, self, granularity - 1) @@ -185,7 +234,10 @@ def _connect_owned_nodes(self, granularity): if input_channel.channel in output_channel.channel.connections: self.graph.edge( output_channel.name, - input_channel.name + input_channel.name, + color=self._gradient_channel_color( + output_channel, input_channel + ) ) # Loop to check for macro input --> internal node input connections @@ -206,7 +258,8 @@ def _connect_matching( if source.channel is destination.channel: self.graph.edge( source.name, - destination.name + destination.name, + color=self._gradient_channel_color(source, destination) ) def build_node_name(self, suffix=""): From fe50c736554500f184211cd1cf1b4c61569230a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Aug 2023 20:44:03 +0000 Subject: [PATCH 493/756] Bump distributed from 2023.5.0 to 2023.7.1 Bumps [distributed](https://github.com/dask/distributed) from 2023.5.0 to 2023.7.1. - [Changelog](https://github.com/dask/distributed/blob/main/docs/release-procedure.md) - [Commits](https://github.com/dask/distributed/compare/2023.5.0...2023.7.1) --- updated-dependencies: - dependency-name: distributed dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8bd3ce90d..1cd868209 100644 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ 'typeguard==4.1.0' ], 'tinybase': [ - 'distributed==2023.5.0', + 'distributed==2023.7.1', 'pympipool==0.6.1' ] }, From 33facc1d1c564ab6a0db4af01a291953d0b585dd Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 2 Aug 2023 20:45:31 +0000 Subject: [PATCH 494/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 9f067f84e..674d0a784 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -22,4 +22,4 @@ dependencies: - typeguard =4.1.0 - aws-sam-translator =1.71.0 - pympipool =0.6.1 -- distributed =2023.5.0 +- distributed =2023.7.1 From 1d5372a896769f6c91a82661b2387f3ae5079582 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 2 Aug 2023 20:45:55 +0000 Subject: [PATCH 495/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index cffd9e1a1..f48190624 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -22,6 +22,6 @@ dependencies: - typeguard =4.1.0 - aws-sam-translator =1.71.0 - pympipool =0.6.1 -- distributed =2023.5.0 +- distributed =2023.7.1 - python >= 3.10 - lammps diff --git a/docs/environment.yml b/docs/environment.yml index a102a6cc9..8e59e742d 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -24,4 +24,4 @@ dependencies: - typeguard =4.1.0 - aws-sam-translator =1.71.0 - pympipool =0.6.1 -- distributed =2023.5.0 +- distributed =2023.7.1 From 2ec691304b683b382c7db4200a630bbf3cba5378 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 2 Aug 2023 13:50:30 -0700 Subject: [PATCH 496/756] Add colour to nodes and IO panels --- pyiron_contrib/workflow/draw.py | 65 ++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index 2629b49bc..69d50f965 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -25,11 +25,26 @@ DATA_SHAPE = "oval" SIGNAL_SHAPE = "cds" +IO_COLOR_OUTSIDE = "gray" +IO_COLOR_INSIDE = "white" +IO_GRADIENT_ANGLE = "0" -def directed_graph(name, label, rankdir="TB"): +NODE_COLOR_START = "blue" +NODE_COLOR_END = "white" +NODE_GRADIENT_ANGLE = "90" + + +def directed_graph(name, label, rankdir, color_start, color_end, gradient_angle): """A shortcut method for instantiating the type of graphviz graph we want""" digraph = graphviz.graphs.Digraph(name=name) - digraph.attr(label=label, compound="true", rankdir=rankdir) + digraph.attr( + label=label, + compound="true", + rankdir=rankdir, + style="filled", + fillcolor=f"{color_start}:{color_end}", + gradientangle=gradient_angle + ) return digraph @@ -115,7 +130,14 @@ def __init__(self, parent: Node): self.data_io, self.signals_io = self._get_node_io() self._name = self.parent.name + self.data_io.__class__.__name__ self._label = self.data_io.__class__.__name__ - self._graph = directed_graph(self.name, self.label, rankdir="TB") + self._graph = directed_graph( + self.name, + self.label, + rankdir="TB", + color_start=self.color_start, + color_end=self.color_end, + gradient_angle=IO_GRADIENT_ANGLE + ) self.channels = [ Channel( @@ -144,6 +166,16 @@ def _get_node_io(self) -> tuple[DataIO, SignalIO]: def in_or_out(self) -> Literal["in", "out"]: pass + @property + @abstractmethod + def color_start(self) -> str: + pass + + @property + @abstractmethod + def color_end(self) -> str: + pass + @property def parent(self) -> Node: return self._parent @@ -172,6 +204,14 @@ def _get_node_io(self) -> tuple[DataIO, SignalIO]: def in_or_out(self) -> Literal["in"]: return "in" + @property + def color_start(self) -> str: + return IO_COLOR_OUTSIDE + + @property + def color_end(self) -> str: + return IO_COLOR_INSIDE + class Outputs(_IO): def _get_node_io(self) -> tuple[DataIO, SignalIO]: @@ -181,19 +221,34 @@ def _get_node_io(self) -> tuple[DataIO, SignalIO]: def in_or_out(self) -> Literal["out"]: return "out" + @property + def color_start(self) -> str: + return IO_COLOR_INSIDE + + @property + def color_end(self) -> str: + return IO_COLOR_OUTSIDE + class Node(WorkflowGraphvizMap): def __init__( self, node: WorkflowNode, parent: Optional[Node] = None, - granularity: int = 1, + granularity: int = 1 ): self.node = node self._parent = parent self._name = self.build_node_name() self._label = self.node.label + ": " + self.node.__class__.__name__ - self._graph = directed_graph(self.name, self.label, rankdir="LR") + self._graph = directed_graph( + self.name, + self.label, + rankdir="LR", + color_start=NODE_COLOR_START, + color_end=NODE_COLOR_END, + gradient_angle=NODE_GRADIENT_ANGLE + ) self.inputs = Inputs(self) self.outputs = Outputs(self) From ab72f08cc4216432ab44a2ccb08a89fc3e5cfb98 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 2 Aug 2023 13:54:05 -0700 Subject: [PATCH 497/756] Don't use alpha --- pyiron_contrib/workflow/draw.py | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index 69d50f965..2d6548f75 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -14,14 +14,8 @@ from pyiron_contrib.workflow.io import DataIO, SignalIO from pyiron_contrib.workflow.node import Node as WorkflowNode -IN_ALPHA = "66" -OUT_ALPHA = "aa" -DATA_COLOR_BASE = "#ebba34" -DATA_COLOR = {"in": DATA_COLOR_BASE + IN_ALPHA, "out": DATA_COLOR_BASE + OUT_ALPHA} -SIGNAL_COLOR_BASE = "#3452ed" -SIGNAL_COLOR = { - "in": SIGNAL_COLOR_BASE + IN_ALPHA, "out": SIGNAL_COLOR_BASE + OUT_ALPHA -} +DATA_COLOR = "#ebba34" +SIGNAL_COLOR = "#3452ed" DATA_SHAPE = "oval" SIGNAL_SHAPE = "cds" @@ -140,19 +134,10 @@ def __init__(self, parent: Node): ) self.channels = [ - Channel( - self, - channel, - SIGNAL_SHAPE, - SIGNAL_COLOR[self.in_or_out] - ) for channel in self.signals_io + Channel(self, channel, SIGNAL_SHAPE, SIGNAL_COLOR) + for channel in self.signals_io ] + [ - Channel( - self, - channel, - DATA_SHAPE, - DATA_COLOR[self.in_or_out] - ) for channel in self.data_io + Channel(self, channel, DATA_SHAPE, DATA_COLOR) for channel in self.data_io ] self.parent.graph.subgraph(self.graph) From 9a3327d463da1a5a2976e6cd245b5d8482e1ef27 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Aug 2023 21:06:33 +0000 Subject: [PATCH 498/756] Bump matplotlib from 3.7.1 to 3.7.2 Bumps [matplotlib](https://github.com/matplotlib/matplotlib) from 3.7.1 to 3.7.2. - [Release notes](https://github.com/matplotlib/matplotlib/releases) - [Commits](https://github.com/matplotlib/matplotlib/compare/v3.7.1...v3.7.2) --- updated-dependencies: - dependency-name: matplotlib dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1e82d67d2..f8c092a8c 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ keywords='pyiron', packages=find_packages(exclude=["*tests*"]), install_requires=[ - 'matplotlib==3.7.1', + 'matplotlib==3.7.2', 'numpy==1.24.3', 'pyiron_base==0.6.3', 'scipy==1.10.1', From a8b0ab5a23eea1506e206ca71d739591f02e53ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Aug 2023 21:06:38 +0000 Subject: [PATCH 499/756] Bump scipy from 1.10.1 to 1.11.1 Bumps [scipy](https://github.com/scipy/scipy) from 1.10.1 to 1.11.1. - [Release notes](https://github.com/scipy/scipy/releases) - [Commits](https://github.com/scipy/scipy/compare/v1.10.1...v1.11.1) --- updated-dependencies: - dependency-name: scipy dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1e82d67d2..fee3268b4 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ 'matplotlib==3.7.1', 'numpy==1.24.3', 'pyiron_base==0.6.3', - 'scipy==1.10.1', + 'scipy==1.11.1', 'seaborn==0.12.2', 'pyparsing==3.0.9', ], From e4f1b9af0a161bf5fae96c370e5d23d5177bca31 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 2 Aug 2023 21:06:52 +0000 Subject: [PATCH 500/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index f2e2f049f..1c41073bc 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -7,7 +7,7 @@ dependencies: - coverage - codacy-coverage - ipython -- matplotlib =3.7.1 +- matplotlib =3.7.2 - numpy =1.24.3 - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 From f8f029c07673f570243d0ec4b57965edcf71ad3d Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 2 Aug 2023 21:06:58 +0000 Subject: [PATCH 501/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index f2e2f049f..86d76f8ba 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -12,7 +12,7 @@ dependencies: - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 - pyparsing =3.0.9 -- scipy =1.10.1 +- scipy =1.11.1 - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 From 833ddeb10fd57d28955d6ca5507cf849cc30d032 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 2 Aug 2023 21:07:17 +0000 Subject: [PATCH 502/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index d4b486e7b..7f71975a7 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -7,7 +7,7 @@ dependencies: - coverage - codacy-coverage - ipython -- matplotlib =3.7.1 +- matplotlib =3.7.2 - numpy =1.24.3 - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 diff --git a/docs/environment.yml b/docs/environment.yml index 7b184eabc..a95006667 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -9,7 +9,7 @@ dependencies: - coverage - codacy-coverage - ipython -- matplotlib =3.7.1 +- matplotlib =3.7.2 - numpy =1.24.3 - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 From 29c3780f7c49b178df0e26935b00587ca99b3889 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 2 Aug 2023 21:07:21 +0000 Subject: [PATCH 503/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index d4b486e7b..ff8d7eb5d 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -12,7 +12,7 @@ dependencies: - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 - pyparsing =3.0.9 -- scipy =1.10.1 +- scipy =1.11.1 - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 diff --git a/docs/environment.yml b/docs/environment.yml index 7b184eabc..55ed290bb 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -14,7 +14,7 @@ dependencies: - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 - pyparsing =3.0.9 -- scipy =1.10.1 +- scipy =1.11.1 - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 From 58be69e658c2b2cde2dc8ee9218a4785bc308c9f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 2 Aug 2023 14:18:33 -0700 Subject: [PATCH 504/756] Refactor colours into classes and make color a node property --- pyiron_contrib/workflow/draw.py | 128 +++++++++++++++----------------- pyiron_contrib/workflow/node.py | 5 ++ 2 files changed, 65 insertions(+), 68 deletions(-) diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index 2d6548f75..fea623c89 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -5,7 +5,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Literal, Optional, TYPE_CHECKING +from typing import Optional, TYPE_CHECKING import graphviz @@ -14,19 +14,6 @@ from pyiron_contrib.workflow.io import DataIO, SignalIO from pyiron_contrib.workflow.node import Node as WorkflowNode -DATA_COLOR = "#ebba34" -SIGNAL_COLOR = "#3452ed" -DATA_SHAPE = "oval" -SIGNAL_SHAPE = "cds" - -IO_COLOR_OUTSIDE = "gray" -IO_COLOR_INSIDE = "white" -IO_GRADIENT_ANGLE = "0" - -NODE_COLOR_START = "blue" -NODE_COLOR_END = "white" -NODE_GRADIENT_ANGLE = "90" - def directed_graph(name, label, rankdir, color_start, color_end, gradient_angle): """A shortcut method for instantiating the type of graphviz graph we want""" @@ -42,6 +29,10 @@ def directed_graph(name, label, rankdir, color_start, color_end, gradient_angle) return digraph +def lighten_hex_color(color): + return "white" + + class WorkflowGraphvizMap(ABC): @property @abstractmethod @@ -63,30 +54,33 @@ def label(self) -> str: def graph(self) -> graphviz.graphs.Digraph: pass + @property + @abstractmethod + def color(self) -> str: + pass -class Channel(WorkflowGraphvizMap): - def __init__( - self, - parent: _IO, - channel: WorkflowChannel, - shape: str, - color: str = "white", - ): + +class _Channel(WorkflowGraphvizMap, ABC): + def __init__(self, parent: _IO, channel: WorkflowChannel): self.channel = channel self._parent = parent self._name = self.parent.name + self.channel.label self._label = self._build_label() self.channel: WorkflowChannel = channel - self._color = color self.graph.node( name=self.name, label=self.label, - shape=shape, + shape=self.shape, color=self.color, style="filled" ) + @property + @abstractmethod + def shape(self) -> str: + pass + def _build_label(self): label = self.channel.label try: @@ -112,9 +106,25 @@ def label(self) -> str: def graph(self) -> graphviz.graphs.Digraph: return self.parent.graph + +class DataChannel(_Channel): + @property + def color(self) -> str: + return "#ebba34" + + @property + def shape(self) -> str: + return "oval" + + +class SignalChannel(_Channel): @property - def color(self): - return self._color + def color(self) -> str: + return "#3452ed" + + @property + def shape(self) -> str: + return "cds" class _IO(WorkflowGraphvizMap, ABC): @@ -128,16 +138,15 @@ def __init__(self, parent: Node): self.name, self.label, rankdir="TB", - color_start=self.color_start, - color_end=self.color_end, - gradient_angle=IO_GRADIENT_ANGLE + color_start=self.color, + color_end=lighten_hex_color(self.color), + gradient_angle=self.gradient_angle ) self.channels = [ - Channel(self, channel, SIGNAL_SHAPE, SIGNAL_COLOR) - for channel in self.signals_io + SignalChannel(self, channel) for channel in self.signals_io ] + [ - Channel(self, channel, DATA_SHAPE, DATA_COLOR) for channel in self.data_io + DataChannel(self, channel) for channel in self.data_io ] self.parent.graph.subgraph(self.graph) @@ -148,17 +157,8 @@ def _get_node_io(self) -> tuple[DataIO, SignalIO]: @property @abstractmethod - def in_or_out(self) -> Literal["in", "out"]: - pass - - @property - @abstractmethod - def color_start(self) -> str: - pass - - @property - @abstractmethod - def color_end(self) -> str: + def gradient_angle(self) -> str: + """Background fill colour angle in degrees""" pass @property @@ -177,6 +177,10 @@ def label(self) -> str: def graph(self) -> graphviz.graphs.Digraph: return self._graph + @property + def color(self) -> str: + return "gray" + def __len__(self): return len(self.channels) @@ -186,16 +190,8 @@ def _get_node_io(self) -> tuple[DataIO, SignalIO]: return self.node.inputs, self.node.signals.input @property - def in_or_out(self) -> Literal["in"]: - return "in" - - @property - def color_start(self) -> str: - return IO_COLOR_OUTSIDE - - @property - def color_end(self) -> str: - return IO_COLOR_INSIDE + def gradient_angle(self) -> str: + return "0" class Outputs(_IO): @@ -203,16 +199,8 @@ def _get_node_io(self) -> tuple[DataIO, SignalIO]: return self.node.outputs, self.node.signals.output @property - def in_or_out(self) -> Literal["out"]: - return "out" - - @property - def color_start(self) -> str: - return IO_COLOR_INSIDE - - @property - def color_end(self) -> str: - return IO_COLOR_OUTSIDE + def gradient_angle(self) -> str: + return "180" class Node(WorkflowGraphvizMap): @@ -230,9 +218,9 @@ def __init__( self.name, self.label, rankdir="LR", - color_start=NODE_COLOR_START, - color_end=NODE_COLOR_END, - gradient_angle=NODE_GRADIENT_ANGLE + color_start=self.color, + color_end=lighten_hex_color(self.color), + gradient_angle="90" ) self.inputs = Inputs(self) @@ -287,8 +275,8 @@ def _connect_owned_nodes(self, granularity): def _connect_matching( self, - sources: list[Channel], - destinations: list[Channel] + sources: list[_Channel], + destinations: list[_Channel] ): """ Draw an edge between two graph channels whose workflow channels are the same @@ -325,3 +313,7 @@ def label(self) -> str: @property def graph(self) -> graphviz.graphs.Digraph: return self._graph + + @property + def color(self) -> str: + return self.node.color diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 52bdac95f..23c4fcb7c 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -317,5 +317,10 @@ def __call__(self, **kwargs) -> None: self._batch_update_input(**kwargs) return self.update() + @property + def color(self) -> str: + """For drawing the graph""" + return "blue" + def draw(self, granularity=1) -> graphviz.graphs.Digraph: return GraphvizNode(self, granularity=granularity).graph From 88a96f8656a27e3d541c52100fc7969c1ebb3fa4 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 2 Aug 2023 14:24:01 -0700 Subject: [PATCH 505/756] Apply seaborn palette and give different node types different colors --- pyiron_contrib/workflow/composite.py | 5 +++++ pyiron_contrib/workflow/draw.py | 6 +++--- pyiron_contrib/workflow/function.py | 15 +++++++++++++++ pyiron_contrib/workflow/node.py | 4 ++-- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 77fd539a2..f8e0bdc80 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -292,3 +292,8 @@ def register_nodes(self, domain: str, *nodes: list[type[Node]]): list, e.g. modules or even urls. """ setattr(self, domain, NodePackage(self._parent, *nodes)) + + @property + def color(self) -> str: + """For drawing the graph""" + return "#8c564b" diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index fea623c89..cceb26c79 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -110,7 +110,7 @@ def graph(self) -> graphviz.graphs.Digraph: class DataChannel(_Channel): @property def color(self) -> str: - return "#ebba34" + return "#ff7f0e" @property def shape(self) -> str: @@ -120,7 +120,7 @@ def shape(self) -> str: class SignalChannel(_Channel): @property def color(self) -> str: - return "#3452ed" + return "#1f77b4" @property def shape(self) -> str: @@ -179,7 +179,7 @@ def graph(self) -> graphviz.graphs.Digraph: @property def color(self) -> str: - return "gray" + return "#7f7f7f" def __len__(self): return len(self.channels) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 01bb04fe9..9552a9c33 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -610,6 +610,11 @@ def to_dict(self): "signals": self.signals.to_dict(), } + @property + def color(self) -> str: + """For drawing the graph""" + return "#2ca02c" + class Slow(Function): """ @@ -641,6 +646,11 @@ def __init__( **kwargs, ) + @property + def color(self) -> str: + """For drawing the graph""" + return "#d62728" + class SingleValue(Function, HasChannel): """ @@ -692,6 +702,11 @@ def channel(self) -> OutputData: """The channel for the single output""" return list(self.outputs.channel_dict.values())[0] + @property + def color(self) -> str: + """For drawing the graph""" + return "#17becf" + def __getitem__(self, item): return self.single_value.__getitem__(item) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 23c4fcb7c..a81a2330c 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -319,8 +319,8 @@ def __call__(self, **kwargs) -> None: @property def color(self) -> str: - """For drawing the graph""" - return "blue" + """A hex code color for use in drawing.""" + return "#ffffff" def draw(self, granularity=1) -> graphviz.graphs.Digraph: return GraphvizNode(self, granularity=granularity).graph From 8565b060436a802971c23f6095399fa3b3c49b83 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 2 Aug 2023 14:36:33 -0700 Subject: [PATCH 506/756] Implement color blending --- pyiron_contrib/workflow/draw.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index cceb26c79..4b0979ce1 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -8,6 +8,7 @@ from typing import Optional, TYPE_CHECKING import graphviz +from matplotlib.colors import to_hex, to_rgb if TYPE_CHECKING: from pyiron_contrib.workflow.channels import Channel as WorkflowChannel @@ -29,8 +30,19 @@ def directed_graph(name, label, rankdir, color_start, color_end, gradient_angle) return digraph -def lighten_hex_color(color): - return "white" +def blend_colours(color_a, color_b, fraction_a=0.5): + """Blends two hex code colours together""" + return to_hex( + tuple( + fraction_a * a + (1 - fraction_a) * b + for (a, b) in zip(to_rgb(color_a), to_rgb(color_b)) + ) + ) + + +def lighten_hex_color(color, lightness=0.7): + """Blends the given hex code color with pure white.""" + return blend_colours("#ffffff", color, fraction_a=lightness) class WorkflowGraphvizMap(ABC): From 1569853a3161840d7d7929fbccdf7a95f3cb321a Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 2 Aug 2023 14:43:09 -0700 Subject: [PATCH 507/756] Refactor: rename granularity to depth --- pyiron_contrib/workflow/draw.py | 10 +++++----- pyiron_contrib/workflow/node.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index 4b0979ce1..14326619c 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -220,7 +220,7 @@ def __init__( self, node: WorkflowNode, parent: Optional[Node] = None, - granularity: int = 1 + depth: int = 1 ): self.node = node self._parent = parent @@ -243,9 +243,9 @@ def __init__( style="invis" ) - if granularity > 0: + if depth > 0: try: - self._connect_owned_nodes(granularity) + self._connect_owned_nodes(depth) except AttributeError: # Only composite nodes have their own nodes attribute pass @@ -256,9 +256,9 @@ def __init__( def _gradient_channel_color(self, start_channel, end_channel): return f"{start_channel.color};0.5:{end_channel.color};0.5" - def _connect_owned_nodes(self, granularity): + def _connect_owned_nodes(self, depth): nodes = [ - Node(node, self, granularity - 1) + Node(node, self, depth - 1) for node in self.node.nodes.values() ] internal_inputs = [ diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index a81a2330c..266a8a3b1 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -322,5 +322,5 @@ def color(self) -> str: """A hex code color for use in drawing.""" return "#ffffff" - def draw(self, granularity=1) -> graphviz.graphs.Digraph: - return GraphvizNode(self, granularity=granularity).graph + def draw(self, depth=1) -> graphviz.graphs.Digraph: + return GraphvizNode(self, depth=depth).graph From 6c097719240ce04ff3706b7fb80c14e63f2dfe75 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 2 Aug 2023 14:58:32 -0700 Subject: [PATCH 508/756] Add docstrings --- pyiron_contrib/workflow/draw.py | 33 ++++++++++++++++++++++++++++++++- pyiron_contrib/workflow/node.py | 19 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index 14326619c..34ed2c2e3 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -46,6 +46,10 @@ def lighten_hex_color(color, lightness=0.7): class WorkflowGraphvizMap(ABC): + """ + A parent class defining the interface for the graphviz representation of all our + workflow objects. + """ @property @abstractmethod def parent(self) -> WorkflowGraphvizMap | None: @@ -73,6 +77,10 @@ def color(self) -> str: class _Channel(WorkflowGraphvizMap, ABC): + """ + An abstract representation for channel objects, which are "nodes" in graphviz + parlance. + """ def __init__(self, parent: _IO, channel: WorkflowChannel): self.channel = channel self._parent = parent @@ -140,9 +148,13 @@ def shape(self) -> str: class _IO(WorkflowGraphvizMap, ABC): + """ + An abstract class for IO panels, which are represented as a "subgraph" in graphviz + parlance. + """ def __init__(self, parent: Node): self._parent = parent - self.node = self.parent.node + self.node: WorkflowNode = self.parent.node self.data_io, self.signals_io = self._get_node_io() self._name = self.parent.name + self.data_io.__class__.__name__ self._label = self.data_io.__class__.__name__ @@ -216,6 +228,25 @@ def gradient_angle(self) -> str: class Node(WorkflowGraphvizMap): + """ + A wrapper class to connect graphviz to our workflow nodes. The nodes are + represented by a "graph" or "subgraph" in graphviz parlance (depending on whether + the node being visualized is the top-most node or not). + + Visualized nodes show their label and type, and IO panels with label and type. + Colors and shapes are exploited to differentiate various node classes, input/output, + and data/signal channels. + + If the node is composite in nature and the `depth` argument is at least `1`, owned + children are also visualized (recursively with `depth = depth - 1`) inside the scope + of this node. + + Args: + node (pyiron_contrib.workflow.node.Node): The node to visualize. + parent (Optional[pyiron_contrib.workflow.draw.Node]): The visualization that + owns this visualization (if any). + depth (int): How deeply to decompose any child nodes beyond showing their IO. + """ def __init__( self, node: WorkflowNode, diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 266a8a3b1..469ffabe2 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -323,4 +323,23 @@ def color(self) -> str: return "#ffffff" def draw(self, depth=1) -> graphviz.graphs.Digraph: + """ + Draw the node structure. + + Args: + depth (int): How deeply to decompose the representation of composite nodes + to reveal their inner structure. (Default is 1, which will show owned + nodes if _this_ is a composite node, but all children will be drawn + at the level of showing their IO only.) A depth value greater than the + max depth of the node will have no adverse side effects. + + Returns: + (graphviz.graphs.Digraph): The resulting graph object. + + Note: + The graphviz docs will elucidate all the possibilities of what to do with + the returned object, but the thing you are most likely to need is the + `render` method, which allows you to save the resulting graph as an image. + E.g. `self.draw().render(filename="my_node", format="png")`. + """ return GraphvizNode(self, depth=depth).graph From f100b272935d83b5a2cfb27ad8046cb45a9596b6 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 2 Aug 2023 14:59:38 -0700 Subject: [PATCH 509/756] Refactor: rename method --- pyiron_contrib/workflow/draw.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index 34ed2c2e3..368868094 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -284,7 +284,7 @@ def __init__( if self.parent is not None: self.parent.graph.subgraph(self.graph) - def _gradient_channel_color(self, start_channel, end_channel): + def _channel_bicolor(self, start_channel, end_channel): return f"{start_channel.color};0.5:{end_channel.color};0.5" def _connect_owned_nodes(self, depth): @@ -306,9 +306,7 @@ def _connect_owned_nodes(self, depth): self.graph.edge( output_channel.name, input_channel.name, - color=self._gradient_channel_color( - output_channel, input_channel - ) + color=self._channel_bicolor(output_channel, input_channel) ) # Loop to check for macro input --> internal node input connections @@ -330,7 +328,7 @@ def _connect_matching( self.graph.edge( source.name, destination.name, - color=self._gradient_channel_color(source, destination) + color=self._channel_bicolor(source, destination) ) def build_node_name(self, suffix=""): From 8c4ffde38955627d535cadc50f9fc2f54c3a305c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 2 Aug 2023 15:03:11 -0700 Subject: [PATCH 510/756] Extend node and workflow docstrings --- pyiron_contrib/workflow/node.py | 2 ++ pyiron_contrib/workflow/workflow.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 469ffabe2..7cf8f79cd 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -113,6 +113,8 @@ class Node(HasToDict, ABC): Methods: disconnect: Remove all connections, including signals. + draw: Use graphviz to visualize the node, its IO and, if composite in nature, + its internal structure. on_run: **Abstract.** Do the thing. run: A wrapper to handle all the infrastructure around executing `on_run`. """ diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 2856090ff..314270a77 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -117,6 +117,12 @@ class Workflow(Composite): ... y=wf.calc.outputs.temperature ... ) + Workflows can be visualized in the notebook using graphviz: + >>> wf.draw() + + The resulting object can be saved as an image, e.g. + >>> wf.draw().render(filename="demo", format="png") + TODO: Workflows can be serialized. TODO: Once you're satisfied with how a workflow is structured, you can export it From f79197b40e364c5b129e3253a845f5f0d7172fd2 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 2 Aug 2023 15:07:37 -0700 Subject: [PATCH 511/756] Update demo notebook --- notebooks/workflow_example.ipynb | 1026 +++++++++++++++++++++++++++++- 1 file changed, 1015 insertions(+), 11 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index e273a9812..f7b21d48f 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -11,7 +11,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "00c1cb12911741a18f9c06ba09e74ae6", + "model_id": "71c73d24f4ea44e5aef03b0611a3c9a4", "version_major": 2, "version_minor": 0 }, @@ -716,7 +716,7 @@ "outputs": [ { "data": { - "image/png": "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xrqA16GzV9UUZKWGrSbCCAAAMSQ+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", + "image/png": "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", "text/plain": [ "
" ] @@ -805,7 +805,7 @@ "outputs": [ { "data": { - "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=", + "image/png": "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", "text/plain": [ "
" ] @@ -1038,9 +1038,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/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", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:220: 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:224: 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:220: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent with_prebuilt.\n", " warn(\n" ] }, @@ -1055,9 +1055,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/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", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:220: 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:224: 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:220: UserWarning: Reassigning the node scatter to the label plot when adding it to the parent with_prebuilt.\n", " warn(\n" ] }, @@ -1088,13 +1088,1017 @@ ")" ] }, + { + "cell_type": "markdown", + "id": "fa52dae9-8b3e-4de5-a916-eb548f7b9845", + "metadata": {}, + "source": [ + "Nodes and workflows can be visualized using graphviz:" + ] + }, { "cell_type": "code", - "execution_count": null, - "id": "cab89cc8-2409-4bdb-8b50-ccdf48e9ec5d", + "execution_count": 34, + "id": "be3dd2a3-0cb2-4fc4-a07f-7ec719bbc6c9", "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label element to the io key structure_element\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label cubic to the io key structure_cubic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label repeat to the io key structure_repeat\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc_n_ionic_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label n_print to the io key calc_n_print\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label temperature to the io key calc_temperature\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label pressure to the io key calc_pressure\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label cells to the io key calc_cells\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label displacements to the io key calc_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label energy_pot to the io key calc_energy_pot\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label energy_tot to the io key calc_energy_tot\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label force_max to the io key calc_force_max\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label forces to the io key calc_forces\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label indices to the io key calc_indices\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label positions to the io key calc_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label pressures to the io key calc_pressures\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label total_displacements to the io key calc_total_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc_unwrapped_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label volume to the io key calc_volume\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label fig to the io key plot_fig\n", + " warn(\n" + ] + }, + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuilt\n", + "\n", + "with_prebuilt: Workflow\n", + "\n", + "clusterwith_prebuiltInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterwith_prebuiltOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "clusterwith_prebuiltstructure\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "structure: BulkStructure\n", + "\n", + "\n", + "clusterwith_prebuiltstructureInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterwith_prebuiltstructureOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "clusterwith_prebuiltengine\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "engine: Lammps\n", + "\n", + "\n", + "clusterwith_prebuiltengineInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterwith_prebuiltengineOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "clusterwith_prebuiltcalc\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "calc: CalcMd\n", + "\n", + "\n", + "clusterwith_prebuiltcalcInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "clusterwith_prebuiltplot\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "plot: Scatter\n", + "\n", + "\n", + "clusterwith_prebuiltplotInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterwith_prebuiltplotOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputselement\n", + "\n", + "element: str\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltstructureInputselement\n", + "\n", + "element: str\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputselement->clusterwith_prebuiltstructureInputselement\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputscubic\n", + "\n", + "cubic: bool\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltstructureInputscubic\n", + "\n", + "cubic: bool\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputscubic->clusterwith_prebuiltstructureInputscubic\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsrepeat\n", + "\n", + "repeat: int\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltstructureInputsrepeat\n", + "\n", + "repeat: int\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsrepeat->clusterwith_prebuiltstructureInputsrepeat\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsn_ionic_steps\n", + "\n", + "n_ionic_steps: int\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcInputsn_ionic_steps\n", + "\n", + "n_ionic_steps: int\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsn_ionic_steps->clusterwith_prebuiltcalcInputsn_ionic_steps\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsn_print\n", + "\n", + "n_print: int\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcInputsn_print\n", + "\n", + "n_print: int\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsn_print->clusterwith_prebuiltcalcInputsn_print\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputstemperature\n", + "\n", + "temperature\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcInputstemperature\n", + "\n", + "temperature\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputstemperature->clusterwith_prebuiltcalcInputstemperature\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputspressure\n", + "\n", + "pressure\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcInputspressure\n", + "\n", + "pressure\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputspressure->clusterwith_prebuiltcalcInputspressure\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputscells\n", + "\n", + "cells\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsdisplacements\n", + "\n", + "displacements\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsenergy_pot\n", + "\n", + "energy_pot\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsenergy_tot\n", + "\n", + "energy_tot\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsforce_max\n", + "\n", + "force_max\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsforces\n", + "\n", + "forces\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsindices\n", + "\n", + "indices\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputspositions\n", + "\n", + "positions\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputspressures\n", + "\n", + "pressures\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputstotal_displacements\n", + "\n", + "total_displacements\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsunwrapped_positions\n", + "\n", + "unwrapped_positions\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsvolume\n", + "\n", + "volume\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsfig\n", + "\n", + "fig\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltstructureInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltstructureOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltstructureOutputsstructure\n", + "\n", + "structure: Atoms\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltengineInputsstructure\n", + "\n", + "structure: Optional\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltstructureOutputsstructure->clusterwith_prebuiltengineInputsstructure\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltengineInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltengineOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltengineOutputsjob\n", + "\n", + "job: Lammps\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcInputsjob\n", + "\n", + "job: AtomisticGenericJob\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltengineOutputsjob->clusterwith_prebuiltcalcInputsjob\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputscells\n", + "\n", + "cells\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputscells->clusterwith_prebuiltOutputscells\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsdisplacements\n", + "\n", + "displacements\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsdisplacements->clusterwith_prebuiltOutputsdisplacements\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsenergy_pot\n", + "\n", + "energy_pot\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsenergy_pot->clusterwith_prebuiltOutputsenergy_pot\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsenergy_tot\n", + "\n", + "energy_tot\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsenergy_tot->clusterwith_prebuiltOutputsenergy_tot\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsforce_max\n", + "\n", + "force_max\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsforce_max->clusterwith_prebuiltOutputsforce_max\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsforces\n", + "\n", + "forces\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsforces->clusterwith_prebuiltOutputsforces\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsindices\n", + "\n", + "indices\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsindices->clusterwith_prebuiltOutputsindices\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputspositions\n", + "\n", + "positions\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputspositions->clusterwith_prebuiltOutputspositions\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputspressures\n", + "\n", + "pressures\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputspressures->clusterwith_prebuiltOutputspressures\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputssteps\n", + "\n", + "steps\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltplotInputsx\n", + "\n", + "x: Union\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputssteps->clusterwith_prebuiltplotInputsx\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputstemperature\n", + "\n", + "temperature\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltplotInputsy\n", + "\n", + "y: Union\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputstemperature->clusterwith_prebuiltplotInputsy\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputstotal_displacements\n", + "\n", + "total_displacements\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputstotal_displacements->clusterwith_prebuiltOutputstotal_displacements\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsunwrapped_positions\n", + "\n", + "unwrapped_positions\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsunwrapped_positions->clusterwith_prebuiltOutputsunwrapped_positions\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsvolume\n", + "\n", + "volume\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsvolume->clusterwith_prebuiltOutputsvolume\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltplotInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltplotOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltplotOutputsfig\n", + "\n", + "fig\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltplotOutputsfig->clusterwith_prebuiltOutputsfig\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "wf.draw()" + ] + }, + { + "cell_type": "markdown", + "id": "43c09aa8-8229-4636-aaeb-9214b723c2fc", + "metadata": {}, + "source": [ + "In case you want to see more or less of the inner workings of the nodes, you can modify the `depth` parameter, which controls how deeply child nodes are decomposed. E.g. we can force our workflow to only show us it's basic IO by setting `depth=0`:" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "2114d0c3-cdad-43c7-9ffa-50c36d56d18f", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label element to the io key structure_element\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label cubic to the io key structure_cubic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label repeat to the io key structure_repeat\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc_n_ionic_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label n_print to the io key calc_n_print\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label temperature to the io key calc_temperature\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label pressure to the io key calc_pressure\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label cells to the io key calc_cells\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label displacements to the io key calc_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label energy_pot to the io key calc_energy_pot\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label energy_tot to the io key calc_energy_tot\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label force_max to the io key calc_force_max\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label forces to the io key calc_forces\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label indices to the io key calc_indices\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label positions to the io key calc_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label pressures to the io key calc_pressures\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label total_displacements to the io key calc_total_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc_unwrapped_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label volume to the io key calc_volume\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label fig to the io key plot_fig\n", + " warn(\n" + ] + }, + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuilt\n", + "\n", + "with_prebuilt: Workflow\n", + "\n", + "clusterwith_prebuiltInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterwith_prebuiltOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputselement\n", + "\n", + "element: str\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputscubic\n", + "\n", + "cubic: bool\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsrepeat\n", + "\n", + "repeat: int\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsn_ionic_steps\n", + "\n", + "n_ionic_steps: int\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsn_print\n", + "\n", + "n_print: int\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputstemperature\n", + "\n", + "temperature\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputspressure\n", + "\n", + "pressure\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputscells\n", + "\n", + "cells\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsdisplacements\n", + "\n", + "displacements\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsenergy_pot\n", + "\n", + "energy_pot\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsenergy_tot\n", + "\n", + "energy_tot\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsforce_max\n", + "\n", + "force_max\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsforces\n", + "\n", + "forces\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsindices\n", + "\n", + "indices\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputspositions\n", + "\n", + "positions\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputspressures\n", + "\n", + "pressures\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputstotal_displacements\n", + "\n", + "total_displacements\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsunwrapped_positions\n", + "\n", + "unwrapped_positions\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsvolume\n", + "\n", + "volume\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltOutputsfig\n", + "\n", + "fig\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "wf.draw(depth=0)" + ] } ], "metadata": { From 099a728e92a0fc3a80459806028fdd8fd9c6c4e8 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 2 Aug 2023 15:19:55 -0700 Subject: [PATCH 512/756] Allow users to toggle from left-right to top-bottom alignment --- pyiron_contrib/workflow/draw.py | 20 ++++++++++++++++---- pyiron_contrib/workflow/node.py | 10 +++++++--- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index 368868094..c05a492bb 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -5,7 +5,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Optional, TYPE_CHECKING +from typing import Literal, Optional, TYPE_CHECKING import graphviz from matplotlib.colors import to_hex, to_rgb @@ -30,6 +30,15 @@ def directed_graph(name, label, rankdir, color_start, color_end, gradient_angle) return digraph +def reverse_rankdir(rankdir: Literal["LR", "TB"]): + if rankdir == "LR": + return "TB" + elif rankdir == "TB": + return "LR" + else: + raise ValueError(f"Expected rankdir of 'LR' or 'TB' but got {rankdir}") + + def blend_colours(color_a, color_b, fraction_a=0.5): """Blends two hex code colours together""" return to_hex( @@ -161,7 +170,7 @@ def __init__(self, parent: Node): self._graph = directed_graph( self.name, self.label, - rankdir="TB", + rankdir=reverse_rankdir(self.parent.rankdir), color_start=self.color, color_end=lighten_hex_color(self.color), gradient_angle=self.gradient_angle @@ -246,21 +255,24 @@ class Node(WorkflowGraphvizMap): parent (Optional[pyiron_contrib.workflow.draw.Node]): The visualization that owns this visualization (if any). depth (int): How deeply to decompose any child nodes beyond showing their IO. + rankdir ("LR" | "TB"): Use left-right or top-bottom graphviz `rankdir`. """ def __init__( self, node: WorkflowNode, parent: Optional[Node] = None, - depth: int = 1 + depth: int = 1, + rankdir: Literal["LR", "TB"] = "LR" ): self.node = node self._parent = parent self._name = self.build_node_name() self._label = self.node.label + ": " + self.node.__class__.__name__ + self.rankdir: Literal["LR", "TB"] = rankdir self._graph = directed_graph( self.name, self.label, - rankdir="LR", + rankdir=self.rankdir, color_start=self.color, color_end=lighten_hex_color(self.color), gradient_angle="90" diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 7cf8f79cd..c5fcdca20 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 Any, Optional, TYPE_CHECKING +from typing import Any, Literal, Optional, TYPE_CHECKING from pyiron_contrib.executors import CloudpickleProcessPoolExecutor from pyiron_contrib.workflow.draw import Node as GraphvizNode @@ -324,7 +324,9 @@ def color(self) -> str: """A hex code color for use in drawing.""" return "#ffffff" - def draw(self, depth=1) -> graphviz.graphs.Digraph: + def draw( + self, depth: int = 1, rankdir: Literal["LR", "TB"] = "LR" + ) -> graphviz.graphs.Digraph: """ Draw the node structure. @@ -334,6 +336,8 @@ def draw(self, depth=1) -> graphviz.graphs.Digraph: nodes if _this_ is a composite node, but all children will be drawn at the level of showing their IO only.) A depth value greater than the max depth of the node will have no adverse side effects. + rankdir ("LR" | "TB"): Use left-right or top-bottom graphviz `rankdir` to + orient the flow of the graph. Returns: (graphviz.graphs.Digraph): The resulting graph object. @@ -344,4 +348,4 @@ def draw(self, depth=1) -> graphviz.graphs.Digraph: `render` method, which allows you to save the resulting graph as an image. E.g. `self.draw().render(filename="my_node", format="png")`. """ - return GraphvizNode(self, depth=depth).graph + return GraphvizNode(self, depth=depth, rankdir=rankdir).graph From d487757938887b422a26d52127f403c3173b5fe3 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 2 Aug 2023 22:22:08 +0000 Subject: [PATCH 513/756] Format black --- pyiron_contrib/workflow/draw.py | 45 ++++++++++++++------------------- pyiron_contrib/workflow/node.py | 2 +- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index c05a492bb..131cf9acd 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -25,7 +25,7 @@ def directed_graph(name, label, rankdir, color_start, color_end, gradient_angle) rankdir=rankdir, style="filled", fillcolor=f"{color_start}:{color_end}", - gradientangle=gradient_angle + gradientangle=gradient_angle, ) return digraph @@ -59,6 +59,7 @@ class WorkflowGraphvizMap(ABC): A parent class defining the interface for the graphviz representation of all our workflow objects. """ + @property @abstractmethod def parent(self) -> WorkflowGraphvizMap | None: @@ -90,6 +91,7 @@ class _Channel(WorkflowGraphvizMap, ABC): An abstract representation for channel objects, which are "nodes" in graphviz parlance. """ + def __init__(self, parent: _IO, channel: WorkflowChannel): self.channel = channel self._parent = parent @@ -102,7 +104,7 @@ def __init__(self, parent: _IO, channel: WorkflowChannel): label=self.label, shape=self.shape, color=self.color, - style="filled" + style="filled", ) @property @@ -161,6 +163,7 @@ class _IO(WorkflowGraphvizMap, ABC): An abstract class for IO panels, which are represented as a "subgraph" in graphviz parlance. """ + def __init__(self, parent: Node): self._parent = parent self.node: WorkflowNode = self.parent.node @@ -173,14 +176,12 @@ def __init__(self, parent: Node): rankdir=reverse_rankdir(self.parent.rankdir), color_start=self.color, color_end=lighten_hex_color(self.color), - gradient_angle=self.gradient_angle + gradient_angle=self.gradient_angle, ) self.channels = [ SignalChannel(self, channel) for channel in self.signals_io - ] + [ - DataChannel(self, channel) for channel in self.data_io - ] + ] + [DataChannel(self, channel) for channel in self.data_io] self.parent.graph.subgraph(self.graph) @@ -257,12 +258,13 @@ class Node(WorkflowGraphvizMap): depth (int): How deeply to decompose any child nodes beyond showing their IO. rankdir ("LR" | "TB"): Use left-right or top-bottom graphviz `rankdir`. """ + def __init__( - self, - node: WorkflowNode, - parent: Optional[Node] = None, - depth: int = 1, - rankdir: Literal["LR", "TB"] = "LR" + self, + node: WorkflowNode, + parent: Optional[Node] = None, + depth: int = 1, + rankdir: Literal["LR", "TB"] = "LR", ): self.node = node self._parent = parent @@ -275,15 +277,13 @@ def __init__( rankdir=self.rankdir, color_start=self.color, color_end=lighten_hex_color(self.color), - gradient_angle="90" + gradient_angle="90", ) self.inputs = Inputs(self) self.outputs = Outputs(self) self.graph.edge( - self.inputs.channels[0].name, - self.outputs.channels[0].name, - style="invis" + self.inputs.channels[0].name, self.outputs.channels[0].name, style="invis" ) if depth > 0: @@ -300,10 +300,7 @@ def _channel_bicolor(self, start_channel, end_channel): return f"{start_channel.color};0.5:{end_channel.color};0.5" def _connect_owned_nodes(self, depth): - nodes = [ - Node(node, self, depth - 1) - for node in self.node.nodes.values() - ] + nodes = [Node(node, self, depth - 1) for node in self.node.nodes.values()] internal_inputs = [ channel for node in nodes for channel in node.inputs.channels ] @@ -318,7 +315,7 @@ def _connect_owned_nodes(self, depth): self.graph.edge( output_channel.name, input_channel.name, - color=self._channel_bicolor(output_channel, input_channel) + color=self._channel_bicolor(output_channel, input_channel), ) # Loop to check for macro input --> internal node input connections @@ -326,11 +323,7 @@ def _connect_owned_nodes(self, depth): # Loop to check for macro input --> internal node input connections self._connect_matching(internal_outputs, self.outputs.channels) - def _connect_matching( - self, - sources: list[_Channel], - destinations: list[_Channel] - ): + def _connect_matching(self, sources: list[_Channel], destinations: list[_Channel]): """ Draw an edge between two graph channels whose workflow channels are the same """ @@ -340,7 +333,7 @@ def _connect_matching( self.graph.edge( source.name, destination.name, - color=self._channel_bicolor(source, destination) + color=self._channel_bicolor(source, destination), ) def build_node_name(self, suffix=""): diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index c5fcdca20..cebeed305 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -325,7 +325,7 @@ def color(self) -> str: return "#ffffff" def draw( - self, depth: int = 1, rankdir: Literal["LR", "TB"] = "LR" + self, depth: int = 1, rankdir: Literal["LR", "TB"] = "LR" ) -> graphviz.graphs.Digraph: """ Draw the node structure. From 840f096489e94c2d3d481551b4745ae91543ef12 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 2 Aug 2023 15:28:04 -0700 Subject: [PATCH 514/756] Fix codacy nag --- pyiron_contrib/workflow/draw.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index c05a492bb..92e0d4a91 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -192,7 +192,6 @@ def _get_node_io(self) -> tuple[DataIO, SignalIO]: @abstractmethod def gradient_angle(self) -> str: """Background fill colour angle in degrees""" - pass @property def parent(self) -> Node: From bb3a1da4e16a550e245025e5f220eaeba0bb0fb9 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 3 Aug 2023 09:51:40 -0700 Subject: [PATCH 515/756] Add informative (but not too lengthy) string casts --- pyiron_contrib/workflow/has_to_dict.py | 3 +++ pyiron_contrib/workflow/io.py | 6 ++++++ pyiron_contrib/workflow/node.py | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/pyiron_contrib/workflow/has_to_dict.py b/pyiron_contrib/workflow/has_to_dict.py index d08aa8699..4697654c2 100644 --- a/pyiron_contrib/workflow/has_to_dict.py +++ b/pyiron_contrib/workflow/has_to_dict.py @@ -17,3 +17,6 @@ def info(self): def repr_json(self): return JSON(self.to_dict()) + + def __str__(self): + return str(self.to_dict()) diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py index 243ee42a0..37a26c926 100644 --- a/pyiron_contrib/workflow/io.py +++ b/pyiron_contrib/workflow/io.py @@ -125,6 +125,9 @@ def __len__(self): def __dir__(self): return set(super().__dir__() + self.labels) + def __str__(self): + return f"{self.__class__.__name__} {self.labels}" + def to_dict(self): return { "label": self.__class__.__name__, @@ -224,3 +227,6 @@ def to_dict(self): "input": self.input.to_dict(), "output": self.output.to_dict(), } + + def __str__(self): + return f"{str(self.input)}\n{str(self.output)}" diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index e5f2ec3d7..9e58786e1 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -313,3 +313,9 @@ def _batch_update_input(self, **kwargs): def __call__(self, **kwargs) -> None: self._batch_update_input(**kwargs) return self.update() + + def __str__(self): + return f"{self.label} ({self.__class__.__name__}):\n" \ + f"{str(self.inputs)}\n" \ + f"{str(self.outputs)}\n" \ + f"{str(self.signals)}" From fa411a9e5f488fbbe485b4c0c676495337c724ef Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 3 Aug 2023 10:21:48 -0700 Subject: [PATCH 516/756] Make sure getattr raises an _attribute_ error on failure This resolves the _repr_json_ issue for notebook representations --- pyiron_contrib/workflow/composite.py | 9 ++++++++- pyiron_contrib/workflow/io.py | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 77fd539a2..92bc31ce5 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -238,7 +238,14 @@ def __setattr__(self, label: str, node: Node): super().__setattr__(label, node) def __getattr__(self, key): - return self.nodes[key] + try: + return self.nodes[key] + except KeyError: + # Raise an attribute error from getattr to make sure hasattr works well! + raise AttributeError( + f"Could not find attribute {key} on {self.label} " + f"({self.__class__.__name__}) or in its nodes ({self.nodes.keys()})" + ) def __getitem__(self, item): return self.__getattr__(item) diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py index 37a26c926..12aa0aa51 100644 --- a/pyiron_contrib/workflow/io.py +++ b/pyiron_contrib/workflow/io.py @@ -70,7 +70,14 @@ def _assign_a_non_channel_value(self, channel: Channel, value) -> None: pass def __getattr__(self, item) -> Channel: - return self.channel_dict[item] + try: + return self.channel_dict[item] + except KeyError: + # Raise an attribute error from getattr to make sure hasattr works well! + raise AttributeError( + f"Could not find attribute {item} on {self.__class__.__name__} object " + f"nor in its channels ({self.labels})" + ) def __setattr__(self, key, value): if key in self.channel_dict.keys(): From eb7e3005dbd9f3dc06f3e3096834a26d478a974d Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 3 Aug 2023 10:22:45 -0700 Subject: [PATCH 517/756] Remove (now unnecessary) workaround method --- pyiron_contrib/workflow/has_to_dict.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pyiron_contrib/workflow/has_to_dict.py b/pyiron_contrib/workflow/has_to_dict.py index 4697654c2..a78bc4271 100644 --- a/pyiron_contrib/workflow/has_to_dict.py +++ b/pyiron_contrib/workflow/has_to_dict.py @@ -1,8 +1,6 @@ from abc import ABC, abstractmethod from json import dumps -from IPython.display import JSON - class HasToDict(ABC): @abstractmethod @@ -15,8 +13,5 @@ def _repr_json_(self): def info(self): print(dumps(self.to_dict(), indent=2)) - def repr_json(self): - return JSON(self.to_dict()) - def __str__(self): return str(self.to_dict()) From 850841aebbcb7d80cca296e59d8fe4534c4205f4 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 3 Aug 2023 10:24:06 -0700 Subject: [PATCH 518/756] Remove ipython dependency It was just there for the explicit repr_json workaround --- .ci_support/environment.yml | 1 - setup.py | 1 - 2 files changed, 2 deletions(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 153c09279..26e986b19 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -6,7 +6,6 @@ dependencies: - coveralls - coverage - codacy-coverage -- ipython - matplotlib =3.7.2 - numpy =1.24.3 - pyiron_base =0.6.3 diff --git a/setup.py b/setup.py index 23d0155bd..b49099570 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,6 @@ 'workflow': [ 'cloudpickle', 'python>=3.10', - 'ipython', 'typeguard==4.1.0' ], 'tinybase': [ From 9fcf595d5855ad40aac081c81604eb760f6c720d Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Thu, 3 Aug 2023 17:27:22 +0000 Subject: [PATCH 519/756] [dependabot skip] Update env file --- .binder/environment.yml | 1 - docs/environment.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 80ab9edcd..9589b54d0 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -6,7 +6,6 @@ dependencies: - coveralls - coverage - codacy-coverage -- ipython - matplotlib =3.7.2 - numpy =1.24.3 - pyiron_base =0.6.3 diff --git a/docs/environment.yml b/docs/environment.yml index 078484b93..2e5f91857 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -8,7 +8,6 @@ dependencies: - coveralls - coverage - codacy-coverage -- ipython - matplotlib =3.7.2 - numpy =1.24.3 - pyiron_base =0.6.3 From 73b30af9045ed10a9491029981ed0c8971bb8b49 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Thu, 3 Aug 2023 17:31:14 +0000 Subject: [PATCH 520/756] Format black --- pyiron_contrib/workflow/node.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 9e58786e1..0e8c9580b 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -315,7 +315,9 @@ def __call__(self, **kwargs) -> None: return self.update() def __str__(self): - return f"{self.label} ({self.__class__.__name__}):\n" \ - f"{str(self.inputs)}\n" \ - f"{str(self.outputs)}\n" \ - f"{str(self.signals)}" + return ( + f"{self.label} ({self.__class__.__name__}):\n" + f"{str(self.inputs)}\n" + f"{str(self.outputs)}\n" + f"{str(self.signals)}" + ) From 8ed334050323aedffcd56d7d1bbedffb0ce7082a Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 3 Aug 2023 14:06:57 -0700 Subject: [PATCH 521/756] Allow composite nodes to get parents assigned --- pyiron_contrib/workflow/composite.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index c35fef6d8..5df45fa88 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -170,6 +170,11 @@ def add_node(self, node: Node, label: Optional[str] = None) -> None: self.nodes[label] = node node.label = label + print( + f"{self.label} ({type(self)}) " + f"adding {node.label} ({type(node)}) " + f"that has parent {node.parent.label if node.parent is not None else None} ({type(node.parent)})" + ) node.parent = self return node @@ -232,7 +237,7 @@ def remove(self, node: Node | str): del self.nodes[node] def __setattr__(self, label: str, node: Node): - if isinstance(node, Node): + if isinstance(node, Node) and label != "parent": self.add_node(node, label=label) else: super().__setattr__(label, node) From e0b418f7f24f891e239bd453caeca4a7eee4cb58 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 4 Aug 2023 09:48:28 -0700 Subject: [PATCH 522/756] Remove debug print --- pyiron_contrib/workflow/composite.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 5df45fa88..6cfdce0ea 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -170,11 +170,6 @@ def add_node(self, node: Node, label: Optional[str] = None) -> None: self.nodes[label] = node node.label = label - print( - f"{self.label} ({type(self)}) " - f"adding {node.label} ({type(node)}) " - f"that has parent {node.parent.label if node.parent is not None else None} ({type(node.parent)})" - ) node.parent = self return node From 9db6a4c54d7a4eae868afe6a2cc8b379fde1a093 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 4 Aug 2023 10:41:04 -0700 Subject: [PATCH 523/756] Raise the type error directly now that we allow for "parent" key --- tests/unit/workflow/test_workflow.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 7a8efac73..2315a2984 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -138,12 +138,8 @@ def test_no_parents(self): # the spec. If that spec changes, test instead that you _can_ set parents! wf2.parent = "not None" - with self.assertRaises(AttributeError): - # Setting a non-None value to parent raises the type error above - # If that value is further a nodal object, the __setattr__ definition - # takes over, and we try to add it to the nodes, but there we will run into - # the fact you can't add a node to a taken attribute label - # In both cases, we satisfy the spec that workflow's can't have parents + with self.assertRaises(TypeError): + # Setting a non-None value to parent raises the type error from the setter wf2.parent = wf def test_executor(self): From e3f8289bb7bed1957414ec0b359017c2a5ec53c9 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 4 Aug 2023 10:46:18 -0700 Subject: [PATCH 524/756] Add a macro class --- pyiron_contrib/workflow/macro.py | 172 ++++++++++++++++++++++++++++++ tests/unit/workflow/test_macro.py | 148 +++++++++++++++++++++++++ 2 files changed, 320 insertions(+) create mode 100644 pyiron_contrib/workflow/macro.py create mode 100644 tests/unit/workflow/test_macro.py diff --git a/pyiron_contrib/workflow/macro.py b/pyiron_contrib/workflow/macro.py new file mode 100644 index 000000000..839932bff --- /dev/null +++ b/pyiron_contrib/workflow/macro.py @@ -0,0 +1,172 @@ +""" +A base class for macro nodes, which are composite like workflows but have a static +interface and are not intended to be internally modified after instantiation. +""" + +from __future__ import annotations + +from typing import Literal, Optional + +from pyiron_contrib.workflow.composite import Composite +from pyiron_contrib.workflow.io import Outputs, Inputs + + +class Macro(Composite): + """ + A macro is a composite node that holds a graph with a fixed interface, like a + pre-populated workflow that is the same every time you instantiate it. + + At instantiation, the macro uses a provided callable to build and wire the graph, + then builds a static IO interface for this graph. (By default, unconnected IO is + passed using the same formalism as workflows to combine node and channel names, but + this can be overriden to rename the channels in the IO panel and/or to expose + channels that already have an internal connection.) + + Like function nodes, initial values for input can be set using kwargs, and the node + will (by default) attempt to update at the end of the instantiation process. + + It is intended that subclasses override the initialization signature and provide + the graph creation directly from their own method. + + Examples: + Let's consider the simplest case of macros that just consecutively add 1 to + their input: + >>> from pyiron_contrib.workflow.function import SingleValue + >>> from pyiron_contrib.workflow.macro import Macro + >>> + >>> def add_one(x): + ... result = x + 1 + ... return result + >>> + >>> def add_three_macro(macro): + ... macro.one = SingleValue(add_one) + ... macro.two = SingleValue(add_one, macro.one) + ... macro.three = SingleValue(add_one, macro.two) + + We can make a macro by passing this graph-building function (that takes a macro + as its first argument, i.e. `self` from the macro's perspective) to the `Macro` + class. Then, we can use it like a regular node! Just like a workflow, the + io is constructed from unconnected owned-node IO by combining node and channel + labels. + >>> macro = Macro(add_three_macro) + >>> out = macro(one_x=3) + >>> out.three_result + 6 + + If there's a particular macro we're going to use again and again, we might want + to consider making a new child class of `Macro` that overrides the + `graph_creator` arg such that the same graph is always created. We could + override `__init__` the normal way, but it's even faster to just use + `partialmethod`: + >>> from functools import partialmethod + >>> class AddThreeMacro(Macro): + ... def build_graph(self): + ... add_three_macro(self) + ... + ... __init__ = partialmethod( + ... Macro.__init__, + ... build_graph, + ... ) + >>> + >>> macro = AddThreeMacro() + >>> macro(one_x=0).three_result + 3 + + We can also nest macros, rename their IO, and provide access to + internally-connected IO: + >>> def nested_macro(macro): + ... macro.a = SingleValue(add_one) + ... macro.b = Macro(add_three_macro, one_x=macro.a) + ... macro.c = SingleValue(add_one, x=macro.b.outputs.three_result) + >>> + >>> macro = Macro( + ... nested_macro, + ... inputs_map={"a_x": "inp"}, + ... outputs_map={"c_result": "out", "b_result": "intermediate"}, + ... ) + >>> macro(inp=1) + {'intermediate': 5, 'out': 6} + + Since the graph builder has access to the macro being instantiated, we can also + do things like override the starting nodes to be used when invoking a run. E.g. + consider this two-track graph, where we would normally run both nodes on a `run` + call (since they are both head-most nodes), but we override the default behavior + to only run _one_ of the two tracks (note that we stop the child nodes from + running when they get their values updated, just so we can see that one of them + is really not doing anything on the run command): + >>> def modified_start_macro(macro): + ... macro.a = SingleValue(add_one, x=0, run_on_updates=False) + ... macro.b = SingleValue(add_one, x=0, run_on_updates=False) + ... macro.starting_nodes = [macro.b] + >>> + >>> m = Macro(modified_start_macro, update_on_instantiation=False) + >>> m.outputs.to_value_dict() + {'a_result': pyiron_contrib.workflow.channels.NotData, + 'b_result': pyiron_contrib.workflow.channels.NotData} + + >>> m(a_x=1, b_x=2) + {'a_result': pyiron_contrib.workflow.channels.NotData, 'b_result': 3} + """ + + def __init__( + self, + graph_creator: callable[[Macro], None], + label: Optional[str] = None, + inputs_map: Optional[dict] = None, + outputs_map: Optional[dict] = None, + run_on_updates: bool = True, + update_on_instantiation: bool = True, + parent: Optional[Composite] = None, + strict_naming: bool = True, + **kwargs, + ): + self._parent = None + super().__init__( + label=label if label is not None else graph_creator.__name__, + parent=parent, + run_on_updates=run_on_updates, + strict_naming=strict_naming, + ) + graph_creator(self) + + self._inputs: Inputs = self._build_inputs(inputs_map) + self._outputs: Outputs = self._build_outputs(outputs_map) + + self._batch_update_input(**kwargs) + + if update_on_instantiation: + self.update() + + def _build_io( + self, + io: Inputs | Outputs, + target: Literal["inputs", "outputs"], + key_map: dict[str, str] | None + ) -> Inputs | Outputs: + key_map = {} if key_map is None else key_map + for node in self.nodes.values(): + for channel in getattr(node, target): + default_key = f"{node.label}_{channel.label}" + try: + io[key_map[default_key]] = channel + except KeyError: + if not channel.connected: + io[default_key] = channel + return io + + def _build_inputs(self, key_map: dict[str, str] | None) -> Inputs: + return self._build_io(Inputs(), "inputs", key_map) + + def _build_outputs(self, key_map: dict[str, str] | None) -> Outputs: + return self._build_io(Outputs(), "outputs", key_map) + + @property + def inputs(self) -> Inputs: + return self._inputs + + @property + def outputs(self) -> Outputs: + return self._outputs + + def to_workfow(self): + raise NotImplementedError diff --git a/tests/unit/workflow/test_macro.py b/tests/unit/workflow/test_macro.py new file mode 100644 index 000000000..65011d8e9 --- /dev/null +++ b/tests/unit/workflow/test_macro.py @@ -0,0 +1,148 @@ +from functools import partialmethod +import unittest +from sys import version_info + +from pyiron_contrib.workflow.channels import NotData +from pyiron_contrib.workflow.function import SingleValue +from pyiron_contrib.workflow.macro import Macro + + +def add_one(x): + result = x + 1 + return result + + +def add_three_macro(macro): + macro.one = SingleValue(add_one) + SingleValue(add_one, macro.one, label="two", parent=macro) + macro.add(SingleValue(add_one, macro.two, label="three")) + # Cover a handful of addition methods, + # although these are more thoroughly tested in Workflow tests + + +@unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") +class TestMacro(unittest.TestCase): + + def test_labels(self): + m = Macro(add_three_macro) + self.assertEqual( + m.label, + add_three_macro.__name__, + msg="Label should be automatically generated" + ) + label = "custom_name" + m2 = Macro(add_three_macro, label=label) + self.assertEqual(m2.label, label, msg="Should be able to specify a label") + + def test_by_function(self): + m = Macro(add_three_macro) + + self.assertIs( + m.outputs.three_result.value, + NotData, + msg="Output should be accessible with the usual naming convention, but we " + "asked the node not to run yet so there shouldn't be any data" + ) + + input_x = 1 + expected_value = add_one(add_one(add_one(input_x))) + out = m(one_x=input_x) # Take kwargs to set input at runtime + + self.assertEqual( + out.three_result, + expected_value, + msg="Macros should return the output, just like other nodes" + ) + self.assertEqual( + m.outputs.three_result.value, + expected_value, + msg="Macros should get output updated, just like other nodes" + ) + + def test_by_subclass(self): + class MyMacro(Macro): + def build_graph(self): + add_three_macro(self) + + __init__ = partialmethod( + Macro.__init__, + build_graph, + ) + + x = 0 + m = MyMacro(one_x=x) + self.assertEqual( + m.outputs.three_result.value, + add_one(add_one(add_one(x))), + msg="Subclasses should be able to simply override the graph_creator arg" + ) + + def test_key_map(self): + m = Macro( + add_three_macro, + inputs_map={"one_x": "my_input"}, + outputs_map={"three_result": "my_output", "two_result": "intermediate"}, + ) + self.assertSetEqual( + set(m.inputs.labels), + set(("my_input",)), + msg="Input should be relabelled, but not added to or taken away from" + ) + self.assertSetEqual( + set(m.outputs.labels), + set(("my_output", "intermediate")), + msg="Output should be relabelled and expanded" + ) + + with self.subTest("Make new names can be used as usual"): + x = 0 + out = m(my_input=x) + self.assertEqual( + out.my_output, + add_one(add_one(add_one(x))), + msg="Expected output but relabeled should be accessible" + ) + self.assertEqual( + out.intermediate, + add_one(add_one(x)), + msg="New, internally connected output that was specifically requested " + "should be accessible" + ) + + def test_nesting(self): + def nested_macro(macro): + macro.a = SingleValue(add_one) + macro.b = Macro(add_three_macro, one_x=macro.a) + macro.c = SingleValue(add_one, x=macro.b.outputs.three_result) + + m = Macro(nested_macro) + self.assertEqual(m(a_x=0).c_result, 5) + + def test_custom_start(self): + def modified_start_macro(macro): + macro.a = SingleValue(add_one, x=0, run_on_updates=False) + macro.b = SingleValue(add_one, x=0, run_on_updates=False) + macro.starting_nodes = [macro.b] + + m = Macro(modified_start_macro, update_on_instantiation=False) + self.assertIs( + m.outputs.a_result.value, + NotData, + msg="Node should not have run when the macro batch updated input" + ) + self.assertIs( + m.outputs.b_result.value, + NotData, + msg="Node should not have run when the macro batch updated input" + ) + m.run() + self.assertIs( + m.outputs.a_result.value, + NotData, + msg="Was not included in starting nodes, should not have run" + ) + self.assertEqual( + m.outputs.b_result.value, + 1, + msg="Was included in starting nodes, should have run" + ) From f363455ff53201033e2b0eea4e691dab64ba479f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 4 Aug 2023 11:00:07 -0700 Subject: [PATCH 525/756] Move IO construction up to Composite so Workflow can relabel IO too --- pyiron_contrib/workflow/composite.py | 30 +++++++++++++++++++++++- pyiron_contrib/workflow/macro.py | 35 ++++++---------------------- pyiron_contrib/workflow/workflow.py | 26 ++++++++++----------- tests/unit/workflow/test_workflow.py | 10 ++++++++ 4 files changed, 58 insertions(+), 43 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 6cfdce0ea..209f7a2ff 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -7,10 +7,11 @@ from abc import ABC from functools import partial -from typing import Optional +from typing import Literal, Optional from warnings import warn from pyiron_contrib.executors import CloudpickleProcessPoolExecutor +from pyiron_contrib.workflow.io import Outputs, Inputs from pyiron_contrib.workflow.node import Node from pyiron_contrib.workflow.function import ( Function, @@ -97,12 +98,16 @@ def __init__( parent: Optional[Composite] = None, run_on_updates: bool = True, strict_naming: bool = True, + inputs_map: Optional[dict] = None, + outputs_map: Optional[dict] = None, **kwargs, ): super().__init__( *args, label=label, parent=parent, run_on_updates=run_on_updates, **kwargs ) self.strict_naming: bool = strict_naming + self.inputs_map = inputs_map + self.outputs_map = outputs_map self.nodes: DotDict[str:Node] = DotDict() self.add: NodeAdder = NodeAdder(self) self.starting_nodes: None | list[Node] = None @@ -149,6 +154,29 @@ def run_graph(self): def run_args(self) -> dict: return {"self": self} + def _build_io( + self, + io: Inputs | Outputs, + target: Literal["inputs", "outputs"], + key_map: dict[str, str] | None + ) -> Inputs | Outputs: + key_map = {} if key_map is None else key_map + for node in self.nodes.values(): + for channel in getattr(node, target): + default_key = f"{node.label}_{channel.label}" + try: + io[key_map[default_key]] = channel + except KeyError: + if not channel.connected: + io[default_key] = channel + return io + + def _build_inputs(self) -> Inputs: + return self._build_io(Inputs(), "inputs", self.inputs_map) + + def _build_outputs(self) -> Outputs: + return self._build_io(Outputs(), "outputs", self.outputs_map) + def add_node(self, node: Node, label: Optional[str] = None) -> None: """ Assign a node to the parent. Optionally provide a new label for that node. diff --git a/pyiron_contrib/workflow/macro.py b/pyiron_contrib/workflow/macro.py index 839932bff..a173219ed 100644 --- a/pyiron_contrib/workflow/macro.py +++ b/pyiron_contrib/workflow/macro.py @@ -5,7 +5,7 @@ from __future__ import annotations -from typing import Literal, Optional +from typing import Optional from pyiron_contrib.workflow.composite import Composite from pyiron_contrib.workflow.io import Outputs, Inputs @@ -112,12 +112,12 @@ def __init__( self, graph_creator: callable[[Macro], None], label: Optional[str] = None, - inputs_map: Optional[dict] = None, - outputs_map: Optional[dict] = None, run_on_updates: bool = True, update_on_instantiation: bool = True, parent: Optional[Composite] = None, strict_naming: bool = True, + inputs_map: Optional[dict] = None, + outputs_map: Optional[dict] = None, **kwargs, ): self._parent = None @@ -126,40 +126,19 @@ def __init__( parent=parent, run_on_updates=run_on_updates, strict_naming=strict_naming, + inputs_map=inputs_map, + outputs_map=outputs_map, ) graph_creator(self) - self._inputs: Inputs = self._build_inputs(inputs_map) - self._outputs: Outputs = self._build_outputs(outputs_map) + self._inputs: Inputs = self._build_inputs() + self._outputs: Outputs = self._build_outputs() self._batch_update_input(**kwargs) if update_on_instantiation: self.update() - def _build_io( - self, - io: Inputs | Outputs, - target: Literal["inputs", "outputs"], - key_map: dict[str, str] | None - ) -> Inputs | Outputs: - key_map = {} if key_map is None else key_map - for node in self.nodes.values(): - for channel in getattr(node, target): - default_key = f"{node.label}_{channel.label}" - try: - io[key_map[default_key]] = channel - except KeyError: - if not channel.connected: - io[default_key] = channel - return io - - def _build_inputs(self, key_map: dict[str, str] | None) -> Inputs: - return self._build_io(Inputs(), "inputs", key_map) - - def _build_outputs(self, key_map: dict[str, str] | None) -> Outputs: - return self._build_io(Outputs(), "outputs", key_map) - @property def inputs(self) -> Inputs: return self._inputs diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 314270a77..ef5127f5c 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -6,7 +6,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import Optional, TYPE_CHECKING from pyiron_contrib.workflow.composite import Composite from pyiron_contrib.workflow.io import Inputs, Outputs @@ -137,13 +137,21 @@ class Workflow(Composite): """ def __init__( - self, label: str, *nodes: Node, run_on_updates: bool = True, strict_naming=True + self, + label: str, + *nodes: Node, + run_on_updates: bool = True, + strict_naming: bool = True, + inputs_map: Optional[dict] = None, + outputs_map: Optional[dict] = None, ): super().__init__( label=label, parent=None, run_on_updates=run_on_updates, strict_naming=strict_naming, + inputs_map=inputs_map, + outputs_map=outputs_map, ) for node in nodes: @@ -151,21 +159,11 @@ def __init__( @property def inputs(self) -> Inputs: - inputs = Inputs() - for node_label, node in self.nodes.items(): - for channel in node.inputs: - if not channel.connected: - inputs[f"{node_label}_{channel.label}"] = channel - return inputs + return self._build_inputs() @property def outputs(self) -> Outputs: - outputs = Outputs() - for node_label, node in self.nodes.items(): - for channel in node.outputs: - if not channel.connected: - outputs[f"{node_label}_{channel.label}"] = channel - return outputs + return self._build_outputs() def to_node(self): """ diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 2315a2984..c64e85c3e 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -111,6 +111,16 @@ def test_workflow_io(self): self.assertEqual(len(wf.inputs), 1) self.assertEqual(len(wf.outputs), 1) + with self.subTest( + "IO should be re-mappable, including exposing internally connected " + "channels" + ): + wf.inputs_map = {"n1_x": "inp"} + wf.outputs_map = {"n3_y": "out", "n2_y": "intermediate"} + out = wf(inp=0) + self.assertEqual(out.out, 3) + self.assertEqual(out.intermediate, 2) + def test_node_decorator_access(self): @Workflow.wrap_as.function_node(output_labels="y") def plus_one(x: int = 0) -> int: From 1eb93cd0652ac15021891c123fdf901a10bc0d0a Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 4 Aug 2023 11:00:49 -0700 Subject: [PATCH 526/756] Add footer for tests outside the IDE --- tests/unit/workflow/test_macro.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/unit/workflow/test_macro.py b/tests/unit/workflow/test_macro.py index 65011d8e9..a311ada1b 100644 --- a/tests/unit/workflow/test_macro.py +++ b/tests/unit/workflow/test_macro.py @@ -146,3 +146,7 @@ def modified_start_macro(macro): 1, msg="Was included in starting nodes, should have run" ) + + +if __name__ == '__main__': + unittest.main() From 5f028b2f2d8c3dc4ac074157ba9068c62d6b2e41 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 4 Aug 2023 11:02:01 -0700 Subject: [PATCH 527/756] Remove unused file --- pyiron_contrib/workflow/is_nodal.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 pyiron_contrib/workflow/is_nodal.py diff --git a/pyiron_contrib/workflow/is_nodal.py b/pyiron_contrib/workflow/is_nodal.py deleted file mode 100644 index e69de29bb..000000000 From c1763e58fe577ec9fadd61530221276306043fda Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 4 Aug 2023 14:17:01 -0700 Subject: [PATCH 528/756] Expose macros as wrappers --- pyiron_contrib/workflow/composite.py | 13 +++++++++++++ pyiron_contrib/workflow/macro.py | 29 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 209f7a2ff..6b04d2a0f 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -33,6 +33,19 @@ class _NodeDecoratorAccess: slow_node = slow_node single_value_node = single_value_node + _macro_node = None + + @classmethod + @property + def macro_node(cls): + # This jankiness is to avoid circular imports + # Chaining classmethod and property like this got deprecated in python 3.11, + # but it does what I want, so I'm going to use it anyhow + if cls._macro_node is None: + from pyiron_contrib.workflow.macro import macro_node + cls._macro_node = macro_node + return cls._macro_node + class Creator: """A shortcut interface for creating non-Node objects from the workflow class.""" diff --git a/pyiron_contrib/workflow/macro.py b/pyiron_contrib/workflow/macro.py index a173219ed..b90570d66 100644 --- a/pyiron_contrib/workflow/macro.py +++ b/pyiron_contrib/workflow/macro.py @@ -5,6 +5,7 @@ from __future__ import annotations +from functools import partialmethod from typing import Optional from pyiron_contrib.workflow.composite import Composite @@ -149,3 +150,31 @@ def outputs(self) -> Outputs: def to_workfow(self): raise NotImplementedError + + +def macro_node(**node_class_kwargs): + """ + A decorator for dynamically creating macro classes from graph-creating functions. + + Decorates a function. + Returns a `Macro` subclass whose name is the camel-case version of the + graph-creating function, and whose signature is modified to exclude this function + and provided kwargs. + + Optionally takes any keyword arguments of `Macro`. + """ + + def as_node(graph_creator: callable[[Macro], None]): + return type( + graph_creator.__name__.title().replace("_", ""), # fnc_name to CamelCase + (Macro,), # Define parentage + { + "__init__": partialmethod( + Macro.__init__, + graph_creator, + **node_class_kwargs, + ) + }, + ) + + return as_node From 985eadaea576bfbefb9cd4001528ff1595f7ec16 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 4 Aug 2023 14:22:17 -0700 Subject: [PATCH 529/756] Extend the structure node to take crystalstructure --- .../workflow/node_library/atomistics.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/node_library/atomistics.py b/pyiron_contrib/workflow/node_library/atomistics.py index 7ff060c01..cee361dd1 100644 --- a/pyiron_contrib/workflow/node_library/atomistics.py +++ b/pyiron_contrib/workflow/node_library/atomistics.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Optional +from typing import Literal, Optional from pyiron_atomistics import Project, _StructureFactory from pyiron_atomistics.atomistics.job.atomistic import AtomisticGenericJob @@ -11,8 +11,20 @@ @single_value_node(output_labels="structure") -def bulk_structure(element: str = "Fe", cubic: bool = False, repeat: int = 1) -> Atoms: - return _StructureFactory().bulk(element, cubic=cubic).repeat(repeat) +def bulk_structure( + element: str = "Fe", + cubic: bool = False, + crystalstructure: Optional[ + Literal[ + "sc", "fcc", "bcc", "hcp", "diamond", "zincblende", "rocksalt", + "cesiumchloride", "fluorite", "wurtzite" + ] + ] = None, + repeat: int = 1, +) -> Atoms: + return _StructureFactory().bulk( + element, cubic=cubic, crystalstructure=crystalstructure + ).repeat(repeat) @single_value_node(output_labels="job") From 0b3d516f04bed345e135b9f019a3f5ac7005e9fb Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 4 Aug 2023 14:23:34 -0700 Subject: [PATCH 530/756] Add a minimization node to the library --- .../workflow/node_library/atomistics.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/pyiron_contrib/workflow/node_library/atomistics.py b/pyiron_contrib/workflow/node_library/atomistics.py index cee361dd1..668e0f78b 100644 --- a/pyiron_contrib/workflow/node_library/atomistics.py +++ b/pyiron_contrib/workflow/node_library/atomistics.py @@ -164,9 +164,53 @@ def calc_md(job, n_ionic_steps, n_print, temperature, pressure): ) +@slow_node( + output_labels=[ + "cells", + "displacements", + "energy_pot", + "energy_tot", + "force_max", + "forces", + "indices", + "positions", + "pressures", + "steps", + "total_displacements", + "unwrapped_positions", + "volume", + ] +) +def calc_min( + job: AtomisticGenericJob, + n_ionic_steps: int = 1000, + n_print: int = 100, + pressure: float + | tuple[float, float, float] + | tuple[float, float, float, float, float, float] + | None = None, +): + def calc_min(job, n_ionic_steps, n_print, pressure): + job.calc_minimize( + max_iter=n_ionic_steps, # Calc minimize uses a different var than MD + n_print=n_print, + pressure=pressure, + ) + return job + + return _run_and_remove_job( + job=job, + modifier=calc_min, + n_ionic_steps=n_ionic_steps, + n_print=n_print, + pressure=pressure, + ) + + nodes = [ bulk_structure, calc_md, + calc_min, calc_static, lammps, ] From fb16ee825e69cec951f26d725e46dee7a0a761e7 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 4 Aug 2023 15:27:39 -0700 Subject: [PATCH 531/756] Propagate non-label IO panel keys --- pyiron_contrib/workflow/composite.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 6b04d2a0f..7dd474297 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -175,8 +175,10 @@ def _build_io( ) -> Inputs | Outputs: key_map = {} if key_map is None else key_map for node in self.nodes.values(): - for channel in getattr(node, target): - default_key = f"{node.label}_{channel.label}" + panel = getattr(node, target) + for channel_label in panel.labels: + channel = panel[channel_label] + default_key = f"{node.label}_{channel_label}" try: io[key_map[default_key]] = channel except KeyError: From 2b8e43ceb54252f81b093b99c5762fa63359514e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 4 Aug 2023 15:27:59 -0700 Subject: [PATCH 532/756] Add user input node to library --- pyiron_contrib/workflow/node_library/standard.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyiron_contrib/workflow/node_library/standard.py b/pyiron_contrib/workflow/node_library/standard.py index 1a2d11e21..64cce0122 100644 --- a/pyiron_contrib/workflow/node_library/standard.py +++ b/pyiron_contrib/workflow/node_library/standard.py @@ -15,6 +15,12 @@ def scatter( return plt.scatter(x, y) +@single_value_node() +def user_input(user_input): + return user_input + + nodes = [ scatter, + user_input, ] From 0ec1d7aac11bfbc7bb14d8c2a97540ff1ecc1ecc Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 4 Aug 2023 15:28:49 -0700 Subject: [PATCH 533/756] Just wrap the whole bulk structure function Means name changes and no repeat in examples and docs --- .../workflow/node_library/atomistics.py | 18 ++---------------- pyiron_contrib/workflow/workflow.py | 3 +-- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/pyiron_contrib/workflow/node_library/atomistics.py b/pyiron_contrib/workflow/node_library/atomistics.py index 668e0f78b..94dc6f281 100644 --- a/pyiron_contrib/workflow/node_library/atomistics.py +++ b/pyiron_contrib/workflow/node_library/atomistics.py @@ -10,21 +10,7 @@ from pyiron_contrib.workflow.function import single_value_node, slow_node -@single_value_node(output_labels="structure") -def bulk_structure( - element: str = "Fe", - cubic: bool = False, - crystalstructure: Optional[ - Literal[ - "sc", "fcc", "bcc", "hcp", "diamond", "zincblende", "rocksalt", - "cesiumchloride", "fluorite", "wurtzite" - ] - ] = None, - repeat: int = 1, -) -> Atoms: - return _StructureFactory().bulk( - element, cubic=cubic, crystalstructure=crystalstructure - ).repeat(repeat) +Bulk = single_value_node(output_labels="structure")(_StructureFactory().bulk) @single_value_node(output_labels="job") @@ -208,7 +194,7 @@ def calc_min(job, n_ionic_steps, n_print, pressure): nodes = [ - bulk_structure, + Bulk, calc_md, calc_min, calc_static, diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index ef5127f5c..09343264c 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -101,8 +101,7 @@ class Workflow(Composite): namespaces, e.g. >>> wf = Workflow("with_prebuilt") >>> - >>> wf.structure = wf.add.atomistics.BulkStructure( - ... repeat=3, + >>> wf.structure = wf.add.atomistics.Bulk( ... cubic=True, ... element="Al" ... ) From 194e76843ae6e0f9f4e5ac1380677e266ada2146 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 4 Aug 2023 16:11:31 -0700 Subject: [PATCH 534/756] Update example --- notebooks/workflow_example.ipynb | 2864 +++++++++++++++++++++++++----- 1 file changed, 2447 insertions(+), 417 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index f7b21d48f..571ca2b6e 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -11,7 +11,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "71c73d24f4ea44e5aef03b0611a3c9a4", + "model_id": "a2c8ea2bb05240b0b5813a6c12426893", "version_major": 2, "version_minor": 0 }, @@ -716,7 +716,7 @@ "outputs": [ { "data": { - "image/png": "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", + "image/png": "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359QwQAAYGnJtDugvb1ddXV1am1t1YYNG/T000+rqqpKfX19uvLKK2cdc+edd+q9997TM888o9/93d/V8PCwPvzwwwsuHgAApD6HZVmWnQHl5eUqKSlRW1vbTFthYaG2bt2qlpaWuP6dnZ360z/9U73zzjvKyclJqMhIJCKPx6NwOKzs7OyE3gMAACTXfH+/bS3TTE5OqqenR4FAIKY9EAiou7t71jE/+tGPVFpaqm9+85u64oordM011+ihhx7Sb37zmzk/Z2JiQpFIJOYAAABLk61lmpGREUWjUXm93ph2r9eroaGhWce88847Onr0qNxut374wx9qZGREX/nKV3T27Nk5rxtpaWlRc3OzndIAAECKSugCVofDEfPasqy4tmlTU1NyOBw6dOiQysrKdNttt2nPnj167rnn5pwdaWhoUDgcnjkGBwcTKRMAAKQAWzMjubm5cjqdcbMgw8PDcbMl03w+n6644gp5PJ6ZtsLCQlmWpVOnTunqq6+OG+NyueRyueyUBgAAUpStmZGsrCz5/X4Fg8GY9mAwqMrKylnHbNiwQWfOnNH7778/0/bmm28qIyNDa9asSaBkAACwlNhepqmvr9f3v/997d+/X7/4xS/0l3/5lxoYGFBtba2kc0ssd99990z/bdu2aeXKlbr33nvV19enV199VQ8//LD+/M//XMuWLVu4bwIAAFKS7X1GqqurNTo6qt27dysUCqm4uFgdHR3Kz8+XJIVCIQ0MDMz0/53f+R0Fg0F99atfVWlpqVauXKk777xTjz/++MJ9CwAAkLJs7zNiAvuMAACQei7KPiMAAAALjTACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMSCiOtra0qKCiQ2+2W3+9XV1fXnH3/7d/+TQ6HI+74z//8z4SLBgAAS4ftMNLe3q66ujo1Njaqt7dXGzduVFVVlQYGBs477pe//KVCodDMcfXVVydcNAAAWDpsh5E9e/Zox44d2rlzpwoLC7V3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AIBRhBEAAGAUYQQAABhFGEkBra2tKigokNvtlt/vV1dX15x9X375ZW3atEmXXXaZsrOzVVFRoZ/85CdJrDa92TlXH/Xaa68pMzNTN9xww8UtEDHsnq+JiQk1NjYqPz9fLpdLa9eu1f79+5NUbXqze64OHTqk66+/Xp/61Kfk8/l07733anR0NEnVwjYLi9oPfvAD65JLLrG+973vWX19fdbXvvY1a/ny5dZ///d/z9r/a1/7mvWNb3zDOnbsmPXmm29aDQ0N1iWXXGKdOHEiyZWnH7vnatqvfvUr69Of/rQVCASs66+/PjnFIqHz9fnPf94qLy+3gsGg1d/fb/37v/+79dprryWx6vRk91x1dXVZGRkZ1pNPPmm98847VldXl7V+/Xpr69atSa4c80UYWeTKysqs2tramLZrr73WeuSRR+b9HkVFRVZzc/NCl4aPSfRcVVdXW3/9139tNTU1EUaSyO75+vGPf2x5PB5rdHQ0GeXhI+yeq29961vWpz/96Zi273znO9aaNWsuWo24MCzTLGKTk5Pq6elRIBCIaQ8EAuru7p7Xe0xNTWlsbEw5OTkXo0T8n0TP1bPPPqu3335bTU1NF7tEfEQi5+tHP/qRSktL9c1vflNXXHGFrrnmGj300EP6zW9+k4yS01Yi56qyslKnTp1SR0eHLMvSe++9pxdffFG33357MkpGAlLiQXnpamRkRNFoVF6vN6bd6/VqaGhoXu/xxBNP6Ne//rXuvPPOi1Ei/k8i5+qtt97SI488oq6uLmVm8k8xmRI5X++8846OHj0qt9utH/7whxoZGdFXvvIVnT17lutGLqJEzlVlZaUOHTqk6upqjY+P68MPP9TnP/95ffe7301GyUgAMyMpwOFwxLy2LCuubTaHDx/WY489pvb2dq1atepilYePmO+5ikaj2rZtm5qbm3XNNdckqzx8jJ1/W1NTU3I4HDp06JDKysp02223ac+ePXruueeYHUkCO+eqr69PDz74oB599FH19PSos7NT/f39qq2tTUapSAD/ObaI5ebmyul0xqX/4eHhuP9K+Lj29nbt2LFDL7zwgm699daLWSZk/1yNjY3p+PHj6u3t1QMPPCDp3I+dZVnKzMzUT3/6U918881JqT0dJfJvy+fz6Yorroh5HHphYaEsy9KpU6d09dVXX9Sa01Ui56qlpUUbNmzQww8/LEm67rrrtHz5cm3cuFGPP/64fD7fRa8b9jAzsohlZWXJ7/crGAzGtAeDQVVWVs457vDhw9q+fbuef/551kiTxO65ys7O1s9//nO9/vrrM0dtba3WrVun119/XeXl5ckqPS0l8m9rw4YNOnPmjN5///2ZtjfffFMZGRlas2bNRa03nSVyrj744ANlZMT+vDmdTknnZlSwCJm7dhbzMX1L2zPPPGP19fVZdXV11vLly613333XsizLeuSRR6yampqZ/s8//7yVmZlpPfXUU1YoFJo5fvWrX5n6CmnD7rn6OO6mSS6752tsbMxas2aN9cd//MfWG2+8Yb3yyivW1Vdfbe3cudPUV0gbds/Vs88+a2VmZlqtra3W22+/bR09etQqLS21ysrKTH0FfALCSAp46qmnrPz8fCsrK8sqKSmxXnnllZm/3XPPPdZNN9008/qmm26yJMUd99xzT/ILT0N2ztXHEUaSz+75+sUvfmHdeuut1rJly6w1a9ZY9fX11gcffJDkqtOT3XP1ne98xyoqKrKWLVtm+Xw+68/+7M+sU6dOJblqzJfDspizAgAA5nDNCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwKj/D/RGKNtPVo3wAAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -805,7 +805,7 @@ "outputs": [ { "data": { - "image/png": "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", + "image/png": "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q7r99ttTUXLac/ygPGSXkZERRSIRFRYWxrQXFhZqaGhoQV/j+9//vj766CPdcccdi1EiLpLINfv973+vxx57TF1dXcrN5ddCKiVyvU6fPq133nlHPp9Pr7/+ukZGRvTggw9qbGyMdSMpkMg127Bhgw4cOKDa2lpNTk7q/Pnz+spXvqIf/ehHqSg57TEyggXxeDwxr40xc9riOXjwoL7zne+otbVVK1asWKzyEMdCr1kkEtGdd96pxsZG3XTTTakqDxdx8n8sGo3K4/HowIEDqqio0G233aYnn3xSL774IqMjKeTkmvX19Wnnzp16/PHH1dPTo46ODvX396uuri4VpaY9/gTCJS1fvlxer3dO2h8eHp7zV8HFWltbdd999+mVV17RLbfcsphl4gJOr9nExISOHj2q48eP69vf/rak6Q87Y4xyc3P15ptv6ktf+lJKas9GifwfCwQCuu6662Iezb527VoZY3TmzBndeOONi1pztkvkmjU1NWnjxo169NFHJUmf+cxndM0112jTpk367ne/q0AgsOh1pzNGRnBJS5YsUXl5uTo7O2PaOzs7tWHDhnnPO3jwoO655x69/PLLzImmmNNrlp+fr9/97nc6ceLE7FFXV6e/+qu/0okTJ7R+/fpUlZ6VEvk/tnHjRr3//vv68MMPZ9vee+895eTkaNWqVYtaLxK7Zh9//LFycmI/cr1er6TpEZWsZ2/tLNxi5ha2559/3vT19Zldu3aZa665xvzP//yPMcaYxx57zNx1112z/V9++WWTm5trnn76aRMKhWaPP/7xj7a+hazj9JpdjLtpUsvp9ZqYmDCrVq0yf/u3f2veffdd8/bbb5sbb7zR3H///ba+hazj9Jq98MILJjc31zQ3N5tTp06Zd955x6xbt85UVFTY+hbSCmEEC/L000+b4uJis2TJElNWVmbefvvt2X+7++67zRe+8IXZ11/4wheMpDnH3XffnfrCs5iTa3YxwkjqOb1eJ0+eNLfccou5+uqrzapVq8zu3bvNxx9/nOKqs5vTa/bUU0+ZkpISc/XVV5tAIGD+/u//3pw5cybFVacnjzGMDwEAAHtYMwIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALDqfwFa0c4H0SXirgAAAABJRU5ErkJggg==", "text/plain": [ "
" ] @@ -924,7 +924,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label sum_ to the io key sum_sum_\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label sum_ to the io key sum_sum_\n", " warn(\n" ] } @@ -963,9 +963,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label x to the io key a_x\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label x to the io key a_x\n", " warn(\n", - "/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", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label x to the io key b_x\n", " warn(\n" ] }, @@ -1038,9 +1038,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:220: 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:263: UserWarning: Reassigning the node bulk 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:220: 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:263: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent with_prebuilt.\n", " warn(\n" ] }, @@ -1055,15 +1055,15 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:220: 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:263: 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:220: 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:263: UserWarning: Reassigning the node scatter to the label plot when adding it to the parent with_prebuilt.\n", " warn(\n" ] }, { "data": { - "image/png": "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d7vd8nq9re4jFApp9erVWrdunUaNGiVJKi4uVk5OjrZs2aKxY8dGewwAAKCH6dCbZEOhkCQpLS1y1tytW7cqMzNTl112mYYNG6annnpKmZmZkqTy8nKdOHFCY8aMceqzs7Pl9/u1Y8eOVgNKOBxWOBw+43vr6uo60nwAANCFTp23jTHnrG13QDHG6JFHHtFtt90mv9/vrB8/frzuuusu5ebmqqqqSr/4xS80YsQIlZeXy+12KxgMKjExUX369InYX1ZWloLBYKvfVVRUpGXLlp2xPicnp73NBwAA3aS+vl4ej+esNe0OKA8//LD+/Oc/a/v27RHrp02b5vzZ7/dr4MCBys3N1dtvv60pU6a0uT9jjFyu1ie5W7x4sR555BHn55aWFh09elTp6eltfqa96urqlJOTo+rqaqWmpnbqvvH/6OeuQT93Dfq569DXXeNC9bMxRvX19crOzj5nbbsCyrx58/TWW2/pgw8+0BVXXHHWWp/Pp9zcXB06dEiS5PV61dTUpNra2ohRlJqaGg0dOrTVfbjdbrnd7oh1l112WXuaft5SU1P5x98F6OeuQT93Dfq569DXXeNC9PO5Rk5OieopHmOMHn74Yb355pt6//33lZeXd87PHDlyRNXV1fL5fJKkgoICJSQkqKyszKkJBAKqrKxsM6AAAICLS1QjKHPnztX69ev1+9//XikpKc49Ix6PR7169VJDQ4OWLl2qO++8Uz6fT59++qmWLFmijIwM3XHHHU7t7NmztWDBAqWnpystLU0LFy5Ufn6+81QPAAC4uEUVUFauXClJGj58eMT6V199VbNmzVJcXJwqKiq0du1aHTt2TD6fT7fffrveeOMNpaSkOPUrVqxQfHy8pk6dqsbGRo0cOVJr1qxRXFxcx4+og9xut5544okzLimhc9HPXYN+7hr0c9ehr7uGDf3sMufzrA8AAEAXYi4eAABgHQIKAACwDgEFAABYh4ACAACsQ0D5O//+7/+uvLw8JSUlqaCgQH/84x+7u0kxpaioSDfffLNSUlKUmZmpH//4x/roo48iaowxWrp0qbKzs9WrVy8NHz5cBw4ciKgJh8OaN2+eMjIylJycrMmTJ+vw4cNdeSgxpaioSC6XS4WFhc46+rlzfPHFF5oxY4bS09PVu3dv3XjjjSovL3e2088d99133+lf//VflZeXp169eumqq67SL3/5S7W0tDg19HP7fPDBB5o0aZKys7Plcrn0u9/9LmJ7Z/VrbW2t7rvvPnk8Hnk8Ht133306duxYxw/AwBhjTElJiUlISDCrVq0yBw8eNPPnzzfJycnms88+6+6mxYyxY8eaV1991VRWVpr9+/ebCRMmmCuvvNI0NDQ4NU8//bRJSUkxv/3tb01FRYWZNm2a8fl8pq6uzql58MEHzfe+9z1TVlZm9u7da26//XZzww03mO+++647Dstqu3fvNv/wD/9g+vfvb+bPn++sp5877ujRoyY3N9fMmjXLfPjhh6aqqsps2bLF/PWvf3Vq6OeOe/LJJ016err5j//4D1NVVWV+85vfmEsvvdS8+OKLTg393D4bN240jz/+uPntb39rJJnS0tKI7Z3Vr+PGjTN+v9/s2LHD7Nixw/j9fjNx4sQOt5+A8n9uueUW8+CDD0asu+6668xjjz3WTS2KfTU1NUaS2bZtmzHGmJaWFuP1es3TTz/t1Bw/ftx4PB7z61//2hhjzLFjx0xCQoIpKSlxar744gtzySWXmE2bNnXtAViuvr7e9O3b15SVlZlhw4Y5AYV+7hyPPvqoue2229rcTj93jgkTJpif/vSnEeumTJliZsyYYYyhnzvL6QGls/r14MGDRpLZtWuXU7Nz504jyfz3f/93h9rMJR5JTU1NKi8v15gxYyLWjxkzRjt27OimVsW+UCgkSUpLS5MkVVVVKRgMRvSz2+3WsGHDnH4uLy/XiRMnImqys7Pl9/v5uzjN3LlzNWHChDPewEw/d4633npLAwcO1F133aXMzEwNGDBAq1atcrbTz53jtttu03vvvaePP/5YkvRf//Vf2r59u/7xH/9REv18oXRWv+7cuVMej0eDBg1yagYPHiyPx9Phvm/3bMY9yddff63m5mZlZWVFrM/KynJe54/oGGP0yCOP6LbbbpPf75ckpy9b6+fPPvvMqUlMTIyYSPJUDX8X/6+kpER79+7Vnj17zthGP3eOTz75RCtXrtQjjzyiJUuWaPfu3frZz34mt9utn/zkJ/RzJ3n00UcVCoV03XXXKS4uTs3NzXrqqad0zz33SOLf84XSWf0aDAaVmZl5xv4zMzM73PcElL/jcrkifjbGnLEO5+fhhx/Wn//8Z23fvv2Mbe3pZ/4u/l91dbXmz5+vzZs3Kykpqc06+rljWlpaNHDgQC1fvlySNGDAAB04cEArV67UT37yE6eOfu6YN954Q8XFxVq/fr2uv/567d+/X4WFhcrOztbMmTOdOvr5wuiMfm2tvjP6nks8kjIyMhQXF3dG2qupqTkjXeLc5s2bp7feekt/+MMfdMUVVzjrvV6vJJ21n71er5qamlRbW9tmzcWuvLxcNTU1KigoUHx8vOLj47Vt2za99NJLio+Pd/qJfu4Yn8+nfv36Raz7wQ9+oM8//1wS/547y7/8y7/oscce09133638/Hzdd999+vnPf66ioiJJ9POF0ln96vV69dVXX52x///5n//pcN8TUCQlJiaqoKBAZWVlEevLyso0dOjQbmpV7DHG6OGHH9abb76p999/X3l5eRHb8/Ly5PV6I/q5qalJ27Ztc/q5oKBACQkJETWBQECVlZX8XfyfkSNHqqKiQvv373eWgQMHavr06dq/f7+uuuoq+rkT3HrrrWc8Jv/xxx8rNzdXEv+eO8u3336rSy6JPBXFxcU5jxnTzxdGZ/XrkCFDFAqFtHv3bqfmww8/VCgU6njfd+gW2x7k1GPGq1evNgcPHjSFhYUmOTnZfPrpp93dtJgxZ84c4/F4zNatW00gEHCWb7/91ql5+umnjcfjMW+++aapqKgw99xzT6uPtV1xxRVmy5YtZu/evWbEiBEX/eOC5/L3T/EYQz93ht27d5v4+Hjz1FNPmUOHDpnXX3/d9O7d2xQXFzs19HPHzZw503zve99zHjN+8803TUZGhlm0aJFTQz+3T319vdm3b5/Zt2+fkWReeOEFs2/fPuf1GZ3Vr+PGjTP9+/c3O3fuNDt37jT5+fk8ZtzZ/u3f/s3k5uaaxMREc9NNNzmPx+L8SGp1efXVV52alpYW88QTTxiv12vcbrf54Q9/aCoqKiL209jYaB5++GGTlpZmevXqZSZOnGg+//zzLj6a2HJ6QKGfO8eGDRuM3+83brfbXHfddeaVV16J2E4/d1xdXZ2ZP3++ufLKK01SUpK56qqrzOOPP27C4bBTQz+3zx/+8IdWfyfPnDnTGNN5/XrkyBEzffp0k5KSYlJSUsz06dNNbW1th9vvMsaYjo3BAAAAdC7uQQEAANYhoAAAAOsQUAAAgHUIKAAAwDoEFAAAYB0CCgAAsA4BBQAAWIeAAgAArENAAQAA1iGgAAAA6xBQAACAdQgoAADAOv8L4r+IohWRUVIAAAAASUVORK5CYII=", + "image/png": "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", "text/plain": [ "
" ] @@ -1075,7 +1075,7 @@ "source": [ "wf = Workflow(\"with_prebuilt\")\n", "\n", - "wf.structure = wf.add.atomistics.BulkStructure(repeat=3, cubic=True, element=\"Al\")\n", + "wf.structure = wf.add.atomistics.Bulk(cubic=True, name=\"Al\")\n", "wf.engine = wf.add.atomistics.Lammps(structure=wf.structure)\n", "wf.calc = wf.add.atomistics.CalcMd(\n", " job=wf.engine, \n", @@ -1106,45 +1106,55 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label element to the io key structure_element\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label name to the io key structure_name\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label cubic to the io key structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key structure_crystalstructure\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label repeat to the io key structure_repeat\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key structure_a\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key structure_c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label n_print to the io key calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key structure_covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label temperature to the io key calc_temperature\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key structure_u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label pressure to the io key calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key structure_orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label cells to the io key calc_cells\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key structure_cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label displacements to the io key calc_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc_n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label energy_pot to the io key calc_energy_pot\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key calc_n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label energy_tot to the io key calc_energy_tot\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label temperature to the io key calc_temperature\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label force_max to the io key calc_force_max\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key calc_pressure\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label forces to the io key calc_forces\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key calc_cells\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label indices to the io key calc_indices\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key calc_displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label positions to the io key calc_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_pot to the io key calc_energy_pot\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label pressures to the io key calc_pressures\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key calc_energy_tot\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label total_displacements to the io key calc_total_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key calc_force_max\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc_unwrapped_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key calc_forces\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label volume to the io key calc_volume\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key calc_indices\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label fig to the io key plot_fig\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key calc_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key calc_pressures\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key calc_total_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc_unwrapped_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key calc_volume\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label fig to the io key plot_fig\n", " warn(\n" ] }, @@ -1157,692 +1167,787 @@ "\n", "\n", - "\n", + "\n", "\n", "clusterwith_prebuilt\n", - "\n", - "with_prebuilt: Workflow\n", + "\n", + "with_prebuilt: Workflow\n", "\n", "clusterwith_prebuiltInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", "clusterwith_prebuiltOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "clusterwith_prebuiltstructure\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "structure: BulkStructure\n", + "\n", + "structure: Bulk\n", "\n", "\n", "clusterwith_prebuiltstructureInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", "clusterwith_prebuiltstructureOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "clusterwith_prebuiltengine\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "engine: Lammps\n", + "\n", + "engine: Lammps\n", "\n", "\n", "clusterwith_prebuiltengineInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", "clusterwith_prebuiltengineOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "clusterwith_prebuiltcalc\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "calc: CalcMd\n", + "\n", + "calc: CalcMd\n", "\n", "\n", "clusterwith_prebuiltcalcInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", "clusterwith_prebuiltcalcOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "clusterwith_prebuiltplot\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "plot: Scatter\n", + "\n", + "plot: Scatter\n", "\n", "\n", "clusterwith_prebuiltplotInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", "clusterwith_prebuiltplotOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", - "\n", + "\n", "\n", - "clusterwith_prebuiltInputselement\n", - "\n", - "element: str\n", + "clusterwith_prebuiltInputsname\n", + "\n", + "name\n", "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureInputselement\n", - "\n", - "element: str\n", + "\n", + "\n", + "clusterwith_prebuiltstructureInputsname\n", + "\n", + "name\n", "\n", - "\n", + "\n", "\n", - "clusterwith_prebuiltInputselement->clusterwith_prebuiltstructureInputselement\n", - "\n", - "\n", - "\n", + "clusterwith_prebuiltInputsname->clusterwith_prebuiltstructureInputsname\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "clusterwith_prebuiltInputscubic\n", - "\n", - "cubic: bool\n", + "clusterwith_prebuiltInputscrystalstructure\n", + "\n", + "crystalstructure\n", "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureInputscubic\n", - "\n", - "cubic: bool\n", + "\n", + "\n", + "clusterwith_prebuiltstructureInputscrystalstructure\n", + "\n", + "crystalstructure\n", "\n", - "\n", + "\n", "\n", - "clusterwith_prebuiltInputscubic->clusterwith_prebuiltstructureInputscubic\n", - "\n", - "\n", - "\n", + "clusterwith_prebuiltInputscrystalstructure->clusterwith_prebuiltstructureInputscrystalstructure\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "clusterwith_prebuiltInputsrepeat\n", - "\n", - "repeat: int\n", + "clusterwith_prebuiltInputsa\n", + "\n", + "a\n", "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureInputsrepeat\n", - "\n", - "repeat: int\n", + "\n", + "\n", + "clusterwith_prebuiltstructureInputsa\n", + "\n", + "a\n", "\n", - "\n", + "\n", "\n", - "clusterwith_prebuiltInputsrepeat->clusterwith_prebuiltstructureInputsrepeat\n", - "\n", - "\n", - "\n", + "clusterwith_prebuiltInputsa->clusterwith_prebuiltstructureInputsa\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", + "clusterwith_prebuiltInputsc\n", + "\n", + "c\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltstructureInputsc\n", + "\n", + "c\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsc->clusterwith_prebuiltstructureInputsc\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputscovera\n", + "\n", + "covera\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltstructureInputscovera\n", + "\n", + "covera\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputscovera->clusterwith_prebuiltstructureInputscovera\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsu\n", + "\n", + "u\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltstructureInputsu\n", + "\n", + "u\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsu->clusterwith_prebuiltstructureInputsu\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsorthorhombic\n", + "\n", + "orthorhombic\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltstructureInputsorthorhombic\n", + "\n", + "orthorhombic\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsorthorhombic->clusterwith_prebuiltstructureInputsorthorhombic\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputscubic\n", + "\n", + "cubic\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltstructureInputscubic\n", + "\n", + "cubic\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputscubic->clusterwith_prebuiltstructureInputscubic\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "clusterwith_prebuiltInputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcInputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputsn_ionic_steps->clusterwith_prebuiltcalcInputsn_ionic_steps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcInputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputsn_print->clusterwith_prebuiltcalcInputsn_print\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputstemperature\n", - "\n", - "temperature\n", + "\n", + "temperature\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcInputstemperature\n", - "\n", - "temperature\n", + "\n", + "temperature\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputstemperature->clusterwith_prebuiltcalcInputstemperature\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcInputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputspressure->clusterwith_prebuiltcalcInputspressure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsenergy_pot\n", - "\n", - "energy_pot\n", + "\n", + "energy_pot\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsfig\n", - "\n", - "fig\n", + "\n", + "fig\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltstructureInputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltstructureOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltstructureOutputsstructure\n", - "\n", - "structure: Atoms\n", + "\n", + "structure\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltengineInputsstructure\n", - "\n", - "structure: Optional\n", + "\n", + "structure: Optional\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureOutputsstructure->clusterwith_prebuiltengineInputsstructure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltengineInputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltengineOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltengineOutputsjob\n", - "\n", - "job: Lammps\n", + "\n", + "job: Lammps\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcInputsjob\n", - "\n", - "job: AtomisticGenericJob\n", + "\n", + "job: AtomisticGenericJob\n", "\n", "\n", "\n", "clusterwith_prebuiltengineOutputsjob->clusterwith_prebuiltcalcInputsjob\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcInputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputscells->clusterwith_prebuiltOutputscells\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsdisplacements->clusterwith_prebuiltOutputsdisplacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsenergy_pot\n", - "\n", - "energy_pot\n", + "\n", + "energy_pot\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsenergy_pot->clusterwith_prebuiltOutputsenergy_pot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsenergy_tot->clusterwith_prebuiltOutputsenergy_tot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsforce_max->clusterwith_prebuiltOutputsforce_max\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsforces->clusterwith_prebuiltOutputsforces\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsindices->clusterwith_prebuiltOutputsindices\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputspositions->clusterwith_prebuiltOutputspositions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputspressures->clusterwith_prebuiltOutputspressures\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltplotInputsx\n", - "\n", - "x: Union\n", + "\n", + "x: Union\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputssteps->clusterwith_prebuiltplotInputsx\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputstemperature\n", - "\n", - "temperature\n", + "\n", + "temperature\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltplotInputsy\n", - "\n", - "y: Union\n", + "\n", + "y: Union\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputstemperature->clusterwith_prebuiltplotInputsy\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputstotal_displacements->clusterwith_prebuiltOutputstotal_displacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsunwrapped_positions->clusterwith_prebuiltOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsvolume->clusterwith_prebuiltOutputsvolume\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltplotInputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltplotOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltplotOutputsfig\n", - "\n", - "fig\n", + "\n", + "fig\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltplotOutputsfig->clusterwith_prebuiltOutputsfig\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 34, @@ -1872,45 +1977,55 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label element to the io key structure_element\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label name to the io key structure_name\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key structure_crystalstructure\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key structure_a\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label cubic to the io key structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key structure_c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label repeat to the io key structure_repeat\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key structure_covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key structure_u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label n_print to the io key calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key structure_orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label temperature to the io key calc_temperature\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key structure_cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label pressure to the io key calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc_n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label cells to the io key calc_cells\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key calc_n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label displacements to the io key calc_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label temperature to the io key calc_temperature\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label energy_pot to the io key calc_energy_pot\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key calc_pressure\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label energy_tot to the io key calc_energy_tot\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key calc_cells\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label force_max to the io key calc_force_max\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key calc_displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label forces to the io key calc_forces\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_pot to the io key calc_energy_pot\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label indices to the io key calc_indices\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key calc_energy_tot\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label positions to the io key calc_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key calc_force_max\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label pressures to the io key calc_pressures\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key calc_forces\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label total_displacements to the io key calc_total_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key calc_indices\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc_unwrapped_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key calc_positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label volume to the io key calc_volume\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key calc_pressures\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:80: UserWarning: Assigning a channel with the label fig to the io key plot_fig\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key calc_total_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc_unwrapped_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key calc_volume\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label fig to the io key plot_fig\n", " warn(\n" ] }, @@ -1932,12 +2047,12 @@ "\n", "clusterwith_prebuiltInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", @@ -1958,128 +2073,158 @@ "run\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsran\n", "\n", "ran\n", "\n", "\n", - "\n", + "\n", "\n", - "clusterwith_prebuiltInputselement\n", - "\n", - "element: str\n", + "clusterwith_prebuiltInputsname\n", + "\n", + "name\n", "\n", - "\n", + "\n", "\n", - "clusterwith_prebuiltInputscubic\n", - "\n", - "cubic: bool\n", + "clusterwith_prebuiltInputscrystalstructure\n", + "\n", + "crystalstructure\n", "\n", - "\n", + "\n", "\n", - "clusterwith_prebuiltInputsrepeat\n", - "\n", - "repeat: int\n", + "clusterwith_prebuiltInputsa\n", + "\n", + "a\n", "\n", - "\n", + "\n", "\n", + "clusterwith_prebuiltInputsc\n", + "\n", + "c\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputscovera\n", + "\n", + "covera\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsu\n", + "\n", + "u\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputsorthorhombic\n", + "\n", + "orthorhombic\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltInputscubic\n", + "\n", + "cubic\n", + "\n", + "\n", + "\n", "clusterwith_prebuiltInputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputstemperature\n", - "\n", - "temperature\n", + "\n", + "temperature\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputscells\n", "\n", "cells\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsdisplacements\n", "\n", "displacements\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsenergy_pot\n", "\n", "energy_pot\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsenergy_tot\n", "\n", "energy_tot\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsforce_max\n", "\n", "force_max\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsforces\n", "\n", "forces\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsindices\n", "\n", "indices\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputspositions\n", "\n", "positions\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputspressures\n", "\n", "pressures\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputstotal_displacements\n", "\n", "total_displacements\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsunwrapped_positions\n", "\n", "unwrapped_positions\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsvolume\n", "\n", "volume\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltOutputsfig\n", "\n", "fig\n", @@ -2088,7 +2233,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 35, @@ -2099,6 +2244,1891 @@ "source": [ "wf.draw(depth=0)" ] + }, + { + "cell_type": "markdown", + "id": "d1f3b308-28b2-466b-8cf5-6bfd806c08ca", + "metadata": {}, + "source": [ + "# Macros\n", + "\n", + "Once you have a workflow that you're happy with, you may want to store it as a macro so it can be stored in a human-readable way, reused, and shared. Automated conversion of an existing `Workflow` instance into a `Macro` subclass is still on the TODO list, but defining a new macro is pretty easy: they are just composite nodes that have a function defining their graph setup:" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "c71a8308-f8a1-4041-bea0-1c841e072a6d", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_contrib.workflow.macro import Macro" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "2b9bb21a-73cd-444e-84a9-100e202aa422", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label x to the io key add_one_x\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label result to the io key add_three_result\n", + " warn(\n" + ] + }, + { + "data": { + "text/plain": [ + "13" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "@Workflow.wrap_as.single_value_node()\n", + "def add_one(x):\n", + " result = x + 1\n", + " return result\n", + "\n", + "def add_three_macro(macro: Macro) -> None:\n", + " \"\"\"\n", + " The graph constructor a Macro expects must take the macro as its only argument\n", + " (i.e. \"self\" from the macro's perspective) and return nothing.\n", + " Inside, it should add nodes to the macro, wire their connections, etc.\n", + " \"\"\"\n", + " macro.add_one = add_one(0)\n", + " macro.add_two = add_one(macro.add_one)\n", + " macro.add_three = add_one(macro.add_two)\n", + " macro.starting_nodes = [macro.add_one] \n", + " # Setting this starting node is silly, since as the head-most node it would \n", + " # have been the starting node anyway; the point is you have access to the \n", + " # macro object and can do these sorts of setup proceedures here\n", + " \n", + "macro = Macro(add_three_macro)\n", + "macro(add_one_x=10).add_three_result" + ] + }, + { + "cell_type": "markdown", + "id": "bd5099c4-1c01-4a45-a5bb-e5087595db9f", + "metadata": {}, + "source": [ + "Of course, we can also use a decorator like for other node types. This is shown below, along with an example of how exploit label maps to give our macro IO easier-to-use names (and expose IO that would be skipped by default because it's internally connected):" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "3668f9a9-adca-48a4-84ea-13add965897c", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label result to the io key intermediate\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label result to the io key plus_three\n", + " warn(\n" + ] + }, + { + "data": { + "text/plain": [ + "{'intermediate': 102, 'plus_three': 103}" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "@Workflow.wrap_as.macro_node()\n", + "def add_three_macro(macro: Macro) -> None:\n", + " \"\"\"\n", + " The graph constructor a Macro expects must take the macro as its only argument\n", + " (i.e. \"self\" from the macro's perspective) and return nothing.\n", + " Inside, it should add nodes to the macro, wire their connections, etc.\n", + " \"\"\"\n", + " macro.add_one = add_one(0)\n", + " macro.add_two = add_one(macro.add_one)\n", + " macro.add_three = add_one(macro.add_two)\n", + " macro.inputs_map = {\"add_one_x\": \"x\"}\n", + " macro.outputs_map = {\"add_three_result\": \"plus_three\", \"add_two_result\": \"intermediate\"}\n", + " \n", + "macro = add_three_macro(x=100)\n", + "macro.outputs.to_value_dict()" + ] + }, + { + "cell_type": "markdown", + "id": "22d2fdcf-0206-497d-9344-a71e3472a2c0", + "metadata": {}, + "source": [ + "## Nesting\n", + "\n", + "Composite nodes can be nested to abstract workflows into simpler components -- i.e. macros can be added to workflows, and macros can be used inside of macros.\n", + "\n", + "For our final example, let's define a macro for doing Lammps minimizations, then use this in a workflow to compare energies between different phases." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "9aaeeec0-5f88-4c94-a6cc-45b56d2f0111", + "metadata": {}, + "outputs": [], + "source": [ + "@Workflow.wrap_as.macro_node()\n", + "def lammps_minimize(macro):\n", + " macro.structure = macro.add.atomistics.Bulk(run_on_updates=False)\n", + " macro.engine = macro.add.atomistics.Lammps(structure=macro.structure, run_on_updates=False)\n", + " macro.calc = macro.add.atomistics.CalcMin(job=macro.engine, pressure=0)\n", + " \n", + " macro.engine.signals.input.run = macro.structure.signals.output.ran\n", + " macro.calc.signals.input.run = macro.engine.signals.output.ran\n", + " \n", + " macro.inputs_map = {\n", + " \"structure_name\": \"element\", \n", + " \"structure_crystalstructure\": \"crystalstructure\",\n", + " \"structure_a\": \"lattice_guess\",\n", + " }\n", + " macro.outputs_map = {\n", + " \"calc_energy_pot\": \"energy\",\n", + " \"structure_structure\": \"structure\",\n", + " }\n", + " \n", + " macro.starting_nodes = [macro.structure]\n", + " # Note: Because we'll be embedding this macro in a workflow, NONE\n", + " # of its nodes will have totally unconnected input, and therefore \n", + " # do not register as \"upstream-most\", so we _need_ to specify the \n", + " # starting nodes. This is a bug, not a feature -- the macro should\n", + " # only be looking for _internal_ connections when determining \n", + " # upstream-most-ness\n", + "\n", + "@Workflow.wrap_as.single_value_node()\n", + "def per_atom_energy_difference(structure1, energy1, structure2, energy2):\n", + " de = (energy2[-1]/len(structure2)) - (energy1[-1]/len(structure1))\n", + " return de" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "a832e552-b3cc-411a-a258-ef21574fc439", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:263: UserWarning: Reassigning the node user_input to the label element when adding it to the parent phase_preference.\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:263: UserWarning: Reassigning the node bulk to the label structure when adding it to the parent lammps_minimize.\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:263: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent lammps_minimize.\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:263: UserWarning: Reassigning the node calc_min to the label calc when adding it to the parent lammps_minimize.\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label name to the io key element\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key structure_c\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key structure_covera\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key structure_u\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key structure_orthorhombic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key structure_cubic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc_n_ionic_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key calc_n_print\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key calc_pressure\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key calc_cells\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key calc_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_pot to the io key energy\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key calc_energy_tot\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key calc_force_max\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key calc_forces\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key calc_indices\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key calc_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key calc_pressures\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key calc_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key calc_total_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc_unwrapped_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key calc_volume\n", + " warn(\n" + ] + } + ], + "source": [ + "wf = Workflow(\"phase_preference\")\n", + "wf.element = wf.add.standard.UserInput()\n", + "wf.min_phase1 = lammps_minimize(element=wf.element)\n", + "wf.min_phase2 = lammps_minimize(element=wf.element)\n", + "wf.compare = per_atom_energy_difference(\n", + " wf.min_phase1.outputs.structure,\n", + " wf.min_phase1.outputs.energy,\n", + " wf.min_phase2.outputs.structure,\n", + " wf.min_phase2.outputs.energy,\n", + ")\n", + "\n", + "wf.min_phase1.signals.input.run = wf.element.signals.output.ran\n", + "wf.min_phase2.signals.input.run = wf.element.signals.output.ran\n", + "# We stopped all the elements inside lammps_minimize from running on update\n", + "# So we'll need to hit the macro with an explicit run command\n", + "\n", + "wf.inputs_map = {\n", + " \"element_user_input\": \"element\",\n", + " \"min_phase1_crystalstructure\": \"phase1\",\n", + " \"min_phase2_crystalstructure\": \"phase2\",\n", + " \"min_phase1_lattice_guess\": \"lattice_guess1\",\n", + " \"min_phase2_lattice_guess\": \"lattice_guess2\",\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "b764a447-236f-4cb7-952a-7cba4855087d", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label user_input to the io key element\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase1\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1_structure_c\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1_structure_covera\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1_structure_u\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1_structure_orthorhombic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1_structure_cubic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1_calc_n_ionic_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1_calc_n_print\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1_calc_pressure\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2_structure_c\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2_structure_covera\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2_structure_u\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2_structure_orthorhombic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2_structure_cubic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2_calc_n_ionic_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2_calc_n_print\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2_calc_pressure\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase1_calc_cells\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase1_calc_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase1_calc_energy_tot\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase1_calc_force_max\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase1_calc_forces\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase1_calc_indices\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase1_calc_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase1_calc_pressures\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase1_calc_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase1_calc_total_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase1_calc_unwrapped_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase1_calc_volume\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase2_calc_cells\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase2_calc_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase2_calc_energy_tot\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase2_calc_force_max\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase2_calc_forces\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase2_calc_indices\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase2_calc_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase2_calc_pressures\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase2_calc_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase2_calc_total_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase2_calc_unwrapped_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase2_calc_volume\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label de to the io key compare_de\n", + " warn(\n" + ] + }, + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preference\n", + "\n", + "phase_preference: Workflow\n", + "\n", + "clusterphase_preferenceInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterphase_preferenceOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "clusterphase_preferenceelement\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "element: UserInput\n", + "\n", + "\n", + "clusterphase_preferenceelementInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterphase_preferenceelementOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "min_phase1: LammpsMinimize\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Inputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "min_phase2: LammpsMinimize\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Inputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "clusterphase_preferencecompare\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "compare: PerAtomEnergyDifference\n", + "\n", + "\n", + "clusterphase_preferencecompareInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterphase_preferencecompareOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsuser_input\n", + "\n", + "user_input\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceelementInputsuser_input\n", + "\n", + "user_input\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsuser_input->clusterphase_preferenceelementInputsuser_input\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputscrystalstructure\n", + "\n", + "crystalstructure\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Inputscrystalstructure\n", + "\n", + "crystalstructure\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputscrystalstructure->clusterphase_preferencemin_phase1Inputscrystalstructure\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Inputscrystalstructure\n", + "\n", + "crystalstructure\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputscrystalstructure->clusterphase_preferencemin_phase2Inputscrystalstructure\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsa\n", + "\n", + "a\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Inputsa\n", + "\n", + "a\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsa->clusterphase_preferencemin_phase1Inputsa\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Inputsa\n", + "\n", + "a\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsa->clusterphase_preferencemin_phase2Inputsa\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsc\n", + "\n", + "c\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Inputsc\n", + "\n", + "c\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsc->clusterphase_preferencemin_phase1Inputsc\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Inputsc\n", + "\n", + "c\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsc->clusterphase_preferencemin_phase2Inputsc\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputscovera\n", + "\n", + "covera\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Inputscovera\n", + "\n", + "covera\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputscovera->clusterphase_preferencemin_phase1Inputscovera\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Inputscovera\n", + "\n", + "covera\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputscovera->clusterphase_preferencemin_phase2Inputscovera\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsu\n", + "\n", + "u\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Inputsu\n", + "\n", + "u\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsu->clusterphase_preferencemin_phase1Inputsu\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Inputsu\n", + "\n", + "u\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsu->clusterphase_preferencemin_phase2Inputsu\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsorthorhombic\n", + "\n", + "orthorhombic\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Inputsorthorhombic\n", + "\n", + "orthorhombic\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsorthorhombic->clusterphase_preferencemin_phase1Inputsorthorhombic\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Inputsorthorhombic\n", + "\n", + "orthorhombic\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsorthorhombic->clusterphase_preferencemin_phase2Inputsorthorhombic\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputscubic\n", + "\n", + "cubic\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Inputscubic\n", + "\n", + "cubic\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputscubic->clusterphase_preferencemin_phase1Inputscubic\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Inputscubic\n", + "\n", + "cubic\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputscubic->clusterphase_preferencemin_phase2Inputscubic\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsn_ionic_steps\n", + "\n", + "n_ionic_steps: int\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Inputsn_ionic_steps\n", + "\n", + "n_ionic_steps: int\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsn_ionic_steps->clusterphase_preferencemin_phase1Inputsn_ionic_steps\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Inputsn_ionic_steps\n", + "\n", + "n_ionic_steps: int\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsn_ionic_steps->clusterphase_preferencemin_phase2Inputsn_ionic_steps\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsn_print\n", + "\n", + "n_print: int\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Inputsn_print\n", + "\n", + "n_print: int\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsn_print->clusterphase_preferencemin_phase1Inputsn_print\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Inputsn_print\n", + "\n", + "n_print: int\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputsn_print->clusterphase_preferencemin_phase2Inputsn_print\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputspressure\n", + "\n", + "pressure\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Inputspressure\n", + "\n", + "pressure\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputspressure->clusterphase_preferencemin_phase1Inputspressure\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Inputspressure\n", + "\n", + "pressure\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceInputspressure->clusterphase_preferencemin_phase2Inputspressure\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceOutputscells\n", + "\n", + "cells\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceOutputsdisplacements\n", + "\n", + "displacements\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceOutputsenergy_tot\n", + "\n", + "energy_tot\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceOutputsforce_max\n", + "\n", + "force_max\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceOutputsforces\n", + "\n", + "forces\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceOutputsindices\n", + "\n", + "indices\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceOutputspositions\n", + "\n", + "positions\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceOutputspressures\n", + "\n", + "pressures\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceOutputssteps\n", + "\n", + "steps\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceOutputstotal_displacements\n", + "\n", + "total_displacements\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceOutputsunwrapped_positions\n", + "\n", + "unwrapped_positions\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceOutputsvolume\n", + "\n", + "volume\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceOutputsde\n", + "\n", + "de\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceelementInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceelementOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Inputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceelementOutputsran->clusterphase_preferencemin_phase1Inputsrun\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Inputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceelementOutputsran->clusterphase_preferencemin_phase2Inputsrun\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceelementOutputsuser_input\n", + "\n", + "user_input\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Inputsname\n", + "\n", + "name\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceelementOutputsuser_input->clusterphase_preferencemin_phase1Inputsname\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Inputsname\n", + "\n", + "name\n", + "\n", + "\n", + "\n", + "clusterphase_preferenceelementOutputsuser_input->clusterphase_preferencemin_phase2Inputsname\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsstructure\n", + "\n", + "structure\n", + "\n", + "\n", + "\n", + "clusterphase_preferencecompareInputsstructure1\n", + "\n", + "structure1\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsstructure->clusterphase_preferencecompareInputsstructure1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputscells\n", + "\n", + "cells\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputscells->clusterphase_preferenceOutputscells\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsdisplacements\n", + "\n", + "displacements\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsdisplacements->clusterphase_preferenceOutputsdisplacements\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsenergy_pot\n", + "\n", + "energy_pot\n", + "\n", + "\n", + "\n", + "clusterphase_preferencecompareInputsenergy1\n", + "\n", + "energy1\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsenergy_pot->clusterphase_preferencecompareInputsenergy1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsenergy_tot\n", + "\n", + "energy_tot\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsenergy_tot->clusterphase_preferenceOutputsenergy_tot\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsforce_max\n", + "\n", + "force_max\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsforce_max->clusterphase_preferenceOutputsforce_max\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsforces\n", + "\n", + "forces\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsforces->clusterphase_preferenceOutputsforces\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsindices\n", + "\n", + "indices\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsindices->clusterphase_preferenceOutputsindices\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputspositions\n", + "\n", + "positions\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputspositions->clusterphase_preferenceOutputspositions\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputspressures\n", + "\n", + "pressures\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputspressures->clusterphase_preferenceOutputspressures\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputssteps\n", + "\n", + "steps\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputssteps->clusterphase_preferenceOutputssteps\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputstotal_displacements\n", + "\n", + "total_displacements\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputstotal_displacements->clusterphase_preferenceOutputstotal_displacements\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsunwrapped_positions\n", + "\n", + "unwrapped_positions\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsunwrapped_positions->clusterphase_preferenceOutputsunwrapped_positions\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsvolume\n", + "\n", + "volume\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsvolume->clusterphase_preferenceOutputsvolume\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsstructure\n", + "\n", + "structure\n", + "\n", + "\n", + "\n", + "clusterphase_preferencecompareInputsstructure2\n", + "\n", + "structure2\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsstructure->clusterphase_preferencecompareInputsstructure2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputscells\n", + "\n", + "cells\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputscells->clusterphase_preferenceOutputscells\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsdisplacements\n", + "\n", + "displacements\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsdisplacements->clusterphase_preferenceOutputsdisplacements\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsenergy_pot\n", + "\n", + "energy_pot\n", + "\n", + "\n", + "\n", + "clusterphase_preferencecompareInputsenergy2\n", + "\n", + "energy2\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsenergy_pot->clusterphase_preferencecompareInputsenergy2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsenergy_tot\n", + "\n", + "energy_tot\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsenergy_tot->clusterphase_preferenceOutputsenergy_tot\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsforce_max\n", + "\n", + "force_max\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsforce_max->clusterphase_preferenceOutputsforce_max\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsforces\n", + "\n", + "forces\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsforces->clusterphase_preferenceOutputsforces\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsindices\n", + "\n", + "indices\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsindices->clusterphase_preferenceOutputsindices\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputspositions\n", + "\n", + "positions\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputspositions->clusterphase_preferenceOutputspositions\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputspressures\n", + "\n", + "pressures\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputspressures->clusterphase_preferenceOutputspressures\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputssteps\n", + "\n", + "steps\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputssteps->clusterphase_preferenceOutputssteps\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputstotal_displacements\n", + "\n", + "total_displacements\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputstotal_displacements->clusterphase_preferenceOutputstotal_displacements\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsunwrapped_positions\n", + "\n", + "unwrapped_positions\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsunwrapped_positions->clusterphase_preferenceOutputsunwrapped_positions\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsvolume\n", + "\n", + "volume\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsvolume->clusterphase_preferenceOutputsvolume\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencecompareInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterphase_preferencecompareOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencecompareOutputsde\n", + "\n", + "de\n", + "\n", + "\n", + "\n", + "clusterphase_preferencecompareOutputsde->clusterphase_preferenceOutputsde\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "wf.draw()" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "b51bef25-86c5-4d57-80c1-ab733e703caf", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label user_input to the io key element\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase1\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1_structure_c\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1_structure_covera\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1_structure_u\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1_structure_orthorhombic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1_structure_cubic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1_calc_n_ionic_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1_calc_n_print\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1_calc_pressure\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2_structure_c\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2_structure_covera\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2_structure_u\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2_structure_orthorhombic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2_structure_cubic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2_calc_n_ionic_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2_calc_n_print\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2_calc_pressure\n", + " warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The job JUSTAJOBNAME was saved and received the ID: 9558\n", + "The job JUSTAJOBNAME was saved and received the ID: 9558\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label user_input to the io key element\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase1\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1_structure_c\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1_structure_covera\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1_structure_u\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1_structure_orthorhombic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1_structure_cubic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1_calc_n_ionic_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1_calc_n_print\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1_calc_pressure\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2_structure_c\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2_structure_covera\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2_structure_u\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2_structure_orthorhombic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2_structure_cubic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2_calc_n_ionic_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2_calc_n_print\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2_calc_pressure\n", + " warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The job JUSTAJOBNAME was saved and received the ID: 9558\n", + "The job JUSTAJOBNAME was saved and received the ID: 9558\n", + "Al: E(hcp) - E(fcc) = 1.17 eV/atom\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase1_calc_cells\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase1_calc_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase1_calc_energy_tot\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase1_calc_force_max\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase1_calc_forces\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase1_calc_indices\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase1_calc_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase1_calc_pressures\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase1_calc_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase1_calc_total_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase1_calc_unwrapped_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase1_calc_volume\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase2_calc_cells\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase2_calc_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase2_calc_energy_tot\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase2_calc_force_max\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase2_calc_forces\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase2_calc_indices\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase2_calc_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase2_calc_pressures\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase2_calc_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase2_calc_total_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase2_calc_unwrapped_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase2_calc_volume\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label de to the io key compare_de\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label user_input to the io key element\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase1\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1_structure_c\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1_structure_covera\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1_structure_u\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1_structure_orthorhombic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1_structure_cubic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1_calc_n_ionic_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1_calc_n_print\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1_calc_pressure\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2_structure_c\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2_structure_covera\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2_structure_u\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2_structure_orthorhombic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2_structure_cubic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2_calc_n_ionic_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2_calc_n_print\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2_calc_pressure\n", + " warn(\n" + ] + } + ], + "source": [ + "out = wf(element=\"Al\", phase1=\"fcc\", phase2=\"hcp\", lattice_guess1=4, lattice_guess2=4)\n", + "print(f\"{wf.inputs.element.value}: E({wf.inputs.phase2.value}) - E({wf.inputs.phase1.value}) = {out.compare_de:.2f} eV/atom\")" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "091e2386-0081-436c-a736-23d019bd9b91", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The job JUSTAJOBNAME was saved and received the ID: 9558\n", + "The job JUSTAJOBNAME was saved and received the ID: 9558\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label user_input to the io key element\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase1\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1_structure_c\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1_structure_covera\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1_structure_u\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1_structure_orthorhombic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1_structure_cubic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1_calc_n_ionic_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1_calc_n_print\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1_calc_pressure\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2_structure_c\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2_structure_covera\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2_structure_u\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2_structure_orthorhombic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2_structure_cubic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2_calc_n_ionic_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2_calc_n_print\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2_calc_pressure\n", + " warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The job JUSTAJOBNAME was saved and received the ID: 9558\n", + "The job JUSTAJOBNAME was saved and received the ID: 9558\n", + "Mg: E(hcp) - E(fcc) = -4.54 eV/atom\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase1_calc_cells\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase1_calc_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase1_calc_energy_tot\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase1_calc_force_max\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase1_calc_forces\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase1_calc_indices\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase1_calc_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase1_calc_pressures\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase1_calc_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase1_calc_total_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase1_calc_unwrapped_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase1_calc_volume\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase2_calc_cells\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase2_calc_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase2_calc_energy_tot\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase2_calc_force_max\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase2_calc_forces\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase2_calc_indices\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase2_calc_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase2_calc_pressures\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase2_calc_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase2_calc_total_displacements\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase2_calc_unwrapped_positions\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase2_calc_volume\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label de to the io key compare_de\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label user_input to the io key element\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase1\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1_structure_c\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1_structure_covera\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1_structure_u\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1_structure_orthorhombic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1_structure_cubic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1_calc_n_ionic_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1_calc_n_print\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1_calc_pressure\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2_structure_c\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2_structure_covera\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2_structure_u\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2_structure_orthorhombic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2_structure_cubic\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2_calc_n_ionic_steps\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2_calc_n_print\n", + " warn(\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2_calc_pressure\n", + " warn(\n" + ] + } + ], + "source": [ + "out = wf(element=\"Mg\", phase1=\"fcc\", phase2=\"hcp\", lattice_guess1=3, lattice_guess2=3)\n", + "print(f\"{wf.inputs.element.value}: E({wf.inputs.phase2.value}) - E({wf.inputs.phase1.value}) = {out.compare_de:.2f} eV/atom\")" + ] } ], "metadata": { From 57abc4ecf5459f750bd9cc94c8bc81d1b9c35df7 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 4 Aug 2023 16:35:35 -0700 Subject: [PATCH 535/756] Propagate changes to the structure node to workflow tests --- tests/unit/workflow/test_workflow.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index c64e85c3e..53f91a04d 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -68,13 +68,13 @@ def test_node_packages(self): wf = Workflow("my_workflow") # Test invocation - wf.add.atomistics.BulkStructure(repeat=3, cubic=True, element="Al") + wf.add.atomistics.Bulk(cubic=True, element="Al") # Test invocation with attribute assignment - wf.engine = wf.add.atomistics.Lammps(structure=wf.bulk_structure) + wf.engine = wf.add.atomistics.Lammps(structure=wf.bulk) self.assertSetEqual( set(wf.nodes.keys()), - set(["bulk_structure", "engine"]), + set(["bulk", "engine"]), msg=f"Expected one node label generated automatically from the class and " f"the other from the attribute assignment, but got {wf.nodes.keys()}" ) From 412b567315bd5786daa8118e7db3bca5e3caeec5 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Fri, 4 Aug 2023 23:36:24 +0000 Subject: [PATCH 536/756] Format black --- pyiron_contrib/workflow/composite.py | 9 +++++---- pyiron_contrib/workflow/macro.py | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 7dd474297..4aa56707a 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -43,6 +43,7 @@ def macro_node(cls): # but it does what I want, so I'm going to use it anyhow if cls._macro_node is None: from pyiron_contrib.workflow.macro import macro_node + cls._macro_node = macro_node return cls._macro_node @@ -168,10 +169,10 @@ def run_args(self) -> dict: return {"self": self} def _build_io( - self, - io: Inputs | Outputs, - target: Literal["inputs", "outputs"], - key_map: dict[str, str] | None + self, + io: Inputs | Outputs, + target: Literal["inputs", "outputs"], + key_map: dict[str, str] | None, ) -> Inputs | Outputs: key_map = {} if key_map is None else key_map for node in self.nodes.values(): diff --git a/pyiron_contrib/workflow/macro.py b/pyiron_contrib/workflow/macro.py index b90570d66..40bafdbf9 100644 --- a/pyiron_contrib/workflow/macro.py +++ b/pyiron_contrib/workflow/macro.py @@ -110,16 +110,16 @@ class Macro(Composite): """ def __init__( - self, - graph_creator: callable[[Macro], None], - label: Optional[str] = None, - run_on_updates: bool = True, - update_on_instantiation: bool = True, - parent: Optional[Composite] = None, - strict_naming: bool = True, - inputs_map: Optional[dict] = None, - outputs_map: Optional[dict] = None, - **kwargs, + self, + graph_creator: callable[[Macro], None], + label: Optional[str] = None, + run_on_updates: bool = True, + update_on_instantiation: bool = True, + parent: Optional[Composite] = None, + strict_naming: bool = True, + inputs_map: Optional[dict] = None, + outputs_map: Optional[dict] = None, + **kwargs, ): self._parent = None super().__init__( From 8a4ec97cab747cbe6dfb7a3fdda2b0564330f3c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 11:20:23 +0000 Subject: [PATCH 537/756] Bump pympipool from 0.6.1 to 0.6.2 Bumps [pympipool](https://github.com/jan-janssen/pympipool) from 0.6.1 to 0.6.2. - [Release notes](https://github.com/jan-janssen/pympipool/releases) - [Commits](https://github.com/jan-janssen/pympipool/compare/pympipool-0.6.1...pympipool-0.6.2) --- updated-dependencies: - dependency-name: pympipool dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b49099570..943f8717d 100644 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ ], 'tinybase': [ 'distributed==2023.7.1', - 'pympipool==0.6.1' + 'pympipool==0.6.2' ] }, cmdclass=versioneer.get_cmdclass(), From 20df10bdb3963d9695315187de7b90a884c17e82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 11:20:43 +0000 Subject: [PATCH 538/756] Bump boto3 from 1.28.15 to 1.28.20 Bumps [boto3](https://github.com/boto/boto3) from 1.28.15 to 1.28.20. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.28.15...1.28.20) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b49099570..90824b5ea 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ ], 'image': ['scikit-image==0.21.0'], 'generic': [ - 'boto3==1.28.15', + 'boto3==1.28.20', 'moto==4.1.14' ], 'workflow': [ From 62f8e6bbbda8e47c4476a99083ae281222ae00dc Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 7 Aug 2023 11:20:50 +0000 Subject: [PATCH 539/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 26e986b19..707b5d4d1 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -20,5 +20,5 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.1.0 - aws-sam-translator =1.71.0 -- pympipool =0.6.1 +- pympipool =0.6.2 - distributed =2023.7.1 From 8567eb9989a3c48fba668102dd2aac3df8d7c40d Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 7 Aug 2023 11:21:03 +0000 Subject: [PATCH 540/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 26e986b19..360ec0866 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 -- boto3 =1.28.15 +- boto3 =1.28.20 - moto =4.1.14 - pycp2k =0.2.2 - typeguard =4.1.0 From 185bc6535f1276001f4ec429c23adaad80d537ce Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 7 Aug 2023 11:21:20 +0000 Subject: [PATCH 541/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 9589b54d0..18ceedc3a 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -20,7 +20,7 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.1.0 - aws-sam-translator =1.71.0 -- pympipool =0.6.1 +- pympipool =0.6.2 - distributed =2023.7.1 - python >= 3.10 - lammps diff --git a/docs/environment.yml b/docs/environment.yml index 2e5f91857..19b99d741 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -22,5 +22,5 @@ dependencies: - pycp2k =0.2.2 - typeguard =4.1.0 - aws-sam-translator =1.71.0 -- pympipool =0.6.1 +- pympipool =0.6.2 - distributed =2023.7.1 From a8432e84fb29773ff92fe581fdc77d344a667621 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 7 Aug 2023 11:21:32 +0000 Subject: [PATCH 542/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 9589b54d0..a92a6abeb 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 -- boto3 =1.28.15 +- boto3 =1.28.20 - moto =4.1.14 - pycp2k =0.2.2 - typeguard =4.1.0 diff --git a/docs/environment.yml b/docs/environment.yml index 2e5f91857..6d849759b 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -17,7 +17,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 -- boto3 =1.28.15 +- boto3 =1.28.20 - moto =4.1.14 - pycp2k =0.2.2 - typeguard =4.1.0 From 64c59532c85f829ef5f12f2e551ee389b98607af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 14:39:10 +0000 Subject: [PATCH 543/756] Bump distributed from 2023.7.1 to 2023.8.0 Bumps [distributed](https://github.com/dask/distributed) from 2023.7.1 to 2023.8.0. - [Changelog](https://github.com/dask/distributed/blob/main/docs/release-procedure.md) - [Commits](https://github.com/dask/distributed/compare/2023.7.1...2023.8.0) --- updated-dependencies: - dependency-name: distributed dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a2a7e067f..bb3121bbb 100644 --- a/setup.py +++ b/setup.py @@ -61,7 +61,7 @@ 'typeguard==4.1.0' ], 'tinybase': [ - 'distributed==2023.7.1', + 'distributed==2023.8.0', 'pympipool==0.6.2' ] }, From 5a76debf7e45e350fdff052d94a51c61db32da24 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 7 Aug 2023 14:43:03 +0000 Subject: [PATCH 544/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 8604033da..ee1eb62ed 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -21,4 +21,4 @@ dependencies: - typeguard =4.1.0 - aws-sam-translator =1.71.0 - pympipool =0.6.2 -- distributed =2023.7.1 +- distributed =2023.8.0 From 71a51a45c2b9f5aafac42b15649be902be9f6620 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 7 Aug 2023 14:46:40 +0000 Subject: [PATCH 545/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index a4846519f..4aebe366e 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -21,6 +21,6 @@ dependencies: - typeguard =4.1.0 - aws-sam-translator =1.71.0 - pympipool =0.6.2 -- distributed =2023.7.1 +- distributed =2023.8.0 - python >= 3.10 - lammps diff --git a/docs/environment.yml b/docs/environment.yml index 5877c42f8..0b48b5806 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -23,4 +23,4 @@ dependencies: - typeguard =4.1.0 - aws-sam-translator =1.71.0 - pympipool =0.6.2 -- distributed =2023.7.1 +- distributed =2023.8.0 From 8ca1f17566104f1b421eb4f5dd73086c7c9ed00a Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 8 Aug 2023 10:41:35 -0700 Subject: [PATCH 546/756] Centralize color definitions following seaborn --- pyiron_contrib/workflow/composite.py | 4 ++-- pyiron_contrib/workflow/draw.py | 10 ++++++---- pyiron_contrib/workflow/function.py | 7 ++++--- pyiron_contrib/workflow/node.py | 3 ++- pyiron_contrib/workflow/util.py | 20 ++++++++++++++++++++ 5 files changed, 34 insertions(+), 10 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index c35fef6d8..d6833ecb6 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -22,7 +22,7 @@ ) from pyiron_contrib.workflow.node_library import atomistics, standard from pyiron_contrib.workflow.node_library.package import NodePackage -from pyiron_contrib.workflow.util import DotDict +from pyiron_contrib.workflow.util import DotDict, SeabornColors class _NodeDecoratorAccess: @@ -303,4 +303,4 @@ def register_nodes(self, domain: str, *nodes: list[type[Node]]): @property def color(self) -> str: """For drawing the graph""" - return "#8c564b" + return SeabornColors.brown diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py index 0767bd987..636b2cdb7 100644 --- a/pyiron_contrib/workflow/draw.py +++ b/pyiron_contrib/workflow/draw.py @@ -10,6 +10,8 @@ import graphviz from matplotlib.colors import to_hex, to_rgb +from pyiron_contrib.workflow.util import SeabornColors + if TYPE_CHECKING: from pyiron_contrib.workflow.channels import Channel as WorkflowChannel from pyiron_contrib.workflow.io import DataIO, SignalIO @@ -51,7 +53,7 @@ def blend_colours(color_a, color_b, fraction_a=0.5): def lighten_hex_color(color, lightness=0.7): """Blends the given hex code color with pure white.""" - return blend_colours("#ffffff", color, fraction_a=lightness) + return blend_colours(SeabornColors.white, color, fraction_a=lightness) class WorkflowGraphvizMap(ABC): @@ -141,7 +143,7 @@ def graph(self) -> graphviz.graphs.Digraph: class DataChannel(_Channel): @property def color(self) -> str: - return "#ff7f0e" + return SeabornColors.orange @property def shape(self) -> str: @@ -151,7 +153,7 @@ def shape(self) -> str: class SignalChannel(_Channel): @property def color(self) -> str: - return "#1f77b4" + return SeabornColors.blue @property def shape(self) -> str: @@ -212,7 +214,7 @@ def graph(self) -> graphviz.graphs.Digraph: @property def color(self) -> str: - return "#7f7f7f" + return SeabornColors.gray def __len__(self): return len(self.channels) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 9552a9c33..f6f40fd1f 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -10,6 +10,7 @@ from pyiron_contrib.workflow.io import Inputs, Outputs, Signals from pyiron_contrib.workflow.node import Node from pyiron_contrib.workflow.output_parser import ParseOutput +from pyiron_contrib.workflow.util import SeabornColors if TYPE_CHECKING: from pyiron_contrib.workflow.composite import Composite @@ -613,7 +614,7 @@ def to_dict(self): @property def color(self) -> str: """For drawing the graph""" - return "#2ca02c" + return SeabornColors.green class Slow(Function): @@ -649,7 +650,7 @@ def __init__( @property def color(self) -> str: """For drawing the graph""" - return "#d62728" + return SeabornColors.red class SingleValue(Function, HasChannel): @@ -705,7 +706,7 @@ def channel(self) -> OutputData: @property def color(self) -> str: """For drawing the graph""" - return "#17becf" + return SeabornColors.cyan def __getitem__(self, item): return self.single_value.__getitem__(item) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 3f181f072..e1e4b0267 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -15,6 +15,7 @@ from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.has_to_dict import HasToDict from pyiron_contrib.workflow.io import Signals, InputSignal, OutputSignal +from pyiron_contrib.workflow.util import SeabornColors if TYPE_CHECKING: import graphviz @@ -322,7 +323,7 @@ def __call__(self, **kwargs) -> None: @property def color(self) -> str: """A hex code color for use in drawing.""" - return "#ffffff" + return SeabornColors.white def draw( self, depth: int = 1, rankdir: Literal["LR", "TB"] = "LR" diff --git a/pyiron_contrib/workflow/util.py b/pyiron_contrib/workflow/util.py index 1ed77339e..b4c0041cd 100644 --- a/pyiron_contrib/workflow/util.py +++ b/pyiron_contrib/workflow/util.py @@ -7,3 +7,23 @@ def __setattr__(self, key, value): def __dir__(self): return set(super().__dir__() + list(self.keys())) + + +class SeabornColors: + """ + Hex codes for the ten `seaborn.color_palette()` colors (plus pure white and black), + recreated to avoid adding an entire dependency. + """ + + blue = "#1f77b4" + orange = "#ff7f0e" + green = "#2ca02c" + red = "#d62728" + purple = "#9467bd" + brown = "#8c564b" + pink = "#e377c2" + gray = "#7f7f7f" + olive = "#bcbd22" + cyan = "#17becf" + white = "#ffffff" + black = "#000000" From 2f0e2130b4d81071ef8128b5aebb1c03c7f28e63 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 8 Aug 2023 10:44:02 -0700 Subject: [PATCH 547/756] [bug] Define color property on the right class --- pyiron_contrib/workflow/composite.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index d6833ecb6..fed373d84 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -262,6 +262,11 @@ def __len__(self): def __dir__(self): return set(super().__dir__() + list(self.nodes.keys())) + @property + def color(self) -> str: + """For drawing the graph""" + return SeabornColors.brown + class NodeAdder: """ @@ -299,8 +304,3 @@ def register_nodes(self, domain: str, *nodes: list[type[Node]]): list, e.g. modules or even urls. """ setattr(self, domain, NodePackage(self._parent, *nodes)) - - @property - def color(self) -> str: - """For drawing the graph""" - return SeabornColors.brown From eb7a440b953ac405dfddb5281bfd1222c8de6e99 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 8 Aug 2023 11:25:45 -0700 Subject: [PATCH 548/756] [QoL] Add a convenience property for IO to see all its connections --- pyiron_contrib/workflow/io.py | 7 +++++++ tests/unit/workflow/test_io.py | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py index 12aa0aa51..897e3f481 100644 --- a/pyiron_contrib/workflow/io.py +++ b/pyiron_contrib/workflow/io.py @@ -107,6 +107,13 @@ def __getitem__(self, item) -> Channel: def __setitem__(self, key, value): self.__setattr__(key, value) + @property + def connections(self) -> list[Channel]: + """All the unique connections across all channels""" + return list(set( + [connection for channel in self for connection in channel.connections] + )) + @property def connected(self): return any([c.connected for c in self]) diff --git a/tests/unit/workflow/test_io.py b/tests/unit/workflow/test_io.py index 36e15a22f..3122669a9 100644 --- a/tests/unit/workflow/test_io.py +++ b/tests/unit/workflow/test_io.py @@ -91,4 +91,36 @@ def test_conversion(self): ) def test_iteration(self): - self.assertTrue(all([c.label in self.input.labels for c in self.input])) \ No newline at end of file + self.assertTrue(all([c.label in self.input.labels for c in self.input])) + + def test_connections_property(self): + self.assertEqual( + len(self.input.connections), + 0, + msg="Sanity check expectations about self.input" + ) + self.assertEqual( + len(self.output.connections), + 0, + msg="Sanity check expectations about self.input" + ) + + for inp in self.input: + inp.connect(self.output.a) + + self.assertEqual( + len(self.output.connections), + len(self.input), + msg="Expected to find all the channels in the input" + ) + self.assertEqual( + len(self.input.connections), + 1, + msg="Each unique connection should appear only once" + ) + self.assertIs( + self.input.connections[0], + self.input.x.connections[0], + msg="The IO connection found should be the same object as the channel " + "connection" + ) From f9e3bd69ee1c1d9f7fa320c8f590f72c125d8dd9 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 8 Aug 2023 13:34:17 -0700 Subject: [PATCH 549/756] Fix upstream check to depend only on whether the node in question gets input from elsewhere in its local (parent) scope. To do this more easily, I added some helper methods for checking if a node connects to any (recursive) child node of the composite. --- pyiron_contrib/workflow/composite.py | 51 ++++++++++++++++++- tests/unit/workflow/test_macro.py | 73 ++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 0f62c1fce..32f61cd4c 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -7,7 +7,7 @@ from abc import ABC from functools import partial -from typing import Literal, Optional +from typing import Literal, Optional, TYPE_CHECKING from warnings import warn from pyiron_contrib.executors import CloudpickleProcessPoolExecutor @@ -25,6 +25,9 @@ from pyiron_contrib.workflow.node_library.package import NodePackage from pyiron_contrib.workflow.util import DotDict, SeabornColors +if TYPE_CHECKING: + from pyiron_contrib.workflow.channels import Channel + class _NodeDecoratorAccess: """An intermediate container to store node-creating decorators as class methods.""" @@ -145,12 +148,56 @@ def to_dict(self): @property def upstream_nodes(self) -> list[Node]: + """ + A list of owned nodes that receive no input from any other owned nodes. + """ return [ node for node in self.nodes.values() - if node.outputs.connected and not node.inputs.connected + if not self.connects_to_input_of(node) ] + def has_locally_scoped_connection(self, node_connections: list[Channel]) -> bool: + """ + Check whether connections are made to any (recursively) owned nodes. + + Args: + node_connections [list[Channel]]: A list of connections. + + Returns: + (bool): Whether or not any of those connections are locally scoped to the + nodes owned by this composite node. + """ + return len( + set( + [connection.node for connection in node_connections] + ).intersection( + self.nodes.values() + ) + ) > 0 or any( + node.has_locally_scoped_connection(node_connections) + for node in self.nodes.values() + if isinstance(node, Composite) + ) + + def connects_to_output_of(self, node: Node) -> bool: + """ + Checks whether the passed node receives output from any of this composite node's + (recursively) owned nodes. + """ + return self.has_locally_scoped_connection( + node.outputs.connections + node.signals.output.connections + ) + + def connects_to_input_of(self, node: Node) -> bool: + """ + Checks whether the passed node receives input from any of this composite node's + (recursively) owned nodes. + """ + return self.has_locally_scoped_connection( + node.inputs.connections + node.signals.input.connections + ) + @property def on_run(self): return self.run_graph diff --git a/tests/unit/workflow/test_macro.py b/tests/unit/workflow/test_macro.py index a311ada1b..607933453 100644 --- a/tests/unit/workflow/test_macro.py +++ b/tests/unit/workflow/test_macro.py @@ -118,6 +118,79 @@ def nested_macro(macro): m = Macro(nested_macro) self.assertEqual(m(a_x=0).c_result, 5) + def test_upstream_detection(self): + def my_macro(macro): + macro.a = SingleValue(add_one, x=0, run_on_updates=False) + macro.b = SingleValue(add_one, x=macro.a, run_on_updates=False) + + m = Macro(my_macro) + self.assertTrue( + m.connects_to_input_of(m.b), + msg="b should have input from a" + ) + self.assertFalse( + m.connects_to_output_of(m.b), + msg="b should not show any local output connections" + ) + self.assertFalse( + m.connects_to_input_of(m.a), + msg="a should not show any local input connections" + ) + self.assertTrue( + m.connects_to_output_of(m.a), + msg="b should have input from a" + ) + self.assertEqual( + len(m.upstream_nodes), + 1, + msg="Only the a-node should have connected output but no connected input" + ) + self.assertIs(m.upstream_nodes[0], m.a) + + m2 = Macro(my_macro) + m.inputs.a_x = m2.outputs.b_result + self.assertIs( + m.upstream_nodes[0], + m.a, + msg="External connections should not impact upstream-ness" + ) + self.assertTrue( + m.connects_to_output_of(m2.b), + msg="Should be able to check if external nodes have local connections" + ) + + m.inputs.a_x = m.outputs.b_result # Infinite loop self-connection + self.assertEqual( + len(m.upstream_nodes), + 0, + msg="Internal connections _should_ impact upstream-ness" + ) + + m.b.disconnect() + self.assertEqual( + m.upstream_nodes[0], + m.a, + msg="After disconnecting the b-node, the a-node no longer has internal " + "input and should register as upstream again, regardless of whether its " + "output is connected to anything (which it isn't, since we fully " + "disconnected m.b)" + ) + + def deep_macro(macro): + macro.a = SingleValue(add_one, x=0, run_on_updates=False) + macro.m = Macro(my_macro) + macro.m.inputs.a_x = macro.a + + nested = Macro(deep_macro) + plain = Macro(my_macro) + # plain.inputs.a_x = nested.m.outputs.a_result + # self.assertTrue( + # nested.connects_to_input_of(plain), + # msg="A child of the nested macro has a connection to the plain macros" + # "input, so the entire nested macro should count as having a " + # "connection to the plain macro's input." + # ) + def test_custom_start(self): def modified_start_macro(macro): macro.a = SingleValue(add_one, x=0, run_on_updates=False) From 5e4e2e8151f25b1e30b3ea156edcd4e1519f6585 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 8 Aug 2023 13:37:58 -0700 Subject: [PATCH 550/756] Update example notebook Just a change to one of the comments --- notebooks/workflow_example.ipynb | 668 +++++++++++++++---------------- 1 file changed, 333 insertions(+), 335 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 571ca2b6e..7daf21e0e 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -11,7 +11,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "a2c8ea2bb05240b0b5813a6c12426893", + "model_id": "9b4519052d45432fa6e92b5cf667fc56", "version_major": 2, "version_minor": 0 }, @@ -716,7 +716,7 @@ "outputs": [ { "data": { - "image/png": "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359QwQAAYGnJtDugvb1ddXV1am1t1YYNG/T000+rqqpKfX19uvLKK2cdc+edd+q9997TM888o9/93d/V8PCwPvzwwwsuHgAApD6HZVmWnQHl5eUqKSlRW1vbTFthYaG2bt2qlpaWuP6dnZ360z/9U73zzjvKyclJqMhIJCKPx6NwOKzs7OyE3gMAACTXfH+/bS3TTE5OqqenR4FAIKY9EAiou7t71jE/+tGPVFpaqm9+85u64oordM011+ihhx7Sb37zmzk/Z2JiQpFIJOYAAABLk61lmpGREUWjUXm93ph2r9eroaGhWce88847Onr0qNxut374wx9qZGREX/nKV3T27Nk5rxtpaWlRc3OzndIAAECKSugCVofDEfPasqy4tmlTU1NyOBw6dOiQysrKdNttt2nPnj167rnn5pwdaWhoUDgcnjkGBwcTKRMAAKQAWzMjubm5cjqdcbMgw8PDcbMl03w+n6644gp5PJ6ZtsLCQlmWpVOnTunqq6+OG+NyueRyueyUBgAAUpStmZGsrCz5/X4Fg8GY9mAwqMrKylnHbNiwQWfOnNH7778/0/bmm28qIyNDa9asSaBkAACwlNhepqmvr9f3v/997d+/X7/4xS/0l3/5lxoYGFBtba2kc0ssd99990z/bdu2aeXKlbr33nvV19enV199VQ8//LD+/M//XMuWLVu4bwIAAFKS7X1GqqurNTo6qt27dysUCqm4uFgdHR3Kz8+XJIVCIQ0MDMz0/53f+R0Fg0F99atfVWlpqVauXKk777xTjz/++MJ9CwAAkLJs7zNiAvuMAACQei7KPiMAAAALjTACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMSCiOtra0qKCiQ2+2W3+9XV1fXnH3/7d/+TQ6HI+74z//8z4SLBgAAS4ftMNLe3q66ujo1Njaqt7dXGzduVFVVlQYGBs477pe//KVCodDMcfXVVydcNAAAWDpsh5E9e/Zox44d2rlzpwoLC7V3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AIBRhBEAAGAUYQQAABhFGEkBra2tKigokNvtlt/vV1dX15x9X375ZW3atEmXXXaZsrOzVVFRoZ/85CdJrDa92TlXH/Xaa68pMzNTN9xww8UtEDHsnq+JiQk1NjYqPz9fLpdLa9eu1f79+5NUbXqze64OHTqk66+/Xp/61Kfk8/l07733anR0NEnVwjYLi9oPfvAD65JLLrG+973vWX19fdbXvvY1a/ny5dZ///d/z9r/a1/7mvWNb3zDOnbsmPXmm29aDQ0N1iWXXGKdOHEiyZWnH7vnatqvfvUr69Of/rQVCASs66+/PjnFIqHz9fnPf94qLy+3gsGg1d/fb/37v/+79dprryWx6vRk91x1dXVZGRkZ1pNPPmm98847VldXl7V+/Xpr69atSa4c80UYWeTKysqs2tramLZrr73WeuSRR+b9HkVFRVZzc/NCl4aPSfRcVVdXW3/9139tNTU1EUaSyO75+vGPf2x5PB5rdHQ0GeXhI+yeq29961vWpz/96Zi273znO9aaNWsuWo24MCzTLGKTk5Pq6elRIBCIaQ8EAuru7p7Xe0xNTWlsbEw5OTkXo0T8n0TP1bPPPqu3335bTU1NF7tEfEQi5+tHP/qRSktL9c1vflNXXHGFrrnmGj300EP6zW9+k4yS01Yi56qyslKnTp1SR0eHLMvSe++9pxdffFG33357MkpGAlLiQXnpamRkRNFoVF6vN6bd6/VqaGhoXu/xxBNP6Ne//rXuvPPOi1Ei/k8i5+qtt97SI488oq6uLmVm8k8xmRI5X++8846OHj0qt9utH/7whxoZGdFXvvIVnT17lutGLqJEzlVlZaUOHTqk6upqjY+P68MPP9TnP/95ffe7301GyUgAMyMpwOFwxLy2LCuubTaHDx/WY489pvb2dq1atepilYePmO+5ikaj2rZtm5qbm3XNNdckqzx8jJ1/W1NTU3I4HDp06JDKysp02223ac+ePXruueeYHUkCO+eqr69PDz74oB599FH19PSos7NT/f39qq2tTUapSAD/ObaI5ebmyul0xqX/4eHhuP9K+Lj29nbt2LFDL7zwgm699daLWSZk/1yNjY3p+PHj6u3t1QMPPCDp3I+dZVnKzMzUT3/6U918881JqT0dJfJvy+fz6Yorroh5HHphYaEsy9KpU6d09dVXX9Sa01Ui56qlpUUbNmzQww8/LEm67rrrtHz5cm3cuFGPP/64fD7fRa8b9jAzsohlZWXJ7/crGAzGtAeDQVVWVs457vDhw9q+fbuef/551kiTxO65ys7O1s9//nO9/vrrM0dtba3WrVun119/XeXl5ckqPS0l8m9rw4YNOnPmjN5///2ZtjfffFMZGRlas2bNRa03nSVyrj744ANlZMT+vDmdTknnZlSwCJm7dhbzMX1L2zPPPGP19fVZdXV11vLly613333XsizLeuSRR6yampqZ/s8//7yVmZlpPfXUU1YoFJo5fvWrX5n6CmnD7rn6OO6mSS6752tsbMxas2aN9cd//MfWG2+8Yb3yyivW1Vdfbe3cudPUV0gbds/Vs88+a2VmZlqtra3W22+/bR09etQqLS21ysrKTH0FfALCSAp46qmnrPz8fCsrK8sqKSmxXnnllZm/3XPPPdZNN9008/qmm26yJMUd99xzT/ILT0N2ztXHEUaSz+75+sUvfmHdeuut1rJly6w1a9ZY9fX11gcffJDkqtOT3XP1ne98xyoqKrKWLVtm+Xw+68/+7M+sU6dOJblqzJfDspizAgAA5nDNCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwKj/D/RGKNtPVo3wAAAAAElFTkSuQmCC", + "image/png": "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ZD311FNWZ2endd9991lLliyx/ud//mfS/ufOnbMuu+wy67777rM6Ozutp556ylq0aJH1wgsvzHHlsc3uuN93333Wt771Lautrc168803rerqamvRokXW6dOn57jy2Gd37Mf99re/ta666iqrtLTUuu666+am2DgSybh/6UtfsoqKiiyfz2d1dXVZ//Ef/2G9/vrrc1h1fLA79i0tLVZCQoL1+OOPW+fOnbNaWlqsdevWWVu3bp3jymNbU1OTtXfvXuv48eOWJOull16atn80v1/nfRgpLCy0KioqwtquueYaa8+ePZP2/7u/+zvrmmuuCWu76667rPXr10etxnhkd9wnk5uba9XW1s52aXEv0rEvLy+3/uEf/sGqqakhjETA7rj/+Mc/ttxutzUwMDAX5cU1u2P/ne98x7rqqqvC2r73ve9ZK1eujFqN8W4mYSSa36/zeplmZGRE7e3tKi0tDWsvLS1Va2vrpOf8/Oc/n9D/C1/4gk6dOqX3338/arXGk0jG/aPGxsY0ODio1NTUaJQYtyId+2eeeUZvv/22ampqol1iXIpk3H/0ox+poKBA3/72t3XFFVfo6quv1gMPPKA//OEPc1Fy3Ihk7EtKSnT+/Hk1NTXJsiy9++67euGFFyJ6KjxmLprfr7YflDeX+vv7NTo6qvT09LD29PT00NN/P6q3t3fS/h988IH6+/vl8XiiVm+8iGTcP+rRRx/V73//e91yyy3RKDFuRTL2b731lvbs2aOWlhYlJs7r/0nPW5GM+7lz53Ty5Em5XC699NJL6u/v1ze+8Q1dvHiR60ZsiGTsS0pKdPToUZWXl2toaEgffPCBvvSlL+n73//+XJS8YEXz+3Vez4yMczgcYa8ty5rQ9nH9J2vH9OyO+7hjx47p4YcfVmNjo5YvXx6t8uLaTMd+dHRUt912m2pra3X11VfPVXlxy87f/NjYmBwOh44eParCwkLddNNN2r9/v5599llmRyJgZ+w7Ozt177336qGHHlJ7e7uam5vV1dWlioqKuSh1QYvW9+u8/mdUWlqanE7nhHTc19c3IZ2Ny8jImLR/YmKili1bFrVa40kk4z6usbFRO3fu1PPPP68bb7wxmmXGJbtjPzg4qFOnTqmjo0P33HOPpEtfkpZlKTExUa+88oquv/76Oak9lkXyN+/xeHTFFVeEPR49JydHlmXp/PnzWrNmTVRrjheRjH1dXZ02bNigBx98UJJ07bXXasmSJdq4caMeeeQRZsCjJJrfr/N6ZiQpKUler1c+ny+s3efzqaSkZNJziouLJ/R/5ZVXVFBQoEWLFkWt1ngSybhLl2ZE7rjjDj333HOs3UbI7tinpKTol7/8pc6cORM6KioqtHbtWp05c0ZFRUVzVXpMi+RvfsOGDXrnnXf0u9/9LtT25ptvKiEhQStXroxqvfEkkrF/7733lJAQ/vXldDol/f9/qWP2RfX79RNfAhtl47d8HTx40Ors7LQqKyutJUuWWL/5zW8sy7KsPXv2WNu3bw/1H7/16P7777c6OzutgwcPcmtvBOyO+3PPPWclJiZaTzzxhOX3+0PHb3/7W1MfIWbZHfuP4m6ayNgd98HBQWvlypXWX/7lX1q/+tWvrFdffdVas2aNtWvXLlMfIWbZHftnnnnGSkxMtOrr6623337bOnnypFVQUGAVFhaa+ggxaXBw0Oro6LA6OjosSdb+/futjo6O0C3Vc/n9Ou/DiGVZ1hNPPGFlZWVZSUlJVn5+vvXqq6+Gfnb77bdbn/vc58L6/+xnP7M++9nPWklJSdaqVaushoaGOa44PtgZ98997nOWpAnH7bffPveFxwG7f/MfRhiJnN1xf+ONN6wbb7zRWrx4sbVy5UqrqqrKeu+99+a46vhgd+y/973vWbm5udbixYstj8dj/fVf/7V1/vz5Oa46tv30pz+d9v+35/L71WFZzGkBAABz5vU1IwAAIP4RRgAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABj1/wAygnrgjAYcMQAAAABJRU5ErkJggg==", "text/plain": [ "
" ] @@ -805,7 +805,7 @@ "outputs": [ { "data": { - "image/png": "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q7r99ttTUXLac/ygPGSXkZERRSIRFRYWxrQXFhZqaGhoQV/j+9//vj766CPdcccdi1EiLpLINfv973+vxx57TF1dXcrN5ddCKiVyvU6fPq133nlHPp9Pr7/+ukZGRvTggw9qbGyMdSMpkMg127Bhgw4cOKDa2lpNTk7q/Pnz+spXvqIf/ehHqSg57TEyggXxeDwxr40xc9riOXjwoL7zne+otbVVK1asWKzyEMdCr1kkEtGdd96pxsZG3XTTTakqDxdx8n8sGo3K4/HowIEDqqio0G233aYnn3xSL774IqMjKeTkmvX19Wnnzp16/PHH1dPTo46ODvX396uuri4VpaY9/gTCJS1fvlxer3dO2h8eHp7zV8HFWltbdd999+mVV17RLbfcsphl4gJOr9nExISOHj2q48eP69vf/rak6Q87Y4xyc3P15ptv6ktf+lJKas9GifwfCwQCuu6662Iezb527VoZY3TmzBndeOONi1pztkvkmjU1NWnjxo169NFHJUmf+cxndM0112jTpk367ne/q0AgsOh1pzNGRnBJS5YsUXl5uTo7O2PaOzs7tWHDhnnPO3jwoO655x69/PLLzImmmNNrlp+fr9/97nc6ceLE7FFXV6e/+qu/0okTJ7R+/fpUlZ6VEvk/tnHjRr3//vv68MMPZ9vee+895eTkaNWqVYtaLxK7Zh9//LFycmI/cr1er6TpEZWsZ2/tLNxi5ha2559/3vT19Zldu3aZa665xvzP//yPMcaYxx57zNx1112z/V9++WWTm5trnn76aRMKhWaPP/7xj7a+hazj9JpdjLtpUsvp9ZqYmDCrVq0yf/u3f2veffdd8/bbb5sbb7zR3H///ba+hazj9Jq98MILJjc31zQ3N5tTp06Zd955x6xbt85UVFTY+hbSCmEEC/L000+b4uJis2TJElNWVmbefvvt2X+7++67zRe+8IXZ11/4wheMpDnH3XffnfrCs5iTa3YxwkjqOb1eJ0+eNLfccou5+uqrzapVq8zu3bvNxx9/nOKqs5vTa/bUU0+ZkpISc/XVV5tAIGD+/u//3pw5cybFVacnjzGMDwEAAHtYMwIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALDqfwFa0c4H0SXirgAAAABJRU5ErkJggg==", + "image/png": "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Wloac35+fr4WLFiQslqzQSLHe0Z7e7vuuusuPf/887r++utTWWbWcHq8JyYm9NZbb6mvr0/f+ta3JE2fMI0xys/P16uvvqrrrrsuLbW7TSJ/twOBgJYsWRLxqPSVK1fKGKNTp07piiuuSGnNbpbI8W5padGaNWt0//33S5I+85nP6LLLLtPatWv14IMPsqqdZOk4V2bNykhBQYGqqqrU1dUVMd7V1aXa2tqY76mpqYma/+qrr2rVqlWaN29eymrNBokcb2l6ReSOO+7Qs88+y/VdB5we76KiIv3lL3/RkSNHZl+NjY361Kc+pSNHjmj16tXpKt11Evm7vWbNGp0+fVr/+te/Zsfeeecd5eXlaenSpSmt1+0SOd7vv/++8vIiT19er1fS///EjuRJy7kyabfCZoCZ7WFPPfWU6e/vN1u3bjWXXXaZ+fvf/26MMWbHjh3mtttum50/s11p27Ztpr+/3zz11FNs7XXA6fF+9tlnTX5+vnniiSdMMBicff3zn/+09Su4itPj/WHspomf02M9MTFhli5dar7yla+Yv/71r+b11183V1xxhbn77rtt/Qqu4vR4P/300yY/P9+0traa48ePmwMHDphVq1aZ6upqW7+Cq0xMTJi+vj7T19dnJJlHHnnE9PX1zW6ltnGuzKowYowxTzzxhFm+fLkpKCgwlZWV5vXXX5/93u23324+97nPRcz/4x//aD772c+agoIC84lPfMK0tbWluWJ3c3K8P/e5zxlJUa/bb789/YW7lNO/3x9EGHHG6bE+duyYuf76680ll1xili5darZv327ef//9NFftXk6P92OPPWbKy8vNJZdcYgKBgPnqV79qTp06leaq3ekPf/jDef8vtnGu9BjDmhYAALAna+4ZAQAA7kQYAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFWEEQAAYNX/BZ6XXB7rq5klAAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -1038,9 +1038,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:263: UserWarning: Reassigning the node bulk to the label structure when adding it to the parent with_prebuilt.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:311: UserWarning: Reassigning the node bulk 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:263: 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:311: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent with_prebuilt.\n", " warn(\n" ] }, @@ -1055,9 +1055,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:263: 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:311: 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:263: 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:311: UserWarning: Reassigning the node scatter to the label plot when adding it to the parent with_prebuilt.\n", " warn(\n" ] }, @@ -1947,7 +1947,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 34, @@ -2233,7 +2233,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 35, @@ -2407,13 +2407,11 @@ " \"structure_structure\": \"structure\",\n", " }\n", " \n", - " macro.starting_nodes = [macro.structure]\n", - " # Note: Because we'll be embedding this macro in a workflow, NONE\n", - " # of its nodes will have totally unconnected input, and therefore \n", - " # do not register as \"upstream-most\", so we _need_ to specify the \n", - " # starting nodes. This is a bug, not a feature -- the macro should\n", - " # only be looking for _internal_ connections when determining \n", - " # upstream-most-ness\n", + " # macro.starting_nodes = [macro.structure]\n", + " # Note: We _could_ customize macro features like the starting nodes here.\n", + " # For this particular case we don't need to, since macro.structure will \n", + " # be automatically detected as the \"upstream-most\" node, since it receives\n", + " # no input from any other nodes belonging to this macro.\n", "\n", "@Workflow.wrap_as.single_value_node()\n", "def per_atom_energy_difference(structure1, energy1, structure2, energy2):\n", @@ -2431,13 +2429,13 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:263: UserWarning: Reassigning the node user_input to the label element when adding it to the parent phase_preference.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:311: UserWarning: Reassigning the node user_input to the label element when adding it to the parent phase_preference.\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:263: UserWarning: Reassigning the node bulk to the label structure when adding it to the parent lammps_minimize.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:311: UserWarning: Reassigning the node bulk to the label structure when adding it to the parent lammps_minimize.\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:263: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent lammps_minimize.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:311: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent lammps_minimize.\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:263: UserWarning: Reassigning the node calc_min to the label calc when adding it to the parent lammps_minimize.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:311: UserWarning: Reassigning the node calc_min to the label calc when adding it to the parent lammps_minimize.\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label name to the io key element\n", " warn(\n", @@ -2633,160 +2631,160 @@ "clusterphase_preference\n", "\n", "phase_preference: Workflow\n", - "\n", - "clusterphase_preferenceInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterphase_preferenceOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", "\n", "clusterphase_preferenceelement\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "element: UserInput\n", "\n", "\n", "clusterphase_preferenceelementInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterphase_preferenceelementOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", "\n", "clusterphase_preferencemin_phase1\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "min_phase1: LammpsMinimize\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", "\n", "clusterphase_preferencemin_phase2\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "min_phase2: LammpsMinimize\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", "\n", "clusterphase_preferencecompare\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "compare: PerAtomEnergyDifference\n", "\n", "\n", "clusterphase_preferencecompareInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterphase_preferencecompareOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", + "\n", + "clusterphase_preferenceInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterphase_preferenceOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", "\n", "\n", "clusterphase_preferenceInputsrun\n", @@ -2822,394 +2820,394 @@ "\n", "\n", "clusterphase_preferenceInputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", "\n", "clusterphase_preferenceInputscrystalstructure->clusterphase_preferencemin_phase1Inputscrystalstructure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", "\n", "clusterphase_preferenceInputscrystalstructure->clusterphase_preferencemin_phase2Inputscrystalstructure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", "\n", "clusterphase_preferenceInputsa->clusterphase_preferencemin_phase1Inputsa\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", "\n", "clusterphase_preferenceInputsa->clusterphase_preferencemin_phase2Inputsa\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", "\n", "clusterphase_preferenceInputsc->clusterphase_preferencemin_phase1Inputsc\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", "\n", "clusterphase_preferenceInputsc->clusterphase_preferencemin_phase2Inputsc\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", "\n", "clusterphase_preferenceInputscovera->clusterphase_preferencemin_phase1Inputscovera\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", "\n", "clusterphase_preferenceInputscovera->clusterphase_preferencemin_phase2Inputscovera\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", "\n", "clusterphase_preferenceInputsu->clusterphase_preferencemin_phase1Inputsu\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", "\n", "clusterphase_preferenceInputsu->clusterphase_preferencemin_phase2Inputsu\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", "\n", "clusterphase_preferenceInputsorthorhombic->clusterphase_preferencemin_phase1Inputsorthorhombic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", "\n", "clusterphase_preferenceInputsorthorhombic->clusterphase_preferencemin_phase2Inputsorthorhombic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", "\n", "clusterphase_preferenceInputscubic->clusterphase_preferencemin_phase1Inputscubic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", "\n", "clusterphase_preferenceInputscubic->clusterphase_preferencemin_phase2Inputscubic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_ionic_steps->clusterphase_preferencemin_phase1Inputsn_ionic_steps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_ionic_steps->clusterphase_preferencemin_phase2Inputsn_ionic_steps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_print->clusterphase_preferencemin_phase1Inputsn_print\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_print->clusterphase_preferencemin_phase2Inputsn_print\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", "\n", "clusterphase_preferenceInputspressure->clusterphase_preferencemin_phase1Inputspressure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", "\n", "clusterphase_preferenceInputspressure->clusterphase_preferencemin_phase2Inputspressure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceOutputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", "\n", "clusterphase_preferenceOutputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", "\n", "clusterphase_preferenceOutputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", "\n", "clusterphase_preferenceOutputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", "\n", "clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", "\n", @@ -3317,28 +3315,28 @@ "\n", "\n", "clusterphase_preferencemin_phase1Outputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputscells->clusterphase_preferenceOutputscells\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsdisplacements->clusterphase_preferenceOutputsdisplacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3362,132 +3360,132 @@ "\n", "\n", "clusterphase_preferencemin_phase1Outputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsenergy_tot->clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforce_max->clusterphase_preferenceOutputsforce_max\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforces->clusterphase_preferenceOutputsforces\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsindices->clusterphase_preferenceOutputsindices\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspositions->clusterphase_preferenceOutputspositions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspressures->clusterphase_preferenceOutputspressures\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputssteps->clusterphase_preferenceOutputssteps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputstotal_displacements->clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsunwrapped_positions->clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsvolume->clusterphase_preferenceOutputsvolume\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3518,28 +3516,28 @@ "\n", "\n", "clusterphase_preferencemin_phase2Outputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputscells->clusterphase_preferenceOutputscells\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsdisplacements->clusterphase_preferenceOutputsdisplacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3563,132 +3561,132 @@ "\n", "\n", "clusterphase_preferencemin_phase2Outputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsenergy_tot->clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforce_max->clusterphase_preferenceOutputsforce_max\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforces->clusterphase_preferenceOutputsforces\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsindices->clusterphase_preferenceOutputsindices\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspositions->clusterphase_preferenceOutputspositions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspressures->clusterphase_preferenceOutputspressures\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputssteps->clusterphase_preferenceOutputssteps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputstotal_displacements->clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsunwrapped_positions->clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsvolume->clusterphase_preferenceOutputsvolume\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3720,7 +3718,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 41, From baa16e1c8af175801338d3c2ca552824380298c6 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 8 Aug 2023 20:46:55 +0000 Subject: [PATCH 551/756] Format black --- pyiron_contrib/workflow/composite.py | 8 ++------ pyiron_contrib/workflow/io.py | 6 +++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 32f61cd4c..96e01cbc0 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -152,9 +152,7 @@ def upstream_nodes(self) -> list[Node]: A list of owned nodes that receive no input from any other owned nodes. """ return [ - node - for node in self.nodes.values() - if not self.connects_to_input_of(node) + node for node in self.nodes.values() if not self.connects_to_input_of(node) ] def has_locally_scoped_connection(self, node_connections: list[Channel]) -> bool: @@ -169,9 +167,7 @@ def has_locally_scoped_connection(self, node_connections: list[Channel]) -> bool nodes owned by this composite node. """ return len( - set( - [connection.node for connection in node_connections] - ).intersection( + set([connection.node for connection in node_connections]).intersection( self.nodes.values() ) ) > 0 or any( diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py index 897e3f481..1a4df7cdd 100644 --- a/pyiron_contrib/workflow/io.py +++ b/pyiron_contrib/workflow/io.py @@ -110,9 +110,9 @@ def __setitem__(self, key, value): @property def connections(self) -> list[Channel]: """All the unique connections across all channels""" - return list(set( - [connection for channel in self for connection in channel.connections] - )) + return list( + set([connection for channel in self for connection in channel.connections]) + ) @property def connected(self): From 156d9a3968b3a506b0a60f25127bdcc6efe322f7 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 13 Jun 2023 11:29:00 -0700 Subject: [PATCH 552/756] Introduce meta nodes callables that create node classes following a pattern --- pyiron_contrib/workflow/meta.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 pyiron_contrib/workflow/meta.py diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py new file mode 100644 index 000000000..1b6b2bcdc --- /dev/null +++ b/pyiron_contrib/workflow/meta.py @@ -0,0 +1,27 @@ +""" +Meta nodes are callables that create a node class instead of a node instance. +""" + +from __future__ import annotations + +from pyiron_contrib.workflow.node import single_value_node, SingleValueNode + + +def _input_to_list(n_elements) -> callable: + string = "def input_to_list(" + for i in range(n_elements): + string += f"i{i}=None, " + string += "): return [" + for i in range(n_elements): + string += f"i{i}, " + string += "]" + exec(string) + return locals()["input_to_list"] + + +class MetaNodes: + """A container class for meta node access""" + + @classmethod + def input_to_list(cls, n_inputs: int) -> SingleValueNode: + return single_value_node("list")(_input_to_list(n_inputs)) From ff20a37a36935c03d04076c8b0c5ea4fb7def72c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 8 Aug 2023 16:16:46 -0700 Subject: [PATCH 553/756] Finish rebasing --- pyiron_contrib/workflow/composite.py | 2 ++ pyiron_contrib/workflow/meta.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 96e01cbc0..adf4d1d1c 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -12,6 +12,7 @@ from pyiron_contrib.executors import CloudpickleProcessPoolExecutor from pyiron_contrib.workflow.io import Outputs, Inputs +from pyiron_contrib.workflow.meta import MetaNodes from pyiron_contrib.workflow.node import Node from pyiron_contrib.workflow.function import ( Function, @@ -55,6 +56,7 @@ class Creator: """A shortcut interface for creating non-Node objects from the workflow class.""" CloudpickleProcessPoolExecutor = CloudpickleProcessPoolExecutor + meta = MetaNodes class Composite(Node, ABC): diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index 1b6b2bcdc..fd937a1df 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -4,7 +4,7 @@ from __future__ import annotations -from pyiron_contrib.workflow.node import single_value_node, SingleValueNode +from pyiron_contrib.workflow.function import single_value_node, SingleValue def _input_to_list(n_elements) -> callable: @@ -23,5 +23,5 @@ class MetaNodes: """A container class for meta node access""" @classmethod - def input_to_list(cls, n_inputs: int) -> SingleValueNode: + def input_to_list(cls, n_inputs: int) -> SingleValue: return single_value_node("list")(_input_to_list(n_inputs)) From 6b7d27b61e328820b1d82b4b01369d863ec93068 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 8 Aug 2023 16:28:58 -0700 Subject: [PATCH 554/756] Refactor: rename variable Because checking if label was parent looked confusing --- pyiron_contrib/workflow/composite.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 96e01cbc0..e9b9a79c5 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -318,11 +318,11 @@ def remove(self, node: Node | str): else: del self.nodes[node] - def __setattr__(self, label: str, node: Node): - if isinstance(node, Node) and label != "parent": - self.add_node(node, label=label) + def __setattr__(self, key: str, node: Node): + if isinstance(node, Node) and key != "parent": + self.add_node(node, label=key) else: - super().__setattr__(label, node) + super().__setattr__(key, node) def __getattr__(self, key): try: From 29d1ae5adad09a798c8a26a604be4e931d36cfde Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 9 Aug 2023 13:32:10 -0700 Subject: [PATCH 555/756] Centralize the creator/wrappers So that all funkiness with circular imports is at least in one place --- pyiron_contrib/workflow/composite.py | 39 +------------ pyiron_contrib/workflow/interfaces.py | 79 +++++++++++++++++++++++++++ tests/unit/workflow/test_workflow.py | 2 +- 3 files changed, 83 insertions(+), 37 deletions(-) create mode 100644 pyiron_contrib/workflow/interfaces.py diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index e9b9a79c5..7df0efc64 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -10,16 +10,13 @@ from typing import Literal, Optional, TYPE_CHECKING from warnings import warn -from pyiron_contrib.executors import CloudpickleProcessPoolExecutor +from pyiron_contrib.workflow.interfaces import Creator, Wrappers from pyiron_contrib.workflow.io import Outputs, Inputs from pyiron_contrib.workflow.node import Node from pyiron_contrib.workflow.function import ( Function, SingleValue, Slow, - function_node, - slow_node, - single_value_node, ) from pyiron_contrib.workflow.node_library import atomistics, standard from pyiron_contrib.workflow.node_library.package import NodePackage @@ -29,34 +26,6 @@ from pyiron_contrib.workflow.channels import Channel -class _NodeDecoratorAccess: - """An intermediate container to store node-creating decorators as class methods.""" - - function_node = function_node - slow_node = slow_node - single_value_node = single_value_node - - _macro_node = None - - @classmethod - @property - def macro_node(cls): - # This jankiness is to avoid circular imports - # Chaining classmethod and property like this got deprecated in python 3.11, - # but it does what I want, so I'm going to use it anyhow - if cls._macro_node is None: - from pyiron_contrib.workflow.macro import macro_node - - cls._macro_node = macro_node - return cls._macro_node - - -class Creator: - """A shortcut interface for creating non-Node objects from the workflow class.""" - - CloudpickleProcessPoolExecutor = CloudpickleProcessPoolExecutor - - class Composite(Node, ABC): """ A base class for nodes that have internal structure -- i.e. they hold a sub-graph. @@ -103,10 +72,8 @@ class Composite(Node, ABC): subgraph, and set its parent to `None`. """ - wrap_as = _NodeDecoratorAccess # Class method access to decorators - # Allows users/devs to easily create new nodes when using children of this class - - create = Creator + wrap_as = Wrappers() + create = Creator() def __init__( self, diff --git a/pyiron_contrib/workflow/interfaces.py b/pyiron_contrib/workflow/interfaces.py new file mode 100644 index 000000000..418524bc6 --- /dev/null +++ b/pyiron_contrib/workflow/interfaces.py @@ -0,0 +1,79 @@ +""" +Container classes for giving access to various workflow objects and tools +""" + +from __future__ import annotations + +from pyiron_base.interfaces.singleton import Singleton + +from pyiron_contrib.executors import CloudpickleProcessPoolExecutor +from pyiron_contrib.workflow.function import ( + Function, SingleValue, Slow, function_node, single_value_node, slow_node +) + + +class Creator(metaclass=Singleton): + """ + A container class for providing access to various workflow objects. + Handles the registration of new node packages and, by virtue of being a singleton, + makes them available to all composite nodes holding a creator. + """ + def __init__(self): + self._nodes = Nodes() + self.executor = Executors + + @property + def node(self) -> Nodes: + return self._nodes + + +class Executors: + CloudpickleProcessPoolExecutor = CloudpickleProcessPoolExecutor + + +class Nodes(metaclass=Singleton): + """ + A container class for giving access to the basic node classes. + """ + def __init__(self): + self.Function = Function + self.SingleValue = SingleValue + self.Slow = Slow + + # Avoid circular imports by delaying import for children of Composite + self._macro = None + self._workflow = None + + @property + def Macro(self): + if self._macro is None: + from pyiron_contrib.workflow.macro import Macro + self._macro = Macro + return self._macro + + @property + def Workflow(self): + if self._workflow is None: + from pyiron_contrib.workflow.workflow import Workflow + self._workflow = Workflow + return self._workflow + + +class Wrappers(metaclass=Singleton): + """ + A container class giving access to the decorators that transform functions to nodes. + """ + def __init__(self): + self.function_node = function_node + self.single_value_node = single_value_node + self.slow_node = slow_node + + # Avoid circular imports by delaying import when wrapping children of Composite + self._macro_node = None + + @property + def macro_node(self): + if self._macro_node is None: + from pyiron_contrib.workflow.macro import macro_node + self._macro_node = macro_node + return self._macro_node diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 53f91a04d..f9cae4ec4 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -177,7 +177,7 @@ def sum(a, b): wf.fast = five() wf.sum = sum(a=wf.fast, b=wf.slow) - wf.slow.executor = wf.create.CloudpickleProcessPoolExecutor() + wf.slow.executor = wf.create.executor.CloudpickleProcessPoolExecutor() wf.slow.run() wf.fast.run() From 65c7e79bd66822fa05dbcc4f3a60d8b5ebd4e47e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 9 Aug 2023 13:43:55 -0700 Subject: [PATCH 556/756] Have the node adder lean on the node creator --- pyiron_contrib/workflow/composite.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 7df0efc64..1066f1aa7 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -10,14 +10,9 @@ from typing import Literal, Optional, TYPE_CHECKING from warnings import warn -from pyiron_contrib.workflow.interfaces import Creator, Wrappers +from pyiron_contrib.workflow.interfaces import Creator, Nodes as NodeCreator, Wrappers from pyiron_contrib.workflow.io import Outputs, Inputs from pyiron_contrib.workflow.node import Node -from pyiron_contrib.workflow.function import ( - Function, - SingleValue, - Slow, -) from pyiron_contrib.workflow.node_library import atomistics, standard from pyiron_contrib.workflow.node_library.package import NodePackage from pyiron_contrib.workflow.util import DotDict, SeabornColors @@ -333,17 +328,14 @@ class NodeAdder: def __init__(self, parent: Composite): self._parent: Composite = parent + self._node_creator: NodeCreator = NodeCreator() self.register_nodes("atomistics", *atomistics.nodes) self.register_nodes("standard", *standard.nodes) - Function = Function - Slow = Slow - SingleValue = SingleValue - - def __getattribute__(self, key): - value = super().__getattribute__(key) - if value == Function: - return partial(Function, parent=self._parent) + def __getattr__(self, key): + value = getattr(self._node_creator, key) + if issubclass(value, Node): + return partial(value, parent=self._parent) return value def __call__(self, node: Node): From 4faeaca596be3926282e6edab3496cf1630ad9c2 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 9 Aug 2023 13:49:11 -0700 Subject: [PATCH 557/756] Use the creator in workflow tests --- tests/unit/workflow/test_workflow.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index f9cae4ec4..b945741ca 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -4,7 +4,6 @@ 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 @@ -21,10 +20,10 @@ def test_node_addition(self): wf = Workflow("my_workflow") # Validate the four ways to add a node - wf.add(Function(plus_one, label="foo")) + wf.add(wf.create.node.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) + wf.baz = wf.create.node.Function(plus_one, label="whatever_baz_gets_used") + wf.create.node.Function(plus_one, label="qux", parent=wf) self.assertListEqual(list(wf.nodes.keys()), ["foo", "bar", "baz", "qux"]) wf.boa = wf.qux self.assertListEqual( @@ -35,13 +34,13 @@ def test_node_addition(self): wf.strict_naming = False # Validate name incrementation - wf.add(Function(plus_one, label="foo")) + wf.add(wf.create.node.Function(plus_one, label="foo")) wf.add.Function(plus_one, label="bar") - wf.baz = Function( + wf.baz = wf.create.node.Function( plus_one, label="without_strict_you_can_override_by_assignment" ) - Function(plus_one, label="boa", parent=wf) + wf.create.node.Function(plus_one, label="boa", parent=wf) self.assertListEqual( list(wf.nodes.keys()), [ @@ -53,16 +52,16 @@ def test_node_addition(self): wf.strict_naming = True # Validate name preservation with self.assertRaises(AttributeError): - wf.add(Function(plus_one, label="foo")) + wf.add(wf.create.node.Function(plus_one, label="foo")) with self.assertRaises(AttributeError): wf.add.Function(plus_one, label="bar") with self.assertRaises(AttributeError): - wf.baz = Function(plus_one, label="whatever_baz_gets_used") + wf.baz = wf.create.node.Function(plus_one, label="whatever_baz_gets_used") with self.assertRaises(AttributeError): - Function(plus_one, label="boa", parent=wf) + wf.create.node.Function(plus_one, label="boa", parent=wf) def test_node_packages(self): wf = Workflow("my_workflow") @@ -82,7 +81,9 @@ def test_node_packages(self): def test_double_workfloage_and_node_removal(self): wf1 = Workflow("one") wf1.add.Function(plus_one, label="node1") - node2 = Function(plus_one, label="node2", parent=wf1, x=wf1.node1.outputs.y) + node2 = wf1.create.node.Function( + plus_one, label="node2", parent=wf1, x=wf1.node1.outputs.y + ) self.assertTrue(node2.connected) wf2 = Workflow("two") From 4723d6547bcbbf7ff4ef7e1ee89f6aa2f6453397 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 9 Aug 2023 13:57:11 -0700 Subject: [PATCH 558/756] Improve test readability --- tests/unit/workflow/test_workflow.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index b945741ca..aacc3848e 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -49,19 +49,20 @@ def test_node_addition(self): ] ) - wf.strict_naming = True - # Validate name preservation - with self.assertRaises(AttributeError): - wf.add(wf.create.node.Function(plus_one, label="foo")) + with self.subTest("Make sure strict naming causes a bunch of attribute errors"): + wf.strict_naming = True + # Validate name preservation + with self.assertRaises(AttributeError): + wf.add(wf.create.node.Function(plus_one, label="foo")) - with self.assertRaises(AttributeError): - wf.add.Function(plus_one, label="bar") + with self.assertRaises(AttributeError): + wf.add.Function(plus_one, label="bar") - with self.assertRaises(AttributeError): - wf.baz = wf.create.node.Function(plus_one, label="whatever_baz_gets_used") + with self.assertRaises(AttributeError): + wf.baz = wf.create.node.Function(plus_one, label="whatever_baz_gets_used") - with self.assertRaises(AttributeError): - wf.create.node.Function(plus_one, label="boa", parent=wf) + with self.assertRaises(AttributeError): + wf.create.node.Function(plus_one, label="boa", parent=wf) def test_node_packages(self): wf = Workflow("my_workflow") From e836395a29f58d8be86f435fab6c40892f8171b5 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 9 Aug 2023 14:00:47 -0700 Subject: [PATCH 559/756] Flatten the creator --- pyiron_contrib/workflow/composite.py | 6 +++--- pyiron_contrib/workflow/interfaces.py | 17 +---------------- tests/unit/workflow/test_workflow.py | 22 +++++++++++----------- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 1066f1aa7..493611347 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -10,7 +10,7 @@ from typing import Literal, Optional, TYPE_CHECKING from warnings import warn -from pyiron_contrib.workflow.interfaces import Creator, Nodes as NodeCreator, Wrappers +from pyiron_contrib.workflow.interfaces import Creator, Wrappers from pyiron_contrib.workflow.io import Outputs, Inputs from pyiron_contrib.workflow.node import Node from pyiron_contrib.workflow.node_library import atomistics, standard @@ -328,12 +328,12 @@ class NodeAdder: def __init__(self, parent: Composite): self._parent: Composite = parent - self._node_creator: NodeCreator = NodeCreator() + self._creator: Creator = Creator() self.register_nodes("atomistics", *atomistics.nodes) self.register_nodes("standard", *standard.nodes) def __getattr__(self, key): - value = getattr(self._node_creator, key) + value = getattr(self._creator, key) if issubclass(value, Node): return partial(value, parent=self._parent) return value diff --git a/pyiron_contrib/workflow/interfaces.py b/pyiron_contrib/workflow/interfaces.py index 418524bc6..11c84929d 100644 --- a/pyiron_contrib/workflow/interfaces.py +++ b/pyiron_contrib/workflow/interfaces.py @@ -19,23 +19,8 @@ class Creator(metaclass=Singleton): makes them available to all composite nodes holding a creator. """ def __init__(self): - self._nodes = Nodes() - self.executor = Executors + self.CloudpickleProcessPoolExecutor = CloudpickleProcessPoolExecutor - @property - def node(self) -> Nodes: - return self._nodes - - -class Executors: - CloudpickleProcessPoolExecutor = CloudpickleProcessPoolExecutor - - -class Nodes(metaclass=Singleton): - """ - A container class for giving access to the basic node classes. - """ - def __init__(self): self.Function = Function self.SingleValue = SingleValue self.Slow = Slow diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index aacc3848e..b4588c33f 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -20,10 +20,10 @@ def test_node_addition(self): wf = Workflow("my_workflow") # Validate the four ways to add a node - wf.add(wf.create.node.Function(plus_one, label="foo")) + wf.add(wf.create.Function(plus_one, label="foo")) wf.add.Function(plus_one, label="bar") - wf.baz = wf.create.node.Function(plus_one, label="whatever_baz_gets_used") - wf.create.node.Function(plus_one, label="qux", parent=wf) + wf.baz = wf.create.Function(plus_one, label="whatever_baz_gets_used") + wf.create.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(wf.create.node.Function(plus_one, label="foo")) + wf.add(wf.create.Function(plus_one, label="foo")) wf.add.Function(plus_one, label="bar") - wf.baz = wf.create.node.Function( + wf.baz = wf.create.Function( plus_one, label="without_strict_you_can_override_by_assignment" ) - wf.create.node.Function(plus_one, label="boa", parent=wf) + wf.create.Function(plus_one, label="boa", parent=wf) self.assertListEqual( list(wf.nodes.keys()), [ @@ -53,16 +53,16 @@ def test_node_addition(self): wf.strict_naming = True # Validate name preservation with self.assertRaises(AttributeError): - wf.add(wf.create.node.Function(plus_one, label="foo")) + wf.add(wf.create.Function(plus_one, label="foo")) with self.assertRaises(AttributeError): wf.add.Function(plus_one, label="bar") with self.assertRaises(AttributeError): - wf.baz = wf.create.node.Function(plus_one, label="whatever_baz_gets_used") + wf.baz = wf.create.Function(plus_one, label="whatever_baz_gets_used") with self.assertRaises(AttributeError): - wf.create.node.Function(plus_one, label="boa", parent=wf) + wf.create.Function(plus_one, label="boa", parent=wf) def test_node_packages(self): wf = Workflow("my_workflow") @@ -82,7 +82,7 @@ def test_node_packages(self): def test_double_workfloage_and_node_removal(self): wf1 = Workflow("one") wf1.add.Function(plus_one, label="node1") - node2 = wf1.create.node.Function( + node2 = wf1.create.Function( plus_one, label="node2", parent=wf1, x=wf1.node1.outputs.y ) self.assertTrue(node2.connected) @@ -179,7 +179,7 @@ def sum(a, b): wf.fast = five() wf.sum = sum(a=wf.fast, b=wf.slow) - wf.slow.executor = wf.create.executor.CloudpickleProcessPoolExecutor() + wf.slow.executor = wf.create.CloudpickleProcessPoolExecutor() wf.slow.run() wf.fast.run() From 7e93e0ca42c16f42fae1bfe14bcb7c1a3ca93e68 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 9 Aug 2023 15:28:07 -0700 Subject: [PATCH 560/756] Get rid of add and do everything through create Which behaves differently depending on whether you access it from a composite class or a composite instance -- in the latter case, there are wrappers so that any created nodes get their parent automatically set to the instance being used to create them --- pyiron_contrib/workflow/composite.py | 84 ++++++++++++------- pyiron_contrib/workflow/interfaces.py | 30 +++++++ .../package.py => node_package.py} | 19 +---- pyiron_contrib/workflow/workflow.py | 2 +- tests/unit/workflow/test_node_package.py | 18 ++-- tests/unit/workflow/test_workflow.py | 40 ++++----- 6 files changed, 114 insertions(+), 79 deletions(-) rename pyiron_contrib/workflow/{node_library/package.py => node_package.py} (70%) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 493611347..2805e6fa2 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -13,8 +13,7 @@ from pyiron_contrib.workflow.interfaces import Creator, Wrappers from pyiron_contrib.workflow.io import Outputs, Inputs from pyiron_contrib.workflow.node import Node -from pyiron_contrib.workflow.node_library import atomistics, standard -from pyiron_contrib.workflow.node_library.package import NodePackage +from pyiron_contrib.workflow.node_package import NodePackage from pyiron_contrib.workflow.util import DotDict, SeabornColors if TYPE_CHECKING: @@ -34,6 +33,12 @@ class Composite(Node, ABC): Offers a class method (`wrap_as`) to give easy access to the node-creating decorators. + Offers a creator (the `create` method) which allows instantiation of other workflow + objects. + This method behaves _differently_ on the composite class and its instances -- on + instances, any created nodes get their `parent` attribute automatically set to the + composite instance being used. + Specifies the required `on_run()` to call `run()` on a subset of owned nodes, i.e. to kick-start computation on the owned sub-graph. By default, `run()` will be called on all owned nodes have output connections but no @@ -88,8 +93,18 @@ def __init__( self.inputs_map = inputs_map self.outputs_map = outputs_map self.nodes: DotDict[str:Node] = DotDict() - self.add: NodeAdder = NodeAdder(self) self.starting_nodes: None | list[Node] = None + self._creator = self.create + self.create = self._owned_creator # Override the create method from the class + + @property + def _owned_creator(self): + """ + A misdirection so that the `create` method behaves differently on the class + and on instances (in the latter case, created nodes should get the instance as + their parent). + """ + return OwnedCreator(self, self._creator) @property def executor(self) -> None: @@ -198,7 +213,7 @@ def _build_inputs(self) -> Inputs: def _build_outputs(self) -> Outputs: return self._build_io(Outputs(), "outputs", self.outputs_map) - def add_node(self, node: Node, label: Optional[str] = None) -> None: + def add(self, node: Node, label: Optional[str] = None) -> None: """ Assign a node to the parent. Optionally provide a new label for that node. @@ -282,7 +297,7 @@ def remove(self, node: Node | str): def __setattr__(self, key: str, node: Node): if isinstance(node, Node) and key != "parent": - self.add_node(node, label=key) + self.add(node, label=key) else: super().__setattr__(key, node) @@ -317,36 +332,47 @@ def color(self) -> str: return SeabornColors.brown -class NodeAdder: +class OwnedCreator: """ - This class provides a layer of misdirection so that `Composite` objects can set - themselves as the parent of owned nodes. + A creator that overrides the `parent` arg of all accessed nodes to its own parent. - It also provides access to packages of nodes and the ability to register new - packages. + Necessary so that `Workflow.create.Function(...)` returns an unowned function node, + while `some_workflow_instance.create.Function(...)` returns a function node owned + by the workflow instance. """ - def __init__(self, parent: Composite): - self._parent: Composite = parent - self._creator: Creator = Creator() - self.register_nodes("atomistics", *atomistics.nodes) - self.register_nodes("standard", *standard.nodes) + def __init__(self, parent: Composite, creator: Creator): + self._parent = parent + self._creator = creator + + def __getattr__(self, item): + value = getattr(self._creator, item) + + try: + is_node_class = issubclass(value, Node) + except TypeError: + # issubclass complains if the value isn't even a class + is_node_class = False + + if is_node_class: + value = partial(value, parent=self._parent) + elif isinstance(value, NodePackage): + value = OwnedNodePackage(self._parent, value) - def __getattr__(self, key): - value = getattr(self._creator, key) - if issubclass(value, Node): - return partial(value, parent=self._parent) return value - def __call__(self, node: Node): - return self._parent.add_node(node) - def register_nodes(self, domain: str, *nodes: list[type[Node]]): - """ - Add a list of node classes to be accessible for creation under the provided - domain name. +class OwnedNodePackage: + """ + A wrapper for node packages so that accessed node classes can have their parent + value automatically filled. + """ + def __init__(self, parent: Composite, node_package: NodePackage): + self._parent = parent + self._node_package = node_package - TODO: multiple dispatch so we can handle registering something other than a - list, e.g. modules or even urls. - """ - setattr(self, domain, NodePackage(self._parent, *nodes)) + def __getattr__(self, item): + value = getattr(self._node_package, item) + if issubclass(value, Node): + value = partial(value, parent=self._parent) + return value diff --git a/pyiron_contrib/workflow/interfaces.py b/pyiron_contrib/workflow/interfaces.py index 11c84929d..42ab5bfe2 100644 --- a/pyiron_contrib/workflow/interfaces.py +++ b/pyiron_contrib/workflow/interfaces.py @@ -4,6 +4,8 @@ from __future__ import annotations +from typing import TYPE_CHECKING + from pyiron_base.interfaces.singleton import Singleton from pyiron_contrib.executors import CloudpickleProcessPoolExecutor @@ -11,6 +13,10 @@ Function, SingleValue, Slow, function_node, single_value_node, slow_node ) +if TYPE_CHECKING: + from pyiron_contrib.workflow.composite import Composite + from pyiron_contrib.workflow.node import Node + class Creator(metaclass=Singleton): """ @@ -43,6 +49,30 @@ def Workflow(self): self._workflow = Workflow return self._workflow + @property + def standard(self): + try: + return self._standard + except AttributeError: + from pyiron_contrib.workflow.node_library.standard import nodes + self.register("_standard", *nodes) + return self._standard + + @property + def atomistics(self): + try: + return self._atomistics + except AttributeError: + from pyiron_contrib.workflow.node_library.atomistics import nodes + self.register("_atomistics", *nodes) + return self._atomistics + + def register(self, domain: str, *nodes: list[type[Node]]): + if domain in self.__dir__(): + raise AttributeError(f"{domain} is already an attribute of {self}") + from pyiron_contrib.workflow.node_package import NodePackage + setattr(self, domain, NodePackage(*nodes)) + class Wrappers(metaclass=Singleton): """ diff --git a/pyiron_contrib/workflow/node_library/package.py b/pyiron_contrib/workflow/node_package.py similarity index 70% rename from pyiron_contrib/workflow/node_library/package.py rename to pyiron_contrib/workflow/node_package.py index 748e4ed75..56c990a9b 100644 --- a/pyiron_contrib/workflow/node_library/package.py +++ b/pyiron_contrib/workflow/node_package.py @@ -1,19 +1,12 @@ from __future__ import annotations -from functools import partial -from typing import TYPE_CHECKING - from pyiron_contrib.workflow.node import Node from pyiron_contrib.workflow.util import DotDict -if TYPE_CHECKING: - from pyiron_contrib.workflow.composite import Composite - class NodePackage(DotDict): """ - A collection of node classes that, when instantiated, will have their workflow - automatically set. + A collection of node classes. Node classes are accessible by their _class name_ by item or attribute access. @@ -21,9 +14,8 @@ class NodePackage(DotDict): but to update an existing node the `update` method must be used. """ - def __init__(self, parent: Composite, *node_classes: Node): + def __init__(self, *node_classes: Node): super().__init__() - self.__dict__["_parent"] = parent # Avoid the __setattr__ override for node in node_classes: self[node.__name__] = node @@ -42,13 +34,6 @@ def __setitem__(self, key, value): ) super().__setitem__(key, value) - def __getitem__(self, item): - value = super().__getitem__(item) - if issubclass(value, Node): - return partial(value, parent=self._parent) - else: - return value - def update(self, *node_classes): replacing = set(self.keys()).intersection([n.__name__ for n in node_classes]) for name in replacing: diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 09343264c..394573165 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -154,7 +154,7 @@ def __init__( ) for node in nodes: - self.add_node(node) + self.add(node) @property def inputs(self) -> Inputs: diff --git a/tests/unit/workflow/test_node_package.py b/tests/unit/workflow/test_node_package.py index 4e89db0b4..82cf8e2cd 100644 --- a/tests/unit/workflow/test_node_package.py +++ b/tests/unit/workflow/test_node_package.py @@ -1,11 +1,11 @@ from unittest import TestCase, skipUnless from sys import version_info -from pyiron_contrib.workflow.node_library.package import NodePackage -from pyiron_contrib.workflow.workflow import Workflow +from pyiron_contrib.workflow.node_package import NodePackage +from pyiron_contrib.workflow.function import function_node -@Workflow.wrap_as.function_node() +@function_node() def dummy(x: int = 0): return x @@ -13,8 +13,7 @@ def dummy(x: int = 0): @skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestNodePackage(TestCase): def setUp(self) -> None: - self.wf = Workflow("test_workflow") - self.package = NodePackage(self.wf, dummy) + self.package = NodePackage(dummy) def test_init(self): self.assertTrue( @@ -25,11 +24,6 @@ def test_init(self): def test_access(self): node = self.package.Dummy() self.assertIsInstance(node, dummy) - self.assertIs( - node.parent, - self.package._parent, - msg="Package workflow should get assigned to node instances" - ) def test_update(self): with self.assertRaises(KeyError): @@ -41,7 +35,7 @@ def test_update(self): with self.assertRaises(TypeError): self.package.available_name = "But we can still only assign node classes" - @Workflow.wrap_as.function_node(output_label="y") + @function_node(output_label="y") def add(x: int = 0): return x + 1 @@ -53,7 +47,7 @@ def add(x: int = 0): old_dummy_instance = self.package.Dummy(label="old_dummy_instance") - @Workflow.wrap_as.function_node() + @function_node() def dummy(x: int = 0): y = x + 1 return y diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index b4588c33f..fd01db5e2 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -20,10 +20,10 @@ def test_node_addition(self): wf = Workflow("my_workflow") # Validate the four ways to add a node - wf.add(wf.create.Function(plus_one, label="foo")) - wf.add.Function(plus_one, label="bar") + wf.add(Workflow.create.Function(plus_one, label="foo")) + wf.create.Function(plus_one, label="bar") wf.baz = wf.create.Function(plus_one, label="whatever_baz_gets_used") - wf.create.Function(plus_one, label="qux", parent=wf) + Workflow.create.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(wf.create.Function(plus_one, label="foo")) - wf.add.Function(plus_one, label="bar") + wf.add(Workflow.create.Function(plus_one, label="foo")) + wf.create.Function(plus_one, label="bar") wf.baz = wf.create.Function( plus_one, label="without_strict_you_can_override_by_assignment" ) - wf.create.Function(plus_one, label="boa", parent=wf) + Workflow.create.Function(plus_one, label="boa", parent=wf) self.assertListEqual( list(wf.nodes.keys()), [ @@ -56,21 +56,21 @@ def test_node_addition(self): wf.add(wf.create.Function(plus_one, label="foo")) with self.assertRaises(AttributeError): - wf.add.Function(plus_one, label="bar") + wf.create.Function(plus_one, label="bar") with self.assertRaises(AttributeError): wf.baz = wf.create.Function(plus_one, label="whatever_baz_gets_used") with self.assertRaises(AttributeError): - wf.create.Function(plus_one, label="boa", parent=wf) + Workflow.create.Function(plus_one, label="boa", parent=wf) def test_node_packages(self): wf = Workflow("my_workflow") # Test invocation - wf.add.atomistics.Bulk(cubic=True, element="Al") + wf.create.atomistics.Bulk(cubic=True, element="Al") # Test invocation with attribute assignment - wf.engine = wf.add.atomistics.Lammps(structure=wf.bulk) + wf.engine = wf.create.atomistics.Lammps(structure=wf.bulk) self.assertSetEqual( set(wf.nodes.keys()), @@ -81,8 +81,8 @@ def test_node_packages(self): def test_double_workfloage_and_node_removal(self): wf1 = Workflow("one") - wf1.add.Function(plus_one, label="node1") - node2 = wf1.create.Function( + wf1.create.Function(plus_one, label="node1") + node2 = Workflow.create.Function( plus_one, label="node2", parent=wf1, x=wf1.node1.outputs.y ) self.assertTrue(node2.connected) @@ -98,9 +98,9 @@ def test_double_workfloage_and_node_removal(self): def test_workflow_io(self): wf = Workflow("wf") - wf.add.Function(plus_one, label="n1") - wf.add.Function(plus_one, label="n2") - wf.add.Function(plus_one, label="n3") + wf.create.Function(plus_one, label="n1") + wf.create.Function(plus_one, label="n2") + wf.create.Function(plus_one, label="n3") with self.subTest("Workflow IO should be drawn from its nodes"): self.assertEqual(len(wf.inputs), 3) @@ -135,7 +135,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(plus_one) + wf.create.Function(plus_one) self.assertTrue( str(wf.plus_one.working_directory.path).endswith(wf.plus_one.label) ) @@ -211,8 +211,8 @@ def sum(a, b): def test_call(self): wf = Workflow("wf") - wf.a = wf.add.SingleValue(plus_one) - wf.b = wf.add.SingleValue(plus_one) + wf.a = wf.create.SingleValue(plus_one) + wf.b = wf.create.SingleValue(plus_one) @Workflow.wrap_as.single_value_node(output_labels="sum") def sum_(a, b): @@ -241,8 +241,8 @@ def sum_(a, b): 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) + wf.a = wf.create.SingleValue(plus_one) + wf.b = wf.create.SingleValue(plus_one, x=wf.a) with self.subTest("Run on main process"): return_on_call = wf(a_x=1) From 7992d864664cc56b41271d0f474133830149a787 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 9 Aug 2023 15:43:46 -0700 Subject: [PATCH 561/756] Update docstrings --- pyiron_contrib/workflow/composite.py | 4 +++ pyiron_contrib/workflow/macro.py | 21 ++++++------- pyiron_contrib/workflow/workflow.py | 44 ++++++++++++++++++---------- 3 files changed, 43 insertions(+), 26 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 2805e6fa2..fb45dc2fc 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -70,6 +70,10 @@ class Composite(Node, ABC): add(node: Node): Add the node instance to this subgraph. remove(node: Node): Break all connections the node has, remove it from this subgraph, and set its parent to `None`. + + TODO: + Wrap node registration at the class level so we don't need to do + `X.create.register` but can just do `X.register` """ wrap_as = Wrappers() diff --git a/pyiron_contrib/workflow/macro.py b/pyiron_contrib/workflow/macro.py index 40bafdbf9..b6f3eee75 100644 --- a/pyiron_contrib/workflow/macro.py +++ b/pyiron_contrib/workflow/macro.py @@ -32,7 +32,6 @@ class Macro(Composite): Examples: Let's consider the simplest case of macros that just consecutively add 1 to their input: - >>> from pyiron_contrib.workflow.function import SingleValue >>> from pyiron_contrib.workflow.macro import Macro >>> >>> def add_one(x): @@ -40,9 +39,9 @@ class Macro(Composite): ... return result >>> >>> def add_three_macro(macro): - ... macro.one = SingleValue(add_one) - ... macro.two = SingleValue(add_one, macro.one) - ... macro.three = SingleValue(add_one, macro.two) + ... macro.one = macro.create.SingleValue(add_one) + ... macro.two = macro.create.SingleValue(add_one, macro.one) + ... macro.three = macro.create.SingleValue(add_one, macro.two) We can make a macro by passing this graph-building function (that takes a macro as its first argument, i.e. `self` from the macro's perspective) to the `Macro` @@ -76,14 +75,16 @@ class Macro(Composite): We can also nest macros, rename their IO, and provide access to internally-connected IO: >>> def nested_macro(macro): - ... macro.a = SingleValue(add_one) - ... macro.b = Macro(add_three_macro, one_x=macro.a) - ... macro.c = SingleValue(add_one, x=macro.b.outputs.three_result) + ... macro.a = macro.create.SingleValue(add_one) + ... macro.b = macro.create.Macro(add_three_macro, one_x=macro.a) + ... macro.c = macro.create.SingleValue( + ... add_one, x=macro.b.outputs.three_result + ... ) >>> >>> macro = Macro( ... nested_macro, ... inputs_map={"a_x": "inp"}, - ... outputs_map={"c_result": "out", "b_result": "intermediate"}, + ... outputs_map={"c_result": "out", "b_three_result": "intermediate"}, ... ) >>> macro(inp=1) {'intermediate': 5, 'out': 6} @@ -96,8 +97,8 @@ class Macro(Composite): running when they get their values updated, just so we can see that one of them is really not doing anything on the run command): >>> def modified_start_macro(macro): - ... macro.a = SingleValue(add_one, x=0, run_on_updates=False) - ... macro.b = SingleValue(add_one, x=0, run_on_updates=False) + ... macro.a = macro.create.SingleValue(add_one, x=0, run_on_updates=False) + ... macro.b = macro.create.SingleValue(add_one, x=0, run_on_updates=False) ... macro.starting_nodes = [macro.b] >>> >>> m = Macro(modified_start_macro, update_on_instantiation=False) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 394573165..fac4e16c6 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -29,23 +29,35 @@ class Workflow(Composite): Using the `input` and `output` attributes, the workflow gives access to all the IO channels among its nodes which are currently unconnected. + The `Workflow` class acts as a single-point-of-import for us; + Directly from the class we can use the `create` method to instantiate workflow + objects. + When called from a workflow _instance_, any created nodes get their parent set to + the workflow instance being used. + Examples: We allow adding nodes to workflows in five equivalent ways: >>> from pyiron_contrib.workflow.workflow import Workflow - >>> from pyiron_contrib.workflow.function import Function >>> >>> def fnc(x=0): ... return x + 1 >>> - >>> n1 = Function(fnc, label="n1") + >>> # (1) As *args at instantiation + >>> n1 = Workflow.create.Function(fnc, label="n1") + >>> wf = Workflow("my_workflow", n1) + >>> + >>> # (2) Being passed to the `add` method + >>> wf.add(Workflow.create.Function(fnc, label="n2")) + >>> + >>> # (3) Calling `create` from the _workflow instance_ that will own the node + >>> wf.create.Function(fnc, label="n3") # Instantiating from add + >>> + >>> # (4) By attribute assignment (here the node can be created from the + >>> # workflow class or instance and the end result is the same + >>> wf.n4 = wf.create.Function(fnc, label="anyhow_n4_gets_used") >>> - >>> wf = Workflow("my_workflow", n1) # As *args at instantiation - >>> wf.add(Function(fnc, label="n2")) # Passing a node to the add caller - >>> wf.add.Function(fnc, label="n3") # Instantiating from add - >>> wf.n4 = Function(fnc, label="whatever_n4_gets_used") - >>> # By attribute assignment - >>> Function(fnc, label="n5", parent=wf) - >>> # By instantiating the node with a workflow + >>> # (5) By creating from the workflow class but specifying the parent kwarg + >>> Workflow.create.Function(fnc, label="n5", parent=wf) By default, the node naming scheme is strict, so if you try to add a node to a label that already exists, you will get an error. This behaviour can be changed @@ -53,9 +65,9 @@ class Workflow(Composite): bool to this property. When deactivated, repeated assignments to the same label just get appended with an index: >>> wf.strict_naming = False - >>> wf.my_node = Function(fnc, x=0) - >>> wf.my_node = Function(fnc, x=1) - >>> wf.my_node = Function(fnc, x=2) + >>> wf.my_node = wf.create.Function(fnc, x=0) + >>> wf.my_node = wf.create.Function(fnc, x=1) + >>> wf.my_node = wf.create.Function(fnc, x=2) >>> print(wf.my_node.inputs.x, wf.my_node0.inputs.x, wf.my_node1.inputs.x) 0, 1, 2 @@ -101,17 +113,17 @@ class Workflow(Composite): namespaces, e.g. >>> wf = Workflow("with_prebuilt") >>> - >>> wf.structure = wf.add.atomistics.Bulk( + >>> wf.structure = wf.create.atomistics.Bulk( ... cubic=True, ... element="Al" ... ) - >>> wf.engine = wf.add.atomistics.Lammps(structure=wf.structure) - >>> wf.calc = wf.add.atomistics.CalcMd( + >>> wf.engine = wf.create.atomistics.Lammps(structure=wf.structure) + >>> wf.calc = wf.create.atomistics.CalcMd( ... job=wf.engine, ... run_on_updates=True, ... update_on_instantiation=True, ... ) - >>> wf.plot = wf.add.standard.Scatter( + >>> wf.plot = wf.create.standard.Scatter( ... x=wf.calc.outputs.steps, ... y=wf.calc.outputs.temperature ... ) From b55740187e3c6cf7e59baafd759330ecd9013431 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 9 Aug 2023 15:50:50 -0700 Subject: [PATCH 562/756] Update example notebook --- notebooks/workflow_example.ipynb | 681 ++++++++++++++++--------------- 1 file changed, 342 insertions(+), 339 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 7daf21e0e..66420b0cb 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -11,7 +11,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "9b4519052d45432fa6e92b5cf667fc56", + "model_id": "89e12c6b0d0543659659b75b37c94bcd", "version_major": 2, "version_minor": 0 }, @@ -716,7 +716,7 @@ "outputs": [ { "data": { - "image/png": "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ZD311FNWZ2endd9991lLliyx/ud//mfS/ufOnbMuu+wy67777rM6Ozutp556ylq0aJH1wgsvzHHlsc3uuN93333Wt771Lautrc168803rerqamvRokXW6dOn57jy2Gd37Mf99re/ta666iqrtLTUuu666+am2DgSybh/6UtfsoqKiiyfz2d1dXVZ//Ef/2G9/vrrc1h1fLA79i0tLVZCQoL1+OOPW+fOnbNaWlqsdevWWVu3bp3jymNbU1OTtXfvXuv48eOWJOull16atn80v1/nfRgpLCy0KioqwtquueYaa8+ePZP2/7u/+zvrmmuuCWu76667rPXr10etxnhkd9wnk5uba9XW1s52aXEv0rEvLy+3/uEf/sGqqakhjETA7rj/+Mc/ttxutzUwMDAX5cU1u2P/ne98x7rqqqvC2r73ve9ZK1eujFqN8W4mYSSa36/zeplmZGRE7e3tKi0tDWsvLS1Va2vrpOf8/Oc/n9D/C1/4gk6dOqX3338/arXGk0jG/aPGxsY0ODio1NTUaJQYtyId+2eeeUZvv/22ampqol1iXIpk3H/0ox+poKBA3/72t3XFFVfo6quv1gMPPKA//OEPc1Fy3Ihk7EtKSnT+/Hk1NTXJsiy9++67euGFFyJ6KjxmLprfr7YflDeX+vv7NTo6qvT09LD29PT00NN/P6q3t3fS/h988IH6+/vl8XiiVm+8iGTcP+rRRx/V73//e91yyy3RKDFuRTL2b731lvbs2aOWlhYlJs7r/0nPW5GM+7lz53Ty5Em5XC699NJL6u/v1ze+8Q1dvHiR60ZsiGTsS0pKdPToUZWXl2toaEgffPCBvvSlL+n73//+XJS8YEXz+3Vez4yMczgcYa8ty5rQ9nH9J2vH9OyO+7hjx47p4YcfVmNjo5YvXx6t8uLaTMd+dHRUt912m2pra3X11VfPVXlxy87f/NjYmBwOh44eParCwkLddNNN2r9/v5599llmRyJgZ+w7Ozt177336qGHHlJ7e7uam5vV1dWlioqKuSh1QYvW9+u8/mdUWlqanE7nhHTc19c3IZ2Ny8jImLR/YmKili1bFrVa40kk4z6usbFRO3fu1PPPP68bb7wxmmXGJbtjPzg4qFOnTqmjo0P33HOPpEtfkpZlKTExUa+88oquv/76Oak9lkXyN+/xeHTFFVeEPR49JydHlmXp/PnzWrNmTVRrjheRjH1dXZ02bNigBx98UJJ07bXXasmSJdq4caMeeeQRZsCjJJrfr/N6ZiQpKUler1c+ny+s3efzqaSkZNJziouLJ/R/5ZVXVFBQoEWLFkWt1ngSybhLl2ZE7rjjDj333HOs3UbI7tinpKTol7/8pc6cORM6KioqtHbtWp05c0ZFRUVzVXpMi+RvfsOGDXrnnXf0u9/9LtT25ptvKiEhQStXroxqvfEkkrF/7733lJAQ/vXldDol/f9/qWP2RfX79RNfAhtl47d8HTx40Ors7LQqKyutJUuWWL/5zW8sy7KsPXv2WNu3bw/1H7/16P7777c6OzutgwcPcmtvBOyO+3PPPWclJiZaTzzxhOX3+0PHb3/7W1MfIWbZHfuP4m6ayNgd98HBQWvlypXWX/7lX1q/+tWvrFdffdVas2aNtWvXLlMfIWbZHftnnnnGSkxMtOrr6623337bOnnypFVQUGAVFhaa+ggxaXBw0Oro6LA6OjosSdb+/futjo6O0C3Vc/n9Ou/DiGVZ1hNPPGFlZWVZSUlJVn5+vvXqq6+Gfnb77bdbn/vc58L6/+xnP7M++9nPWklJSdaqVaushoaGOa44PtgZ98997nOWpAnH7bffPveFxwG7f/MfRhiJnN1xf+ONN6wbb7zRWrx4sbVy5UqrqqrKeu+99+a46vhgd+y/973vWbm5udbixYstj8dj/fVf/7V1/vz5Oa46tv30pz+d9v+35/L71WFZzGkBAABz5vU1IwAAIP4RRgAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABj1/wAygnrgjAYcMQAAAABJRU5ErkJggg==", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiMAAAGdCAYAAADAAnMpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAkkElEQVR4nO3df3BU1d3H8c9mY7JRk6WBkqwkjRE1JaQVCRNMKHVGNEJbKu20xlKwWOg09Aci1RkY2sYwncloW+vPZKQSrYJKK9rCiLSZqdUgj6ZC6DRGxULaBNiYSaibWJvQJuf5gycZ9kkCuZvNnt3s+zVz/9iTe7Pf7yxkP3Puvee6jDFGAAAAliTYLgAAAMQ3wggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqxJtFzAWAwMDOnnypFJTU+VyuWyXAwAAxsAYo56eHl1yySVKSBh9/iMmwsjJkyeVnZ1tuwwAABCCtrY2ZWVljfrzmAgjqampks40k5aWZrkaAAAwFt3d3crOzh76Hh9NTISRwVMzaWlphBEAAGLM+S6x4AJWAABgFWEEAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFUxsegZgPDoHzBqaDmljp5eTU/1qCg3Xe4EnvcEwC7CCBAn9jX5VbmnWf5A79CYz+tRxdJ8LS7wWawMQLzjNA0QB/Y1+bV2+6GgICJJ7YFerd1+SPua/JYqAwDCCDDp9Q8YVe5plhnhZ4NjlXua1T8w0h4AMPEII8Ak19ByatiMyNmMJH+gVw0tpyJXFACchTACTHIdPaMHkVD2A4BwI4wAk9z0VE9Y9wOAcCOMAJNcUW66fF6PRruB16Uzd9UU5aZHsiwAGEIYASY5d4JLFUvzJWlYIBl8XbE0n/VGAFhDGAHiwOICn2pWzFWmN/hUTKbXo5oVc1lnBIBVLHoGxInFBT7dkJ/JCqwAog5hBIgj7gSXimdOtV0GAAThNA0AALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAq0IKI9XV1crNzZXH41FhYaHq6+vPuf+OHTt01VVX6cILL5TP59Ntt92mrq6ukAoGAACTi+MwsnPnTq1fv16bN29WY2OjFi5cqCVLlqi1tXXE/ffv369bb71Vq1ev1ltvvaXf/OY3+vOf/6w1a9aMu3gAABD7HIeR++67T6tXr9aaNWs0a9Ys3X///crOzlZNTc2I+7/++uu69NJLtW7dOuXm5uozn/mMvv3tb+vNN98cd/EAACD2OQojp0+f1sGDB1VaWho0XlpaqgMHDox4TElJiY4fP669e/fKGKP3339fzz33nD7/+c+P+j59fX3q7u4O2gAAwOTkKIx0dnaqv79fGRkZQeMZGRlqb28f8ZiSkhLt2LFDZWVlSkpKUmZmpqZMmaKHHnpo1PepqqqS1+sd2rKzs52UCQAAYkhIF7C6XK6g18aYYWODmpubtW7dOv34xz/WwYMHtW/fPrW0tKi8vHzU379p0yYFAoGhra2tLZQyAQBADEh0svO0adPkdruHzYJ0dHQMmy0ZVFVVpQULFuiuu+6SJH3605/WRRddpIULF+onP/mJfD7fsGOSk5OVnJzspDQAABCjHM2MJCUlqbCwUHV1dUHjdXV1KikpGfGYjz76SAkJwW/jdrslnZlRAQAA8c3xaZoNGzboscceU21trd5++23dcccdam1tHTrtsmnTJt16661D+y9dulTPP/+8ampqdOzYMb322mtat26dioqKdMkll4SvEwAAEJMcnaaRpLKyMnV1dWnLli3y+/0qKCjQ3r17lZOTI0ny+/1Ba46sWrVKPT09evjhh/WDH/xAU6ZM0XXXXad77rknfF0AAICY5TIxcK6ku7tbXq9XgUBAaWlptssBAABjMNbvb55NAwAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAqkTbBQCAbf0DRg0tp9TR06vpqR4V5abLneCyXRYQNwgjAOLavia/Kvc0yx/oHRrzeT2qWJqvxQU+i5UB8YPTNADi1r4mv9ZuPxQURCSpPdCrtdsPaV+T31JlQHwhjACIS/0DRpV7mmVG+NngWOWeZvUPjLQHgHAijACISw0tp4bNiJzNSPIHetXQcipyRQFxijACIC519IweRELZD0DoCCMA4tL0VE9Y9wMQOsIIgLhUlJsun9ej0W7gdenM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zZuVk5Oj5ORkzZw5U7W1tRGqNjROely1atWIn+Hs2bMjWLEzTj/DHTt26KqrrtKFF14on8+n2267TV1dXRGq1jmn/T3yyCOaNWuWUlJSlJeXpyeffDK8BZk48+yzz5oLLrjA/PKXvzTNzc3m9ttvNxdddJH5xz/+MeL+LS0tZt26deZXv/qVmTNnjrn99tsjW7BDTvu7/fbbzT333GMaGhrMkSNHzKZNm8wFF1xgDh06FOHKx8Zpf4cOHTJPP/20aWpqMi0tLeapp54yF154oXn00UcjXPnYOe1x0AcffGAuu+wyU1paaq666qrIFBsCp/29/PLLRpJ59913jd/vH9r++9//RrjysQnl8/viF79o5s+fb+rq6kxLS4t54403zGuvvRbBqp1x2uMHH3wQ9Nm1tbWZ9PR0U1FREdnCx8hpf/X19SYhIcE88MAD5tixY6a+vt7Mnj3bLFu2LMKVj43T/qqrq01qaqp59tlnzdGjR80zzzxjLr74YrN79+6w1RR3YaSoqMiUl5cHjX3yk580GzduPO+x1157bdSHkfH0Nyg/P99UVlaGu7SwCEd/X/rSl8yKFSvCXVrYhNpjWVmZ+eEPf2gqKiqiOow47W8wjPzzn/+MQHXj57S/l156yXi9XtPV1RWJ8sJivP8PX3jhBeNyuczf//73iShv3Jz299Of/tRcdtllQWMPPvigycrKmrAax8Npf8XFxebOO+8MGrv99tvNggULwlZTXJ2mOX36tA4ePKjS0tKg8dLSUh04cMBSVeETjv4GBgbU09Oj9PT0iShxXMLRX2Njow4cOKBrr712Ikoct1B7fPzxx3X06FFVVFRMdInjMp7P8Oqrr5bP59OiRYv08ssvT2SZIQulv927d2vevHm69957NWPGDF155ZW688479e9//zsSJTsWjv+H27Zt0/XXX6+cnJyJKHFcQumvpKREx48f1969e2WM0fvvv6/nnntOn//85yNRsiOh9NfX1yePxxM0lpKSooaGBv3nP/8JS11xFUY6OzvV39+vjIyMoPGMjAy1t7dbqip8wtHfz3/+c/3rX//SzTffPBEljst4+svKylJycrLmzZun7373u1qzZs1ElhqyUHp87733tHHjRu3YsUOJidH97MtQ+vP5fNq6dat27dql559/Xnl5eVq0aJFeffXVSJTsSCj9HTt2TPv371dTU5NeeOEF3X///Xruuef03e9+NxIlOzbevzN+v18vvfTSpPo/WFJSoh07dqisrExJSUnKzMzUlClT9NBDD0WiZEdC6e/GG2/UY489poMHD8oYozfffFO1tbX6z3/+o87OzrDUFd1/uSaIy+UKem2MGTYWy0Lt75lnntHdd9+t3/3ud5o+ffpElTduofRXX1+vDz/8UK+//ro2btyoyy+/XF/72tcmssxxGWuP/f39Wr58uSorK3XllVdGqrxxc/IZ5uXlKS8vb+h1cXGx2tra9LOf/Uyf/exnJ7TOUDnpb2BgQC6XSzt27Bh6uul9992nr3zlK3rkkUeUkpIy4fWGItS/M0888YSmTJmiZcuWTVBl4eGkv+bmZq1bt04//vGPdeONN8rv9+uuu+5SeXm5tm3bFolyHXPS349+9CO1t7frmmuukTFGGRkZWrVqle6991653e6w1BNXMyPTpk2T2+0elv46OjqGpcRYNJ7+du7cqdWrV+vXv/61rr/++oksM2Tj6S83N1ef+tSn9K1vfUt33HGH7r777gmsNHROe+zp6dGbb76p733ve0pMTFRiYqK2bNmiv/zlL0pMTNQf//jHSJU+JuH6P3jNNdfovffeC3d54xZKfz6fTzNmzAh6zPqsWbNkjNHx48cntN5QjOczNMaotrZWK1euVFJS0kSWGbJQ+quqqtKCBQt011136dOf/rRuvPFGVVdXq7a2Vn6/PxJlj1ko/aWkpKi2tlYfffSR/v73v6u1tVWXXnqpUlNTNW3atLDUFVdhJCkpSYWFhaqrqwsar6urU0lJiaWqwifU/p555hmtWrVKTz/9dFSe4xwUrs/PGKO+vr5wlxcWTntMS0vTX//6Vx0+fHhoKy8vV15eng4fPqz58+dHqvQxCddn2NjYKJ/PF+7yxi2U/hYsWKCTJ0/qww8/HBo7cuSIEhISlJWVNaH1hmI8n+Err7yiv/3tb1q9evVEljguofT30UcfKSEh+Ot0cMbARNnj38bz+V1wwQXKysqS2+3Ws88+qy984QvD+g5Z2C6FjRGDtzRt27bNNDc3m/Xr15uLLrpo6KrujRs3mpUrVwYd09jYaBobG01hYaFZvny5aWxsNG+99ZaN8s/LaX9PP/20SUxMNI888kjQrXcffPCBrRbOyWl/Dz/8sNm9e7c5cuSIOXLkiKmtrTVpaWlm8+bNtlo4r1D+jZ4t2u+mcdrfL37xC/PCCy+YI0eOmKamJrNx40YjyezatctWC+fktL+enh6TlZVlvvKVr5i33nrLvPLKK+aKK64wa9assdXCeYX6b3TFihVm/vz5kS7XMaf9Pf744yYxMdFUV1ebo0ePmv3795t58+aZoqIiWy2ck9P+3n33XfPUU0+ZI0eOmDfeeMOUlZWZ9PR009LSEraa4i6MGGPMI488YnJyckxSUpKZO3eueeWVV4Z+9o1vfMNce+21QftLGrbl5OREtmgHnPR37bXXjtjfN77xjcgXPkZO+nvwwQfN7NmzzYUXXmjS0tLM1Vdfbaqrq01/f7+FysfO6b/Rs0V7GDHGWX/33HOPmTlzpvF4POZjH/uY+cxnPmNefPFFC1WPndPP7+233zbXX3+9SUlJMVlZWWbDhg3mo48+inDVzjjt8YMPPjApKSlm69atEa40NE77e/DBB01+fr5JSUkxPp/PfP3rXzfHjx+PcNVj56S/5uZmM2fOHJOSkmLS0tLMTTfdZN55552w1uMyJsrmkAAAQFyJq2tGAABA9CGMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsOp/AdzC0SP69nD3AAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -805,7 +805,7 @@ "outputs": [ { "data": { - "image/png": "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Wloac35+fr4WLFiQslqzQSLHe0Z7e7vuuusuPf/887r++utTWWbWcHq8JyYm9NZbb6mvr0/f+ta3JE2fMI0xys/P16uvvqrrrrsuLbW7TSJ/twOBgJYsWRLxqPSVK1fKGKNTp07piiuuSGnNbpbI8W5padGaNWt0//33S5I+85nP6LLLLtPatWv14IMPsqqdZOk4V2bNykhBQYGqqqrU1dUVMd7V1aXa2tqY76mpqYma/+qrr2rVqlWaN29eymrNBokcb2l6ReSOO+7Qs88+y/VdB5we76KiIv3lL3/RkSNHZl+NjY361Kc+pSNHjmj16tXpKt11Evm7vWbNGp0+fVr/+te/Zsfeeecd5eXlaenSpSmt1+0SOd7vv/++8vIiT19er1fS///EjuRJy7kyabfCZoCZ7WFPPfWU6e/vN1u3bjWXXXaZ+fvf/26MMWbHjh3mtttum50/s11p27Ztpr+/3zz11FNs7XXA6fF+9tlnTX5+vnniiSdMMBicff3zn/+09Su4itPj/WHspomf02M9MTFhli5dar7yla+Yv/71r+b11183V1xxhbn77rtt/Qqu4vR4P/300yY/P9+0traa48ePmwMHDphVq1aZ6upqW7+Cq0xMTJi+vj7T19dnJJlHHnnE9PX1zW6ltnGuzKowYowxTzzxhFm+fLkpKCgwlZWV5vXXX5/93u23324+97nPRcz/4x//aD772c+agoIC84lPfMK0tbWluWJ3c3K8P/e5zxlJUa/bb789/YW7lNO/3x9EGHHG6bE+duyYuf76680ll1xili5darZv327ef//9NFftXk6P92OPPWbKy8vNJZdcYgKBgPnqV79qTp06leaq3ekPf/jDef8vtnGu9BjDmhYAALAna+4ZAQAA7kQYAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFWEEQAAYNX/BZ6XXB7rq5klAAAAAElFTkSuQmCC", + "image/png": "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pwAAAK7juE9KOtAnBQCAzHOlr98Z8S7I0RxFvxQAADJH9HU72fWQjAgpU1NTkkS/FAAAMtDU1JT8fr/j4zLick8kEtH58+dVUFBwyX4sTk1OTqqsrExnz57lMlIKcd5Tj3OeHpz39OC8p95c59wYo6mpKa1YsUI5Oc5bs2XESkpOTo5Wrly5aF+/sLCQX+Q04LynHuc8PTjv6cF5T71E5zyZFZSopDrOAgAALDZCCgAAsFJWh5T8/Hw1NzfTgj/FOO+pxzlPD857enDeU2+xznlG3DgLAACyT1avpAAAAHsRUgAAgJUIKQAAwEqEFAAAYCXXh5T9+/ervLxcPp9P1dXV6unpueT8119/XdXV1fL5fLr22mv1ne98J0WVuouT8/7yyy9rw4YN+uhHP6rCwkLV1tbqpz/9aQqrdQenv+tRb775pnJzc3XTTTctboEu5fS8X7hwQXv37tWqVauUn5+v1atX68CBAymq1j2cnvdDhw7pxhtv1NVXX61AIKD7779f4+PjKao2873xxhtqaGjQihUr5PF49OMf//iyxyzI66lxsR/+8IdmyZIl5plnnjGDg4PmoYceMkuXLjW//vWvE84/c+aMufrqq81DDz1kBgcHzTPPPGOWLFliXnrppRRXntmcnveHHnrIPP7446a3t9e89dZbZs+ePWbJkiXmxIkTKa48czk951G//e1vzbXXXmvq6+vNjTfemJpiXSSZ837nnXeadevWme7ubjM0NGT+8z//07z55psprDrzOT3vPT09Jicnxzz55JPmzJkzpqenx3z84x83mzdvTnHlmauzs9Ps3bvX/OhHPzKSzCuvvHLJ+Qv1eurqkFJTU2Oamppixm644Qaze/fuhPP//u//3txwww0xY1/84hfNzTffvGg1upHT855IRUWFaWlpWejSXCvZc97Y2Gj+8R//0TQ3NxNSkuD0vP/rv/6r8fv9Znx8PBXluZbT8/7P//zP5tprr40Ze+qpp8zKlSsXrUY3m09IWajXU9de7rl48aL6+vpUX18fM15fX6+jR48mPObYsWNx8z/zmc/o+PHj+sMf/rBotbpJMuf9wyKRiKamplRUVLQYJbpOsuf8+eef1+nTp9Xc3LzYJbpSMuf91Vdf1dq1a/X1r39d11xzja6//np99atf1e9///tUlOwKyZz3uro6nTt3Tp2dnTLG6De/+Y1eeukl3XHHHakoOSst1OtpRrzBYDLGxsYUDodVUlISM15SUqKRkZGEx4yMjCSc//7772tsbEyBQGDR6nWLZM77h33jG9/Qu+++q7vuumsxSnSdZM7522+/rd27d6unp0e5ua59GlhUyZz3M2fO6MiRI/L5fHrllVc0NjamL33pS5qYmOC+lHlK5rzX1dXp0KFDamxs1PT0tN5//33deeed+ta3vpWKkrPSQr2eunYlJcrj8cR8bIyJG7vc/ETjuDSn5z3q8OHD+trXvqb29nYVFxcvVnmuNN9zHg6Hdc8996ilpUXXX399qspzLSe/65FIRB6PR4cOHVJNTY1uv/12PfHEE/r+97/PaopDTs774OCgtm/frkcffVR9fX3q6urS0NCQmpqaUlFq1lqI11PX/gu1fPlyeb3euGQ9Ojoal+6iSktLE87Pzc3VsmXLFq1WN0nmvEe1t7frgQce0IsvvqjbbrttMct0FafnfGpqSsePH1d/f7++8pWvSJp58TTGKDc3V6+99ppuvfXWlNSeyZL5XQ8EArrmmmti3rp+zZo1Msbo3Llzuu666xa1ZjdI5ry3trbqlltu0cMPPyxJ+sQnPqGlS5dq/fr1euyxx1glXwQL9Xrq2pWUvLw8VVdXq7u7O2a8u7tbdXV1CY+pra2Nm//aa69p7dq1WrJkyaLV6ibJnHdpZgXlvvvu0wsvvMB1YoecnvPCwkL98pe/1MmTJ2cfTU1N+tM//VOdPHlS69atS1XpGS2Z3/VbbrlF58+f1+9+97vZsbfeeks5OTlauXLlotbrFsmc9/fee085ObEvd16vV9If/7vHwlqw11NHt9lmmOg2teeee84MDg6aHTt2mKVLl5r/+Z//McYYs3v3bnPvvffOzo9umdq5c6cZHBw0zz33HFuQk+D0vL/wwgsmNzfXPP300yYYDM4+fvvb36brR8g4Ts/5h7G7JzlOz/vU1JRZuXKl+fznP29+9atfmddff91cd9115sEHH0zXj5CRnJ73559/3uTm5pr9+/eb06dPmyNHjpi1a9eampqadP0IGWdqasr09/eb/v5+I8k88cQTpr+/f3bb92K9nro6pBhjzNNPP21WrVpl8vLyTFVVlXn99ddnP/eFL3zBfPrTn46Z/x//8R/mk5/8pMnLyzMf+9jHTFtbW4ordgcn5/3Tn/60kRT3+MIXvpD6wjOY09/1DyKkJM/peT916pS57bbbzFVXXWVWrlxpdu3aZd57770UV535nJ73p556ylRUVJirrrrKBAIB81d/9Vfm3LlzKa46c/37v//7JZ+nF+v11GMMa10AAMA+rr0nBQAAZDZCCgAAsBIhBQAAWImQAgAArERIAQAAViKkAAAAKxFSAACAlQgpAADASoQUAABgJUIKAACwEiEFAABYiZACAACs9P8Al9OAmjFo1BcAAAAASUVORK5CYII=", "text/plain": [ "
" ] @@ -863,7 +863,7 @@ "source": [ "## Adding nodes to a workflow\n", "\n", - "All five of the following approaches are equivalent ways to add a node to a workflow:" + "All five of the approaches below are equivalent ways to add a node to a workflow. Note that when `create` is called from the workflow _class_ it just gives you access to the class being created; when it is called from a workflow _instance_, it wraps this class so that the created node has its parent value automatically set to the workflow instance that's creating it." ] }, { @@ -877,6 +877,11 @@ "output_type": "stream", "text": [ "n1 n1 n1 (GreaterThanHalf) output single-value: False\n", + "n2 n2 n2 (Slow):\n", + "Inputs []\n", + "Outputs ['p1']\n", + "InputSignals ['run']\n", + "OutputSignals ['ran']\n", "n3 n3 n3 (GreaterThanHalf) output single-value: False\n", "n4 n4 n4 (GreaterThanHalf) output single-value: False\n", "n5 n5 n5 (GreaterThanHalf) output single-value: False\n" @@ -884,12 +889,10 @@ } ], "source": [ - "from pyiron_contrib.workflow.function import Slow\n", - "\n", "n1 = greater_than_half(label=\"n1\")\n", "\n", "wf = Workflow(\"my_wf\", n1) # As args at init\n", - "wf.add.Slow(lambda: x + 1, output_labels=\"p1\", label=\"n2\") # Instantiating from the class with a lambda function\n", + "wf.create.Slow(lambda: x + 1, output_labels=\"p1\", label=\"n2\") # Instantiating from the class with a lambda function\n", "# (Slow since we don't have an x default)\n", "wf.add(greater_than_half(label=\"n3\")) # Instantiating then passing to node adder\n", "wf.n4 = greater_than_half(label=\"will_get_overwritten_with_n4\") # Set attribute to instance\n", @@ -1038,9 +1041,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:311: UserWarning: Reassigning the node bulk to the label structure when adding it to the parent with_prebuilt.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:288: UserWarning: Reassigning the node bulk 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:311: 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:288: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent with_prebuilt.\n", " warn(\n" ] }, @@ -1055,9 +1058,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:311: 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:288: 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:311: 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:288: UserWarning: Reassigning the node scatter to the label plot when adding it to the parent with_prebuilt.\n", " warn(\n" ] }, @@ -1075,14 +1078,14 @@ "source": [ "wf = Workflow(\"with_prebuilt\")\n", "\n", - "wf.structure = wf.add.atomistics.Bulk(cubic=True, name=\"Al\")\n", - "wf.engine = wf.add.atomistics.Lammps(structure=wf.structure)\n", - "wf.calc = wf.add.atomistics.CalcMd(\n", + "wf.structure = wf.create.atomistics.Bulk(cubic=True, name=\"Al\")\n", + "wf.engine = wf.create.atomistics.Lammps(structure=wf.structure)\n", + "wf.calc = wf.create.atomistics.CalcMd(\n", " job=wf.engine, \n", " run_on_updates=True, \n", " update_on_instantiation=True\n", ")\n", - "wf.plot = wf.add.standard.Scatter(\n", + "wf.plot = wf.create.standard.Scatter(\n", " x=wf.calc.outputs.steps, \n", " y=wf.calc.outputs.temperature\n", ")" @@ -1947,7 +1950,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 34, @@ -2233,7 +2236,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 35, @@ -2307,9 +2310,9 @@ " macro.add_one = add_one(0)\n", " macro.add_two = add_one(macro.add_one)\n", " macro.add_three = add_one(macro.add_two)\n", - " macro.starting_nodes = [macro.add_one] \n", - " # Setting this starting node is silly, since as the head-most node it would \n", - " # have been the starting node anyway; the point is you have access to the \n", + " # macro.starting_nodes = [macro.add_one] \n", + " # Setting this starting node is silly, since as the head-most node \n", + " # it is the starting node anyway; the point is you have access to the \n", " # macro object and can do these sorts of setup proceedures here\n", " \n", "macro = Macro(add_three_macro)\n", @@ -2390,9 +2393,9 @@ "source": [ "@Workflow.wrap_as.macro_node()\n", "def lammps_minimize(macro):\n", - " macro.structure = macro.add.atomistics.Bulk(run_on_updates=False)\n", - " macro.engine = macro.add.atomistics.Lammps(structure=macro.structure, run_on_updates=False)\n", - " macro.calc = macro.add.atomistics.CalcMin(job=macro.engine, pressure=0)\n", + " macro.structure = macro.create.atomistics.Bulk(run_on_updates=False)\n", + " macro.engine = macro.create.atomistics.Lammps(structure=macro.structure, run_on_updates=False)\n", + " macro.calc = macro.create.atomistics.CalcMin(job=macro.engine, pressure=0)\n", " \n", " macro.engine.signals.input.run = macro.structure.signals.output.ran\n", " macro.calc.signals.input.run = macro.engine.signals.output.ran\n", @@ -2429,13 +2432,13 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:311: UserWarning: Reassigning the node user_input to the label element when adding it to the parent phase_preference.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:288: UserWarning: Reassigning the node user_input to the label element when adding it to the parent phase_preference.\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:311: UserWarning: Reassigning the node bulk to the label structure when adding it to the parent lammps_minimize.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:288: UserWarning: Reassigning the node bulk to the label structure when adding it to the parent lammps_minimize.\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:311: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent lammps_minimize.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:288: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent lammps_minimize.\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:311: UserWarning: Reassigning the node calc_min to the label calc when adding it to the parent lammps_minimize.\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:288: UserWarning: Reassigning the node calc_min to the label calc when adding it to the parent lammps_minimize.\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label name to the io key element\n", " warn(\n", @@ -2488,7 +2491,7 @@ ], "source": [ "wf = Workflow(\"phase_preference\")\n", - "wf.element = wf.add.standard.UserInput()\n", + "wf.element = wf.create.standard.UserInput()\n", "wf.min_phase1 = lammps_minimize(element=wf.element)\n", "wf.min_phase2 = lammps_minimize(element=wf.element)\n", "wf.compare = per_atom_energy_difference(\n", @@ -2631,160 +2634,160 @@ "clusterphase_preference\n", "\n", "phase_preference: Workflow\n", + "\n", + "clusterphase_preferenceInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterphase_preferenceOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", "\n", "clusterphase_preferenceelement\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "element: UserInput\n", "\n", "\n", "clusterphase_preferenceelementInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterphase_preferenceelementOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", "\n", "clusterphase_preferencemin_phase1\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "min_phase1: LammpsMinimize\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", "\n", "clusterphase_preferencemin_phase2\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "min_phase2: LammpsMinimize\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", "\n", "clusterphase_preferencecompare\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "compare: PerAtomEnergyDifference\n", "\n", "\n", "clusterphase_preferencecompareInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterphase_preferencecompareOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", - "\n", - "clusterphase_preferenceInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterphase_preferenceOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", "\n", "\n", "clusterphase_preferenceInputsrun\n", @@ -2820,394 +2823,394 @@ "\n", "\n", "clusterphase_preferenceInputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", "\n", "clusterphase_preferenceInputscrystalstructure->clusterphase_preferencemin_phase1Inputscrystalstructure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", "\n", "clusterphase_preferenceInputscrystalstructure->clusterphase_preferencemin_phase2Inputscrystalstructure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", "\n", "clusterphase_preferenceInputsa->clusterphase_preferencemin_phase1Inputsa\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", "\n", "clusterphase_preferenceInputsa->clusterphase_preferencemin_phase2Inputsa\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", "\n", "clusterphase_preferenceInputsc->clusterphase_preferencemin_phase1Inputsc\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", "\n", "clusterphase_preferenceInputsc->clusterphase_preferencemin_phase2Inputsc\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", "\n", "clusterphase_preferenceInputscovera->clusterphase_preferencemin_phase1Inputscovera\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", "\n", "clusterphase_preferenceInputscovera->clusterphase_preferencemin_phase2Inputscovera\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", "\n", "clusterphase_preferenceInputsu->clusterphase_preferencemin_phase1Inputsu\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", "\n", "clusterphase_preferenceInputsu->clusterphase_preferencemin_phase2Inputsu\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", "\n", "clusterphase_preferenceInputsorthorhombic->clusterphase_preferencemin_phase1Inputsorthorhombic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", "\n", "clusterphase_preferenceInputsorthorhombic->clusterphase_preferencemin_phase2Inputsorthorhombic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", "\n", "clusterphase_preferenceInputscubic->clusterphase_preferencemin_phase1Inputscubic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", "\n", "clusterphase_preferenceInputscubic->clusterphase_preferencemin_phase2Inputscubic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_ionic_steps->clusterphase_preferencemin_phase1Inputsn_ionic_steps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_ionic_steps->clusterphase_preferencemin_phase2Inputsn_ionic_steps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_print->clusterphase_preferencemin_phase1Inputsn_print\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_print->clusterphase_preferencemin_phase2Inputsn_print\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", "\n", "clusterphase_preferenceInputspressure->clusterphase_preferencemin_phase1Inputspressure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", "\n", "clusterphase_preferenceInputspressure->clusterphase_preferencemin_phase2Inputspressure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceOutputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", "\n", "clusterphase_preferenceOutputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", "\n", "clusterphase_preferenceOutputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", "\n", "clusterphase_preferenceOutputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", "\n", "clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", "\n", @@ -3315,28 +3318,28 @@ "\n", "\n", "clusterphase_preferencemin_phase1Outputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputscells->clusterphase_preferenceOutputscells\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsdisplacements->clusterphase_preferenceOutputsdisplacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3360,132 +3363,132 @@ "\n", "\n", "clusterphase_preferencemin_phase1Outputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsenergy_tot->clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforce_max->clusterphase_preferenceOutputsforce_max\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforces->clusterphase_preferenceOutputsforces\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsindices->clusterphase_preferenceOutputsindices\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspositions->clusterphase_preferenceOutputspositions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspressures->clusterphase_preferenceOutputspressures\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputssteps->clusterphase_preferenceOutputssteps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputstotal_displacements->clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsunwrapped_positions->clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsvolume->clusterphase_preferenceOutputsvolume\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3516,28 +3519,28 @@ "\n", "\n", "clusterphase_preferencemin_phase2Outputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputscells->clusterphase_preferenceOutputscells\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsdisplacements->clusterphase_preferenceOutputsdisplacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3561,132 +3564,132 @@ "\n", "\n", "clusterphase_preferencemin_phase2Outputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsenergy_tot->clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforce_max->clusterphase_preferenceOutputsforce_max\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforces->clusterphase_preferenceOutputsforces\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsindices->clusterphase_preferenceOutputsindices\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspositions->clusterphase_preferenceOutputspositions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspressures->clusterphase_preferenceOutputspressures\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputssteps->clusterphase_preferenceOutputssteps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputstotal_displacements->clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsunwrapped_positions->clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsvolume->clusterphase_preferenceOutputsvolume\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3718,7 +3721,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 41, From 8e3a347221d5a8ae4fb19180a5fb4a650e922694 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 9 Aug 2023 23:12:18 +0000 Subject: [PATCH 563/756] Format black --- pyiron_contrib/workflow/composite.py | 1 + pyiron_contrib/workflow/interfaces.py | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index fb45dc2fc..0b2099dc0 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -371,6 +371,7 @@ class OwnedNodePackage: A wrapper for node packages so that accessed node classes can have their parent value automatically filled. """ + def __init__(self, parent: Composite, node_package: NodePackage): self._parent = parent self._node_package = node_package diff --git a/pyiron_contrib/workflow/interfaces.py b/pyiron_contrib/workflow/interfaces.py index 42ab5bfe2..607a2f6a7 100644 --- a/pyiron_contrib/workflow/interfaces.py +++ b/pyiron_contrib/workflow/interfaces.py @@ -10,7 +10,12 @@ from pyiron_contrib.executors import CloudpickleProcessPoolExecutor from pyiron_contrib.workflow.function import ( - Function, SingleValue, Slow, function_node, single_value_node, slow_node + Function, + SingleValue, + Slow, + function_node, + single_value_node, + slow_node, ) if TYPE_CHECKING: @@ -24,6 +29,7 @@ class Creator(metaclass=Singleton): Handles the registration of new node packages and, by virtue of being a singleton, makes them available to all composite nodes holding a creator. """ + def __init__(self): self.CloudpickleProcessPoolExecutor = CloudpickleProcessPoolExecutor @@ -39,6 +45,7 @@ def __init__(self): def Macro(self): if self._macro is None: from pyiron_contrib.workflow.macro import Macro + self._macro = Macro return self._macro @@ -46,6 +53,7 @@ def Macro(self): def Workflow(self): if self._workflow is None: from pyiron_contrib.workflow.workflow import Workflow + self._workflow = Workflow return self._workflow @@ -55,6 +63,7 @@ def standard(self): return self._standard except AttributeError: from pyiron_contrib.workflow.node_library.standard import nodes + self.register("_standard", *nodes) return self._standard @@ -64,6 +73,7 @@ def atomistics(self): return self._atomistics except AttributeError: from pyiron_contrib.workflow.node_library.atomistics import nodes + self.register("_atomistics", *nodes) return self._atomistics @@ -71,6 +81,7 @@ def register(self, domain: str, *nodes: list[type[Node]]): if domain in self.__dir__(): raise AttributeError(f"{domain} is already an attribute of {self}") from pyiron_contrib.workflow.node_package import NodePackage + setattr(self, domain, NodePackage(*nodes)) @@ -78,6 +89,7 @@ class Wrappers(metaclass=Singleton): """ A container class giving access to the decorators that transform functions to nodes. """ + def __init__(self): self.function_node = function_node self.single_value_node = single_value_node @@ -90,5 +102,6 @@ def __init__(self): def macro_node(self): if self._macro_node is None: from pyiron_contrib.workflow.macro import macro_node + self._macro_node = macro_node return self._macro_node From 7acea43989f5eda757952a8af79bfa4a7df135a9 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 10 Aug 2023 09:59:26 -0700 Subject: [PATCH 564/756] Better indicate node and channel components of automatic channel name --- pyiron_contrib/workflow/composite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 0b2099dc0..8ef5f698f 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -203,7 +203,7 @@ def _build_io( panel = getattr(node, target) for channel_label in panel.labels: channel = panel[channel_label] - default_key = f"{node.label}_{channel_label}" + default_key = f"{node.label}__{channel_label}" try: io[key_map[default_key]] = channel except KeyError: From 8112571ea54d8f5ac50e6f06512ba354a77e352c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 10 Aug 2023 10:12:31 -0700 Subject: [PATCH 565/756] Update docstrings --- pyiron_contrib/workflow/macro.py | 22 +++++++++++----------- pyiron_contrib/workflow/workflow.py | 8 ++++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pyiron_contrib/workflow/macro.py b/pyiron_contrib/workflow/macro.py index b6f3eee75..5f43e8231 100644 --- a/pyiron_contrib/workflow/macro.py +++ b/pyiron_contrib/workflow/macro.py @@ -49,8 +49,8 @@ class Macro(Composite): io is constructed from unconnected owned-node IO by combining node and channel labels. >>> macro = Macro(add_three_macro) - >>> out = macro(one_x=3) - >>> out.three_result + >>> out = macro(one__x=3) + >>> out.three__result 6 If there's a particular macro we're going to use again and again, we might want @@ -69,22 +69,22 @@ class Macro(Composite): ... ) >>> >>> macro = AddThreeMacro() - >>> macro(one_x=0).three_result + >>> macro(one__x=0).three__result 3 We can also nest macros, rename their IO, and provide access to internally-connected IO: >>> def nested_macro(macro): ... macro.a = macro.create.SingleValue(add_one) - ... macro.b = macro.create.Macro(add_three_macro, one_x=macro.a) + ... macro.b = macro.create.Macro(add_three_macro, one__x=macro.a) ... macro.c = macro.create.SingleValue( - ... add_one, x=macro.b.outputs.three_result + ... add_one, x=macro.b.outputs.three__result ... ) >>> >>> macro = Macro( ... nested_macro, - ... inputs_map={"a_x": "inp"}, - ... outputs_map={"c_result": "out", "b_three_result": "intermediate"}, + ... inputs_map={"a__x": "inp"}, + ... outputs_map={"c__result": "out", "b__three__result": "intermediate"}, ... ) >>> macro(inp=1) {'intermediate': 5, 'out': 6} @@ -103,11 +103,11 @@ class Macro(Composite): >>> >>> m = Macro(modified_start_macro, update_on_instantiation=False) >>> m.outputs.to_value_dict() - {'a_result': pyiron_contrib.workflow.channels.NotData, - 'b_result': pyiron_contrib.workflow.channels.NotData} + {'a__result': pyiron_contrib.workflow.channels.NotData, + 'b__result': pyiron_contrib.workflow.channels.NotData} - >>> m(a_x=1, b_x=2) - {'a_result': pyiron_contrib.workflow.channels.NotData, 'b_result': 3} + >>> m(a__x=1, b__x=2) + {'a__result': pyiron_contrib.workflow.channels.NotData, 'b__result': 3} """ def __init__( diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index fac4e16c6..49e12a3ab 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -94,19 +94,19 @@ class Workflow(Composite): The workflow joins node lavels and channel labels with a `_` character to provide direct access to the output: - >>> print(wf.outputs.second_y.value) + >>> print(wf.outputs.second__y.value) 2 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: - >>> out = wf(first_x=10) + >>> out = wf(first__x=10) >>> out - {'second_y': 12} + {'second__y': 12} Note: this _looks_ like a dictionary, but has some extra convenience that we can dot-access data: - >>> out.second_y + >>> out.second__y 12 Workflows also give access to packages of pre-built nodes under different From 0eee93f38a2d69e865996636cc1a2c5369184d94 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 10 Aug 2023 10:24:02 -0700 Subject: [PATCH 566/756] Update tests --- tests/unit/workflow/test_macro.py | 38 ++++++++++++++-------------- tests/unit/workflow/test_workflow.py | 16 ++++++------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/unit/workflow/test_macro.py b/tests/unit/workflow/test_macro.py index 607933453..fb61a48d1 100644 --- a/tests/unit/workflow/test_macro.py +++ b/tests/unit/workflow/test_macro.py @@ -38,7 +38,7 @@ def test_by_function(self): m = Macro(add_three_macro) self.assertIs( - m.outputs.three_result.value, + m.outputs.three__result.value, NotData, msg="Output should be accessible with the usual naming convention, but we " "asked the node not to run yet so there shouldn't be any data" @@ -46,15 +46,15 @@ def test_by_function(self): input_x = 1 expected_value = add_one(add_one(add_one(input_x))) - out = m(one_x=input_x) # Take kwargs to set input at runtime + out = m(one__x=input_x) # Take kwargs to set input at runtime self.assertEqual( - out.three_result, + out.three__result, expected_value, msg="Macros should return the output, just like other nodes" ) self.assertEqual( - m.outputs.three_result.value, + m.outputs.three__result.value, expected_value, msg="Macros should get output updated, just like other nodes" ) @@ -70,9 +70,9 @@ def build_graph(self): ) x = 0 - m = MyMacro(one_x=x) + m = MyMacro(one__x=x) self.assertEqual( - m.outputs.three_result.value, + m.outputs.three__result.value, add_one(add_one(add_one(x))), msg="Subclasses should be able to simply override the graph_creator arg" ) @@ -80,8 +80,8 @@ def build_graph(self): def test_key_map(self): m = Macro( add_three_macro, - inputs_map={"one_x": "my_input"}, - outputs_map={"three_result": "my_output", "two_result": "intermediate"}, + inputs_map={"one__x": "my_input"}, + outputs_map={"three__result": "my_output", "two__result": "intermediate"}, ) self.assertSetEqual( set(m.inputs.labels), @@ -112,11 +112,11 @@ def test_key_map(self): def test_nesting(self): def nested_macro(macro): macro.a = SingleValue(add_one) - macro.b = Macro(add_three_macro, one_x=macro.a) - macro.c = SingleValue(add_one, x=macro.b.outputs.three_result) + macro.b = Macro(add_three_macro, one__x=macro.a) + macro.c = SingleValue(add_one, x=macro.b.outputs.three__result) m = Macro(nested_macro) - self.assertEqual(m(a_x=0).c_result, 5) + self.assertEqual(m(a__x=0).c__result, 5) def test_upstream_detection(self): def my_macro(macro): @@ -148,7 +148,7 @@ def my_macro(macro): self.assertIs(m.upstream_nodes[0], m.a) m2 = Macro(my_macro) - m.inputs.a_x = m2.outputs.b_result + m.inputs.a__x = m2.outputs.b__result self.assertIs( m.upstream_nodes[0], m.a, @@ -159,7 +159,7 @@ def my_macro(macro): msg="Should be able to check if external nodes have local connections" ) - m.inputs.a_x = m.outputs.b_result # Infinite loop self-connection + m.inputs.a__x = m.outputs.b__result # Infinite loop self-connection self.assertEqual( len(m.upstream_nodes), 0, @@ -179,11 +179,11 @@ def my_macro(macro): def deep_macro(macro): macro.a = SingleValue(add_one, x=0, run_on_updates=False) macro.m = Macro(my_macro) - macro.m.inputs.a_x = macro.a + macro.m.inputs.a__x = macro.a nested = Macro(deep_macro) plain = Macro(my_macro) - # plain.inputs.a_x = nested.m.outputs.a_result + # plain.inputs.a__x = nested.m.outputs.a__result # self.assertTrue( # nested.connects_to_input_of(plain), # msg="A child of the nested macro has a connection to the plain macros" @@ -199,23 +199,23 @@ def modified_start_macro(macro): m = Macro(modified_start_macro, update_on_instantiation=False) self.assertIs( - m.outputs.a_result.value, + m.outputs.a__result.value, NotData, msg="Node should not have run when the macro batch updated input" ) self.assertIs( - m.outputs.b_result.value, + m.outputs.b__result.value, NotData, msg="Node should not have run when the macro batch updated input" ) m.run() self.assertIs( - m.outputs.a_result.value, + m.outputs.a__result.value, NotData, msg="Was not included in starting nodes, should not have run" ) self.assertEqual( - m.outputs.b_result.value, + m.outputs.b__result.value, 1, msg="Was included in starting nodes, should have run" ) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index fd01db5e2..bc247c548 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -117,8 +117,8 @@ def test_workflow_io(self): "IO should be re-mappable, including exposing internally connected " "channels" ): - wf.inputs_map = {"n1_x": "inp"} - wf.outputs_map = {"n3_y": "out", "n2_y": "intermediate"} + wf.inputs_map = {"n1__x": "inp"} + wf.outputs_map = {"n3__y": "out", "n2__y": "intermediate"} out = wf(inp=0) self.assertEqual(out.out, 3) self.assertEqual(out.intermediate, 2) @@ -224,7 +224,7 @@ def sum_(a, b): wf.sum.outputs.sum.value, msg="Sanity check" ) - wf(a_x=42, b_x=42) + wf(a__x=42, b__x=42) self.assertEqual( plus_one(42) + plus_one(42), wf.sum.outputs.sum.value, @@ -245,17 +245,17 @@ def test_return_value(self): wf.b = wf.create.SingleValue(plus_one, x=wf.a) with self.subTest("Run on main process"): - return_on_call = wf(a_x=1) + return_on_call = wf(a__x=1) self.assertEqual( return_on_call, - DotDict({"b_y": 1 + 2}), + 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, + return_on_update.b__y, 1 + 2, msg="Run output should be returned on update" ) @@ -266,14 +266,14 @@ def test_return_value(self): 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) + 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"], + 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" From 244c0c68c46e63ec047cd4cd826cfdadbe7ebac5 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 10 Aug 2023 10:32:19 -0700 Subject: [PATCH 567/756] Update example notebook --- notebooks/workflow_example.ipynb | 542 +++++++++++++++---------------- 1 file changed, 271 insertions(+), 271 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 66420b0cb..244b43628 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -11,7 +11,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "89e12c6b0d0543659659b75b37c94bcd", + "model_id": "160e79d4e1574a3089b810aa46d95d3f", "version_major": 2, "version_minor": 0 }, @@ -716,7 +716,7 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiMAAAGdCAYAAADAAnMpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAkkElEQVR4nO3df3BU1d3H8c9mY7JRk6WBkqwkjRE1JaQVCRNMKHVGNEJbKu20xlKwWOg09Aci1RkY2sYwncloW+vPZKQSrYJKK9rCiLSZqdUgj6ZC6DRGxULaBNiYSaibWJvQJuf5gycZ9kkCuZvNnt3s+zVz/9iTe7Pf7yxkP3Puvee6jDFGAAAAliTYLgAAAMQ3wggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqxJtFzAWAwMDOnnypFJTU+VyuWyXAwAAxsAYo56eHl1yySVKSBh9/iMmwsjJkyeVnZ1tuwwAABCCtrY2ZWVljfrzmAgjqampks40k5aWZrkaAAAwFt3d3crOzh76Hh9NTISRwVMzaWlphBEAAGLM+S6x4AJWAABgFWEEAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFUxsegZgPDoHzBqaDmljp5eTU/1qCg3Xe4EnvcEwC7CCBAn9jX5VbmnWf5A79CYz+tRxdJ8LS7wWawMQLzjNA0QB/Y1+bV2+6GgICJJ7YFerd1+SPua/JYqAwDCCDDp9Q8YVe5plhnhZ4NjlXua1T8w0h4AMPEII8Ak19ByatiMyNmMJH+gVw0tpyJXFACchTACTHIdPaMHkVD2A4BwI4wAk9z0VE9Y9wOAcCOMAJNcUW66fF6PRruB16Uzd9UU5aZHsiwAGEIYASY5d4JLFUvzJWlYIBl8XbE0n/VGAFhDGAHiwOICn2pWzFWmN/hUTKbXo5oVc1lnBIBVLHoGxInFBT7dkJ/JCqwAog5hBIgj7gSXimdOtV0GAAThNA0AALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAq0IKI9XV1crNzZXH41FhYaHq6+vPuf+OHTt01VVX6cILL5TP59Ntt92mrq6ukAoGAACTi+MwsnPnTq1fv16bN29WY2OjFi5cqCVLlqi1tXXE/ffv369bb71Vq1ev1ltvvaXf/OY3+vOf/6w1a9aMu3gAABD7HIeR++67T6tXr9aaNWs0a9Ys3X///crOzlZNTc2I+7/++uu69NJLtW7dOuXm5uozn/mMvv3tb+vNN98cd/EAACD2OQojp0+f1sGDB1VaWho0XlpaqgMHDox4TElJiY4fP669e/fKGKP3339fzz33nD7/+c+P+j59fX3q7u4O2gAAwOTkKIx0dnaqv79fGRkZQeMZGRlqb28f8ZiSkhLt2LFDZWVlSkpKUmZmpqZMmaKHHnpo1PepqqqS1+sd2rKzs52UCQAAYkhIF7C6XK6g18aYYWODmpubtW7dOv34xz/WwYMHtW/fPrW0tKi8vHzU379p0yYFAoGhra2tLZQyAQBADEh0svO0adPkdruHzYJ0dHQMmy0ZVFVVpQULFuiuu+6SJH3605/WRRddpIULF+onP/mJfD7fsGOSk5OVnJzspDQAABCjHM2MJCUlqbCwUHV1dUHjdXV1KikpGfGYjz76SAkJwW/jdrslnZlRAQAA8c3xaZoNGzboscceU21trd5++23dcccdam1tHTrtsmnTJt16661D+y9dulTPP/+8ampqdOzYMb322mtat26dioqKdMkll4SvEwAAEJMcnaaRpLKyMnV1dWnLli3y+/0qKCjQ3r17lZOTI0ny+/1Ba46sWrVKPT09evjhh/WDH/xAU6ZM0XXXXad77rknfF0AAICY5TIxcK6ku7tbXq9XgUBAaWlptssBAABjMNbvb55NAwAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAqkTbBQCAbf0DRg0tp9TR06vpqR4V5abLneCyXRYQNwgjAOLavia/Kvc0yx/oHRrzeT2qWJqvxQU+i5UB8YPTNADi1r4mv9ZuPxQURCSpPdCrtdsPaV+T31JlQHwhjACIS/0DRpV7mmVG+NngWOWeZvUPjLQHgHAijACISw0tp4bNiJzNSPIHetXQcipyRQFxijACIC519IweRELZD0DoCCMA4tL0VE9Y9wMQOsIIgLhUlJsun9ej0W7gdenM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zZuVk5Oj5ORkzZw5U7W1tRGqNjROely1atWIn+Hs2bMjWLEzTj/DHTt26KqrrtKFF14on8+n2267TV1dXRGq1jmn/T3yyCOaNWuWUlJSlJeXpyeffDK8BZk48+yzz5oLLrjA/PKXvzTNzc3m9ttvNxdddJH5xz/+MeL+LS0tZt26deZXv/qVmTNnjrn99tsjW7BDTvu7/fbbzT333GMaGhrMkSNHzKZNm8wFF1xgDh06FOHKx8Zpf4cOHTJPP/20aWpqMi0tLeapp54yF154oXn00UcjXPnYOe1x0AcffGAuu+wyU1paaq666qrIFBsCp/29/PLLRpJ59913jd/vH9r++9//RrjysQnl8/viF79o5s+fb+rq6kxLS4t54403zGuvvRbBqp1x2uMHH3wQ9Nm1tbWZ9PR0U1FREdnCx8hpf/X19SYhIcE88MAD5tixY6a+vt7Mnj3bLFu2LMKVj43T/qqrq01qaqp59tlnzdGjR80zzzxjLr74YrN79+6w1RR3YaSoqMiUl5cHjX3yk580GzduPO+x1157bdSHkfH0Nyg/P99UVlaGu7SwCEd/X/rSl8yKFSvCXVrYhNpjWVmZ+eEPf2gqKiqiOow47W8wjPzzn/+MQHXj57S/l156yXi9XtPV1RWJ8sJivP8PX3jhBeNyuczf//73iShv3Jz299Of/tRcdtllQWMPPvigycrKmrAax8Npf8XFxebOO+8MGrv99tvNggULwlZTXJ2mOX36tA4ePKjS0tKg8dLSUh04cMBSVeETjv4GBgbU09Oj9PT0iShxXMLRX2Njow4cOKBrr712Ikoct1B7fPzxx3X06FFVVFRMdInjMp7P8Oqrr5bP59OiRYv08ssvT2SZIQulv927d2vevHm69957NWPGDF155ZW688479e9//zsSJTsWjv+H27Zt0/XXX6+cnJyJKHFcQumvpKREx48f1969e2WM0fvvv6/nnntOn//85yNRsiOh9NfX1yePxxM0lpKSooaGBv3nP/8JS11xFUY6OzvV39+vjIyMoPGMjAy1t7dbqip8wtHfz3/+c/3rX//SzTffPBEljst4+svKylJycrLmzZun7373u1qzZs1ElhqyUHp87733tHHjRu3YsUOJidH97MtQ+vP5fNq6dat27dql559/Xnl5eVq0aJFeffXVSJTsSCj9HTt2TPv371dTU5NeeOEF3X///Xruuef03e9+NxIlOzbevzN+v18vvfTSpPo/WFJSoh07dqisrExJSUnKzMzUlClT9NBDD0WiZEdC6e/GG2/UY489poMHD8oYozfffFO1tbX6z3/+o87OzrDUFd1/uSaIy+UKem2MGTYWy0Lt75lnntHdd9+t3/3ud5o+ffpElTduofRXX1+vDz/8UK+//ro2btyoyy+/XF/72tcmssxxGWuP/f39Wr58uSorK3XllVdGqrxxc/IZ5uXlKS8vb+h1cXGx2tra9LOf/Uyf/exnJ7TOUDnpb2BgQC6XSzt27Bh6uul9992nr3zlK3rkkUeUkpIy4fWGItS/M0888YSmTJmiZcuWTVBl4eGkv+bmZq1bt04//vGPdeONN8rv9+uuu+5SeXm5tm3bFolyHXPS349+9CO1t7frmmuukTFGGRkZWrVqle6991653e6w1BNXMyPTpk2T2+0elv46OjqGpcRYNJ7+du7cqdWrV+vXv/61rr/++oksM2Tj6S83N1ef+tSn9K1vfUt33HGH7r777gmsNHROe+zp6dGbb76p733ve0pMTFRiYqK2bNmiv/zlL0pMTNQf//jHSJU+JuH6P3jNNdfovffeC3d54xZKfz6fTzNmzAh6zPqsWbNkjNHx48cntN5QjOczNMaotrZWK1euVFJS0kSWGbJQ+quqqtKCBQt011136dOf/rRuvPFGVVdXq7a2Vn6/PxJlj1ko/aWkpKi2tlYfffSR/v73v6u1tVWXXnqpUlNTNW3atLDUFVdhJCkpSYWFhaqrqwsar6urU0lJiaWqwifU/p555hmtWrVKTz/9dFSe4xwUrs/PGKO+vr5wlxcWTntMS0vTX//6Vx0+fHhoKy8vV15eng4fPqz58+dHqvQxCddn2NjYKJ/PF+7yxi2U/hYsWKCTJ0/qww8/HBo7cuSIEhISlJWVNaH1hmI8n+Err7yiv/3tb1q9evVEljguofT30UcfKSEh+Ot0cMbARNnj38bz+V1wwQXKysqS2+3Ws88+qy984QvD+g5Z2C6FjRGDtzRt27bNNDc3m/Xr15uLLrpo6KrujRs3mpUrVwYd09jYaBobG01hYaFZvny5aWxsNG+99ZaN8s/LaX9PP/20SUxMNI888kjQrXcffPCBrRbOyWl/Dz/8sNm9e7c5cuSIOXLkiKmtrTVpaWlm8+bNtlo4r1D+jZ4t2u+mcdrfL37xC/PCCy+YI0eOmKamJrNx40YjyezatctWC+fktL+enh6TlZVlvvKVr5i33nrLvPLKK+aKK64wa9assdXCeYX6b3TFihVm/vz5kS7XMaf9Pf744yYxMdFUV1ebo0ePmv3795t58+aZoqIiWy2ck9P+3n33XfPUU0+ZI0eOmDfeeMOUlZWZ9PR009LSEraa4i6MGGPMI488YnJyckxSUpKZO3eueeWVV4Z+9o1vfMNce+21QftLGrbl5OREtmgHnPR37bXXjtjfN77xjcgXPkZO+nvwwQfN7NmzzYUXXmjS0tLM1Vdfbaqrq01/f7+FysfO6b/Rs0V7GDHGWX/33HOPmTlzpvF4POZjH/uY+cxnPmNefPFFC1WPndPP7+233zbXX3+9SUlJMVlZWWbDhg3mo48+inDVzjjt8YMPPjApKSlm69atEa40NE77e/DBB01+fr5JSUkxPp/PfP3rXzfHjx+PcNVj56S/5uZmM2fOHJOSkmLS0tLMTTfdZN55552w1uMyJsrmkAAAQFyJq2tGAABA9CGMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsOp/AdzC0SP69nD3AAAAAElFTkSuQmCC", + "image/png": "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AKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrogojDQ0NKiwslMfjUWlpqVpaWsbsu3//fi1ZskQzZszQ1KlTtXDhQv3P//xP1AUDAIDkku70hF27dmndunVqaGjQkiVL9Nxzz2nZsmXy+/2aM2dOWP9p06bpvvvu02WXXaZp06Zp//79uueeezRt2jT95Cc/mZAPAQAAEpfLGGOcnFBRUaGSkhI1NjaOti1atEgrVqxQfX39uH7HzTffrGnTpun3v//9uPr39PTI6/UqGAwqKyvLSbkAAMCS8X5/O1qmGRgY0OHDh1VdXR3SXl1drQMHDozrdxw9elQHDhzQNddcM2af/v5+9fT0hBwAACA5OQojnZ2dGhwcVE5OTkh7Tk6O2tvbz3tuXl6eMjMzVVZWpp/+9Kdas2bNmH3r6+vl9XpHj/z8fCdlAgCABBLVBawulyvktTEmrO2rWlpadOjQIT377LN68skn9corr4zZd+PGjQoGg6PHqVOnoikTAAAkAEcXsM6cOVNutztsFqSjoyNstuSrCgsLJUnf+c539Mknn+iRRx7RD3/4w4h9MzMzlZmZ6aQ0AACQoBzNjGRkZKi0tFTNzc0h7c3Nzaqqqhr37zHGqL+/38lbAwCAJOX41t7169dr9erVKisrU2VlpbZs2aK2tjbV1NRIGl5iOXPmjLZv3y5JeuaZZzRnzhwtXLhQ0vC+I48//rjuv//+CfwYAAAgUTkOIytXrlRXV5c2b96sQCCg4uJi7d27VwUFBZKkQCCgtra20f5DQ0PauHGjWltblZ6errlz5+pXv/qV7rnnnon7FAAAIGE53mfEBvYZAQAg8UzKPiMAAAATjTACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALAq3XYB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", "text/plain": [ "
" ] @@ -805,7 +805,7 @@ "outputs": [ { "data": { - "image/png": "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pwAAAK7juE9KOtAnBQCAzHOlr98Z8S7I0RxFvxQAADJH9HU72fWQjAgpU1NTkkS/FAAAMtDU1JT8fr/j4zLick8kEtH58+dVUFBwyX4sTk1OTqqsrExnz57lMlIKcd5Tj3OeHpz39OC8p95c59wYo6mpKa1YsUI5Oc5bs2XESkpOTo5Wrly5aF+/sLCQX+Q04LynHuc8PTjv6cF5T71E5zyZFZSopDrOAgAALDZCCgAAsFJWh5T8/Hw1NzfTgj/FOO+pxzlPD857enDeU2+xznlG3DgLAACyT1avpAAAAHsRUgAAgJUIKQAAwEqEFAAAYCXXh5T9+/ervLxcPp9P1dXV6unpueT8119/XdXV1fL5fLr22mv1ne98J0WVuouT8/7yyy9rw4YN+uhHP6rCwkLV1tbqpz/9aQqrdQenv+tRb775pnJzc3XTTTctboEu5fS8X7hwQXv37tWqVauUn5+v1atX68CBAymq1j2cnvdDhw7pxhtv1NVXX61AIKD7779f4+PjKao2873xxhtqaGjQihUr5PF49OMf//iyxyzI66lxsR/+8IdmyZIl5plnnjGDg4PmoYceMkuXLjW//vWvE84/c+aMufrqq81DDz1kBgcHzTPPPGOWLFliXnrppRRXntmcnveHHnrIPP7446a3t9e89dZbZs+ePWbJkiXmxIkTKa48czk951G//e1vzbXXXmvq6+vNjTfemJpiXSSZ837nnXeadevWme7ubjM0NGT+8z//07z55psprDrzOT3vPT09Jicnxzz55JPmzJkzpqenx3z84x83mzdvTnHlmauzs9Ps3bvX/OhHPzKSzCuvvHLJ+Qv1eurqkFJTU2Oamppixm644Qaze/fuhPP//u//3txwww0xY1/84hfNzTffvGg1upHT855IRUWFaWlpWejSXCvZc97Y2Gj+8R//0TQ3NxNSkuD0vP/rv/6r8fv9Znx8PBXluZbT8/7P//zP5tprr40Ze+qpp8zKlSsXrUY3m09IWajXU9de7rl48aL6+vpUX18fM15fX6+jR48mPObYsWNx8z/zmc/o+PHj+sMf/rBotbpJMuf9wyKRiKamplRUVLQYJbpOsuf8+eef1+nTp9Xc3LzYJbpSMuf91Vdf1dq1a/X1r39d11xzja6//np99atf1e9///tUlOwKyZz3uro6nTt3Tp2dnTLG6De/+Y1eeukl3XHHHakoOSst1OtpRrzBYDLGxsYUDodVUlISM15SUqKRkZGEx4yMjCSc//7772tsbEyBQGDR6nWLZM77h33jG9/Qu+++q7vuumsxSnSdZM7522+/rd27d6unp0e5ua59GlhUyZz3M2fO6MiRI/L5fHrllVc0NjamL33pS5qYmOC+lHlK5rzX1dXp0KFDamxs1PT0tN5//33deeed+ta3vpWKkrPSQr2eunYlJcrj8cR8bIyJG7vc/ETjuDSn5z3q8OHD+trXvqb29nYVFxcvVnmuNN9zHg6Hdc8996ilpUXXX399qspzLSe/65FIRB6PR4cOHVJNTY1uv/12PfHEE/r+97/PaopDTs774OCgtm/frkcffVR9fX3q6urS0NCQmpqaUlFq1lqI11PX/gu1fPlyeb3euGQ9Ojoal+6iSktLE87Pzc3VsmXLFq1WN0nmvEe1t7frgQce0IsvvqjbbrttMct0FafnfGpqSsePH1d/f7++8pWvSJp58TTGKDc3V6+99ppuvfXWlNSeyZL5XQ8EArrmmmti3rp+zZo1Msbo3Llzuu666xa1ZjdI5ry3trbqlltu0cMPPyxJ+sQnPqGlS5dq/fr1euyxx1glXwQL9Xrq2pWUvLw8VVdXq7u7O2a8u7tbdXV1CY+pra2Nm//aa69p7dq1WrJkyaLV6ibJnHdpZgXlvvvu0wsvvMB1YoecnvPCwkL98pe/1MmTJ2cfTU1N+tM//VOdPHlS69atS1XpGS2Z3/VbbrlF58+f1+9+97vZsbfeeks5OTlauXLlotbrFsmc9/fee085ObEvd16vV9If/7vHwlqw11NHt9lmmOg2teeee84MDg6aHTt2mKVLl5r/+Z//McYYs3v3bnPvvffOzo9umdq5c6cZHBw0zz33HFuQk+D0vL/wwgsmNzfXPP300yYYDM4+fvvb36brR8g4Ts/5h7G7JzlOz/vU1JRZuXKl+fznP29+9atfmddff91cd9115sEHH0zXj5CRnJ73559/3uTm5pr9+/eb06dPmyNHjpi1a9eampqadP0IGWdqasr09/eb/v5+I8k88cQTpr+/f3bb92K9nro6pBhjzNNPP21WrVpl8vLyTFVVlXn99ddnP/eFL3zBfPrTn46Z/x//8R/mk5/8pMnLyzMf+9jHTFtbW4ordgcn5/3Tn/60kRT3+MIXvpD6wjOY09/1DyKkJM/peT916pS57bbbzFVXXWVWrlxpdu3aZd57770UV535nJ73p556ylRUVJirrrrKBAIB81d/9Vfm3LlzKa46c/37v//7JZ+nF+v11GMMa10AAMA+rr0nBQAAZDZCCgAAsBIhBQAAWImQAgAArERIAQAAViKkAAAAKxFSAACAlQgpAADASoQUAABgJUIKAACwEiEFAABYiZACAACs9P8Al9OAmjFo1BcAAAAASUVORK5CYII=", + "image/png": "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", "text/plain": [ "
" ] @@ -927,7 +927,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label sum_ to the io key sum_sum_\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label sum_ to the io key sum__sum_\n", " warn(\n" ] } @@ -945,7 +945,7 @@ "wf.sum = add_node(wf.a, wf.b) \n", "# Remember, with single value nodes we can pass the whole node instead of an output channel!\n", "\n", - "print(wf.outputs.sum_sum_.value)" + "print(wf.outputs.sum__sum_.value)" ] }, { @@ -966,16 +966,16 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label x to the io key a_x\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label x to the io key a__x\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label x to the io key b_x\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label x to the io key b__x\n", " warn(\n" ] }, { "data": { "text/plain": [ - "{'sum_sum_': 7}" + "{'sum__sum_': 7}" ] }, "execution_count": 31, @@ -984,7 +984,7 @@ } ], "source": [ - "out = wf(a_x=2, b_x=3)\n", + "out = wf(a__x=2, b__x=3)\n", "out" ] }, @@ -1006,7 +1006,7 @@ } ], "source": [ - "out.sum_sum_" + "out.sum__sum_" ] }, { @@ -1109,55 +1109,55 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label name to the io key structure_name\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label name to the io key structure__name\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key structure_crystalstructure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key structure__crystalstructure\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key structure_a\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key structure__a\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key structure_c\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key structure__c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key structure_covera\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key structure__covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key structure_u\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key structure__u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key structure_orthorhombic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key structure__orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key structure__cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc__n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key calc__n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label temperature to the io key calc_temperature\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label temperature to the io key calc__temperature\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key calc__pressure\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key calc_cells\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key calc__cells\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key calc_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key calc__displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_pot to the io key calc_energy_pot\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_pot to the io key calc__energy_pot\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key calc_energy_tot\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key calc__energy_tot\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key calc_force_max\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key calc__force_max\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key calc_forces\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key calc__forces\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key calc_indices\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key calc__indices\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key calc_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key calc__positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key calc_pressures\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key calc__pressures\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key calc_total_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key calc__total_displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc_unwrapped_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc__unwrapped_positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key calc_volume\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key calc__volume\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label fig to the io key plot_fig\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label fig to the io key plot__fig\n", " warn(\n" ] }, @@ -1950,7 +1950,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 34, @@ -1980,55 +1980,55 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label name to the io key structure_name\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label name to the io key structure__name\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key structure_crystalstructure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key structure__crystalstructure\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key structure_a\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key structure__a\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key structure_c\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key structure__c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key structure_covera\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key structure__covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key structure_u\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key structure__u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key structure_orthorhombic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key structure__orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key structure__cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc__n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key calc__n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label temperature to the io key calc_temperature\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label temperature to the io key calc__temperature\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key calc__pressure\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key calc_cells\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key calc__cells\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key calc_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key calc__displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_pot to the io key calc_energy_pot\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_pot to the io key calc__energy_pot\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key calc_energy_tot\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key calc__energy_tot\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key calc_force_max\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key calc__force_max\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key calc_forces\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key calc__forces\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key calc_indices\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key calc__indices\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key calc_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key calc__positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key calc_pressures\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key calc__pressures\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key calc_total_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key calc__total_displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc_unwrapped_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc__unwrapped_positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key calc_volume\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key calc__volume\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label fig to the io key plot_fig\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label fig to the io key plot__fig\n", " warn(\n" ] }, @@ -2236,7 +2236,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 35, @@ -2278,9 +2278,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label x to the io key add_one_x\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label x to the io key add_one__x\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label result to the io key add_three_result\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label result to the io key add_three__result\n", " warn(\n" ] }, @@ -2316,7 +2316,7 @@ " # macro object and can do these sorts of setup proceedures here\n", " \n", "macro = Macro(add_three_macro)\n", - "macro(add_one_x=10).add_three_result" + "macro(add_one__x=10).add_three__result" ] }, { @@ -2365,8 +2365,8 @@ " macro.add_one = add_one(0)\n", " macro.add_two = add_one(macro.add_one)\n", " macro.add_three = add_one(macro.add_two)\n", - " macro.inputs_map = {\"add_one_x\": \"x\"}\n", - " macro.outputs_map = {\"add_three_result\": \"plus_three\", \"add_two_result\": \"intermediate\"}\n", + " macro.inputs_map = {\"add_one__x\": \"x\"}\n", + " macro.outputs_map = {\"add_three__result\": \"plus_three\", \"add_two__result\": \"intermediate\"}\n", " \n", "macro = add_three_macro(x=100)\n", "macro.outputs.to_value_dict()" @@ -2401,13 +2401,13 @@ " macro.calc.signals.input.run = macro.engine.signals.output.ran\n", " \n", " macro.inputs_map = {\n", - " \"structure_name\": \"element\", \n", - " \"structure_crystalstructure\": \"crystalstructure\",\n", - " \"structure_a\": \"lattice_guess\",\n", + " \"structure__name\": \"element\", \n", + " \"structure__crystalstructure\": \"crystalstructure\",\n", + " \"structure__a\": \"lattice_guess\",\n", " }\n", " macro.outputs_map = {\n", - " \"calc_energy_pot\": \"energy\",\n", - " \"structure_structure\": \"structure\",\n", + " \"calc__energy_pot\": \"energy\",\n", + " \"structure__structure\": \"structure\",\n", " }\n", " \n", " # macro.starting_nodes = [macro.structure]\n", @@ -2444,47 +2444,47 @@ " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key structure_c\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key structure__c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key structure_covera\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key structure__covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key structure_u\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key structure__u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key structure_orthorhombic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key structure__orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key structure__cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc__n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key calc__n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key calc__pressure\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key calc_cells\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key calc__cells\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key calc_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key calc__displacements\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_pot to the io key energy\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key calc_energy_tot\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key calc__energy_tot\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key calc_force_max\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key calc__force_max\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key calc_forces\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key calc__forces\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key calc_indices\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key calc__indices\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key calc_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key calc__positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key calc_pressures\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key calc__pressures\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key calc_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key calc__steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key calc_total_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key calc__total_displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc_unwrapped_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc__unwrapped_positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key calc_volume\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key calc__volume\n", " warn(\n" ] } @@ -2507,11 +2507,11 @@ "# So we'll need to hit the macro with an explicit run command\n", "\n", "wf.inputs_map = {\n", - " \"element_user_input\": \"element\",\n", - " \"min_phase1_crystalstructure\": \"phase1\",\n", - " \"min_phase2_crystalstructure\": \"phase2\",\n", - " \"min_phase1_lattice_guess\": \"lattice_guess1\",\n", - " \"min_phase2_lattice_guess\": \"lattice_guess2\",\n", + " \"element__user_input\": \"element\",\n", + " \"min_phase1__crystalstructure\": \"phase1\",\n", + " \"min_phase2__crystalstructure\": \"phase2\",\n", + " \"min_phase1__lattice_guess\": \"lattice_guess1\",\n", + " \"min_phase2__lattice_guess\": \"lattice_guess2\",\n", "}" ] }, @@ -2531,91 +2531,91 @@ " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1_structure_c\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1__structure__c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1_structure_covera\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1__structure__covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1_structure_u\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1__structure__u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1_structure_orthorhombic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1__structure__orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1_structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1__structure__cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1_calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1__calc__n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1_calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1__calc__n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1_calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1__calc__pressure\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2_structure_c\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2__structure__c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2_structure_covera\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2__structure__covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2_structure_u\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2__structure__u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2_structure_orthorhombic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2__structure__orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2_structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2__structure__cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2_calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2__calc__n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2_calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2__calc__n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2_calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2__calc__pressure\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase1_calc_cells\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase1__calc__cells\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase1_calc_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase1__calc__displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase1_calc_energy_tot\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase1__calc__energy_tot\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase1_calc_force_max\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase1__calc__force_max\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase1_calc_forces\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase1__calc__forces\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase1_calc_indices\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase1__calc__indices\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase1_calc_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase1__calc__positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase1_calc_pressures\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase1__calc__pressures\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase1_calc_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase1__calc__steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase1_calc_total_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase1__calc__total_displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase1_calc_unwrapped_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase1__calc__unwrapped_positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase1_calc_volume\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase1__calc__volume\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase2_calc_cells\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase2__calc__cells\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase2_calc_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase2__calc__displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase2_calc_energy_tot\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase2__calc__energy_tot\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase2_calc_force_max\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase2__calc__force_max\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase2_calc_forces\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase2__calc__forces\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase2_calc_indices\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase2__calc__indices\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase2_calc_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase2__calc__positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase2_calc_pressures\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase2__calc__pressures\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase2_calc_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase2__calc__steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase2_calc_total_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase2__calc__total_displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase2_calc_unwrapped_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase2__calc__unwrapped_positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase2_calc_volume\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase2__calc__volume\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label de to the io key compare_de\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label de to the io key compare__de\n", " warn(\n" ] }, @@ -3721,7 +3721,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 41, @@ -3749,41 +3749,41 @@ " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1_structure_c\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1__structure__c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1_structure_covera\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1__structure__covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1_structure_u\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1__structure__u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1_structure_orthorhombic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1__structure__orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1_structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1__structure__cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1_calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1__calc__n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1_calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1__calc__n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1_calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1__calc__pressure\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2_structure_c\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2__structure__c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2_structure_covera\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2__structure__covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2_structure_u\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2__structure__u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2_structure_orthorhombic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2__structure__orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2_structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2__structure__cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2_calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2__calc__n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2_calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2__calc__n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2_calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2__calc__pressure\n", " warn(\n" ] }, @@ -3805,41 +3805,41 @@ " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1_structure_c\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1__structure__c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1_structure_covera\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1__structure__covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1_structure_u\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1__structure__u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1_structure_orthorhombic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1__structure__orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1_structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1__structure__cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1_calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1__calc__n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1_calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1__calc__n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1_calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1__calc__pressure\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2_structure_c\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2__structure__c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2_structure_covera\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2__structure__covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2_structure_u\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2__structure__u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2_structure_orthorhombic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2__structure__orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2_structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2__structure__cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2_calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2__calc__n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2_calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2__calc__n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2_calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2__calc__pressure\n", " warn(\n" ] }, @@ -3856,55 +3856,55 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase1_calc_cells\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase1__calc__cells\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase1_calc_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase1__calc__displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase1_calc_energy_tot\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase1__calc__energy_tot\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase1_calc_force_max\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase1__calc__force_max\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase1_calc_forces\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase1__calc__forces\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase1_calc_indices\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase1__calc__indices\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase1_calc_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase1__calc__positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase1_calc_pressures\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase1__calc__pressures\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase1_calc_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase1__calc__steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase1_calc_total_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase1__calc__total_displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase1_calc_unwrapped_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase1__calc__unwrapped_positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase1_calc_volume\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase1__calc__volume\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase2_calc_cells\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase2__calc__cells\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase2_calc_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase2__calc__displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase2_calc_energy_tot\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase2__calc__energy_tot\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase2_calc_force_max\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase2__calc__force_max\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase2_calc_forces\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase2__calc__forces\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase2_calc_indices\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase2__calc__indices\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase2_calc_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase2__calc__positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase2_calc_pressures\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase2__calc__pressures\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase2_calc_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase2__calc__steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase2_calc_total_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase2__calc__total_displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase2_calc_unwrapped_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase2__calc__unwrapped_positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase2_calc_volume\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase2__calc__volume\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label de to the io key compare_de\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label de to the io key compare__de\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label user_input to the io key element\n", " warn(\n", @@ -3912,48 +3912,48 @@ " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1_structure_c\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1__structure__c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1_structure_covera\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1__structure__covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1_structure_u\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1__structure__u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1_structure_orthorhombic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1__structure__orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1_structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1__structure__cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1_calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1__calc__n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1_calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1__calc__n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1_calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1__calc__pressure\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2_structure_c\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2__structure__c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2_structure_covera\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2__structure__covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2_structure_u\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2__structure__u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2_structure_orthorhombic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2__structure__orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2_structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2__structure__cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2_calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2__calc__n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2_calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2__calc__n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2_calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2__calc__pressure\n", " warn(\n" ] } ], "source": [ "out = wf(element=\"Al\", phase1=\"fcc\", phase2=\"hcp\", lattice_guess1=4, lattice_guess2=4)\n", - "print(f\"{wf.inputs.element.value}: E({wf.inputs.phase2.value}) - E({wf.inputs.phase1.value}) = {out.compare_de:.2f} eV/atom\")" + "print(f\"{wf.inputs.element.value}: E({wf.inputs.phase2.value}) - E({wf.inputs.phase1.value}) = {out.compare__de:.2f} eV/atom\")" ] }, { @@ -3980,41 +3980,41 @@ " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1_structure_c\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1__structure__c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1_structure_covera\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1__structure__covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1_structure_u\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1__structure__u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1_structure_orthorhombic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1__structure__orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1_structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1__structure__cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1_calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1__calc__n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1_calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1__calc__n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1_calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1__calc__pressure\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2_structure_c\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2__structure__c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2_structure_covera\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2__structure__covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2_structure_u\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2__structure__u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2_structure_orthorhombic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2__structure__orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2_structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2__structure__cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2_calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2__calc__n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2_calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2__calc__n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2_calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2__calc__pressure\n", " warn(\n" ] }, @@ -4031,55 +4031,55 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase1_calc_cells\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase1__calc__cells\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase1_calc_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase1__calc__displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase1_calc_energy_tot\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase1__calc__energy_tot\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase1_calc_force_max\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase1__calc__force_max\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase1_calc_forces\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase1__calc__forces\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase1_calc_indices\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase1__calc__indices\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase1_calc_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase1__calc__positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase1_calc_pressures\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase1__calc__pressures\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase1_calc_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase1__calc__steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase1_calc_total_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase1__calc__total_displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase1_calc_unwrapped_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase1__calc__unwrapped_positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase1_calc_volume\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase1__calc__volume\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase2_calc_cells\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase2__calc__cells\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase2_calc_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase2__calc__displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase2_calc_energy_tot\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase2__calc__energy_tot\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase2_calc_force_max\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase2__calc__force_max\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase2_calc_forces\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase2__calc__forces\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase2_calc_indices\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase2__calc__indices\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase2_calc_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase2__calc__positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase2_calc_pressures\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase2__calc__pressures\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase2_calc_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase2__calc__steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase2_calc_total_displacements\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase2__calc__total_displacements\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase2_calc_unwrapped_positions\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase2__calc__unwrapped_positions\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase2_calc_volume\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase2__calc__volume\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label de to the io key compare_de\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label de to the io key compare__de\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label user_input to the io key element\n", " warn(\n", @@ -4087,48 +4087,48 @@ " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1_structure_c\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1__structure__c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1_structure_covera\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1__structure__covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1_structure_u\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1__structure__u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1_structure_orthorhombic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1__structure__orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1_structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1__structure__cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1_calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1__calc__n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1_calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1__calc__n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1_calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1__calc__pressure\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", " warn(\n", "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2_structure_c\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2__structure__c\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2_structure_covera\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2__structure__covera\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2_structure_u\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2__structure__u\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2_structure_orthorhombic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2__structure__orthorhombic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2_structure_cubic\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2__structure__cubic\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2_calc_n_ionic_steps\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2__calc__n_ionic_steps\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2_calc_n_print\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2__calc__n_print\n", " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2_calc_pressure\n", + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2__calc__pressure\n", " warn(\n" ] } ], "source": [ "out = wf(element=\"Mg\", phase1=\"fcc\", phase2=\"hcp\", lattice_guess1=3, lattice_guess2=3)\n", - "print(f\"{wf.inputs.element.value}: E({wf.inputs.phase2.value}) - E({wf.inputs.phase1.value}) = {out.compare_de:.2f} eV/atom\")" + "print(f\"{wf.inputs.element.value}: E({wf.inputs.phase2.value}) - E({wf.inputs.phase1.value}) = {out.compare__de:.2f} eV/atom\")" ] } ], From f8489df697221ebd73a88d37815352094c4e29cc Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 10 Aug 2023 10:34:31 -0700 Subject: [PATCH 568/756] Add macros to list of topics covered --- notebooks/workflow_example.ipynb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 244b43628..3672ba0ef 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -39,7 +39,8 @@ "- 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", - "- Using pre-defined nodes " + "- Using pre-defined nodes \n", + "- Macro nodes" ] }, { From e01abc3d55058c38010db783e7124309021edd7b Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 10 Aug 2023 10:39:44 -0700 Subject: [PATCH 569/756] Uncomment test And adjust the path to the sub-node output ever so slightly --- tests/unit/workflow/test_macro.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/unit/workflow/test_macro.py b/tests/unit/workflow/test_macro.py index fb61a48d1..29e53935d 100644 --- a/tests/unit/workflow/test_macro.py +++ b/tests/unit/workflow/test_macro.py @@ -183,13 +183,14 @@ def deep_macro(macro): nested = Macro(deep_macro) plain = Macro(my_macro) - # plain.inputs.a__x = nested.m.outputs.a__result - # self.assertTrue( - # nested.connects_to_input_of(plain), - # msg="A child of the nested macro has a connection to the plain macros" - # "input, so the entire nested macro should count as having a " - # "connection to the plain macro's input." - # ) + plain.inputs.a__x = nested.m.outputs.b__result + print(nested.m.outputs.labels) + self.assertTrue( + nested.connects_to_input_of(plain), + msg="A child of the nested macro has a connection to the plain macros" + "input, so the entire nested macro should count as having a " + "connection to the plain macro's input." + ) def test_custom_start(self): def modified_start_macro(macro): From 107e5b51dcf41250559f4122bccff7bcb75f2845 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 10 Aug 2023 11:19:40 -0700 Subject: [PATCH 570/756] Use info instead of a warning sometimes Where info comes from the pyiron_base logger --- pyiron_contrib/workflow/composite.py | 7 +++---- pyiron_contrib/workflow/io.py | 5 ++--- pyiron_contrib/workflow/util.py | 5 +++++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 8ef5f698f..bec0a6887 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -8,13 +8,12 @@ from abc import ABC from functools import partial from typing import Literal, Optional, TYPE_CHECKING -from warnings import warn from pyiron_contrib.workflow.interfaces import Creator, Wrappers from pyiron_contrib.workflow.io import Outputs, Inputs from pyiron_contrib.workflow.node import Node from pyiron_contrib.workflow.node_package import NodePackage -from pyiron_contrib.workflow.util import DotDict, SeabornColors +from pyiron_contrib.workflow.util import logger, DotDict, SeabornColors if TYPE_CHECKING: from pyiron_contrib.workflow.channels import Channel @@ -265,7 +264,7 @@ def _add_suffix_to_label(self, label): new_label = f"{label}{i}" i += 1 if new_label != label: - warn( + logger.info( f"{label} is already a node; appending an index to the " f"node label instead: {new_label}" ) @@ -285,7 +284,7 @@ def _ensure_node_is_not_duplicated(self, node: Node, label: str): and label != node.label and self.nodes[node.label] is node ): - warn( + logger.info( f"Reassigning the node {node.label} to the label {label} when " f"adding it to the parent {self.label}." ) diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py index 1a4df7cdd..52487c805 100644 --- a/pyiron_contrib/workflow/io.py +++ b/pyiron_contrib/workflow/io.py @@ -5,7 +5,6 @@ from __future__ import annotations from abc import ABC, abstractmethod -from warnings import warn from pyiron_contrib.workflow.channels import ( Channel, @@ -18,7 +17,7 @@ ) from pyiron_contrib.workflow.has_channel import HasChannel from pyiron_contrib.workflow.has_to_dict import HasToDict -from pyiron_contrib.workflow.util import DotDict +from pyiron_contrib.workflow.util import DotDict, logger class IO(HasToDict, ABC): @@ -84,7 +83,7 @@ def __setattr__(self, key, value): self._assign_value_to_existing_channel(self.channel_dict[key], value) elif isinstance(value, self._channel_class): if key != value.label: - warn( + logger.info( f"Assigning a channel with the label {value.label} to the io key " f"{key}" ) diff --git a/pyiron_contrib/workflow/util.py b/pyiron_contrib/workflow/util.py index b4c0041cd..61dae6c7c 100644 --- a/pyiron_contrib/workflow/util.py +++ b/pyiron_contrib/workflow/util.py @@ -1,3 +1,8 @@ +from pyiron_base import state + +logger = state.logger + + class DotDict(dict): def __getattr__(self, item): return self.__getitem__(item) From a5e130f00ff59a92e30e99d78a1993924340af4f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 10 Aug 2023 11:20:08 -0700 Subject: [PATCH 571/756] Update the example notebook No change except that it's much easier to read now that there aren't all those ugly warnings about stuff being renamed! --- notebooks/workflow_example.ipynb | 699 +------------------------------ 1 file changed, 9 insertions(+), 690 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 3672ba0ef..c349e5178 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -11,7 +11,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "160e79d4e1574a3089b810aa46d95d3f", + "model_id": "19642bc7474d485f853346bc831a1366", "version_major": 2, "version_minor": 0 }, @@ -717,7 +717,7 @@ "outputs": [ { "data": { - "image/png": "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AKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrogojDQ0NKiwslMfjUWlpqVpaWsbsu3//fi1ZskQzZszQ1KlTtXDhQv3P//xP1AUDAIDkku70hF27dmndunVqaGjQkiVL9Nxzz2nZsmXy+/2aM2dOWP9p06bpvvvu02WXXaZp06Zp//79uueeezRt2jT95Cc/mZAPAQAAEpfLGGOcnFBRUaGSkhI1NjaOti1atEgrVqxQfX39uH7HzTffrGnTpun3v//9uPr39PTI6/UqGAwqKyvLSbkAAMCS8X5/O1qmGRgY0OHDh1VdXR3SXl1drQMHDozrdxw9elQHDhzQNddcM2af/v5+9fT0hBwAACA5OQojnZ2dGhwcVE5OTkh7Tk6O2tvbz3tuXl6eMjMzVVZWpp/+9Kdas2bNmH3r6+vl9XpHj/z8fCdlAgCABBLVBawulyvktTEmrO2rWlpadOjQIT377LN68skn9corr4zZd+PGjQoGg6PHqVOnoikTAAAkAEcXsM6cOVNutztsFqSjoyNstuSrCgsLJUnf+c539Mknn+iRRx7RD3/4w4h9MzMzlZmZ6aQ0AACQoBzNjGRkZKi0tFTNzc0h7c3Nzaqqqhr37zHGqL+/38lbAwCAJOX41t7169dr9erVKisrU2VlpbZs2aK2tjbV1NRIGl5iOXPmjLZv3y5JeuaZZzRnzhwtXLhQ0vC+I48//rjuv//+CfwYAAAgUTkOIytXrlRXV5c2b96sQCCg4uJi7d27VwUFBZKkQCCgtra20f5DQ0PauHGjWltblZ6errlz5+pXv/qV7rnnnon7FAAAIGE53mfEBvYZAQAg8UzKPiMAAAATjTACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALAq3XYB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", + "image/png": "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AACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAq8IKI3V1dcrOzpbH41FhYaGam5tP239wcFCbN29WVlaW3G63FixYoB07doRVMAAAiC9JTk9oaGhQZWWl6urqtHjxYj3xxBNavny5Ojo6dNFFF53ynJtuukkff/yxtm/frq997Wvq7e3V559/PuXiAQBA7HMZY4yTExYtWqSCggLV19ePteXm5mrlypWqra0d13///v36q7/6K73//vtKS0sLq8hgMCiv16tAIKDU1NSwngMAAETXZN+/HS3TDA0NqbW1VWVlZSHtZWVlamlpOeU5L730koqKivTggw/qwgsv1KWXXqp77rlHv//97yf8PYODgwoGgyEHAACIT46Wafr6+jQ8PKz09PSQ9vT0dPX09JzynPfff18HDhyQx+PRiy++qL6+Pv3d3/2djh8/PuF1I7W1taqpqXFSGgAAiFFhXcDqcrlCHhtjxrWNGhkZkcvl0u7du1VcXKxvfetbevjhh/X0009PODtSVVWlQCAwdnR3d4dTJgAAiAGOZkbmzp2rxMTEcbMgvb2942ZLRvl8Pl144YXyer1jbbm5uTLG6MMPP9Qll1wy7hy32y232+2kNAAAEKMczYwkJyersLBQTU1NIe1NTU0qLS095TmLFy/WRx99pE8//XSs7ciRI0pISND8+fPDKBkAAMQTx8s0Gzdu1JNPPqkdO3bo7bff1t13362uri5VVFRIOrnEcvPNN4/1X7VqlebMmaNbb71VHR0deu2113Tvvffqb/7mb5SSkhK5kQAAgJjkeJ+R8vJy9ff3a8uWLfL7/crPz1djY6OysrIkSX6/X11dXWP9v/KVr6ipqUnf//73VVRUpDlz5uimm27SD37wg8iNAgAAxCzH+4zYwD4jAADEnmnZZwQAACDSCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKuSbBcAAEA4hkeMDnYeV+/ACc2b7VFxdpoSE1y2y0IYCCMAgJizv92vmn0d8gdOjLX5vB5Vr8jTsnyfxcoQDpZpAAAxZX+7Xxt2HQoJIpLUEzihDbsO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", "text/plain": [ "
" ] @@ -806,7 +806,7 @@ "outputs": [ { "data": { - "image/png": "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", + "image/png": "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368vfelLGhgY0OXLl6+7eMApJxcctopOLwOj0X8vYukHYOYch5G9e/dq8+bN2rJliyRp3759evvtt9Xc3KzGxsaw/m1tbTp69KjOnz+vvLw8SdKtt956fVUDMeKCg2gWzXdfu5ODfgBmztE0zaVLl9TV1aWqqqqQ9qqqKp04cSLiMW+99ZbKysr0ox/9SEuWLNHtt9+uJ598Up988knUzxkfH9fIyEjIC5gNXHAQTXlRnrwet6LdT+XS5CLn8qK8eJYFpAVHYWRwcFDBYFD5+fkh7fn5+erv7494zPnz5/XOO++op6dHb775pvbt26fXX39d3/3ud6N+TmNjozwez/SrsLDQSZlAVFxwEE1mhkv11cWSFPb7MfW+vrqY27+BORDTAlaXK/QfozEmrG3KxMSEXC6XDh06pPLyct13333au3evXn755aijI7t27VIgEJh+XbhwIZYygTBccHA160u8at64SgWe0JGxAo+b23qBOeRozcjChQuVmZkZNgoyMDAQNloyxev1asmSJfJ4PNNtK1eulDFGFy9e1G233RZ2TE5OjnJycpyUBszY1AXn8/uMFLDPCDT5+7GuuIAN8YA4chRGsrOzVVpaqvb2dv3lX/7ldHt7e7u+9a1vRTxmzZo1eu211/Thhx/qC1/4giTp3XffVUZGhpYuXXodpQOx44KDq8nMcHE3FRBHjqdp6urq9OKLL+rAgQM6e/asduzYob6+PtXW1kqanGLZtGnTdP+HHnpICxYs0GOPPSafz6djx47pqaee0t/8zd/ohhtumL2fBHBo6oLzrbuWqGLFAoIIAFji+NbempoaDQ0Nac+ePfL7/SopKVFra6uWLVsmSfL7/err65vu/4UvfEHt7e36u7/7O5WVlWnBggV68MEH9cwzz8zeTwEAAJKWyxiT8FtNjoyMyOPxKBAIKDc313Y5AABgBmZ6/ebZNAAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACr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5aUcp+f7n//5n83y5ctD2p599lmzdOnSOasxFc0kjMT7WpnU0zSXLl1SV1eXqqqqQtqrqqp04sSJiMecPHkyrP+9996rU6dO6dNPP52zWlNBLOf78yYmJjQ6Oqq8vLy5KDGlxHq+X3rpJZ07d0719fVzXWJKieV8v/XWWyorK9OPfvQjLVmyRLfffruefPJJffLJJ/EoOanFcr4rKyt18eJFtba2yhijDz74QK+//rruv//+eJScVuJ9rUyKp/ZGMzg4qGAwqPz8/JD2/Px89ff3Rzymv78/Yv/Lly9rcHBQXq93zupNdrGc78/78Y9/rI8++kgPPvjgXJSYUmI533/4wx+0c+dOdXR0KCsrqf95x10s5/v8+fN655135Ha79eabb2pwcFDf+c53NDw8zLqRa4jlfFdWVurQoUOqqanR2NiYLl++rG9+85v66U9/Go+S00q8r5VJPTIyxeVyhbw3xoS1Xat/pHZE5vR8T3n11Vf1gx/8QC0tLVq0aNFclZdyZnq+g8GgHnroITU0NOj222+PV3kpx8nv98TEhFwulw4dOqTy8nLdd9992rt3r15++WVGR2bIyfn2+Xzatm2bnn76aXV1damtrU29vb2qra2NR6lpJ57XyqT+r9PChQuVmZkZlqIHBgbCEt2UgoKCiP2zsrK0YMGCOas1FcRyvqe0tLRo8+bNeu2113TPPffMZZkpw+n5Hh0d1alTp9Td3a3vfe97kiYvlsYYZWVl6ciRI7r77rvjUnsyiuX32+v1asmSJfJ4PNNtK1eulDFGFy9e1G233TanNSezWM53Y2Oj1qxZo6eeekqS9NWvflU33XST1q5dq2eeeYaR7VkU72tlUo+MZGdnq7S0VO3t7SHt7e3tqqysjHhMRUVFWP8jR46orKxM8+bNm7NaU0Es51uaHBF59NFH9corrzC364DT852bm6vf/e53OnPmzPSrtrZWX/7yl3XmzBmtXr06XqUnpVh+v9esWaM//vGP+vDDD6fb3n33XWVkZGjp0qVzWm+yi+V8f/zxx8rICL1sZWZmSvqf/7VjdsT9Wjkny2LjaOrWsP379xufz2e2b99ubrrpJvPf//3fxhhjdu7caR5++OHp/lO3K+3YscP4fD6zf/9+bu11wOn5fuWVV0xWVpZ57rnnjN/vn3796U9/svUjJBWn5/vzuJvGGafne3R01CxdutT81V/9lfn9739vjh49am677TazZcsWWz9CUnF6vl966SWTlZVlmpqazLlz58w777xjysrKTHl5ua0fIWmMjo6a7u5u093dbSSZvXv3mu7u7unbqG1fK5M+jBhjzHPPPWeWLVtmsrOzzapVq8zRo0en/+yRRx4xX//610P6//u//7v5sz/7M5OdnW1uvfVW09zcHOeKk5uT8/31r3/dSAp7PfLII/EvPEk5/f2+EmHEOafn++zZs+aee+4xN9xwg1m6dKmpq6szH3/8cZyrTl5Oz/ezzz5riouLzQ033GC8Xq/567/+a3Px4sU4V518/u3f/u2q38W2r5UuYxjbAgAA9iT1mhEAAJD8CCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACw6v8DGQK5uK9x00UAAAAASUVORK5CYII=", "text/plain": [ "
" ] @@ -923,14 +923,6 @@ "text": [ "2\n" ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label sum_ to the io key sum__sum_\n", - " warn(\n" - ] } ], "source": [ @@ -963,16 +955,6 @@ "id": "52c48d19-10a2-4c48-ae81-eceea4129a60", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label x to the io key a__x\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label x to the io key b__x\n", - " warn(\n" - ] - }, { "data": { "text/plain": [ @@ -1038,16 +1020,6 @@ "id": "ae500d5e-e55b-432c-8b5f-d5892193cdf5", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:288: UserWarning: Reassigning the node bulk 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:288: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent with_prebuilt.\n", - " warn(\n" - ] - }, { "name": "stdout", "output_type": "stream", @@ -1055,16 +1027,6 @@ "The job JUSTAJOBNAME was saved and received the ID: 9558\n" ] }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:288: 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:288: UserWarning: Reassigning the node scatter to the label plot when adding it to the parent with_prebuilt.\n", - " warn(\n" - ] - }, { "data": { "image/png": "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", @@ -1106,62 +1068,6 @@ "id": "be3dd2a3-0cb2-4fc4-a07f-7ec719bbc6c9", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label name to the io key structure__name\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key structure__crystalstructure\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key structure__a\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key structure__c\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key structure__covera\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key structure__u\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key structure__orthorhombic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key structure__cubic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc__n_ionic_steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key calc__n_print\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label temperature to the io key calc__temperature\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key calc__pressure\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key calc__cells\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key calc__displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_pot to the io key calc__energy_pot\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key calc__energy_tot\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key calc__force_max\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key calc__forces\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key calc__indices\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key calc__positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key calc__pressures\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key calc__total_displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc__unwrapped_positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key calc__volume\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label fig to the io key plot__fig\n", - " warn(\n" - ] - }, { "data": { "image/svg+xml": [ @@ -1951,7 +1857,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 34, @@ -1977,62 +1883,6 @@ "id": "2114d0c3-cdad-43c7-9ffa-50c36d56d18f", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label name to the io key structure__name\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key structure__crystalstructure\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key structure__a\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key structure__c\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key structure__covera\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key structure__u\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key structure__orthorhombic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key structure__cubic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc__n_ionic_steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key calc__n_print\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label temperature to the io key calc__temperature\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key calc__pressure\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key calc__cells\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key calc__displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_pot to the io key calc__energy_pot\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key calc__energy_tot\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key calc__force_max\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key calc__forces\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key calc__indices\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key calc__positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key calc__pressures\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key calc__total_displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc__unwrapped_positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key calc__volume\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label fig to the io key plot__fig\n", - " warn(\n" - ] - }, { "data": { "image/svg+xml": [ @@ -2237,7 +2087,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 35, @@ -2275,16 +2125,6 @@ "id": "2b9bb21a-73cd-444e-84a9-100e202aa422", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label x to the io key add_one__x\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label result to the io key add_three__result\n", - " warn(\n" - ] - }, { "data": { "text/plain": [ @@ -2334,16 +2174,6 @@ "id": "3668f9a9-adca-48a4-84ea-13add965897c", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label result to the io key intermediate\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label result to the io key plus_three\n", - " warn(\n" - ] - }, { "data": { "text/plain": [ @@ -2428,68 +2258,7 @@ "execution_count": 40, "id": "a832e552-b3cc-411a-a258-ef21574fc439", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:288: UserWarning: Reassigning the node user_input to the label element when adding it to the parent phase_preference.\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:288: UserWarning: Reassigning the node bulk to the label structure when adding it to the parent lammps_minimize.\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:288: UserWarning: Reassigning the node lammps to the label engine when adding it to the parent lammps_minimize.\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/composite.py:288: UserWarning: Reassigning the node calc_min to the label calc when adding it to the parent lammps_minimize.\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label name to the io key element\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key structure__c\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key structure__covera\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key structure__u\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key structure__orthorhombic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key structure__cubic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key calc__n_ionic_steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key calc__n_print\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key calc__pressure\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key calc__cells\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key calc__displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_pot to the io key energy\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key calc__energy_tot\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key calc__force_max\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key calc__forces\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key calc__indices\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key calc__positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key calc__pressures\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key calc__steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key calc__total_displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key calc__unwrapped_positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key calc__volume\n", - " warn(\n" - ] - } - ], + "outputs": [], "source": [ "wf = Workflow(\"phase_preference\")\n", "wf.element = wf.create.standard.UserInput()\n", @@ -2522,104 +2291,6 @@ "id": "b764a447-236f-4cb7-952a-7cba4855087d", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label user_input to the io key element\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase1\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1__structure__c\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1__structure__covera\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1__structure__u\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1__structure__orthorhombic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1__structure__cubic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1__calc__n_ionic_steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1__calc__n_print\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1__calc__pressure\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2__structure__c\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2__structure__covera\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2__structure__u\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2__structure__orthorhombic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2__structure__cubic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2__calc__n_ionic_steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2__calc__n_print\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2__calc__pressure\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase1__calc__cells\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase1__calc__displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase1__calc__energy_tot\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase1__calc__force_max\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase1__calc__forces\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase1__calc__indices\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase1__calc__positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase1__calc__pressures\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase1__calc__steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase1__calc__total_displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase1__calc__unwrapped_positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase1__calc__volume\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase2__calc__cells\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase2__calc__displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase2__calc__energy_tot\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase2__calc__force_max\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase2__calc__forces\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase2__calc__indices\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase2__calc__positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase2__calc__pressures\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase2__calc__steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase2__calc__total_displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase2__calc__unwrapped_positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase2__calc__volume\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label de to the io key compare__de\n", - " warn(\n" - ] - }, { "data": { "image/svg+xml": [ @@ -3722,7 +3393,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 41, @@ -3740,216 +3411,16 @@ "id": "b51bef25-86c5-4d57-80c1-ab733e703caf", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label user_input to the io key element\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase1\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1__structure__c\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1__structure__covera\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1__structure__u\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1__structure__orthorhombic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1__structure__cubic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1__calc__n_ionic_steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1__calc__n_print\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1__calc__pressure\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2__structure__c\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2__structure__covera\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2__structure__u\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2__structure__orthorhombic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2__structure__cubic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2__calc__n_ionic_steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2__calc__n_print\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2__calc__pressure\n", - " warn(\n" - ] - }, { "name": "stdout", "output_type": "stream", "text": [ "The job JUSTAJOBNAME was saved and received the ID: 9558\n", - "The job JUSTAJOBNAME was saved and received the ID: 9558\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label user_input to the io key element\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase1\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1__structure__c\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1__structure__covera\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1__structure__u\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1__structure__orthorhombic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1__structure__cubic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1__calc__n_ionic_steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1__calc__n_print\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1__calc__pressure\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2__structure__c\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2__structure__covera\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2__structure__u\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2__structure__orthorhombic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2__structure__cubic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2__calc__n_ionic_steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2__calc__n_print\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2__calc__pressure\n", - " warn(\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "The job JUSTAJOBNAME was saved and received the ID: 9558\n", "The job JUSTAJOBNAME was saved and received the ID: 9558\n", "The job JUSTAJOBNAME was saved and received the ID: 9558\n", "Al: E(hcp) - E(fcc) = 1.17 eV/atom\n" ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase1__calc__cells\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase1__calc__displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase1__calc__energy_tot\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase1__calc__force_max\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase1__calc__forces\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase1__calc__indices\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase1__calc__positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase1__calc__pressures\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase1__calc__steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase1__calc__total_displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase1__calc__unwrapped_positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase1__calc__volume\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase2__calc__cells\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase2__calc__displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase2__calc__energy_tot\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase2__calc__force_max\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase2__calc__forces\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase2__calc__indices\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase2__calc__positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase2__calc__pressures\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase2__calc__steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase2__calc__total_displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase2__calc__unwrapped_positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase2__calc__volume\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label de to the io key compare__de\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label user_input to the io key element\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase1\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1__structure__c\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1__structure__covera\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1__structure__u\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1__structure__orthorhombic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1__structure__cubic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1__calc__n_ionic_steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1__calc__n_print\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1__calc__pressure\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2__structure__c\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2__structure__covera\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2__structure__u\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2__structure__orthorhombic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2__structure__cubic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2__calc__n_ionic_steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2__calc__n_print\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2__calc__pressure\n", - " warn(\n" - ] } ], "source": [ @@ -3968,163 +3439,11 @@ "output_type": "stream", "text": [ "The job JUSTAJOBNAME was saved and received the ID: 9558\n", - "The job JUSTAJOBNAME was saved and received the ID: 9558\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label user_input to the io key element\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase1\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1__structure__c\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1__structure__covera\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1__structure__u\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1__structure__orthorhombic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1__structure__cubic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1__calc__n_ionic_steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1__calc__n_print\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1__calc__pressure\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2__structure__c\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2__structure__covera\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2__structure__u\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2__structure__orthorhombic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2__structure__cubic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2__calc__n_ionic_steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2__calc__n_print\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2__calc__pressure\n", - " warn(\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "The job JUSTAJOBNAME was saved and received the ID: 9558\n", "The job JUSTAJOBNAME was saved and received the ID: 9558\n", "The job JUSTAJOBNAME was saved and received the ID: 9558\n", "Mg: E(hcp) - E(fcc) = -4.54 eV/atom\n" ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase1__calc__cells\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase1__calc__displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase1__calc__energy_tot\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase1__calc__force_max\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase1__calc__forces\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase1__calc__indices\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase1__calc__positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase1__calc__pressures\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase1__calc__steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase1__calc__total_displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase1__calc__unwrapped_positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase1__calc__volume\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cells to the io key min_phase2__calc__cells\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label displacements to the io key min_phase2__calc__displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label energy_tot to the io key min_phase2__calc__energy_tot\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label force_max to the io key min_phase2__calc__force_max\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label forces to the io key min_phase2__calc__forces\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label indices to the io key min_phase2__calc__indices\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label positions to the io key min_phase2__calc__positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressures to the io key min_phase2__calc__pressures\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label steps to the io key min_phase2__calc__steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label total_displacements to the io key min_phase2__calc__total_displacements\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label unwrapped_positions to the io key min_phase2__calc__unwrapped_positions\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label volume to the io key min_phase2__calc__volume\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label de to the io key compare__de\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label user_input to the io key element\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase1\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess1\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase1__structure__c\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase1__structure__covera\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase1__structure__u\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase1__structure__orthorhombic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase1__structure__cubic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase1__calc__n_ionic_steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase1__calc__n_print\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase1__calc__pressure\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label crystalstructure to the io key phase2\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label a to the io key lattice_guess2\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label c to the io key min_phase2__structure__c\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label covera to the io key min_phase2__structure__covera\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label u to the io key min_phase2__structure__u\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label orthorhombic to the io key min_phase2__structure__orthorhombic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label cubic to the io key min_phase2__structure__cubic\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_ionic_steps to the io key min_phase2__calc__n_ionic_steps\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label n_print to the io key min_phase2__calc__n_print\n", - " warn(\n", - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/workflow/io.py:87: UserWarning: Assigning a channel with the label pressure to the io key min_phase2__calc__pressure\n", - " warn(\n" - ] } ], "source": [ From 0f7105af6ae22a65793430fccf58fec2779e8c60 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 10 Aug 2023 11:28:11 -0700 Subject: [PATCH 572/756] Test to the behaviour promised in the warning --- tests/unit/workflow/test_function.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 4d40382ad..67afdb5d9 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -328,10 +328,16 @@ def test_call(self): msg="__call__ should invoke update s.t. run gets called if run_on_updates" ) - with self.subTest("Check that node kwargs can also be updated"): + with self.subTest("Check that bad kwargs don't stop good ones"): with self.assertWarns(Warning): - node(4, run_on_updates=False, y=5) + node.run_on_updates = True + node(4, run_on_updates=False, y=5, foobar="not a kwarg of any sort") + self.assertTrue( + node.run_on_updates, + msg="You should only be able to update input on a call, that's " + "what the warning is for!" + ) self.assertTupleEqual( (node.inputs.x.value, node.inputs.y.value), (4, 5), From dce649729cb50cf9647edbeddb1985cbaf708c8c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 10 Aug 2023 13:53:02 -0700 Subject: [PATCH 573/756] Give access to meta nodes on creator --- pyiron_contrib/workflow/interfaces.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyiron_contrib/workflow/interfaces.py b/pyiron_contrib/workflow/interfaces.py index 607a2f6a7..24f5c9805 100644 --- a/pyiron_contrib/workflow/interfaces.py +++ b/pyiron_contrib/workflow/interfaces.py @@ -40,6 +40,7 @@ def __init__(self): # Avoid circular imports by delaying import for children of Composite self._macro = None self._workflow = None + self._meta = None @property def Macro(self): @@ -77,6 +78,13 @@ def atomistics(self): self.register("_atomistics", *nodes) return self._atomistics + @property + def meta(self): + if self._meta is None: + from pyiron_contrib.workflow.meta import MetaNodes + self._meta = MetaNodes + return self._meta + def register(self, domain: str, *nodes: list[type[Node]]): if domain in self.__dir__(): raise AttributeError(f"{domain} is already an attribute of {self}") From 34dd622729122f675da135df13e2f8e64330fa76 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 11 Aug 2023 12:49:00 -0700 Subject: [PATCH 574/756] Draft a for-loop --- pyiron_contrib/workflow/interfaces.py | 5 +- pyiron_contrib/workflow/meta.py | 168 +++++++++++++++++++++++--- 2 files changed, 154 insertions(+), 19 deletions(-) diff --git a/pyiron_contrib/workflow/interfaces.py b/pyiron_contrib/workflow/interfaces.py index 24f5c9805..861ee41d3 100644 --- a/pyiron_contrib/workflow/interfaces.py +++ b/pyiron_contrib/workflow/interfaces.py @@ -19,7 +19,6 @@ ) if TYPE_CHECKING: - from pyiron_contrib.workflow.composite import Composite from pyiron_contrib.workflow.node import Node @@ -81,8 +80,8 @@ def atomistics(self): @property def meta(self): if self._meta is None: - from pyiron_contrib.workflow.meta import MetaNodes - self._meta = MetaNodes + from pyiron_contrib.workflow.meta import meta_nodes + self._meta = meta_nodes return self._meta def register(self, domain: str, *nodes: list[type[Node]]): diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index fd937a1df..ada417ffd 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -4,24 +4,160 @@ from __future__ import annotations -from pyiron_contrib.workflow.function import single_value_node, SingleValue +from pyiron_contrib.workflow.function import ( + Function, SingleValue, function_node, single_value_node +) +from pyiron_contrib.workflow.macro import Macro, macro_node +from pyiron_contrib.workflow.node import Node +from pyiron_contrib.workflow.util import DotDict -def _input_to_list(n_elements) -> callable: - string = "def input_to_list(" - for i in range(n_elements): - string += f"i{i}=None, " - string += "): return [" - for i in range(n_elements): - string += f"i{i}, " - string += "]" - exec(string) - return locals()["input_to_list"] +def list_to_output(length: int, **node_class_kwargs) -> type[Function]: + """ + A meta-node that returns a node class with `length` input channels and + maps these to a single output channel with type `list`. + """ + def _list_to_many(length: int): + template = f""" +def __list_to_many(l: list): + {"; ".join([f"out{i} = l[{i}]" for i in range(length)])} + return [{", ".join([f"out{i}" for i in range(length)])}] + """ + exec(template) + return locals()["__list_to_many"] -class MetaNodes: - """A container class for meta node access""" + return function_node(**node_class_kwargs)(_list_to_many(length=length)) + + +def input_to_list(length: int, **node_class_kwargs) -> type[SingleValue]: + """ + A meta-node that returns a node class with `length` output channels and + maps an input list to these. + """ + + def _many_to_list(length: int): + template = f""" +def __many_to_list({", ".join([f"inp{i}=None" for i in range(length)])}): + return [{", ".join([f"inp{i}" for i in range(length)])}] + """ + exec(template) + return locals()["__many_to_list"] + + return single_value_node(**node_class_kwargs)(_many_to_list(length=length)) + + +def for_loop( + node_class: type[Node], + length: int, + iterate_on: str | tuple[str] | list[str], + # TODO: +) -> type[Macro]: + """ + An _extremely rough_ first draft of a for-loop meta-node. + + Takes a node class, how long the loop should be, and which input(s) of the provided + node class should be looped over (given as strings of the channel labels) and + builds a macro that + - Makes copies of the provided node class, i.e. the "body node" + - For each input channel specified to "loop over", creates a list-to-many node and + connects each of its outputs to their respective body node inputs + - For all other inputs, makes a 1:1 node and connects its output to _all_ of the + body nodes + - Relables the macro IO to match the passed node class IO so that list-ified IO + (i.e. the specified input and all output) is all caps + + Examples: + >>> bulk_loop = for_loop( + ... Workflow.create.atomistics.Bulk, + ... 5, + ... iterate_on = ("a",), + ... )() + >>> + >>> [ + ... struct.cell.volume for struct in bulk_loop( + ... name="Al", # Sent equally to each body node + ... A=np.linspace(3.9, 4.1, 5).tolist(), # Distributed across body nodes + ... ).STRUCTURE + ... ] + [14.829749999999995, + 15.407468749999998, + 15.999999999999998, + 16.60753125, + 17.230249999999995] + + TODO: + - Refactor like crazy, it's super hard to read and some stuff is too hard-coded + - Give some sort of access to flow control?? + - How to handle passing executors to the children? Maybe this is more + generically a Macro question? + - Is it possible to somehow dynamically adapt the held graph depending on the + length of the input values being iterated over? Tricky to keep IO well defined + - Allow a different mode, or make a different meta node, that makes all possible + pairs of body nodes given the input being looped over instead of just `length` + - Provide enter and exit magic methods so we can `for` or `with` this fancy-like + """ + iterate_on = [iterate_on] if isinstance(iterate_on, str) else iterate_on + + def make_loop(macro): + macro.inputs_map = {} + macro.outputs_map = {} + body_nodes = [] + + # Parallelize over body nodes + for n in range(length): + body_nodes.append( + macro.add(node_class(label=f"{node_class.__name__}_{n}")) + ) + + # Make input interface + for inp in body_nodes[0].inputs: + # Scatter a list of inputs to each node separately + if inp.label in iterate_on: + interface = list_to_output(length)( + parent=macro, + label=inp.label.upper(), + output_labels=[f"{node_class.__name__}__{inp.label}_{i}" for i in + range(length)], + l=[inp.default] * length + ) + # Connect each body node input to the input interface's respective output + for body_node, out in zip(body_nodes, interface.outputs): + body_node.inputs[inp.label] = out + macro.inputs_map[f"{interface.label}__l"] = interface.label + # TODO: Don't hardcode __l + # Or distribute the same input to each node equally + else: + interface = macro.create.standard.UserInput( + label=inp.label, + output_labels=inp.label, + user_input=inp.default + ) + for body_node in body_nodes: + body_node.inputs[inp.label] = interface + macro.inputs_map[f"{interface.label}__user_input"] = interface.label + # TODO: Don't hardcode __user_input + + # Make output interface: outputs to lists + for out in body_nodes[0].outputs: + interface = input_to_list(length)( + parent=macro, + label=out.label.upper(), + output_labels=f"{node_class.__name__}__{out.label}" + ) + # Connect each body node output to the output interface's respective input + for body_node, inp in zip(body_nodes, interface.inputs): + inp.connect(body_node.outputs[out.label]) + macro.outputs_map[ + f"{interface.label}__{node_class.__name__}__{out.label}"] = interface.label + # TODO: Don't manually copy the output label construction + + return macro_node()(make_loop) + + +meta_nodes = DotDict({ + for_loop.__name__: for_loop, + input_to_list.__name__: input_to_list, + list_to_output.__name__: list_to_output, +}) - @classmethod - def input_to_list(cls, n_inputs: int) -> SingleValue: - return single_value_node("list")(_input_to_list(n_inputs)) From 9aa2206bcbc430aa80e12ada59e877605efc869a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:41:04 +0000 Subject: [PATCH 575/756] Bump boto3 from 1.28.20 to 1.28.25 Bumps [boto3](https://github.com/boto/boto3) from 1.28.20 to 1.28.25. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.28.20...1.28.25) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d50eeb473..1487597f4 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ ], 'image': ['scikit-image==0.21.0'], 'generic': [ - 'boto3==1.28.20', + 'boto3==1.28.25', 'moto==4.1.14' ], 'workflow': [ From 635dd853e47d6da8730d9db3e134e0fe5af5c3df Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 14 Aug 2023 11:41:29 +0000 Subject: [PATCH 576/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 6d4aea1e5..2af400b5d 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 -- boto3 =1.28.20 +- boto3 =1.28.25 - moto =4.1.14 - pycp2k =0.2.2 - python-graphviz From 5bd16bcb48bb6e54d4f8c36cb5adc641e628f31b Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 14 Aug 2023 11:41:55 +0000 Subject: [PATCH 577/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index a576baaa4..987884164 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -15,7 +15,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 -- boto3 =1.28.20 +- boto3 =1.28.25 - moto =4.1.14 - pycp2k =0.2.2 - python-graphviz diff --git a/docs/environment.yml b/docs/environment.yml index ea6aa8775..1ac522adf 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -17,7 +17,7 @@ dependencies: - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 -- boto3 =1.28.20 +- boto3 =1.28.25 - moto =4.1.14 - pycp2k =0.2.2 - python-graphviz From eaf501aa772812fddcdcd3ab177dfaa679a6f847 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 14 Aug 2023 08:07:23 -0700 Subject: [PATCH 578/756] Add items method to IO --- pyiron_contrib/workflow/io.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py index 52487c805..f1476e367 100644 --- a/pyiron_contrib/workflow/io.py +++ b/pyiron_contrib/workflow/io.py @@ -129,6 +129,9 @@ def disconnect(self): def labels(self): return list(self.channel_dict.keys()) + def items(self): + return self.channel_dict.items() + def __iter__(self): return self.channel_dict.values().__iter__() From 6951f6180a4d73227e505920effb0b4d826bd784 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Mon, 14 Aug 2023 08:07:40 -0700 Subject: [PATCH 579/756] Have meta nodes use the IO label, not the channel label --- pyiron_contrib/workflow/meta.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index ada417ffd..c35a24705 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -111,45 +111,48 @@ def make_loop(macro): ) # Make input interface - for inp in body_nodes[0].inputs: + for label, inp in body_nodes[0].inputs.items(): + # Don't rely on inp.label directly, since inputs may be a Composite IO + # panel that has a different key for this input channel than its label + # Scatter a list of inputs to each node separately - if inp.label in iterate_on: + if label in iterate_on: interface = list_to_output(length)( parent=macro, - label=inp.label.upper(), + label=label.upper(), output_labels=[f"{node_class.__name__}__{inp.label}_{i}" for i in range(length)], l=[inp.default] * length ) # Connect each body node input to the input interface's respective output for body_node, out in zip(body_nodes, interface.outputs): - body_node.inputs[inp.label] = out + body_node.inputs[label] = out macro.inputs_map[f"{interface.label}__l"] = interface.label # TODO: Don't hardcode __l # Or distribute the same input to each node equally else: interface = macro.create.standard.UserInput( - label=inp.label, - output_labels=inp.label, + label=label, + output_labels=label, user_input=inp.default ) for body_node in body_nodes: - body_node.inputs[inp.label] = interface + body_node.inputs[label] = interface macro.inputs_map[f"{interface.label}__user_input"] = interface.label # TODO: Don't hardcode __user_input # Make output interface: outputs to lists - for out in body_nodes[0].outputs: + for label, out in body_nodes[0].outputs.items(): interface = input_to_list(length)( parent=macro, - label=out.label.upper(), - output_labels=f"{node_class.__name__}__{out.label}" + label=label.upper(), + output_labels=f"{node_class.__name__}__{label}" ) # Connect each body node output to the output interface's respective input for body_node, inp in zip(body_nodes, interface.inputs): - inp.connect(body_node.outputs[out.label]) + inp.connect(body_node.outputs[label]) macro.outputs_map[ - f"{interface.label}__{node_class.__name__}__{out.label}"] = interface.label + f"{interface.label}__{node_class.__name__}__{label}"] = interface.label # TODO: Don't manually copy the output label construction return macro_node()(make_loop) From 60055c95a65a2b1d164041b2a75fef71f283e340 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 11:22:36 +0000 Subject: [PATCH 580/756] Bump scipy from 1.11.1 to 1.11.2 Bumps [scipy](https://github.com/scipy/scipy) from 1.11.1 to 1.11.2. - [Release notes](https://github.com/scipy/scipy/releases) - [Commits](https://github.com/scipy/scipy/compare/v1.11.1...v1.11.2) --- updated-dependencies: - dependency-name: scipy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1487597f4..bf7c8db6c 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ 'matplotlib==3.7.2', 'numpy==1.24.3', 'pyiron_base==0.6.3', - 'scipy==1.11.1', + 'scipy==1.11.2', 'seaborn==0.12.2', 'pyparsing==3.0.9', ], From 2da1de6542d26212f6e496950b9287e504d0a3a3 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 21 Aug 2023 11:23:03 +0000 Subject: [PATCH 581/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 2af400b5d..ab45ba441 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -11,7 +11,7 @@ dependencies: - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 - pyparsing =3.0.9 -- scipy =1.11.1 +- scipy =1.11.2 - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 From 65bb1cae21af07b2e3837d7d04cf7e76a9155dab Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 21 Aug 2023 11:23:31 +0000 Subject: [PATCH 582/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 987884164..d24450514 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -11,7 +11,7 @@ dependencies: - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 - pyparsing =3.0.9 -- scipy =1.11.1 +- scipy =1.11.2 - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 diff --git a/docs/environment.yml b/docs/environment.yml index 1ac522adf..3f1080207 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -13,7 +13,7 @@ dependencies: - pyiron_base =0.6.3 - pyiron_atomistics =0.3.0 - pyparsing =3.0.9 -- scipy =1.11.1 +- scipy =1.11.2 - seaborn =0.12.2 - scikit-image =0.21.0 - randspg =0.0.1 From 3c022420d2c6accad6d1cb0797f83a66c397b410 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 25 Aug 2023 08:51:49 -0700 Subject: [PATCH 583/756] Refactor: rename --- pyiron_contrib/workflow/meta.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index c35a24705..4c15f05d1 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -48,7 +48,7 @@ def __many_to_list({", ".join([f"inp{i}=None" for i in range(length)])}): def for_loop( - node_class: type[Node], + loop_body_class: type[Node], length: int, iterate_on: str | tuple[str] | list[str], # TODO: @@ -107,7 +107,7 @@ def make_loop(macro): # Parallelize over body nodes for n in range(length): body_nodes.append( - macro.add(node_class(label=f"{node_class.__name__}_{n}")) + macro.add(loop_body_class(label=f"{loop_body_class.__name__}_{n}")) ) # Make input interface @@ -120,7 +120,7 @@ def make_loop(macro): interface = list_to_output(length)( parent=macro, label=label.upper(), - output_labels=[f"{node_class.__name__}__{inp.label}_{i}" for i in + output_labels=[f"{loop_body_class.__name__}__{inp.label}_{i}" for i in range(length)], l=[inp.default] * length ) @@ -146,13 +146,13 @@ def make_loop(macro): interface = input_to_list(length)( parent=macro, label=label.upper(), - output_labels=f"{node_class.__name__}__{label}" + output_labels=f"{loop_body_class.__name__}__{label}" ) # Connect each body node output to the output interface's respective input for body_node, inp in zip(body_nodes, interface.inputs): inp.connect(body_node.outputs[label]) macro.outputs_map[ - f"{interface.label}__{node_class.__name__}__{label}"] = interface.label + f"{interface.label}__{loop_body_class.__name__}__{label}"] = interface.label # TODO: Don't manually copy the output label construction return macro_node()(make_loop) From 4453450746ed9d3746e6a44555e61c02270df2c1 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 25 Aug 2023 09:19:51 -0700 Subject: [PATCH 584/756] Introduce an if node to convert conditions to bools --- .../workflow/node_library/standard.py | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/node_library/standard.py b/pyiron_contrib/workflow/node_library/standard.py index 64cce0122..a0d0c0e2a 100644 --- a/pyiron_contrib/workflow/node_library/standard.py +++ b/pyiron_contrib/workflow/node_library/standard.py @@ -1,11 +1,13 @@ from __future__ import annotations +from inspect import isclass from typing import Optional import numpy as np from matplotlib import pyplot as plt -from pyiron_contrib.workflow.function import single_value_node +from pyiron_contrib.workflow.channels import NotData, OutputSignal +from pyiron_contrib.workflow.function import SingleValue, single_value_node @single_value_node(output_labels="fig") @@ -20,7 +22,41 @@ def user_input(user_input): return user_input +class If(SingleValue): + """ + Has two extra signal channels: true and false. Evaluates the input as a boolean and + fires the corresponding output signal after running. + """ + + def __init__(self, **kwargs): + super().__init__( + self.if_, + output_labels="truth", + **kwargs + ) + self.signals.output.true = OutputSignal("true", self) + self.signals.output.false = OutputSignal("false", self) + + @staticmethod + def if_(condition): + if isclass(condition) and issubclass(condition, NotData): + raise TypeError(f"Logic 'If' node expected data but got NotData as input.") + return bool(condition) + + def process_run_result(self, function_output): + """ + Process the output as usual, then fire signals accordingly. + """ + super().process_run_result(function_output) + + if self.outputs.truth.value: + self.signals.output.true() + else: + self.signals.output.false() + + nodes = [ scatter, user_input, + If, ] From a44776af51436b528dadf0e1fb4fff40ebbf9c4c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 25 Aug 2023 11:25:56 -0700 Subject: [PATCH 585/756] Introduce a while node --- pyiron_contrib/workflow/meta.py | 95 +++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index 4c15f05d1..9c49e4e42 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -158,9 +158,104 @@ def make_loop(macro): return macro_node()(make_loop) +def while_loop( + loop_body_class: type[Node], +) -> type[Macro]: + """ + An _extremely rough_ first draft of a for-loop meta-node. + + Takes a node class and builds a macro that makes a cyclic signal connection between + that body node and an "if" node, i.e. when the body node finishes it runs the + if-node, and when the if-node finishes and evaluates `True` then it runs the body + node. + The if-node condition is exposed as input on the resulting macro with the label + "condition", but it is left to the user to connect... + - The condition to some output of another node, either an internal node of the body + node (if it's a macro) or any other node in the workflow + - The (sub)input of the body node to the (sub)output of the body node, so it + actually does something different at each iteration + + Args: + loop_body_class (type[pyiron_contrib.workflow.node.Node]): The class for the + body of the while-loop. + + Examples: + >>> import numpy as np + >>> np.random.seed(0) # Just for docstring tests, so the output is predictable + >>> + >>> from pyiron_contrib.workflow import Workflow + >>> + >>> # Build tools + >>> + >>> @Workflow.wrap_as.single_value_node() + >>> def random(length: int | None = None): + ... random = np.random.random(length) + ... return random + >>> + >>> @Workflow.wrap_as.single_value_node() + >>> def greater_than(x: float, threshold: float): + ... gt = x > threshold + ... symbol = ">" if gt else "<=" + ... print(f"{x:.3f} {symbol} {threshold}") + ... return gt + >>> + >>> RandomWhile = Workflow.create.meta.while_loop(random) + >>> + >>> # Define workflow + >>> + >>> wf = Workflow("random_until_small_enough") + >>> + >>> ## Wire together the while loop and its condition + >>> + >>> wf.gt = greater_than() + >>> wf.random_while = RandomWhile(condition=wf.gt) + >>> wf.gt.inputs.x = wf.random_while.Random + >>> + >>> wf.starting_nodes = [wf.random_while] + >>> + >>> ## Give convenient labels + >>> wf.inputs_map = {"gt__threshold": "threshold"} + >>> wf.outputs_map = {"random_while__Random__random": "capped_value"} + >>> # Set a threshold and run + >>> + >>> print(f"Finally {wf(threshold=0.1).capped_value:.3f}") + 0.549 > 0.1 + 0.715 > 0.1 + 0.603 > 0.1 + 0.545 > 0.1 + 0.424 > 0.1 + 0.646 > 0.1 + 0.438 > 0.1 + 0.892 > 0.1 + 0.964 > 0.1 + 0.383 > 0.1 + 0.792 > 0.1 + 0.529 > 0.1 + 0.568 > 0.1 + 0.926 > 0.1 + 0.071 <= 0.1 + Finally 0.071 + """ + def make_loop(macro): + body_node = macro.add(loop_body_class(label=loop_body_class.__name__)) + macro.create.standard.If(label="if_", run_on_updates=False) + + # Create a cyclic loop between body and if nodes, so that they will keep + # triggering themselves until the if evaluates false + body_node.signals.input.run = macro.if_.signals.output.true + macro.if_.signals.input.run = body_node.signals.output.ran + macro.starting_nodes = [body_node] + + # Just for convenience: + macro.inputs_map = {"if___condition": "condition"} + + return macro_node()(make_loop) + + meta_nodes = DotDict({ for_loop.__name__: for_loop, input_to_list.__name__: input_to_list, list_to_output.__name__: list_to_output, + while_loop.__name__: while_loop, }) From ca271c32495b26e821676d2e6672388347fd1a57 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 25 Aug 2023 11:28:25 -0700 Subject: [PATCH 586/756] Tidy integration test --- tests/integration/test_workflow.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py index 9b018c1bb..6809250fb 100644 --- a/tests/integration/test_workflow.py +++ b/tests/integration/test_workflow.py @@ -1,4 +1,3 @@ -import time import unittest import numpy as np @@ -8,12 +7,10 @@ from pyiron_contrib.workflow.workflow import Workflow -class TestNothing(unittest.TestCase): - def test_cyclic_graphs(self): +class TestTopology(unittest.TestCase): + def test_manually_constructed_cyclic_graph(self): """ Check that cyclic graphs run. - - TODO: Update once logical switches are included in the node library """ @Workflow.wrap_as.single_value_node() From 8446f8efdde847eb7b234cc8b51b515f4aff6d0d Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 25 Aug 2023 11:31:38 -0700 Subject: [PATCH 587/756] Add the while loop to integration tests --- tests/integration/test_workflow.py | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py index 6809250fb..322701bb2 100644 --- a/tests/integration/test_workflow.py +++ b/tests/integration/test_workflow.py @@ -75,3 +75,43 @@ def numpy_sqrt(value=0): self.assertAlmostEqual( np.sqrt(wf.rand.outputs.rand.value), wf.sqrt.outputs.sqrt.value, 6 ) + + def test_while_loop(self): + np.random.seed(0) + + # Build tools + + @Workflow.wrap_as.single_value_node() + def random(length: int | None = None): + random = np.random.random(length) + return random + + @Workflow.wrap_as.single_value_node() + def greater_than(x: float, threshold: float): + gt = x > threshold + symbol = ">" if gt else "<=" + # print(f"{x:.3f} {symbol} {threshold}") + return gt + + RandomWhile = Workflow.create.meta.while_loop(random) + + # Define workflow + + wf = Workflow("random_until_small_enough") + + ## Wire together the while loop and its condition + + wf.gt = greater_than() + wf.random_while = RandomWhile(condition=wf.gt) + wf.gt.inputs.x = wf.random_while.Random + + wf.starting_nodes = [wf.random_while] + + ## Give convenient labels + wf.inputs_map = {"gt__threshold": "threshold"} + wf.outputs_map = {"random_while__Random__random": "capped_value"} + + self.assertAlmostEqual( + wf(threshold=0.1).capped_value, + 0.07103605819788694, # For this reason we set the random seed + ) From 3c0b762d575f50b6871706cef990a7e8f7534fc9 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 25 Aug 2023 11:34:34 -0700 Subject: [PATCH 588/756] Add the for loop to integration tests --- tests/integration/test_workflow.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py index 322701bb2..078e3744b 100644 --- a/tests/integration/test_workflow.py +++ b/tests/integration/test_workflow.py @@ -76,6 +76,33 @@ def numpy_sqrt(value=0): np.sqrt(wf.rand.outputs.rand.value), wf.sqrt.outputs.sqrt.value, 6 ) + def test_for_loop(self): + n = 5 + + bulk_loop = Workflow.create.meta.for_loop( + Workflow.create.atomistics.Bulk, + n, + iterate_on=("a",), + )() + + out = bulk_loop( + name="Al", # Sent equally to each body node + A=np.linspace(3.9, 4.1, n).tolist(), # Distributed across body nodes + ) + + self.assertTrue( + np.allclose( + [struct.cell.volume for struct in out.STRUCTURE], + [ + 14.829749999999995, + 15.407468749999998, + 15.999999999999998, + 16.60753125, + 17.230249999999995 + ] + ) + ) + def test_while_loop(self): np.random.seed(0) From c6bda89223ac5382f4627a83c747d5b4fa96903f Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Fri, 25 Aug 2023 18:40:35 +0000 Subject: [PATCH 589/756] Format black --- pyiron_contrib/workflow/interfaces.py | 1 + pyiron_contrib/workflow/meta.py | 48 +++++++++++-------- .../workflow/node_library/standard.py | 6 +-- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/pyiron_contrib/workflow/interfaces.py b/pyiron_contrib/workflow/interfaces.py index 861ee41d3..3309d3497 100644 --- a/pyiron_contrib/workflow/interfaces.py +++ b/pyiron_contrib/workflow/interfaces.py @@ -81,6 +81,7 @@ def atomistics(self): def meta(self): if self._meta is None: from pyiron_contrib.workflow.meta import meta_nodes + self._meta = meta_nodes return self._meta diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index 9c49e4e42..b0ba18045 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -5,7 +5,10 @@ from __future__ import annotations from pyiron_contrib.workflow.function import ( - Function, SingleValue, function_node, single_value_node + Function, + SingleValue, + function_node, + single_value_node, ) from pyiron_contrib.workflow.macro import Macro, macro_node from pyiron_contrib.workflow.node import Node @@ -48,10 +51,10 @@ def __many_to_list({", ".join([f"inp{i}=None" for i in range(length)])}): def for_loop( - loop_body_class: type[Node], - length: int, - iterate_on: str | tuple[str] | list[str], - # TODO: + loop_body_class: type[Node], + length: int, + iterate_on: str | tuple[str] | list[str], + # TODO: ) -> type[Macro]: """ An _extremely rough_ first draft of a for-loop meta-node. @@ -120,9 +123,11 @@ def make_loop(macro): interface = list_to_output(length)( parent=macro, label=label.upper(), - output_labels=[f"{loop_body_class.__name__}__{inp.label}_{i}" for i in - range(length)], - l=[inp.default] * length + output_labels=[ + f"{loop_body_class.__name__}__{inp.label}_{i}" + for i in range(length) + ], + l=[inp.default] * length, ) # Connect each body node input to the input interface's respective output for body_node, out in zip(body_nodes, interface.outputs): @@ -132,9 +137,7 @@ def make_loop(macro): # Or distribute the same input to each node equally else: interface = macro.create.standard.UserInput( - label=label, - output_labels=label, - user_input=inp.default + label=label, output_labels=label, user_input=inp.default ) for body_node in body_nodes: body_node.inputs[label] = interface @@ -146,20 +149,21 @@ def make_loop(macro): interface = input_to_list(length)( parent=macro, label=label.upper(), - output_labels=f"{loop_body_class.__name__}__{label}" + output_labels=f"{loop_body_class.__name__}__{label}", ) # Connect each body node output to the output interface's respective input for body_node, inp in zip(body_nodes, interface.inputs): inp.connect(body_node.outputs[label]) macro.outputs_map[ - f"{interface.label}__{loop_body_class.__name__}__{label}"] = interface.label + f"{interface.label}__{loop_body_class.__name__}__{label}" + ] = interface.label # TODO: Don't manually copy the output label construction return macro_node()(make_loop) def while_loop( - loop_body_class: type[Node], + loop_body_class: type[Node], ) -> type[Macro]: """ An _extremely rough_ first draft of a for-loop meta-node. @@ -236,6 +240,7 @@ def while_loop( 0.071 <= 0.1 Finally 0.071 """ + def make_loop(macro): body_node = macro.add(loop_body_class(label=loop_body_class.__name__)) macro.create.standard.If(label="if_", run_on_updates=False) @@ -252,10 +257,11 @@ def make_loop(macro): return macro_node()(make_loop) -meta_nodes = DotDict({ - for_loop.__name__: for_loop, - input_to_list.__name__: input_to_list, - list_to_output.__name__: list_to_output, - while_loop.__name__: while_loop, -}) - +meta_nodes = DotDict( + { + for_loop.__name__: for_loop, + input_to_list.__name__: input_to_list, + list_to_output.__name__: list_to_output, + while_loop.__name__: while_loop, + } +) diff --git a/pyiron_contrib/workflow/node_library/standard.py b/pyiron_contrib/workflow/node_library/standard.py index a0d0c0e2a..5c9ac3125 100644 --- a/pyiron_contrib/workflow/node_library/standard.py +++ b/pyiron_contrib/workflow/node_library/standard.py @@ -29,11 +29,7 @@ class If(SingleValue): """ def __init__(self, **kwargs): - super().__init__( - self.if_, - output_labels="truth", - **kwargs - ) + super().__init__(self.if_, output_labels="truth", **kwargs) self.signals.output.true = OutputSignal("true", self) self.signals.output.false = OutputSignal("false", self) From 1d42e4d9aca108844cc25e775e1253b90dca135c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 30 Aug 2023 10:41:21 -0700 Subject: [PATCH 590/756] Update example notebook --- notebooks/workflow_example.ipynb | 655 ++++++++++++++++++++++++++++++- 1 file changed, 639 insertions(+), 16 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index c349e5178..7ce58fc25 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -11,7 +11,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "19642bc7474d485f853346bc831a1366", + "model_id": "c6007a8518bf4d769184af10a6b1f113", "version_major": 2, "version_minor": 0 }, @@ -19,6 +19,14 @@ }, "metadata": {}, "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n", + " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" + ] } ], "source": [ @@ -717,7 +725,7 @@ "outputs": [ { "data": { - "image/png": "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AACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAq8IKI3V1dcrOzpbH41FhYaGam5tP239wcFCbN29WVlaW3G63FixYoB07doRVMAAAiC9JTk9oaGhQZWWl6urqtHjxYj3xxBNavny5Ojo6dNFFF53ynJtuukkff/yxtm/frq997Wvq7e3V559/PuXiAQBA7HMZY4yTExYtWqSCggLV19ePteXm5mrlypWqra0d13///v36q7/6K73//vtKS0sLq8hgMCiv16tAIKDU1NSwngMAAETXZN+/HS3TDA0NqbW1VWVlZSHtZWVlamlpOeU5L730koqKivTggw/qwgsv1KWXXqp77rlHv//97yf8PYODgwoGgyEHAACIT46Wafr6+jQ8PKz09PSQ9vT0dPX09JzynPfff18HDhyQx+PRiy++qL6+Pv3d3/2djh8/PuF1I7W1taqpqXFSGgAAiFFhXcDqcrlCHhtjxrWNGhkZkcvl0u7du1VcXKxvfetbevjhh/X0009PODtSVVWlQCAwdnR3d4dTJgAAiAGOZkbmzp2rxMTEcbMgvb2942ZLRvl8Pl144YXyer1jbbm5uTLG6MMPP9Qll1wy7hy32y232+2kNAAAEKMczYwkJyersLBQTU1NIe1NTU0qLS095TmLFy/WRx99pE8//XSs7ciRI0pISND8+fPDKBkAAMQTx8s0Gzdu1JNPPqkdO3bo7bff1t13362uri5VVFRIOrnEcvPNN4/1X7VqlebMmaNbb71VHR0deu2113Tvvffqb/7mb5SSkhK5kQAAgJjkeJ+R8vJy9ff3a8uWLfL7/crPz1djY6OysrIkSX6/X11dXWP9v/KVr6ipqUnf//73VVRUpDlz5uimm27SD37wg8iNAgAAxCzH+4zYwD4jAADEnmnZZwQAACDSCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKuSbBcAAEA4hkeMDnYeV+/ACc2b7VFxdpoSE1y2y0IYCCMAgJizv92vmn0d8gdOjLX5vB5Vr8jTsnyfxcoQDpZpAAAxZX+7Xxt2HQoJIpLUEzihDbsO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", + "image/png": "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XkQ57fUnP/mJueyyy4LGHnvsMZOWljZmNUaK014LCgrMfffdFzR2zz33mKKiojGrMdJGEk7+6Z/+ycyfPz9o7I477jDXXnuto8+aFKd1zp49q8bGRhUXFweNFxcXq76+fkTv0d/fr56eHiUnJ49FiRETiV6bmppUX1+vG264YSxKjJhwe33qqaf0/vvvq7y8fKxLjJjRfK/XXHONfD6fFi9erFdeeWUsy4yIcHp96aWXlJeXp4ceekiXXHKJrrjiCt13333605/+FI2SwxaJf1937NihG2+8URkZGWNRYsSE02thYaE+/PBDHThwQMYYffTRR3rhhRf0zW9+Mxolhy2cXnt7e+XxeILGEhIS1NDQoE8//XTMao22//qv/wr5e1myZIkOHz7sqM9JEU66urrU19enlJSUoPGUlBR1dHSM6D1++tOf6v/+7/908803j0WJETOaXtPS0hQfH6+8vDzdeeedWrt27ViWOmrh9Pree+9p48aN2r17t2JjJ8RzLSWF16vP59P27du1d+9evfjii5o3b54WL16s119/PRolhy2cXo8dO6ZDhw6publZ+/bt09atW/XCCy/ozjvvjEbJYRvtf5v8fr9efvll6/9dlcLrtbCwULt371ZJSYni4uKUmpqqGTNm6Gc/+1k0Sg5bOL0uWbJETz75pBobG2WM0eHDh1VTU6NPP/1UXV1d0Sg7Kjo6Oob8e/nss88c9Tlx/us9Ai6XK+i1MSZkbCjPPfecHnjgAf3Hf/yHZs+ePVblRVQ4vdbV1emTTz7Rm2++qY0bN+orX/mKbrnllrEsMyJG2mtfX59uvfVWVVRU6IorrohWeRHl5HudN2+e5s2bN/i6oKBA7e3tevjhh/W1r31tTOuMBCe99vf3y+Vyaffu3YNPON2yZYu++93vatu2bUpISBjzekcj3P82Pf3005oxY4ZWrFgxRpVFnpNeW1padPfdd+tHP/qRlixZIr/fr/vvv1+lpaXasWNHNModFSe9/vCHP1RHR4euvfZaGWOUkpKi1atX66GHHpLb7Y5GuVEz1N/LUOMXMimOnMyaNUtutzsksXZ2doYkuM/bs2eP1qxZo3/7t3/TjTfeOJZlRsRoes3MzNRXv/pVff/739e9996rBx54YAwrHT2nvfb09Ojw4cO66667FBsbq9jYWG3evFn/8z//o9jYWP3mN7+JVumOjeZ7Pd+1116r9957L9LlRVQ4vfp8Pl1yySVBj17PysqSMUYffvjhmNY7GqP5Xo0xqqmp0apVqxQXFzeWZUZEOL1WVlaqqKhI999/v6688kotWbJEVVVVqqmpkd/vj0bZYQmn14SEBNXU1Oj06dP64IMP1NbWpjlz5igxMVGzZs2KRtlRkZqaOuTfS2xsrGbOnDni95kU4SQuLk65ubmqra0NGq+trVVhYeGw+z333HNavXq1nn32WevPcQ4It9fPM8aot7c30uVFlNNek5KS9Lvf/U5vvfXW4FZaWqp58+bprbfe0qJFi6JVumOR+l6bmprk8/kiXV5EhdNrUVGRTpw4oU8++WRw7OjRo4qJiVFaWtqY1jsao/leX3vtNf3hD3/QmjVrxrLEiAmn19OnTysmJvhnaOAogrH4sW+j+V6nTZumtLQ0ud1uPf/88/rWt74V8ncwkRUUFIT8vfz6179WXl6epk2bNvI3cnT5rMUGbuvasWOHaWlpMevXrzfTp08fvMJ948aNZtWqVYPzn332WRMbG2u2bdsWdNvexx9/PF4tjJjTXn/+85+bl156yRw9etQcPXrU1NTUmKSkJLNp06bxamHEnPb6eRPpbh2nvT7yyCNm37595ujRo6a5udls3LjRSDJ79+4drxZGzGmvPT09Ji0tzXz3u981v//9781rr71mLr/8crN27drxamHEwv1neOXKlWbRokXRLndUnPb61FNPmdjYWFNVVWXef/99c+jQIZOXl2fy8/PHq4URc9rru+++a5555hlz9OhR89vf/taUlJSY5ORk09raOk4djExPT49pamoyTU1NRpLZsmWLaWpqGrxl+vN9DtxKfO+995qWlhazY8eOqX0rsTHGbNu2zWRkZJi4uDizcOFC89prrw3+2fe+9z1zww03DL6+4YYbjKSQ7Xvf+170Cw+Dk14fe+wxs2DBAnPRRReZpKQkc80115iqqirT19c3DpU756TXz5tI4cQYZ70++OCDZu7cucbj8Zgvf/nL5rrrrjO/+tWvxqHq8Dj9Xt9++21z4403moSEBJOWlmY2bNhgTp8+HeWqw+O0148//tgkJCSY7du3R7nS0XPa62OPPWays7NNQkKC8fl85m//9m/Nhx9+GOWqw+Ok15aWFnP11VebhIQEk5SUZL797W+bd955ZxyqdmZgyYLhfiuH+k5fffVVc80115i4uDgzZ84cU11d7fhzXcZYfOwMAABMOZPnRBcAAJgUCCcAAMAqhBMAAGAVwgkAALAK4QQAAFiFcAIAAKxCOAEAAFYhnAAAAKsQTgAAgFUIJwAAwCqEEwAAYBXCCQAAsMr/B4sK/w0pV/iMAAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -806,7 +814,7 @@ "outputs": [ { "data": { - "image/png": "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368vfelLGhgY0OXLl6+7eMApJxcctopOLwOj0X8vYukHYOYch5G9e/dq8+bN2rJliyRp3759evvtt9Xc3KzGxsaw/m1tbTp69KjOnz+vvLw8SdKtt956fVUDMeKCg2gWzXdfu5ODfgBmztE0zaVLl9TV1aWqqqqQ9qqqKp04cSLiMW+99ZbKysr0ox/9SEuWLNHtt9+uJ598Up988knUzxkfH9fIyEjIC5gNXHAQTXlRnrwet6LdT+XS5CLn8qK8eJYFpAVHYWRwcFDBYFD5+fkh7fn5+erv7494zPnz5/XOO++op6dHb775pvbt26fXX39d3/3ud6N+TmNjozwez/SrsLDQSZlAVFxwEE1mhkv11cWSFPb7MfW+vrqY27+BORDTAlaXK/QfozEmrG3KxMSEXC6XDh06pPLyct13333au3evXn755aijI7t27VIgEJh+XbhwIZYygTBccHA160u8at64SgWe0JGxAo+b23qBOeRozcjChQuVmZkZNgoyMDAQNloyxev1asmSJfJ4PNNtK1eulDFGFy9e1G233RZ2TE5OjnJycpyUBszY1AXn8/uMFLDPCDT5+7GuuIAN8YA4chRGsrOzVVpaqvb2dv3lX/7ldHt7e7u+9a1vRTxmzZo1eu211/Thhx/qC1/4giTp3XffVUZGhpYuXXodpQOx44KDq8nMcHE3FRBHjqdp6urq9OKLL+rAgQM6e/asduzYob6+PtXW1kqanGLZtGnTdP+HHnpICxYs0GOPPSafz6djx47pqaee0t/8zd/ohhtumL2fBHBo6oLzrbuWqGLFAoIIAFji+NbempoaDQ0Nac+ePfL7/SopKVFra6uWLVsmSfL7/err65vu/4UvfEHt7e36u7/7O5WVlWnBggV68MEH9cwzz8zeTwEAAJKWyxiT8FtNjoyMyOPxKBAIKDc313Y5AABgBmZ6/ebZNAAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACr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5aUcp+f7n//5n83y5ctD2p599lmzdOnSOasxFc0kjMT7WpnU0zSXLl1SV1eXqqqqQtqrqqp04sSJiMecPHkyrP+9996rU6dO6dNPP52zWlNBLOf78yYmJjQ6Oqq8vLy5KDGlxHq+X3rpJZ07d0719fVzXWJKieV8v/XWWyorK9OPfvQjLVmyRLfffruefPJJffLJJ/EoOanFcr4rKyt18eJFtba2yhijDz74QK+//rruv//+eJScVuJ9rUyKp/ZGMzg4qGAwqPz8/JD2/Px89ff3Rzymv78/Yv/Lly9rcHBQXq93zupNdrGc78/78Y9/rI8++kgPPvjgXJSYUmI533/4wx+0c+dOdXR0KCsrqf95x10s5/v8+fN655135Ha79eabb2pwcFDf+c53NDw8zLqRa4jlfFdWVurQoUOqqanR2NiYLl++rG9+85v66U9/Go+S00q8r5VJPTIyxeVyhbw3xoS1Xat/pHZE5vR8T3n11Vf1gx/8QC0tLVq0aNFclZdyZnq+g8GgHnroITU0NOj222+PV3kpx8nv98TEhFwulw4dOqTy8nLdd9992rt3r15++WVGR2bIyfn2+Xzatm2bnn76aXV1damtrU29vb2qra2NR6lpJ57XyqT+r9PChQuVmZkZlqIHBgbCEt2UgoKCiP2zsrK0YMGCOas1FcRyvqe0tLRo8+bNeu2113TPPffMZZkpw+n5Hh0d1alTp9Td3a3vfe97kiYvlsYYZWVl6ciRI7r77rvjUnsyiuX32+v1asmSJfJ4PNNtK1eulDFGFy9e1G233TanNSezWM53Y2Oj1qxZo6eeekqS9NWvflU33XST1q5dq2eeeYaR7VkU72tlUo+MZGdnq7S0VO3t7SHt7e3tqqysjHhMRUVFWP8jR46orKxM8+bNm7NaU0Es51uaHBF59NFH9corrzC364DT852bm6vf/e53OnPmzPSrtrZWX/7yl3XmzBmtXr06XqUnpVh+v9esWaM//vGP+vDDD6fb3n33XWVkZGjp0qVzWm+yi+V8f/zxx8rICL1sZWZmSvqf/7VjdsT9Wjkny2LjaOrWsP379xufz2e2b99ubrrpJvPf//3fxhhjdu7caR5++OHp/lO3K+3YscP4fD6zf/9+bu11wOn5fuWVV0xWVpZ57rnnjN/vn3796U9/svUjJBWn5/vzuJvGGafne3R01CxdutT81V/9lfn9739vjh49am677TazZcsWWz9CUnF6vl966SWTlZVlmpqazLlz58w777xjysrKTHl5ua0fIWmMjo6a7u5u093dbSSZvXv3mu7u7unbqG1fK5M+jBhjzHPPPWeWLVtmsrOzzapVq8zRo0en/+yRRx4xX//610P6//u//7v5sz/7M5OdnW1uvfVW09zcHOeKk5uT8/31r3/dSAp7PfLII/EvPEk5/f2+EmHEOafn++zZs+aee+4xN9xwg1m6dKmpq6szH3/8cZyrTl5Oz/ezzz5riouLzQ033GC8Xq/567/+a3Px4sU4V518/u3f/u2q38W2r5UuYxjbAgAA9iT1mhEAAJD8CCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACw6v8DGQK5uK9x00UAAAAASUVORK5CYII=", + "image/png": "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UVB7UVGRhoaGwm7zhz/8QXv37lV3d7eysiJ7u+bmZjU1NUVTGhYhjjqAhcHVHvFR4cqX02HXkG8y7AiuTVKxY/oyX1xeTCew2mzB105blhXSJkl+v1933nmnmpqadP3110f8+vv27ZPP55t9XLhwIZYykeI46gCuHuddxU9mhk2NtW5J08FjrpnnjbVu1huJQFRhZPny5crMzAwZBRkeHg4ZLZGkiYkJnTp1St/+9reVlZWlrKwsHThwQG+++aaysrL02muvhX2fnJwc5eXlBT2QfmaOOub7M7Zp+uiOow4gPM67ir+aEqdat5Wq2BF8UFTssKt1WykjTxGKapomOztbZWVl6urq0te+9rXZ9q6uLn31q18N6Z+Xl6ff/e53QW0tLS167bXX9MILL8jlcsVYNtLBzFHHziO9sklB/1A56gCuLJrzrirXFSSusEWmpsSpze5iVmC9ClGvwNrQ0KC77rpL5eXlqqys1E9/+lMNDg6qvr5e0vQUy7vvvquf/exnysjIUElJSdD2hYWFstvtIe1AODNHHZ+e7y5mvhu4Is67SpzMDBuB7ipEHUbq6uo0OjqqAwcOyOv1qqSkRB0dHVqzZo0kyev1XnHNESAaHHUAseG8K6QKm2VZST9ZOD4+LofDIZ/Px/kjABAhf8DSTY+9dsWrPY4/fDPhHnER6fc396YBgEWKqz2QKggjALCIcbUHUkHU54wAAFIL510h2RFGACANcLUHkhnTNAAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjMoyXYAp/oClnoExDU9MqjDXrgpXvjIzbKbLAgAg7aRlGOns96qp3SOvb3K2zemwq7HWrZoSp8HKAABIP2k3TdPZ79XOI71BQUSShnyT2nmkV539XkOVAQCQntIqjPgDlpraPbLC/GymrandI38gXA8AABAPaRVG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", "text/plain": [ "
" ] @@ -1857,7 +1865,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 34, @@ -1898,27 +1906,27 @@ "clusterwith_prebuilt\n", "\n", "with_prebuilt: Workflow\n", - "\n", - "clusterwith_prebuiltInputs\n", + "\n", + "clusterwith_prebuiltOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Outputs\n", "\n", - "\n", - "clusterwith_prebuiltOutputs\n", + "\n", + "clusterwith_prebuiltInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Inputs\n", "\n", "\n", "\n", @@ -2087,7 +2095,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 35, @@ -3393,7 +3401,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 41, @@ -3450,6 +3458,621 @@ "out = wf(element=\"Mg\", phase1=\"fcc\", phase2=\"hcp\", lattice_guess1=3, lattice_guess2=3)\n", "print(f\"{wf.inputs.element.value}: E({wf.inputs.phase2.value}) - E({wf.inputs.phase1.value}) = {out.compare__de:.2f} eV/atom\")" ] + }, + { + "cell_type": "markdown", + "id": "f447531e-3e8c-4c7e-a579-5f9c56b75a5b", + "metadata": {}, + "source": [ + "# Here be dragons\n", + "\n", + "While everything in the workflows sub-module is under development, the following complex features are _even more likely_ to see substantial modifications to their interface and behaviour. Nonetheless, they're fun so let's look at them." + ] + }, + { + "cell_type": "markdown", + "id": "1f012460-19af-45f7-98aa-a0ad5b8e6faa", + "metadata": {}, + "source": [ + "## Meta-nodes and flow control\n", + "\n", + "A meta-node is a function that produces a node _class_ instedad of a node _instance_.\n", + "Right now, these are used to produce parameterized flow-control nodes, which take an node class as input and return a new macro class that builds some graph using the passed node class, e.g. for- and while-loops.\n", + "\n", + "### For-loops\n", + "\n", + "One meta node is a for-loop builder, which creates a macro with $n$ internal copies of the \"loop body\" node, and a new IO interface.\n", + "The new input allows you to specify which input channels are being looped over -- such that the macro input for this channel is interpreted as list-like and distributed to all the copies of the nodes separately --, and which is _not_ being looped over -- and thus interpreted as the loop body node would normally interpret the input and passed to all copies equally.\n", + "All of the loop body outputs are then collected as a list of length $n$.\n", + "\n", + "We follow a convention that inputs and outputs being looped over are indicated by their channel labels being ALL CAPS.\n", + "\n", + "In the example below, we loop over the bulk structure node to create structures with different lattice constants:" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "0b373764-b389-4c24-8086-f3d33a4f7fd7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[14.829749999999995,\n", + " 15.407468749999998,\n", + " 15.999999999999998,\n", + " 16.60753125,\n", + " 17.230249999999995]" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "n = 5\n", + "\n", + "# Define the for-loop macro class\n", + "BulkStructureLoop = Workflow.create.meta.for_loop(\n", + " Workflow.create.atomistics.Bulk,\n", + " n,\n", + " iterate_on = \"a\", # We could also pass more than one channel label as a tuple\n", + ")\n", + "\n", + "# Instantiate the macro as a node\n", + "bulk_loop = BulkStructureLoop()\n", + "\n", + "# Invoke the node with input to get a result\n", + "out = bulk_loop(\n", + " name=\"Al\", # Sent equally to each body node\n", + " A=np.linspace(3.9, 4.1, n).tolist(), # Distributed across body nodes\n", + ")\n", + "\n", + "[struct.cell.volume for struct in out.STRUCTURE] \n", + "# output is a list collected from copies of the body node, as indicated by CAPS label" + ] + }, + { + "cell_type": "markdown", + "id": "4e7ed210-dbc2-4afa-825e-b91168baff25", + "metadata": {}, + "source": [ + "## While-loops\n", + "\n", + "We can also create a while-loop, which also takes a body node, but instead of creating copies of it, the body node gets re-run until a specified condition `Output` evaluates to `False`.\n", + "\n", + "In the example below, we have a node that adds two inputs together, and keep looping the result back into itself until the sum is greater than or equal to ten.\n", + "\n", + "Note that initializing the `a` input to a numeric value when we call the workflow does not destroy the connection made between the body node input and output -- so the first run of the body node uses the initial value passed, but then it updates its own input for subsequent calls!" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "37cbdd70-5c98-4ca0-83f9-cbfeff3a09db", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_while\n", + "\n", + "do_while: Workflow\n", + "\n", + "clusterdo_whileInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterdo_whileOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "clusterdo_whilelt10\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lt10: LessThanTen\n", + "\n", + "\n", + "clusterdo_whilelt10Inputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterdo_whilelt10Outputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "clusterdo_whileadd_while\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "add_while: MakeLoop\n", + "\n", + "\n", + "clusterdo_whileadd_whileInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterdo_whileadd_whileOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "clusterdo_whileAddadd_while\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Add: Add\n", + "\n", + "\n", + "clusterdo_whileAddadd_whileInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterdo_whileAddadd_whileOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "clusterdo_whileif_add_while\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "if_: If\n", + "\n", + "\n", + "clusterdo_whileif_add_whileInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterdo_whileif_add_whileOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "\n", + "clusterdo_whileInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterdo_whileOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileInputsa\n", + "\n", + "a\n", + "\n", + "\n", + "\n", + "clusterdo_whileadd_whileInputsa\n", + "\n", + "a\n", + "\n", + "\n", + "\n", + "clusterdo_whileInputsa->clusterdo_whileadd_whileInputsa\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileInputsb\n", + "\n", + "b\n", + "\n", + "\n", + "\n", + "clusterdo_whileadd_whileInputsb\n", + "\n", + "b\n", + "\n", + "\n", + "\n", + "clusterdo_whileInputsb->clusterdo_whileadd_whileInputsb\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileOutputsa + b\n", + "\n", + "a + b\n", + "\n", + "\n", + "\n", + "clusterdo_whileOutputstruth\n", + "\n", + "truth\n", + "\n", + "\n", + "\n", + "clusterdo_whilelt10Inputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterdo_whilelt10Outputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whilelt10Inputsvalue\n", + "\n", + "value\n", + "\n", + "\n", + "\n", + "clusterdo_whilelt10Outputsvalue < 10\n", + "\n", + "value < 10\n", + "\n", + "\n", + "\n", + "clusterdo_whileadd_whileInputscondition\n", + "\n", + "condition\n", + "\n", + "\n", + "\n", + "clusterdo_whilelt10Outputsvalue < 10->clusterdo_whileadd_whileInputscondition\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileadd_whileInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterdo_whileadd_whileOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileAddadd_whileInputsa\n", + "\n", + "a\n", + "\n", + "\n", + "\n", + "clusterdo_whileadd_whileInputsa->clusterdo_whileAddadd_whileInputsa\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileAddadd_whileInputsb\n", + "\n", + "b\n", + "\n", + "\n", + "\n", + "clusterdo_whileadd_whileInputsb->clusterdo_whileAddadd_whileInputsb\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileif_add_whileInputscondition\n", + "\n", + "condition\n", + "\n", + "\n", + "\n", + "clusterdo_whileadd_whileInputscondition->clusterdo_whileif_add_whileInputscondition\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileadd_whileOutputsa + b\n", + "\n", + "a + b\n", + "\n", + "\n", + "\n", + "clusterdo_whileadd_whileOutputsa + b->clusterdo_whileOutputsa + b\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileadd_whileOutputsa + b->clusterdo_whilelt10Inputsvalue\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileadd_whileOutputsa + b->clusterdo_whileadd_whileInputsa\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileadd_whileOutputstruth\n", + "\n", + "truth\n", + "\n", + "\n", + "\n", + "clusterdo_whileadd_whileOutputstruth->clusterdo_whileOutputstruth\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileAddadd_whileInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterdo_whileAddadd_whileOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileif_add_whileInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterdo_whileAddadd_whileOutputsran->clusterdo_whileif_add_whileInputsrun\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileAddadd_whileOutputsa + b\n", + "\n", + "a + b\n", + "\n", + "\n", + "\n", + "clusterdo_whileAddadd_whileOutputsa + b->clusterdo_whileadd_whileOutputsa + b\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileAddadd_whileOutputsa + b->clusterdo_whileAddadd_whileInputsa\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileif_add_whileOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileif_add_whileOutputstrue\n", + "\n", + "true\n", + "\n", + "\n", + "\n", + "clusterdo_whileif_add_whileOutputstrue->clusterdo_whileAddadd_whileInputsrun\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileif_add_whileOutputsfalse\n", + "\n", + "false\n", + "\n", + "\n", + "\n", + "clusterdo_whileif_add_whileOutputstruth\n", + "\n", + "truth\n", + "\n", + "\n", + "\n", + "clusterdo_whileif_add_whileOutputstruth->clusterdo_whileadd_whileOutputstruth\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "@Workflow.wrap_as.single_value_node(run_on_updates=False)\n", + "def add(a, b):\n", + " print(f\"Adding {a} + {b}\")\n", + " return a + b\n", + "\n", + "@Workflow.wrap_as.single_value_node()\n", + "def less_than_ten(value):\n", + " return value < 10\n", + "\n", + "AddWhile = Workflow.create.meta.while_loop(add)\n", + "\n", + "wf = Workflow(\"do_while\")\n", + "wf.lt10 = less_than_ten()\n", + "wf.add_while = AddWhile(condition=wf.lt10)\n", + "\n", + "wf.lt10.inputs.value = wf.add_while.Add\n", + "wf.add_while.Add.inputs.a = wf.add_while.Add\n", + "\n", + "wf.starting_nodes = [wf.add_while]\n", + "wf.inputs_map = {\n", + " \"add_while__Add__a\": \"a\", \n", + " \"add_while__Add__b\": \"b\"\n", + "}\n", + "wf.outputs_map = {\"add_while__Add__a + b\": \"total\"}\n", + "\n", + "wf.draw(depth=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "2dfb967b-41ac-4463-b606-3e315e617f2a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Adding 1 + 2\n", + "Adding 3 + 2\n", + "Adding 5 + 2\n", + "Adding 7 + 2\n", + "Adding 9 + 2\n", + "Finally 11\n" + ] + } + ], + "source": [ + "response = wf(a=1, b=2)\n", + "print(\"Finally\", response.total)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e87f858-b327-4f6b-9237-c8a557f29aeb", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From d7772f29e8184a08a51e2e5f8a7938405251282e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 30 Aug 2023 10:45:45 -0700 Subject: [PATCH 591/756] Expand docstring examples --- pyiron_contrib/workflow/meta.py | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index 9c49e4e42..3765b9d03 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -235,6 +235,44 @@ def while_loop( 0.926 > 0.1 0.071 <= 0.1 Finally 0.071 + + We can also loop data _internally_ in the body node. + In such cases, we can still _initialize_ the data to some other value, because + this has no impact on the connections -- so for the first run of the body node + we wind up using to initial value, but then the body node pushes elements of its + own output back to its own input for future runs. + E.g.) + >>> @Workflow.wrap_as.single_value_node(run_on_updates=False) + >>> def add(a, b): + ... print(f"Adding {a} + {b}") + ... return a + b + >>> + >>> @Workflow.wrap_as.single_value_node() + >>> def less_than_ten(value): + ... return value < 10 + >>> + >>> AddWhile = Workflow.create.meta.while_loop(add) + >>> + >>> wf = Workflow("do_while") + >>> wf.lt10 = less_than_ten() + >>> wf.add_while = AddWhile(condition=wf.lt10) + >>> + >>> wf.lt10.inputs.value = wf.add_while.Add + >>> wf.add_while.Add.inputs.a = wf.add_while.Add + >>> + >>> wf.starting_nodes = [wf.add_while] + >>> wf.inputs_map = { + ... "add_while__Add__a": "a", + ... "add_while__Add__b": "b" + ... } + >>> wf.outputs_map = {"add_while__Add__a + b": "total"} + >>> response = wf(a=1, b=2) + >>> print(response.total) + 11 + + Note that we needed to specify a starting node because in this case our + graph is cyclic and _all_ our nodes have connected input! We obviously cannot + automatically detect the "upstream-most" node in a circle! """ def make_loop(macro): body_node = macro.add(loop_body_class(label=loop_body_class.__name__)) From 03ec8f49cfba59a2640db353f02bcb1a50e877b9 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 30 Aug 2023 10:48:38 -0700 Subject: [PATCH 592/756] Expand integration tests --- tests/integration/test_workflow.py | 85 ++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 27 deletions(-) diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py index 078e3744b..92e2e5e1b 100644 --- a/tests/integration/test_workflow.py +++ b/tests/integration/test_workflow.py @@ -104,41 +104,72 @@ def test_for_loop(self): ) def test_while_loop(self): - np.random.seed(0) + with self.subTest("Random") + np.random.seed(0) - # Build tools + # Build tools - @Workflow.wrap_as.single_value_node() - def random(length: int | None = None): - random = np.random.random(length) - return random + @Workflow.wrap_as.single_value_node() + def random(length: int | None = None): + random = np.random.random(length) + return random - @Workflow.wrap_as.single_value_node() - def greater_than(x: float, threshold: float): - gt = x > threshold - symbol = ">" if gt else "<=" - # print(f"{x:.3f} {symbol} {threshold}") - return gt + @Workflow.wrap_as.single_value_node() + def greater_than(x: float, threshold: float): + gt = x > threshold + symbol = ">" if gt else "<=" + # print(f"{x:.3f} {symbol} {threshold}") + return gt - RandomWhile = Workflow.create.meta.while_loop(random) + RandomWhile = Workflow.create.meta.while_loop(random) - # Define workflow + # Define workflow - wf = Workflow("random_until_small_enough") + wf = Workflow("random_until_small_enough") - ## Wire together the while loop and its condition + ## Wire together the while loop and its condition - wf.gt = greater_than() - wf.random_while = RandomWhile(condition=wf.gt) - wf.gt.inputs.x = wf.random_while.Random + wf.gt = greater_than() + wf.random_while = RandomWhile(condition=wf.gt) + wf.gt.inputs.x = wf.random_while.Random - wf.starting_nodes = [wf.random_while] + wf.starting_nodes = [wf.random_while] - ## Give convenient labels - wf.inputs_map = {"gt__threshold": "threshold"} - wf.outputs_map = {"random_while__Random__random": "capped_value"} + ## Give convenient labels + wf.inputs_map = {"gt__threshold": "threshold"} + wf.outputs_map = {"random_while__Random__random": "capped_value"} - self.assertAlmostEqual( - wf(threshold=0.1).capped_value, - 0.07103605819788694, # For this reason we set the random seed - ) + self.assertAlmostEqual( + wf(threshold=0.1).capped_value, + 0.07103605819788694, # For this reason we set the random seed + ) + + with self.subTest("Self-data-loop"): + from pyiron_contrib.workflow import Workflow + + @Workflow.wrap_as.single_value_node(run_on_updates=False) + def add(a, b): + return a + b + + @Workflow.wrap_as.single_value_node() + def less_than_ten(value): + return value < 10 + + AddWhile = Workflow.create.meta.while_loop(add) + + wf = Workflow("do_while") + wf.lt10 = less_than_ten() + wf.add_while = AddWhile(condition=wf.lt10) + + wf.lt10.inputs.value = wf.add_while.Add + wf.add_while.Add.inputs.a = wf.add_while.Add + + wf.starting_nodes = [wf.add_while] + wf.inputs_map = { + "add_while__Add__a": "a", + "add_while__Add__b": "b" + } + wf.outputs_map = {"add_while__Add__a + b": "total"} + + out = wf(a=1, b=2) + self.assertEqual(out.total, 11) From c6f865e781d5d2ea3840a9a6b565ae163ee23bd9 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 30 Aug 2023 10:54:01 -0700 Subject: [PATCH 593/756] :bug: fix syntax error --- tests/integration/test_workflow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py index 92e2e5e1b..16962a4c5 100644 --- a/tests/integration/test_workflow.py +++ b/tests/integration/test_workflow.py @@ -104,7 +104,7 @@ def test_for_loop(self): ) def test_while_loop(self): - with self.subTest("Random") + with self.subTest("Random"): np.random.seed(0) # Build tools From 0784c739881f8bc2be05b6244252a6fdc951598c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 30 Aug 2023 11:06:48 -0700 Subject: [PATCH 594/756] :bug: fix weird import/scope error I'm not sure how this was causing trouble, but the tests pass locally now... --- tests/integration/test_workflow.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py index 16962a4c5..064325046 100644 --- a/tests/integration/test_workflow.py +++ b/tests/integration/test_workflow.py @@ -107,8 +107,6 @@ def test_while_loop(self): with self.subTest("Random"): np.random.seed(0) - # Build tools - @Workflow.wrap_as.single_value_node() def random(length: int | None = None): random = np.random.random(length) @@ -145,7 +143,6 @@ def greater_than(x: float, threshold: float): ) with self.subTest("Self-data-loop"): - from pyiron_contrib.workflow import Workflow @Workflow.wrap_as.single_value_node(run_on_updates=False) def add(a, b): From cc94fca99678c62597ab5bf6945be8f2f789f86c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 6 Sep 2023 13:20:39 -0700 Subject: [PATCH 595/756] Implement ">" syntactic sugar for run/ran connections per @JNmpi's idea --- pyiron_contrib/workflow/channels.py | 6 ++++++ pyiron_contrib/workflow/node.py | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index 71a2ebb98..4add9a9be 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -461,6 +461,9 @@ def _is_IO_pair(self, other) -> bool: other, self.__class__ ) + def connect_output_signal(self, signal: OutputSignal): + self.connect(signal) + class InputSignal(SignalChannel): """ @@ -512,3 +515,6 @@ def __str__(self): f"{self.label} activates " f"{[f'{c.node.label}.{c.label}' for c in self.connections]}" ) + + def __gt__(self, other: InputSignal | Node): + other.connect_output_signal(self) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index e1e4b0267..3165757e0 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -358,3 +358,12 @@ def __str__(self): f"{str(self.outputs)}\n" f"{str(self.signals)}" ) + + def connect_output_signal(self, signal: OutputSignal): + self.signals.input.run.connect(signal) + + def __gt__(self, other: InputSignal | Node): + """ + Allows users to connect run and ran signals like: `first_node > second_node`. + """ + other.connect_output_signal(self.signals.output.ran) From 42e21a9a1a519ee313571de8c628c30ab8dd25aa Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 6 Sep 2023 13:20:44 -0700 Subject: [PATCH 596/756] Add tests --- tests/unit/workflow/test_channels.py | 5 +++++ tests/unit/workflow/test_function.py | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/tests/unit/workflow/test_channels.py b/tests/unit/workflow/test_channels.py index 48bbd0b3d..a366fd768 100644 --- a/tests/unit/workflow/test_channels.py +++ b/tests/unit/workflow/test_channels.py @@ -170,6 +170,11 @@ def test_connections(self): with self.assertRaises(TypeError): self.inp.connect(bad) + with self.subTest("Test syntactic sugar"): + self.out.disconnect_all() + self.out > self.inp + self.assertIn(self.out, self.inp.connections) + def test_calls(self): self.out.connect(self.inp) self.out() diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 67afdb5d9..acbc6dd55 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -204,6 +204,31 @@ def times_two(y): msg="Running the upstream node should trigger a run here" ) + with self.subTest("Test syntactic sugar"): + t2.signals.input.run.disconnect_all() + l > t2 + self.assertIn( + l.signals.output.ran, + t2.signals.input.run.connections, + msg="> should be equivalent to run/ran connection" + ) + + t2.signals.input.run.disconnect_all() + l > t2.signals.input.run + self.assertIn( + l.signals.output.ran, + t2.signals.input.run.connections, + msg="> should allow us to mix and match nodes and signal channels" + ) + + t2.signals.input.run.disconnect_all() + l.signals.output.ran > t2 + self.assertIn( + l.signals.output.ran, + t2.signals.input.run.connections, + msg="Mixing and matching should work both directions" + ) + def test_statuses(self): n = Function(plus_one, run_on_updates=False) self.assertTrue(n.ready) From 700e21b67aac1dd385999308d5f6e0d45d610aa2 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 6 Sep 2023 13:24:31 -0700 Subject: [PATCH 597/756] Update docstrings --- pyiron_contrib/workflow/channels.py | 4 ++++ pyiron_contrib/workflow/node.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index 4add9a9be..eb10f192a 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -419,6 +419,10 @@ class SignalChannel(Channel, ABC): Output channels can be called to trigger the callback functions of all input channels to which they are connected. + + Signal channels support `>` as syntactic sugar for their connections, i.e. + `some_output > some_input` is equivalent to `some_input.connect(some_output)`. + (This is also interoperable with `Node` objects, cf. the `Node` docs.) """ @abstractmethod diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 3165757e0..7aceee756 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -48,6 +48,11 @@ 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. + These signal connections can be made manually by reference to the node signals + channel, or with the `>` symbol to indicate a flow of execution. This syntactic + sugar can be mixed between actual signal channels (output signal > input signal), + or nodes, but when refering to nodes it is always a shortcut to the `run`/`ran` + channels. 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 From e03b0c18167944f197c40d6bc04f84dd5c618e69 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 6 Sep 2023 13:26:51 -0700 Subject: [PATCH 598/756] Use new syntax in integration tests --- tests/integration/test_workflow.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py index 064325046..99315aa5c 100644 --- a/tests/integration/test_workflow.py +++ b/tests/integration/test_workflow.py @@ -67,9 +67,8 @@ def numpy_sqrt(value=0): wf.sqrt = numpy_sqrt(run_on_updates=False) wf.sqrt.inputs.value = wf.rand - wf.gt_switch.signals.input.run = wf.rand.signals.output.ran - wf.sqrt.signals.input.run = wf.gt_switch.signals.output.true - wf.rand.signals.input.run = wf.gt_switch.signals.output.false + wf.gt_switch.signals.output.false > wf.rand > wf.gt_switch # Loop on false + wf.gt_switch.signals.output.true > wf.sqrt # On true break to sqrt node wf.rand.update() self.assertAlmostEqual( From 22854dfed1d46dc240acb1cab35578483f2b7b8e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 6 Sep 2023 13:44:12 -0700 Subject: [PATCH 599/756] Allow and test chaining connections By preventing early termination of the comparison expression --- pyiron_contrib/workflow/channels.py | 1 + pyiron_contrib/workflow/node.py | 1 + tests/unit/workflow/test_function.py | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index eb10f192a..e6de5ae06 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -522,3 +522,4 @@ def __str__(self): def __gt__(self, other: InputSignal | Node): other.connect_output_signal(self) + return True diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 7aceee756..32f710831 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -372,3 +372,4 @@ def __gt__(self, other: InputSignal | Node): Allows users to connect run and ran signals like: `first_node > second_node`. """ other.connect_output_signal(self.signals.output.ran) + return True diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index acbc6dd55..1223f501b 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -229,6 +229,14 @@ def times_two(y): msg="Mixing and matching should work both directions" ) + t2.signals.input.run.disconnect_all() + l > t2 > l + self.assertTrue( + l.signals.input.run.connections[0] is t2.signals.output.ran + and t2.signals.input.run.connections[0] is l.signals.output.ran, + msg="> should allow chaining signal connections" + ) + def test_statuses(self): n = Function(plus_one, run_on_updates=False) self.assertTrue(n.ready) From 0527c68493bc4c2de2c9893306e3da7cfaa01f1f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 6 Sep 2023 13:45:10 -0700 Subject: [PATCH 600/756] Use the syntactic sugar in the while meta node --- pyiron_contrib/workflow/meta.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index 2353f89f1..48d4945a8 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -283,10 +283,7 @@ def make_loop(macro): body_node = macro.add(loop_body_class(label=loop_body_class.__name__)) macro.create.standard.If(label="if_", run_on_updates=False) - # Create a cyclic loop between body and if nodes, so that they will keep - # triggering themselves until the if evaluates false - body_node.signals.input.run = macro.if_.signals.output.true - macro.if_.signals.input.run = body_node.signals.output.ran + macro.if_.signals.output.true > body_node > macro.if_ # Loop until false macro.starting_nodes = [body_node] # Just for convenience: From da77358f2224f8450afb53c2c1fead6f2ea63ede Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 6 Sep 2023 13:49:56 -0700 Subject: [PATCH 601/756] Add a bit of needed documentation as discovered by @ligerzero-ai Not really part of this PR, but let's slip it in while we're working on the latest version of the demo notebook. --- notebooks/workflow_example.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 7ce58fc25..61d8477de 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -11,7 +11,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "c6007a8518bf4d769184af10a6b1f113", + "model_id": "8173fbaf4eec43a4a3f14d5546565f9c", "version_major": 2, "version_minor": 0 }, @@ -154,6 +154,8 @@ "By default, a softer `update()` call is made at instantiation and whenever the node input is updated.\n", "This call checks to make sure the input is `ready` before moving on to `run()`. \n", "\n", + "(Note: If you _do_ uncomment this cell and run it, not only will you get the expected error, but `pm_node` will also set its `failed` attribute to `True` -- this will prevent it from being `ready` again until you manually reset `pm_node.failed = False`.)\n", + "\n", "If we update the input, we'll give the node enough data to work with and it will automatically update the output:" ] }, From c8cecc76af506ab1b749ee7ba17a79997632c237 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 6 Sep 2023 13:57:02 -0700 Subject: [PATCH 602/756] Update example notebook to use new syntax --- notebooks/workflow_example.ipynb | 195 ++++++++++++++++--------------- 1 file changed, 98 insertions(+), 97 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 61d8477de..9b1014594 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -11,7 +11,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "8173fbaf4eec43a4a3f14d5546565f9c", + "model_id": "2ecd6508f47745f192a550ae8b50ea13", "version_major": 2, "version_minor": 0 }, @@ -664,7 +664,7 @@ "\n", "Often, you will want to have nodes with data connections to have signal connections, but this is not strictly required. Here, we'll introduce a (not strictly necessary) third node to control starting the workflow, and chain together to signals from our two functional nodes.\n", "\n", - "Note that we have all the same syntacic sugar from data channels when creating connections between signal channels." + "Note that we have all the same syntacic sugar from data channels when creating connections between signal channels. We also have special syntactic sugar just for signals: `>`. This operator takes an output signal (or node) on the left-hand-side and an input signal (or node) on the right hand side and creates a connection between them. In the case that a node is used, this is just a shortcut to the `run`/`ran` channel depending whether an input/output signal is needed. These expressions can be chained together." ] }, { @@ -687,8 +687,10 @@ " return\n", "\n", "c = control()\n", - "l.signals.input.run = c.signals.output.ran\n", - "t2.signals.input.run = l.signals.output.ran\n", + "c > l > t2 # Execution flow\n", + "# The above expression is equivalent to these two lines:\n", + "# l.signals.input.run = c.signals.output.ran\n", + "# t2.signals.input.run = l.signals.output.ran\n", "c.run()\n", "print(t2.outputs.double.value)" ] @@ -727,7 +729,7 @@ "outputs": [ { "data": { - "image/png": "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XkQ57fUnP/mJueyyy4LGHnvsMZOWljZmNUaK014LCgrMfffdFzR2zz33mKKiojGrMdJGEk7+6Z/+ycyfPz9o7I477jDXXnuto8+aFKd1zp49q8bGRhUXFweNFxcXq76+fkTv0d/fr56eHiUnJ49FiRETiV6bmppUX1+vG264YSxKjJhwe33qqaf0/vvvq7y8fKxLjJjRfK/XXHONfD6fFi9erFdeeWUsy4yIcHp96aWXlJeXp4ceekiXXHKJrrjiCt13333605/+FI2SwxaJf1937NihG2+8URkZGWNRYsSE02thYaE+/PBDHThwQMYYffTRR3rhhRf0zW9+Mxolhy2cXnt7e+XxeILGEhIS1NDQoE8//XTMao22//qv/wr5e1myZIkOHz7sqM9JEU66urrU19enlJSUoPGUlBR1dHSM6D1++tOf6v/+7/908803j0WJETOaXtPS0hQfH6+8vDzdeeedWrt27ViWOmrh9Pree+9p48aN2r17t2JjJ8RzLSWF16vP59P27du1d+9evfjii5o3b54WL16s119/PRolhy2cXo8dO6ZDhw6publZ+/bt09atW/XCCy/ozjvvjEbJYRvtf5v8fr9efvll6/9dlcLrtbCwULt371ZJSYni4uKUmpqqGTNm6Gc/+1k0Sg5bOL0uWbJETz75pBobG2WM0eHDh1VTU6NPP/1UXV1d0Sg7Kjo6Oob8e/nss88c9Tlx/us9Ai6XK+i1MSZkbCjPPfecHnjgAf3Hf/yHZs+ePVblRVQ4vdbV1emTTz7Rm2++qY0bN+orX/mKbrnllrEsMyJG2mtfX59uvfVWVVRU6IorrohWeRHl5HudN2+e5s2bN/i6oKBA7e3tevjhh/W1r31tTOuMBCe99vf3y+Vyaffu3YNPON2yZYu++93vatu2bUpISBjzekcj3P82Pf3005oxY4ZWrFgxRpVFnpNeW1padPfdd+tHP/qRlixZIr/fr/vvv1+lpaXasWNHNModFSe9/vCHP1RHR4euvfZaGWOUkpKi1atX66GHHpLb7Y5GuVEz1N/LUOMXMimOnMyaNUtutzsksXZ2doYkuM/bs2eP1qxZo3/7t3/TjTfeOJZlRsRoes3MzNRXv/pVff/739e9996rBx54YAwrHT2nvfb09Ojw4cO66667FBsbq9jYWG3evFn/8z//o9jYWP3mN7+JVumOjeZ7Pd+1116r9957L9LlRVQ4vfp8Pl1yySVBj17PysqSMUYffvjhmNY7GqP5Xo0xqqmp0apVqxQXFzeWZUZEOL1WVlaqqKhI999/v6688kotWbJEVVVVqqmpkd/vj0bZYQmn14SEBNXU1Oj06dP64IMP1NbWpjlz5igxMVGzZs2KRtlRkZqaOuTfS2xsrGbOnDni95kU4SQuLk65ubmqra0NGq+trVVhYeGw+z333HNavXq1nn32WevPcQ4It9fPM8aot7c30uVFlNNek5KS9Lvf/U5vvfXW4FZaWqp58+bprbfe0qJFi6JVumOR+l6bmprk8/kiXV5EhdNrUVGRTpw4oU8++WRw7OjRo4qJiVFaWtqY1jsao/leX3vtNf3hD3/QmjVrxrLEiAmn19OnTysmJvhnaOAogrH4sW+j+V6nTZumtLQ0ud1uPf/88/rWt74V8ncwkRUUFIT8vfz6179WXl6epk2bNvI3cnT5rMUGbuvasWOHaWlpMevXrzfTp08fvMJ948aNZtWqVYPzn332WRMbG2u2bdsWdNvexx9/PF4tjJjTXn/+85+bl156yRw9etQcPXrU1NTUmKSkJLNp06bxamHEnPb6eRPpbh2nvT7yyCNm37595ujRo6a5udls3LjRSDJ79+4drxZGzGmvPT09Ji0tzXz3u981v//9781rr71mLr/8crN27drxamHEwv1neOXKlWbRokXRLndUnPb61FNPmdjYWFNVVWXef/99c+jQIZOXl2fy8/PHq4URc9rru+++a5555hlz9OhR89vf/taUlJSY5ORk09raOk4djExPT49pamoyTU1NRpLZsmWLaWpqGrxl+vN9DtxKfO+995qWlhazY8eOqX0rsTHGbNu2zWRkZJi4uDizcOFC89prrw3+2fe+9z1zww03DL6+4YYbjKSQ7Xvf+170Cw+Dk14fe+wxs2DBAnPRRReZpKQkc80115iqqirT19c3DpU756TXz5tI4cQYZ70++OCDZu7cucbj8Zgvf/nL5rrrrjO/+tWvxqHq8Dj9Xt9++21z4403moSEBJOWlmY2bNhgTp8+HeWqw+O0148//tgkJCSY7du3R7nS0XPa62OPPWays7NNQkKC8fl85m//9m/Nhx9+GOWqw+Ok15aWFnP11VebhIQEk5SUZL797W+bd955ZxyqdmZgyYLhfiuH+k5fffVVc80115i4uDgzZ84cU11d7fhzXcZYfOwMAABMOZPnRBcAAJgUCCcAAMAqhBMAAGAVwgkAALAK4QQAAFiFcAIAAKxCOAEAAFYhnAAAAKsQTgAAgFUIJwAAwCqEEwAAYBXCCQAAsMr/B4sK/w0pV/iMAAAAAElFTkSuQmCC", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiMAAAGdCAYAAADAAnMpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAjAklEQVR4nO3df3BU1f3/8ddmQ7JIyc4ETLKSGCPlV0gVk0wwMNRWJQUdFDsdYilg/fGpwR8FGe3A0DGGcSatH7XUjomiokNBmvFXPzKlaGZaJYifMoQwI42tDsSGHxvThHE3/khSkvP9g2/yYU0CuWuyJ7v7fMzcme7JuZv3zmncF+eee67LGGMEAABgSYLtAgAAQHwjjAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwKtF2AcPR29urU6dOaeLEiXK5XLbLAQAAw2CMUUdHhy655BIlJAw9/xEVYeTUqVPKysqyXQYAAAjD8ePHlZmZOeTPoyKMTJw4UdLZD5OSkmK5GgAAMBzBYFBZWVn93+NDiYow0ndpJiUlhTACAECUudASCxawAgAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKyKik3PAACjp6fX6EDTabV2dCptokdFOalyJ/AcMEQOYQQA4tieI35V7GqUP9DZ3+bzelS+JFeL8nwWK0M84TINAMSpPUf8Wr39UEgQkaSWQKdWbz+kPUf8lipDvCGMAEAc6uk1qtjVKDPIz/raKnY1qqd3sB7AyCKMAEAcOtB0esCMyLmMJH+gUweaTkeuKMQtwggAxKHWjqGDSDj9gG+CMAIAcShtomdE+wHfBGEEAOJQUU6qfF6PhrqB16Wzd9UU5aRGsizEKcIIAMQhd4JL5UtyJWlAIOl7Xb4kl/1GEBGEEQCIU4vyfKpeka8Mb+ilmAyvR9Ur8tlnBBHDpmcAEMcW5fm0MDeDHVhhFWEEAOKcO8Gl4qmTbJeBOEYYAQBYxbNxQBgBAFjDs3EgsYAVAGAJz8ZBH8IIACDieDYOzkUYAQBEHM/GwbkIIwCAiOPZODgXYQQAEHE8GwfnIowAACKOZ+PgXIQRAEDE8WwcnIswAgCwgmfjoA+bngEArOHZOJAIIwAAy3g2DrhMAwAArCKMAAAAqwgjAADAqrDCSFVVlXJycuTxeFRQUKC6urrz9u/q6tLGjRuVnZ2t5ORkTZ06VVu3bg2rYAAAEFscL2CtqanR2rVrVVVVpfnz5+vZZ5/V4sWL1djYqEsvvXTQc5YtW6ZPP/1UL7zwgr797W+rtbVVZ86c+cbFAwCA6Ocyxjh6JOLcuXOVn5+v6urq/rZZs2Zp6dKlqqysHNB/z549uvXWW3Xs2DGlpoa3k14wGJTX61UgEFBKSkpY7wEAACJruN/fji7TdHd3q76+XiUlJSHtJSUl2r9//6DnvPnmmyosLNRjjz2mKVOmaPr06XrwwQf11VdfDfl7urq6FAwGQw4AABCbHF2maWtrU09Pj9LT00Pa09PT1dLSMug5x44d0759++TxePTGG2+ora1N99xzj06fPj3kupHKykpVVFQ4KQ0AAESpsBawulyhO+MZYwa09ent7ZXL5dKOHTtUVFSkG264QU8++aReeumlIWdHNmzYoEAg0H8cP348nDIBAEAUcDQzMnnyZLnd7gGzIK2trQNmS/r4fD5NmTJFXq+3v23WrFkyxujEiROaNm3agHOSk5OVnJzspDQAABClHM2MJCUlqaCgQLW1tSHttbW1mjdv3qDnzJ8/X6dOndLnn3/e3/bRRx8pISFBmZmZYZQMAABiiePLNOvWrdPzzz+vrVu36sMPP9QDDzyg5uZmlZWVSTp7iWXVqlX9/ZcvX65Jkybp9ttvV2Njo/bu3auHHnpId9xxh8aPHz9ynwQAAEQlx/uMlJaWqr29XZs2bZLf71deXp52796t7OxsSZLf71dzc3N//29961uqra3V/fffr8LCQk2aNEnLli3To48+OnKfAgAARC3H+4zYwD4jAABEn1HZZwQAAGCkEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFWO9xkBAMCWnl6jA02n1drRqbSJHhXlpMqdMPiz0RA9CCMAgKiw54hfFbsa5Q909rf5vB6VL8nVojyfxcqi11gJd4QRAMCYt+eIX6u3H9LXd+lsCXRq9fZDql6RTyBxaCyFO9aMAADGtJ5eo4pdjQOCiKT+topdjerpHfMbio8ZfeHu3CAi/V+423PEH9F6CCMAgDHtQNPpAV+a5zKS/IFOHWg6HbmiothYDHeEEQDAmNbaMXQQCadfvBuL4Y4wAgAY09Imeka0X7wbi+GOMAIAGNOKclLl83o01D0eLp1deFmUkxrJsqLWWAx3hBEAwJjmTnCpfEmuJA0IJH2vy5fkst/IMI3FcEcYAQCMeYvyfKpeka8Mb+i/1jO8Hm7rdWgshjuXMWbM3wsVDAbl9XoVCASUkpJiuxwAgCVjZZOuWBCJfUaG+/1NGAEAIE6Ndrgb7vc3O7ACABCn3AkuFU+dZLsM1owAAAC7CCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIp9RgAAVrCbKvoQRgAAEReJrcgRPbhMAwCIqD1H/Fq9/VBIEJGklkCnVm8/pD1H/JYqgy2EEQBAxPT0GlXsatRgD0Xra6vY1aie3jH/2DSMIMIIACBiDjSdHjAjci4jyR/o1IGm05ErCtYRRgAAEdPaMXQQCacfYgNhBAAQMWkTPSPaD7GBMAIAiJiinFT5vB4NdQOvS2fvqinKSY1kWbCMMAIAiBh3gkvlS3IlaUAg6XtdviSX/UbiDGEEABBRi/J8ql6Rrwxv6KWYDK9H1Svy2WckDrHpGQAg4hbl+bQwN4MdWCGJMAIAsMSd4FLx1Em2y8AYwGUaAABgFWEEAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFgVVhipqqpSTk6OPB6PCgoKVFdXN2Tfd955Ry6Xa8Dxj3/8I+yiAQBA7HAcRmpqarR27Vpt3LhRDQ0NWrBggRYvXqzm5ubznvfPf/5Tfr+//5g2bVrYRQMAgNjhOIw8+eSTuvPOO3XXXXdp1qxZ2rx5s7KyslRdXX3e89LS0pSRkdF/uN3usIsGAACxw1EY6e7uVn19vUpKSkLaS0pKtH///vOee9VVV8nn8+m6667TX//61/P27erqUjAYDDkAAEBschRG2tra1NPTo/T09JD29PR0tbS0DHqOz+fTli1b9Nprr+n111/XjBkzdN1112nv3r1D/p7Kykp5vd7+Iysry0mZAAAgioT1bBqXK/RBRsaYAW19ZsyYoRkzZvS/Li4u1vHjx/X444/ru9/97qDnbNiwQevWret/HQwGCSQAAMQoRzMjkydPltvtHjAL0traOmC25Hyuvvpqffzxx0P+PDk5WSkpKSEHAACITY7CSFJSkgoKClRbWxvSXltbq3nz5g37fRoaGuTz+Zz8agAAEKMcX6ZZt26dVq5cqcLCQhUXF2vLli1qbm5WWVmZpLOXWE6ePKlt27ZJkjZv3qzLLrtMs2fPVnd3t7Zv367XXntNr7322sh+EgAAEJUch5HS0lK1t7dr06ZN8vv9ysvL0+7du5WdnS1J8vv9IXuOdHd368EHH9TJkyc1fvx4zZ49W3/60590ww03jNynAAAAUctljDG2i7iQYDAor9erQCDA+hEAAKLEcL+/eTYNAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwKqwwkhVVZVycnLk8XhUUFCgurq6YZ333nvvKTExUXPmzAnn1wIAgBjkOIzU1NRo7dq12rhxoxoaGrRgwQItXrxYzc3N5z0vEAho1apVuu6668IuFgAAxB6XMcY4OWHu3LnKz89XdXV1f9usWbO0dOlSVVZWDnnerbfeqmnTpsntduuPf/yjDh8+POzfGQwG5fV6FQgElJKS4qRcAABgyXC/vx3NjHR3d6u+vl4lJSUh7SUlJdq/f/+Q57344os6evSoysvLh/V7urq6FAwGQw4AABCbHIWRtrY29fT0KD09PaQ9PT1dLS0tg57z8ccfa/369dqxY4cSExOH9XsqKyvl9Xr7j6ysLCdlAgCAKBLWAlaXyxXy2hgzoE2Senp6tHz5clVUVGj69OnDfv8NGzYoEAj0H8ePHw+nTAAAEAWGN1Xx/02ePFlut3vALEhra+uA2RJJ6ujo0MGDB9XQ0KD77rtPktTb2ytjjBITE/X222/r2muvHXBecnKykpOTnZQGRKWeXqMDTafV2tGptIkeFeWkyp0wMNgDQCxzFEaSkpJUUFCg2tpa3XLLLf3ttbW1uvnmmwf0T0lJ0QcffBDSVlVVpb/85S969dVXlZOTE2bZQPTbc8Svil2N8gc6+9t8Xo/Kl+RqUZ7PYmUAEFmOwogkrVu3TitXrlRhYaGKi4u1ZcsWNTc3q6ysTNLZSywnT57Utm3blJCQoLy8vJDz09LS5PF4BrQD8WTPEb9Wbz+kr9/K1hLo1Orth1S9Ip9AAiBuOA4jpaWlam9v16ZNm+T3+5WXl6fdu3crOztbkuT3+y+45wgQz3p6jSp2NQ4IIpJkJLkkVexq1MLcDC7ZAIgLjvcZsYF9RhBL3j/arh8/978X7Lfzv65W8dRJEagIAEbHqOwzAuCba+3ovHAnB/0AINoRRoAIS5voGdF+ABDtCCNAhBXlpMrn9Wio1SAunb2rpignNZJlAYA1hBEgwtwJLpUvyZWkAYGk73X5klwWrwKIG4QRwIJFeT5Vr8hXhjf0UkyG18NtvQDijuNbewGMjEV5Pi3MzWAHVgBxjzACWOROcHH7LoC4x2UaAABgFWEEAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFWEEQAAYBVhBAAAWEUYAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFWEEQAAYBVhBAAAWEUYAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFWEEQAAYBVhBAAAWEUYAQAAVhFGAACAVYQRAABgVVhhpKqqSjk5OfJ4PCooKFBdXd2Qffft26f58+dr0qRJGj9+vGbOnKnf/OY3YRcMAABiS6LTE2pqarR27VpVVVVp/vz5evbZZ7V48WI1Njbq0ksvHdB/woQJuu+++3TFFVdowoQJ2rdvn+6++25NmDBBP/vZz0bkQwAAgOjlMsYYJyfMnTtX+fn5qq6u7m+bNWuWli5dqsrKymG9xw9/+ENNmDBBv//974fVPxgMyuv1KhAIKCUlxUm5AADAkuF+fzu6TNPd3a36+nqVlJSEtJeUlGj//v3Deo+Ghgbt379f11xzzZB9urq6FAwGQw4AABCbHIWRtrY29fT0KD09PaQ9PT1dLS0t5z03MzNTycnJKiws1L333qu77rpryL6VlZXyer39R1ZWlpMyAQBAFAlrAavL5Qp5bYwZ0PZ1dXV1OnjwoJ555hlt3rxZO3fuHLLvhg0bFAgE+o/jx4+HUyYAAIgCjhawTp48WW63e8AsSGtr64DZkq/LycmRJH3nO9/Rp59+qkceeUQ//vGPB+2bnJys5ORkJ6UBAIAo5WhmJCkpSQUFBaqtrQ1pr62t1bx584b9PsYYdXV1OfnVAAAgRjm+tXfdunVauXKlCgsLVVxcrC1btqi5uVllZWWSzl5iOXnypLZt2yZJevrpp3XppZdq5syZks7uO/L444/r/vvvH8GPAQAAopXjMFJaWqr29nZt2rRJfr9feXl52r17t7KzsyVJfr9fzc3N/f17e3u1YcMGNTU1KTExUVOnTtWvfvUr3X333SP3KQAAQNRyvM+IDewzAgBA9BmVfUYAAABGGmEEAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFWEEQAAYBVhBAAAWEUYAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABWEUYAAIBVhBEAAGBVou0CbOnpNTrQdFqtHZ1Km+hRUU6q3Aku22UBABB34jKM7DniV8WuRvkDnf1tPq9H5UtytSjPZ7EyAADiT9xdptlzxK/V2w+FBBFJagl0avX2Q9pzxG+pMgAA4lNchZGeXqOKXY0yg/ysr61iV6N6egfrAQAARkNchZEDTacHzIicy0jyBzp1oOl05IoCACDOxVUYae0YOoiE0w8AAHxzcRVG0iZ6RrQfAAD45uIqjBTlpMrn9WioG3hdOntXTVFOaiTLAgAgrsVVGHEnuFS+JFeSBgSSvtflS3LZbwQAgAiKqzAiSYvyfKpeka8Mb+ilmAyvR9Ur8tlnBACACIvLTc8W5fm0MDeDHVgBABgD4jKMSGcv2RRPnWS7DAAA4l7cXaYBAABjC2EEAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFWEEQAAYBVhBAAAWEUYAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABWhRVGqqqqlJOTI4/Ho4KCAtXV1Q3Z9/XXX9fChQt18cUXKyUlRcXFxXrrrbfCLhgAAMQWx2GkpqZGa9eu1caNG9XQ0KAFCxZo8eLFam5uHrT/3r17tXDhQu3evVv19fX6/ve/ryVLlqihoeEbFw8AAKKfyxhjnJwwd+5c5efnq7q6ur9t1qxZWrp0qSorK4f1HrNnz1ZpaakefvjhYfUPBoPyer0KBAJKSUlxUi4AALBkuN/fjmZGuru7VV9fr5KSkpD2kpIS7d+/f1jv0dvbq46ODqWmpg7Zp6urS8FgMOQAAACxyVEYaWtrU09Pj9LT00Pa09PT1dLSMqz3eOKJJ/TFF19o2bJlQ/aprKyU1+vtP7KyspyUCQAAokhYC1hdLlfIa2PMgLbB7Ny5U4888ohqamqUlpY2ZL8NGzYoEAj0H8ePHw+nTAAAEAUSnXSePHmy3G73gFmQ1tbWAbMlX1dTU6M777xTr7zyiq6//vrz9k1OTlZycrKT0oB+Pb1GB5pOq7WjU2kTPSrKSZU74cJhGQBgh6MwkpSUpIKCAtXW1uqWW27pb6+trdXNN9885Hk7d+7UHXfcoZ07d+rGG28Mv1rgAvYc8atiV6P8gc7+Np/Xo/IluVqU57NYGQBgKI4v06xbt07PP/+8tm7dqg8//FAPPPCAmpubVVZWJunsJZZVq1b199+5c6dWrVqlJ554QldffbVaWlrU0tKiQCAwcp8C0Nkgsnr7oZAgIkktgU6t3n5Ie474LVUGADgfx2GktLRUmzdv1qZNmzRnzhzt3btXu3fvVnZ2tiTJ7/eH7Dny7LPP6syZM7r33nvl8/n6jzVr1ozcp0Dc6+k1qtjVqMHuU+9rq9jVqJ5eR3eyAwAiwPE+Izawzwgu5P2j7frxc/97wX47/+tqFU+dFIGKAACjss8IMFa1dnReuJODfgCAyCGMICakTfSMaD8AQOQQRhATinJS5fN6NNQNvC6dvaumKGfonX8BAHYQRhAT3AkulS/JlaQBgaTvdfmSXPYbAYAxiDCCmLEoz6fqFfnK8IZeisnwelS9Ip99RgBgjHK06Rkw1i3K82lhbgY7sAJAFCGMIOa4E1zcvgsAUYTLNAAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwCrCCAAAsIowAgAArCKMAAAAqwgjAADAKsIIAACwijACAACsIowAAACrCCMAAMAqwggAALCKMAIAAKwijAAAAKsIIwAAwKqwwkhVVZVycnLk8XhUUFCgurq6Ifv6/X4tX75cM2bMUEJCgtauXRturQAAIAY5DiM1NTVau3atNm7cqIaGBi1YsECLFy9Wc3PzoP27urp08cUXa+PGjbryyiu/ccEAACC2uIwxxskJc+fOVX5+vqqrq/vbZs2apaVLl6qysvK8537ve9/TnDlztHnzZkdFBoNBeb1eBQIBpaSkODoXAADYMdzvb0czI93d3aqvr1dJSUlIe0lJifbv3x9epYPo6upSMBgMOQAAQGxyFEba2trU09Oj9PT0kPb09HS1tLSMWFGVlZXyer39R1ZW1oi9NwAAGFvCWsDqcrlCXhtjBrR9Exs2bFAgEOg/jh8/PmLvDQAAxpZEJ50nT54st9s9YBaktbV1wGzJN5GcnKzk5OQRez8AADB2OZoZSUpKUkFBgWpra0Paa2trNW/evBEtDAAAxAdHMyOStG7dOq1cuVKFhYUqLi7Wli1b1NzcrLKyMklnL7GcPHlS27Zt6z/n8OHDkqTPP/9c//73v3X48GElJSUpNzd3ZD4FAACIWo7DSGlpqdrb27Vp0yb5/X7l5eVp9+7dys7OlnR2k7Ov7zly1VVX9f/v+vp6vfzyy8rOztYnn3zyzaoHAABRz/E+IzawzwgAANFnVPYZAQAAGGmEEQAAYBVhBAAAWEUYAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFWEEQAAYBVhBAAAWEUYAQAAVhFGAACAVYQRAABgFWEEAABYlWi7ACBa9fQaHWg6rdaOTqVN9KgoJ1XuBJftsgAg6hBGgDDsOeJXxa5G+QOd/W0+r0flS3K1KM9nsTIAiD5cpgEc2nPEr9XbD4UEEUlqCXRq9fZD2nPEb6kyAIhOhBHAgZ5eo4pdjTKD/KyvrWJXo3p6B+sBABgMYQRw4EDT6QEzIucykvyBTh1oOh25ohA3enqN3j/arv85fFLvH20n9CJmsGYEcKC1Y+ggEk4/YLhYp4RYxswI4EDaRM+I9gOGg3VKiHWEEcCBopxU+bweDXUDr0tn/7ValJMaybIQw1inhHhAGAEccCe4VL4kV5IGBJK+1+VLctlvBCOGdUqIB4QRwKFFeT5Vr8hXhjf0UkyG16PqFflcv8eIYp0S4gELWIEwLMrzaWFuBjuwYtSxTgnxgDAChMmd4FLx1Em2y0CM61un1BLoHHTdiEtnZ+VYp4RoxmUaABjDWKeEeEAYAYAxjnVKiHVcpgGAKMA6JcQywggARAnWKSFWcZkGAABYRRgBAABWEUYAAIBVhBEAAGAVYQQAAFhFGAEAAFYRRgAAgFWEEQAAYBVhBAAAWBUVO7Aac/ZZlcFg0HIlAABguPq+t/u+x4cSFWGko6NDkpSVlWW5EgAA4FRHR4e8Xu+QP3eZC8WVMaC3t1enTp3SxIkT5XI5eyhUMBhUVlaWjh8/rpSUlFGqEE4xLmMPYzL2MCZjD2PijDFGHR0duuSSS5SQMPTKkKiYGUlISFBmZuY3eo+UlBT+jzMGMS5jD2My9jAmYw9jMnznmxHpwwJWAABgFWEEAABYFfNhJDk5WeXl5UpOTrZdCs7BuIw9jMnYw5iMPYzJ6IiKBawAACB2xfzMCAAAGNsIIwAAwCrCCAAAsIowAgAArIqJMFJVVaWcnBx5PB4VFBSorq7uvP3fffddFRQUyOPx6PLLL9czzzwToUrjh5Mxef3117Vw4UJdfPHFSklJUXFxsd56660IVhsfnP6d9HnvvfeUmJioOXPmjG6BccrpuHR1dWnjxo3Kzs5WcnKypk6dqq1bt0ao2vjgdEx27NihK6+8UhdddJF8Pp9uv/12tbe3R6jaGGGi3B/+8Aczbtw489xzz5nGxkazZs0aM2HCBPOvf/1r0P7Hjh0zF110kVmzZo1pbGw0zz33nBk3bpx59dVXI1x57HI6JmvWrDG//vWvzYEDB8xHH31kNmzYYMaNG2cOHToU4cpjl9Mx6fPZZ5+Zyy+/3JSUlJgrr7wyMsXGkXDG5aabbjJz5841tbW1pqmpyfztb38z7733XgSrjm1Ox6Surs4kJCSY3/72t+bYsWOmrq7OzJ492yxdujTClUe3qA8jRUVFpqysLKRt5syZZv369YP2/8UvfmFmzpwZ0nb33Xebq6++etRqjDdOx2Qwubm5pqKiYqRLi1vhjklpaan55S9/acrLywkjo8DpuPz5z382Xq/XtLe3R6K8uOR0TP77v//bXH755SFtTz31lMnMzBy1GmNRVF+m6e7uVn19vUpKSkLaS0pKtH///kHPef/99wf0/8EPfqCDBw/qP//5z6jVGi/CGZOv6+3tVUdHh1JTU0ejxLgT7pi8+OKLOnr0qMrLy0e7xLgUzri8+eabKiws1GOPPaYpU6Zo+vTpevDBB/XVV19FouSYF86YzJs3TydOnNDu3btljNGnn36qV199VTfeeGMkSo4ZUfGgvKG0tbWpp6dH6enpIe3p6elqaWkZ9JyWlpZB+585c0ZtbW3y+XyjVm88CGdMvu6JJ57QF198oWXLlo1GiXEnnDH5+OOPtX79etXV1SkxMar/MzFmhTMux44d0759++TxePTGG2+ora1N99xzj06fPs26kREQzpjMmzdPO3bsUGlpqTo7O3XmzBnddNNN+t3vfheJkmNGVM+M9HG5XCGvjTED2i7Uf7B2hM/pmPTZuXOnHnnkEdXU1CgtLW20yotLwx2Tnp4eLV++XBUVFZo+fXqkyotbTv5Went75XK5tGPHDhUVFemGG27Qk08+qZdeeonZkRHkZEwaGxv185//XA8//LDq6+u1Z88eNTU1qaysLBKlxoyo/ifP5MmT5Xa7ByTW1tbWAcm2T0ZGxqD9ExMTNWnSpFGrNV6EMyZ9ampqdOedd+qVV17R9ddfP5plxhWnY9LR0aGDBw+qoaFB9913n6SzX4LGGCUmJurtt9/WtddeG5HaY1k4fys+n09TpkwJeST7rFmzZIzRiRMnNG3atFGtOdaFMyaVlZWaP3++HnroIUnSFVdcoQkTJmjBggV69NFHmW0fpqieGUlKSlJBQYFqa2tD2mtrazVv3rxBzykuLh7Q/+2331ZhYaHGjRs3arXGi3DGRDo7I/LTn/5UL7/8MtdaR5jTMUlJSdEHH3ygw4cP9x9lZWWaMWOGDh8+rLlz50aq9JgWzt/K/PnzderUKX3++ef9bR999JESEhKUmZk5qvXGg3DG5Msvv1RCQuhXqdvtlvR/s+4YBlsrZ0dK321YL7zwgmlsbDRr1641EyZMMJ988okxxpj169eblStX9vfvu7X3gQceMI2NjeaFF17g1t4R5nRMXn75ZZOYmGiefvpp4/f7+4/PPvvM1keIOU7H5Ou4m2Z0OB2Xjo4Ok5mZaX70ox+Zv//97+bdd98106ZNM3fddZetjxBznI7Jiy++aBITE01VVZU5evSo2bdvnyksLDRFRUW2PkJUivowYowxTz/9tMnOzjZJSUkmPz/fvPvuu/0/u+2228w111wT0v+dd94xV111lUlKSjKXXXaZqa6ujnDFsc/JmFxzzTVG0oDjtttui3zhMczp38m5CCOjx+m4fPjhh+b6668348ePN5mZmWbdunXmyy+/jHDVsc3pmDz11FMmNzfXjB8/3vh8PvOTn/zEnDhxIsJVRzeXMcwjAQAAe6J6zQgAAIh+hBEAAGAVYQQAAFhFGAEAAFYRRgAAgFWEEQAAYBVhBAAAWEUYAQAAVhFGAACAVYQRAABgFWEEAABYRRgBAABW/T+jsK5QymkWfgAAAABJRU5ErkJggg==", "text/plain": [ "
" ] @@ -816,7 +818,7 @@ "outputs": [ { "data": { - "image/png": "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UVB7UVGRhoaGwm7zhz/8QXv37lV3d7eysiJ7u+bmZjU1NUVTGhYhjjqAhcHVHvFR4cqX02HXkG8y7AiuTVKxY/oyX1xeTCew2mzB105blhXSJkl+v1933nmnmpqadP3110f8+vv27ZPP55t9XLhwIZYykeI46gCuHuddxU9mhk2NtW5J08FjrpnnjbVu1huJQFRhZPny5crMzAwZBRkeHg4ZLZGkiYkJnTp1St/+9reVlZWlrKwsHThwQG+++aaysrL02muvhX2fnJwc5eXlBT2QfmaOOub7M7Zp+uiOow4gPM67ir+aEqdat5Wq2BF8UFTssKt1WykjTxGKapomOztbZWVl6urq0te+9rXZ9q6uLn31q18N6Z+Xl6ff/e53QW0tLS167bXX9MILL8jlcsVYNtLBzFHHziO9sklB/1A56gCuLJrzrirXFSSusEWmpsSpze5iVmC9ClGvwNrQ0KC77rpL5eXlqqys1E9/+lMNDg6qvr5e0vQUy7vvvquf/exnysjIUElJSdD2hYWFstvtIe1AODNHHZ+e7y5mvhu4Is67SpzMDBuB7ipEHUbq6uo0OjqqAwcOyOv1qqSkRB0dHVqzZo0kyev1XnHNESAaHHUAseG8K6QKm2VZST9ZOD4+LofDIZ/Px/kjABAhf8DSTY+9dsWrPY4/fDPhHnER6fc396YBgEWKqz2QKggjALCIcbUHUkHU54wAAFIL510h2RFGACANcLUHkhnTNAAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjMoyXYAp/oClnoExDU9MqjDXrgpXvjIzbKbLAgAg7aRlGOns96qp3SOvb3K2zemwq7HWrZoSp8HKAABIP2k3TdPZ79XOI71BQUSShnyT2nmkV539XkOVAQCQntIqjPgDlpraPbLC/GymrandI38gXA8AABAPaRVGegbGQkZE5rIkeX2T6hkYS1xRAACkubQKI8MT8weRWPoBAICrl1ZhpDDXvqD9AADA1UurMFLhypfTYdd8F/DaNH1VTYUrP5FlAQCQ1tIqjGRm2NRY65akkEAy87yx1s16IwAAJFBahRFJqilxqnVbqYodwVMxxQ67WreVss4IAAAJlpaLntWUOLXZXcwKrAAAJIG0DCPS9JRN5boC02UAAJD20m6aBgAAJBfCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwKqYw0tLSIpfLJbvdrrKyMnV3d8/b96WXXtLmzZv1mc98Rnl5eaqsrNRvfvObmAsGAACLS9RhpK2tTbt379b+/fvV19enTZs2acuWLRocHAzb//XXX9fmzZvV0dGh06dP68tf/rJqa2vV19d31cUDAIDUZ7Msy4pmgw0bNqi0tFStra2zbevXr9fWrVvV3Nwc0Wt8/vOfV11dnR555JGI+o+Pj8vhcMjn8ykvLy+achPOH7BY2RUAAEX+/R3VCqwXL17U6dOntXfv3qD26upqnThxIqLXCAQCmpiYUH7+/HfGnZqa0tTU1Ozz8fHxaMo0prPfq6Z2j7y+ydk2p8Ouxlo397wBACSdZDmAjiqMjIyMyO/3q6ioKKi9qKhIQ0NDEb3G97//fX344Ye644475u3T3NyspqamaEozrrPfq51HevXpYaYh36R2HunlJnwAgKSSTAfQMZ3AarMFpybLskLawnn++ef13e9+V21tbSosLJy33759++Tz+WYfFy5ciKXMhPEHLDW1e0KCiKTZtqZ2j/yBqGbEAACIi5kD6LlBRPrLAXRnvzeh9UQVRpYvX67MzMyQUZDh4eGQ0ZJPa2tr044dO/SLX/xCt9xyy2X75uTkKC8vL+iRzHoGxkJ26FyWJK9vUj0DY4krCgCAMJLxADqqMJKdna2ysjJ1dXUFtXd1damqqmre7Z5//nndc889eu6553T77bfHVmkSG56YP4jE0g8AgHhJxgPoqM4ZkaSGhgbdddddKi8vV2VlpX76059qcHBQ9fX1kqanWN5991397Gc/kzQdRLZv364f/vCH+uIXvzg7qrJ06VI5HI4F/CjmFObaF7QfAADxkowH0FGHkbq6Oo2OjurAgQPyer0qKSlRR0eH1qxZI0nyer1Ba4785Cc/0aVLl/Stb31L3/rWt2bb7777bj377LNX/wmSQIUrX06HXUO+ybDDXjZJxY7ps5QBADApGQ+go15nxIRUWGdk5mQgSUGBZOa0Xq6mAQAkA3/A0k2PvXbFA+jjD9981Zf5Rvr9zb1pFkhNiVOt20pV7AhOksUOO0EEAJA0MjNsaqx1S/rLAfOMmeeNte6ErjfCyMgCS5YFZAAAuJxErDMS6fc3YQQAgDQV7wPouCwHDwAAFo/MDJsq1xWYLoNzRgAAgFmEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYxTojQBJg5V4A6YwwAhiWiCWZASCZMU0DGDRzt+e5QUSShnyT2nmkV539XkOVAUDiEEYAQ/wBS03tnrC38J5pa2r3yB9I+ttHAcBVIYwAhvQMjIWMiMxlSfL6JtUzMJa4ogDAAMIIYMjwxPxBJJZ+AJCqCCOAIYW59gXtBwCpijACGFLhypfTYdd8F/DaNH1VTYUrP5FlAUDCEUYAQzIzbGqsdUtSSCCZed5Y62a9EQCLHmEEMKimxKnWbaUqdgRPxRQ77GrdVso6IwDSAoueAYbVlDi12V3MCqwA0hZhBEgCmRk2Va4rMF0GABjBNA0AADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKOyTBcAID35A5Z6BsY0PDGpwly7Klz5ysywmS4LgAGEEQAJ19nvVVO7R17f5Gyb02FXY61bNSVOg5UBMIFpGgAJ1dnv1c4jvUFBRJKGfJPaeaRXnf1eQ5UBMIUwAiBh/AFLTe0eWWF+NtPW1O6RPxCuB4DFijACIGF6BsZCRkTmsiR5fZPqGRhLXFEAjCOMAEiY4Yn5g0gs/QAsDoQRAAlTmGtf0H4AFgfCCICEqXDly+mwa74LeG2avqqmwpWfyLIAGEYYAZAwmRk2Nda6JSkkkMw8b6x1s94IkGYIIwASqqbEqdZtpSp2BE/FFDvsat1WyjojQBpi0TMACVdT4tRmdzErsAKQFOPISEtLi1wul+x2u8rKytTd3X3Z/seOHVNZWZnsdrvWrl2rJ598MqZiASwemRk2Va4r0FdvXKnKdQUEESCNRR1G2tratHv3bu3fv199fX3atGmTtmzZosHBwbD9BwYGdNttt2nTpk3q6+vTd77zHe3atUsvvvjiVRcPAABSn82yrKiWOtywYYNKS0vV2to627Z+/Xpt3bpVzc3NIf0ffvhhvfLKKzp79uxsW319vd58802dPHkyovccHx+Xw+GQz+dTXl5eNOUCAABDIv3+jmpk5OLFizp9+rSqq6uD2qurq3XixImw25w8eTKk/6233qpTp07pk08+CbvN1NSUxsfHgx4AAGBxiiqMjIyMyO/3q6ioKKi9qKhIQ0NDYbcZGhoK2//SpUsaGRkJu01zc7McDsfsY/Xq1dGUCQAAUkhMJ7DabMEnmlmWFdJ2pf7h2mfs27dPPp9v9nHhwoVYygQAACkgqkt7ly9frszMzJBRkOHh4ZDRjxnFxcVh+2dlZamgoCDsNjk5OcrJyYmmNAAAkKKiGhnJzs5WWVmZurq6gtq7urpUVVUVdpvKysqQ/q+++qrKy8u1ZMmSKMsFAACLTdTTNA0NDXr66ad1+PBhnT17Vnv27NHg4KDq6+slTU+xbN++fbZ/fX293n77bTU0NOjs2bM6fPiwDh06pAcffHDhPgUAAEhZUa/AWldXp9HRUR04cEBer1clJSXq6OjQmjVrJElerzdozRGXy6WOjg7t2bNHTzzxhFasWKHHH39cX//61xfuUwAAgJQV9TojJvh8Pl177bW6cOEC64wAAJAixsfHtXr1ar3//vtyOBzz9kuJe9NMTExIEpf4AgCQgiYmJi4bRlJiZCQQCOi9995Tbm7uZS8hTkUzqZFRn+TGfkod7KvUwb5KDVeznyzL0sTEhFasWKGMjPlPU02JkZGMjAytWrXKdBlxlZeXxx9jCmA/pQ72VepgX6WGWPfT5UZEZsS06BkAAMBCIYwAAACjCCOG5eTkqLGxkRVnkxz7KXWwr1IH+yo1JGI/pcQJrAAAYPFiZAQAABhFGAEAAEYRRgAAgFGEEQAAYBRhJAFaWlrkcrlkt9tVVlam7u7uefu+9NJL2rx5sz7zmc8oLy9PlZWV+s1vfpPAatNXNPtprjfeeENZWVm68cYb41sgZkW7r6amprR//36tWbNGOTk5WrdunQ4fPpygatNXtPvp6NGjuuGGG3TNNdfI6XTq3nvv1ejoaIKqTV+vv/66amtrtWLFCtlsNv3qV7+64jbHjh1TWVmZ7Ha71q5dqyeffPLqirAQVz//+c+tJUuWWE899ZTl8XisBx54wFq2bJn19ttvh+3/wAMPWI899pjV09NjvfXWW9a+ffusJUuWWL29vQmuPL1Eu59mvP/++9batWut6upq64YbbkhMsWkuln31la98xdqwYYPV1dVlDQwMWP/7v/9rvfHGGwmsOv1Eu5+6u7utjIwM64c//KF1/vx5q7u72/r85z9vbd26NcGVp5+Ojg5r//791osvvmhJsl5++eXL9j9//rx1zTXXWA888IDl8Xisp556ylqyZIn1wgsvxFwDYSTOKioqrPr6+qC2z33uc9bevXsjfg232201NTUtdGmYI9b9VFdXZ/3Hf/yH1djYSBhJkGj31X/9139ZDofDGh0dTUR5+LNo99N//ud/WmvXrg1qe/zxx61Vq1bFrUaEiiSM/Pu//7v1uc99Lqjt/vvvt774xS/G/L5M08TRxYsXdfr0aVVXVwe1V1dX68SJExG9RiAQ0MTEhPLz8+NRIhT7fnrmmWd07tw5NTY2xrtE/Fks++qVV15ReXm5vve972nlypW6/vrr9eCDD+rjjz9ORMlpKZb9VFVVpXfeeUcdHR2yLEt/+tOf9MILL+j2229PRMmIwsmTJ0P27a233qpTp07pk08+iek1U+JGealqZGREfr9fRUVFQe1FRUUaGhqK6DW+//3v68MPP9Qdd9wRjxKh2PbTH/7wB+3du1fd3d3KyuLPKFFi2Vfnz5/X8ePHZbfb9fLLL2tkZETf/OY3NTY2xnkjcRLLfqqqqtLRo0dVV1enyclJXbp0SV/5ylf0ox/9KBElIwpDQ0Nh9+2lS5c0MjIip9MZ9WsyMpIANpst6LllWSFt4Tz//PP67ne/q7a2NhUWFsarPPxZpPvJ7/frzjvvVFNTk66//vpElYc5ovmbCgQCstlsOnr0qCoqKnTbbbfp4MGDevbZZxkdibNo9pPH49GuXbv0yCOP6PTp0+rs7NTAwIDq6+sTUSqiFG7fhmuPFId0cbR8+XJlZmaGHAkMDw+HpMpPa2tr044dO/TLX/5St9xySzzLTHvR7qeJiQmdOnVKfX19+va3vy1p+gvPsixlZWXp1Vdf1c0335yQ2tNNLH9TTqdTK1euDLqN+fr162VZlt555x199rOfjWvN6SiW/dTc3KyNGzfqoYcekiR94Qtf0LJly7Rp0yY9+uijMR1tIz6Ki4vD7tusrCwVFBTE9JqMjMRRdna2ysrK1NXVFdTe1dWlqqqqebd7/vnndc899+i5555jvjQBot1PeXl5+t3vfqczZ87MPurr6/V3f/d3OnPmjDZs2JCo0tNOLH9TGzdu1HvvvacPPvhgtu2tt95SRkaGVq1aFdd601Us++mjjz5SRkbwV1JmZqakvxx1IzlUVlaG7NtXX31V5eXlWrJkSWwvGvOpr4jIzOVthw4dsjwej7V7925r2bJl1h//+EfLsixr79691l133TXb/7nnnrOysrKsJ554wvJ6vbOP999/39RHSAvR7qdP42qaxIl2X01MTFirVq2yvvGNb1i///3vrWPHjlmf/exnrfvuu8/UR0gL0e6nZ555xsrKyrJaWlqsc+fOWcePH7fKy8utiooKUx8hbUxMTFh9fX1WX1+fJck6ePCg1dfXN3sZ9qf31cylvXv27LE8Ho916NAhLu1NBU888YS1Zs0aKzs72yotLbWOHTs2+7O7777b+tKXvjT7/Etf+pIlKeRx9913J77wNBPNfvo0wkhiRbuvzp49a91yyy3W0qVLrVWrVlkNDQ3WRx99lOCq00+0++nxxx+33G63tXTpUsvpdFr//M//bL3zzjsJrjr9/Pd///dlv3fC7av/+Z//sf7+7//eys7Otq677jqrtbX1qmqwWRbjXwAAwBzOGQEAAEYRRgAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABj1/wHh+OeqseAz5wAAAABJRU5ErkJggg==", + "image/png": "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", "text/plain": [ "
" ] @@ -1867,7 +1869,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 34, @@ -1908,27 +1910,27 @@ "clusterwith_prebuilt\n", "\n", "with_prebuilt: Workflow\n", - "\n", - "clusterwith_prebuiltOutputs\n", + "\n", + "clusterwith_prebuiltInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Inputs\n", "\n", - "\n", - "clusterwith_prebuiltInputs\n", + "\n", + "clusterwith_prebuiltOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Outputs\n", "\n", "\n", "\n", @@ -2097,7 +2099,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 35, @@ -2238,8 +2240,7 @@ " macro.engine = macro.create.atomistics.Lammps(structure=macro.structure, run_on_updates=False)\n", " macro.calc = macro.create.atomistics.CalcMin(job=macro.engine, pressure=0)\n", " \n", - " macro.engine.signals.input.run = macro.structure.signals.output.ran\n", - " macro.calc.signals.input.run = macro.engine.signals.output.ran\n", + " macro.structure > macro.engine > macro.calc\n", " \n", " macro.inputs_map = {\n", " \"structure__name\": \"element\", \n", @@ -2281,8 +2282,8 @@ " wf.min_phase2.outputs.energy,\n", ")\n", "\n", - "wf.min_phase1.signals.input.run = wf.element.signals.output.ran\n", - "wf.min_phase2.signals.input.run = wf.element.signals.output.ran\n", + "wf.element > wf.min_phase1\n", + "wf.element > wf.min_phase2\n", "# We stopped all the elements inside lammps_minimize from running on update\n", "# So we'll need to hit the macro with an explicit run command\n", "\n", @@ -2371,105 +2372,105 @@ "\n", "Outputs\n", "\n", + "\n", + "clusterphase_preferencecompare\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "compare: PerAtomEnergyDifference\n", + "\n", + "\n", + "clusterphase_preferencecompareInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterphase_preferencecompareOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", "\n", "clusterphase_preferencemin_phase1\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "min_phase1: LammpsMinimize\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", "\n", "clusterphase_preferencemin_phase2\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "min_phase2: LammpsMinimize\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", - "\n", - "clusterphase_preferencecompare\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "compare: PerAtomEnergyDifference\n", - "\n", - "\n", - "clusterphase_preferencecompareInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterphase_preferencecompareOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", "\n", "\n", "clusterphase_preferenceInputsrun\n", @@ -2981,21 +2982,21 @@ "\n", "\n", "clusterphase_preferencemin_phase1Outputsstructure\n", - "\n", - "structure\n", + "\n", + "structure\n", "\n", "\n", "\n", "clusterphase_preferencecompareInputsstructure1\n", - "\n", - "structure1\n", + "\n", + "structure1\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsstructure->clusterphase_preferencecompareInputsstructure1\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3026,21 +3027,21 @@ "\n", "\n", "clusterphase_preferencemin_phase1Outputsenergy_pot\n", - "\n", - "energy_pot\n", + "\n", + "energy_pot\n", "\n", "\n", "\n", "clusterphase_preferencecompareInputsenergy1\n", - "\n", - "energy1\n", + "\n", + "energy1\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsenergy_pot->clusterphase_preferencecompareInputsenergy1\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3182,21 +3183,21 @@ "\n", "\n", "clusterphase_preferencemin_phase2Outputsstructure\n", - "\n", - "structure\n", + "\n", + "structure\n", "\n", "\n", "\n", "clusterphase_preferencecompareInputsstructure2\n", - "\n", - "structure2\n", + "\n", + "structure2\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsstructure->clusterphase_preferencecompareInputsstructure2\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3227,21 +3228,21 @@ "\n", "\n", "clusterphase_preferencemin_phase2Outputsenergy_pot\n", - "\n", - "energy_pot\n", + "\n", + "energy_pot\n", "\n", "\n", "\n", "clusterphase_preferencecompareInputsenergy2\n", - "\n", - "energy2\n", + "\n", + "energy2\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsenergy_pot->clusterphase_preferencecompareInputsenergy2\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3403,7 +3404,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 41, @@ -4007,7 +4008,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 45, From 7fefabf067843c7620915f13d29a30c577600720 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 7 Sep 2023 12:39:53 -0700 Subject: [PATCH 603/756] Run on calls --- pyiron_contrib/workflow/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 32f710831..413abc6a0 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -323,7 +323,7 @@ def _batch_update_input(self, **kwargs): def __call__(self, **kwargs) -> None: self._batch_update_input(**kwargs) - return self.update() + return self.run() @property def color(self) -> str: From 88b448d4875cb9b60d5ed8e95b6a6bf3074730f5 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 7 Sep 2023 13:26:54 -0700 Subject: [PATCH 604/756] Never push NotData from OutputData to InputData --- pyiron_contrib/workflow/channels.py | 8 +++++--- tests/unit/workflow/test_channels.py | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index e6de5ae06..cd4149df9 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -266,7 +266,8 @@ def connect(self, *others: DataChannel) -> None: self.connections.append(other) other.connections.append(self) out, inp = self._figure_out_who_is_who(other) - inp.update(out.value) + if out.value is not NotData: + inp.update(out.value) else: if isinstance(other, DataChannel): warn( @@ -408,8 +409,9 @@ class OutputData(DataChannel): """ def _after_update(self) -> None: - for inp in self.connections: - inp.update(self.value) + if self.value is not NotData: + for inp in self.connections: + inp.update(self.value) class SignalChannel(Channel, ABC): diff --git a/tests/unit/workflow/test_channels.py b/tests/unit/workflow/test_channels.py index a366fd768..fec45d566 100644 --- a/tests/unit/workflow/test_channels.py +++ b/tests/unit/workflow/test_channels.py @@ -63,6 +63,31 @@ def test_connections(self): with self.subTest("Test iteration"): self.assertTrue(all([con in self.no.connections for con in self.no])) + with self.subTest("Don't push NotData"): + self.no.disconnect_all() + self.no.value = NotData + self.ni1.value = 1 + self.ni1.connect(self.no) + self.assertEqual( + self.ni1.value, + 1, + msg="NotData should not be getting pushed on connection" + ) + self.ni2.value = 2 + self.no.value = 3 + self.ni2.connect(self.no) + self.assertEqual( + self.ni2.value, + 3, + msg="Actual data should be getting pushed" + ) + self.no.update(NotData) + self.assertEqual( + self.ni2.value, + 3, + msg="NotData should not be getting pushed on updates" + ) + def test_connection_validity_tests(self): self.ni1.type_hint = int | float | bool # Override with a larger set self.ni2.type_hint = int # Override with a smaller set From 1bde273382bb0c2d5fdf2d8a51fc91a0b902dc1c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 7 Sep 2023 14:08:50 -0700 Subject: [PATCH 605/756] Rework the while loop meta node to keep the condition inside --- pyiron_contrib/workflow/meta.py | 34 ++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index 48d4945a8..950cdef4e 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -4,6 +4,8 @@ from __future__ import annotations +from typing import Optional + from pyiron_contrib.workflow.function import ( Function, SingleValue, @@ -132,6 +134,7 @@ def make_loop(macro): # Connect each body node input to the input interface's respective output for body_node, out in zip(body_nodes, interface.outputs): body_node.inputs[label] = out + interface > body_node macro.inputs_map[f"{interface.label}__l"] = interface.label # TODO: Don't hardcode __l # Or distribute the same input to each node equally @@ -141,6 +144,7 @@ def make_loop(macro): ) for body_node in body_nodes: body_node.inputs[label] = interface + interface > body_node macro.inputs_map[f"{interface.label}__user_input"] = interface.label # TODO: Don't hardcode __user_input @@ -154,6 +158,13 @@ def make_loop(macro): # Connect each body node output to the output interface's respective input for body_node, inp in zip(body_nodes, interface.inputs): inp.connect(body_node.outputs[label]) + if body_node.executor is not None: + raise NotImplementedError( + "Right now the output interface gets run after each body node," + "if the body nodes can run asynchronously we need something " + "more clever than that!" + ) + body_node > interface macro.outputs_map[ f"{interface.label}__{loop_body_class.__name__}__{label}" ] = interface.label @@ -164,6 +175,10 @@ def make_loop(macro): def while_loop( loop_body_class: type[Node], + condition_class: type[SingleValue], + internal_connection_map: dict[str, str], + inputs_map: Optional[dict[str, str]] = None, + outputs_map: Optional[dict[str, str]] = None, ) -> type[Macro]: """ An _extremely rough_ first draft of a for-loop meta-node. @@ -182,7 +197,11 @@ def while_loop( Args: loop_body_class (type[pyiron_contrib.workflow.node.Node]): The class for the body of the while-loop. - + condition_class (type[pyiron_contrib.workflow.function.SingleValue]): A single + value node returning a `bool` controlling the while loop exit condition + (exits on False) + internal_connection_map (dict[str, str]): String names of internal connections + between the body node outputs and condition node inputs. Examples: >>> import numpy as np >>> np.random.seed(0) # Just for docstring tests, so the output is predictable @@ -281,13 +300,18 @@ def while_loop( def make_loop(macro): body_node = macro.add(loop_body_class(label=loop_body_class.__name__)) - macro.create.standard.If(label="if_", run_on_updates=False) + condition_node = macro.add(condition_class(label=condition_class.__name__)) + switch = macro.create.standard.If(label="switch") + + switch.inputs.condition = condition_node + for body_channel, condition_channel in internal_connection_map.items(): + condition_node.inputs[condition_channel] = body_node.outputs[body_channel] - macro.if_.signals.output.true > body_node > macro.if_ # Loop until false + switch.signals.output.true > body_node > condition_node > switch macro.starting_nodes = [body_node] - # Just for convenience: - macro.inputs_map = {"if___condition": "condition"} + macro.inputs_map = {} if inputs_map is None else inputs_map + macro.outputs_map = {} if outputs_map is None else outputs_map return macro_node()(make_loop) From 26d8e8e2608c60ddd8a1a9a7f21613dc62cb9e9b Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 7 Sep 2023 14:33:09 -0700 Subject: [PATCH 606/756] Fix while meta node I'm not super happy with its current form, but this is anyhow intermediate as we make everything fully delayed --- pyiron_contrib/workflow/meta.py | 9 ++++--- tests/integration/test_workflow.py | 42 ++++++++++++++++++------------ 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index 950cdef4e..edbf83524 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -200,8 +200,9 @@ def while_loop( condition_class (type[pyiron_contrib.workflow.function.SingleValue]): A single value node returning a `bool` controlling the while loop exit condition (exits on False) - internal_connection_map (dict[str, str]): String names of internal connections - between the body node outputs and condition node inputs. + internal_connection_map (list[tuple[str, str, str, str]]): String tuples + giving (input node, input channel, output node, output channel) labels + connecting channel pairs inside the macro. Examples: >>> import numpy as np >>> np.random.seed(0) # Just for docstring tests, so the output is predictable @@ -304,8 +305,8 @@ def make_loop(macro): switch = macro.create.standard.If(label="switch") switch.inputs.condition = condition_node - for body_channel, condition_channel in internal_connection_map.items(): - condition_node.inputs[condition_channel] = body_node.outputs[body_channel] + for (out_n, out_c, in_n, in_c) in internal_connection_map: + macro.nodes[in_n].inputs[in_c] = macro.nodes[out_n].outputs[out_c] switch.signals.output.true > body_node > condition_node > switch macro.starting_nodes = [body_node] diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py index 99315aa5c..ba5640228 100644 --- a/tests/integration/test_workflow.py +++ b/tests/integration/test_workflow.py @@ -70,7 +70,7 @@ def numpy_sqrt(value=0): wf.gt_switch.signals.output.false > wf.rand > wf.gt_switch # Loop on false wf.gt_switch.signals.output.true > wf.sqrt # On true break to sqrt node - wf.rand.update() + wf.rand.run() self.assertAlmostEqual( np.sqrt(wf.rand.outputs.rand.value), wf.sqrt.outputs.sqrt.value, 6 ) @@ -118,7 +118,12 @@ def greater_than(x: float, threshold: float): # print(f"{x:.3f} {symbol} {threshold}") return gt - RandomWhile = Workflow.create.meta.while_loop(random) + RandomWhile = Workflow.create.meta.while_loop( + loop_body_class=random, + condition_class=greater_than, + internal_connection_map=[("Random", "random", "GreaterThan", "x")], + outputs_map={"Random__random": "capped_result"} + ) # Define workflow @@ -126,18 +131,16 @@ def greater_than(x: float, threshold: float): ## Wire together the while loop and its condition - wf.gt = greater_than() - wf.random_while = RandomWhile(condition=wf.gt) - wf.gt.inputs.x = wf.random_while.Random + wf.random_while = RandomWhile() wf.starting_nodes = [wf.random_while] ## Give convenient labels - wf.inputs_map = {"gt__threshold": "threshold"} - wf.outputs_map = {"random_while__Random__random": "capped_value"} + wf.inputs_map = {"random_while__GreaterThan__threshold": "threshold"} + wf.outputs_map = {"random_while__capped_result": "capped_result"} self.assertAlmostEqual( - wf(threshold=0.1).capped_value, + wf(threshold=0.1).capped_result, 0.07103605819788694, # For this reason we set the random seed ) @@ -151,21 +154,26 @@ def add(a, b): def less_than_ten(value): return value < 10 - AddWhile = Workflow.create.meta.while_loop(add) + AddWhile = Workflow.create.meta.while_loop( + loop_body_class=add, + condition_class=less_than_ten, + internal_connection_map=[ + ("Add", "a + b", "LessThanTen", "value"), + ("Add", "a + b", "Add", "a") + ], + inputs_map={"Add__a": "a", "Add__b": "b"}, + outputs_map={"Add__a + b": "total"} + ) wf = Workflow("do_while") - wf.lt10 = less_than_ten() - wf.add_while = AddWhile(condition=wf.lt10) - - wf.lt10.inputs.value = wf.add_while.Add - wf.add_while.Add.inputs.a = wf.add_while.Add + wf.add_while = AddWhile() wf.starting_nodes = [wf.add_while] wf.inputs_map = { - "add_while__Add__a": "a", - "add_while__Add__b": "b" + "add_while__a": "a", + "add_while__b": "b" } - wf.outputs_map = {"add_while__Add__a + b": "total"} + wf.outputs_map = {"add_while__total": "total"} out = wf(a=1, b=2) self.assertEqual(out.total, 11) From 0db4bad44abde69e1771cc127a80d76a54cd32d4 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 7 Sep 2023 14:54:29 -0700 Subject: [PATCH 607/756] Make everything slow by default, the remove Slow --- pyiron_contrib/workflow/function.py | 71 ++----------------- pyiron_contrib/workflow/interfaces.py | 4 -- .../workflow/node_library/atomistics.py | 8 +-- 3 files changed, 8 insertions(+), 75 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index f6f40fd1f..2687ad629 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -356,8 +356,8 @@ def __init__( node_function: callable, *args, label: Optional[str] = None, - run_on_updates: bool = True, - update_on_instantiation: bool = True, + run_on_updates: bool = False, + update_on_instantiation: bool = False, channels_requiring_update_after_run: Optional[list[str]] = None, parent: Optional[Composite] = None, output_labels: Optional[str | list[str] | tuple[str]] = None, @@ -617,42 +617,6 @@ def color(self) -> str: return SeabornColors.green -class Slow(Function): - """ - Like a regular node, but `run_on_updates` and `update_on_instantiation` default to - `False`. - This is intended for wrapping function which are potentially expensive to call, - where you don't want the output recomputed unless `run()` is _explicitly_ called. - """ - - def __init__( - self, - node_function: callable, - *args, - label: Optional[str] = None, - run_on_updates=False, - update_on_instantiation=False, - parent: Optional[Workflow] = None, - output_labels: Optional[str | list[str] | tuple[str]] = None, - **kwargs, - ): - super().__init__( - node_function, - *args, - label=label, - run_on_updates=run_on_updates, - update_on_instantiation=update_on_instantiation, - parent=parent, - output_labels=output_labels, - **kwargs, - ) - - @property - def color(self) -> str: - """For drawing the graph""" - return SeabornColors.red - - class SingleValue(Function, HasChannel): """ A node that _must_ return only a single value. @@ -668,8 +632,8 @@ def __init__( node_function: callable, *args, label: Optional[str] = None, - run_on_updates=True, - update_on_instantiation=True, + run_on_updates=False, + update_on_instantiation=False, parent: Optional[Workflow] = None, output_labels: Optional[str | list[str] | tuple[str]] = None, **kwargs, @@ -751,33 +715,6 @@ def as_node(node_function: callable): return as_node -def slow_node(**node_class_kwargs): - """ - A decorator for dynamically creating slow node classes from functions. - - Unlike normal nodes, slow nodes do update themselves on initialization and do not - run themselves when they get updated -- i.e. they will not run when their input - changes, `run()` must be explicitly called. - - Optionally takes any keyword arguments of `Slow`. - """ - - def as_slow_node(node_function: callable): - return type( - node_function.__name__.title().replace("_", ""), # fnc_name to CamelCase - (Slow,), # Define parentage - { - "__init__": partialmethod( - Slow.__init__, - node_function, - **node_class_kwargs, - ) - }, - ) - - return as_slow_node - - def single_value_node(**node_class_kwargs): """ A decorator for dynamically creating fast node classes from functions. diff --git a/pyiron_contrib/workflow/interfaces.py b/pyiron_contrib/workflow/interfaces.py index 3309d3497..e604588c2 100644 --- a/pyiron_contrib/workflow/interfaces.py +++ b/pyiron_contrib/workflow/interfaces.py @@ -12,10 +12,8 @@ from pyiron_contrib.workflow.function import ( Function, SingleValue, - Slow, function_node, single_value_node, - slow_node, ) if TYPE_CHECKING: @@ -34,7 +32,6 @@ def __init__(self): self.Function = Function self.SingleValue = SingleValue - self.Slow = Slow # Avoid circular imports by delaying import for children of Composite self._macro = None @@ -101,7 +98,6 @@ class Wrappers(metaclass=Singleton): def __init__(self): self.function_node = function_node self.single_value_node = single_value_node - self.slow_node = slow_node # Avoid circular imports by delaying import when wrapping children of Composite self._macro_node = None diff --git a/pyiron_contrib/workflow/node_library/atomistics.py b/pyiron_contrib/workflow/node_library/atomistics.py index 94dc6f281..0c8e8e530 100644 --- a/pyiron_contrib/workflow/node_library/atomistics.py +++ b/pyiron_contrib/workflow/node_library/atomistics.py @@ -7,7 +7,7 @@ from pyiron_atomistics.atomistics.structure.atoms import Atoms from pyiron_atomistics.lammps.lammps import Lammps as LammpsJob -from pyiron_contrib.workflow.function import single_value_node, slow_node +from pyiron_contrib.workflow.function import function_node, single_value_node Bulk = single_value_node(output_labels="structure")(_StructureFactory().bulk) @@ -79,7 +79,7 @@ def _run_and_remove_job(job, modifier: Optional[callable] = None, **modifier_kwa ) -@slow_node( +@function_node( output_labels=[ "cells", "displacements", @@ -103,7 +103,7 @@ def calc_static( return _run_and_remove_job(job=job) -@slow_node( +@function_node( output_labels=[ "cells", "displacements", @@ -150,7 +150,7 @@ def calc_md(job, n_ionic_steps, n_print, temperature, pressure): ) -@slow_node( +@function_node( output_labels=[ "cells", "displacements", From 0a4992fd13e69f0ec9bea6aa1591ee2166f2b16e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 7 Sep 2023 15:26:00 -0700 Subject: [PATCH 608/756] Don't let input channels run things automatically --- pyiron_contrib/workflow/channels.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index cd4149df9..e96479e25 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -379,7 +379,6 @@ def _before_update(self) -> None: def _after_update(self) -> None: self.waiting_for_update = False - self.node.update() def require_update_after_node_runs(self, wait_now=False) -> None: """ @@ -409,7 +408,7 @@ class OutputData(DataChannel): """ def _after_update(self) -> None: - if self.value is not NotData: + if self._value_is_data: for inp in self.connections: inp.update(self.value) From b9e2310abd81ba111540a289f00a51c265da8f7e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 7 Sep 2023 15:26:29 -0700 Subject: [PATCH 609/756] Remove update_on_instantiation and run_on_updates --- pyiron_contrib/workflow/composite.py | 5 +---- pyiron_contrib/workflow/function.py | 28 +--------------------------- pyiron_contrib/workflow/macro.py | 12 +++--------- pyiron_contrib/workflow/meta.py | 2 +- pyiron_contrib/workflow/node.py | 15 +++------------ pyiron_contrib/workflow/workflow.py | 4 ---- 6 files changed, 9 insertions(+), 57 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index bec0a6887..cf295316f 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -83,15 +83,12 @@ def __init__( label: str, *args, parent: Optional[Composite] = None, - run_on_updates: bool = True, strict_naming: bool = True, inputs_map: Optional[dict] = None, outputs_map: Optional[dict] = None, **kwargs, ): - super().__init__( - *args, label=label, parent=parent, run_on_updates=run_on_updates, **kwargs - ) + super().__init__(*args, label=label, parent=parent, **kwargs) self.strict_naming: bool = strict_naming self.inputs_map = inputs_map self.outputs_map = outputs_map diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 2687ad629..9815afd9a 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -69,8 +69,6 @@ class Function(Node): After a node is instantiated, its input can be updated as `*args` and/or `**kwargs` 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 @@ -79,10 +77,6 @@ class Function(Node): Args: node_function (callable): The function determining the behaviour of the node. label (str): The node's label. (Defaults to the node function's name.) - run_on_updates (bool): Whether to run when you are updated and all your - input is ready. (Default is True). - update_on_instantiation (bool): Whether to force an update at the end of - instantiation. (Default is True.) channels_requiring_update_after_run (list[str]): All the input channels named here will be set to `wait_for_update()` at the end of each node run, such that they are not `ready` again until they have had their `.update` method @@ -115,8 +109,7 @@ class Function(Node): fully_connected (bool): Every IO channel has at least one connection. Methods: - update: If `run_on_updates` is true and all your input is ready, will - run the engine. + update: If your input is ready, will run the engine. run: Parse and process the input, execute the engine, process the results and update the output. disconnect: Disconnect all data and signal IO connections. @@ -180,7 +173,6 @@ class Function(Node): >>> plus_minus_1 = Function( ... mwe, output_labels=("p1", "m1"), ... x=0, y=0, - ... run_on_updates=False, update_on_instantiation=False ... ) >>> plus_minus_1.outputs.p1.value @@ -276,15 +268,11 @@ class Function(Node): ... def __init__( ... self, ... label: Optional[str] = None, - ... run_on_updates: bool = True, - ... update_on_instantiation: bool = False, ... **kwargs ... ): ... super().__init__( ... self.alphabet_mod_three, ... label=label, - ... run_on_updates=run_on_updates, - ... update_on_instantiation=update_on_instantiation, ... **kwargs ... ) ... @@ -293,11 +281,6 @@ class Function(Node): ... letter = ["a", "b", "c"][i % 3] ... return letter - Note that we've overridden the default value for `update_on_instantiation` - above. - We can also provide different defaults for these flags as kwargs in the - decorator. - The second effectively does the same thing, but leverages python's `functools.partialmethod` to do so much more succinctly. In this example, note that the function is declared _before_ `__init__` is set, @@ -356,8 +339,6 @@ def __init__( node_function: callable, *args, label: Optional[str] = None, - run_on_updates: bool = False, - update_on_instantiation: bool = False, channels_requiring_update_after_run: Optional[list[str]] = None, parent: Optional[Composite] = None, output_labels: Optional[str | list[str] | tuple[str]] = None, @@ -366,7 +347,6 @@ def __init__( super().__init__( label=label if label is not None else node_function.__name__, parent=parent, - run_on_updates=run_on_updates, # **kwargs, ) @@ -388,8 +368,6 @@ def __init__( self._batch_update_input(*args, **kwargs) - if update_on_instantiation: - self.update() def _get_output_labels(self, output_labels: str | list[str] | tuple[str] | None): """ @@ -632,8 +610,6 @@ def __init__( node_function: callable, *args, label: Optional[str] = None, - run_on_updates=False, - update_on_instantiation=False, parent: Optional[Workflow] = None, output_labels: Optional[str | list[str] | tuple[str]] = None, **kwargs, @@ -642,8 +618,6 @@ def __init__( node_function, *args, label=label, - run_on_updates=run_on_updates, - update_on_instantiation=update_on_instantiation, parent=parent, output_labels=output_labels, **kwargs, diff --git a/pyiron_contrib/workflow/macro.py b/pyiron_contrib/workflow/macro.py index 5f43e8231..f9be95f3f 100644 --- a/pyiron_contrib/workflow/macro.py +++ b/pyiron_contrib/workflow/macro.py @@ -97,11 +97,11 @@ class Macro(Composite): running when they get their values updated, just so we can see that one of them is really not doing anything on the run command): >>> def modified_start_macro(macro): - ... macro.a = macro.create.SingleValue(add_one, x=0, run_on_updates=False) - ... macro.b = macro.create.SingleValue(add_one, x=0, run_on_updates=False) + ... macro.a = macro.create.SingleValue(add_one, x=0) + ... macro.b = macro.create.SingleValue(add_one, x=0) ... macro.starting_nodes = [macro.b] >>> - >>> m = Macro(modified_start_macro, update_on_instantiation=False) + >>> m = Macro(modified_start_macro) >>> m.outputs.to_value_dict() {'a__result': pyiron_contrib.workflow.channels.NotData, 'b__result': pyiron_contrib.workflow.channels.NotData} @@ -114,8 +114,6 @@ def __init__( self, graph_creator: callable[[Macro], None], label: Optional[str] = None, - run_on_updates: bool = True, - update_on_instantiation: bool = True, parent: Optional[Composite] = None, strict_naming: bool = True, inputs_map: Optional[dict] = None, @@ -126,7 +124,6 @@ def __init__( super().__init__( label=label if label is not None else graph_creator.__name__, parent=parent, - run_on_updates=run_on_updates, strict_naming=strict_naming, inputs_map=inputs_map, outputs_map=outputs_map, @@ -138,9 +135,6 @@ def __init__( self._batch_update_input(**kwargs) - if update_on_instantiation: - self.update() - @property def inputs(self) -> Inputs: return self._inputs diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index edbf83524..62295c976 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -266,7 +266,7 @@ def while_loop( we wind up using to initial value, but then the body node pushes elements of its own output back to its own input for future runs. E.g.) - >>> @Workflow.wrap_as.single_value_node(run_on_updates=False) + >>> @Workflow.wrap_as.single_value_node() >>> def add(a, b): ... print(f"Adding {a} + {b}") ... return a + b diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 413abc6a0..8833eb647 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -56,7 +56,7 @@ class Node(HasToDict, ABC): 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`. + returns this output if the node is `ready`. 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 @@ -100,8 +100,6 @@ class Node(HasToDict, ABC): owning this, if any. ready (bool): Whether the inputs are all ready and the node is neither already running nor already failed. - run_on_updates (bool): Whether to run when you are updated and all your input - is ready and your status does not prohibit running. (Default is False). running (bool): Whether the node has called `run` and has not yet received output from this call. (Default is False.) server (Optional[pyiron_base.jobs.job.extension.server.generic.Server]): A @@ -130,7 +128,6 @@ def __init__( label: str, *args, parent: Optional[Composite] = None, - run_on_updates: bool = False, **kwargs, ): """ @@ -141,8 +138,6 @@ def __init__( label (str): A name for this node. *args: Arguments passed on with `super`. **kwargs: Keyword arguments passed on with `super`. - - TODO: Shouldn't `update_on_instantiation` and `run_on_updates` both live here?? """ super().__init__(*args, **kwargs) self.label: str = label @@ -159,7 +154,6 @@ def __init__( # TODO: Provide support for actually computing stuff with the server/executor self.signals = self._build_signal_channels() self._working_directory = None - self.run_on_updates: bool = run_on_updates self.executor: None | CloudpickleProcessPoolExecutor = None self.future: None | Future = None @@ -258,7 +252,7 @@ def _build_signal_channels(self) -> Signals: return signals def update(self) -> Any | tuple | Future | None: - if self.run_on_updates and self.ready: + if self.ready: return self.run() @property @@ -308,7 +302,6 @@ def _batch_update_input(self, **kwargs): **kwargs: input label - input value (including channels for connection) pairs. """ - run_on_updates, self.run_on_updates = self.run_on_updates, False for k, v in kwargs.items(): if k in self.inputs.labels: self.inputs[k] = v @@ -316,10 +309,8 @@ def _batch_update_input(self, **kwargs): warnings.warn( f"The keyword '{k}' was not found among input labels. If you are " f"trying to update a node keyword, please use attribute assignment " - f"directly instead of calling, e.g. " - f"`my_node_instance.run_on_updates = False`." + f"directly instead of calling" ) - self.run_on_updates = run_on_updates # Restore provided value def __call__(self, **kwargs) -> None: self._batch_update_input(**kwargs) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 49e12a3ab..78916ecd3 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -120,8 +120,6 @@ class Workflow(Composite): >>> wf.engine = wf.create.atomistics.Lammps(structure=wf.structure) >>> wf.calc = wf.create.atomistics.CalcMd( ... job=wf.engine, - ... run_on_updates=True, - ... update_on_instantiation=True, ... ) >>> wf.plot = wf.create.standard.Scatter( ... x=wf.calc.outputs.steps, @@ -151,7 +149,6 @@ def __init__( self, label: str, *nodes: Node, - run_on_updates: bool = True, strict_naming: bool = True, inputs_map: Optional[dict] = None, outputs_map: Optional[dict] = None, @@ -159,7 +156,6 @@ def __init__( super().__init__( label=label, parent=None, - run_on_updates=run_on_updates, strict_naming=strict_naming, inputs_map=inputs_map, outputs_map=outputs_map, From e8867b6feddd2b41555f14e62733c99bf1308f88 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 7 Sep 2023 15:36:49 -0700 Subject: [PATCH 610/756] Remove the idea of channels "waiting to update" --- pyiron_contrib/workflow/channels.py | 40 ----------------------------- pyiron_contrib/workflow/function.py | 30 ---------------------- 2 files changed, 70 deletions(-) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index e96479e25..da010f1be 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -320,8 +320,6 @@ class InputData(DataChannel): On `update`, Input channels will then propagate their value along to their owning node by invoking its `update` method. - `InputData` channels may be set to `wait_for_update()`, and they are only `ready` - when they are not `waiting_for_update`. Their parent node can be told to always set them to wait for an update after the node runs using `require_update_after_node_runs()`. This allows nodes to complete the update of multiple channels before running again. @@ -348,27 +346,6 @@ def __init__( type_hint=type_hint, ) self.strict_connections = strict_connections - self.waiting_for_update = False - - def wait_for_update(self) -> None: - """ - Sets `waiting_for_update` to `True`, which prevents `ready` from returning - `True` until `update` is called. - """ - self.waiting_for_update = True - - @property - def ready(self) -> bool: - """ - Extends the parent class check for whether the value matches the type hint with - a check for whether the channel has been told to wait for an update (and not - been updated since then). - - Returns: - (bool): True when the stored value matches the type hint and the channel - has not been told to wait for an update. - """ - return not self.waiting_for_update and super().ready def _before_update(self) -> None: if self.node.running: @@ -377,23 +354,6 @@ def _before_update(self) -> None: f"cannot be updated." ) - def _after_update(self) -> None: - self.waiting_for_update = False - - def require_update_after_node_runs(self, wait_now=False) -> None: - """ - Registers this channel with its owning node as one that should have - `wait_for_update()` applied after each time the node runs. - - Args: - wait_now (bool): Also call `wait_for_update()` right now. (Default is - False.) - """ - if self.label not in self.node.channels_requiring_update_after_run: - self.node.channels_requiring_update_after_run.append(self.label) - if wait_now: - self.wait_for_update() - def activate_strict_connections(self) -> None: self.strict_connections = True diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 9815afd9a..e18223c4d 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -77,12 +77,6 @@ class Function(Node): Args: node_function (callable): The function determining the behaviour of the node. label (str): The node's label. (Defaults to the node function's name.) - channels_requiring_update_after_run (list[str]): All the input channels named - here will be set to `wait_for_update()` at the end of each node run, such - that they are not `ready` again until they have had their `.update` method - called. This can be used to create sets of input data _all_ of which must - be updated before the node is ready to produce output again. (Default is - None, which makes the list empty.) output_labels (Optional[str | list[str] | tuple[str]]): A name for each return value of the node function OR a single label. (Default is None, which scrapes output labels automatically from the source code of the wrapped @@ -339,7 +333,6 @@ def __init__( node_function: callable, *args, label: Optional[str] = None, - channels_requiring_update_after_run: Optional[list[str]] = None, parent: Optional[Composite] = None, output_labels: Optional[str | list[str] | tuple[str]] = None, **kwargs, @@ -358,17 +351,8 @@ def __init__( # TODO: Parse output labels from the node function in case output_labels is None self.signals = self._build_signal_channels() - - self.channels_requiring_update_after_run = ( - [] - if channels_requiring_update_after_run is None - else channels_requiring_update_after_run - ) - self._verify_that_channels_requiring_update_all_exist() - self._batch_update_input(*args, **kwargs) - def _get_output_labels(self, output_labels: str | list[str] | tuple[str] | None): """ If output labels are provided, turn convert them to a list if passed as a @@ -500,17 +484,6 @@ def _build_output_channels(self, *return_labels: str): return channels - def _verify_that_channels_requiring_update_all_exist(self): - if not all( - channel_name in self.inputs.labels - for channel_name in self.channels_requiring_update_after_run - ): - raise ValueError( - f"On or more channel name among those listed as requiring updates " - f"after the node runs ({self.channels_requiring_update_after_run}) was " - f"not found among the input channels ({self.inputs.labels})" - ) - @property def on_run(self): return self.node_function @@ -538,9 +511,6 @@ def process_run_result(self, function_output): so that the node can finishing "running" and push its data forward when that execution is finished. """ - for channel_name in self.channels_requiring_update_after_run: - self.inputs[channel_name].wait_for_update() - if len(self.outputs) == 0: return elif len(self.outputs) == 1: From 2a88452b758147c5e2ed1ef9218384340d04a5ca Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 7 Sep 2023 15:40:10 -0700 Subject: [PATCH 611/756] Wrap string --- pyiron_contrib/workflow/function.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index e18223c4d..3c1e23832 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -531,7 +531,9 @@ def _convert_input_args_and_kwargs_to_input_kwargs(self, *args, **kwargs): if len(set(positional_keywords).intersection(kwargs.keys())) > 0: raise ValueError( f"Cannot use {set(positional_keywords).intersection(kwargs.keys())} " - f"as both positional _and_ keyword arguments; args {args}, kwargs {kwargs}, reverse_keys {reverse_keys}, positional_keyworkds {positional_keywords}" + f"as both positional _and_ keyword arguments; args {args}, kwargs " + f"{kwargs}, reverse_keys {reverse_keys}, positional_keyworkds " + f"{positional_keywords}" ) for arg in args: From 8adce6b773f0d375d941cd0443665e5bc6e95afe Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 7 Sep 2023 15:46:11 -0700 Subject: [PATCH 612/756] Fix tests --- tests/integration/test_workflow.py | 8 +- tests/unit/workflow/test_channels.py | 8 - tests/unit/workflow/test_function.py | 283 ++++++----------------- tests/unit/workflow/test_macro.py | 15 +- tests/unit/workflow/test_node_package.py | 2 + tests/unit/workflow/test_workflow.py | 30 +-- 6 files changed, 91 insertions(+), 255 deletions(-) diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py index ba5640228..19acc530e 100644 --- a/tests/integration/test_workflow.py +++ b/tests/integration/test_workflow.py @@ -59,12 +59,12 @@ def numpy_sqrt(value=0): wf = Workflow("rand_until_big_then_sqrt") - wf.rand = numpy_randint(update_on_instantiation=False) + wf.rand = numpy_randint() - wf.gt_switch = GreaterThanLimitSwitch(run_on_updates=False) + wf.gt_switch = GreaterThanLimitSwitch() wf.gt_switch.inputs.value = wf.rand - wf.sqrt = numpy_sqrt(run_on_updates=False) + wf.sqrt = numpy_sqrt() wf.sqrt.inputs.value = wf.rand wf.gt_switch.signals.output.false > wf.rand > wf.gt_switch # Loop on false @@ -146,7 +146,7 @@ def greater_than(x: float, threshold: float): with self.subTest("Self-data-loop"): - @Workflow.wrap_as.single_value_node(run_on_updates=False) + @Workflow.wrap_as.single_value_node() def add(a, b): return a + b diff --git a/tests/unit/workflow/test_channels.py b/tests/unit/workflow/test_channels.py index fec45d566..48f8f5c10 100644 --- a/tests/unit/workflow/test_channels.py +++ b/tests/unit/workflow/test_channels.py @@ -142,14 +142,6 @@ def test_ready(self): self.ni1.value = 1 self.assertTrue(self.ni1.ready) - with self.subTest("Test the waiting mechanism"): - self.ni1.wait_for_update() - self.assertTrue(self.ni1.waiting_for_update) - self.assertFalse(self.ni1.ready) - self.ni1.update(2) - self.assertFalse(self.ni1.waiting_for_update) - self.assertTrue(self.ni1.ready) - self.ni1.value = "Not numeric at all" self.assertFalse(self.ni1.ready) diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 1223f501b..35c74d9a8 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -8,7 +8,7 @@ from pyiron_contrib.workflow.channels import NotData from pyiron_contrib.workflow.files import DirectoryObject from pyiron_contrib.workflow.function import ( - Slow, Function, SingleValue, function_node, single_value_node + Function, SingleValue, function_node, single_value_node ) @@ -48,22 +48,57 @@ def test_instantiation(self): self.assertEqual(len(void_node.outputs), 0) with self.subTest("Args and kwargs at initialization"): - node = Function(returns_multiple, 1, y=2) + node = Function(plus_one) + self.assertIs( + node.outputs.y.value, + NotData, + msg="Nodes should not run at instantiation", + ) + node.inputs.x = 10 + self.assertIs( + node.outputs.y.value, + NotData, + msg="Nodes should not run on input updates", + ) + node.run() self.assertEqual( - node.inputs.x.value, - 1, - msg="Should be able to set function input as args" + node.outputs.y.value, + 11, + msg=f"Slow nodes should still run when asked! Expected 11 but got " + f"{node.outputs.y.value}" + ) + + node = Function(no_default, 1, y=2, output_labels="output") + node.run() + self.assertEqual( + no_default(1, 2), + node.outputs.output.value, + msg="Nodes should allow input initialization by arg and kwarg" ) + node(2, y=3) + node.run() self.assertEqual( - node.inputs.y.value, - 2, - msg="Should be able to set function input as kwargs" + no_default(2, 3), + node.outputs.output.value, + msg="Nodes should allow input update on call by arg and kwarg" ) with self.assertRaises(ValueError): # Can't pass more args than the function takes Function(returns_multiple, 1, 2, 3) + with self.subTest("Initializing with connections"): + node = Function(plus_one, x=2) + node2 = Function(plus_one, x=node.outputs.y) + self.assertIs( + node2.inputs.x.connections[0], + node.outputs.y, + msg="Should be able to make a connection at initialization" + ) + node > node2 + node.run() + self.assertEqual(4, node2.outputs.y.value, msg="Initialize from connection") + def test_defaults(self): with_defaults = Function(plus_one) self.assertEqual( @@ -108,71 +143,6 @@ def test_label_choices(self): switch = Function(multiple_branches, output_labels="bool") self.assertListEqual(switch.outputs.labels, ["bool"]) - def test_instantiation_update(self): - no_update = Function( - plus_one, - run_on_updates=True, - update_on_instantiation=False - ) - self.assertIs( - no_update.outputs.y.value, - NotData, - msg=f"Expected the output to be in its initialized and not-updated NotData " - f"state, but got {no_update.outputs.y.value}" - ) - - update = Function( - plus_one, - run_on_updates=True, - update_on_instantiation=True - ) - self.assertEqual(2, update.outputs.y.value) - - default = Function(plus_one) - self.assertEqual( - 2, - default.outputs.y.value, - msg="Default behaviour should be to run on updates and update on " - "instantiation", - ) - - with self.assertRaises(TypeError): - run_without_value = Function(no_default) - run_without_value.run() - # None + None + 1 -> error - - with self.assertRaises(TypeError): - run_without_value = Function(no_default, x=1) - run_without_value.run() - # 1 + None + 1 -> error - - deferred_update = Function(no_default, x=1, y=1) - deferred_update.run() - self.assertEqual( - deferred_update.outputs["x + y + 1"].value, - 3, - msg="By default, all initial values should be parsed before triggering " - "an update" - ) - - def test_input_kwargs(self): - node = Function(plus_one, x=2) - self.assertEqual(3, node.outputs.y.value, msg="Initialize from value") - - node2 = Function(plus_one, x=node.outputs.y) - node.update() - self.assertEqual(4, node2.outputs.y.value, msg="Initialize from connection") - - def test_automatic_updates(self): - node = Function(throw_error, "no_return", update_on_instantiation=False) - - with self.subTest("Shouldn't run for invalid input on update"): - node.inputs.x.update("not an int") - - with self.subTest("Valid data should trigger a run"): - with self.assertRaises(RuntimeError): - node.inputs.x.update(1) - def test_signals(self): @function_node() def linear(x): @@ -184,8 +154,6 @@ def times_two(y): l = linear(x=1) t2 = times_two( - update_on_instantiation=False, - run_automatically=False, output_labels=["double"], y=l.outputs.x ) @@ -238,7 +206,7 @@ def times_two(y): ) def test_statuses(self): - n = Function(plus_one, run_on_updates=False) + n = Function(plus_one) self.assertTrue(n.ready) self.assertFalse(n.running) self.assertFalse(n.failed) @@ -259,13 +227,10 @@ def test_statuses(self): self.assertTrue(n.failed) n.inputs.x = 1 - n.update() self.assertFalse( n.ready, - msg="Update _checks_ for ready, so should still have failed status" + msg="Should not be ready while it has failed status" ) - # self.assertFalse(n.running) - self.assertTrue(n.failed) n.run() self.assertTrue( @@ -331,7 +296,7 @@ def with_messed_self(x: float, self) -> float: self.assertEqual(len(warning_list), 1) def test_call(self): - node = Function(no_default, output_labels="output", run_on_updates=False) + node = Function(no_default, output_labels="output") with self.subTest("Ensure desired failures occur"): with self.assertRaises(ValueError): @@ -345,29 +310,34 @@ def test_call(self): with self.subTest("Make sure data updates work as planned"): node(1, y=2) self.assertEqual( - node.inputs.x.value, 1, msg="__call__ should accept args to update input" + node.inputs.x.value, + 1, + msg="__call__ should accept args to update input" ) self.assertEqual( - node.inputs.y.value, 2, msg="__call__ should accept kwargs to update input" + node.inputs.y.value, + 2, + msg="__call__ should accept kwargs to update input" ) self.assertEqual( - node.outputs.output.value, NotData, msg="__call__ should not run things" + node.outputs.output.value, 1 + 2 + 1, msg="__call__ should run things" ) - node.run_on_updates = True + node(3) # Implicitly test partial update self.assertEqual( no_default(3, 2), node.outputs.output.value, - msg="__call__ should invoke update s.t. run gets called if run_on_updates" + msg="__call__ should allow updating only _some_ input before running" ) with self.subTest("Check that bad kwargs don't stop good ones"): with self.assertWarns(Warning): - node.run_on_updates = True - node(4, run_on_updates=False, y=5, foobar="not a kwarg of any sort") + original_label = node.label + node(4, label="won't get read", y=5, foobar="not a kwarg of any sort") - self.assertTrue( - node.run_on_updates, + self.assertEqual( + node.label, + original_label, msg="You should only be able to update input on a call, that's " "what the warning is for!" ) @@ -393,24 +363,7 @@ def test_return_value(self): 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" - ) + node.inputs.x = 2 return_on_explicit_run = node.run() self.assertEqual( return_on_explicit_run, @@ -421,14 +374,7 @@ def test_return_value(self): 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, @@ -442,59 +388,12 @@ def test_return_value(self): 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): - def test_instantiation(self): - slow = Slow(plus_one) - self.assertIs( - slow.outputs.y.value, - NotData, - msg="Slow nodes should not run at instantiation", - ) - slow.inputs.x = 10 - self.assertIs( - slow.outputs.y.value, - NotData, - msg="Slow nodes should not run on updates", - ) - slow.run() - self.assertEqual( - slow.outputs.y.value, - 11, - msg=f"Slow nodes should still run when asked! Expected 11 but got " - f"{slow.outputs.y.value}" - ) - - node = Slow(no_default, 1, y=2, output_labels="output") - node.run() - self.assertEqual( - no_default(1, 2), - node.outputs.output.value, - msg="Slow nodes should allow input initialization by arg and kwarg" - ) - node(2, y=3) - node.run() - self.assertEqual( - no_default(2, 3), - node.outputs.output.value, - msg="Slow nodes should allow input update on call by arg and kwarg" - ) - @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") class TestSingleValue(unittest.TestCase): def test_instantiation(self): node = SingleValue(no_default, 1, y=2, output_labels="output") + node.run() self.assertEqual( no_default(1, 2), node.outputs.output.value, @@ -520,6 +419,7 @@ def returns_foo() -> Foo: return Foo() svn = SingleValue(returns_foo, output_labels="foo") + svn.run() self.assertEqual( svn.some_attribute, @@ -551,6 +451,7 @@ def returns_foo() -> Foo: def test_repr(self): with self.subTest("Filled data"): svn = SingleValue(plus_one) + svn.run() self.assertEqual( svn.__repr__(), svn.outputs.y.value.__repr__(), msg="SingleValueNodes should have their output as their representation" @@ -567,6 +468,7 @@ def test_repr(self): def test_str(self): svn = SingleValue(plus_one) + svn.run() self.assertTrue( str(svn).endswith(str(svn.single_value)), msg="SingleValueNodes should have their output as a string in their string " @@ -586,7 +488,8 @@ def test_easy_output_connection(self): "output and another node's input by passing themselves" ) - regular.run() + svn > regular + svn.run() self.assertEqual( regular.outputs.y.value, 3, msg="SingleValue connections should pass data just like usual; in this " @@ -600,58 +503,6 @@ def test_easy_output_connection(self): "from assignment at instantiation" ) - def test_channels_requiring_update_after_run(self): - @single_value_node(output_labels="sum") - def my_node(x: int = 0, y: int = 0, z: int = 0): - return x + y + z - - n = my_node(channels_requiring_update_after_run=["x"]) - n.inputs.y.require_update_after_node_runs() - n.inputs.z.require_update_after_node_runs(wait_now=True) - - self.assertTrue( - n.inputs.x.waiting_for_update, - msg="Should have to wait because it was passed at init" - ) - self.assertFalse( - n.inputs.y.waiting_for_update, - msg="Should not have to wait, because the node has not run since it was set " - "as requiring updates after runs." - ) - self.assertTrue( - n.inputs.z.waiting_for_update, - msg="Should have to wait because it was told to wait now" - ) - - n.inputs.y.wait_for_update() - - n.inputs.x = 1 - self.assertFalse( - n.inputs.x.waiting_for_update, - msg="It got updated", - ) - self.assertTrue( - n.inputs.y.waiting_for_update and n.inputs.z.waiting_for_update, - msg="They did not get updated" - ) - self.assertFalse( - n.ready, - msg="Should still be waiting for y and z to get updated" - ) - - n.inputs.y = 2 - n.inputs.z = 3 - self.assertEqual( - n.outputs.sum.value, 6, - msg="Should have run after all inputs got updated" - ) - self.assertTrue( - n.inputs.x.waiting_for_update and - n.inputs.y.waiting_for_update and - n.inputs.z.waiting_for_update, - msg="After the run, all three should now be waiting for updates again" - ) - def test_working_directory(self): n_f = Function(plus_one) self.assertTrue(n_f._working_directory is None) diff --git a/tests/unit/workflow/test_macro.py b/tests/unit/workflow/test_macro.py index 29e53935d..696f7c4a0 100644 --- a/tests/unit/workflow/test_macro.py +++ b/tests/unit/workflow/test_macro.py @@ -18,6 +18,7 @@ def add_three_macro(macro): macro.add(SingleValue(add_one, macro.two, label="three")) # Cover a handful of addition methods, # although these are more thoroughly tested in Workflow tests + macro.one > macro.two > macro.three @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") @@ -71,6 +72,7 @@ def build_graph(self): x = 0 m = MyMacro(one__x=x) + m.run() self.assertEqual( m.outputs.three__result.value, add_one(add_one(add_one(x))), @@ -114,14 +116,15 @@ def nested_macro(macro): macro.a = SingleValue(add_one) macro.b = Macro(add_three_macro, one__x=macro.a) macro.c = SingleValue(add_one, x=macro.b.outputs.three__result) + macro.a > macro.b > macro.c m = Macro(nested_macro) self.assertEqual(m(a__x=0).c__result, 5) def test_upstream_detection(self): def my_macro(macro): - macro.a = SingleValue(add_one, x=0, run_on_updates=False) - macro.b = SingleValue(add_one, x=macro.a, run_on_updates=False) + macro.a = SingleValue(add_one, x=0) + macro.b = SingleValue(add_one, x=macro.a) m = Macro(my_macro) self.assertTrue( @@ -177,7 +180,7 @@ def my_macro(macro): ) def deep_macro(macro): - macro.a = SingleValue(add_one, x=0, run_on_updates=False) + macro.a = SingleValue(add_one, x=0) macro.m = Macro(my_macro) macro.m.inputs.a__x = macro.a @@ -194,11 +197,11 @@ def deep_macro(macro): def test_custom_start(self): def modified_start_macro(macro): - macro.a = SingleValue(add_one, x=0, run_on_updates=False) - macro.b = SingleValue(add_one, x=0, run_on_updates=False) + macro.a = SingleValue(add_one, x=0) + macro.b = SingleValue(add_one, x=0) macro.starting_nodes = [macro.b] - m = Macro(modified_start_macro, update_on_instantiation=False) + m = Macro(modified_start_macro) self.assertIs( m.outputs.a__result.value, NotData, diff --git a/tests/unit/workflow/test_node_package.py b/tests/unit/workflow/test_node_package.py index 82cf8e2cd..0ef4d0320 100644 --- a/tests/unit/workflow/test_node_package.py +++ b/tests/unit/workflow/test_node_package.py @@ -58,6 +58,8 @@ def dummy(x: int = 0): new_dummy_instance = self.package.Dummy(label="new_dummy_instance") + old_dummy_instance.run() + new_dummy_instance.run() self.assertEqual( old_dummy_instance.outputs.x.value, 0, msg="Should have old functionality" ) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index bc247c548..5d76bf93e 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -108,6 +108,7 @@ def test_workflow_io(self): wf.n3.inputs.x = wf.n2.outputs.y wf.n2.inputs.x = wf.n1.outputs.y + wf.n1 > wf.n2 > wf.n3 with self.subTest("Only unconnected channels should count"): self.assertEqual(len(wf.inputs), 1) @@ -128,7 +129,7 @@ def test_node_decorator_access(self): def plus_one(x: int = 0) -> int: return x + 1 - self.assertEqual(plus_one().outputs.y.value, 1) + self.assertEqual(plus_one().run(), 1) def test_working_directory(self): wf = Workflow("wf") @@ -165,7 +166,7 @@ def test_executor(self): def test_parallel_execution(self): wf = Workflow("wf") - @Workflow.wrap_as.single_value_node(run_on_updates=False) + @Workflow.wrap_as.single_value_node() def five(sleep_time=0.): sleep(sleep_time) five = 5 @@ -201,6 +202,7 @@ def sum(a, b): while wf.slow.future.running(): sleep(0.1) + wf.sum.run() self.assertEqual( wf.sum.outputs.sum.value, 5 + 5, @@ -219,6 +221,9 @@ def sum_(a, b): return a + b wf.sum = sum_(wf.a, wf.b) + wf.a > wf.b > wf.sum + wf.starting_nodes = [wf.a] + wf.run() self.assertEqual( wf.a.outputs.y.value + wf.b.outputs.y.value, wf.sum.outputs.sum.value, @@ -240,9 +245,9 @@ def sum_(a, b): def test_return_value(self): wf = Workflow("wf") - wf.run_on_updates = True wf.a = wf.create.SingleValue(plus_one) wf.b = wf.create.SingleValue(plus_one, x=wf.a) + wf.a > wf.b with self.subTest("Run on main process"): return_on_call = wf(a__x=1) @@ -253,24 +258,7 @@ def test_return_value(self): "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" - ) + wf.inputs.a__x = 2 return_on_explicit_run = wf.run() self.assertEqual( return_on_explicit_run["b__y"], From 777394ba9579eee316cbc59ae97e78acc0e30ce9 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 7 Sep 2023 15:46:17 -0700 Subject: [PATCH 613/756] Update channels docs --- pyiron_contrib/workflow/channels.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index da010f1be..6dfbc4089 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -2,9 +2,9 @@ Channels are access points for information to flow into and out of nodes. Data channels carry, unsurprisingly, data. -Input data channels force an update on their owning node when they are updated with new -data, and output data channels update all the input data channels to which they are -connected. +Output data channels will attempt to push their new value to all their connected input +data channels on update, while input data channels will reject any updates if their +parent node is running. In this way, data channels facilitate forward propagation of data through a graph. They hold data persistently. @@ -155,7 +155,8 @@ class DataChannel(Channel, ABC): They may optionally have a type hint. They have a `ready` attribute which tells whether their value matches their type hint (if one is provided, else `True`). - They may optionally have a storage priority (but this doesn't do anything yet). + (In the future they may optionally have a storage priority.) + (In the future they may optionally have a storage history limit.) (In the future they may optionally have an ontological type.) The `value` held by a channel can be manually assigned, but should normally be set @@ -317,12 +318,8 @@ def to_dict(self) -> dict: class InputData(DataChannel): """ - On `update`, Input channels will then propagate their value along to their owning - node by invoking its `update` method. - - Their parent node can be told to always set them to wait for an update after the - node runs using `require_update_after_node_runs()`. - This allows nodes to complete the update of multiple channels before running again. + On `update`, Input channels will only `update` if their parent node is not + `running`. The `strict_connections` parameter controls whether connections are subject to type checking requirements. @@ -363,8 +360,9 @@ def deactivate_strict_connections(self) -> None: class OutputData(DataChannel): """ - On `update`, Output channels propagate their value to all the input channels to - which they are connected by invoking their `update` method. + On `update`, Output channels propagate their value (as long as it's actually data) + to all the input channels to which they are connected by invoking their `update` + method. """ def _after_update(self) -> None: From 42d538d3253b61fef0609e24ab2e0dde58ab11d9 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 7 Sep 2023 16:07:47 -0700 Subject: [PATCH 614/756] Update node and function docs --- pyiron_contrib/workflow/function.py | 74 +++++++++++------------------ pyiron_contrib/workflow/node.py | 19 ++++---- 2 files changed, 40 insertions(+), 53 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 3c1e23832..ce7c2f4ab 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -59,10 +59,6 @@ class Function(Node): Further, functions with multiple return branches that return different types or numbers of return values may or may not work smoothly, depending on the details. - By default, function nodes will attempt to run whenever one or more inputs is - updated, and will attempt to update on initialization (after setting _all_ initial - input values). - Output is updated in the `process_run_result` inside the parent class `finish_run` call, such that output data gets pushed after the node stops running but before then `ran` signal fires: run, process and push result, ran. @@ -72,7 +68,7 @@ class Function(Node): `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. + the node is run, or, in the case of `update()`, `None` if it is not `ready` to run. Args: node_function (callable): The function determining the behaviour of the node. @@ -122,9 +118,7 @@ class Function(Node): There is no output because we haven't given our function any input, it has - no defaults, and we never ran it! It tried to `update()` on instantiation, but - the update never got to `run()` because the node could see that some its input - had never been specified. So outputs have the channel default value of + no defaults, and we never ran it! So outputs have the channel default value of `NotData` -- a special non-data class (since `None` is sometimes a meaningful value in python). @@ -132,27 +126,28 @@ class Function(Node): run: >>> plus_minus_1.inputs.x = 2 >>> plus_minus_1.run() - TypeError + TypeError: unsupported operand type(s) for -: 'type' and 'int' This is because the second input (`y`) still has no input value, so we can't do - the sum. + the sum between `NotData` and `2`. - Once we update `y`, all the input is ready and the automatic `update()` call - will be allowed to proceed to a `run()` call, which succeeds and updates the - output. + Once we update `y`, all the input is ready we will be allowed to proceed to a + `run()` call, which succeeds and updates the output. The final thing we need to do is disable the `failed` status we got from our last run call >>> plus_minus_1.failed = False >>> plus_minus_1.inputs.y = 3 + >>> plus_minus_1.run() >>> plus_minus_1.outputs.to_value_dict() {'x+1': 3, 'y-1': 2} We can also, optionally, provide initial values for some or all of the input and labels for the output: >>> plus_minus_1 = Function(mwe, output_labels=("p1", "m1"), x=1) - >>> plus_minus_1.inputs.y = 2 # Automatically triggers an update call now - >>> plus_minus_1.outputs.to_value_dict() - {'p1': 2, 'm1': 1} + >>> plus_minus_1.inputs.y = 2 + >>> out = plus_minus_1.run() + >>> out + (2, 1) Input data can be provided to both initialization and on call as ordered args or keyword kwargs. @@ -162,28 +157,6 @@ class Function(Node): >>> plus_minus_1(2, y=3) (3, 2) - Finally, we might stop these updates from happening automatically, even when - all the input data is present and available: - >>> plus_minus_1 = Function( - ... mwe, output_labels=("p1", "m1"), - ... x=0, y=0, - ... ) - >>> plus_minus_1.outputs.p1.value - - - With these flags set, the node requires us to manually call a run: - >>> plus_minus_1.run() - (-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. - However, we could still get them to raise an error by providing the _wrong_ - data: - >>> plus_minus_1 = Function(mwe, x=1, y="can't add to an int") - TypeError - - Here everything tries to run automatically, but we get an error from adding the - integer and string! We can make our node even more sensible by adding type hints (and, optionally, default values) when defining the function that the node wraps. @@ -197,7 +170,11 @@ class Function(Node): variety of common use cases. Note that getting "good" (i.e. dot-accessible) output labels can be achieved by using good variable names and returning those variables instead of using - `output_labels`: + `output_labels`. + If we force the node to `run()` (or call it) with bad types, it will raise an + error. + But, if we use the gentler `update()`, it will check types first and simply + return `None` if the input is not all `ready`. >>> from typing import Union >>> >>> def hinted_example( @@ -208,9 +185,10 @@ class Function(Node): ... return p1, m1 >>> >>> plus_minus_1 = Function(hinted_example, x="not an int") + >>> plus_minus_1.update() >>> plus_minus_1.outputs.to_value_dict() - {'p1': , 'm1': } + {'p1': , + 'm1': } Here, even though all the input has data, the node sees that some of it is the wrong type and so the automatic updates don't proceed all the way to a run. @@ -245,8 +223,8 @@ class Function(Node): ... return x+1, y-1 >>> >>> node_instance = my_mwe_node(x=0) - >>> node_instance.outputs.to_value_dict() - {'p1': 1, 'm1': 0} + >>> node_instance(y=0) + (1, -1) Where we've passed the output labels and class arguments to the decorator, and inital values to the newly-created node class (`my_mwe_node`) at @@ -296,18 +274,24 @@ class Function(Node): Finally, let's put it all together by using both of these nodes at once. Instead of setting input to a particular data value, we'll set it to be another node's output channel, thus forming a connection. + Then we need to define the corresponding execution flow, which can be done + by directly connecting `.signals.input.run` and `.signals.output.ran` channels + just like we connect data channels, but can also be accomplished with some + syntactic sugar using the `>` operator. When we update the upstream node, we'll see the result passed downstream: >>> adder = Adder() >>> alpha = AlphabetModThree(i=adder.outputs.sum) + >>> adder > alpha >>> - >>> adder.inputs.x = 1 + >>> adder(x=1) >>> print(alpha.outputs.letter) "b" - >>> adder.inputs.y = 1 + >>> adder(y=1) >>> print(alpha.outputs.letter) "c" >>> adder.inputs.x = 0 >>> adder.inputs.y = 0 + >>> adder() >>> print(alpha.outputs.letter) "a" diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 8833eb647..fb5e5f335 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -31,13 +31,14 @@ class Node(HasToDict, ABC): Nodes are elements of a computational graph. They have input and output data channels that interface with the outside world, and a callable that determines what they actually compute, and input and - output signal channels that can be used to customize the execution flow of the + output signal channels that can be used to customize the execution flow of their graph; - Together these channels represent edges on the computational graph. + Together these channels represent edges on the dual data and execution computational + graphs. Nodes can be run to force their computation, or more gently updated, which will - trigger a run only if the `run_on_update` flag is set to true and all of the input - is ready (i.e. channel values conform to any type hints provided). + trigger a run only if all of the input is ready (i.e. channel values conform to + any type hints provided). Nodes may have a `parent` node that owns them as part of a sub-graph. @@ -51,7 +52,7 @@ class Node(HasToDict, ABC): These signal connections can be made manually by reference to the node signals channel, or with the `>` symbol to indicate a flow of execution. This syntactic sugar can be mixed between actual signal channels (output signal > input signal), - or nodes, but when refering to nodes it is always a shortcut to the `run`/`ran` + or nodes, but when referring to nodes it is always a shortcut to the `run`/`ran` channels. The `run()` method returns a representation of the node output (possible a futures @@ -60,12 +61,12 @@ class Node(HasToDict, ABC): 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()`. + all supplied input and then calling `run()`. 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. + boolean flag attributes. Their value is controlled automatically in the defined `run` and `finish_run` methods. @@ -73,6 +74,8 @@ class Node(HasToDict, ABC): appropriate executor to their `executor` attribute. In case they are run with an executor, their `future` attribute will be populated with the resulting future object. + WARNING: Executors are currently only working when the node executable function does + not use `self`. This is an abstract class. Children *must* define how `inputs` and `outputs` are constructed, and what will @@ -296,7 +299,7 @@ def fully_connected(self): def _batch_update_input(self, **kwargs): """ - Temporarily disable running on updates to set all input values at once. + Match keywords to input channel labels and update input values. Args: **kwargs: input label - input value (including channels for connection) From 15ea7e68b0deb2b2faf00e55d138f794f0e25ef1 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 7 Sep 2023 16:27:13 -0700 Subject: [PATCH 615/756] Update composite, macro, and workflow docs --- pyiron_contrib/workflow/composite.py | 9 +++++---- pyiron_contrib/workflow/macro.py | 25 +++++++++++++------------ pyiron_contrib/workflow/workflow.py | 7 +++++++ 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index cf295316f..52f4e7ff6 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -43,9 +43,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. + The `run()` method (and `update()`, and calling the workflow) 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. @@ -57,13 +57,14 @@ class Composite(Node, ABC): existing node label will raise an error, otherwise the label gets appended with an index and the assignment proceeds. (Default is true: disallow assigning to existing labels.) - add (NodeAdder): A tool for adding new nodes to this subgraph. + create (Creator): A tool for adding new nodes to this subgraph. upstream_nodes (list[pyiron_contrib.workflow.node,Node]): All the owned nodes that have output connections but no input connections, i.e. the upstream-most nodes. starting_nodes (None | list[pyiron_contrib.workflow.node,Node]): A subset of the owned nodes to be used on running. (Default is None, running falls back on using the `upstream_nodes`.) + wrap_as (Wrappers): A tool for accessing node-creating decorators Methods: add(node: Node): Add the node instance to this subgraph. diff --git a/pyiron_contrib/workflow/macro.py b/pyiron_contrib/workflow/macro.py index f9be95f3f..234179489 100644 --- a/pyiron_contrib/workflow/macro.py +++ b/pyiron_contrib/workflow/macro.py @@ -42,6 +42,7 @@ class Macro(Composite): ... macro.one = macro.create.SingleValue(add_one) ... macro.two = macro.create.SingleValue(add_one, macro.one) ... macro.three = macro.create.SingleValue(add_one, macro.two) + ... macro.one > macro.two > macro.three We can make a macro by passing this graph-building function (that takes a macro as its first argument, i.e. `self` from the macro's perspective) to the `Macro` @@ -80,6 +81,7 @@ class Macro(Composite): ... macro.c = macro.create.SingleValue( ... add_one, x=macro.b.outputs.three__result ... ) + ... macro.a > macro.b > macro.c >>> >>> macro = Macro( ... nested_macro, @@ -89,25 +91,24 @@ class Macro(Composite): >>> macro(inp=1) {'intermediate': 5, 'out': 6} - Since the graph builder has access to the macro being instantiated, we can also - do things like override the starting nodes to be used when invoking a run. E.g. - consider this two-track graph, where we would normally run both nodes on a `run` - call (since they are both head-most nodes), but we override the default behavior - to only run _one_ of the two tracks (note that we stop the child nodes from - running when they get their values updated, just so we can see that one of them - is really not doing anything on the run command): + Macros and workflows automatically look for the upstream-most data nodes and use + those to start calculations when run. + Let's build a simple macro with two independent tracks: >>> def modified_start_macro(macro): ... macro.a = macro.create.SingleValue(add_one, x=0) ... macro.b = macro.create.SingleValue(add_one, x=0) - ... macro.starting_nodes = [macro.b] >>> >>> m = Macro(modified_start_macro) >>> m.outputs.to_value_dict() - {'a__result': pyiron_contrib.workflow.channels.NotData, - 'b__result': pyiron_contrib.workflow.channels.NotData} - >>> m(a__x=1, b__x=2) - {'a__result': pyiron_contrib.workflow.channels.NotData, 'b__result': 3} + {'a__result': 2, 'b__result': 3} + + We can override which nodes get used to start by specifying the `starting_nodes` + property. + Let's use this and then observe how the `a` sub-node no longer gets run: + >>> m.starting_nodes = [m.b] + >>> m(a__x=1000, b__x=2000) + {'a__result': 2, 'b__result': 2001} """ def __init__( diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 78916ecd3..0f35ab4f9 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -92,6 +92,11 @@ class Workflow(Composite): >>> print(len(wf.inputs), len(wf.outputs)) 1 1 + We can define the execution flow by making connections between channels held + in the `signals` panels, but it's easier to use the `>` syntactic sugar: + >>> wf.first > wf.second + >>> out = wf.run() + The workflow joins node lavels and channel labels with a `_` character to provide direct access to the output: >>> print(wf.outputs.second__y.value) @@ -125,6 +130,8 @@ class Workflow(Composite): ... x=wf.calc.outputs.steps, ... y=wf.calc.outputs.temperature ... ) + >>> + >>> wf.structure > wf.engine > wf.calc > wf.plot Workflows can be visualized in the notebook using graphviz: >>> wf.draw() From d02fbe7144227d1fd6cfe00007e54cd6bfe56683 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 7 Sep 2023 16:37:33 -0700 Subject: [PATCH 616/756] Update meta node docs --- pyiron_contrib/workflow/meta.py | 119 +++++++++++++++----------------- 1 file changed, 55 insertions(+), 64 deletions(-) diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index 62295c976..fc9c49dab 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -73,6 +73,9 @@ def for_loop( (i.e. the specified input and all output) is all caps Examples: + >>> import numpy as np + >>> from pyiron_contrib.workflow.meta import for_loop + >>> >>> bulk_loop = for_loop( ... Workflow.create.atomistics.Bulk, ... 5, @@ -183,16 +186,14 @@ def while_loop( """ An _extremely rough_ first draft of a for-loop meta-node. - Takes a node class and builds a macro that makes a cyclic signal connection between - that body node and an "if" node, i.e. when the body node finishes it runs the - if-node, and when the if-node finishes and evaluates `True` then it runs the body - node. - The if-node condition is exposed as input on the resulting macro with the label - "condition", but it is left to the user to connect... - - The condition to some output of another node, either an internal node of the body - node (if it's a macro) or any other node in the workflow - - The (sub)input of the body node to the (sub)output of the body node, so it - actually does something different at each iteration + Takes body and condition node classes and builds a macro that makes a cyclic signal + connection between them and an "if" switch, i.e. when the body node finishes it + runs the condtion, which runs the switch, and as long as the condition result was + `True`, the switch loops back to run the body again. + We additionally allow four-tuples of (input node, input channel, output node, + output channel) labels to wire data connections inside the macro, e.g. to pass data + from the body to the condition. This is beastly syntax, but it will suffice for now. + Finally, you can set input and output maps as normal. Args: loop_body_class (type[pyiron_contrib.workflow.node.Node]): The class for the @@ -204,6 +205,46 @@ def while_loop( giving (input node, input channel, output node, output channel) labels connecting channel pairs inside the macro. Examples: + >>> from pyiron_contrib.workflow import Workflow + >>> + >>> @Workflow.wrap_as.single_value_node() + >>> def add(a, b): + ... print(f"{a} + {b} = {a + b}") + ... return a + b + >>> + >>> @Workflow.wrap_as.single_value_node() + >>> def less_than_ten(value): + ... return value < 10 + >>> + >>> AddWhile = Workflow.create.meta.while_loop( + ... loop_body_class=add, + ... condition_class=less_than_ten, + ... internal_connection_map=[ + ... ("Add", "a + b", "LessThanTen", "value"), + ... ("Add", "a + b", "Add", "a") + ... ], + ... inputs_map={"Add__a": "a", "Add__b": "b"}, + ... outputs_map={"Add__a + b": "total"} + ... ) + >>> + >>> wf = Workflow("do_while") + >>> wf.add_while = AddWhile() + + >>> wf.starting_nodes = [wf.add_while] + >>> wf.inputs_map = { + ... "add_while__a": "a", + ... "add_while__b": "b" + ... } + >>> wf.outputs_map = {"add_while__total": "total"} + >>> + >>> print(f"Finally, {wf(a=1, b=2).total}") + 1 + 2 = 3 + 3 + 2 = 5 + 5 + 2 = 7 + 7 + 2 = 9 + 9 + 2 = 11 + Finally, 11 + >>> import numpy as np >>> np.random.seed(0) # Just for docstring tests, so the output is predictable >>> @@ -243,60 +284,10 @@ def while_loop( >>> # Set a threshold and run >>> >>> print(f"Finally {wf(threshold=0.1).capped_value:.3f}") - 0.549 > 0.1 - 0.715 > 0.1 - 0.603 > 0.1 - 0.545 > 0.1 - 0.424 > 0.1 - 0.646 > 0.1 - 0.438 > 0.1 - 0.892 > 0.1 - 0.964 > 0.1 - 0.383 > 0.1 - 0.792 > 0.1 - 0.529 > 0.1 - 0.568 > 0.1 - 0.926 > 0.1 - 0.071 <= 0.1 - Finally 0.071 - - We can also loop data _internally_ in the body node. - In such cases, we can still _initialize_ the data to some other value, because - this has no impact on the connections -- so for the first run of the body node - we wind up using to initial value, but then the body node pushes elements of its - own output back to its own input for future runs. - E.g.) - >>> @Workflow.wrap_as.single_value_node() - >>> def add(a, b): - ... print(f"Adding {a} + {b}") - ... return a + b - >>> - >>> @Workflow.wrap_as.single_value_node() - >>> def less_than_ten(value): - ... return value < 10 - >>> - >>> AddWhile = Workflow.create.meta.while_loop(add) - >>> - >>> wf = Workflow("do_while") - >>> wf.lt10 = less_than_ten() - >>> wf.add_while = AddWhile(condition=wf.lt10) - >>> - >>> wf.lt10.inputs.value = wf.add_while.Add - >>> wf.add_while.Add.inputs.a = wf.add_while.Add - >>> - >>> wf.starting_nodes = [wf.add_while] - >>> wf.inputs_map = { - ... "add_while__Add__a": "a", - ... "add_while__Add__b": "b" - ... } - >>> wf.outputs_map = {"add_while__Add__a + b": "total"} - >>> response = wf(a=1, b=2) - >>> print(response.total) - 11 - - Note that we needed to specify a starting node because in this case our - graph is cyclic and _all_ our nodes have connected input! We obviously cannot - automatically detect the "upstream-most" node in a circle! + + Note that we _need_ to specify a starting node whenever our graph is cyclic and + _all_ our nodes have connected input! We obviously cannot automatically detect + the "upstream-most" node in a circle! """ def make_loop(macro): From 1a512d399c49bb3b44337d5ccb1101446bd75fb1 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 8 Sep 2023 10:56:25 -0700 Subject: [PATCH 617/756] Refactor: rename method --- pyiron_contrib/workflow/function.py | 6 +++--- pyiron_contrib/workflow/macro.py | 2 +- pyiron_contrib/workflow/node.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index ce7c2f4ab..2d07dac40 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -335,7 +335,7 @@ def __init__( # TODO: Parse output labels from the node function in case output_labels is None self.signals = self._build_signal_channels() - self._batch_update_input(*args, **kwargs) + self.update_input(*args, **kwargs) def _get_output_labels(self, output_labels: str | list[str] | tuple[str] | None): """ @@ -526,9 +526,9 @@ def _convert_input_args_and_kwargs_to_input_kwargs(self, *args, **kwargs): return kwargs - def _batch_update_input(self, *args, **kwargs): + def update_input(self, *args, **kwargs): kwargs = self._convert_input_args_and_kwargs_to_input_kwargs(*args, **kwargs) - return super()._batch_update_input(**kwargs) + return super().update_input(**kwargs) def __call__(self, *args, **kwargs) -> None: kwargs = self._convert_input_args_and_kwargs_to_input_kwargs(*args, **kwargs) diff --git a/pyiron_contrib/workflow/macro.py b/pyiron_contrib/workflow/macro.py index 234179489..64cb93791 100644 --- a/pyiron_contrib/workflow/macro.py +++ b/pyiron_contrib/workflow/macro.py @@ -134,7 +134,7 @@ def __init__( self._inputs: Inputs = self._build_inputs() self._outputs: Outputs = self._build_outputs() - self._batch_update_input(**kwargs) + self.update_input(**kwargs) @property def inputs(self) -> Inputs: diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index fb5e5f335..6dd5a7005 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -297,7 +297,7 @@ def fully_connected(self): and self.signals.fully_connected ) - def _batch_update_input(self, **kwargs): + def update_input(self, **kwargs): """ Match keywords to input channel labels and update input values. @@ -316,7 +316,7 @@ def _batch_update_input(self, **kwargs): ) def __call__(self, **kwargs) -> None: - self._batch_update_input(**kwargs) + self.update_input(**kwargs) return self.run() @property From a71e1b15a7147148b564726427b1457fa56fb071 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 8 Sep 2023 10:58:10 -0700 Subject: [PATCH 618/756] Add docstring and return type hint --- pyiron_contrib/workflow/function.py | 11 ++++++++++- pyiron_contrib/workflow/node.py | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 2d07dac40..bcdff5092 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -526,7 +526,16 @@ def _convert_input_args_and_kwargs_to_input_kwargs(self, *args, **kwargs): return kwargs - def update_input(self, *args, **kwargs): + def update_input(self, *args, **kwargs) -> None: + """ + Match positional and keyword arguments to input channels and update input + values. + + Args: + *args: Interpreted in the same order as node function arguments. + **kwargs: input label - input value (including channels for connection) + pairs. + """ kwargs = self._convert_input_args_and_kwargs_to_input_kwargs(*args, **kwargs) return super().update_input(**kwargs) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 6dd5a7005..579c993d3 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -297,7 +297,7 @@ def fully_connected(self): and self.signals.fully_connected ) - def update_input(self, **kwargs): + def update_input(self, **kwargs) -> None: """ Match keywords to input channel labels and update input values. From d70d017cee79cc890ce48f059c2e0d7e425c8de5 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 8 Sep 2023 12:25:05 -0700 Subject: [PATCH 619/756] Update example notebook --- notebooks/workflow_example.ipynb | 3155 +++++++++++++++++------------- 1 file changed, 1743 insertions(+), 1412 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 9b1014594..ede7c9411 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -1,38 +1,5 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "8dee8129-6b23-4abf-90d2-217d71b8ba7a", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "2ecd6508f47745f192a550ae8b50ea13", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/huber/work/pyiron/pyiron_contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n", - " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" - ] - } - ], - "source": [ - "from pyiron_contrib.workflow.function import Function" - ] - }, { "cell_type": "markdown", "id": "5edfe456-c5b8-4347-a74f-1fb19fdff91b", @@ -63,6 +30,29 @@ "Input and output channels are _automatically_ extracted from the signature and return value(s) of the function. (Note: \"Nodized\" functions must have _at most_ one `return` expression!)" ] }, + { + "cell_type": "code", + "execution_count": 1, + "id": "8aca3b9b-9ba6-497a-ba9e-abdb15a6a5df", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "41d6241a9ab44c44aa1d958ba256debf", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from pyiron_contrib.workflow.function import Function" + ] + }, { "cell_type": "code", "execution_count": 2, @@ -143,7 +133,8 @@ "metadata": {}, "outputs": [], "source": [ - "# pm_node.run()" + "# pm_node.run()\n", + "pm_node.update()" ] }, { @@ -151,17 +142,32 @@ "id": "48b0db5a-548e-4195-8361-76763ddf0474", "metadata": {}, "source": [ - "By default, a softer `update()` call is made at instantiation and whenever the node input is updated.\n", - "This call checks to make sure the input is `ready` before moving on to `run()`. \n", - "\n", - "(Note: If you _do_ uncomment this cell and run it, not only will you get the expected error, but `pm_node` will also set its `failed` attribute to `True` -- this will prevent it from being `ready` again until you manually reset `pm_node.failed = False`.)\n", + "Using the softer `update()` call checks to make sure the input is `ready` before moving on to `run()`, avoiding this error. In this case, `update()` sees we have no input an aborts by returning `None`.\n", "\n", - "If we update the input, we'll give the node enough data to work with and it will automatically update the output:" + "(Note: If you _do_ swap `update()` to `run()` in this cell, not only will you get the expected error, but `pm_node` will also set its `failed` attribute to `True` -- this will prevent it from being `ready` again until you manually reset `pm_node.failed = False`.)" ] }, { "cell_type": "code", "execution_count": 6, + "id": "b6c00a4e-0c39-4283-ac00-53c3d07f7f10", + "metadata": {}, + "outputs": [], + "source": [ + "# pm_node.failed = False" + ] + }, + { + "cell_type": "markdown", + "id": "84af4b04-79b4-4944-a4c9-131af915d254", + "metadata": {}, + "source": [ + "If we update the input, we'll give the node enough data to work with:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, "id": "b1500a40-f4f2-4c06-ad78-aaebcf3e9a50", "metadata": {}, "outputs": [ @@ -175,75 +181,73 @@ ], "source": [ "pm_node.inputs.x = 5\n", + "pm_node.run()\n", "print(pm_node.outputs.to_value_dict())" ] }, { "cell_type": "markdown", - "id": "df4520d7-856e-4bc8-817f-5b2e22c1ddce", + "id": "c54a691e-a075-4d41-bc0f-3a990857a27a", "metadata": {}, "source": [ - "We can be stricter and force the node to wait for an explicit `run()` call by modifying the `run_on_updates` and `update_on_instantiation` flags. \n", - "\n", - "Let's also take the opportunity to give our output channel a better name so we can get it by dot-access." + "Alternatively, the `run()` command (and `update()` when it proceeds to execution) just return the function's return value:" ] }, { "cell_type": "code", - "execution_count": 7, - "id": "ab1ac28a-6e69-491f-882f-da4a43162dd7", + "execution_count": 8, + "id": "e845843c-61f4-4e5c-ac1a-d005787c2841", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "pyiron_contrib.workflow.channels.NotData" + "(6, 4)" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "def adder(x: int, y: int = 1) -> int:\n", - " sum_ = x + y\n", - " return sum_\n", - "\n", - "adder_node = Function(adder, run_on_updates=False)\n", - "adder_node.inputs.x = 1\n", - "adder_node.outputs.sum_.value # We use `value` to see the data the channel holds" + "out = pm_node.run()\n", + "out" ] }, { "cell_type": "markdown", - "id": "0929f222-6073-4201-b5a1-723c31c8998a", + "id": "df4520d7-856e-4bc8-817f-5b2e22c1ddce", "metadata": {}, "source": [ - "We see that now the output did not get populated automatically when we updated `x`. \n", - "We can still get the output by asking for it though:" + "We can give our function defaults so that it's ready to go from the beginning. Let's also take the opportunity to give our output channel a better name so we can get it by dot-access." ] }, { "cell_type": "code", - "execution_count": 8, - "id": "dc41a447-15fd-4df2-b60a-0935d81d469e", + "execution_count": 9, + "id": "ab1ac28a-6e69-491f-882f-da4a43162dd7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "2" + "1" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ + "def adder(x: int = 0, y: int = 1) -> int:\n", + " sum_ = x + y\n", + " return sum_\n", + "\n", + "adder_node = Function(adder)\n", "adder_node.run()\n", - "adder_node.outputs.sum_.value" + "adder_node.outputs.sum_.value # We use `value` to see the data the channel holds" ] }, { @@ -251,31 +255,19 @@ "id": "58ed9b25-6dde-488d-9582-d49d405793c6", "metadata": {}, "source": [ - "This node also exploits type hinting!\n", - "After turning the automatic updates back on, we can see that we can safely pass incorrect data without running into an error:" + "This node also exploits type hinting! `run()` will always force the execution, but `update()` will not only check if the data is there, but also if it is the right type:" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "ac0fe993-6c82-48c8-a780-cbd0c97fc386", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(int, str)" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "adder_node.run_on_updates = True\n", "adder_node.inputs.x = \"not an integer\"\n", "adder_node.inputs.x.type_hint, type(adder_node.inputs.x.value)\n", + "adder_node.update()\n", "# No error because the update doesn't trigger a run since the type hint is not satisfied" ] }, @@ -284,22 +276,22 @@ "id": "2737de39-6e75-44e1-b751-6315afe5c676", "metadata": {}, "source": [ - "But `run()` never got called, so the output is unchanged" + "Since the execution never happened, the output is unchanged" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "bcbd17f1-a3e4-44f0-bde1-cbddc51c5d73", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "2" + "1" ] }, - "execution_count": 10, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -318,7 +310,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "15742a49-4c23-4d4a-84d9-9bf19677544c", "metadata": {}, "outputs": [ @@ -328,14 +320,14 @@ "3" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "adder_node.inputs.x.update(2)\n", - "adder_node.outputs.sum_.value" + "adder_node.update()" ] }, { @@ -348,7 +340,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "id": "0c8f09a7-67c4-4c6c-a021-e3fea1a16576", "metadata": {}, "outputs": [ @@ -358,14 +350,14 @@ "30" ] }, - "execution_count": 12, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "adder_node(10, y=20)\n", - "adder_node.outputs.sum_.value" + "adder_node.run()" ] }, { @@ -373,12 +365,12 @@ "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:" + "Finally, we can update input and then `run` together by calling an already-instantiated node. Just like at node instantiation, the input for `Function` nodes can be set by positional and/or keyword argument. Here we'll use two positional args:" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "id": "69b59737-9e09-4b4b-a0e2-76a09de02c08", "metadata": {}, "outputs": [ @@ -388,7 +380,7 @@ "31" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -414,14 +406,14 @@ "\n", "If we're going to use a node many times, we may want to define a new sub-class of `Function` to handle this.\n", "\n", - "The can be done directly by inheriting from `Function` and overriding it's `__init__` function so that the core functionality of the node (i.e. the node function and output labels) are set in stone, but even easier is to use the `function_node` decorator to do this for you!\n", + "The can be done directly by inheriting from `Function` and overriding it's `__init__` function so that the core functionality of the node (i.e. the node function and output labels) are set in stone, but even easier is to use the `function_node` decorator to do this for you! \n", "\n", - "The decorator takes the output labels and whatever other class kwargs you want to override, and the function is defined like any other node function:" + "The decorator also lets us explicitly choose the names of our output channels by passing the `output_labels` argument to the decorator -- as a string to create a single channel for the returned values, or as a list of strings equal to the number of returned values in a returned tuple." ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "id": "61b43a9b-8dad-48b7-9194-2045e465793b", "metadata": {}, "outputs": [], @@ -431,7 +423,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "647360a9-c971-4272-995c-aa01e5f5bb83", "metadata": {}, "outputs": [ @@ -446,113 +438,122 @@ } ], "source": [ - "@function_node()\n", + "@function_node(output_labels=\"diff\")\n", "def subtract_node(x: int | float = 2, y: int | float = 1) -> int | float:\n", - " diff = x - y\n", - " return diff\n", + " return x - y\n", "\n", "sn = subtract_node()\n", "print(\"class name =\", sn.__class__.__name__)\n", "print(\"label =\", sn.label)\n", + "\n", + "sn() # Runs without updating input data, but we have defaults so that's fine\n", "print(\"default output =\", sn.outputs.diff.value)" ] }, { "cell_type": "markdown", - "id": "7b7133ff-6ef6-49fa-9eb0-7c280de9b1e5", + "id": "9b9220b0-833d-4c6a-9929-5dfa60a47d14", "metadata": {}, "source": [ - "Earlier we saw that we could set input data by function defaults, or by directly setting the `.inputs.*` channel values with an `=` assignement or `.update` method call.\n", + "# Connecting nodes and controlling flow\n", + "\n", + "Multiple nodes can be used together to build a computational graph, with each node performing a particular operation in the overall workflow:\n", "\n", - "We can also set the input of a node instance at instantiation by passing the input labels as kwargs!" + "The input and output of nodes can be chained together by connecting their data channels. When a node runs, its output channels will push their new value to each input node to whom they are connected. In this way, data propagates forwards\n", + "\n", + "In addition to input and output data channels, nodes also have \"signal\" channels available. Input signals are bound to a callback function (typically one of its node's methods), and output signals trigger the callbacks for all the input signal channels they're connected to.\n", + "\n", + "Standard nodes have a `run` input signal (which is, unsurprisingly, bound to the `run` method), and a `ran` output signal (which, again, hopefully with no great surprise, is triggered at the end of the `run` method.)\n", + "\n", + "In the example below we see how this works for a super-simple toy graph:" ] }, { "cell_type": "code", - "execution_count": 16, - "id": "8fb0671b-045a-4d71-9d35-f0beadc9cf3a", + "execution_count": 17, + "id": "2e418abf-7059-4e1e-9b9f-b3dc0a4b5e35", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "-10" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "1 2\n" + ] } ], "source": [ - "subtract_node(10, 20).outputs.diff.value" + "@function_node()\n", + "def linear(x):\n", + " return x\n", + "\n", + "@function_node(output_labels=\"double\")\n", + "def times_two(x):\n", + " return 2 * x\n", + "\n", + "l = linear()\n", + "t2 = times_two()\n", + "\n", + "l.inputs.x = 1\n", + "t2.inputs.x = l.outputs.x\n", + "t2.signals.input.run = l.signals.output.ran\n", + "\n", + "l.run()\n", + "print(t2.inputs.x, t2.outputs.double)" ] }, { "cell_type": "markdown", - "id": "af45e7a1-0a8d-4ad6-9363-ed2bcfe4c405", + "id": "5da1ecfc-7145-4fb2-b5c0-417f050c5de4", "metadata": {}, "source": [ - "## Node connections \n", + "We can use a couple pieces of syntactic sugar to make this faster.\n", "\n", - "Next, we want to make connections between nodes. Instead of `update` we can use the `connect` method to accomplish this, or we can do it with the other syntactic sugar we saw for regular data (i.e. direct attribute access or using kwargs at instantiation):" + "First: data connections can be made with keyword arguments just like other input data definitions.\n", + "\n", + "Second: the `>` is a shortcut for creating connections between the left-hand node's `signals.output.ran` channel and the right-hand node's `signals.input.run` channel.\n", + "\n", + "With both of these together, we can write:" ] }, { "cell_type": "code", - "execution_count": 17, - "id": "5ce91f42-7aec-492c-94fb-2320c971cd79", + "execution_count": 18, + "id": "59c29856-c77e-48a1-9f17-15d4c58be588", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "2 - 4 = -2\n" + "10 20\n" ] } ], "source": [ - "@function_node()\n", - "def add_node(x: int | float = 1, y: int | float = 1) -> int | float:\n", - " sum_ = x + y\n", - " return sum_\n", - "\n", - "add1 = add_node()\n", - "add2 = add_node(2, 2)\n", - "sub = subtract_node(x=add1.outputs.sum_, y=add2.outputs.sum_)\n", - "print(\n", - " f\"{add1.outputs.sum_.value} - {add2.outputs.sum_.value} = {sub.outputs.diff.value}\"\n", - ")" + "l = linear(x=10)\n", + "t2 = times_two(x=l.outputs.x)\n", + "l > t2\n", + "l.run()\n", + "print(t2.inputs.x, t2.outputs.double)" ] }, { "cell_type": "markdown", - "id": "0bea37ae-65f6-4842-b507-9542f17791ca", + "id": "93bb53d9-7044-4269-b532-3a50d6ebd656", "metadata": {}, "source": [ - "Because we've set all of our nodes to run automatically on updates, we can change upstream data and see the result propogate downstream immediately:" + "We can also chain together the signal fl" ] }, { "cell_type": "code", - "execution_count": 18, - "id": "20360fe7-b422-4d78-9bd1-de233f28c8df", + "execution_count": 19, + "id": "e367bc34-da37-4c31-97dd-d581c30952ca", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "11 - 4 = 7\n" - ] - } - ], + "outputs": [], "source": [ - "add1.inputs.x = 10\n", - "print(\n", - " f\"{add1.outputs.sum_.value} - {add2.outputs.sum_.value} = {sub.outputs.diff.value}\"\n", - ")" + "import matplotlib.pyplot as plt" ] }, { @@ -560,28 +561,30 @@ "id": "e5c531a3-77e4-48ad-a189-fed619e79baa", "metadata": {}, "source": [ - "## Special nodes\n", + "## Single Value nodes\n", "\n", - "In addition to the basic `Function` class, for the sake of convenience we also offer `Slow(Function)` -- which changes the defaults of `run_on_updates` and `update_on_instantiation` to `False` so that `run()` calls are necessary -- this can be helpful for nodes that are computationally expensive; and `SingleValue(Function)` -- which enforces that there is only a _single_ return value to the node function (i.e. a single output label), and then lets attribute and item access fall back to looking for attributes and items of this single output value. Of course there are decorators available for both of these.\n", + "Many functions return just a single value. In this case, we can take advantage of the `SingleValue` node class which employs a bunch of syntactic tricks to make our lives easier.\n", + "\n", + "The main difference between this and it's parent the `Function` class is that attribute and item access fall back to looking for attributes and items of this single output value.\n", "\n", "Let's look at a use case:" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "1a4e9693-0980-4435-aecc-3331d8b608dd", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", - "from pyiron_contrib.workflow.function import single_value_node" + "from pyiron_contrib.workflow.function import SingleValue" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "id": "7c4d314b-33bb-4a67-bfb9-ed77fba3949c", "metadata": {}, "outputs": [ @@ -596,14 +599,14 @@ } ], "source": [ - "@single_value_node()\n", "def linspace_node(\n", " start: int | float = 0, stop: int | float = 1, num: int = 50\n", "):\n", " linspace = np.linspace(start=start, stop=stop, num=num)\n", " return linspace\n", "\n", - "lin = linspace_node()\n", + "lin = SingleValue(linspace_node)\n", + "lin.run()\n", "\n", "print(type(lin.outputs.linspace.value)) # Output is just what we expect\n", "print(lin[1:4]) # Gets items from the output\n", @@ -612,132 +615,58 @@ }, { "cell_type": "markdown", - "id": "9b9220b0-833d-4c6a-9929-5dfa60a47d14", + "id": "eef23cb0-6192-4fe6-b9cc-007e261e347a", "metadata": {}, "source": [ - "# Flow control\n", - "\n", - "By default, when a node runs and updates its output, this triggers outputs in all downstream connections. This is useful when all your node functions are small and light, but there may come times when you want something other than this simple \"push\" flow.\n", - "\n", - "In addition to input and output data channels, nodes also have \"signal\" channels available. Input signals are bound to a callback function (typically one of its node's methods), and output signals trigger the callbacks for all the input signal channels they're connected to.\n", - "\n", - "Standard nodes have a `run` input signal (which is, unsurprisingly, bound to the `run` method), and a `ran` output signal (which, again, hopefully with no great surprise, is triggered at the end of the `run` method.)\n", - "\n", - "Below is a super simple example of how these signal channels can be used to delay execution and manually control flow:" + "The other advantage is that single value nodes can also be connected directly to input, since there is only one possible data connection. Of course it has a construction decorator just like `Function`, so let's replace `@function_node` with `@single_value_node` in one of our examples above to see how it tightens up the syntax a bit:" ] }, { "cell_type": "code", - "execution_count": 21, - "id": "2e418abf-7059-4e1e-9b9f-b3dc0a4b5e35", + "execution_count": 22, + "id": "61ae572f-197b-4a60-8d3e-e19c1b9cc6e2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "1 \n" + "times_two (TimesTwo) output single-value: 4\n" ] } ], "source": [ - "@function_node()\n", + "from pyiron_contrib.workflow.function import single_value_node\n", + "\n", + "@single_value_node()\n", "def linear(x):\n", " return x\n", "\n", - "@function_node(run_on_updates=False)\n", + "@single_value_node(output_labels=\"double\")\n", "def times_two(x):\n", - " double = 2 * x\n", - " return double\n", - "\n", - "l = linear(x=1)\n", - "t2 = times_two(x=l.outputs.x)\n", - "print(t2.inputs.x, t2.outputs.double)" - ] - }, - { - "cell_type": "markdown", - "id": "37aa4455-9b98-4be5-a365-363e3c490bb6", - "metadata": {}, - "source": [ - "Now the input of `t2` got updated when the connection is made, but by we told this node not to do any automatic updates, so the output has its uninitialized value of `NotData`.\n", - "\n", - "Often, you will want to have nodes with data connections to have signal connections, but this is not strictly required. Here, we'll introduce a (not strictly necessary) third node to control starting the workflow, and chain together to signals from our two functional nodes.\n", + " return 2 * x\n", "\n", - "Note that we have all the same syntacic sugar from data channels when creating connections between signal channels. We also have special syntactic sugar just for signals: `>`. This operator takes an output signal (or node) on the left-hand-side and an input signal (or node) on the right hand side and creates a connection between them. In the case that a node is used, this is just a shortcut to the `run`/`ran` channel depending whether an input/output signal is needed. These expressions can be chained together." + "l = linear(x=2)\n", + "t2 = times_two(x=l) # Just takes the whole `l` node!\n", + "l > t2\n", + "l.run()\n", + "print(t2)" ] }, { "cell_type": "code", - "execution_count": 22, - "id": "3310eac4-04f6-421b-9824-19bb2d680be6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2\n" - ] - } - ], - "source": [ - "@function_node()\n", - "def control():\n", - " return\n", - "\n", - "c = control()\n", - "c > l > t2 # Execution flow\n", - "# The above expression is equivalent to these two lines:\n", - "# l.signals.input.run = c.signals.output.ran\n", - "# t2.signals.input.run = l.signals.output.ran\n", - "c.run()\n", - "print(t2.outputs.double.value)" - ] - }, - { - "cell_type": "markdown", - "id": "003ed16e-c493-4465-9f08-492f9c51f764", - "metadata": {}, - "source": [ - "`Function` and its children always push out data updates _before_ triggering their `ran` signal." - ] - }, - { - "cell_type": "markdown", - "id": "9ac89662-a34f-4209-a657-dc54b6cc5317", - "metadata": {}, - "source": [ - "It is also possible to flag certain inputs channels so they require updates after each time their node is run before they are `ready` again. These can be defined either at the node instantiation, or after the fact using a method on the channel. Both approaches are shown in the example below:" - ] - }, - { - "cell_type": "code", - "execution_count": 23, + "execution_count": null, "id": "7a6f2bce-6b5e-4321-9457-0a6790d2202a", "metadata": {}, "outputs": [], - "source": [ - "import matplotlib.pyplot as plt" - ] + "source": [] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "id": "6569014a-815b-46dd-8b47-4e1cd4584b3b", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "@single_value_node()\n", "def noise(length: int = 1):\n", @@ -751,12 +680,7 @@ "\n", "x = noise(length=10)\n", "y = noise(length=10)\n", - "f = plot(\n", - " x=x, \n", - " y=y,\n", - " channels_requiring_update_after_run=[\"x\"],\n", - ")\n", - "f.inputs.y.require_update_after_node_runs(wait_now=True)" + "f = plot(x=x, y=y)" ] }, { @@ -769,7 +693,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 24, "id": "25f0495a-e85f-43b7-8a70-a2c9cbd51ebb", "metadata": {}, "outputs": [ @@ -779,7 +703,7 @@ "(False, False)" ] }, - "execution_count": 25, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -790,17 +714,17 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 25, "id": "449ce797-be62-4211-b483-c717a3d70583", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "(True, False)" + "(False, False)" ] }, - "execution_count": 26, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -812,21 +736,10 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 26, "id": "7008b0fc-3644-401c-b49f-9c40f9d89ac4", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "y.inputs.length = 20" ] @@ -856,12 +769,12 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 27, "id": "1cd000bd-9b24-4c39-9cac-70a3291d0660", "metadata": {}, "outputs": [], "source": [ - "from pyiron_contrib.workflow.workflow import Workflow\n", + "from pyiron_contrib.workflow import Workflow\n", "\n", "@Workflow.wrap_as.single_value_node(output_labels=\"is_greater\")\n", "def greater_than_half(x: int | float | bool = 0) -> bool:\n", @@ -881,7 +794,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 28, "id": "7964df3c-55af-4c25-afc5-9e07accb606a", "metadata": {}, "outputs": [ @@ -889,15 +802,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "n1 n1 n1 (GreaterThanHalf) output single-value: False\n", - "n2 n2 n2 (Slow):\n", - "Inputs []\n", - "Outputs ['p1']\n", - "InputSignals ['run']\n", - "OutputSignals ['ran']\n", - "n3 n3 n3 (GreaterThanHalf) output single-value: False\n", - "n4 n4 n4 (GreaterThanHalf) output single-value: False\n", - "n5 n5 n5 (GreaterThanHalf) output single-value: False\n" + "n1 == n1) 0.0 > 0.5 False\n", + "n2 == n2) 0.2 > 0.5 False\n", + "n3 == n3) 0.4 > 0.5 False\n", + "n4 == n4) 0.6 > 0.5 True\n", + "n5 == n5) 0.8 > 0.5 True\n" ] } ], @@ -905,14 +814,15 @@ "n1 = greater_than_half(label=\"n1\")\n", "\n", "wf = Workflow(\"my_wf\", n1) # As args at init\n", - "wf.create.Slow(lambda: x + 1, output_labels=\"p1\", label=\"n2\") # Instantiating from the class with a lambda function\n", - "# (Slow since we don't have an x default)\n", + "wf.create.SingleValue(n1.node_function, output_labels=\"p1\", label=\"n2\") # Instantiating from the class with a function\n", "wf.add(greater_than_half(label=\"n3\")) # Instantiating then passing to node adder\n", "wf.n4 = greater_than_half(label=\"will_get_overwritten_with_n4\") # Set attribute to instance\n", "greater_than_half(label=\"n5\", parent=wf) # By passing the workflow to the node\n", "\n", - "for k, v in wf.nodes.items():\n", - " print(k, v.label, v)" + "for i, (label, node) in enumerate(wf.nodes.items()):\n", + " x = i / len(wf)\n", + " node(x=x)\n", + " print(f\"{label} == {node.label}) {x} > 0.5 {node.single_value}\")" ] }, { @@ -920,12 +830,12 @@ "id": "dd5768a4-1810-4675-9389-bceb053cddfa", "metadata": {}, "source": [ - "Workflows have inputs and outputs just like function nodes, but these are dynamically created to map to all _unconnected_ input and output for their underlying graph:" + "Workflows have inputs and outputs just like function nodes, but these are dynamically created to map to all _unconnected_ input and output for their underlying graph. They automatically get named by connecting the node label and channel label with a double underscore, but this can be overriden by providing an `inputs_map` and/or an `outputs_map` -- these maps can also let you expose data channels that would otherwise be hidden because they have a connection!" ] }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 29, "id": "809178a5-2e6b-471d-89ef-0797db47c5ad", "metadata": {}, "outputs": [ @@ -933,7 +843,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "2\n" + "['ax', 'b__x'] ['ay', 'a + b + 2']\n" ] } ], @@ -945,12 +855,52 @@ " y = x + 1\n", " return y\n", "\n", + "@Workflow.wrap_as.single_value_node(output_labels=\"sum\")\n", + "def add_node(x, y):\n", + " return x + y\n", + "\n", "wf.a = add_one(0)\n", "wf.b = add_one(0)\n", "wf.sum = add_node(wf.a, wf.b) \n", + "wf.inputs_map = {\"a__x\": \"ax\"}\n", + "wf.outputs_map = {\"a__y\": \"ay\", \"sum__sum\": \"a + b + 2\"}\n", "# Remember, with single value nodes we can pass the whole node instead of an output channel!\n", "\n", - "print(wf.outputs.sum__sum_.value)" + "print(wf.inputs.labels, wf.outputs.labels)" + ] + }, + { + "cell_type": "markdown", + "id": "848a45a9-dfcc-4b9e-aec5-e879d88325a2", + "metadata": {}, + "source": [ + "When `run()` is called on a workflow, it will search through its owned nodes to see which have no internal data input connections and will call `run()` on each of these -- `a` and `b` in the example above. This behaviour can be overriden by manually setting the `starting_nodes` list attribute.\n", + "\n", + "Remaining execution flow still needs to be defined.\n", + "\n", + "To make sure we don't have a race condition between our branched input, let's use both of these features:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "520ef824-19a3-403a-ba6f-76a8a2fdbf7b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "wf.starting_nodes = [wf.a]\n", + "wf.a > wf.b > wf.sum" ] }, { @@ -958,7 +908,7 @@ "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. Runs of the workflow (which typically happen when the workflow is updated or called) return a dot-accessible dictionary based on the output channels:" + "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 then return a dot-accessible dictionary based on the output channels:" ] }, { @@ -970,7 +920,7 @@ { "data": { "text/plain": [ - "{'sum__sum_': 7}" + "{'ay': 3, 'a + b + 2': 7}" ] }, "execution_count": 31, @@ -979,10 +929,18 @@ } ], "source": [ - "out = wf(a__x=2, b__x=3)\n", + "out = wf(ax=2, b__x=3)\n", "out" ] }, + { + "cell_type": "markdown", + "id": "e3f4b51b-7c28-47f7-9822-b4755e12bd4d", + "metadata": {}, + "source": [ + "We can see now why we've been trying to givesuccinct string labels to our `Function` node outputs instead of just arbitrary expressions! The expressions are typically not dot-accessible:" + ] + }, { "cell_type": "code", "execution_count": 32, @@ -992,24 +950,350 @@ { "data": { "text/plain": [ - "7" + "(7, 3)" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out[\"a + b + 2\"], out.ay" + ] + }, + { + "cell_type": "markdown", + "id": "c67ddcd9-cea0-4f3f-96aa-491da0a4c459", + "metadata": {}, + "source": [ + "We can also look at our graph:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "2b0d2c85-9049-417b-8739-8a8432a1efbe", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clustersimple\n", + "\n", + "simple: Workflow\n", + "\n", + "clustersimpleInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clustersimpleOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "clustersimplea\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "a: AddOne\n", + "\n", + "\n", + "clustersimpleaInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clustersimpleaOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "clustersimpleb\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "b: AddOne\n", + "\n", + "\n", + "clustersimplebInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clustersimplebOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "clustersimplesum\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "sum: AddNode\n", + "\n", + "\n", + "clustersimplesumInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clustersimplesumOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", + "\n", + "\n", + "clustersimpleInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clustersimpleOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clustersimpleInputsx\n", + "\n", + "x\n", + "\n", + "\n", + "\n", + "clustersimpleaInputsx\n", + "\n", + "x\n", + "\n", + "\n", + "\n", + "clustersimpleInputsx->clustersimpleaInputsx\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clustersimplebInputsx\n", + "\n", + "x\n", + "\n", + "\n", + "\n", + "clustersimpleInputsx->clustersimplebInputsx\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clustersimpleOutputsy\n", + "\n", + "y\n", + "\n", + "\n", + "\n", + "clustersimpleOutputssum\n", + "\n", + "sum\n", + "\n", + "\n", + "\n", + "clustersimpleaInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clustersimpleaOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clustersimplebInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clustersimpleaOutputsran->clustersimplebInputsrun\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clustersimpleaOutputsy\n", + "\n", + "y\n", + "\n", + "\n", + "\n", + "clustersimpleaOutputsy->clustersimpleOutputsy\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clustersimplesumInputsx\n", + "\n", + "x\n", + "\n", + "\n", + "\n", + "clustersimpleaOutputsy->clustersimplesumInputsx\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clustersimplebOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clustersimplesumInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clustersimplebOutputsran->clustersimplesumInputsrun\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clustersimplebOutputsy\n", + "\n", + "y\n", + "\n", + "\n", + "\n", + "clustersimplesumInputsy\n", + "\n", + "y\n", + "\n", + "\n", + "\n", + "clustersimplebOutputsy->clustersimplesumInputsy\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clustersimplesumOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clustersimplesumOutputssum\n", + "\n", + "sum\n", + "\n", + "\n", + "\n", + "clustersimplesumOutputssum->clustersimpleOutputssum\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" ] }, - "execution_count": 32, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "out.sum__sum_" - ] - }, - { - "cell_type": "markdown", - "id": "0d6c7e6a-d39d-4c03-9f73-d506d7975fea", - "metadata": {}, - "source": [ - "(Note, you might see warnings from the workflow IO. This is fine, it's just letting us know that its keys don't match up with the channel labels. We don't see it until we call the input because workflows generate their IO panels dynamically on request to account for the fact that connections may change.)" + "wf.draw()" ] }, { @@ -1019,16 +1303,12 @@ "source": [ "# Example with pre-built nodes\n", "\n", - "Currently we have a handfull of pre-build nodes available for import from the `nodes` package. Let's use these to quickly put together a workflow for looking at some MD data.\n", - "\n", - "The `calc_md` node is `Slow`, but we happen to know that the calculation we're doing here is very easy, so we'll manually set `run_on_updates` and `update_at_instantiation` to `True` to get it to behave like a typical `Function` node.\n", - "\n", - "Finally, `SingleValue` has one more piece of syntactic sugar: when you're making a connection to the (single!) output channel, you can just pass the node itself!" + "Currently we have a handfull of pre-build nodes available for import from the `nodes` package. Let's use these to quickly put together a workflow for looking at some MD data." ] }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 34, "id": "ae500d5e-e55b-432c-8b5f-d5892193cdf5", "metadata": {}, "outputs": [ @@ -1041,7 +1321,17 @@ }, { "data": { - "image/png": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", "text/plain": [ "
" ] @@ -1055,15 +1345,15 @@ "\n", "wf.structure = wf.create.atomistics.Bulk(cubic=True, name=\"Al\")\n", "wf.engine = wf.create.atomistics.Lammps(structure=wf.structure)\n", - "wf.calc = wf.create.atomistics.CalcMd(\n", - " job=wf.engine, \n", - " run_on_updates=True, \n", - " update_on_instantiation=True\n", - ")\n", + "wf.calc = wf.create.atomistics.CalcMd(job=wf.engine)\n", "wf.plot = wf.create.standard.Scatter(\n", " x=wf.calc.outputs.steps, \n", " y=wf.calc.outputs.temperature\n", - ")" + ")\n", + "wf.structure > wf.engine > wf.calc > wf.plot\n", + "\n", + "out = wf.run()\n", + "out.plot__fig" ] }, { @@ -1076,7 +1366,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 35, "id": "be3dd2a3-0cb2-4fc4-a07f-7ec719bbc6c9", "metadata": {}, "outputs": [ @@ -1089,171 +1379,171 @@ "\n", "\n", - "\n", - "\n", + "\n", + "\n", "clusterwith_prebuilt\n", - "\n", + "\n", "with_prebuilt: Workflow\n", - "\n", - "clusterwith_prebuiltInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterwith_prebuiltOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", "\n", "clusterwith_prebuiltstructure\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "structure: Bulk\n", + "\n", + "structure: Bulk\n", "\n", "\n", "clusterwith_prebuiltstructureInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", "clusterwith_prebuiltstructureOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "clusterwith_prebuiltengine\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "engine: Lammps\n", + "\n", + "engine: Lammps\n", "\n", "\n", "clusterwith_prebuiltengineInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", "clusterwith_prebuiltengineOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "clusterwith_prebuiltcalc\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", - "calc: CalcMd\n", + "\n", + "calc: CalcMd\n", "\n", "\n", "clusterwith_prebuiltcalcInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", "clusterwith_prebuiltcalcOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "clusterwith_prebuiltplot\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "plot: Scatter\n", "\n", "\n", "clusterwith_prebuiltplotInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterwith_prebuiltplotOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", + "\n", + "clusterwith_prebuiltInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterwith_prebuiltOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", "\n", "\n", "clusterwith_prebuiltInputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", "\n", @@ -1265,302 +1555,302 @@ "\n", "\n", "clusterwith_prebuiltInputsname\n", - "\n", - "name\n", + "\n", + "name\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputsname\n", - "\n", - "name\n", + "\n", + "name\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputsname->clusterwith_prebuiltstructureInputsname\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputscrystalstructure->clusterwith_prebuiltstructureInputscrystalstructure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputsa->clusterwith_prebuiltstructureInputsa\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputsc->clusterwith_prebuiltstructureInputsc\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputscovera->clusterwith_prebuiltstructureInputscovera\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputsu->clusterwith_prebuiltstructureInputsu\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputsorthorhombic->clusterwith_prebuiltstructureInputsorthorhombic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputscubic->clusterwith_prebuiltstructureInputscubic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcInputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputsn_ionic_steps->clusterwith_prebuiltcalcInputsn_ionic_steps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcInputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputsn_print->clusterwith_prebuiltcalcInputsn_print\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputstemperature\n", - "\n", - "temperature\n", + "\n", + "temperature\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcInputstemperature\n", - "\n", - "temperature\n", + "\n", + "temperature\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputstemperature->clusterwith_prebuiltcalcInputstemperature\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcInputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltInputspressure->clusterwith_prebuiltcalcInputspressure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsenergy_pot\n", - "\n", - "energy_pot\n", + "\n", + "energy_pot\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", "\n", @@ -1571,215 +1861,242 @@ "\n", "\n", "clusterwith_prebuiltstructureInputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", + "\n", + "\n", + "clusterwith_prebuiltengineInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltstructureOutputsran->clusterwith_prebuiltengineInputsrun\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", "clusterwith_prebuiltstructureOutputsstructure\n", - "\n", - "structure\n", + "\n", + "structure\n", "\n", "\n", "\n", "clusterwith_prebuiltengineInputsstructure\n", - "\n", - "structure: Optional\n", + "\n", + "structure: Optional\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltstructureOutputsstructure->clusterwith_prebuiltengineInputsstructure\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltengineInputsrun\n", - "\n", - "run\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltengineOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltengineOutputsran->clusterwith_prebuiltcalcInputsrun\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", "clusterwith_prebuiltengineOutputsjob\n", - "\n", - "job: Lammps\n", + "\n", + "job: Lammps\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcInputsjob\n", - "\n", - "job: AtomisticGenericJob\n", + "\n", + "job: AtomisticGenericJob\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltengineOutputsjob->clusterwith_prebuiltcalcInputsjob\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcInputsrun\n", - "\n", - "run\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", + "\n", + "\n", + "clusterwith_prebuiltplotInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterwith_prebuiltcalcOutputsran->clusterwith_prebuiltplotInputsrun\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputscells->clusterwith_prebuiltOutputscells\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsdisplacements->clusterwith_prebuiltOutputsdisplacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsenergy_pot\n", - "\n", - "energy_pot\n", + "\n", + "energy_pot\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsenergy_pot->clusterwith_prebuiltOutputsenergy_pot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsenergy_tot->clusterwith_prebuiltOutputsenergy_tot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsforce_max->clusterwith_prebuiltOutputsforce_max\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsforces->clusterwith_prebuiltOutputsforces\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsindices->clusterwith_prebuiltOutputsindices\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputspositions->clusterwith_prebuiltOutputspositions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputspressures->clusterwith_prebuiltOutputspressures\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", "\n", "clusterwith_prebuiltplotInputsx\n", - "\n", - "x: Union\n", + "\n", + "x: Union\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputssteps->clusterwith_prebuiltplotInputsx\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -1790,60 +2107,54 @@ "\n", "\n", "clusterwith_prebuiltplotInputsy\n", - "\n", - "y: Union\n", + "\n", + "y: Union\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputstemperature->clusterwith_prebuiltplotInputsy\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputstotal_displacements->clusterwith_prebuiltOutputstotal_displacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsunwrapped_positions->clusterwith_prebuiltOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltcalcOutputsvolume->clusterwith_prebuiltOutputsvolume\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltplotInputsrun\n", - "\n", - "run\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -1859,7 +2170,7 @@ "fig\n", "\n", "\n", - "\n", + "\n", "clusterwith_prebuiltplotOutputsfig->clusterwith_prebuiltOutputsfig\n", "\n", "\n", @@ -1869,10 +2180,10 @@ "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 34, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -1891,7 +2202,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 36, "id": "2114d0c3-cdad-43c7-9ffa-50c36d56d18f", "metadata": {}, "outputs": [ @@ -2099,10 +2410,10 @@ "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 35, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -2123,7 +2434,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 37, "id": "c71a8308-f8a1-4041-bea0-1c841e072a6d", "metadata": {}, "outputs": [], @@ -2133,7 +2444,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 38, "id": "2b9bb21a-73cd-444e-84a9-100e202aa422", "metadata": {}, "outputs": [ @@ -2143,16 +2454,15 @@ "13" ] }, - "execution_count": 37, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "@Workflow.wrap_as.single_value_node()\n", + "@Workflow.wrap_as.single_value_node(output_labels=\"result\")\n", "def add_one(x):\n", - " result = x + 1\n", - " return result\n", + " return x + 1\n", "\n", "def add_three_macro(macro: Macro) -> None:\n", " \"\"\"\n", @@ -2167,6 +2477,7 @@ " # Setting this starting node is silly, since as the head-most node \n", " # it is the starting node anyway; the point is you have access to the \n", " # macro object and can do these sorts of setup proceedures here\n", + " macro.add_one > macro.add_two > macro.add_three\n", " \n", "macro = Macro(add_three_macro)\n", "macro(add_one__x=10).add_three__result" @@ -2182,7 +2493,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 39, "id": "3668f9a9-adca-48a4-84ea-13add965897c", "metadata": {}, "outputs": [ @@ -2192,7 +2503,7 @@ "{'intermediate': 102, 'plus_three': 103}" ] }, - "execution_count": 38, + "execution_count": 39, "metadata": {}, "output_type": "execute_result" } @@ -2210,8 +2521,10 @@ " macro.add_three = add_one(macro.add_two)\n", " macro.inputs_map = {\"add_one__x\": \"x\"}\n", " macro.outputs_map = {\"add_three__result\": \"plus_three\", \"add_two__result\": \"intermediate\"}\n", + " macro.add_one > macro.add_two > macro.add_three\n", " \n", - "macro = add_three_macro(x=100)\n", + "macro = add_three_macro()\n", + "macro(x=100)\n", "macro.outputs.to_value_dict()" ] }, @@ -2229,15 +2542,15 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 40, "id": "9aaeeec0-5f88-4c94-a6cc-45b56d2f0111", "metadata": {}, "outputs": [], "source": [ "@Workflow.wrap_as.macro_node()\n", "def lammps_minimize(macro):\n", - " macro.structure = macro.create.atomistics.Bulk(run_on_updates=False)\n", - " macro.engine = macro.create.atomistics.Lammps(structure=macro.structure, run_on_updates=False)\n", + " macro.structure = macro.create.atomistics.Bulk()\n", + " macro.engine = macro.create.atomistics.Lammps(structure=macro.structure)\n", " macro.calc = macro.create.atomistics.CalcMin(job=macro.engine, pressure=0)\n", " \n", " macro.structure > macro.engine > macro.calc\n", @@ -2266,7 +2579,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 41, "id": "a832e552-b3cc-411a-a258-ef21574fc439", "metadata": {}, "outputs": [], @@ -2282,8 +2595,7 @@ " wf.min_phase2.outputs.energy,\n", ")\n", "\n", - "wf.element > wf.min_phase1\n", - "wf.element > wf.min_phase2\n", + "wf.element > wf.min_phase1 > wf.min_phase2 > wf.compare\n", "# We stopped all the elements inside lammps_minimize from running on update\n", "# So we'll need to hit the macro with an explicit run command\n", "\n", @@ -2298,7 +2610,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 42, "id": "b764a447-236f-4cb7-952a-7cba4855087d", "metadata": {}, "outputs": [ @@ -2311,1103 +2623,1110 @@ "\n", "\n", - "\n", - "\n", + "\n", + "\n", "clusterphase_preference\n", - "\n", + "\n", "phase_preference: Workflow\n", - "\n", - "clusterphase_preferenceInputs\n", + "\n", + "clusterphase_preferencemin_phase1\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "min_phase1: LammpsMinimize\n", "\n", - "\n", - "clusterphase_preferenceOutputs\n", + "\n", + "clusterphase_preferencemin_phase1Inputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Inputs\n", "\n", - "\n", - "clusterphase_preferenceelement\n", + "\n", + "clusterphase_preferencemin_phase1Outputs\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", - "element: UserInput\n", + "\n", + "Outputs\n", "\n", - "\n", - "clusterphase_preferenceelementInputs\n", + "\n", + "clusterphase_preferenceInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", - "\n", - "clusterphase_preferenceelementOutputs\n", + "\n", + "clusterphase_preferenceOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", - "\n", - "clusterphase_preferencecompare\n", + "\n", + "clusterphase_preferenceelement\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "compare: PerAtomEnergyDifference\n", + "\n", + "element: UserInput\n", "\n", - "\n", - "clusterphase_preferencecompareInputs\n", + "\n", + "clusterphase_preferenceelementOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Outputs\n", "\n", - "\n", - "clusterphase_preferencecompareOutputs\n", + "\n", + "clusterphase_preferenceelementInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Inputs\n", "\n", - "\n", - "clusterphase_preferencemin_phase1\n", + "\n", + "clusterphase_preferencemin_phase2\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "min_phase1: LammpsMinimize\n", + "\n", + "min_phase2: LammpsMinimize\n", "\n", - "\n", - "clusterphase_preferencemin_phase1Inputs\n", + "\n", + "clusterphase_preferencemin_phase2Inputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", - "\n", - "clusterphase_preferencemin_phase1Outputs\n", + "\n", + "clusterphase_preferencemin_phase2Outputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", - "\n", - "clusterphase_preferencemin_phase2\n", + "\n", + "clusterphase_preferencecompare\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", - "min_phase2: LammpsMinimize\n", + "\n", + "compare: PerAtomEnergyDifference\n", "\n", - "\n", - "clusterphase_preferencemin_phase2Inputs\n", + "\n", + "clusterphase_preferencecompareInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", - "\n", - "clusterphase_preferencemin_phase2Outputs\n", + "\n", + "clusterphase_preferencecompareOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "\n", "clusterphase_preferenceInputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsuser_input\n", - "\n", - "user_input\n", + "\n", + "user_input\n", "\n", "\n", "\n", "clusterphase_preferenceelementInputsuser_input\n", - "\n", - "user_input\n", + "\n", + "user_input\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsuser_input->clusterphase_preferenceelementInputsuser_input\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputscrystalstructure->clusterphase_preferencemin_phase1Inputscrystalstructure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputscrystalstructure->clusterphase_preferencemin_phase2Inputscrystalstructure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsa->clusterphase_preferencemin_phase1Inputsa\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsa->clusterphase_preferencemin_phase2Inputsa\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsc->clusterphase_preferencemin_phase1Inputsc\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsc->clusterphase_preferencemin_phase2Inputsc\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputscovera->clusterphase_preferencemin_phase1Inputscovera\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputscovera->clusterphase_preferencemin_phase2Inputscovera\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsu->clusterphase_preferencemin_phase1Inputsu\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsu->clusterphase_preferencemin_phase2Inputsu\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsorthorhombic->clusterphase_preferencemin_phase1Inputsorthorhombic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsorthorhombic->clusterphase_preferencemin_phase2Inputsorthorhombic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputscubic->clusterphase_preferencemin_phase1Inputscubic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputscubic->clusterphase_preferencemin_phase2Inputscubic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsn_ionic_steps->clusterphase_preferencemin_phase1Inputsn_ionic_steps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsn_ionic_steps->clusterphase_preferencemin_phase2Inputsn_ionic_steps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsn_print->clusterphase_preferencemin_phase1Inputsn_print\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsn_print->clusterphase_preferencemin_phase2Inputsn_print\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputspressure->clusterphase_preferencemin_phase1Inputspressure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputspressure->clusterphase_preferencemin_phase2Inputspressure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceOutputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", "\n", "clusterphase_preferenceOutputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", "\n", "clusterphase_preferenceOutputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", "\n", "clusterphase_preferenceOutputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", "\n", "clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsde\n", - "\n", - "de\n", + "\n", + "de\n", "\n", "\n", "\n", "clusterphase_preferenceelementInputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", "\n", "clusterphase_preferenceelementOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", "\n", "clusterphase_preferenceelementOutputsran->clusterphase_preferencemin_phase1Inputsrun\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Inputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceelementOutputsran->clusterphase_preferencemin_phase2Inputsrun\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceelementOutputsuser_input\n", - "\n", - "user_input\n", + "\n", + "user_input\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsname\n", - "\n", - "name\n", + "\n", + "name\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceelementOutputsuser_input->clusterphase_preferencemin_phase1Inputsname\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsname\n", - "\n", - "name\n", + "\n", + "name\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceelementOutputsuser_input->clusterphase_preferencemin_phase2Inputsname\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Inputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputsran->clusterphase_preferencemin_phase2Inputsrun\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsstructure\n", - "\n", - "structure\n", + "\n", + "structure\n", "\n", "\n", "\n", "clusterphase_preferencecompareInputsstructure1\n", - "\n", - "structure1\n", + "\n", + "structure1\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsstructure->clusterphase_preferencecompareInputsstructure1\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputscells->clusterphase_preferenceOutputscells\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsdisplacements->clusterphase_preferenceOutputsdisplacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsenergy_pot\n", - "\n", - "energy_pot\n", + "\n", + "energy_pot\n", "\n", "\n", "\n", "clusterphase_preferencecompareInputsenergy1\n", - "\n", - "energy1\n", + "\n", + "energy1\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsenergy_pot->clusterphase_preferencecompareInputsenergy1\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsenergy_tot->clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsforce_max->clusterphase_preferenceOutputsforce_max\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsforces->clusterphase_preferenceOutputsforces\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsindices->clusterphase_preferenceOutputsindices\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputspositions->clusterphase_preferenceOutputspositions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputspressures->clusterphase_preferenceOutputspressures\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputssteps->clusterphase_preferenceOutputssteps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputstotal_displacements->clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsunwrapped_positions->clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsvolume->clusterphase_preferenceOutputsvolume\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", + "\n", + "\n", + "clusterphase_preferencecompareInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Outputsran->clusterphase_preferencecompareInputsrun\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsstructure\n", - "\n", - "structure\n", + "\n", + "structure\n", "\n", "\n", "\n", "clusterphase_preferencecompareInputsstructure2\n", - "\n", - "structure2\n", + "\n", + "structure2\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsstructure->clusterphase_preferencecompareInputsstructure2\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputscells->clusterphase_preferenceOutputscells\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsdisplacements->clusterphase_preferenceOutputsdisplacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsenergy_pot\n", - "\n", - "energy_pot\n", + "\n", + "energy_pot\n", "\n", "\n", "\n", "clusterphase_preferencecompareInputsenergy2\n", - "\n", - "energy2\n", + "\n", + "energy2\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsenergy_pot->clusterphase_preferencecompareInputsenergy2\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsenergy_tot->clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsforce_max->clusterphase_preferenceOutputsforce_max\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsforces->clusterphase_preferenceOutputsforces\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsindices->clusterphase_preferenceOutputsindices\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputspositions->clusterphase_preferenceOutputspositions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputspressures->clusterphase_preferenceOutputspressures\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputssteps->clusterphase_preferenceOutputssteps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputstotal_displacements->clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsunwrapped_positions->clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsvolume->clusterphase_preferenceOutputsvolume\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencecompareInputsrun\n", - "\n", - "run\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencecompareOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", "\n", "\n", "clusterphase_preferencecompareOutputsde\n", - "\n", - "de\n", + "\n", + "de\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencecompareOutputsde->clusterphase_preferenceOutputsde\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 41, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } @@ -3418,7 +3737,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 43, "id": "b51bef25-86c5-4d57-80c1-ab733e703caf", "metadata": {}, "outputs": [ @@ -3426,8 +3745,6 @@ "name": "stdout", "output_type": "stream", "text": [ - "The job JUSTAJOBNAME was saved and received the ID: 9558\n", - "The job JUSTAJOBNAME was saved and received the ID: 9558\n", "The job JUSTAJOBNAME was saved and received the ID: 9558\n", "The job JUSTAJOBNAME was saved and received the ID: 9558\n", "Al: E(hcp) - E(fcc) = 1.17 eV/atom\n" @@ -3441,7 +3758,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 44, "id": "091e2386-0081-436c-a736-23d019bd9b91", "metadata": {}, "outputs": [ @@ -3449,8 +3766,6 @@ "name": "stdout", "output_type": "stream", "text": [ - "The job JUSTAJOBNAME was saved and received the ID: 9558\n", - "The job JUSTAJOBNAME was saved and received the ID: 9558\n", "The job JUSTAJOBNAME was saved and received the ID: 9558\n", "The job JUSTAJOBNAME was saved and received the ID: 9558\n", "Mg: E(hcp) - E(fcc) = -4.54 eV/atom\n" @@ -3462,6 +3777,22 @@ "print(f\"{wf.inputs.element.value}: E({wf.inputs.phase2.value}) - E({wf.inputs.phase1.value}) = {out.compare__de:.2f} eV/atom\")" ] }, + { + "cell_type": "markdown", + "id": "3eb1bfd6-3c51-43cf-ae6c-c06d3ad0f7df", + "metadata": {}, + "source": [ + "# On the future\n", + "\n", + "Currently things are at an intermediate state where we _always_ need to worry about specifying the execution flow with signals. While signals are a necessity for cyclic graphs, valid execution patterns can be determined _automatically_ from the topology of data connections in the case of a directed acyclic graph (DAG -- which includes all the workflows we've seen so far!) So in the near future you can look forward to this step being fully automated such that typical users _only_ need to worry about the data graph.\n", + "\n", + "Additionally, we're working on better integration of executors so that processing power outside the main python process controlling the workflow can be used for individual nodes (including macros).\n", + "\n", + "Finally, we will start working on serialization soon so that workflows can be saved/loaded/restarted.\n", + "\n", + "(And, of course, there will be ongoing changes in UI/UX, like debug logs, more workflow visualization options, etc.)" + ] + }, { "cell_type": "markdown", "id": "f447531e-3e8c-4c7e-a579-5f9c56b75a5b", @@ -3484,7 +3815,7 @@ "\n", "### For-loops\n", "\n", - "One meta node is a for-loop builder, which creates a macro with $n$ internal copies of the \"loop body\" node, and a new IO interface.\n", + "One meta node is a for-loop builder, which creates a macro with $n$ internal instances of the \"loop body\" node class, and a new IO interface.\n", "The new input allows you to specify which input channels are being looped over -- such that the macro input for this channel is interpreted as list-like and distributed to all the copies of the nodes separately --, and which is _not_ being looped over -- and thus interpreted as the loop body node would normally interpret the input and passed to all copies equally.\n", "All of the loop body outputs are then collected as a list of length $n$.\n", "\n", @@ -3495,7 +3826,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 45, "id": "0b373764-b389-4c24-8086-f3d33a4f7fd7", "metadata": {}, "outputs": [ @@ -3509,7 +3840,7 @@ " 17.230249999999995]" ] }, - "execution_count": 44, + "execution_count": 45, "metadata": {}, "output_type": "execute_result" } @@ -3517,17 +3848,12 @@ "source": [ "n = 5\n", "\n", - "# Define the for-loop macro class\n", - "BulkStructureLoop = Workflow.create.meta.for_loop(\n", + "bulk_loop = Workflow.create.meta.for_loop(\n", " Workflow.create.atomistics.Bulk,\n", " n,\n", - " iterate_on = \"a\", # We could also pass more than one channel label as a tuple\n", - ")\n", - "\n", - "# Instantiate the macro as a node\n", - "bulk_loop = BulkStructureLoop()\n", + " iterate_on=(\"a\",),\n", + ")()\n", "\n", - "# Invoke the node with input to get a result\n", "out = bulk_loop(\n", " name=\"Al\", # Sent equally to each body node\n", " A=np.linspace(3.9, 4.1, n).tolist(), # Distributed across body nodes\n", @@ -3544,16 +3870,14 @@ "source": [ "## While-loops\n", "\n", - "We can also create a while-loop, which also takes a body node, but instead of creating copies of it, the body node gets re-run until a specified condition `Output` evaluates to `False`.\n", + "We can also create a while-loop, which takes both a body node and a condition node. The condition node must be a `SingleValue` returning a `bool` type. Instead of creating copies of the body node, the body node gets re-run until the condition node returns `False`.\n", "\n", - "In the example below, we have a node that adds two inputs together, and keep looping the result back into itself until the sum is greater than or equal to ten.\n", - "\n", - "Note that initializing the `a` input to a numeric value when we call the workflow does not destroy the connection made between the body node input and output -- so the first run of the body node uses the initial value passed, but then it updates its own input for subsequent calls!" + "You _must_ specify the data connection so that the body node passes information to the condition node. You may optionally also loop output of the body node back to input of the body node to change the input at each iteration. Right now this is done with horribly ugly string tuples, but we're still working on it." ] }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 46, "id": "37cbdd70-5c98-4ca0-83f9-cbfeff3a09db", "metadata": {}, "outputs": [ @@ -3566,458 +3890,452 @@ "\n", "\n", - "\n", - "\n", + "\n", + "\n", "clusterdo_while\n", - "\n", - "do_while: Workflow\n", + "\n", + "do_while: Workflow\n", "\n", "clusterdo_whileInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", "clusterdo_whileOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", - "clusterdo_whilelt10\n", + "clusterdo_whileadd_while\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", - "lt10: LessThanTen\n", + "\n", + "add_while: MakeLoop\n", "\n", "\n", - "clusterdo_whilelt10Inputs\n", + "clusterdo_whileadd_whileInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", - "clusterdo_whilelt10Outputs\n", + "clusterdo_whileadd_whileOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", - "clusterdo_whileadd_while\n", + "clusterdo_whileAddadd_while\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", - "add_while: MakeLoop\n", + "\n", + "Add: Add\n", "\n", "\n", - "clusterdo_whileadd_whileInputs\n", + "clusterdo_whileAddadd_whileInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", - "clusterdo_whileadd_whileOutputs\n", + "clusterdo_whileAddadd_whileOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", - "clusterdo_whileAddadd_while\n", + "clusterdo_whileLessThanTenadd_while\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Add: Add\n", + "\n", + "LessThanTen: LessThanTen\n", "\n", "\n", - "clusterdo_whileAddadd_whileInputs\n", + "clusterdo_whileLessThanTenadd_whileInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", - "clusterdo_whileAddadd_whileOutputs\n", + "clusterdo_whileLessThanTenadd_whileOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", - "clusterdo_whileif_add_while\n", + "clusterdo_whileswitchadd_while\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "if_: If\n", + "\n", + "switch: If\n", "\n", "\n", - "clusterdo_whileif_add_whileInputs\n", + "clusterdo_whileswitchadd_whileInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", - "clusterdo_whileif_add_whileOutputs\n", + "clusterdo_whileswitchadd_whileOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "\n", "clusterdo_whileInputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", "\n", "clusterdo_whileOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", "\n", "\n", "clusterdo_whileInputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", - "\n", + "\n", "clusterdo_whileadd_whileInputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", "\n", "clusterdo_whileInputsa->clusterdo_whileadd_whileInputsa\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterdo_whileInputsb\n", - "\n", - "b\n", + "\n", + "b\n", "\n", "\n", - "\n", + "\n", "clusterdo_whileadd_whileInputsb\n", - "\n", - "b\n", + "\n", + "b\n", "\n", "\n", "\n", "clusterdo_whileInputsb->clusterdo_whileadd_whileInputsb\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterdo_whileOutputsa + b\n", - "\n", - "a + b\n", + "\n", + "a + b\n", "\n", "\n", "\n", "clusterdo_whileOutputstruth\n", - "\n", - "truth\n", - "\n", - "\n", - "\n", - "clusterdo_whilelt10Inputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterdo_whilelt10Outputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whilelt10Inputsvalue\n", - "\n", - "value\n", - "\n", - "\n", - "\n", - "clusterdo_whilelt10Outputsvalue < 10\n", - "\n", - "value < 10\n", - "\n", - "\n", - "\n", - "clusterdo_whileadd_whileInputscondition\n", - "\n", - "condition\n", - "\n", - "\n", - "\n", - "clusterdo_whilelt10Outputsvalue < 10->clusterdo_whileadd_whileInputscondition\n", - "\n", - "\n", - "\n", + "\n", + "truth\n", "\n", "\n", - "\n", + "\n", "clusterdo_whileadd_whileInputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", - "\n", + "\n", "clusterdo_whileadd_whileOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", "\n", - "\n", + "\n", "clusterdo_whileAddadd_whileInputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", - "\n", + "\n", "clusterdo_whileadd_whileInputsa->clusterdo_whileAddadd_whileInputsa\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterdo_whileAddadd_whileInputsb\n", - "\n", - "b\n", + "\n", + "b\n", "\n", "\n", - "\n", + "\n", "clusterdo_whileadd_whileInputsb->clusterdo_whileAddadd_whileInputsb\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileif_add_whileInputscondition\n", - "\n", - "condition\n", - "\n", - "\n", - "\n", - "clusterdo_whileadd_whileInputscondition->clusterdo_whileif_add_whileInputscondition\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterdo_whileadd_whileOutputsa + b\n", - "\n", - "a + b\n", + "\n", + "a + b\n", "\n", "\n", "\n", "clusterdo_whileadd_whileOutputsa + b->clusterdo_whileOutputsa + b\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileadd_whileOutputsa + b->clusterdo_whilelt10Inputsvalue\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterdo_whileadd_whileOutputsa + b->clusterdo_whileadd_whileInputsa\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterdo_whileadd_whileOutputstruth\n", - "\n", - "truth\n", + "\n", + "truth\n", "\n", "\n", "\n", "clusterdo_whileadd_whileOutputstruth->clusterdo_whileOutputstruth\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterdo_whileAddadd_whileInputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", - "\n", + "\n", "clusterdo_whileAddadd_whileOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", - "\n", - "\n", - "clusterdo_whileif_add_whileInputsrun\n", - "\n", - "run\n", + "\n", + "\n", + "clusterdo_whileLessThanTenadd_whileInputsrun\n", + "\n", + "run\n", "\n", - "\n", + "\n", "\n", - "clusterdo_whileAddadd_whileOutputsran->clusterdo_whileif_add_whileInputsrun\n", - "\n", - "\n", - "\n", + "clusterdo_whileAddadd_whileOutputsran->clusterdo_whileLessThanTenadd_whileInputsrun\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "clusterdo_whileAddadd_whileOutputsa + b\n", - "\n", - "a + b\n", + "\n", + "a + b\n", "\n", "\n", - "\n", + "\n", "clusterdo_whileAddadd_whileOutputsa + b->clusterdo_whileadd_whileOutputsa + b\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterdo_whileAddadd_whileOutputsa + b->clusterdo_whileAddadd_whileInputsa\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileLessThanTenadd_whileInputsvalue\n", + "\n", + "value\n", + "\n", + "\n", + "\n", + "clusterdo_whileAddadd_whileOutputsa + b->clusterdo_whileLessThanTenadd_whileInputsvalue\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileLessThanTenadd_whileOutputsran\n", + "\n", + "ran\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileswitchadd_whileInputsrun\n", + "\n", + "run\n", + "\n", + "\n", + "\n", + "clusterdo_whileLessThanTenadd_whileOutputsran->clusterdo_whileswitchadd_whileInputsrun\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileLessThanTenadd_whileOutputsvalue < 10\n", + "\n", + "value < 10\n", + "\n", + "\n", + "\n", + "clusterdo_whileswitchadd_whileInputscondition\n", + "\n", + "condition\n", + "\n", + "\n", + "\n", + "clusterdo_whileLessThanTenadd_whileOutputsvalue < 10->clusterdo_whileswitchadd_whileInputscondition\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterdo_whileswitchadd_whileOutputsran\n", + "\n", + "ran\n", "\n", - "\n", + "\n", + "\n", "\n", - "clusterdo_whileif_add_whileOutputsran\n", - "\n", - "ran\n", + "clusterdo_whileswitchadd_whileOutputstrue\n", + "\n", + "true\n", "\n", - "\n", - "\n", - "\n", - "clusterdo_whileif_add_whileOutputstrue\n", - "\n", - "true\n", + "\n", + "\n", + "clusterdo_whileswitchadd_whileOutputstrue->clusterdo_whileAddadd_whileInputsrun\n", + "\n", + "\n", + "\n", "\n", - "\n", - "\n", - "clusterdo_whileif_add_whileOutputstrue->clusterdo_whileAddadd_whileInputsrun\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "clusterdo_whileswitchadd_whileOutputsfalse\n", + "\n", + "false\n", "\n", - "\n", + "\n", "\n", - "clusterdo_whileif_add_whileOutputsfalse\n", - "\n", - "false\n", - "\n", - "\n", - "\n", - "clusterdo_whileif_add_whileOutputstruth\n", - "\n", - "truth\n", + "clusterdo_whileswitchadd_whileOutputstruth\n", + "\n", + "truth\n", "\n", - "\n", - "\n", - "clusterdo_whileif_add_whileOutputstruth->clusterdo_whileadd_whileOutputstruth\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "clusterdo_whileswitchadd_whileOutputstruth->clusterdo_whileadd_whileOutputstruth\n", + "\n", + "\n", + "\n", "\n", "\n", "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 45, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "@Workflow.wrap_as.single_value_node(run_on_updates=False)\n", + "@Workflow.wrap_as.single_value_node()\n", "def add(a, b):\n", " print(f\"Adding {a} + {b}\")\n", " return a + b\n", @@ -4026,28 +4344,41 @@ "def less_than_ten(value):\n", " return value < 10\n", "\n", - "AddWhile = Workflow.create.meta.while_loop(add)\n", + "AddWhile = Workflow.create.meta.while_loop(\n", + " loop_body_class=add,\n", + " condition_class=less_than_ten,\n", + " internal_connection_map=[\n", + " (\"Add\", \"a + b\", \"LessThanTen\", \"value\"),\n", + " (\"Add\", \"a + b\", \"Add\", \"a\")\n", + " ],\n", + " inputs_map={\"Add__a\": \"a\", \"Add__b\": \"b\"},\n", + " outputs_map={\"Add__a + b\": \"total\"}\n", + ")\n", "\n", "wf = Workflow(\"do_while\")\n", - "wf.lt10 = less_than_ten()\n", - "wf.add_while = AddWhile(condition=wf.lt10)\n", - "\n", - "wf.lt10.inputs.value = wf.add_while.Add\n", - "wf.add_while.Add.inputs.a = wf.add_while.Add\n", + "wf.add_while = AddWhile()\n", "\n", "wf.starting_nodes = [wf.add_while]\n", "wf.inputs_map = {\n", - " \"add_while__Add__a\": \"a\", \n", - " \"add_while__Add__b\": \"b\"\n", + " \"add_while__a\": \"a\",\n", + " \"add_while__b\": \"b\"\n", "}\n", - "wf.outputs_map = {\"add_while__Add__a + b\": \"total\"}\n", + "wf.outputs_map = {\"add_while__total\": \"total\"}\n", "\n", "wf.draw(depth=2)" ] }, + { + "cell_type": "markdown", + "id": "eb810e1e-4d13-4cb1-94cc-6d191b8c568d", + "metadata": {}, + "source": [ + "Note that initializing the `a` and `b` input to numeric values when we call the workflow below does not destroy the connection made between the body node input and output -- so the first run of the body node uses the initial value passed, but then it updates its own input for subsequent calls!" + ] + }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 47, "id": "2dfb967b-41ac-4463-b606-3e315e617f2a", "metadata": {}, "outputs": [ From 04db459d9afcf11f042384991b95c5e3341cf262 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 8 Sep 2023 13:28:40 -0700 Subject: [PATCH 620/756] Only accept output_labels as input to the decorators The other relevant kwargs got removed (update_on_instantiation and run_on_updates), and this makes using the decorator a bit easier. --- notebooks/workflow_example.ipynb | 900 +++++++++++------------ pyiron_contrib/workflow/function.py | 8 +- pyiron_contrib/workflow/workflow.py | 2 +- tests/integration/test_workflow.py | 12 +- tests/unit/workflow/test_node_package.py | 2 +- tests/unit/workflow/test_workflow.py | 6 +- 6 files changed, 463 insertions(+), 467 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index ede7c9411..68ac682ef 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -39,7 +39,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "41d6241a9ab44c44aa1d958ba256debf", + "model_id": "6e3a162575ab4e43acfc08cf0664b9da", "version_major": 2, "version_minor": 0 }, @@ -438,7 +438,7 @@ } ], "source": [ - "@function_node(output_labels=\"diff\")\n", + "@function_node(\"diff\")\n", "def subtract_node(x: int | float = 2, y: int | float = 1) -> int | float:\n", " return x - y\n", "\n", @@ -487,7 +487,7 @@ "def linear(x):\n", " return x\n", "\n", - "@function_node(output_labels=\"double\")\n", + "@function_node(\"double\")\n", "def times_two(x):\n", " return 2 * x\n", "\n", @@ -642,7 +642,7 @@ "def linear(x):\n", " return x\n", "\n", - "@single_value_node(output_labels=\"double\")\n", + "@single_value_node(\"double\")\n", "def times_two(x):\n", " return 2 * x\n", "\n", @@ -776,7 +776,7 @@ "source": [ "from pyiron_contrib.workflow import Workflow\n", "\n", - "@Workflow.wrap_as.single_value_node(output_labels=\"is_greater\")\n", + "@Workflow.wrap_as.single_value_node(\"is_greater\")\n", "def greater_than_half(x: int | float | bool = 0) -> bool:\n", " \"\"\"The functionality doesn't matter here, it's just an example\"\"\"\n", " return x > 0.5" @@ -855,7 +855,7 @@ " y = x + 1\n", " return y\n", "\n", - "@Workflow.wrap_as.single_value_node(output_labels=\"sum\")\n", + "@Workflow.wrap_as.single_value_node(\"sum\")\n", "def add_node(x, y):\n", " return x + y\n", "\n", @@ -1284,7 +1284,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 33, @@ -1322,7 +1322,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 34, @@ -1385,160 +1385,160 @@ "clusterwith_prebuilt\n", "\n", "with_prebuilt: Workflow\n", + "\n", + "clusterwith_prebuiltInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterwith_prebuiltOutputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", + "\n", "\n", "clusterwith_prebuiltstructure\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "structure: Bulk\n", "\n", "\n", "clusterwith_prebuiltstructureInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterwith_prebuiltstructureOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", "\n", "clusterwith_prebuiltengine\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "engine: Lammps\n", "\n", "\n", "clusterwith_prebuiltengineInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterwith_prebuiltengineOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", "\n", "clusterwith_prebuiltcalc\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "calc: CalcMd\n", "\n", "\n", "clusterwith_prebuiltcalcInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterwith_prebuiltcalcOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", "\n", "clusterwith_prebuiltplot\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "plot: Scatter\n", "\n", "\n", "clusterwith_prebuiltplotInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterwith_prebuiltplotOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", - "\n", - "clusterwith_prebuiltInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterwith_prebuiltOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", "\n", "\n", "clusterwith_prebuiltInputsrun\n", @@ -1555,154 +1555,154 @@ "\n", "\n", "clusterwith_prebuiltInputsname\n", - "\n", - "name\n", + "\n", + "name\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputsname\n", - "\n", - "name\n", + "\n", + "name\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsname->clusterwith_prebuiltstructureInputsname\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", "\n", "clusterwith_prebuiltInputscrystalstructure->clusterwith_prebuiltstructureInputscrystalstructure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsa->clusterwith_prebuiltstructureInputsa\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsc->clusterwith_prebuiltstructureInputsc\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", "\n", "clusterwith_prebuiltInputscovera->clusterwith_prebuiltstructureInputscovera\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsu->clusterwith_prebuiltstructureInputsu\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsorthorhombic->clusterwith_prebuiltstructureInputsorthorhombic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", "\n", "clusterwith_prebuiltstructureInputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", "\n", "clusterwith_prebuiltInputscubic->clusterwith_prebuiltstructureInputscubic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -1783,74 +1783,74 @@ "\n", "\n", "clusterwith_prebuiltOutputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsenergy_pot\n", - "\n", - "energy_pot\n", + "\n", + "energy_pot\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", "\n", @@ -1965,119 +1965,119 @@ "\n", "\n", "clusterwith_prebuiltcalcOutputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputscells->clusterwith_prebuiltOutputscells\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsdisplacements->clusterwith_prebuiltOutputsdisplacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsenergy_pot\n", - "\n", - "energy_pot\n", + "\n", + "energy_pot\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsenergy_pot->clusterwith_prebuiltOutputsenergy_pot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsenergy_tot->clusterwith_prebuiltOutputsenergy_tot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsforce_max->clusterwith_prebuiltOutputsforce_max\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsforces->clusterwith_prebuiltOutputsforces\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsindices->clusterwith_prebuiltOutputsindices\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputspositions->clusterwith_prebuiltOutputspositions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputspressures->clusterwith_prebuiltOutputspressures\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -2120,41 +2120,41 @@ "\n", "\n", "clusterwith_prebuiltcalcOutputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputstotal_displacements->clusterwith_prebuiltOutputstotal_displacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsunwrapped_positions->clusterwith_prebuiltOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", "\n", "clusterwith_prebuiltcalcOutputsvolume->clusterwith_prebuiltOutputsvolume\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -2180,7 +2180,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 35, @@ -2410,7 +2410,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 36, @@ -2460,7 +2460,7 @@ } ], "source": [ - "@Workflow.wrap_as.single_value_node(output_labels=\"result\")\n", + "@Workflow.wrap_as.single_value_node(\"result\")\n", "def add_one(x):\n", " return x + 1\n", "\n", @@ -2629,93 +2629,93 @@ "clusterphase_preference\n", "\n", "phase_preference: Workflow\n", - "\n", - "clusterphase_preferencemin_phase1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "min_phase1: LammpsMinimize\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Inputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", "\n", "clusterphase_preferenceInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Inputs\n", "\n", "\n", "clusterphase_preferenceOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", "\n", "clusterphase_preferenceelement\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "element: UserInput\n", "\n", + "\n", + "clusterphase_preferenceelementInputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Inputs\n", + "\n", "\n", "clusterphase_preferenceelementOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "Outputs\n", "\n", - "\n", - "clusterphase_preferenceelementInputs\n", + "\n", + "clusterphase_preferencemin_phase1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "min_phase1: LammpsMinimize\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Inputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Outputs\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Outputs\n", "\n", "\n", "clusterphase_preferencemin_phase2\n", @@ -3138,74 +3138,74 @@ "\n", "\n", "clusterphase_preferenceOutputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", "\n", "clusterphase_preferenceOutputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", "\n", "clusterphase_preferenceOutputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", "\n", "clusterphase_preferenceOutputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", "\n", "clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", "\n", @@ -3313,28 +3313,28 @@ "\n", "\n", "clusterphase_preferencemin_phase1Outputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputscells->clusterphase_preferenceOutputscells\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsdisplacements->clusterphase_preferenceOutputsdisplacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3358,132 +3358,132 @@ "\n", "\n", "clusterphase_preferencemin_phase1Outputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsenergy_tot->clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforce_max->clusterphase_preferenceOutputsforce_max\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforces->clusterphase_preferenceOutputsforces\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsindices->clusterphase_preferenceOutputsindices\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspositions->clusterphase_preferenceOutputspositions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspressures->clusterphase_preferenceOutputspressures\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputssteps->clusterphase_preferenceOutputssteps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputstotal_displacements->clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsunwrapped_positions->clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsvolume->clusterphase_preferenceOutputsvolume\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3527,28 +3527,28 @@ "\n", "\n", "clusterphase_preferencemin_phase2Outputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputscells->clusterphase_preferenceOutputscells\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsdisplacements->clusterphase_preferenceOutputsdisplacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3572,132 +3572,132 @@ "\n", "\n", "clusterphase_preferencemin_phase2Outputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsenergy_tot->clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforce_max->clusterphase_preferenceOutputsforce_max\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforces->clusterphase_preferenceOutputsforces\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsindices->clusterphase_preferenceOutputsindices\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspositions->clusterphase_preferenceOutputspositions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspressures->clusterphase_preferenceOutputspressures\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputssteps->clusterphase_preferenceOutputssteps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputstotal_displacements->clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsunwrapped_positions->clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsvolume->clusterphase_preferenceOutputsvolume\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -3723,7 +3723,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 42, @@ -3929,126 +3929,126 @@ "\n", "add_while: MakeLoop\n", "\n", - "\n", - "clusterdo_whileadd_whileInputs\n", + "\n", + "clusterdo_whileLessThanTenadd_while\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "LessThanTen: LessThanTen\n", + "\n", + "\n", + "clusterdo_whileLessThanTenadd_whileInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", - "\n", - "clusterdo_whileadd_whileOutputs\n", + "\n", + "clusterdo_whileLessThanTenadd_whileOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", - "\n", - "clusterdo_whileAddadd_while\n", + "\n", + "clusterdo_whileswitchadd_while\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Add: Add\n", + "\n", + "switch: If\n", "\n", - "\n", - "clusterdo_whileAddadd_whileInputs\n", + "\n", + "clusterdo_whileswitchadd_whileInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", - "\n", - "clusterdo_whileAddadd_whileOutputs\n", + "\n", + "clusterdo_whileswitchadd_whileOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clusterdo_whileLessThanTenadd_while\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "LessThanTen: LessThanTen\n", + "\n", + "Outputs\n", "\n", - "\n", - "clusterdo_whileLessThanTenadd_whileInputs\n", + "\n", + "clusterdo_whileadd_whileInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", - "\n", - "clusterdo_whileLessThanTenadd_whileOutputs\n", + "\n", + "clusterdo_whileadd_whileOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", - "\n", - "clusterdo_whileswitchadd_while\n", + "\n", + "clusterdo_whileAddadd_while\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "switch: If\n", + "\n", + "Add: Add\n", "\n", - "\n", - "clusterdo_whileswitchadd_whileInputs\n", + "\n", + "clusterdo_whileAddadd_whileInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", - "\n", - "clusterdo_whileswitchadd_whileOutputs\n", + "\n", + "clusterdo_whileAddadd_whileOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "\n", @@ -4326,7 +4326,7 @@ "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 46, diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index bcdff5092..95e5ed319 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -626,7 +626,7 @@ def __str__(self): ) -def function_node(**node_class_kwargs): +def function_node(output_labels=None): """ A decorator for dynamically creating node classes from functions. @@ -646,7 +646,7 @@ def as_node(node_function: callable): "__init__": partialmethod( Function.__init__, node_function, - **node_class_kwargs, + output_labels=output_labels, ) }, ) @@ -654,7 +654,7 @@ def as_node(node_function: callable): return as_node -def single_value_node(**node_class_kwargs): +def single_value_node(output_labels=None): """ A decorator for dynamically creating fast node classes from functions. @@ -671,7 +671,7 @@ def as_single_value_node(node_function: callable): "__init__": partialmethod( SingleValue.__init__, node_function, - **node_class_kwargs, + output_labels=output_labels, ) }, ) diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 0f35ab4f9..9b1a21cae 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -76,7 +76,7 @@ class Workflow(Composite): workflow (cf. the `Node` docs for more detail on the node types). Let's use these to explore a workflow's input and output, which are dynamically generated from the unconnected IO of its nodes: - >>> @Workflow.wrap_as.function_node(output_labels="y") + >>> @Workflow.wrap_as.function_node("y") >>> def plus_one(x: int = 0): ... return x + 1 >>> diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py index 19acc530e..0d59f1e18 100644 --- a/tests/integration/test_workflow.py +++ b/tests/integration/test_workflow.py @@ -106,17 +106,13 @@ def test_while_loop(self): with self.subTest("Random"): np.random.seed(0) - @Workflow.wrap_as.single_value_node() + @Workflow.wrap_as.single_value_node("random") def random(length: int | None = None): - random = np.random.random(length) - return random + return np.random.random(length) - @Workflow.wrap_as.single_value_node() + @Workflow.wrap_as.single_value_node("gt") def greater_than(x: float, threshold: float): - gt = x > threshold - symbol = ">" if gt else "<=" - # print(f"{x:.3f} {symbol} {threshold}") - return gt + return x > threshold RandomWhile = Workflow.create.meta.while_loop( loop_body_class=random, diff --git a/tests/unit/workflow/test_node_package.py b/tests/unit/workflow/test_node_package.py index 0ef4d0320..0d0c727e5 100644 --- a/tests/unit/workflow/test_node_package.py +++ b/tests/unit/workflow/test_node_package.py @@ -35,7 +35,7 @@ def test_update(self): with self.assertRaises(TypeError): self.package.available_name = "But we can still only assign node classes" - @function_node(output_label="y") + @function_node("y") def add(x: int = 0): return x + 1 diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 5d76bf93e..d7fead1ff 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -125,7 +125,7 @@ def test_workflow_io(self): self.assertEqual(out.intermediate, 2) def test_node_decorator_access(self): - @Workflow.wrap_as.function_node(output_labels="y") + @Workflow.wrap_as.function_node("y") def plus_one(x: int = 0) -> int: return x + 1 @@ -172,7 +172,7 @@ def five(sleep_time=0.): five = 5 return five - @Workflow.wrap_as.single_value_node(output_labels="sum") + @Workflow.wrap_as.single_value_node("sum") def sum(a, b): return a + b @@ -216,7 +216,7 @@ def test_call(self): wf.a = wf.create.SingleValue(plus_one) wf.b = wf.create.SingleValue(plus_one) - @Workflow.wrap_as.single_value_node(output_labels="sum") + @Workflow.wrap_as.single_value_node("sum") def sum_(a, b): return a + b From de4db43449157e16d54a5fd3228a14aeaa74c6db Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 8 Sep 2023 13:29:18 -0700 Subject: [PATCH 621/756] Remove placeholder integration test We have real integration tests now --- tests/integration/test_integration.py | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 tests/integration/test_integration.py diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py deleted file mode 100644 index 9c25ae2df..000000000 --- a/tests/integration/test_integration.py +++ /dev/null @@ -1,6 +0,0 @@ -import unittest - - -class TestNothing(unittest.TestCase): - def test_nothing(self): - self.assertTrue(True) From 3509e93448941b2bc5c6b32a39d7a2cd61f555a3 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Fri, 8 Sep 2023 20:30:07 +0000 Subject: [PATCH 622/756] Format black --- pyiron_contrib/workflow/meta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index fc9c49dab..a6eaec532 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -296,7 +296,7 @@ def make_loop(macro): switch = macro.create.standard.If(label="switch") switch.inputs.condition = condition_node - for (out_n, out_c, in_n, in_c) in internal_connection_map: + for out_n, out_c, in_n, in_c in internal_connection_map: macro.nodes[in_n].inputs[in_c] = macro.nodes[out_n].outputs[out_c] switch.signals.output.true > body_node > condition_node > switch From 27cf81fabb482167904e385927a03e8ee7d0d90e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 8 Sep 2023 20:37:54 -0700 Subject: [PATCH 623/756] Return connection pairs on disconnection --- pyiron_contrib/workflow/channels.py | 13 ++++++++++--- pyiron_contrib/workflow/io.py | 28 +++++++++++++++++++++++----- pyiron_contrib/workflow/node.py | 15 ++++++++++++--- tests/unit/workflow/test_channels.py | 14 ++++++++++++-- tests/unit/workflow/test_function.py | 27 +++++++++++++++++++++++++++ tests/unit/workflow/test_io.py | 11 +++++++++++ 6 files changed, 95 insertions(+), 13 deletions(-) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index 6dfbc4089..7a58b3e55 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -87,24 +87,31 @@ def connect(self, *others: Channel) -> None: """ pass - def disconnect(self, *others: Channel) -> None: + def disconnect(self, *others: Channel) -> list[tuple[Channel, Channel]]: """ If currently connected to any others, removes this and the other from eachothers respective connections lists. Args: *others (Channel): The other channels to disconnect from. + + Returns: + [list[tuple[Channel, Channel]]]: A list of the pairs of channels that no + longer participate in a connection. """ + destroyed_connections = [] for other in others: if other in self.connections: self.connections.remove(other) other.disconnect(self) + destroyed_connections.append((self, other)) + return destroyed_connections - def disconnect_all(self) -> None: + def disconnect_all(self) -> list[tuple[Channel, Channel]]: """ Disconnect from all other channels currently in the connections list. """ - self.disconnect(*self.connections) + return self.disconnect(*self.connections) @property def connected(self) -> bool: diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py index f1476e367..2574afa54 100644 --- a/pyiron_contrib/workflow/io.py +++ b/pyiron_contrib/workflow/io.py @@ -121,9 +121,18 @@ def connected(self): def fully_connected(self): return all([c.connected for c in self]) - def disconnect(self): + def disconnect(self) -> list[tuple[Channel, Channel]]: + """ + Disconnect all connections that owned channels have. + + Returns: + [list[tuple[Channel, Channel]]]: A list of the pairs of channels that no + longer participate in a connection. + """ + destroyed_connections = [] for c in self: - c.disconnect_all() + destroyed_connections.extend(c.disconnect_all()) + return destroyed_connections @property def labels(self): @@ -226,9 +235,18 @@ def __init__(self): self.input = InputSignals() self.output = OutputSignals() - def disconnect(self): - self.input.disconnect() - self.output.disconnect() + def disconnect(self) -> list[tuple[Channel, Channel]]: + """ + Disconnect all connections in input and output signals. + + Returns: + [list[tuple[Channel, Channel]]]: A list of the pairs of channels that no + longer participate in a connection. + """ + destroyed_connections = [] + destroyed_connections.extend(self.input.disconnect()) + destroyed_connections.extend(self.output.disconnect()) + return destroyed_connections @property def connected(self): diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 579c993d3..1be1c978a 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -277,9 +277,18 @@ def server(self, server: Server | None): self._server = server def disconnect(self): - self.inputs.disconnect() - self.outputs.disconnect() - self.signals.disconnect() + """ + Disconnect all connections belonging to inputs, outputs, and signals channels. + + Returns: + [list[tuple[Channel, Channel]]]: A list of the pairs of channels that no + longer participate in a connection. + """ + destroyed_connections = [] + destroyed_connections.extend(self.inputs.disconnect()) + destroyed_connections.extend(self.outputs.disconnect()) + destroyed_connections.extend(self.signals.disconnect()) + return destroyed_connections @property def ready(self) -> bool: diff --git a/tests/unit/workflow/test_channels.py b/tests/unit/workflow/test_channels.py index 48f8f5c10..e8b1dc0b0 100644 --- a/tests/unit/workflow/test_channels.py +++ b/tests/unit/workflow/test_channels.py @@ -45,8 +45,13 @@ def test_connections(self): self.assertEqual(self.no.value, self.ni1.value) with self.subTest("Test disconnection"): - self.ni2.disconnect(self.no) # Should do nothing - self.ni1.disconnect(self.no) + disconnected = self.ni2.disconnect(self.no) + self.assertEqual( + len(disconnected), + 0, + msg="There were no connections to begin with, nothing should be there" + ) + disconnected = self.ni1.disconnect(self.no) self.assertEqual( [], self.ni1.connections, msg="No connections should be left" ) @@ -55,6 +60,11 @@ def test_connections(self): self.no.connections, msg="Disconnection should also have been reflexive" ) + self.assertListEqual( + disconnected, + [(self.ni1, self.no)], + msg="Expected a list of the disconnected pairs." + ) with self.subTest("Test multiple connections"): self.no.connect(self.ni1, self.ni2) diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py index 35c74d9a8..529217c67 100644 --- a/tests/unit/workflow/test_function.py +++ b/tests/unit/workflow/test_function.py @@ -510,6 +510,33 @@ def test_working_directory(self): self.assertTrue(str(n_f.working_directory.path).endswith(n_f.label)) n_f.working_directory.delete() + def test_disconnection(self): + n1 = Function(no_default, output_labels="out") + n2 = Function(no_default, output_labels="out") + n3 = Function(no_default, output_labels="out") + n4 = Function(plus_one) + + n3.inputs.x = n1.outputs.out + n3.inputs.y = n2.outputs.out + n4.inputs.x = n3.outputs.out + n2 > n3 > n4 + disconnected = n3.disconnect() + self.assertListEqual( + disconnected, + [ + # Inputs + (n3.inputs.x, n1.outputs.out), + (n3.inputs.y, n2.outputs.out), + # Outputs + (n3.outputs.out, n4.inputs.x), + # Signals (inputs, then output) + (n3.signals.input.run, n2.signals.output.ran), + (n3.signals.output.ran, n4.signals.input.run), + ], + msg="Expected to find pairs (starting with the node disconnect was called " + "on) of all broken connections among input, output, and signals." + ) + if __name__ == '__main__': unittest.main() diff --git a/tests/unit/workflow/test_io.py b/tests/unit/workflow/test_io.py index 3122669a9..d3fd795d8 100644 --- a/tests/unit/workflow/test_io.py +++ b/tests/unit/workflow/test_io.py @@ -80,6 +80,17 @@ def test_connection(self): self.input.x = 7 self.assertEqual(self.input.x.value, 7) + self.input.y = self.output.a + disconnected = self.input.disconnect() + self.assertListEqual( + disconnected, + [ + (self.input.x, self.output.a), + (self.input.y, self.output.a) + ], + msg="Disconnecting the panel should disconnect all children" + ) + def test_conversion(self): converted = self.input.to_value_dict() for template in self.inputs: From 327041d8f46e115be5c2f88c42fa8c60504724dc Mon Sep 17 00:00:00 2001 From: liamhuber Date: Sat, 9 Sep 2023 12:12:46 -0700 Subject: [PATCH 624/756] Implement a super rough version of DAG linearization --- .ci_support/environment.yml | 1 + pyiron_contrib/workflow/composite.py | 87 ++++++++++++++++++++++++++-- setup.py | 1 + 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 2af400b5d..41cafbb8a 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -14,6 +14,7 @@ dependencies: - scipy =1.11.1 - seaborn =0.12.2 - scikit-image =0.21.0 +- toposort - randspg =0.0.1 - boto3 =1.28.25 - moto =4.1.14 diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 52f4e7ff6..5c8409865 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -9,6 +9,8 @@ from functools import partial from typing import Literal, Optional, TYPE_CHECKING +from toposort import toposort_flatten, CircularDependencyError + from pyiron_contrib.workflow.interfaces import Creator, Wrappers from pyiron_contrib.workflow.io import Outputs, Inputs from pyiron_contrib.workflow.node import Node @@ -87,12 +89,14 @@ def __init__( strict_naming: bool = True, inputs_map: Optional[dict] = None, outputs_map: Optional[dict] = None, + automate_execution: bool = True, **kwargs, ): super().__init__(*args, label=label, parent=parent, **kwargs) self.strict_naming: bool = strict_naming self.inputs_map = inputs_map self.outputs_map = outputs_map + self.automate_execution = automate_execution self.nodes: DotDict[str:Node] = DotDict() self.starting_nodes: None | list[Node] = None self._creator = self.create @@ -178,13 +182,86 @@ def on_run(self): @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() + if self.automate_execution: + self._run_linearly_through_dag() + else: + 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()) + def _run_linearly_through_dag(self): + disconnected_pairs = self._purge_existing_run_signals() + digraph = self._data_flow_as_node_digraph() + execution_order = self._digraph_to_linear_order(digraph) + self._order_run_signals_linearly(execution_order) + self.nodes[execution_order[0]].run() + self._restore_run_signals(disconnected_pairs) + + def _purge_existing_run_signals(self) -> list[tuple[Channel, Channel]]: + disconnected_pairs = [] + for node in self.nodes.values(): + disconnected_pairs.extend(node.signals.input.run.disconnect()) + return disconnected_pairs + + def _data_flow_as_node_digraph(self) -> dict[int, set[int]]: + """ + A dictionary of node indices and its data input dependencies as indices, where + the indices are drawn from order of appearance in `self.nodes`. + + Raises: + RuntimeError: When a node appears in its own input. + """ + digraph = {} + label_index_map = {n.label: i for i, n in enumerate(self.nodes.values())} + + for label, i in label_index_map.items(): + node = self.nodes[label] + node_dependencies = [] + for channel in node.inputs: + node_dependencies.extend( + [upstream.node.label for upstream in channel.connections]) + node_dependencies = set(node_dependencies) + if node.label in node_dependencies: + raise RuntimeError( + "Detected a cycle in the data flow topology, unable to automate " + "the execution of non-DAGs." + ) + digraph[i] = {label_index_map[l] for l in node_dependencies} + + return digraph + + def _digraph_to_linear_order(self, digraph): + try: + # Topological sorting ensures that all input dependencies have been + # executed before the node depending on them gets run + # The flattened part is just that we don't care about topological + # generations that are mutually independent (inefficient but easier for now) + execution_order = toposort_flatten(digraph) + nodes = list(self.nodes.values()) + as_labels = [nodes[i].label for i in execution_order] + # Do dictionaries guarantee this to be in the same order as our earlier map? + return as_labels + except CircularDependencyError: + raise RuntimeError( + "Detected a cycle in the data flow topology, unable to automate the " + "execution of non-DAGs." + ) + + def _order_run_signals_linearly(self, execution_order: list[int]): + for i, label in enumerate(execution_order[:-1]): + next_node = execution_order[i + 1] + self.nodes[label] > self.nodes[next_node] + + def _restore_run_signals(self, run_signal_pairs_to_restore): + self._purge_existing_run_signals() + for pairs in run_signal_pairs_to_restore: + pairs[0].connect(pairs[1]) + @property def run_args(self) -> dict: return {"self": self} diff --git a/setup.py b/setup.py index 1487597f4..84f4d4a62 100644 --- a/setup.py +++ b/setup.py @@ -59,6 +59,7 @@ 'cloudpickle', 'python>=3.10', 'graphviz', + 'toposort', 'typeguard==4.1.0' ], 'tinybase': [ From efdbf483560e9305735d05e631f37bf1392d496b Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Sat, 9 Sep 2023 20:02:03 +0000 Subject: [PATCH 625/756] [dependabot skip] Update env file --- .binder/environment.yml | 1 + docs/environment.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.binder/environment.yml b/.binder/environment.yml index 987884164..3bbed17d9 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -14,6 +14,7 @@ dependencies: - scipy =1.11.1 - seaborn =0.12.2 - scikit-image =0.21.0 +- toposort - randspg =0.0.1 - boto3 =1.28.25 - moto =4.1.14 diff --git a/docs/environment.yml b/docs/environment.yml index 1ac522adf..bd03e9a2a 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -16,6 +16,7 @@ dependencies: - scipy =1.11.1 - seaborn =0.12.2 - scikit-image =0.21.0 +- toposort - randspg =0.0.1 - boto3 =1.28.25 - moto =4.1.14 From 9b70c4720bb7f57197e4235f10320d6bef5fbbc3 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Sat, 9 Sep 2023 17:15:53 -0700 Subject: [PATCH 626/756] Don't automate while loop execution Cyclic graphs require explicit execution flow instruction --- pyiron_contrib/workflow/meta.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index a6eaec532..faf545d85 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -304,6 +304,7 @@ def make_loop(macro): macro.inputs_map = {} if inputs_map is None else inputs_map macro.outputs_map = {} if outputs_map is None else outputs_map + macro.automate_execution = False return macro_node()(make_loop) From df6450383cf0342c9a138613864f65ffbbaaef5c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Sat, 9 Sep 2023 17:19:33 -0700 Subject: [PATCH 627/756] Don't automate on explicit non-automated test --- tests/unit/workflow/test_macro.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/workflow/test_macro.py b/tests/unit/workflow/test_macro.py index 696f7c4a0..9efba378d 100644 --- a/tests/unit/workflow/test_macro.py +++ b/tests/unit/workflow/test_macro.py @@ -202,6 +202,7 @@ def modified_start_macro(macro): macro.starting_nodes = [macro.b] m = Macro(modified_start_macro) + m.automate_execution = False self.assertIs( m.outputs.a__result.value, NotData, From 0749f1f050cd723aa6bd0333dd113f0b9614c46f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 11:13:17 -0700 Subject: [PATCH 628/756] Add a shortcut for just disconnecting the run signals --- pyiron_contrib/workflow/io.py | 9 ++++++ tests/unit/workflow/test_io.py | 50 ++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py index 2574afa54..772035cf0 100644 --- a/pyiron_contrib/workflow/io.py +++ b/pyiron_contrib/workflow/io.py @@ -215,6 +215,12 @@ class InputSignals(SignalIO): def _channel_class(self) -> type(InputSignal): return InputSignal + def disconnect_run(self) -> list[tuple[Channel, Channel]]: + try: + return self.run.disconnect_all() + except AttributeError: + return [] + class OutputSignals(SignalIO): @property @@ -248,6 +254,9 @@ def disconnect(self) -> list[tuple[Channel, Channel]]: destroyed_connections.extend(self.output.disconnect()) return destroyed_connections + def disconnect_run(self) -> list[tuple[Channel, Channel]]: + return self.input.disconnect_run() + @property def connected(self): return self.input.connected or self.output.connected diff --git a/tests/unit/workflow/test_io.py b/tests/unit/workflow/test_io.py index d3fd795d8..e32c3815c 100644 --- a/tests/unit/workflow/test_io.py +++ b/tests/unit/workflow/test_io.py @@ -1,8 +1,10 @@ from unittest import TestCase, skipUnless from sys import version_info -from pyiron_contrib.workflow.channels import InputData, OutputData -from pyiron_contrib.workflow.io import Inputs, Outputs +from pyiron_contrib.workflow.channels import ( + InputData, InputSignal, OutputData, OutputSignal +) +from pyiron_contrib.workflow.io import Inputs, Outputs, Signals class DummyNode: @@ -15,7 +17,7 @@ def update(self): @skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestIO(TestCase): +class TestDataIO(TestCase): @classmethod def setUp(self) -> None: @@ -135,3 +137,45 @@ def test_connections_property(self): msg="The IO connection found should be the same object as the channel " "connection" ) + +@skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") +class TestDataIO(TestCase): + def setUp(self) -> None: + node = DummyNode() + + def do_nothing(): + pass + + signals = Signals() + signals.input.run = InputSignal("run", node, do_nothing) + signals.input.foo = InputSignal("foo", node, do_nothing) + signals.output.ran = OutputSignal("ran", node) + signals.output.bar = OutputSignal("bar", node) + + signals.output.ran > signals.input.run + signals.output.ran > signals.input.foo + signals.output.bar > signals.input.run + signals.output.bar > signals.input.foo + + self.signals = signals + + def test_disconnect(self): + self.assertEqual( + 4, + len(self.signals.disconnect()), + msg="Disconnect should disconnect all on panels and the Signals super-panel" + ) + + def test_disconnect_run(self): + self.assertEqual( + 2, + len(self.signals.disconnect_run()), + msg="Should disconnect exactly everything connected to run" + ) + + no_run_signals = Signals() + self.assertEqual( + 0, + len(no_run_signals.disconnect_run()), + msg="If there is no run channel, the list of disconnections should be empty" + ) From f33018ee20bf52ccec3a80cd07f33d4691ecc9b3 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 11:16:15 -0700 Subject: [PATCH 629/756] Use new signals connection tool --- pyiron_contrib/workflow/composite.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 5c8409865..3e05ce8f5 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -195,17 +195,17 @@ def run_graph(self): return DotDict(self.outputs.to_value_dict()) def _run_linearly_through_dag(self): - disconnected_pairs = self._purge_existing_run_signals() + disconnected_pairs = self._disconnect_run() digraph = self._data_flow_as_node_digraph() execution_order = self._digraph_to_linear_order(digraph) self._order_run_signals_linearly(execution_order) self.nodes[execution_order[0]].run() self._restore_run_signals(disconnected_pairs) - def _purge_existing_run_signals(self) -> list[tuple[Channel, Channel]]: + def _disconnect_run(self) -> list[tuple[Channel, Channel]]: disconnected_pairs = [] for node in self.nodes.values(): - disconnected_pairs.extend(node.signals.input.run.disconnect()) + disconnected_pairs.extend(node.signals.disconnect_run()) return disconnected_pairs def _data_flow_as_node_digraph(self) -> dict[int, set[int]]: @@ -258,7 +258,7 @@ def _order_run_signals_linearly(self, execution_order: list[int]): self.nodes[label] > self.nodes[next_node] def _restore_run_signals(self, run_signal_pairs_to_restore): - self._purge_existing_run_signals() + self._disconnect_run() for pairs in run_signal_pairs_to_restore: pairs[0].connect(pairs[1]) From 898e413d17bdaedb030825743b80440f893df315 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 11:19:58 -0700 Subject: [PATCH 630/756] Refactor: rename --- pyiron_contrib/workflow/composite.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 3e05ce8f5..5433ec8eb 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -200,7 +200,7 @@ def _run_linearly_through_dag(self): execution_order = self._digraph_to_linear_order(digraph) self._order_run_signals_linearly(execution_order) self.nodes[execution_order[0]].run() - self._restore_run_signals(disconnected_pairs) + self._reconnect_run(disconnected_pairs) def _disconnect_run(self) -> list[tuple[Channel, Channel]]: disconnected_pairs = [] @@ -257,7 +257,7 @@ def _order_run_signals_linearly(self, execution_order: list[int]): next_node = execution_order[i + 1] self.nodes[label] > self.nodes[next_node] - def _restore_run_signals(self, run_signal_pairs_to_restore): + def _reconnect_run(self, run_signal_pairs_to_restore): self._disconnect_run() for pairs in run_signal_pairs_to_restore: pairs[0].connect(pairs[1]) From e0760e31d2f3a3b067ce013d92a216312f9ead38 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 11:39:13 -0700 Subject: [PATCH 631/756] Refactor: just renaming stuff and moving it around a bit --- pyiron_contrib/workflow/composite.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 5433ec8eb..b06cfd7c1 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -196,10 +196,8 @@ def run_graph(self): def _run_linearly_through_dag(self): disconnected_pairs = self._disconnect_run() - digraph = self._data_flow_as_node_digraph() - execution_order = self._digraph_to_linear_order(digraph) - self._order_run_signals_linearly(execution_order) - self.nodes[execution_order[0]].run() + starting_node = self._set_run_signals_to_linear() + starting_node.run() self._reconnect_run(disconnected_pairs) def _disconnect_run(self) -> list[tuple[Channel, Channel]]: @@ -208,7 +206,14 @@ def _disconnect_run(self) -> list[tuple[Channel, Channel]]: disconnected_pairs.extend(node.signals.disconnect_run()) return disconnected_pairs - def _data_flow_as_node_digraph(self) -> dict[int, set[int]]: + def _set_run_signals_to_linear(self) -> Node: + execution_order = self._sort_nodes_linearly_by_data_digraph() + for i, label in enumerate(execution_order[:-1]): + next_node = execution_order[i + 1] + self.nodes[label] > self.nodes[next_node] + return self.nodes[execution_order[0]] + + def _get_data_digraph(self) -> dict[int, set[int]]: """ A dictionary of node indices and its data input dependencies as indices, where the indices are drawn from order of appearance in `self.nodes`. @@ -235,12 +240,13 @@ def _data_flow_as_node_digraph(self) -> dict[int, set[int]]: return digraph - def _digraph_to_linear_order(self, digraph): + def _sort_nodes_linearly_by_data_digraph(self) -> list[str]: try: # Topological sorting ensures that all input dependencies have been # executed before the node depending on them gets run # The flattened part is just that we don't care about topological # generations that are mutually independent (inefficient but easier for now) + digraph = self._get_data_digraph() execution_order = toposort_flatten(digraph) nodes = list(self.nodes.values()) as_labels = [nodes[i].label for i in execution_order] @@ -252,11 +258,6 @@ def _digraph_to_linear_order(self, digraph): "execution of non-DAGs." ) - def _order_run_signals_linearly(self, execution_order: list[int]): - for i, label in enumerate(execution_order[:-1]): - next_node = execution_order[i + 1] - self.nodes[label] > self.nodes[next_node] - def _reconnect_run(self, run_signal_pairs_to_restore): self._disconnect_run() for pairs in run_signal_pairs_to_restore: From 61d46ca1d0d52f4207218d399f5e25ce7bfdce97 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 11:40:35 -0700 Subject: [PATCH 632/756] Format --- pyiron_contrib/workflow/composite.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index b06cfd7c1..b9cece574 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -229,7 +229,8 @@ def _get_data_digraph(self) -> dict[int, set[int]]: node_dependencies = [] for channel in node.inputs: node_dependencies.extend( - [upstream.node.label for upstream in channel.connections]) + [upstream.node.label for upstream in channel.connections] + ) node_dependencies = set(node_dependencies) if node.label in node_dependencies: raise RuntimeError( From b253ba57361566a6437bf3b2850acbd22844fd1c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 11:45:21 -0700 Subject: [PATCH 633/756] Remove upstream nodes This idea is superceded by the topological analysis --- pyiron_contrib/workflow/composite.py | 15 +----- tests/unit/workflow/test_macro.py | 74 ---------------------------- 2 files changed, 1 insertion(+), 88 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index b9cece574..bbe28290a 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -128,15 +128,6 @@ def to_dict(self): "nodes": {n.label: n.to_dict() for n in self.nodes.values()}, } - @property - def upstream_nodes(self) -> list[Node]: - """ - A list of owned nodes that receive no input from any other owned nodes. - """ - return [ - node for node in self.nodes.values() if not self.connects_to_input_of(node) - ] - def has_locally_scoped_connection(self, node_connections: list[Channel]) -> bool: """ Check whether connections are made to any (recursively) owned nodes. @@ -185,11 +176,7 @@ def run_graph(self): if self.automate_execution: self._run_linearly_through_dag() else: - starting_nodes = ( - self.upstream_nodes if self.starting_nodes is None - else self.starting_nodes - ) - for node in starting_nodes: + for node in self.starting_nodes: node.run() return DotDict(self.outputs.to_value_dict()) diff --git a/tests/unit/workflow/test_macro.py b/tests/unit/workflow/test_macro.py index 9efba378d..8b0362225 100644 --- a/tests/unit/workflow/test_macro.py +++ b/tests/unit/workflow/test_macro.py @@ -121,80 +121,6 @@ def nested_macro(macro): m = Macro(nested_macro) self.assertEqual(m(a__x=0).c__result, 5) - def test_upstream_detection(self): - def my_macro(macro): - macro.a = SingleValue(add_one, x=0) - macro.b = SingleValue(add_one, x=macro.a) - - m = Macro(my_macro) - self.assertTrue( - m.connects_to_input_of(m.b), - msg="b should have input from a" - ) - self.assertFalse( - m.connects_to_output_of(m.b), - msg="b should not show any local output connections" - ) - self.assertFalse( - m.connects_to_input_of(m.a), - msg="a should not show any local input connections" - ) - self.assertTrue( - m.connects_to_output_of(m.a), - msg="b should have input from a" - ) - self.assertEqual( - len(m.upstream_nodes), - 1, - msg="Only the a-node should have connected output but no connected input" - ) - self.assertIs(m.upstream_nodes[0], m.a) - - m2 = Macro(my_macro) - m.inputs.a__x = m2.outputs.b__result - self.assertIs( - m.upstream_nodes[0], - m.a, - msg="External connections should not impact upstream-ness" - ) - self.assertTrue( - m.connects_to_output_of(m2.b), - msg="Should be able to check if external nodes have local connections" - ) - - m.inputs.a__x = m.outputs.b__result # Infinite loop self-connection - self.assertEqual( - len(m.upstream_nodes), - 0, - msg="Internal connections _should_ impact upstream-ness" - ) - - m.b.disconnect() - self.assertEqual( - m.upstream_nodes[0], - m.a, - msg="After disconnecting the b-node, the a-node no longer has internal " - "input and should register as upstream again, regardless of whether its " - "output is connected to anything (which it isn't, since we fully " - "disconnected m.b)" - ) - - def deep_macro(macro): - macro.a = SingleValue(add_one, x=0) - macro.m = Macro(my_macro) - macro.m.inputs.a__x = macro.a - - nested = Macro(deep_macro) - plain = Macro(my_macro) - plain.inputs.a__x = nested.m.outputs.b__result - print(nested.m.outputs.labels) - self.assertTrue( - nested.connects_to_input_of(plain), - msg="A child of the nested macro has a connection to the plain macros" - "input, so the entire nested macro should count as having a " - "connection to the plain macro's input." - ) - def test_custom_start(self): def modified_start_macro(macro): macro.a = SingleValue(add_one, x=0) From 5495d61349b4f617e0c22a50499895b64fdb7ff6 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 11:49:33 -0700 Subject: [PATCH 634/756] Devolve the automatic running down to workflow Composite will build its innards only once --- pyiron_contrib/workflow/composite.py | 16 ++-------------- pyiron_contrib/workflow/workflow.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index bbe28290a..cf2bd9482 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -89,14 +89,12 @@ def __init__( strict_naming: bool = True, inputs_map: Optional[dict] = None, outputs_map: Optional[dict] = None, - automate_execution: bool = True, **kwargs, ): super().__init__(*args, label=label, parent=parent, **kwargs) self.strict_naming: bool = strict_naming self.inputs_map = inputs_map self.outputs_map = outputs_map - self.automate_execution = automate_execution self.nodes: DotDict[str:Node] = DotDict() self.starting_nodes: None | list[Node] = None self._creator = self.create @@ -173,20 +171,10 @@ def on_run(self): @staticmethod def run_graph(self): - if self.automate_execution: - self._run_linearly_through_dag() - else: - for node in self.starting_nodes: - node.run() - + for node in self.starting_nodes: + node.run() return DotDict(self.outputs.to_value_dict()) - def _run_linearly_through_dag(self): - disconnected_pairs = self._disconnect_run() - starting_node = self._set_run_signals_to_linear() - starting_node.run() - self._reconnect_run(disconnected_pairs) - def _disconnect_run(self) -> list[tuple[Channel, Channel]]: disconnected_pairs = [] for node in self.nodes.values(): diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 9b1a21cae..00e873ef9 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -10,6 +10,7 @@ from pyiron_contrib.workflow.composite import Composite from pyiron_contrib.workflow.io import Inputs, Outputs +from pyiron_contrib.workflow.util import DotDict if TYPE_CHECKING: @@ -159,6 +160,7 @@ def __init__( strict_naming: bool = True, inputs_map: Optional[dict] = None, outputs_map: Optional[dict] = None, + automate_execution: bool = True, ): super().__init__( label=label, @@ -167,6 +169,7 @@ def __init__( inputs_map=inputs_map, outputs_map=outputs_map, ) + self.automate_execution = automate_execution for node in nodes: self.add(node) @@ -179,6 +182,21 @@ def inputs(self) -> Inputs: def outputs(self) -> Outputs: return self._build_outputs() + @staticmethod + def run_graph(self): + if self.automate_execution: + self._run_linearly_through_dag() + output_dict = DotDict(self.outputs.to_value_dict()) + else: + output_dict = super().run_graph() + return output_dict + + def _run_linearly_through_dag(self): + disconnected_pairs = self._disconnect_run() + starting_node = self._set_run_signals_to_linear() + starting_node.run() + self._reconnect_run(disconnected_pairs) + def to_node(self): """ Export the workflow to a macro node, with the currently exposed IO mapped to From a98d9b01066c29384a62cf1c80b704f83569025f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 11:57:46 -0700 Subject: [PATCH 635/756] Streamline the run call --- pyiron_contrib/workflow/composite.py | 5 +++-- pyiron_contrib/workflow/workflow.py | 13 ++----------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index cf2bd9482..9f3c079ef 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -181,12 +181,13 @@ def _disconnect_run(self) -> list[tuple[Channel, Channel]]: disconnected_pairs.extend(node.signals.disconnect_run()) return disconnected_pairs - def _set_run_signals_to_linear(self) -> Node: + def _set_run_signals_to_linear(self): + self._disconnect_run() execution_order = self._sort_nodes_linearly_by_data_digraph() for i, label in enumerate(execution_order[:-1]): next_node = execution_order[i + 1] self.nodes[label] > self.nodes[next_node] - return self.nodes[execution_order[0]] + self.starting_nodes = [self.nodes[execution_order[0]]] def _get_data_digraph(self) -> dict[int, set[int]]: """ diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 00e873ef9..658146a25 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -185,17 +185,8 @@ def outputs(self) -> Outputs: @staticmethod def run_graph(self): if self.automate_execution: - self._run_linearly_through_dag() - output_dict = DotDict(self.outputs.to_value_dict()) - else: - output_dict = super().run_graph() - return output_dict - - def _run_linearly_through_dag(self): - disconnected_pairs = self._disconnect_run() - starting_node = self._set_run_signals_to_linear() - starting_node.run() - self._reconnect_run(disconnected_pairs) + self._set_run_signals_to_linear() + return super().run_graph(self) def to_node(self): """ From a797f3a4157c88731b868cbb908f62c37aff9110 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 13:44:58 -0700 Subject: [PATCH 636/756] Provide automatic execution graphs for macros at instantiation --- pyiron_contrib/workflow/macro.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pyiron_contrib/workflow/macro.py b/pyiron_contrib/workflow/macro.py index 64cb93791..1637dc1a2 100644 --- a/pyiron_contrib/workflow/macro.py +++ b/pyiron_contrib/workflow/macro.py @@ -130,6 +130,7 @@ def __init__( outputs_map=outputs_map, ) graph_creator(self) + self._configure_graph_execution() self._inputs: Inputs = self._build_inputs() self._outputs: Outputs = self._build_outputs() @@ -144,6 +145,27 @@ def inputs(self) -> Inputs: def outputs(self) -> Outputs: return self._outputs + def _configure_graph_execution(self): + run_signals = self._disconnect_run() + + has_signals = len(run_signals) > 0 + has_starters = len(self.starting_nodes) > 0 + + if has_signals and has_starters: + # Assume the user knows what they're doing + self._reconnect_run(run_signals) + elif not has_signals and not has_starters: + # Automate construction of the execution graph + self._set_run_signals_to_linear() + else: + raise ValueError( + f"The macro '{self.label}' has {len(run_signals)} run signals " + f"internally and {len(self.starting_nodes)} starting nodes. Either " + f"the entire execution graph must be specified manually, or both run " + f"signals and starting nodes must be left entirely unspecified for " + f"automatic construction of the execution graph." + ) + def to_workfow(self): raise NotImplementedError From c3b688b5fe1131a893d87358e1d96c8604b6dd3f Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 13:50:38 -0700 Subject: [PATCH 637/756] Update upstream/starting nodes docs --- pyiron_contrib/workflow/composite.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 9f3c079ef..323bb8282 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -53,19 +53,16 @@ class Composite(Node, ABC): requirement is still passed on to children. Attributes: - nodes (DotDict[pyiron_contrib.workflow.node,Node]): The owned nodes that + nodes (DotDict[pyiron_contrib.workflow.node.Node]): The owned nodes that form the composite subgraph. strict_naming (bool): When true, repeated assignment of a new node to an existing node label will raise an error, otherwise the label gets appended with an index and the assignment proceeds. (Default is true: disallow assigning to existing labels.) create (Creator): A tool for adding new nodes to this subgraph. - upstream_nodes (list[pyiron_contrib.workflow.node,Node]): All the owned - nodes that have output connections but no input connections, i.e. the - upstream-most nodes. - starting_nodes (None | list[pyiron_contrib.workflow.node,Node]): A subset - of the owned nodes to be used on running. (Default is None, running falls back - on using the `upstream_nodes`.) + starting_nodes (None | list[pyiron_contrib.workflow.node.Node]): A subset + of the owned nodes to be used on running. Only necessary if the execution graph + has been manually specified with `run` signals. (Default is an empty list.) wrap_as (Wrappers): A tool for accessing node-creating decorators Methods: @@ -96,7 +93,7 @@ def __init__( self.inputs_map = inputs_map self.outputs_map = outputs_map self.nodes: DotDict[str:Node] = DotDict() - self.starting_nodes: None | list[Node] = None + self.starting_nodes: list[Node] = [] self._creator = self.create self.create = self._owned_creator # Override the create method from the class From 32d6d3377aa111d0476f080e248cdda833436c09 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 14:40:48 -0700 Subject: [PATCH 638/756] Add a tool for finding the most proximate channel owner --- pyiron_contrib/workflow/channels.py | 34 +++++++++++++++++++++++++++++ tests/unit/workflow/test_macro.py | 19 ++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index 7a58b3e55..d7db630f9 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -32,6 +32,7 @@ ) if typing.TYPE_CHECKING: + from pyiron_contrib.workflow.composite import Composite from pyiron_contrib.workflow.node import Node @@ -140,6 +141,39 @@ def to_dict(self) -> dict: "connections": [f"{c.node.label}.{c.label}" for c in self.connections], } + def get_node_belonging_to(self, composite): + """ + Composite nodes directly expose the channels of their children. This happens + recursively such that `my_composite.inputs.foo` might belong to the deeply + nested node `my_composite.child.grandchild` etc. + + This method allows you to find the node from which this channel can be accessed + that is a direct child of a provided composite node. + + Args: + composite [Composite]: The node relative to which to search for access + to this channel. + + Returns: + Node: The direct child of the provided composite node that gives access to + this channel. + + Raises: + if the provided composite is not in this channel's parentage. + """ + node = self.node + parent = self.node.parent + while composite is not parent: + try: + node = parent + parent = node.parent + except AttributeError: + raise ValueError( + f"The channel {self.node.label}:{self.label} could not find the " + f"composite node {composite.label} in its parentage." + ) + return node + class NotData: """ diff --git a/tests/unit/workflow/test_macro.py b/tests/unit/workflow/test_macro.py index 8b0362225..89f9c6de0 100644 --- a/tests/unit/workflow/test_macro.py +++ b/tests/unit/workflow/test_macro.py @@ -117,10 +117,29 @@ def nested_macro(macro): macro.b = Macro(add_three_macro, one__x=macro.a) macro.c = SingleValue(add_one, x=macro.b.outputs.three__result) macro.a > macro.b > macro.c + macro.starting_nodes = [macro.a] + macro.outputs_map = {"b__two__result": "deep_output"} m = Macro(nested_macro) self.assertEqual(m(a__x=0).c__result, 5) + print(m.inputs, m.outputs) + with self.subTest("Test Channel.get_node_belonging_to"): + deep_channel = m.outputs.deep_output + self.assertIs( + m.b.three, + deep_channel.node, + msg="Channel node should be the node that holds it directly." + ) + self.assertIs( + m.b, + deep_channel.get_node_belonging_to(m) + ) + + with self.assertRaises(ValueError): + m2 = Macro(nested_macro) # Not in deep_channel's parentage! + deep_channel.get_node_belonging_to(m2) + def test_custom_start(self): def modified_start_macro(macro): macro.a = SingleValue(add_one, x=0) From 2abe4da2e9a615f86f8c0db7ee7a26d1a583f2d1 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 15:00:00 -0700 Subject: [PATCH 639/756] Use the new channel tool to find locally scoped data connections --- pyiron_contrib/workflow/composite.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 323bb8282..d6f5f0615 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -201,9 +201,15 @@ def _get_data_digraph(self) -> dict[int, set[int]]: node = self.nodes[label] node_dependencies = [] for channel in node.inputs: - node_dependencies.extend( - [upstream.node.label for upstream in channel.connections] - ) + locally_scoped_upstream_node_labels = [] + for upstream in channel.connections: + try: + locally_scoped_upstream_node_labels.append( + upstream.get_node_belonging_to(self).label + ) + except ValueError: + pass + node_dependencies.extend(locally_scoped_upstream_node_labels) node_dependencies = set(node_dependencies) if node.label in node_dependencies: raise RuntimeError( From 5dce0bc90349a5d8878b388831532abf1e5d6988 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 15:00:07 -0700 Subject: [PATCH 640/756] Update macro tests --- tests/unit/workflow/test_macro.py | 79 ++++++++++++++++++------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/tests/unit/workflow/test_macro.py b/tests/unit/workflow/test_macro.py index 89f9c6de0..47407a1a6 100644 --- a/tests/unit/workflow/test_macro.py +++ b/tests/unit/workflow/test_macro.py @@ -18,7 +18,6 @@ def add_three_macro(macro): macro.add(SingleValue(add_one, macro.two, label="three")) # Cover a handful of addition methods, # although these are more thoroughly tested in Workflow tests - macro.one > macro.two > macro.three @unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") @@ -35,14 +34,14 @@ def test_labels(self): m2 = Macro(add_three_macro, label=label) self.assertEqual(m2.label, label, msg="Should be able to specify a label") - def test_by_function(self): + def test_wrapper_function(self): m = Macro(add_three_macro) self.assertIs( m.outputs.three__result.value, NotData, msg="Output should be accessible with the usual naming convention, but we " - "asked the node not to run yet so there shouldn't be any data" + "have not run yet so there shouldn't be any data" ) input_x = 1 @@ -60,7 +59,7 @@ def test_by_function(self): msg="Macros should get output updated, just like other nodes" ) - def test_by_subclass(self): + def test_subclass(self): class MyMacro(Macro): def build_graph(self): add_three_macro(self) @@ -114,20 +113,23 @@ def test_key_map(self): def test_nesting(self): def nested_macro(macro): macro.a = SingleValue(add_one) - macro.b = Macro(add_three_macro, one__x=macro.a) + macro.b = Macro( + add_three_macro, + one__x=macro.a, + outputs_map={"two__result": "intermediate_result"} + ) macro.c = SingleValue(add_one, x=macro.b.outputs.three__result) macro.a > macro.b > macro.c macro.starting_nodes = [macro.a] - macro.outputs_map = {"b__two__result": "deep_output"} + macro.outputs_map = {"b__intermediate_result": "deep_output"} m = Macro(nested_macro) self.assertEqual(m(a__x=0).c__result, 5) - print(m.inputs, m.outputs) with self.subTest("Test Channel.get_node_belonging_to"): deep_channel = m.outputs.deep_output self.assertIs( - m.b.three, + m.b.two, deep_channel.node, msg="Channel node should be the node that holds it directly." ) @@ -140,36 +142,47 @@ def nested_macro(macro): m2 = Macro(nested_macro) # Not in deep_channel's parentage! deep_channel.get_node_belonging_to(m2) - def test_custom_start(self): - def modified_start_macro(macro): - macro.a = SingleValue(add_one, x=0) - macro.b = SingleValue(add_one, x=0) - macro.starting_nodes = [macro.b] + def test_execution_automation(self): + fully_automatic = add_three_macro - m = Macro(modified_start_macro) - m.automate_execution = False - self.assertIs( - m.outputs.a__result.value, - NotData, - msg="Node should not have run when the macro batch updated input" - ) - self.assertIs( - m.outputs.b__result.value, - NotData, - msg="Node should not have run when the macro batch updated input" - ) - m.run() - self.assertIs( - m.outputs.a__result.value, - NotData, - msg="Was not included in starting nodes, should not have run" + def fully_defined(macro): + add_three_macro(macro) + macro.one > macro.two > macro.three + macro.starting_nodes = [macro.one] + + def only_order(macro): + add_three_macro(macro) + macro.two > macro.three + + def only_starting(macro): + add_three_macro(macro) + macro.starting_nodes = [macro.one] + + m_auto = Macro(fully_automatic) + m_user = Macro(fully_defined) + + x = 0 + expected = add_one(add_one(add_one(x))) + self.assertEqual( + m_auto(one__x=x).three__result, + expected, + "DAG macros should run fine without user specification of execution." ) self.assertEqual( - m.outputs.b__result.value, - 1, - msg="Was included in starting nodes, should have run" + m_user(one__x=x).three__result, + expected, + "Macros should run fine if the user nicely specifies the exeuction graph." ) + with self.subTest("Partially specified execution should fail"): + # We don't yet check for _crappy_ user-defined execution, + # But we should make sure it's at least valid in principle + with self.assertRaises(ValueError): + Macro(only_order) + + with self.assertRaises(ValueError): + Macro(only_starting) + if __name__ == '__main__': unittest.main() From 27f2a9bebd5f7c57903ab443aff88180a09a0c37 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 15:04:23 -0700 Subject: [PATCH 641/756] Remove unnecessary flow control from workflow tests --- tests/unit/workflow/test_workflow.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index d7fead1ff..4b8787dfe 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -108,7 +108,6 @@ def test_workflow_io(self): wf.n3.inputs.x = wf.n2.outputs.y wf.n2.inputs.x = wf.n1.outputs.y - wf.n1 > wf.n2 > wf.n3 with self.subTest("Only unconnected channels should count"): self.assertEqual(len(wf.inputs), 1) @@ -221,8 +220,6 @@ def sum_(a, b): return a + b wf.sum = sum_(wf.a, wf.b) - wf.a > wf.b > wf.sum - wf.starting_nodes = [wf.a] wf.run() self.assertEqual( wf.a.outputs.y.value + wf.b.outputs.y.value, @@ -247,7 +244,6 @@ def test_return_value(self): wf = Workflow("wf") wf.a = wf.create.SingleValue(plus_one) wf.b = wf.create.SingleValue(plus_one, x=wf.a) - wf.a > wf.b with self.subTest("Run on main process"): return_on_call = wf(a__x=1) From 407937c0c6b45c932f6071b6dd390a1fc96eed1a Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 15:40:39 -0700 Subject: [PATCH 642/756] Raise the same type of error that toposort.CircularValueError is --- pyiron_contrib/workflow/composite.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index d6f5f0615..cb2daeb0a 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -192,7 +192,7 @@ def _get_data_digraph(self) -> dict[int, set[int]]: the indices are drawn from order of appearance in `self.nodes`. Raises: - RuntimeError: When a node appears in its own input. + ValueError: When a node appears in its own input. """ digraph = {} label_index_map = {n.label: i for i, n in enumerate(self.nodes.values())} @@ -212,9 +212,9 @@ def _get_data_digraph(self) -> dict[int, set[int]]: node_dependencies.extend(locally_scoped_upstream_node_labels) node_dependencies = set(node_dependencies) if node.label in node_dependencies: - raise RuntimeError( - "Detected a cycle in the data flow topology, unable to automate " - "the execution of non-DAGs." + raise ValueError( + f"Detected a cycle in the data flow topology, unable to automate " + f"the execution of non-DAGs: {node.label} appears in its own input." ) digraph[i] = {label_index_map[l] for l in node_dependencies} @@ -233,7 +233,7 @@ def _sort_nodes_linearly_by_data_digraph(self) -> list[str]: # Do dictionaries guarantee this to be in the same order as our earlier map? return as_labels except CircularDependencyError: - raise RuntimeError( + raise ValueError( "Detected a cycle in the data flow topology, unable to automate the " "execution of non-DAGs." ) From 552c0b6e6c4d25c8aaa7c1b8ce1f88d971987f7a Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 15:41:53 -0700 Subject: [PATCH 643/756] Update workflow tests --- tests/unit/workflow/test_workflow.py | 72 ++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py index 4b8787dfe..2becf7818 100644 --- a/tests/unit/workflow/test_workflow.py +++ b/tests/unit/workflow/test_workflow.py @@ -266,6 +266,78 @@ def test_return_value(self): # Note: We don't need to test running on an executor, because Workflows can't # do that yet + def test_execution_automation(self): + @Workflow.wrap_as.single_value_node("out") + def foo(x, y): + return x + y + + def make_workflow(): + wf = Workflow("dag") + wf.n1l = foo(0, 1) + wf.n1r = foo(2, 0) + wf.n2l = foo(-10, wf.n1l) + wf.n2m = foo(wf.n1l, wf.n1r) + wf.n2r = foo(wf.n1r, 10) + return wf + + def matches_expectations(results): + expected = {'n2l__out': -9, 'n2m__out': 3, 'n2r__out': 12} + return all(expected[k] == v for k, v in results.items()) + + auto = make_workflow() + self.assertTrue( + matches_expectations(auto()), + msg="DAGs should run automatically" + ) + + user = make_workflow() + user.automate_execution = False + user.n1l > user.n1r > user.n2l + user.n1r > user.n2m + user.n1r > user.n2r + user.starting_nodes = [user.n1l] + self.assertTrue( + matches_expectations(user()), + msg="Users shoudl be allowed to ask to run things manually" + ) + + self.assertIn( + user.n1r.signals.output.ran, + user.n2r.signals.input.run.connections, + msg="Expected execution signals as manually defined" + ) + user.automate_execution = True + self.assertTrue( + matches_expectations(user()), + msg="Users should be able to switch back to automatic execution" + ) + self.assertNotIn( + user.n1r.signals.output.ran, + user.n2r.signals.input.run.connections, + msg="Expected old execution signals to be overwritten" + ) + self.assertIn( + user.n2m.signals.output.ran, + user.n2r.signals.input.run.connections, + msg="At time of writing tests, automation makes a linear execution flow " + "based on node topology and initialized by the order of appearance in " + "the nodes list, so for a simple DAG like this the final node should " + "be getting triggered by the penultimate node." + "If this test failed, maybe you've written more sophisticated " + "automation." + ) + + with self.subTest("Make sure automated cyclic graphs throw an error"): + trivially_cyclic = make_workflow() + trivially_cyclic.n1l.inputs.y = trivially_cyclic.n1l + with self.assertRaises(ValueError): + trivially_cyclic() + + cyclic = make_workflow() + cyclic.n1l.inputs.y = cyclic.n2l + with self.assertRaises(ValueError): + cyclic() + if __name__ == '__main__': unittest.main() From 648e7e9140e60bd647dc37898b14e1b6218b332e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 15:52:12 -0700 Subject: [PATCH 644/756] Make the cyclic error a bit more informative --- pyiron_contrib/workflow/composite.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index cb2daeb0a..d940c9749 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -232,10 +232,15 @@ def _sort_nodes_linearly_by_data_digraph(self) -> list[str]: as_labels = [nodes[i].label for i in execution_order] # Do dictionaries guarantee this to be in the same order as our earlier map? return as_labels - except CircularDependencyError: + except CircularDependencyError as e: + nodes = list(self.nodes.values()) + cyclic_node_labels = { + nodes[k].label: " ".join([nodes[i].label for i in v]) + for k, v in e.data.items() + } raise ValueError( - "Detected a cycle in the data flow topology, unable to automate the " - "execution of non-DAGs." + f"Detected a cycle in the data flow topology, unable to automate the " + f"execution of non-DAGs: cycles found among {cyclic_node_labels}" ) def _reconnect_run(self, run_signal_pairs_to_restore): From 858f7f36b0f8773f027ce2650e87ed7c0ccc89d3 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 15:54:09 -0700 Subject: [PATCH 645/756] Remove unnecessary execution commands from meta --- pyiron_contrib/workflow/meta.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index faf545d85..20c744008 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -137,7 +137,6 @@ def make_loop(macro): # Connect each body node input to the input interface's respective output for body_node, out in zip(body_nodes, interface.outputs): body_node.inputs[label] = out - interface > body_node macro.inputs_map[f"{interface.label}__l"] = interface.label # TODO: Don't hardcode __l # Or distribute the same input to each node equally @@ -147,7 +146,6 @@ def make_loop(macro): ) for body_node in body_nodes: body_node.inputs[label] = interface - interface > body_node macro.inputs_map[f"{interface.label}__user_input"] = interface.label # TODO: Don't hardcode __user_input @@ -167,7 +165,6 @@ def make_loop(macro): "if the body nodes can run asynchronously we need something " "more clever than that!" ) - body_node > interface macro.outputs_map[ f"{interface.label}__{loop_body_class.__name__}__{label}" ] = interface.label From 47af2b610fd7dc657cfaeca05e2752fafadb9aed Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 16:05:37 -0700 Subject: [PATCH 646/756] Macro's don't have `automate_execution` --- pyiron_contrib/workflow/meta.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index 20c744008..0b5702ac6 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -301,7 +301,6 @@ def make_loop(macro): macro.inputs_map = {} if inputs_map is None else inputs_map macro.outputs_map = {} if outputs_map is None else outputs_map - macro.automate_execution = False return macro_node()(make_loop) From e744dd829220fb6f218badace8f33903c98bc620 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 16:09:16 -0700 Subject: [PATCH 647/756] Update integration tests --- tests/integration/test_workflow.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py index 0d59f1e18..e07d3f4a9 100644 --- a/tests/integration/test_workflow.py +++ b/tests/integration/test_workflow.py @@ -57,7 +57,7 @@ def numpy_sqrt(value=0): print(f"sqrt({value}) = {sqrt}") return sqrt - wf = Workflow("rand_until_big_then_sqrt") + wf = Workflow("rand_until_big_then_sqrt", automate_execution=False) wf.rand = numpy_randint() @@ -69,8 +69,9 @@ def numpy_sqrt(value=0): wf.gt_switch.signals.output.false > wf.rand > wf.gt_switch # Loop on false wf.gt_switch.signals.output.true > wf.sqrt # On true break to sqrt node + wf.starting_nodes = [wf.rand] - wf.rand.run() + wf.run() self.assertAlmostEqual( np.sqrt(wf.rand.outputs.rand.value), wf.sqrt.outputs.sqrt.value, 6 ) @@ -129,8 +130,6 @@ def greater_than(x: float, threshold: float): wf.random_while = RandomWhile() - wf.starting_nodes = [wf.random_while] - ## Give convenient labels wf.inputs_map = {"random_while__GreaterThan__threshold": "threshold"} wf.outputs_map = {"random_while__capped_result": "capped_result"} @@ -164,12 +163,17 @@ def less_than_ten(value): wf = Workflow("do_while") wf.add_while = AddWhile() - wf.starting_nodes = [wf.add_while] wf.inputs_map = { "add_while__a": "a", "add_while__b": "b" } wf.outputs_map = {"add_while__total": "total"} + # add_while has cyclic data connection at the level of the workflow + # we could go back and hide this cyclicity in a macro + # or we can just specify the execution flow for the workflow: + wf.automate_execution = False + wf.starting_nodes = [wf.add_while] + out = wf(a=1, b=2) self.assertEqual(out.total, 11) From 7934d4b71f35f6a68e3f78e4e1484a1f6d6fceeb Mon Sep 17 00:00:00 2001 From: liamhuber Date: Tue, 12 Sep 2023 16:14:45 -0700 Subject: [PATCH 648/756] Add dev comment --- tests/unit/workflow/test_macro.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/workflow/test_macro.py b/tests/unit/workflow/test_macro.py index 47407a1a6..9913adba0 100644 --- a/tests/unit/workflow/test_macro.py +++ b/tests/unit/workflow/test_macro.py @@ -121,6 +121,9 @@ def nested_macro(macro): macro.c = SingleValue(add_one, x=macro.b.outputs.three__result) macro.a > macro.b > macro.c macro.starting_nodes = [macro.a] + # This definition of the execution graph is not strictly necessary in this + # simple DAG case; we just do it to make sure nesting definied/automatic + # macros works ok macro.outputs_map = {"b__intermediate_result": "deep_output"} m = Macro(nested_macro) From 2bd79006ca60a5c44cf99afed53abb4ecaf5cdcc Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 13 Sep 2023 10:05:28 -0700 Subject: [PATCH 649/756] Add comment --- 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 d940c9749..27351a3c6 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -212,6 +212,9 @@ def _get_data_digraph(self) -> dict[int, set[int]]: node_dependencies.extend(locally_scoped_upstream_node_labels) node_dependencies = set(node_dependencies) if node.label in node_dependencies: + # the toposort library has a + # [known issue](https://gitlab.com/ericvsmith/toposort/-/issues/3) + # That self-dependency isn't caught, so we catch it manually here. raise ValueError( f"Detected a cycle in the data flow topology, unable to automate " f"the execution of non-DAGs: {node.label} appears in its own input." From 227b8c6a063a8f75801077e015b1aac41a7197d0 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 13 Sep 2023 13:52:50 -0700 Subject: [PATCH 650/756] Replace channel parentage method with node methods --- pyiron_contrib/workflow/channels.py | 33 ----------------- pyiron_contrib/workflow/node.py | 17 +++++++++ tests/unit/workflow/test_macro.py | 57 ++++++++++++++++++++++------- 3 files changed, 61 insertions(+), 46 deletions(-) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index d7db630f9..57f5f4a5a 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -141,39 +141,6 @@ def to_dict(self) -> dict: "connections": [f"{c.node.label}.{c.label}" for c in self.connections], } - def get_node_belonging_to(self, composite): - """ - Composite nodes directly expose the channels of their children. This happens - recursively such that `my_composite.inputs.foo` might belong to the deeply - nested node `my_composite.child.grandchild` etc. - - This method allows you to find the node from which this channel can be accessed - that is a direct child of a provided composite node. - - Args: - composite [Composite]: The node relative to which to search for access - to this channel. - - Returns: - Node: The direct child of the provided composite node that gives access to - this channel. - - Raises: - if the provided composite is not in this channel's parentage. - """ - node = self.node - parent = self.node.parent - while composite is not parent: - try: - node = parent - parent = node.parent - except AttributeError: - raise ValueError( - f"The channel {self.node.label}:{self.label} could not find the " - f"composite node {composite.label} in its parentage." - ) - return node - class NotData: """ diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py index 1be1c978a..49d6cff2b 100644 --- a/pyiron_contrib/workflow/node.py +++ b/pyiron_contrib/workflow/node.py @@ -376,3 +376,20 @@ def __gt__(self, other: InputSignal | Node): """ other.connect_output_signal(self.signals.output.ran) return True + + def get_parent_proximate_to(self, composite: Composite) -> Composite | None: + parent = self.parent + while parent is not None and parent.parent is not composite: + parent = parent.parent + return parent + + def get_first_shared_parent(self, other: Node) -> Composite | None: + our, their = self, other + while our.parent is not None: + while their.parent is not None: + if our.parent is their.parent: + return our.parent + their = their.parent + our = our.parent + their = other + return None diff --git a/tests/unit/workflow/test_macro.py b/tests/unit/workflow/test_macro.py index 9913adba0..380da01af 100644 --- a/tests/unit/workflow/test_macro.py +++ b/tests/unit/workflow/test_macro.py @@ -118,8 +118,16 @@ def nested_macro(macro): one__x=macro.a, outputs_map={"two__result": "intermediate_result"} ) - macro.c = SingleValue(add_one, x=macro.b.outputs.three__result) - macro.a > macro.b > macro.c + macro.c = Macro( + add_three_macro, + one__x=macro.b.outputs.three__result, + outputs_map={"two__result": "intermediate_result"} + ) + macro.d = SingleValue( + add_one, + x=macro.c.outputs.three__result, + ) + macro.a > macro.b > macro.c > macro.d macro.starting_nodes = [macro.a] # This definition of the execution graph is not strictly necessary in this # simple DAG case; we just do it to make sure nesting definied/automatic @@ -127,23 +135,46 @@ def nested_macro(macro): macro.outputs_map = {"b__intermediate_result": "deep_output"} m = Macro(nested_macro) - self.assertEqual(m(a__x=0).c__result, 5) + self.assertEqual(m(a__x=0).d__result, 8) + + m2 = Macro(nested_macro) - with self.subTest("Test Channel.get_node_belonging_to"): - deep_channel = m.outputs.deep_output + with self.subTest("Test Node.get_parent_proximate_to"): self.assertIs( - m.b.two, - deep_channel.node, - msg="Channel node should be the node that holds it directly." + m.b, + m.b.two.get_parent_proximate_to(m), + msg="Should return parent closest to the passed composite" + ) + + self.assertIsNone( + m.b.two.get_parent_proximate_to(m2), + msg="Should return None when composite is not in parentage" ) + + with self.subTest("Test Node.get_first_shared_parent"): self.assertIs( m.b, - deep_channel.get_node_belonging_to(m) + m.b.two.get_first_shared_parent(m.b.three), + msg="Should get the parent when parents are the same" + ) + self.assertIs( + m, + m.b.two.get_first_shared_parent(m.c.two), + msg="Should find first matching object in parentage" + ) + self.assertIs( + m, + m.b.two.get_first_shared_parent(m.d), + msg="Should work when depth is not equal" + ) + self.assertIsNone( + m.b.two.get_first_shared_parent(m2.b.two), + msg="Should return None when no shared parent exists" + ) + self.assertIsNone( + m.get_first_shared_parent(m.b), + msg="Should return None when parent is None" ) - - with self.assertRaises(ValueError): - m2 = Macro(nested_macro) # Not in deep_channel's parentage! - deep_channel.get_node_belonging_to(m2) def test_execution_automation(self): fully_automatic = add_three_macro From 3da5c2306e1db2118d5e7deb18f771bfd879ba08 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 13 Sep 2023 14:00:17 -0700 Subject: [PATCH 651/756] Debug and streamline DAG analysis --- pyiron_contrib/workflow/composite.py | 34 +++++++++------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 27351a3c6..7be4e47fe 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -195,21 +195,19 @@ def _get_data_digraph(self) -> dict[int, set[int]]: ValueError: When a node appears in its own input. """ digraph = {} - label_index_map = {n.label: i for i, n in enumerate(self.nodes.values())} - for label, i in label_index_map.items(): - node = self.nodes[label] + for node in self.nodes.values(): node_dependencies = [] for channel in node.inputs: - locally_scoped_upstream_node_labels = [] + locally_scoped_dependencies = [] for upstream in channel.connections: - try: - locally_scoped_upstream_node_labels.append( - upstream.get_node_belonging_to(self).label + if upstream.node.parent is self: + locally_scoped_dependencies.append(upstream.node.label) + elif channel.node.get_first_shared_parent(upstream.node) is self: + locally_scoped_dependencies.append( + upstream.node.get_parent_proximate_to(self).label ) - except ValueError: - pass - node_dependencies.extend(locally_scoped_upstream_node_labels) + node_dependencies.extend(locally_scoped_dependencies) node_dependencies = set(node_dependencies) if node.label in node_dependencies: # the toposort library has a @@ -219,7 +217,7 @@ def _get_data_digraph(self) -> dict[int, set[int]]: f"Detected a cycle in the data flow topology, unable to automate " f"the execution of non-DAGs: {node.label} appears in its own input." ) - digraph[i] = {label_index_map[l] for l in node_dependencies} + digraph[node.label] = node_dependencies return digraph @@ -229,21 +227,11 @@ def _sort_nodes_linearly_by_data_digraph(self) -> list[str]: # executed before the node depending on them gets run # The flattened part is just that we don't care about topological # generations that are mutually independent (inefficient but easier for now) - digraph = self._get_data_digraph() - execution_order = toposort_flatten(digraph) - nodes = list(self.nodes.values()) - as_labels = [nodes[i].label for i in execution_order] - # Do dictionaries guarantee this to be in the same order as our earlier map? - return as_labels + return toposort_flatten(self._get_data_digraph()) except CircularDependencyError as e: - nodes = list(self.nodes.values()) - cyclic_node_labels = { - nodes[k].label: " ".join([nodes[i].label for i in v]) - for k, v in e.data.items() - } raise ValueError( f"Detected a cycle in the data flow topology, unable to automate the " - f"execution of non-DAGs: cycles found among {cyclic_node_labels}" + f"execution of non-DAGs: cycles found among {e.data}" ) def _reconnect_run(self, run_signal_pairs_to_restore): From 05a2aecb2f30954572708daeef32d13259943315 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 13 Sep 2023 14:02:37 -0700 Subject: [PATCH 652/756] Remove manual specification of execution Not necessary now that data cycles inside macros are being properly encapsulated! --- tests/integration/test_workflow.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py index e07d3f4a9..013201d0c 100644 --- a/tests/integration/test_workflow.py +++ b/tests/integration/test_workflow.py @@ -169,11 +169,5 @@ def less_than_ten(value): } wf.outputs_map = {"add_while__total": "total"} - # add_while has cyclic data connection at the level of the workflow - # we could go back and hide this cyclicity in a macro - # or we can just specify the execution flow for the workflow: - wf.automate_execution = False - wf.starting_nodes = [wf.add_while] - out = wf(a=1, b=2) self.assertEqual(out.total, 11) From 41d5dafa0d3629b1494d923b7cae4c8349faba26 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 13 Sep 2023 14:14:03 -0700 Subject: [PATCH 653/756] Update the metanode docstrings --- pyiron_contrib/workflow/meta.py | 59 +++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index 0b5702ac6..774ed5d1c 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -74,9 +74,9 @@ def for_loop( Examples: >>> import numpy as np - >>> from pyiron_contrib.workflow.meta import for_loop + >>> from pyiron_contrib.workflow import Workflow >>> - >>> bulk_loop = for_loop( + >>> bulk_loop = Workflow.create.meta.for_loop( ... Workflow.create.atomistics.Bulk, ... 5, ... iterate_on = ("a",), @@ -201,6 +201,8 @@ def while_loop( internal_connection_map (list[tuple[str, str, str, str]]): String tuples giving (input node, input channel, output node, output channel) labels connecting channel pairs inside the macro. + inputs_map Optional[dict[str, str]]: The inputs map as usual for a macro. + outputs_map Optional[dict[str, str]]: The outputs map as usual for a macro. Examples: >>> from pyiron_contrib.workflow import Workflow >>> @@ -226,8 +228,7 @@ def while_loop( >>> >>> wf = Workflow("do_while") >>> wf.add_while = AddWhile() - - >>> wf.starting_nodes = [wf.add_while] + >>> >>> wf.inputs_map = { ... "add_while__a": "a", ... "add_while__b": "b" @@ -243,16 +244,13 @@ def while_loop( Finally, 11 >>> import numpy as np - >>> np.random.seed(0) # Just for docstring tests, so the output is predictable - >>> >>> from pyiron_contrib.workflow import Workflow >>> - >>> # Build tools + >>> np.random.seed(0) >>> - >>> @Workflow.wrap_as.single_value_node() + >>> @Workflow.wrap_as.single_value_node("random") >>> def random(length: int | None = None): - ... random = np.random.random(length) - ... return random + ... return np.random.random(length) >>> >>> @Workflow.wrap_as.single_value_node() >>> def greater_than(x: float, threshold: float): @@ -261,7 +259,12 @@ def while_loop( ... print(f"{x:.3f} {symbol} {threshold}") ... return gt >>> - >>> RandomWhile = Workflow.create.meta.while_loop(random) + >>> RandomWhile = Workflow.create.meta.while_loop( + ... loop_body_class=random, + ... condition_class=greater_than, + ... internal_connection_map=[("Random", "random", "GreaterThan", "x")], + ... outputs_map={"Random__random": "capped_result"} + ... ) >>> >>> # Define workflow >>> @@ -269,22 +272,30 @@ def while_loop( >>> >>> ## Wire together the while loop and its condition >>> - >>> wf.gt = greater_than() - >>> wf.random_while = RandomWhile(condition=wf.gt) - >>> wf.gt.inputs.x = wf.random_while.Random - >>> - >>> wf.starting_nodes = [wf.random_while] + >>> wf.random_while = RandomWhile() >>> >>> ## Give convenient labels - >>> wf.inputs_map = {"gt__threshold": "threshold"} - >>> wf.outputs_map = {"random_while__Random__random": "capped_value"} + >>> wf.inputs_map = {"random_while__GreaterThan__threshold": "threshold"} + >>> wf.outputs_map = {"random_while__capped_result": "capped_result"} + >>> >>> # Set a threshold and run - >>> - >>> print(f"Finally {wf(threshold=0.1).capped_value:.3f}") - - Note that we _need_ to specify a starting node whenever our graph is cyclic and - _all_ our nodes have connected input! We obviously cannot automatically detect - the "upstream-most" node in a circle! + >>> print(f"Finally {wf(threshold=0.1).capped_result:.3f}") + 0.549 > 0.1 + 0.715 > 0.1 + 0.603 > 0.1 + 0.545 > 0.1 + 0.424 > 0.1 + 0.646 > 0.1 + 0.438 > 0.1 + 0.892 > 0.1 + 0.964 > 0.1 + 0.383 > 0.1 + 0.792 > 0.1 + 0.529 > 0.1 + 0.568 > 0.1 + 0.926 > 0.1 + 0.071 <= 0.1 + Finally 0.071 """ def make_loop(macro): From 5bc57435eaaaf96b823a13b0a37673cbc1bcdc6c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 13 Sep 2023 14:20:23 -0700 Subject: [PATCH 654/756] Update function docs --- pyiron_contrib/workflow/function.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py index 95e5ed319..cacc1b291 100644 --- a/pyiron_contrib/workflow/function.py +++ b/pyiron_contrib/workflow/function.py @@ -92,7 +92,7 @@ class Function(Node): inputs (Inputs): A collection of input data channels. outputs (Outputs): A collection of output data channels. signals (Signals): A holder for input and output collections of signal channels. - ready (bool): All input reports ready, not running or failed. + ready (bool): All input reports ready, node is not running or failed. running (bool): Currently running. failed (bool): An exception was thrown when executing the node function. connected (bool): Any IO channel has at least one connection. @@ -103,6 +103,7 @@ class Function(Node): run: Parse and process the input, execute the engine, process the results and update the output. disconnect: Disconnect all data and signal IO connections. + update_input: Allows input channels' values to be updated without any running. Examples: At the most basic level, to use nodes all we need to do is provide the From e8dda66cfbcca6f76c8b678daf483e472f76d4fd Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 13 Sep 2023 14:51:29 -0700 Subject: [PATCH 655/756] Update composite and children Just docstrings and method renames --- pyiron_contrib/workflow/composite.py | 69 +++++++++++++++++----------- pyiron_contrib/workflow/macro.py | 48 ++++++++++++++----- pyiron_contrib/workflow/workflow.py | 24 ++++++---- 3 files changed, 94 insertions(+), 47 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 7be4e47fe..6f16155a1 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -40,11 +40,13 @@ class Composite(Node, ABC): instances, any created nodes get their `parent` attribute automatically set to the composite instance being used. - Specifies the required `on_run()` to call `run()` on a subset of owned nodes, i.e. - to kick-start computation on the owned sub-graph. - 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. + Specifies the required `on_run()` to call `run()` on a subset of owned + `starting_nodes`nodes to kick-start computation on the owned sub-graph. + Both the specification of these starting nodes and specifying execution signals to + propagate execution through the graph is left to the user/child classes. + In the case of non-cyclic workflows (i.e. DAGs in terms of data flow), both + starting nodes and execution flow can be specified by invoking `` + The `run()` method (and `update()`, and calling the workflow) return a new dot-accessible dictionary of keys and values created from the composite output IO panel. @@ -178,18 +180,47 @@ def _disconnect_run(self) -> list[tuple[Channel, Channel]]: disconnected_pairs.extend(node.signals.disconnect_run()) return disconnected_pairs - def _set_run_signals_to_linear(self): + def set_run_signals_to_dag_execution(self): + """ + Disconnects all `signals.input.run` connections among children and attempts to + reconnect these according to the DAG flow of the data. + + Raises: + ValueError: When the data connections do not form a DAG. + """ self._disconnect_run() - execution_order = self._sort_nodes_linearly_by_data_digraph() + self._set_run_connections_and_starting_nodes_according_to_linear_dag() + # TODO: Replace this linear setup with something more powerful + + def _set_run_connections_and_starting_nodes_according_to_linear_dag(self): + # This is the most primitive sort of topological exploitation we can do + # It is not efficient if the nodes have executors and can run in parallel + try: + # Topological sorting ensures that all input dependencies have been + # executed before the node depending on them gets run + # The flattened part is just that we don't care about topological + # generations that are mutually independent (inefficient but easier for now) + execution_order = toposort_flatten(self.get_data_digraph()) + except CircularDependencyError as e: + raise ValueError( + f"Detected a cycle in the data flow topology, unable to automate the " + f"execution of non-DAGs: cycles found among {e.data}" + ) + for i, label in enumerate(execution_order[:-1]): next_node = execution_order[i + 1] self.nodes[label] > self.nodes[next_node] self.starting_nodes = [self.nodes[execution_order[0]]] - def _get_data_digraph(self) -> dict[int, set[int]]: + def get_data_digraph(self) -> dict[str, set[str]]: """ - A dictionary of node indices and its data input dependencies as indices, where - the indices are drawn from order of appearance in `self.nodes`. + Builds a directed graph of node labels based on data connections between nodes + directly owned by this composite -- i.e. does not worry about data connections + which are entirely internal to an owned sub-graph. + + Returns: + dict[str, set[str]]: A dictionary of nodes and the nodes they depend on for + data. Raises: ValueError: When a node appears in its own input. @@ -221,24 +252,6 @@ def _get_data_digraph(self) -> dict[int, set[int]]: return digraph - def _sort_nodes_linearly_by_data_digraph(self) -> list[str]: - try: - # Topological sorting ensures that all input dependencies have been - # executed before the node depending on them gets run - # The flattened part is just that we don't care about topological - # generations that are mutually independent (inefficient but easier for now) - return toposort_flatten(self._get_data_digraph()) - except CircularDependencyError as e: - raise ValueError( - f"Detected a cycle in the data flow topology, unable to automate the " - f"execution of non-DAGs: cycles found among {e.data}" - ) - - def _reconnect_run(self, run_signal_pairs_to_restore): - self._disconnect_run() - for pairs in run_signal_pairs_to_restore: - pairs[0].connect(pairs[1]) - @property def run_args(self) -> dict: return {"self": self} diff --git a/pyiron_contrib/workflow/macro.py b/pyiron_contrib/workflow/macro.py index 1637dc1a2..9d2608941 100644 --- a/pyiron_contrib/workflow/macro.py +++ b/pyiron_contrib/workflow/macro.py @@ -29,6 +29,14 @@ class Macro(Composite): It is intended that subclasses override the initialization signature and provide the graph creation directly from their own method. + As with workflows, all DAG macros will determine their execution flow automatically, + if you have cycles in your data flow, or otherwise want more control over the + execution, all you need to do is specify the `node.signals.input.run` connections + and `starting_nodes` list yourself. + If only _one_ of these is specified, you'll get an error, but if you've provided + both then no further checks of their validity/reasonableness are performed, so be + careful. + Examples: Let's consider the simplest case of macros that just consecutively add 1 to their input: @@ -43,6 +51,11 @@ class Macro(Composite): ... macro.two = macro.create.SingleValue(add_one, macro.one) ... macro.three = macro.create.SingleValue(add_one, macro.two) ... macro.one > macro.two > macro.three + ... macro.starting_nodes = [macro.one] + + In this case we had _no need_ to specify the execution order and starting nodes + --it's just an extremely simple DAG after all! -- but it's done here to + demonstrate the syntax. We can make a macro by passing this graph-building function (that takes a macro as its first argument, i.e. `self` from the macro's perspective) to the `Macro` @@ -74,14 +87,13 @@ class Macro(Composite): 3 We can also nest macros, rename their IO, and provide access to - internally-connected IO: + internally-connected IO by inputs and outputs maps: >>> def nested_macro(macro): ... macro.a = macro.create.SingleValue(add_one) ... macro.b = macro.create.Macro(add_three_macro, one__x=macro.a) ... macro.c = macro.create.SingleValue( ... add_one, x=macro.b.outputs.three__result ... ) - ... macro.a > macro.b > macro.c >>> >>> macro = Macro( ... nested_macro, @@ -91,24 +103,33 @@ class Macro(Composite): >>> macro(inp=1) {'intermediate': 5, 'out': 6} - Macros and workflows automatically look for the upstream-most data nodes and use - those to start calculations when run. + Macros and workflows automatically generate execution flows when their data + is acyclic. Let's build a simple macro with two independent tracks: - >>> def modified_start_macro(macro): + >>> def modified_flow_macro(macro): ... macro.a = macro.create.SingleValue(add_one, x=0) ... macro.b = macro.create.SingleValue(add_one, x=0) + ... macro.c = macro.create.SingleValue(add_one, x=0) >>> >>> m = Macro(modified_start_macro) >>> m.outputs.to_value_dict() - >>> m(a__x=1, b__x=2) - {'a__result': 2, 'b__result': 3} + >>> m(a__x=1, b__x=2, c__x=3) + {'a__result': 2, 'b__result': 3, 'c__result': 4} We can override which nodes get used to start by specifying the `starting_nodes` property. + If we do this we also need to provide at least one connection among the run + signals, but beyond that the code doesn't hold our hands. Let's use this and then observe how the `a` sub-node no longer gets run: - >>> m.starting_nodes = [m.b] - >>> m(a__x=1000, b__x=2000) - {'a__result': 2, 'b__result': 2001} + >>> m.starting_nodes = [m.b] # At least one starting node + >>> m.b > m.c # At least one run signal + >>> m(a__x=1000, b__x=2000, c__x=3000) + {'a__result': 2, 'b__result': 2001, 'c__result': 3001} + + Note how the `a` node is no longer getting run, so the output is not updated! + Manually controlling execution flow is necessary for cyclic graphs (cf. the + while loop meta-node), but best to avoid when possible as it's easy to miss + intended connections in complex graphs. """ def __init__( @@ -156,7 +177,7 @@ def _configure_graph_execution(self): self._reconnect_run(run_signals) elif not has_signals and not has_starters: # Automate construction of the execution graph - self._set_run_signals_to_linear() + self.set_run_signals_to_dag_execution() else: raise ValueError( f"The macro '{self.label}' has {len(run_signals)} run signals " @@ -166,6 +187,11 @@ def _configure_graph_execution(self): f"automatic construction of the execution graph." ) + def _reconnect_run(self, run_signal_pairs_to_restore): + self._disconnect_run() + for pairs in run_signal_pairs_to_restore: + pairs[0].connect(pairs[1]) + def to_workfow(self): raise NotImplementedError diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py index 658146a25..35d4b2a11 100644 --- a/pyiron_contrib/workflow/workflow.py +++ b/pyiron_contrib/workflow/workflow.py @@ -10,7 +10,6 @@ from pyiron_contrib.workflow.composite import Composite from pyiron_contrib.workflow.io import Inputs, Outputs -from pyiron_contrib.workflow.util import DotDict if TYPE_CHECKING: @@ -93,9 +92,7 @@ class Workflow(Composite): >>> print(len(wf.inputs), len(wf.outputs)) 1 1 - We can define the execution flow by making connections between channels held - in the `signals` panels, but it's easier to use the `>` syntactic sugar: - >>> wf.first > wf.second + Then we just run the workflow >>> out = wf.run() The workflow joins node lavels and channel labels with a `_` character to @@ -121,7 +118,7 @@ class Workflow(Composite): >>> >>> wf.structure = wf.create.atomistics.Bulk( ... cubic=True, - ... element="Al" + ... name="Al" ... ) >>> wf.engine = wf.create.atomistics.Lammps(structure=wf.structure) >>> wf.calc = wf.create.atomistics.CalcMd( @@ -131,8 +128,13 @@ class Workflow(Composite): ... x=wf.calc.outputs.steps, ... y=wf.calc.outputs.temperature ... ) - >>> - >>> wf.structure > wf.engine > wf.calc > wf.plot + + We can give more convenient names to IO, and even access IO that would normally + be hidden (because it's connected) by specifying an `inputs_map` and/or + `outputs_map`. In the example above, let's make the resulting figure a bit + easier to find: + >>> wf.outputs_map = {"plot__fig": "fig"} + >>> wf().fig Workflows can be visualized in the notebook using graphviz: >>> wf.draw() @@ -140,6 +142,12 @@ class Workflow(Composite): The resulting object can be saved as an image, e.g. >>> wf.draw().render(filename="demo", format="png") + When your workflow's data follows a directed-acyclic pattern, it will determine + the execution flow automatically. + If you want or need more control, you can set the `automate_execution` flag to + `False` and manually specify an execution flow. + Cf. the + TODO: Workflows can be serialized. TODO: Once you're satisfied with how a workflow is structured, you can export it @@ -185,7 +193,7 @@ def outputs(self) -> Outputs: @staticmethod def run_graph(self): if self.automate_execution: - self._set_run_signals_to_linear() + self.set_run_signals_to_dag_execution() return super().run_graph(self) def to_node(self): From 18aaa10289b2d56f0df428452e86d5c7380cad36 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 13 Sep 2023 15:29:53 -0700 Subject: [PATCH 656/756] Allow composites to disable IO with their maps --- pyiron_contrib/workflow/composite.py | 3 ++- tests/unit/workflow/test_macro.py | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 6f16155a1..6ae7c4680 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -269,7 +269,8 @@ def _build_io( channel = panel[channel_label] default_key = f"{node.label}__{channel_label}" try: - io[key_map[default_key]] = channel + if key_map[default_key] is not None: + io[key_map[default_key]] = channel except KeyError: if not channel.connected: io[default_key] = channel diff --git a/tests/unit/workflow/test_macro.py b/tests/unit/workflow/test_macro.py index 380da01af..e4c45d466 100644 --- a/tests/unit/workflow/test_macro.py +++ b/tests/unit/workflow/test_macro.py @@ -82,7 +82,10 @@ def test_key_map(self): m = Macro( add_three_macro, inputs_map={"one__x": "my_input"}, - outputs_map={"three__result": "my_output", "two__result": "intermediate"}, + outputs_map={ + "three__result": "my_output", + "two__result": "intermediate" + }, ) self.assertSetEqual( set(m.inputs.labels), @@ -110,6 +113,23 @@ def test_key_map(self): "should be accessible" ) + with self.subTest("IO can be disabled"): + m = Macro( + add_three_macro, + inputs_map={"one__x": None}, + outputs_map={"three__result": None}, + ) + self.assertEqual( + len(m.inputs.labels), + 0, + msg="Only inputs should have been disabled" + ) + self.assertEqual( + len(m.outputs.labels), + 0, + msg="Only outputs should have been disabled" + ) + def test_nesting(self): def nested_macro(macro): macro.a = SingleValue(add_one) From 4a513db7d7c4842cd81cc7230a06d4022f446a11 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 13 Sep 2023 15:30:02 -0700 Subject: [PATCH 657/756] Update example notebook --- notebooks/workflow_example.ipynb | 2635 ++++++++---------------------- 1 file changed, 654 insertions(+), 1981 deletions(-) diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb index 68ac682ef..ed278dbad 100644 --- a/notebooks/workflow_example.ipynb +++ b/notebooks/workflow_example.ipynb @@ -11,9 +11,8 @@ "- 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", + "- SingleValue nodes and syntactic sugar\n", + "- Workflows: keeping your computational graphs organized\n", "- Using pre-defined nodes \n", "- Macro nodes" ] @@ -39,7 +38,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "6e3a162575ab4e43acfc08cf0664b9da", + "model_id": "89ec887909114967be06c171de9e83c6", "version_major": 2, "version_minor": 0 }, @@ -453,7 +452,9 @@ { "cell_type": "markdown", "id": "9b9220b0-833d-4c6a-9929-5dfa60a47d14", - "metadata": {}, + "metadata": { + "tags": [] + }, "source": [ "# Connecting nodes and controlling flow\n", "\n", @@ -472,7 +473,9 @@ "cell_type": "code", "execution_count": 17, "id": "2e418abf-7059-4e1e-9b9f-b3dc0a4b5e35", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -538,24 +541,6 @@ "print(t2.inputs.x, t2.outputs.double)" ] }, - { - "cell_type": "markdown", - "id": "93bb53d9-7044-4269-b532-3a50d6ebd656", - "metadata": {}, - "source": [ - "We can also chain together the signal fl" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "e367bc34-da37-4c31-97dd-d581c30952ca", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt" - ] - }, { "cell_type": "markdown", "id": "e5c531a3-77e4-48ad-a189-fed619e79baa", @@ -572,7 +557,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "id": "1a4e9693-0980-4435-aecc-3331d8b608dd", "metadata": {}, "outputs": [], @@ -584,7 +569,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "id": "7c4d314b-33bb-4a67-bfb9-ed77fba3949c", "metadata": {}, "outputs": [ @@ -623,7 +608,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "id": "61ae572f-197b-4a60-8d3e-e19c1b9cc6e2", "metadata": {}, "outputs": [ @@ -653,103 +638,60 @@ "print(t2)" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "7a6f2bce-6b5e-4321-9457-0a6790d2202a", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "6569014a-815b-46dd-8b47-4e1cd4584b3b", - "metadata": {}, - "outputs": [], - "source": [ - "@single_value_node()\n", - "def noise(length: int = 1):\n", - " array = np.random.rand(length)\n", - " return array\n", - "\n", - "@function_node()\n", - "def plot(x, y):\n", - " fig = plt.scatter(x, y)\n", - " return fig\n", - "\n", - "x = noise(length=10)\n", - "y = noise(length=10)\n", - "f = plot(x=x, y=y)" - ] - }, { "cell_type": "markdown", - "id": "4a717c47-9e5f-496a-82d9-ec5cb69f65e9", + "id": "b2e56a64-d053-4127-bb8c-069777c1c6b5", "metadata": {}, "source": [ - "Now that the plotting node has run, its input channels are no longer `ready` because they are waiting for a fresh update:" + "Nodes can take input from multiple sources, and we can chain together these execution orders:" ] }, { "cell_type": "code", - "execution_count": 24, - "id": "25f0495a-e85f-43b7-8a70-a2c9cbd51ebb", + "execution_count": 22, + "id": "6569014a-815b-46dd-8b47-4e1cd4584b3b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "(False, False)" + "array([0.45174171, 0.42157923, 0.505547 , 0.47028098, 0.43732173,\n", + " 0.50225988, 0.9376775 , 0.61550209, 0.81934053, 0.32220586])" ] }, - "execution_count": 24, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" - } - ], - "source": [ - "f.inputs.x.ready, f.inputs.y.ready" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "449ce797-be62-4211-b483-c717a3d70583", - "metadata": {}, - "outputs": [ + }, { "data": { + "image/png": "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", "text/plain": [ - "(False, False)" + "
" ] }, - "execution_count": 25, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "x.inputs.length = 20\n", - "f.inputs.x.ready, f.inputs.y.ready" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "7008b0fc-3644-401c-b49f-9c40f9d89ac4", - "metadata": {}, - "outputs": [], - "source": [ - "y.inputs.length = 20" - ] - }, - { - "cell_type": "markdown", - "id": "3dfc3f99-be35-4c3f-bf5d-6ff60749e4d2", - "metadata": {}, - "source": [ - "Note that in the second cell, `f` is trying to update itself as soon as its inputs are ready, so if we _hadn't_ set the `f.inputs.y` channel to wait for an update, we would have gotten an error from the plotting command due to the mis-matched lengths of the x- and y-arrays." + "import matplotlib.pyplot as plt\n", + "\n", + "@single_value_node()\n", + "def noise(length: int = 1):\n", + " array = np.random.rand(length)\n", + " return array\n", + "\n", + "@function_node()\n", + "def plot(x, y):\n", + " fig = plt.scatter(x, y)\n", + " return fig\n", + "\n", + "x = noise(length=10)\n", + "y = noise(length=10)\n", + "f = plot(x=x, y=y)\n", + "x > y > f\n", + "x()" ] }, { @@ -764,12 +706,22 @@ "`Workflow` also offers us a single point of entry to the codebase -- i.e. most of the time you shouldn't need the node imports used above, because the decorators are available right on the workflow class.\n", "\n", "We will also see here that we can our node output channels using the `output_labels: Optional[str | list[str] | tuple[str]` kwarg, in case they don't have a convenient name to start with.\n", - "This way we can always have convenient dot-based access (and tab completion) instead of having to access things by string-based keys." + "This way we can always have convenient dot-based access (and tab completion) instead of having to access things by string-based keys.\n", + "\n", + "Finally, when a workflow is run, unless its `automate_execution` flag has been set to `False` or the data connections form a cyclic graph, it will _automatically_ build the necessary run signals! That means for all directed acyclic graph (DAG) workflows, all we typically need to worry about is the data connections." + ] + }, + { + "cell_type": "markdown", + "id": "9b9d3881-3584-4d6f-8068-5eed05760c36", + "metadata": {}, + "source": [ + "Here is an example showing how `Workflow` can be used as a single-point-of-import for defining new nodes:" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 23, "id": "1cd000bd-9b24-4c39-9cac-70a3291d0660", "metadata": {}, "outputs": [], @@ -789,12 +741,14 @@ "source": [ "## Adding nodes to a workflow\n", "\n", + "Each node can belong to exactly one workflow...but how to we create a workflow and add nodes to it\n", + "\n", "All five of the approaches below are equivalent ways to add a node to a workflow. Note that when `create` is called from the workflow _class_ it just gives you access to the class being created; when it is called from a workflow _instance_, it wraps this class so that the created node has its parent value automatically set to the workflow instance that's creating it." ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 24, "id": "7964df3c-55af-4c25-afc5-9e07accb606a", "metadata": {}, "outputs": [ @@ -835,7 +789,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 25, "id": "809178a5-2e6b-471d-89ef-0797db47c5ad", "metadata": {}, "outputs": [ @@ -874,33 +828,9 @@ "id": "848a45a9-dfcc-4b9e-aec5-e879d88325a2", "metadata": {}, "source": [ - "When `run()` is called on a workflow, it will search through its owned nodes to see which have no internal data input connections and will call `run()` on each of these -- `a` and `b` in the example above. This behaviour can be overriden by manually setting the `starting_nodes` list attribute.\n", + "When `run()` is called on a workflow, it will call `run()` on each node in its `starting_nodes` list and rely on these to propagate the execution with their run signals. If your data flow is DAG-like, all of this gets handled automatically so you just need to call `run()` on the workflow.\n", "\n", - "Remaining execution flow still needs to be defined.\n", - "\n", - "To make sure we don't have a race condition between our branched input, let's use both of these features:" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "520ef824-19a3-403a-ba6f-76a8a2fdbf7b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "wf.starting_nodes = [wf.a]\n", - "wf.a > wf.b > wf.sum" + "If you do have cyclic data flows, or just want more control, you are still free to set the `starting_nodes` and run signals yourself, just don't forget to set `automate_execution=False` on the workflow." ] }, { @@ -913,7 +843,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 26, "id": "52c48d19-10a2-4c48-ae81-eceea4129a60", "metadata": {}, "outputs": [ @@ -923,7 +853,7 @@ "{'ay': 3, 'a + b + 2': 7}" ] }, - "execution_count": 31, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -943,7 +873,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 27, "id": "bb35ba3e-602d-4c9c-b046-32da9401dd1c", "metadata": {}, "outputs": [ @@ -953,7 +883,7 @@ "(7, 3)" ] }, - "execution_count": 32, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -972,7 +902,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 28, "id": "2b0d2c85-9049-417b-8739-8a8432a1efbe", "metadata": {}, "outputs": [ @@ -1284,10 +1214,10 @@ "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 33, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -1308,7 +1238,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 29, "id": "ae500d5e-e55b-432c-8b5f-d5892193cdf5", "metadata": {}, "outputs": [ @@ -1322,10 +1252,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 34, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" }, @@ -1358,16 +1288,16 @@ }, { "cell_type": "markdown", - "id": "fa52dae9-8b3e-4de5-a916-eb548f7b9845", + "id": "43c09aa8-8229-4636-aaeb-9214b723c2fc", "metadata": {}, "source": [ - "Nodes and workflows can be visualized using graphviz:" + "In case you want to see more or less of the inner workings of the nodes when visualizing a workflow, you can modify the `depth` parameter, which controls how deeply child nodes are decomposed. E.g. we can force our workflow to only show us it's basic IO by setting `depth=0`:" ] }, { "cell_type": "code", - "execution_count": 35, - "id": "be3dd2a3-0cb2-4fc4-a07f-7ec719bbc6c9", + "execution_count": 30, + "id": "2114d0c3-cdad-43c7-9ffa-50c36d56d18f", "metadata": {}, "outputs": [ { @@ -1379,900 +1309,64 @@ "\n", "\n", - "\n", - "\n", + "\n", + "\n", "clusterwith_prebuilt\n", - "\n", - "with_prebuilt: Workflow\n", + "\n", + "with_prebuilt: Workflow\n", "\n", "clusterwith_prebuiltInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", "clusterwith_prebuiltOutputs\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clusterwith_prebuiltstructure\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "structure: Bulk\n", - "\n", - "\n", - "clusterwith_prebuiltstructureInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterwith_prebuiltstructureOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clusterwith_prebuiltengine\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "engine: Lammps\n", - "\n", - "\n", - "clusterwith_prebuiltengineInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterwith_prebuiltengineOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clusterwith_prebuiltcalc\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "calc: CalcMd\n", - "\n", - "\n", - "clusterwith_prebuiltcalcInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clusterwith_prebuiltplot\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "plot: Scatter\n", - "\n", - "\n", - "clusterwith_prebuiltplotInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterwith_prebuiltplotOutputs\n", - "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", "\n", "clusterwith_prebuiltOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsname\n", - "\n", - "name\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureInputsname\n", - "\n", - "name\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsname->clusterwith_prebuiltstructureInputsname\n", - "\n", - "\n", - "\n", + "\n", + "name\n", "\n", "\n", "\n", "clusterwith_prebuiltInputscrystalstructure\n", - "\n", - "crystalstructure\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureInputscrystalstructure\n", - "\n", - "crystalstructure\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputscrystalstructure->clusterwith_prebuiltstructureInputscrystalstructure\n", - "\n", - "\n", - "\n", + "\n", + "crystalstructure\n", "\n", "\n", "\n", "clusterwith_prebuiltInputsa\n", - "\n", - "a\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureInputsa\n", - "\n", - "a\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsa->clusterwith_prebuiltstructureInputsa\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsc\n", - "\n", - "c\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureInputsc\n", - "\n", - "c\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsc->clusterwith_prebuiltstructureInputsc\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputscovera\n", - "\n", - "covera\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureInputscovera\n", - "\n", - "covera\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputscovera->clusterwith_prebuiltstructureInputscovera\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsu\n", - "\n", - "u\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureInputsu\n", - "\n", - "u\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsu->clusterwith_prebuiltstructureInputsu\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsorthorhombic\n", - "\n", - "orthorhombic\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureInputsorthorhombic\n", - "\n", - "orthorhombic\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsorthorhombic->clusterwith_prebuiltstructureInputsorthorhombic\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputscubic\n", - "\n", - "cubic\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureInputscubic\n", - "\n", - "cubic\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputscubic->clusterwith_prebuiltstructureInputscubic\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcInputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsn_ionic_steps->clusterwith_prebuiltcalcInputsn_ionic_steps\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsn_print\n", - "\n", - "n_print: int\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcInputsn_print\n", - "\n", - "n_print: int\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsn_print->clusterwith_prebuiltcalcInputsn_print\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputstemperature\n", - "\n", - "temperature\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcInputstemperature\n", - "\n", - "temperature\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputstemperature->clusterwith_prebuiltcalcInputstemperature\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputspressure\n", - "\n", - "pressure\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcInputspressure\n", - "\n", - "pressure\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputspressure->clusterwith_prebuiltcalcInputspressure\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputscells\n", - "\n", - "cells\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsdisplacements\n", - "\n", - "displacements\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsenergy_pot\n", - "\n", - "energy_pot\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsenergy_tot\n", - "\n", - "energy_tot\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsforce_max\n", - "\n", - "force_max\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsforces\n", - "\n", - "forces\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsindices\n", - "\n", - "indices\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputspositions\n", - "\n", - "positions\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputspressures\n", - "\n", - "pressures\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputstotal_displacements\n", - "\n", - "total_displacements\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsvolume\n", - "\n", - "volume\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsfig\n", - "\n", - "fig\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltengineInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureOutputsran->clusterwith_prebuiltengineInputsrun\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureOutputsstructure\n", - "\n", - "structure\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltengineInputsstructure\n", - "\n", - "structure: Optional\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltstructureOutputsstructure->clusterwith_prebuiltengineInputsstructure\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltengineOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltengineOutputsran->clusterwith_prebuiltcalcInputsrun\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltengineOutputsjob\n", - "\n", - "job: Lammps\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcInputsjob\n", - "\n", - "job: AtomisticGenericJob\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltengineOutputsjob->clusterwith_prebuiltcalcInputsjob\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltplotInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsran->clusterwith_prebuiltplotInputsrun\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputscells\n", - "\n", - "cells\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputscells->clusterwith_prebuiltOutputscells\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsdisplacements\n", - "\n", - "displacements\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsdisplacements->clusterwith_prebuiltOutputsdisplacements\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsenergy_pot\n", - "\n", - "energy_pot\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsenergy_pot->clusterwith_prebuiltOutputsenergy_pot\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsenergy_tot\n", - "\n", - "energy_tot\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsenergy_tot->clusterwith_prebuiltOutputsenergy_tot\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsforce_max\n", - "\n", - "force_max\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsforce_max->clusterwith_prebuiltOutputsforce_max\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsforces\n", - "\n", - "forces\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsforces->clusterwith_prebuiltOutputsforces\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsindices\n", - "\n", - "indices\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsindices->clusterwith_prebuiltOutputsindices\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputspositions\n", - "\n", - "positions\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputspositions->clusterwith_prebuiltOutputspositions\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputspressures\n", - "\n", - "pressures\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputspressures->clusterwith_prebuiltOutputspressures\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputssteps\n", - "\n", - "steps\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltplotInputsx\n", - "\n", - "x: Union\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputssteps->clusterwith_prebuiltplotInputsx\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputstemperature\n", - "\n", - "temperature\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltplotInputsy\n", - "\n", - "y: Union\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputstemperature->clusterwith_prebuiltplotInputsy\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputstotal_displacements\n", - "\n", - "total_displacements\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputstotal_displacements->clusterwith_prebuiltOutputstotal_displacements\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsunwrapped_positions->clusterwith_prebuiltOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsvolume\n", - "\n", - "volume\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltcalcOutputsvolume->clusterwith_prebuiltOutputsvolume\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltplotOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltplotOutputsfig\n", - "\n", - "fig\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltplotOutputsfig->clusterwith_prebuiltOutputsfig\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 35, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "wf.draw()" - ] - }, - { - "cell_type": "markdown", - "id": "43c09aa8-8229-4636-aaeb-9214b723c2fc", - "metadata": {}, - "source": [ - "In case you want to see more or less of the inner workings of the nodes, you can modify the `depth` parameter, which controls how deeply child nodes are decomposed. E.g. we can force our workflow to only show us it's basic IO by setting `depth=0`:" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "2114d0c3-cdad-43c7-9ffa-50c36d56d18f", - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuilt\n", - "\n", - "with_prebuilt: Workflow\n", - "\n", - "clusterwith_prebuiltInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterwith_prebuiltOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsname\n", - "\n", - "name\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputscrystalstructure\n", - "\n", - "crystalstructure\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", "\n", @@ -2410,10 +1504,10 @@ "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 36, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -2434,7 +1528,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 31, "id": "c71a8308-f8a1-4041-bea0-1c841e072a6d", "metadata": {}, "outputs": [], @@ -2444,7 +1538,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 32, "id": "2b9bb21a-73cd-444e-84a9-100e202aa422", "metadata": {}, "outputs": [ @@ -2454,7 +1548,7 @@ "13" ] }, - "execution_count": 38, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -2473,11 +1567,11 @@ " macro.add_one = add_one(0)\n", " macro.add_two = add_one(macro.add_one)\n", " macro.add_three = add_one(macro.add_two)\n", - " # macro.starting_nodes = [macro.add_one] \n", - " # Setting this starting node is silly, since as the head-most node \n", - " # it is the starting node anyway; the point is you have access to the \n", - " # macro object and can do these sorts of setup proceedures here\n", + " # Just like workflows, for simple DAG macros we don't _need_\n", + " # to set signals and starting nodes -- the macro will build them\n", + " # automatically. But, if you do set both then the macro will use them\n", " macro.add_one > macro.add_two > macro.add_three\n", + " macro.starting_nodes = [macro.add_one] \n", " \n", "macro = Macro(add_three_macro)\n", "macro(add_one__x=10).add_three__result" @@ -2493,7 +1587,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 33, "id": "3668f9a9-adca-48a4-84ea-13add965897c", "metadata": {}, "outputs": [ @@ -2503,7 +1597,7 @@ "{'intermediate': 102, 'plus_three': 103}" ] }, - "execution_count": 39, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -2521,7 +1615,6 @@ " macro.add_three = add_one(macro.add_two)\n", " macro.inputs_map = {\"add_one__x\": \"x\"}\n", " macro.outputs_map = {\"add_three__result\": \"plus_three\", \"add_two__result\": \"intermediate\"}\n", - " macro.add_one > macro.add_two > macro.add_three\n", " \n", "macro = add_three_macro()\n", "macro(x=100)\n", @@ -2542,7 +1635,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 34, "id": "9aaeeec0-5f88-4c94-a6cc-45b56d2f0111", "metadata": {}, "outputs": [], @@ -2553,8 +1646,6 @@ " macro.engine = macro.create.atomistics.Lammps(structure=macro.structure)\n", " macro.calc = macro.create.atomistics.CalcMin(job=macro.engine, pressure=0)\n", " \n", - " macro.structure > macro.engine > macro.calc\n", - " \n", " macro.inputs_map = {\n", " \"structure__name\": \"element\", \n", " \"structure__crystalstructure\": \"crystalstructure\",\n", @@ -2564,12 +1655,6 @@ " \"calc__energy_pot\": \"energy\",\n", " \"structure__structure\": \"structure\",\n", " }\n", - " \n", - " # macro.starting_nodes = [macro.structure]\n", - " # Note: We _could_ customize macro features like the starting nodes here.\n", - " # For this particular case we don't need to, since macro.structure will \n", - " # be automatically detected as the \"upstream-most\" node, since it receives\n", - " # no input from any other nodes belonging to this macro.\n", "\n", "@Workflow.wrap_as.single_value_node()\n", "def per_atom_energy_difference(structure1, energy1, structure2, energy2):\n", @@ -2579,7 +1664,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 35, "id": "a832e552-b3cc-411a-a258-ef21574fc439", "metadata": {}, "outputs": [], @@ -2595,10 +1680,6 @@ " wf.min_phase2.outputs.energy,\n", ")\n", "\n", - "wf.element > wf.min_phase1 > wf.min_phase2 > wf.compare\n", - "# We stopped all the elements inside lammps_minimize from running on update\n", - "# So we'll need to hit the macro with an explicit run command\n", - "\n", "wf.inputs_map = {\n", " \"element__user_input\": \"element\",\n", " \"min_phase1__crystalstructure\": \"phase1\",\n", @@ -2610,7 +1691,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 36, "id": "b764a447-236f-4cb7-952a-7cba4855087d", "metadata": {}, "outputs": [ @@ -2623,1110 +1704,1089 @@ "\n", "\n", - "\n", - "\n", + "\n", + "\n", "clusterphase_preference\n", - "\n", + "\n", "phase_preference: Workflow\n", "\n", "clusterphase_preferenceInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", "clusterphase_preferenceOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "clusterphase_preferenceelement\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "element: UserInput\n", + "\n", + "element: UserInput\n", "\n", "\n", "clusterphase_preferenceelementInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", "clusterphase_preferenceelementOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "clusterphase_preferencemin_phase1\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "min_phase1: LammpsMinimize\n", + "\n", + "min_phase1: LammpsMinimize\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "clusterphase_preferencemin_phase2\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "min_phase2: LammpsMinimize\n", + "\n", + "min_phase2: LammpsMinimize\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "clusterphase_preferencecompare\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "compare: PerAtomEnergyDifference\n", + "\n", + "compare: PerAtomEnergyDifference\n", "\n", "\n", "clusterphase_preferencecompareInputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Inputs\n", + "\n", + "Inputs\n", "\n", "\n", "clusterphase_preferencecompareOutputs\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", - "Outputs\n", + "\n", + "Outputs\n", "\n", "\n", "\n", "clusterphase_preferenceInputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsuser_input\n", - "\n", - "user_input\n", + "\n", + "user_input\n", "\n", "\n", "\n", "clusterphase_preferenceelementInputsuser_input\n", - "\n", - "user_input\n", + "\n", + "user_input\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsuser_input->clusterphase_preferenceelementInputsuser_input\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputscrystalstructure->clusterphase_preferencemin_phase1Inputscrystalstructure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputscrystalstructure\n", - "\n", - "crystalstructure\n", + "\n", + "crystalstructure\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputscrystalstructure->clusterphase_preferencemin_phase2Inputscrystalstructure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsa->clusterphase_preferencemin_phase1Inputsa\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsa\n", - "\n", - "a\n", + "\n", + "a\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsa->clusterphase_preferencemin_phase2Inputsa\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsc->clusterphase_preferencemin_phase1Inputsc\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsc\n", - "\n", - "c\n", + "\n", + "c\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsc->clusterphase_preferencemin_phase2Inputsc\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputscovera->clusterphase_preferencemin_phase1Inputscovera\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputscovera\n", - "\n", - "covera\n", + "\n", + "covera\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputscovera->clusterphase_preferencemin_phase2Inputscovera\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsu->clusterphase_preferencemin_phase1Inputsu\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsu\n", - "\n", - "u\n", + "\n", + "u\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsu->clusterphase_preferencemin_phase2Inputsu\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsorthorhombic->clusterphase_preferencemin_phase1Inputsorthorhombic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsorthorhombic\n", - "\n", - "orthorhombic\n", + "\n", + "orthorhombic\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsorthorhombic->clusterphase_preferencemin_phase2Inputsorthorhombic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputscubic->clusterphase_preferencemin_phase1Inputscubic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputscubic\n", - "\n", - "cubic\n", + "\n", + "cubic\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputscubic->clusterphase_preferencemin_phase2Inputscubic\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsn_ionic_steps->clusterphase_preferencemin_phase1Inputsn_ionic_steps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", + "\n", + "n_ionic_steps: int\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsn_ionic_steps->clusterphase_preferencemin_phase2Inputsn_ionic_steps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsn_print->clusterphase_preferencemin_phase1Inputsn_print\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsn_print\n", - "\n", - "n_print: int\n", + "\n", + "n_print: int\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputsn_print->clusterphase_preferencemin_phase2Inputsn_print\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceInputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputspressure->clusterphase_preferencemin_phase1Inputspressure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputspressure\n", - "\n", - "pressure\n", + "\n", + "pressure\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceInputspressure->clusterphase_preferencemin_phase2Inputspressure\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferenceOutputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", "\n", "clusterphase_preferenceOutputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", "\n", "clusterphase_preferenceOutputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", "\n", "clusterphase_preferenceOutputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", "\n", "clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", "\n", "clusterphase_preferenceOutputsde\n", - "\n", - "de\n", + "\n", + "de\n", "\n", "\n", "\n", "clusterphase_preferenceelementInputsrun\n", - "\n", - "run\n", + "\n", + "run\n", "\n", "\n", "\n", "clusterphase_preferenceelementOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Inputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceelementOutputsran->clusterphase_preferencemin_phase1Inputsrun\n", - "\n", - "\n", - "\n", - "\n", "\n", "\n", "clusterphase_preferenceelementOutputsuser_input\n", - "\n", - "user_input\n", + "\n", + "user_input\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Inputsname\n", - "\n", - "name\n", + "\n", + "name\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceelementOutputsuser_input->clusterphase_preferencemin_phase1Inputsname\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Inputsname\n", - "\n", - "name\n", + "\n", + "name\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferenceelementOutputsuser_input->clusterphase_preferencemin_phase2Inputsname\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase1Inputsrun\n", + "\n", + "run\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Inputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsran->clusterphase_preferencemin_phase2Inputsrun\n", - "\n", - "\n", - "\n", - "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsstructure\n", - "\n", - "structure\n", + "\n", + "structure\n", "\n", "\n", "\n", "clusterphase_preferencecompareInputsstructure1\n", - "\n", - "structure1\n", + "\n", + "structure1\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsstructure->clusterphase_preferencecompareInputsstructure1\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputscells->clusterphase_preferenceOutputscells\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsdisplacements->clusterphase_preferenceOutputsdisplacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsenergy_pot\n", - "\n", - "energy_pot\n", + "\n", + "energy_pot\n", "\n", "\n", "\n", "clusterphase_preferencecompareInputsenergy1\n", - "\n", - "energy1\n", + "\n", + "energy1\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsenergy_pot->clusterphase_preferencecompareInputsenergy1\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsenergy_tot->clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsforce_max->clusterphase_preferenceOutputsforce_max\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsforces->clusterphase_preferenceOutputsforces\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsindices->clusterphase_preferenceOutputsindices\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputspositions->clusterphase_preferenceOutputspositions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputspressures->clusterphase_preferenceOutputspressures\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputssteps->clusterphase_preferenceOutputssteps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputstotal_displacements->clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsunwrapped_positions->clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase1Outputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase1Outputsvolume->clusterphase_preferenceOutputsvolume\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencemin_phase2Inputsrun\n", + "\n", + "run\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", - "\n", - "\n", - "clusterphase_preferencecompareInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsran->clusterphase_preferencecompareInputsrun\n", - "\n", - "\n", - "\n", - "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsstructure\n", - "\n", - "structure\n", + "\n", + "structure\n", "\n", "\n", "\n", "clusterphase_preferencecompareInputsstructure2\n", - "\n", - "structure2\n", + "\n", + "structure2\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsstructure->clusterphase_preferencecompareInputsstructure2\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputscells\n", - "\n", - "cells\n", + "\n", + "cells\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputscells->clusterphase_preferenceOutputscells\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsdisplacements\n", - "\n", - "displacements\n", + "\n", + "displacements\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsdisplacements->clusterphase_preferenceOutputsdisplacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsenergy_pot\n", - "\n", - "energy_pot\n", + "\n", + "energy_pot\n", "\n", "\n", "\n", "clusterphase_preferencecompareInputsenergy2\n", - "\n", - "energy2\n", + "\n", + "energy2\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsenergy_pot->clusterphase_preferencecompareInputsenergy2\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsenergy_tot\n", - "\n", - "energy_tot\n", + "\n", + "energy_tot\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsenergy_tot->clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforce_max\n", - "\n", - "force_max\n", + "\n", + "force_max\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsforce_max->clusterphase_preferenceOutputsforce_max\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsforces\n", - "\n", - "forces\n", + "\n", + "forces\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsforces->clusterphase_preferenceOutputsforces\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsindices\n", - "\n", - "indices\n", + "\n", + "indices\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsindices->clusterphase_preferenceOutputsindices\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspositions\n", - "\n", - "positions\n", + "\n", + "positions\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputspositions->clusterphase_preferenceOutputspositions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputspressures\n", - "\n", - "pressures\n", + "\n", + "pressures\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputspressures->clusterphase_preferenceOutputspressures\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputssteps\n", - "\n", - "steps\n", + "\n", + "steps\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputssteps->clusterphase_preferenceOutputssteps\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputstotal_displacements\n", - "\n", - "total_displacements\n", + "\n", + "total_displacements\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputstotal_displacements->clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", + "\n", + "unwrapped_positions\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsunwrapped_positions->clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "clusterphase_preferencemin_phase2Outputsvolume\n", - "\n", - "volume\n", + "\n", + "volume\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencemin_phase2Outputsvolume->clusterphase_preferenceOutputsvolume\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "clusterphase_preferencecompareInputsrun\n", + "\n", + "run\n", "\n", "\n", "\n", "clusterphase_preferencecompareOutputsran\n", - "\n", - "ran\n", + "\n", + "ran\n", "\n", "\n", "\n", "\n", "clusterphase_preferencecompareOutputsde\n", - "\n", - "de\n", + "\n", + "de\n", "\n", "\n", - "\n", + "\n", "clusterphase_preferencecompareOutputsde->clusterphase_preferenceOutputsde\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 42, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -3737,7 +2797,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 37, "id": "b51bef25-86c5-4d57-80c1-ab733e703caf", "metadata": {}, "outputs": [ @@ -3758,7 +2818,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 38, "id": "091e2386-0081-436c-a736-23d019bd9b91", "metadata": {}, "outputs": [ @@ -3779,28 +2839,41 @@ }, { "cell_type": "markdown", - "id": "3eb1bfd6-3c51-43cf-ae6c-c06d3ad0f7df", + "id": "f447531e-3e8c-4c7e-a579-5f9c56b75a5b", "metadata": {}, "source": [ - "# On the future\n", - "\n", - "Currently things are at an intermediate state where we _always_ need to worry about specifying the execution flow with signals. While signals are a necessity for cyclic graphs, valid execution patterns can be determined _automatically_ from the topology of data connections in the case of a directed acyclic graph (DAG -- which includes all the workflows we've seen so far!) So in the near future you can look forward to this step being fully automated such that typical users _only_ need to worry about the data graph.\n", + "# Here be dragons\n", "\n", - "Additionally, we're working on better integration of executors so that processing power outside the main python process controlling the workflow can be used for individual nodes (including macros).\n", + "While everything in the workflows sub-module is under development, the following complex features are _even more likely_ to see substantial modifications to their interface and behaviour. Nonetheless, they're fun so let's look at them." + ] + }, + { + "cell_type": "markdown", + "id": "069cc8e8-f8b9-4382-a424-b3b2dd2bf739", + "metadata": {}, + "source": [ + "## Parallelization\n", "\n", - "Finally, we will start working on serialization soon so that workflows can be saved/loaded/restarted.\n", + "You can currently run _some_ nodes (namely, `Function` nodes that don't take `self` as an argument) in a background process by setting an `executor` of the right type.\n", + "Cf. the `Workflow` class tests in the source code for an example.\n", "\n", - "(And, of course, there will be ongoing changes in UI/UX, like debug logs, more workflow visualization options, etc.)" + "Right now our treatment of DAGs is quite rudimentary, and the data flow is (unless cyclic) converted into a _linear_ execution pattern. \n", + "This is practical and robust, but highly inefficient when combined with nodes that can run in parallel, i.e. with \"executors\".\n", + "Going forward, we will exploit the same infrastructure of data flow DAGs and run signals to build up more sophisticated execution patterns which support parallelization." ] }, { "cell_type": "markdown", - "id": "f447531e-3e8c-4c7e-a579-5f9c56b75a5b", + "id": "1f29fde8-1645-444e-99dc-3ec465461c7e", "metadata": {}, "source": [ - "# Here be dragons\n", + "## Serialization and node libraries\n", "\n", - "While everything in the workflows sub-module is under development, the following complex features are _even more likely_ to see substantial modifications to their interface and behaviour. Nonetheless, they're fun so let's look at them." + "Serialization doesn't exist yet.\n", + "\n", + "What you _can_ do is `register` new lists of nodes (including macros) with the workflow, so feel free to build up your own `.py` files containing nodes you like to use for easy re-use.\n", + "\n", + "Serialization of workflows is still forthcoming, while for node registration flexibility and documentation is forthcoming but the basics are here already." ] }, { @@ -3826,7 +2899,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 39, "id": "0b373764-b389-4c24-8086-f3d33a4f7fd7", "metadata": {}, "outputs": [ @@ -3840,7 +2913,7 @@ " 17.230249999999995]" ] }, - "execution_count": 45, + "execution_count": 39, "metadata": {}, "output_type": "execute_result" } @@ -3877,467 +2950,14 @@ }, { "cell_type": "code", - "execution_count": 46, - "id": "37cbdd70-5c98-4ca0-83f9-cbfeff3a09db", + "execution_count": 40, + "id": "0dd04b4c-e3e7-4072-ad34-58f2c1e4f596", "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_while\n", - "\n", - "do_while: Workflow\n", - "\n", - "clusterdo_whileInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterdo_whileOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clusterdo_whileadd_while\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "add_while: MakeLoop\n", - "\n", - "\n", - "clusterdo_whileLessThanTenadd_while\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "LessThanTen: LessThanTen\n", - "\n", - "\n", - "clusterdo_whileLessThanTenadd_whileInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterdo_whileLessThanTenadd_whileOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clusterdo_whileswitchadd_while\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "switch: If\n", - "\n", - "\n", - "clusterdo_whileswitchadd_whileInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterdo_whileswitchadd_whileOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clusterdo_whileadd_whileInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterdo_whileadd_whileOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clusterdo_whileAddadd_while\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Add: Add\n", - "\n", - "\n", - "clusterdo_whileAddadd_whileInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterdo_whileAddadd_whileOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "\n", - "clusterdo_whileInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterdo_whileOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileInputsa\n", - "\n", - "a\n", - "\n", - "\n", - "\n", - "clusterdo_whileadd_whileInputsa\n", - "\n", - "a\n", - "\n", - "\n", - "\n", - "clusterdo_whileInputsa->clusterdo_whileadd_whileInputsa\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileInputsb\n", - "\n", - "b\n", - "\n", - "\n", - "\n", - "clusterdo_whileadd_whileInputsb\n", - "\n", - "b\n", - "\n", - "\n", - "\n", - "clusterdo_whileInputsb->clusterdo_whileadd_whileInputsb\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileOutputsa + b\n", - "\n", - "a + b\n", - "\n", - "\n", - "\n", - "clusterdo_whileOutputstruth\n", - "\n", - "truth\n", - "\n", - "\n", - "\n", - "clusterdo_whileadd_whileInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterdo_whileadd_whileOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileAddadd_whileInputsa\n", - "\n", - "a\n", - "\n", - "\n", - "\n", - "clusterdo_whileadd_whileInputsa->clusterdo_whileAddadd_whileInputsa\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileAddadd_whileInputsb\n", - "\n", - "b\n", - "\n", - "\n", - "\n", - "clusterdo_whileadd_whileInputsb->clusterdo_whileAddadd_whileInputsb\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileadd_whileOutputsa + b\n", - "\n", - "a + b\n", - "\n", - "\n", - "\n", - "clusterdo_whileadd_whileOutputsa + b->clusterdo_whileOutputsa + b\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileadd_whileOutputsa + b->clusterdo_whileadd_whileInputsa\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileadd_whileOutputstruth\n", - "\n", - "truth\n", - "\n", - "\n", - "\n", - "clusterdo_whileadd_whileOutputstruth->clusterdo_whileOutputstruth\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileAddadd_whileInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterdo_whileAddadd_whileOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileLessThanTenadd_whileInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterdo_whileAddadd_whileOutputsran->clusterdo_whileLessThanTenadd_whileInputsrun\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileAddadd_whileOutputsa + b\n", - "\n", - "a + b\n", - "\n", - "\n", - "\n", - "clusterdo_whileAddadd_whileOutputsa + b->clusterdo_whileadd_whileOutputsa + b\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileAddadd_whileOutputsa + b->clusterdo_whileAddadd_whileInputsa\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileLessThanTenadd_whileInputsvalue\n", - "\n", - "value\n", - "\n", - "\n", - "\n", - "clusterdo_whileAddadd_whileOutputsa + b->clusterdo_whileLessThanTenadd_whileInputsvalue\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileLessThanTenadd_whileOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileswitchadd_whileInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterdo_whileLessThanTenadd_whileOutputsran->clusterdo_whileswitchadd_whileInputsrun\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileLessThanTenadd_whileOutputsvalue < 10\n", - "\n", - "value < 10\n", - "\n", - "\n", - "\n", - "clusterdo_whileswitchadd_whileInputscondition\n", - "\n", - "condition\n", - "\n", - "\n", - "\n", - "clusterdo_whileLessThanTenadd_whileOutputsvalue < 10->clusterdo_whileswitchadd_whileInputscondition\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileswitchadd_whileOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileswitchadd_whileOutputstrue\n", - "\n", - "true\n", - "\n", - "\n", - "\n", - "clusterdo_whileswitchadd_whileOutputstrue->clusterdo_whileAddadd_whileInputsrun\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterdo_whileswitchadd_whileOutputsfalse\n", - "\n", - "false\n", - "\n", - "\n", - "\n", - "clusterdo_whileswitchadd_whileOutputstruth\n", - "\n", - "truth\n", - "\n", - "\n", - "\n", - "clusterdo_whileswitchadd_whileOutputstruth->clusterdo_whileadd_whileOutputstruth\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 46, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "@Workflow.wrap_as.single_value_node()\n", "def add(a, b):\n", - " print(f\"Adding {a} + {b}\")\n", + " print(f\"{a} + {b} = {a + b}\")\n", " return a + b\n", "\n", "@Workflow.wrap_as.single_value_node()\n", @@ -4358,14 +2978,14 @@ "wf = Workflow(\"do_while\")\n", "wf.add_while = AddWhile()\n", "\n", - "wf.starting_nodes = [wf.add_while]\n", "wf.inputs_map = {\n", " \"add_while__a\": \"a\",\n", " \"add_while__b\": \"b\"\n", "}\n", - "wf.outputs_map = {\"add_while__total\": \"total\"}\n", - "\n", - "wf.draw(depth=2)" + "wf.outputs_map = {\n", + " \"add_while__total\": \"total\", # Rename this output\n", + " \"add_while__switch__truth\": None # Disable this output\n", + "}" ] }, { @@ -4378,7 +2998,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 41, "id": "2dfb967b-41ac-4463-b606-3e315e617f2a", "metadata": {}, "outputs": [ @@ -4386,25 +3006,78 @@ "name": "stdout", "output_type": "stream", "text": [ - "Adding 1 + 2\n", - "Adding 3 + 2\n", - "Adding 5 + 2\n", - "Adding 7 + 2\n", - "Adding 9 + 2\n", - "Finally 11\n" + "1 + 2 = 3\n", + "3 + 2 = 5\n", + "5 + 2 = 7\n", + "7 + 2 = 9\n", + "9 + 2 = 11\n", + "Finally {'total': 11}\n" ] } ], "source": [ "response = wf(a=1, b=2)\n", - "print(\"Finally\", response.total)" + "print(\"Finally\", response)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 42, "id": "2e87f858-b327-4f6b-9237-c8a557f29aeb", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.499 > 0.2\n", + "0.879 > 0.2\n", + "0.993 > 0.2\n", + "0.606 > 0.2\n", + "0.126 <= 0.2\n", + "Finally 0.126\n" + ] + } + ], + "source": [ + "@Workflow.wrap_as.single_value_node(\"random\")\n", + "def random(length: int | None = None):\n", + " return np.random.random(length)\n", + "\n", + "@Workflow.wrap_as.single_value_node()\n", + "def greater_than(x: float, threshold: float):\n", + " gt = x > threshold\n", + " symbol = \">\" if gt else \"<=\"\n", + " print(f\"{x:.3f} {symbol} {threshold}\")\n", + " return gt\n", + "\n", + "RandomWhile = Workflow.create.meta.while_loop(\n", + " loop_body_class=random,\n", + " condition_class=greater_than,\n", + " internal_connection_map=[(\"Random\", \"random\", \"GreaterThan\", \"x\")],\n", + " outputs_map={\"Random__random\": \"capped_result\"}\n", + ")\n", + "\n", + "# Define workflow\n", + "\n", + "wf = Workflow(\"random_until_small_enough\")\n", + "\n", + "## Wire together the while loop and its condition\n", + "\n", + "wf.random_while = RandomWhile()\n", + "\n", + "## Give convenient labels\n", + "wf.inputs_map = {\"random_while__GreaterThan__threshold\": \"threshold\"}\n", + "wf.outputs_map = {\"random_while__capped_result\": \"capped_result\"}\n", + "\n", + "print(f\"Finally {wf(threshold=0.2).capped_result:.3f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f40bfd6f-3fbf-4c2b-aeee-534ed4bcc970", + "metadata": {}, "outputs": [], "source": [] } From d18edc831dbf58e23d0cb4bb7767a991b7de20fe Mon Sep 17 00:00:00 2001 From: liamhuber Date: Wed, 13 Sep 2023 15:36:15 -0700 Subject: [PATCH 658/756] Remove unused code We do this sort of analysis with the data topology now --- pyiron_contrib/workflow/composite.py | 39 ---------------------------- 1 file changed, 39 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 6ae7c4680..8abb95066 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -125,45 +125,6 @@ def to_dict(self): "nodes": {n.label: n.to_dict() for n in self.nodes.values()}, } - def has_locally_scoped_connection(self, node_connections: list[Channel]) -> bool: - """ - Check whether connections are made to any (recursively) owned nodes. - - Args: - node_connections [list[Channel]]: A list of connections. - - Returns: - (bool): Whether or not any of those connections are locally scoped to the - nodes owned by this composite node. - """ - return len( - set([connection.node for connection in node_connections]).intersection( - self.nodes.values() - ) - ) > 0 or any( - node.has_locally_scoped_connection(node_connections) - for node in self.nodes.values() - if isinstance(node, Composite) - ) - - def connects_to_output_of(self, node: Node) -> bool: - """ - Checks whether the passed node receives output from any of this composite node's - (recursively) owned nodes. - """ - return self.has_locally_scoped_connection( - node.outputs.connections + node.signals.output.connections - ) - - def connects_to_input_of(self, node: Node) -> bool: - """ - Checks whether the passed node receives input from any of this composite node's - (recursively) owned nodes. - """ - return self.has_locally_scoped_connection( - node.inputs.connections + node.signals.input.connections - ) - @property def on_run(self): return self.run_graph From 073a5e0cd01e193279de0d86f8f8f88b217d57fa Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 13 Sep 2023 22:37:26 +0000 Subject: [PATCH 659/756] Format black --- pyiron_contrib/workflow/meta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py index 774ed5d1c..9c0bd3d6e 100644 --- a/pyiron_contrib/workflow/meta.py +++ b/pyiron_contrib/workflow/meta.py @@ -277,7 +277,7 @@ def while_loop( >>> ## Give convenient labels >>> wf.inputs_map = {"random_while__GreaterThan__threshold": "threshold"} >>> wf.outputs_map = {"random_while__capped_result": "capped_result"} - >>> + >>> >>> # Set a threshold and run >>> print(f"Finally {wf(threshold=0.1).capped_result:.3f}") 0.549 > 0.1 From 608511951b7ebd9365ef0b6de5ff7ce0eac4eca6 Mon Sep 17 00:00:00 2001 From: Liam Huber Date: Thu, 14 Sep 2023 09:56:35 -0700 Subject: [PATCH 660/756] Update pyiron_contrib/workflow/io.py Co-authored-by: Sam Dareska <37879103+samwaseda@users.noreply.github.com> --- pyiron_contrib/workflow/io.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py index 772035cf0..442dee7eb 100644 --- a/pyiron_contrib/workflow/io.py +++ b/pyiron_contrib/workflow/io.py @@ -249,10 +249,7 @@ def disconnect(self) -> list[tuple[Channel, Channel]]: [list[tuple[Channel, Channel]]]: A list of the pairs of channels that no longer participate in a connection. """ - destroyed_connections = [] - destroyed_connections.extend(self.input.disconnect()) - destroyed_connections.extend(self.output.disconnect()) - return destroyed_connections + return self.input.disconnect() + self.output.disconnect() def disconnect_run(self) -> list[tuple[Channel, Channel]]: return self.input.disconnect_run() From b0540c60b54350333d6b1fe0657dbf479a95780e Mon Sep 17 00:00:00 2001 From: liamhuber Date: Thu, 14 Sep 2023 10:07:06 -0700 Subject: [PATCH 661/756] Add warning per @samwaseda's advice --- pyiron_contrib/workflow/channels.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py index 57f5f4a5a..a241802e2 100644 --- a/pyiron_contrib/workflow/channels.py +++ b/pyiron_contrib/workflow/channels.py @@ -106,6 +106,11 @@ def disconnect(self, *others: Channel) -> list[tuple[Channel, Channel]]: self.connections.remove(other) other.disconnect(self) destroyed_connections.append((self, other)) + else: + warn( + f"The channel {self.label} was not connected to {other.label}, and" + f"thus could not disconnect from it." + ) return destroyed_connections def disconnect_all(self) -> list[tuple[Channel, Channel]]: From 55bc301da61fbdd8e2fe6feb290990140b931331 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 15 Sep 2023 10:26:35 -0700 Subject: [PATCH 662/756] Expose `Composite.disconnect_run` publicly This is useful if you're manually setting up execution flows on a workflow instance and want to start from a clean slate. --- pyiron_contrib/workflow/composite.py | 10 ++++++++-- pyiron_contrib/workflow/macro.py | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py index 8abb95066..20ca3ae88 100644 --- a/pyiron_contrib/workflow/composite.py +++ b/pyiron_contrib/workflow/composite.py @@ -135,7 +135,13 @@ def run_graph(self): node.run() return DotDict(self.outputs.to_value_dict()) - def _disconnect_run(self) -> list[tuple[Channel, Channel]]: + def disconnect_run(self) -> list[tuple[Channel, Channel]]: + """ + Disconnect all `signals.input.run` connections on all child nodes. + + Returns: + list[tuple[Channel, Channel]]: Any disconnected pairs. + """ disconnected_pairs = [] for node in self.nodes.values(): disconnected_pairs.extend(node.signals.disconnect_run()) @@ -149,7 +155,7 @@ def set_run_signals_to_dag_execution(self): Raises: ValueError: When the data connections do not form a DAG. """ - self._disconnect_run() + self.disconnect_run() self._set_run_connections_and_starting_nodes_according_to_linear_dag() # TODO: Replace this linear setup with something more powerful diff --git a/pyiron_contrib/workflow/macro.py b/pyiron_contrib/workflow/macro.py index 9d2608941..36babd071 100644 --- a/pyiron_contrib/workflow/macro.py +++ b/pyiron_contrib/workflow/macro.py @@ -167,7 +167,7 @@ def outputs(self) -> Outputs: return self._outputs def _configure_graph_execution(self): - run_signals = self._disconnect_run() + run_signals = self.disconnect_run() has_signals = len(run_signals) > 0 has_starters = len(self.starting_nodes) > 0 @@ -188,7 +188,7 @@ def _configure_graph_execution(self): ) def _reconnect_run(self, run_signal_pairs_to_restore): - self._disconnect_run() + self.disconnect_run() for pairs in run_signal_pairs_to_restore: pairs[0].connect(pairs[1]) From 397a7901c67e1f291ce66dd8e1e4587a2b834781 Mon Sep 17 00:00:00 2001 From: Leimeroth Date: Thu, 21 Sep 2023 08:50:47 +0200 Subject: [PATCH 663/756] unify to max iter --- pyiron_contrib/atomistics/atomicrex/general_input.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/atomistics/atomicrex/general_input.py b/pyiron_contrib/atomistics/atomicrex/general_input.py index ea9b406a4..4c0697bb5 100644 --- a/pyiron_contrib/atomistics/atomicrex/general_input.py +++ b/pyiron_contrib/atomistics/atomicrex/general_input.py @@ -150,8 +150,8 @@ def ar_lbfgs(conv_threshold=1e-10, max_iter=50, gradient_epsilon=None): ) @staticmethod - def ar_spa(spa_iterations=20, seed=42): - return SpaMinimizer(spa_iterations=spa_iterations, seed=seed) + def ar_spa(max_iter=20, seed=42): + return SpaMinimizer(max_iter=max_iter, seed=seed) @staticmethod def ld_lbfgs( From 4a680ff4e16f4c8e61e855bcec324f73bd69eae0 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Thu, 21 Sep 2023 10:14:25 +0000 Subject: [PATCH 664/756] Format black --- pyiron_contrib/atomistics/ml/potentialfit.py | 35 ++++++++++---------- pyiron_contrib/atomistics/mlip/mlip.py | 23 ++++++++++--- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/pyiron_contrib/atomistics/ml/potentialfit.py b/pyiron_contrib/atomistics/ml/potentialfit.py index d6cf960a8..e57c156fc 100644 --- a/pyiron_contrib/atomistics/ml/potentialfit.py +++ b/pyiron_contrib/atomistics/ml/potentialfit.py @@ -198,10 +198,7 @@ def force_scatter_histogram(self, axis=None, logy=False): plt.xlabel("Training Error [eV/$\AA$]") def force_log_histogram( - self, - bins: int = 20, - logy: bool = False, - axis: Optional[int] = None + self, bins: int = 20, logy: bool = False, axis: Optional[int] = None ): """ Plots a histogram of logarithmic training errors. @@ -246,7 +243,9 @@ def annotated_vline(x, text, linestyle="--"): path_effects=[withStroke(linewidth=4, foreground="w")], ) - plt.hist(df, bins=np.logspace(np.log10(low + 1e-8), np.log10(high), bins), log=logy) + plt.hist( + df, bins=np.logspace(np.log10(low + 1e-8), np.log10(high), bins), log=logy + ) plt.xscale("log") annotated_vline(rmse, f"RMSE = {rmse:.02}") annotated_vline(mae, f"MAE = {mae:.02}") @@ -255,12 +254,12 @@ def annotated_vline(x, text, linestyle="--"): plt.xlabel("Training Error [eV/$\mathrm{\AA}$]") def force_angle_histogram( - self, - bins: int = 180, - logy: bool = True, - tol: float = 1e-6, - angle_in_degrees=True, - cumulative = False + self, + bins: int = 180, + logy: bool = True, + tol: float = 1e-6, + angle_in_degrees=True, + cumulative=False, ): """ Plot histogram of the angle between training and predicted forces. @@ -275,18 +274,20 @@ def force_angle_histogram( force_pred = self._predicted_data["forces"] force_norm_train = np.linalg.norm(force_train, axis=-1).reshape(-1, 1) - force_norm_pred = np.linalg.norm(force_pred, axis=-1).reshape(-1, 1) + force_norm_pred = np.linalg.norm(force_pred, axis=-1).reshape(-1, 1) - I = ( (force_norm_train > tol) & (force_norm_train > tol) ).reshape(-1) + I = ((force_norm_train > tol) & (force_norm_train > tol)).reshape(-1) - force_dir_train = force_train[I]/force_norm_train[I] - force_dir_pred = force_pred[I]/force_norm_pred[I] + force_dir_train = force_train[I] / force_norm_train[I] + force_dir_pred = force_pred[I] / force_norm_pred[I] - err = np.arccos( (force_dir_train * force_dir_pred).sum(axis=-1).round(8) ) + err = np.arccos((force_dir_train * force_dir_pred).sum(axis=-1).round(8)) if angle_in_degrees: err = np.rad2deg(err) if cumulative: logy = False plt.hist(err, bins=bins, log=logy, cumulative=cumulative) - plt.xlabel("Angular Deviation of Force [" + ["rad", "deg"][angle_in_degrees] + "]") + plt.xlabel( + "Angular Deviation of Force [" + ["rad", "deg"][angle_in_degrees] + "]" + ) plt.ylabel("Count") diff --git a/pyiron_contrib/atomistics/mlip/mlip.py b/pyiron_contrib/atomistics/mlip/mlip.py index 054e4cd5f..224517711 100644 --- a/pyiron_contrib/atomistics/mlip/mlip.py +++ b/pyiron_contrib/atomistics/mlip/mlip.py @@ -20,7 +20,12 @@ ) from pyiron_atomistics import ase_to_pyiron, Atoms from pyiron_contrib.atomistics.ml.potentialfit import PotentialFit -from pyiron_contrib.atomistics.mlip.cfgs import savecfgs, loadcfgs, Cfg, load_grades_ids_and_timesteps +from pyiron_contrib.atomistics.mlip.cfgs import ( + savecfgs, + loadcfgs, + Cfg, + load_grades_ids_and_timesteps, +) from pyiron_contrib.atomistics.mlip.potential import MtpPotential __author__ = "Jan Janssen" @@ -235,17 +240,27 @@ def collect_logfiles(self): def collect_output(self): file_name = os.path.join(self.working_directory, "diff.cfg") if os.path.exists(file_name): - _, job_id_diff_lst, timestep_diff_lst = load_grades_ids_and_timesteps(file_name) + _, job_id_diff_lst, timestep_diff_lst = load_grades_ids_and_timesteps( + file_name + ) else: job_id_diff_lst, timestep_diff_lst = [], [] file_name = os.path.join(self.working_directory, "selected.cfg") if os.path.exists(file_name): - _, job_id_new_training_lst, timestep_new_training_lst = load_grades_ids_and_timesteps(file_name) + ( + _, + job_id_new_training_lst, + timestep_new_training_lst, + ) = load_grades_ids_and_timesteps(file_name) else: job_id_new_training_lst, timestep_new_training_lst = [], [] file_name = os.path.join(self.working_directory, "grades.cfg") if os.path.exists(file_name): - grades_lst, job_id_grades_lst, timestep_grades_lst = load_grades_ids_and_timesteps(file_name) + ( + grades_lst, + job_id_grades_lst, + timestep_grades_lst, + ) = load_grades_ids_and_timesteps(file_name) else: grades_lst, job_id_grades_lst, timestep_grades_lst = [], [], [] try: From 4b323ffb1ba734308667511801d758ee64aab88a Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Thu, 22 Jun 2023 15:45:39 +0000 Subject: [PATCH 665/756] Format black --- pyiron_contrib/tinybase/creator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiron_contrib/tinybase/creator.py b/pyiron_contrib/tinybase/creator.py index 4720682fc..8e7bde530 100644 --- a/pyiron_contrib/tinybase/creator.py +++ b/pyiron_contrib/tinybase/creator.py @@ -174,7 +174,6 @@ def bulk(self, *args, **kwargs): atoms = Atoms - class ExecutorCreator(Creator): _DEFAULT_CPUS = min(int(0.5 * cpu_count()), 8) From c02190abcc63b1bdf13934ecb28bd8c9d5beeb1d Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 23 Jun 2023 14:47:55 +0200 Subject: [PATCH 666/756] Tinybase: Rework storage interface Previously Storable implementions of `AbstractTask` and `TinyJob` simply pickled everything at the highest level because some ASE objects couldn't be written directly with the Storable interface. This change adds some functionality to GenericStorage that will automatically detect such objects and pickle only those transparently to the rest of tinybase. As such the full object hierarchy is now represented in storage and neither tasks nor jobs need to know whether some internal objects of theirs have been pickled. It also adds some amount of docstrings and specs to the storage related classes. --- notebooks/tinybase/TinyJob.ipynb | 451 +++++++++++++++++++---------- pyiron_contrib/tinybase/job.py | 19 +- pyiron_contrib/tinybase/storage.py | 132 ++++++++- pyiron_contrib/tinybase/task.py | 8 +- 4 files changed, 415 insertions(+), 195 deletions(-) diff --git a/notebooks/tinybase/TinyJob.ipynb b/notebooks/tinybase/TinyJob.ipynb index cfa47d1ea..01b606009 100644 --- a/notebooks/tinybase/TinyJob.ipynb +++ b/notebooks/tinybase/TinyJob.ipynb @@ -115,7 +115,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "9d9d50a24c6348aa986d0949a2750f6f", + "model_id": "0c4361c2e3764812bf8d41193a473296", "version_major": 2, "version_minor": 0 }, @@ -132,26 +132,37 @@ { "cell_type": "code", "execution_count": 6, - "id": "18e6de26-308c-46ae-9672-b2db43447ea5", + "id": "a750412c-738a-459e-9d58-5d0b520487e3", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "j.input.structure = pr.create.structure.bulk('Fe', a=1.2, cubic=True).repeat(2).to_ase()\n", - "j.input.calculator = MorsePotential()" + "j.storage" ] }, { "cell_type": "code", "execution_count": 7, - "id": "652989f1-6a38-4901-9c21-4302c85bb0d4", + "id": "18e6de26-308c-46ae-9672-b2db43447ea5", "metadata": { "tags": [] }, "outputs": [], "source": [ - "from math import inf" + "j.input.structure = pr.create.structure.bulk('Fe', a=1.2, cubic=True).repeat(2).to_ase()\n", + "j.input.calculator = MorsePotential()" ] }, { @@ -192,6 +203,18 @@ { "cell_type": "code", "execution_count": 10, + "id": "74c84007-e3e0-4071-978f-388abdfffbc5", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "j.wait()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, "id": "49ccfe01-7b7e-4615-bf43-21c1bbffec66", "metadata": { "tags": [] @@ -200,7 +223,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "6a7d186114ee445abf94c0ab53aa0e90", + "model_id": "e8e70439a480408897767ffe4e860811", "version_major": 2, "version_minor": 0 }, @@ -226,7 +249,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "7b807119-da8d-475c-9aa8-c8e8c9afa115", "metadata": { "tags": [] @@ -238,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "id": "3a8cda32-df2e-4884-8cfd-84e438c5be69", "metadata": { "tags": [] @@ -256,7 +279,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "id": "d4b81c3f-4667-4b99-a2b3-08c7ee7e2c82", "metadata": { "tags": [] @@ -271,7 +294,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 15, "id": "e7494fee-d565-45e3-a819-c77ab0d2c7f6", "metadata": { "scrolled": true, @@ -279,53 +302,10 @@ }, "outputs": [ { - "name": "stdout", + "name": "stderr", "output_type": "stream", "text": [ - " Step Time Energy fmax\n", - "LBFGS: 0 12:07:57 11.288146 189.5231\n", - "LBFGS: 1 12:07:57 1.168671 43.6957\n", - "LBFGS: 2 12:07:57 0.860403 38.6924\n", - "LBFGS: 3 12:07:57 0.362400 30.3554\n", - "LBFGS: 4 12:07:57 0.004806 24.0865\n", - "LBFGS: 5 12:07:57 -0.267437 19.0615\n", - "LBFGS: 6 12:07:57 -0.471646 15.0628\n", - "LBFGS: 7 12:07:57 -0.623506 11.8810\n", - "LBFGS: 8 12:07:57 -0.735237 9.3518\n", - "LBFGS: 9 12:07:57 -0.816458 7.3435\n", - "LBFGS: 10 12:07:57 -0.874705 5.7512\n", - "LBFGS: 11 12:07:57 -0.915849 4.4909\n", - "LBFGS: 12 12:07:57 -0.944435 3.4955\n", - "LBFGS: 13 12:07:57 -0.963943 2.7113\n", - "LBFGS: 14 12:07:57 -0.977006 2.0956\n", - "LBFGS: 15 12:07:57 -0.985585 1.6137\n", - "LBFGS: 16 12:07:57 -0.991109 1.2382\n", - "LBFGS: 17 12:07:57 -0.994598 0.9468\n", - "LBFGS: 18 12:07:57 -0.996763 0.7216\n", - "LBFGS: 19 12:07:57 -0.998083 0.5484\n", - "LBFGS: 20 12:07:57 -0.998876 0.4157\n", - "LBFGS: 21 12:07:57 -0.999347 0.3144\n", - "LBFGS: 22 12:07:57 -0.999623 0.2374\n", - "LBFGS: 23 12:07:57 -0.999784 0.1790\n", - "LBFGS: 24 12:07:57 -0.999877 0.1348\n", - "LBFGS: 25 12:07:57 -0.999930 0.1014\n", - "LBFGS: 26 12:07:57 -0.999960 0.0762\n", - "LBFGS: 27 12:07:57 -0.999977 0.0573\n", - "LBFGS: 28 12:07:57 -0.999987 0.0430\n", - "LBFGS: 29 12:07:57 -0.999993 0.0323\n", - "LBFGS: 30 12:07:57 -0.999996 0.0242\n", - "LBFGS: 31 12:07:57 -0.999998 0.0182\n", - "LBFGS: 32 12:07:57 -0.999999 0.0136\n", - "LBFGS: 33 12:07:57 -0.999999 0.0102\n", - "LBFGS: 34 12:07:57 -1.000000 0.0077\n", - "LBFGS: 35 12:07:57 -1.000000 0.0058\n", - "LBFGS: 36 12:07:57 -1.000000 0.0043\n", - "LBFGS: 37 12:07:57 -1.000000 0.0032\n", - "LBFGS: 38 12:07:57 -1.000000 0.0024\n", - "LBFGS: 39 12:07:57 -1.000000 0.0018\n", - "LBFGS: 40 12:07:57 -1.000000 0.0014\n", - "LBFGS: 41 12:07:57 -1.000000 0.0010\n", - "LBFGS: 42 12:07:57 -1.000000 0.0008\n" + "INFO:root:Job already finished!\n" ] } ], @@ -338,7 +318,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 16, "id": "7c16a615-0913-4880-9694-2c125285babc", "metadata": { "tags": [] @@ -379,39 +359,39 @@ " \n", "
\n", " 0\n", - " 6\n", + " 1\n", " pyiron\n", - " murn\n", - " 2\n", + " md\n", + " 1\n", + " 1\n", " 1\n", - " 11\n", " /home/poul/pyiron/contrib/notebooks/tinybase/t...\n", " finished\n", - " MurnaghanTask\n", + " AseMDTask\n", "
\n", "
\n", " 1\n", - " 7\n", + " 2\n", " pyiron\n", - " md\n", - " 1\n", + " min\n", + " 2\n", " 1\n", - " 13\n", + " 2\n", " /home/poul/pyiron/contrib/notebooks/tinybase/t...\n", " finished\n", - " AseMDTask\n", + " AseMinimizeTask\n", "
\n", "
\n", " 2\n", - " 8\n", + " 3\n", " pyiron\n", - " min\n", + " murn\n", " 3\n", " 1\n", - " 14\n", + " 3\n", " /home/poul/pyiron/contrib/notebooks/tinybase/t...\n", " finished\n", - " AseMinimizeTask\n", + " MurnaghanTask\n", "
\n", "
\n", "\n", @@ -419,9 +399,9 @@ ], "text/plain": [ " id username name jobtype_id project_id status_id \\\n", - "0 6 pyiron murn 2 1 11 \n", - "1 7 pyiron md 1 1 13 \n", - "2 8 pyiron min 3 1 14 \n", + "0 1 pyiron md 1 1 1 \n", + "1 2 pyiron min 2 1 2 \n", + "2 3 pyiron murn 3 1 3 \n", "\n", " location status \\\n", "0 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", @@ -429,12 +409,12 @@ "2 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", "\n", " type \n", - "0 MurnaghanTask \n", - "1 AseMDTask \n", - "2 AseMinimizeTask " + "0 AseMDTask \n", + "1 AseMinimizeTask \n", + "2 MurnaghanTask " ] }, - "execution_count": 18, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -445,7 +425,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 17, "id": "59ea5510-b6ce-4317-90c3-4af77db3d59a", "metadata": { "tags": [] @@ -468,7 +448,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 18, "id": "3fb09d42-f800-46ee-9919-83180863e1ee", "metadata": { "tags": [] @@ -477,7 +457,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "fe8ba3dea3e74cf09a80af51a1a8a727", + "model_id": "22e49b9ad64a4b8793c3b70c5a228c5f", "version_major": 2, "version_minor": 0 }, @@ -503,7 +483,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 19, "id": "d32508b9-2854-4076-9109-08ede1b52dc2", "metadata": { "tags": [] @@ -520,7 +500,7 @@ " -0.999999995888409]" ] }, - "execution_count": 17, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -531,7 +511,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 20, "id": "db691097-72c6-45a4-89b1-6ec16018c8b8", "metadata": { "tags": [] @@ -554,14 +534,14 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 21, "id": "23ce6822-b38b-41f3-9269-109dbb152ecf", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "717e3bf94e2a46c39a07430192eb21fc", + "model_id": "af82f8be0cb1474abeedb49698ee8c6c", "version_major": 2, "version_minor": 0 }, @@ -587,7 +567,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 22, "id": "654ce992-b73f-42e3-a32e-2e0dafa7c952", "metadata": { "tags": [] @@ -599,7 +579,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 23, "id": "253237f0-b338-470c-bc54-3c7400a757b7", "metadata": {}, "outputs": [], @@ -610,7 +590,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 24, "id": "c801093b-499e-48a7-8444-77602ed88a96", "metadata": {}, "outputs": [], @@ -620,7 +600,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 25, "id": "1e30b36e-11e6-47d1-836e-cffea7b73cdd", "metadata": {}, "outputs": [], @@ -630,16 +610,23 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 26, "id": "18b5305a-8950-44af-bc2e-c9734b059713", "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:root:Job already finished!\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 1.41 s, sys: 1.23 s, total: 2.64 s\n", - "Wall time: 10.7 s\n" + "CPU times: user 2.62 ms, sys: 1.24 ms, total: 3.86 ms\n", + "Wall time: 3.69 ms\n" ] } ], @@ -651,7 +638,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 27, "id": "836bb2ec-4295-4a3c-b976-7a35d04aad36", "metadata": {}, "outputs": [ @@ -682,7 +669,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 28, "id": "79a2bb61-0a5e-4a3a-b195-46d027738a0e", "metadata": {}, "outputs": [], @@ -692,7 +679,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 29, "id": "b4e6e0c9-a2c6-40ab-884e-a46e16c37b04", "metadata": {}, "outputs": [ @@ -730,7 +717,7 @@ "Index: []" ] }, - "execution_count": 36, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -741,33 +728,21 @@ }, { "cell_type": "code", - "execution_count": 37, - "id": "c81e5148-3da3-428d-bf01-4608f7fdb978", - "metadata": { - "tags": [] - }, + "execution_count": 30, + "id": "cef7c46f-551f-401e-96c2-214628e23967", + "metadata": {}, "outputs": [ { "data": { + "image/png": "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\n", "text/plain": [ - "False" + "
" ] }, - "execution_count": 37, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], - "source": [ - "pr.exists_storage('murn')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cef7c46f-551f-401e-96c2-214628e23967", - "metadata": {}, - "outputs": [], "source": [ "murn = pr.create.job.Murnaghan(\"murn\")\n", "murn.input.task = pr.create.task.AseStatic()\n", @@ -781,7 +756,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "id": "e8a7ee30-d7a6-46fc-bf98-1b52c981470f", "metadata": {}, "outputs": [ @@ -842,7 +817,7 @@ "0 MurnaghanTask " ] }, - "execution_count": 30, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -853,7 +828,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 32, "id": "30871447-3e20-46ee-a58e-853d4f4cb5d9", "metadata": {}, "outputs": [ @@ -861,8 +836,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "DEBUG:pyiron_log:Not supported parameter used!\n", - "INFO:root:Job already finished!\n" + "DEBUG:pyiron_log:Not supported parameter used!\n" ] } ], @@ -880,7 +854,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 33, "id": "e63d43c1-341f-4ec0-b0cf-9cd5ead51926", "metadata": {}, "outputs": [ @@ -938,7 +912,7 @@ " 1\n", " 2\n", " /\n", - " running\n", + " finished\n", " AseMDTask\n", " \n", " \n", @@ -948,14 +922,14 @@ "text/plain": [ " id username name jobtype_id project_id status_id location status \\\n", "0 1 pyiron murn 1 1 1 / finished \n", - "1 2 pyiron md 2 1 2 / running \n", + "1 2 pyiron md 2 1 2 / finished \n", "\n", " type \n", "0 MurnaghanTask \n", "1 AseMDTask " ] }, - "execution_count": 32, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -979,42 +953,131 @@ "metadata": {}, "outputs": [ { - "ename": "PermissionError", - "evalue": "[Errno 13] Permission denied: '/foo'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mPermissionError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[34], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m sub \u001b[38;5;241m=\u001b[39m \u001b[43mpr\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mopen_location\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m/foo\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2\u001b[0m j \u001b[38;5;241m=\u001b[39m sub\u001b[38;5;241m.\u001b[39mcreate\u001b[38;5;241m.\u001b[39mjob\u001b[38;5;241m.\u001b[39mAseMD(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mmd\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[1;32m 3\u001b[0m j\u001b[38;5;241m.\u001b[39minput\u001b[38;5;241m.\u001b[39mstructure \u001b[38;5;241m=\u001b[39m bulk(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mFe\u001b[39m\u001b[38;5;124m'\u001b[39m, a\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1.2\u001b[39m, cubic\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\u001b[38;5;241m.\u001b[39mrepeat(\u001b[38;5;241m2\u001b[39m)\n", - "File \u001b[0;32m~/pyiron/contrib/pyiron_contrib/tinybase/project.py:117\u001b[0m, in \u001b[0;36mProjectAdapter.open_location\u001b[0;34m(cls, location)\u001b[0m\n\u001b[1;32m 115\u001b[0m \u001b[38;5;129m@classmethod\u001b[39m\n\u001b[1;32m 116\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mopen_location\u001b[39m(\u001b[38;5;28mcls\u001b[39m, location):\n\u001b[0;32m--> 117\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mcls\u001b[39m(\u001b[43mProject\u001b[49m\u001b[43m(\u001b[49m\u001b[43mlocation\u001b[49m\u001b[43m)\u001b[49m)\n", - "File \u001b[0;32m~/micromamba/envs/contrib/lib/python3.10/site-packages/pyiron_base/project/generic.py:117\u001b[0m, in \u001b[0;36mProject.__init__\u001b[0;34m(self, path, user, sql_query, default_working_directory)\u001b[0m\n\u001b[1;32m 114\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 115\u001b[0m path \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m--> 117\u001b[0m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mProject\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[38;5;21;43m__init__\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mpath\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mpath\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 119\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39muser \u001b[38;5;241m=\u001b[39m user\n\u001b[1;32m 120\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msql_query \u001b[38;5;241m=\u001b[39m sql_query\n", - "File \u001b[0;32m~/micromamba/envs/contrib/lib/python3.10/site-packages/pyiron_base/project/path.py:224\u001b[0m, in \u001b[0;36mProjectPath.__init__\u001b[0;34m(self, path)\u001b[0m\n\u001b[1;32m 222\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m path \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m 223\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mProjectPath: path is not allowed to be empty!\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m--> 224\u001b[0m generic_path \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_convert_str_to_generic_path\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpath\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 225\u001b[0m \u001b[38;5;28msuper\u001b[39m(ProjectPath, \u001b[38;5;28mself\u001b[39m)\u001b[38;5;241m.\u001b[39m\u001b[38;5;21m__init__\u001b[39m(\n\u001b[1;32m 226\u001b[0m generic_path\u001b[38;5;241m.\u001b[39mroot_path, generic_path\u001b[38;5;241m.\u001b[39mproject_path\n\u001b[1;32m 227\u001b[0m )\n\u001b[1;32m 228\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_history \u001b[38;5;241m=\u001b[39m []\n", - "File \u001b[0;32m~/micromamba/envs/contrib/lib/python3.10/site-packages/pyiron_base/project/path.py:373\u001b[0m, in \u001b[0;36mProjectPath._convert_str_to_generic_path\u001b[0;34m(self, path)\u001b[0m\n\u001b[1;32m 371\u001b[0m path \u001b[38;5;241m=\u001b[39m posixpath\u001b[38;5;241m.\u001b[39mjoin(path_local, path)\n\u001b[1;32m 372\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mexists(path):\n\u001b[0;32m--> 373\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_create_path\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpath\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 374\u001b[0m \u001b[38;5;66;03m# else:\u001b[39;00m\n\u001b[1;32m 375\u001b[0m \u001b[38;5;66;03m# raise ValueError(path, ' does not exist!')\u001b[39;00m\n\u001b[1;32m 376\u001b[0m path \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_windows_path_to_unix_path(path)\n", - "File \u001b[0;32m~/micromamba/envs/contrib/lib/python3.10/site-packages/pyiron_base/project/path.py:394\u001b[0m, in \u001b[0;36mProjectPath._create_path\u001b[0;34m(self, path, rel_path)\u001b[0m\n\u001b[1;32m 392\u001b[0m rel_path \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_windows_path_to_unix_path(rel_path)\n\u001b[1;32m 393\u001b[0m path \u001b[38;5;241m=\u001b[39m posixpath\u001b[38;5;241m.\u001b[39mjoin(path, rel_path)\n\u001b[0;32m--> 394\u001b[0m \u001b[43mos\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmakedirs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpath\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mexist_ok\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/micromamba/envs/contrib/lib/python3.10/os.py:225\u001b[0m, in \u001b[0;36mmakedirs\u001b[0;34m(name, mode, exist_ok)\u001b[0m\n\u001b[1;32m 223\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[1;32m 224\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 225\u001b[0m \u001b[43mmkdir\u001b[49m\u001b[43m(\u001b[49m\u001b[43mname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmode\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 226\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mOSError\u001b[39;00m:\n\u001b[1;32m 227\u001b[0m \u001b[38;5;66;03m# Cannot rely on checking for EEXIST, since the operating system\u001b[39;00m\n\u001b[1;32m 228\u001b[0m \u001b[38;5;66;03m# could give priority to other errors like EACCES or EROFS\u001b[39;00m\n\u001b[1;32m 229\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m exist_ok \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m path\u001b[38;5;241m.\u001b[39misdir(name):\n", - "\u001b[0;31mPermissionError\u001b[0m: [Errno 13] Permission denied: '/foo'" + "name": "stderr", + "output_type": "stream", + "text": [ + "DEBUG:pyiron_log:Not supported parameter used!\n" ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ "sub = pr.open_location(\"/foo\")\n", "j = sub.create.job.AseMD('md')\n", - "j.input.structure = bulk('Fe', a=1.2, cubic=True).repeat(2)\n", + "j.input.structure = pr.create.structure.bulk('Fe', a=1.2, cubic=True).repeat(2).to_ase()\n", "j.input.calculator = MorsePotential()\n", "j.input.steps = 100\n", "j.input.timestep = 3.0\n", "j.input.temperature = 600.0\n", "j.input.output_steps = 20\n", - "j.run(how='process')\n", - "j.wait()" + "j.run()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 35, "id": "a567f96a-cbb3-4d2d-95d1-6dcecee7ddb8", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idusernamenamejobtype_idproject_idstatus_idlocationstatustype
01pyironmurn111/finishedMurnaghanTask
12pyironmd212/finishedAseMDTask
23pyironmd223/foofinishedAseMDTask
\n", + "
" + ], + "text/plain": [ + " id username name jobtype_id project_id status_id location status \\\n", + "0 1 pyiron murn 1 1 1 / finished \n", + "1 2 pyiron md 2 1 2 / finished \n", + "2 3 pyiron md 2 2 3 /foo finished \n", + "\n", + " type \n", + "0 MurnaghanTask \n", + "1 AseMDTask \n", + "2 AseMDTask " + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "pr.job_table()" ] @@ -1029,7 +1092,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 36, "id": "a6fefefb-b09c-4cee-b632-29f88bccfeee", "metadata": {}, "outputs": [], @@ -1039,10 +1102,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 37, "id": "3419f273-b94b-48cf-9373-7441baec2353", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "DatabaseEntry(name='murn', username='pyiron', project='/', status='finished', jobtype='MurnaghanTask')" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "db.get_item(1)" ] @@ -1057,7 +1131,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 38, "id": "cf75b2e8-ec15-4846-bda4-9b6661e8b5fa", "metadata": {}, "outputs": [], @@ -1067,7 +1141,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 39, "id": "1b23728b-1050-47a4-bda0-bd5967ceed2e", "metadata": {}, "outputs": [], @@ -1077,7 +1151,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 40, "id": "4cd73be9-2c6d-4501-8a6c-0afc6083a4b9", "metadata": {}, "outputs": [], @@ -1087,7 +1161,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 41, "id": "e9d9a3e2-5c86-46b4-b6d0-bec3a9f448b0", "metadata": {}, "outputs": [], @@ -1097,52 +1171,107 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 42, "id": "99059ff6-18bd-40b9-85fc-d76e1828f7ac", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "s.query(Job.id).where(Job.name == \"min\", ).all()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 43, "id": "cb4bb9fe-13bf-4736-aa68-662b980d4f00", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[(2, 'finished')]" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "s.query(JobStatus.__table__).select_from(Job).where(Job.id == 2, Job.status_id == JobStatus.id).all()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 44, "id": "4641b048-b7c7-46a2-b67c-835c08916cb0", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[(1, 'finished'), (2, 'finished'), (3, 'finished')]" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "s.query(JobStatus.__table__).all()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 45, "id": "94364e54-b980-48cc-995b-daf977437b1b", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[(1, '/'), (2, '/foo')]" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "s.query(DProject.__table__).all()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 46, "id": "f32c43d5-19c8-4505-bf5c-9ac32bca51d0", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[(1, 'MurnaghanTask'), (2, 'AseMDTask')]" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "s.query(JobType.__table__).all()" ] diff --git a/pyiron_contrib/tinybase/job.py b/pyiron_contrib/tinybase/job.py index 0ced3a8b9..d3fbc08c0 100644 --- a/pyiron_contrib/tinybase/job.py +++ b/pyiron_contrib/tinybase/job.py @@ -6,8 +6,6 @@ from pyiron_contrib.tinybase.storage import ( Storable, GenericStorage, - pickle_load, - pickle_dump, ) from pyiron_contrib.tinybase.executor import ( Executor, @@ -202,11 +200,9 @@ def _update_status(self, status): # Storable Impl' def _store(self, storage): - # preferred solution, but not everything that can be pickled can go into HDF atm - # self._executor.output[-1].store(storage, "output") - storage["task"] = pickle_dump(self.task) + storage["task"] = self.task if self._output is not None: - storage["output"] = pickle_dump(self._output) + storage["output"] = self._output def load(self, storage: GenericStorage = None): """ @@ -220,16 +216,13 @@ def load(self, storage: GenericStorage = None): self._update_id() if storage is None: storage = self.storage - self._task = pickle_load(storage["task"]) - # this would be correct, but since we pickle output and put it into a - # HDF task it doesn't appear here yet! - # if "output" in storage.list_groups(): - if "output" in storage.list_nodes(): - self._output = pickle_load(storage["output"]) + self._task = storage["task"].to_object() + if "output" in storage.list_groups(): + self._output = storage["output"].to_object() @classmethod def _restore(cls, storage, version): - task = pickle_load(storage["task"]) + task = storage["task"].to_object() job = cls(task=task, project=storage.project, job_name=storage.name) job.load(storage=storage) return job diff --git a/pyiron_contrib/tinybase/storage.py b/pyiron_contrib/tinybase/storage.py index 768dfdb89..5d1e19d80 100644 --- a/pyiron_contrib/tinybase/storage.py +++ b/pyiron_contrib/tinybase/storage.py @@ -3,21 +3,13 @@ from typing import Any, Union, Optional from pyiron_base import DataContainer +from pyiron_base.storage.hdfio import ProjectHDFio from pyiron_contrib.tinybase import __version__ as base__version__ import pickle import codecs -# utility functions until ASE can be HDF'd -def pickle_dump(obj): - return codecs.encode(pickle.dumps(obj), "base64").decode() - - -def pickle_load(buf): - return pickle.loads(codecs.decode(buf.encode(), "base64")) - - class GenericStorage(abc.ABC): """ Generic interface to store things. @@ -30,6 +22,25 @@ class GenericStorage(abc.ABC): Implementations must allow multiple objects of this class to refer to the same underlying storage group at the same time and access via the methods here must be atomic. + + Mandatory overrides for all implementations are + + 1. :meth:`.__getitem__` to read values, + 2. :meth:`._set` to write values, + 3. :meth:`.create_group` to create sub groups, + 4. :meth:`.list_nodes` and :meth:`.list_groups` to see the contained groups and nodes, + 5. :attr:`.project` which is a back reference to the project that originally created this storage, + 6. :attr:`.name` which is the name of the group that this object points to, e.g. `storage.create_group(name).name == name`. + + If :meth:`_set` raises a `TypeError` indicating that it does not know how to store an object of a certain type, + :meth:`.__setitem__` will pickle it automatically and try to write it again. Such an object can be retrieved with + :meth:`.to_object`. + + For values that implement :class:`.Storable` there is an intentional asymmetry in item writing and reading. Writing + it calls :meth:`.Storable.store` automatically, but reading will return the :class:`.GenericStorage` group that was + created during writing *without* calling :meth:`.Storable.restore` automatically. The original value can be + obtained by calling :meth:`.GenericStorage.to_object` on the returned group. This is so that power users and + developers can access sub objects efficiently without having to load all the containing objects first. """ @abc.abstractmethod @@ -51,17 +62,61 @@ def __getitem__(self, item: str) -> Union["GenericStorage", Any]: """ pass + def get(self, item, default=None): + """ + Same as item access, but return default if given. + + Args: + item (str): name of value + + Returns: + :class:`.GenericStorage`: if `item` refers to a sub group + object: value that is stored under `item` + + Raises: + KeyError: `item` is neither a node or a sub group of this group + """ + try: + value = self[item] + except KeyError: + if default is not None: + return default + else: + raise + @abc.abstractmethod - def __setitem__(self, item: str, value: Any): + def _set(self, item: str, value: Any): """ Set a value to storage. + If this method raises a `TypeError` when called by + :meth:`~.__setitem__`, that method will pickle the value and try again. + Args: item (str): name of the value value (object): value to store + + Raises: + TypeError: if the underlying storage cannot store values of the given type naively """ pass + def __setitem__(self, item: str, value: Any): + """ + Set a value to storage. + + Args: + item (str): name of the value + value (object): value to store + """ + if isinstance(value, Storable): + value.store(self, group_name=item) + else: + try: + self._set(item, value) + except TypeError: + self[item] = PickleStorable(value) + @abc.abstractmethod def create_group(self, name): """ @@ -169,10 +224,25 @@ def __init__(self, project, hdf): self._hdf = hdf def __getitem__(self, item): - return self._hdf[item] + value = self._hdf[item] + if isinstance(value, ProjectHDFio): + return type(self)(self._project, value) + else: + return value - def __setitem__(self, item, value): - self._hdf[item] = value + def _set(self, item, value): + if item == "structures__index_0": + breakpoint() + if isinstance(value, Storable): + value.store(self, item) + else: + try: + self._hdf[item] = value + except TypeError: # HDF layer doesn't know how to write value + # h5io bug, when triggering an error in the middle of a write + # some residual data maybe left in the file + del self._hdf[item] + raise def create_group(self, name): return ProjectHDFioStorageAdapter(self._project, self._hdf.create_group(name)) @@ -212,7 +282,7 @@ def __getitem__(self, item: str) -> Union["GenericStorage", Any]: else: return v - def __setitem__(self, item: str, value: Any): + def _set(self, item: str, value: Any): self._cont[item] = value def create_group(self, name): @@ -279,6 +349,10 @@ def restore(cls, storage: GenericStorage, version: str) -> "Storable": """ Restore an object of type `cls` from storage. + The object returned may not be of type `cls` in special circumstances, + such as :class:`.PickleStorable`, which returns its underlying value + directly. + Args: storage (:class:`.GenericStorage`): storage to read from version (str): version string of pyiron that wrote the object @@ -292,8 +366,7 @@ def restore(cls, storage: GenericStorage, version: str) -> "Storable": try: return cls._restore(storage, version) except Exception as e: - raise ValueError(f"Failed to restore object with {e}") - + raise ValueError(f"Failed to restore object with: {e}") class HasHDFAdapaterMixin(Storable): """ @@ -310,3 +383,30 @@ def _restore(cls, storage, version): obj = cls(**kw) obj._from_hdf(storage, version) return obj + + +def pickle_dump(obj): + return codecs.encode(pickle.dumps(obj), "base64").decode() + + +def pickle_load(buf): + return pickle.loads(codecs.decode(buf.encode(), "base64")) + + +class PickleStorable(Storable): + """ + Trivial implementation of :class:`.Storable` that pickles values. + + Used as a fallback by :class:`.ProjectHDFioStorageAdapter` if value cannot + be stored in HDF natively. + """ + + def __init__(self, value): + self._value = value + + def _store(self, storage): + storage["pickle"] = pickle_dump(self._value) + + @classmethod + def _restore(cls, storage, version): + return pickle_load(storage["pickle"]) diff --git a/pyiron_contrib/tinybase/task.py b/pyiron_contrib/tinybase/task.py index 65df8250b..fdf249c71 100644 --- a/pyiron_contrib/tinybase/task.py +++ b/pyiron_contrib/tinybase/task.py @@ -5,7 +5,7 @@ from pyiron_base.interfaces.object import HasStorage -from pyiron_contrib.tinybase.storage import Storable, pickle_dump, pickle_load +from pyiron_contrib.tinybase.storage import Storable from pyiron_contrib.tinybase.container import ( AbstractInput, AbstractOutput, @@ -139,14 +139,12 @@ def __iter__( # Storable Impl' # We might even avoid this by deriving from HasStorage and put _input in there def _store(self, storage): - # right now not all ASE objects can be stored in HDF, so let's just pickle for now - storage["input"] = pickle_dump(self.input) - # self.input.store(storage, "input") + storage["input"] = self.input @classmethod def _restore(cls, storage, version): task = cls() - task._input = pickle_load(storage["input"]) + task._input = storage["input"].to_object() return task From c663bd63b2af01ae679838c642b8df4c718319ae Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 23 Jun 2023 14:55:34 +0200 Subject: [PATCH 667/756] Update class reference --- pyiron_contrib/tinybase/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/storage.py b/pyiron_contrib/tinybase/storage.py index 5d1e19d80..2b4906cd9 100644 --- a/pyiron_contrib/tinybase/storage.py +++ b/pyiron_contrib/tinybase/storage.py @@ -397,7 +397,7 @@ class PickleStorable(Storable): """ Trivial implementation of :class:`.Storable` that pickles values. - Used as a fallback by :class:`.ProjectHDFioStorageAdapter` if value cannot + Used as a fallback by :class:`.GenericStorage` if value cannot be stored in HDF natively. """ From f5015d7e5916d1fe6b1afc7b393b7231c8415e8e Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Fri, 23 Jun 2023 15:02:13 +0200 Subject: [PATCH 668/756] Remove breakpoint --- pyiron_contrib/tinybase/storage.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyiron_contrib/tinybase/storage.py b/pyiron_contrib/tinybase/storage.py index 2b4906cd9..524e30aa0 100644 --- a/pyiron_contrib/tinybase/storage.py +++ b/pyiron_contrib/tinybase/storage.py @@ -231,8 +231,6 @@ def __getitem__(self, item): return value def _set(self, item, value): - if item == "structures__index_0": - breakpoint() if isinstance(value, Storable): value.store(self, item) else: From 3a920a75afdef51499e5c2324a64fe409af3d4bc Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 26 Jun 2023 13:19:26 +0200 Subject: [PATCH 669/756] Add ListStorable and patch over some glitches --- notebooks/tinybase/TinyJob.ipynb | 259 +++++++++++++++++------------ pyiron_contrib/tinybase/ase.py | 8 +- pyiron_contrib/tinybase/storage.py | 61 ++++++- 3 files changed, 218 insertions(+), 110 deletions(-) diff --git a/notebooks/tinybase/TinyJob.ipynb b/notebooks/tinybase/TinyJob.ipynb index 01b606009..e216c8ea3 100644 --- a/notebooks/tinybase/TinyJob.ipynb +++ b/notebooks/tinybase/TinyJob.ipynb @@ -115,7 +115,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "0c4361c2e3764812bf8d41193a473296", + "model_id": "8861a4841639439b85dad196355d30fb", "version_major": 2, "version_minor": 0 }, @@ -132,42 +132,19 @@ { "cell_type": "code", "execution_count": 6, - "id": "a750412c-738a-459e-9d58-5d0b520487e3", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "j.storage" - ] - }, - { - "cell_type": "code", - "execution_count": 7, "id": "18e6de26-308c-46ae-9672-b2db43447ea5", "metadata": { "tags": [] }, "outputs": [], "source": [ - "j.input.structure = pr.create.structure.bulk('Fe', a=1.2, cubic=True).repeat(2).to_ase()\n", + "j.input.structure = pr.create.structure.bulk('Fe', a=1.2, cubic=True).repeat(2)\n", "j.input.calculator = MorsePotential()" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "72848cd2-fd51-4ad8-b56e-ca686074bb26", "metadata": { "tags": [] @@ -182,7 +159,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "56c0e73a-c42b-4814-a25a-e6974fea3d00", "metadata": { "tags": [] @@ -202,7 +179,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "74c84007-e3e0-4071-978f-388abdfffbc5", "metadata": { "tags": [] @@ -212,6 +189,29 @@ "j.wait()" ] }, + { + "cell_type": "code", + "execution_count": 10, + "id": "2c533085-ade1-4585-9d3c-ed2e200f387c", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'finished'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "j.status" + ] + }, { "cell_type": "code", "execution_count": 11, @@ -223,12 +223,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e8e70439a480408897767ffe4e860811", + "model_id": "f2e3ffa8f02e41928c74071f3793f3ab", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "NGLWidget(max_frame=21)" + "NGLWidget(max_frame=1)" ] }, "metadata": {}, @@ -272,7 +272,7 @@ " symbols=['Fe', 'Fe'], \n", " positions=[[0,0,0], [0,0, .75]], \n", " cell=[10,10,10]\n", - ").to_ase()\n", + ").to_ase() # since our Atoms cannot be pickled, but parallel execution needs that we still convert back here\n", "j.input.structure.rattle(1e-3)\n", "j.input.calculator = MorsePotential()" ] @@ -295,6 +295,18 @@ { "cell_type": "code", "execution_count": 15, + "id": "f9c60e94-ac8f-407f-b0c4-657c91581637", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "j.remove()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, "id": "e7494fee-d565-45e3-a819-c77ab0d2c7f6", "metadata": { "scrolled": true, @@ -302,10 +314,53 @@ }, "outputs": [ { - "name": "stderr", + "name": "stdout", "output_type": "stream", "text": [ - "INFO:root:Job already finished!\n" + " Step Time Energy fmax\n", + "LBFGS: 0 13:14:53 11.288146 189.5231\n", + "LBFGS: 1 13:14:53 1.168671 43.6957\n", + "LBFGS: 2 13:14:53 0.860403 38.6924\n", + "LBFGS: 3 13:14:53 0.362400 30.3554\n", + "LBFGS: 4 13:14:53 0.004806 24.0865\n", + "LBFGS: 5 13:14:53 -0.267437 19.0615\n", + "LBFGS: 6 13:14:53 -0.471646 15.0628\n", + "LBFGS: 7 13:14:53 -0.623506 11.8810\n", + "LBFGS: 8 13:14:53 -0.735237 9.3518\n", + "LBFGS: 9 13:14:53 -0.816458 7.3435\n", + "LBFGS: 10 13:14:53 -0.874705 5.7512\n", + "LBFGS: 11 13:14:53 -0.915849 4.4909\n", + "LBFGS: 12 13:14:53 -0.944435 3.4955\n", + "LBFGS: 13 13:14:53 -0.963943 2.7113\n", + "LBFGS: 14 13:14:53 -0.977006 2.0956\n", + "LBFGS: 15 13:14:53 -0.985585 1.6137\n", + "LBFGS: 16 13:14:53 -0.991109 1.2382\n", + "LBFGS: 17 13:14:53 -0.994598 0.9468\n", + "LBFGS: 18 13:14:53 -0.996763 0.7216\n", + "LBFGS: 19 13:14:53 -0.998083 0.5484\n", + "LBFGS: 20 13:14:53 -0.998876 0.4157\n", + "LBFGS: 21 13:14:53 -0.999347 0.3144\n", + "LBFGS: 22 13:14:53 -0.999623 0.2374\n", + "LBFGS: 23 13:14:53 -0.999784 0.1790\n", + "LBFGS: 24 13:14:53 -0.999877 0.1348\n", + "LBFGS: 25 13:14:53 -0.999930 0.1014\n", + "LBFGS: 26 13:14:53 -0.999960 0.0762\n", + "LBFGS: 27 13:14:53 -0.999977 0.0573\n", + "LBFGS: 28 13:14:53 -0.999987 0.0430\n", + "LBFGS: 29 13:14:53 -0.999993 0.0323\n", + "LBFGS: 30 13:14:53 -0.999996 0.0242\n", + "LBFGS: 31 13:14:53 -0.999998 0.0182\n", + "LBFGS: 32 13:14:53 -0.999999 0.0136\n", + "LBFGS: 33 13:14:53 -0.999999 0.0102\n", + "LBFGS: 34 13:14:53 -1.000000 0.0077\n", + "LBFGS: 35 13:14:53 -1.000000 0.0058\n", + "LBFGS: 36 13:14:53 -1.000000 0.0043\n", + "LBFGS: 37 13:14:53 -1.000000 0.0032\n", + "LBFGS: 38 13:14:53 -1.000000 0.0024\n", + "LBFGS: 39 13:14:53 -1.000000 0.0018\n", + "LBFGS: 40 13:14:53 -1.000000 0.0014\n", + "LBFGS: 41 13:14:53 -1.000000 0.0010\n", + "LBFGS: 42 13:14:53 -1.000000 0.0008\n" ] } ], @@ -318,7 +373,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "7c16a615-0913-4880-9694-2c125285babc", "metadata": { "tags": [] @@ -371,27 +426,27 @@ " \n", " \n", " 1\n", - " 2\n", + " 3\n", " pyiron\n", - " min\n", - " 2\n", + " murn\n", + " 3\n", " 1\n", - " 2\n", + " 4\n", " /home/poul/pyiron/contrib/notebooks/tinybase/t...\n", " finished\n", - " AseMinimizeTask\n", + " MurnaghanTask\n", " \n", " \n", " 2\n", - " 3\n", + " 4\n", " pyiron\n", - " murn\n", - " 3\n", + " min\n", + " 2\n", " 1\n", - " 3\n", + " 5\n", " /home/poul/pyiron/contrib/notebooks/tinybase/t...\n", - " finished\n", - " MurnaghanTask\n", + " collect\n", + " AseMinimizeTask\n", " \n", " \n", "\n", @@ -400,21 +455,21 @@ "text/plain": [ " id username name jobtype_id project_id status_id \\\n", "0 1 pyiron md 1 1 1 \n", - "1 2 pyiron min 2 1 2 \n", - "2 3 pyiron murn 3 1 3 \n", + "1 3 pyiron murn 3 1 4 \n", + "2 4 pyiron min 2 1 5 \n", "\n", " location status \\\n", "0 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", "1 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", - "2 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", + "2 /home/poul/pyiron/contrib/notebooks/tinybase/t... collect \n", "\n", " type \n", "0 AseMDTask \n", - "1 AseMinimizeTask \n", - "2 MurnaghanTask " + "1 MurnaghanTask \n", + "2 AseMinimizeTask " ] }, - "execution_count": 16, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -425,7 +480,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "59ea5510-b6ce-4317-90c3-4af77db3d59a", "metadata": { "tags": [] @@ -448,7 +503,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "3fb09d42-f800-46ee-9919-83180863e1ee", "metadata": { "tags": [] @@ -457,7 +512,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "22e49b9ad64a4b8793c3b70c5a228c5f", + "model_id": "e1d35958a5b44caba8dc6719f6493844", "version_major": 2, "version_minor": 0 }, @@ -483,7 +538,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "d32508b9-2854-4076-9109-08ede1b52dc2", "metadata": { "tags": [] @@ -500,7 +555,7 @@ " -0.999999995888409]" ] }, - "execution_count": 19, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -511,7 +566,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "id": "db691097-72c6-45a4-89b1-6ec16018c8b8", "metadata": { "tags": [] @@ -534,14 +589,14 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "id": "23ce6822-b38b-41f3-9269-109dbb152ecf", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "af82f8be0cb1474abeedb49698ee8c6c", + "model_id": "38fea66abec4408c8ba29585d7bb2419", "version_major": 2, "version_minor": 0 }, @@ -567,7 +622,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "id": "654ce992-b73f-42e3-a32e-2e0dafa7c952", "metadata": { "tags": [] @@ -579,7 +634,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 24, "id": "253237f0-b338-470c-bc54-3c7400a757b7", "metadata": {}, "outputs": [], @@ -590,17 +645,17 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "id": "c801093b-499e-48a7-8444-77602ed88a96", "metadata": {}, "outputs": [], "source": [ - "murn.input.structure = pr.create.structure.bulk(\"Fe\", a=1.2).to_ase()" + "murn.input.structure = pr.create.structure.bulk(\"Fe\", a=1.2).to_ase() # since our Atoms cannot be pickled, but parallel execution needs that we still convert back here" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "id": "1e30b36e-11e6-47d1-836e-cffea7b73cdd", "metadata": {}, "outputs": [], @@ -610,7 +665,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "id": "18b5305a-8950-44af-bc2e-c9734b059713", "metadata": {}, "outputs": [ @@ -625,8 +680,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 2.62 ms, sys: 1.24 ms, total: 3.86 ms\n", - "Wall time: 3.69 ms\n" + "CPU times: user 1.88 ms, sys: 845 µs, total: 2.73 ms\n", + "Wall time: 2.6 ms\n" ] } ], @@ -638,7 +693,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 28, "id": "836bb2ec-4295-4a3c-b976-7a35d04aad36", "metadata": {}, "outputs": [ @@ -669,7 +724,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "id": "79a2bb61-0a5e-4a3a-b195-46d027738a0e", "metadata": {}, "outputs": [], @@ -679,7 +734,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 30, "id": "b4e6e0c9-a2c6-40ab-884e-a46e16c37b04", "metadata": {}, "outputs": [ @@ -717,7 +772,7 @@ "Index: []" ] }, - "execution_count": 29, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -728,7 +783,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "id": "cef7c46f-551f-401e-96c2-214628e23967", "metadata": {}, "outputs": [ @@ -747,7 +802,7 @@ "murn = pr.create.job.Murnaghan(\"murn\")\n", "murn.input.task = pr.create.task.AseStatic()\n", "murn.input.task.input.calculator = MorsePotential()\n", - "murn.input.structure = pr.create.structure.bulk(\"Fe\", a=1.2).to_ase()\n", + "murn.input.structure = pr.create.structure.bulk(\"Fe\", a=1.2).to_ase() # since our Atoms cannot be pickled, but parallel execution needs that we still convert back here\n", "murn.input.set_strain_range(.5, 500)\n", "murn.run(executor='process')\n", "murn.wait()\n", @@ -756,7 +811,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 32, "id": "e8a7ee30-d7a6-46fc-bf98-1b52c981470f", "metadata": {}, "outputs": [ @@ -817,7 +872,7 @@ "0 MurnaghanTask " ] }, - "execution_count": 31, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -828,7 +883,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 33, "id": "30871447-3e20-46ee-a58e-853d4f4cb5d9", "metadata": {}, "outputs": [ @@ -842,7 +897,7 @@ ], "source": [ "j = pr.create.job.AseMD('md')\n", - "j.input.structure = pr.create.structure.bulk('Fe', a=1.2, cubic=True).repeat(2).to_ase()\n", + "j.input.structure = pr.create.structure.bulk('Fe', a=1.2, cubic=True).repeat(2).to_ase() # since our Atoms cannot be pickled, but parallel execution needs that we still convert back here\n", "j.input.calculator = MorsePotential()\n", "j.input.steps = 100\n", "j.input.timestep = 3.0\n", @@ -854,7 +909,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 34, "id": "e63d43c1-341f-4ec0-b0cf-9cd5ead51926", "metadata": {}, "outputs": [ @@ -929,7 +984,7 @@ "1 AseMDTask " ] }, - "execution_count": 33, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -948,7 +1003,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 35, "id": "80da39e2-76d1-42e6-977f-241d2683188d", "metadata": {}, "outputs": [ @@ -962,10 +1017,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 34, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -973,7 +1028,7 @@ "source": [ "sub = pr.open_location(\"/foo\")\n", "j = sub.create.job.AseMD('md')\n", - "j.input.structure = pr.create.structure.bulk('Fe', a=1.2, cubic=True).repeat(2).to_ase()\n", + "j.input.structure = pr.create.structure.bulk('Fe', a=1.2, cubic=True).repeat(2).to_ase() # since our Atoms cannot be pickled, but parallel execution needs that we still convert back here\n", "j.input.calculator = MorsePotential()\n", "j.input.steps = 100\n", "j.input.timestep = 3.0\n", @@ -984,7 +1039,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 36, "id": "a567f96a-cbb3-4d2d-95d1-6dcecee7ddb8", "metadata": {}, "outputs": [ @@ -1073,7 +1128,7 @@ "2 AseMDTask " ] }, - "execution_count": 35, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -1092,7 +1147,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 37, "id": "a6fefefb-b09c-4cee-b632-29f88bccfeee", "metadata": {}, "outputs": [], @@ -1102,7 +1157,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 38, "id": "3419f273-b94b-48cf-9373-7441baec2353", "metadata": {}, "outputs": [ @@ -1112,7 +1167,7 @@ "DatabaseEntry(name='murn', username='pyiron', project='/', status='finished', jobtype='MurnaghanTask')" ] }, - "execution_count": 37, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" } @@ -1131,7 +1186,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 39, "id": "cf75b2e8-ec15-4846-bda4-9b6661e8b5fa", "metadata": {}, "outputs": [], @@ -1141,7 +1196,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 40, "id": "1b23728b-1050-47a4-bda0-bd5967ceed2e", "metadata": {}, "outputs": [], @@ -1151,7 +1206,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 41, "id": "4cd73be9-2c6d-4501-8a6c-0afc6083a4b9", "metadata": {}, "outputs": [], @@ -1161,7 +1216,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 42, "id": "e9d9a3e2-5c86-46b4-b6d0-bec3a9f448b0", "metadata": {}, "outputs": [], @@ -1171,7 +1226,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 43, "id": "99059ff6-18bd-40b9-85fc-d76e1828f7ac", "metadata": {}, "outputs": [ @@ -1181,7 +1236,7 @@ "[]" ] }, - "execution_count": 42, + "execution_count": 43, "metadata": {}, "output_type": "execute_result" } @@ -1192,7 +1247,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 44, "id": "cb4bb9fe-13bf-4736-aa68-662b980d4f00", "metadata": {}, "outputs": [ @@ -1202,7 +1257,7 @@ "[(2, 'finished')]" ] }, - "execution_count": 43, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } @@ -1213,7 +1268,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 45, "id": "4641b048-b7c7-46a2-b67c-835c08916cb0", "metadata": {}, "outputs": [ @@ -1223,7 +1278,7 @@ "[(1, 'finished'), (2, 'finished'), (3, 'finished')]" ] }, - "execution_count": 44, + "execution_count": 45, "metadata": {}, "output_type": "execute_result" } @@ -1234,7 +1289,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 46, "id": "94364e54-b980-48cc-995b-daf977437b1b", "metadata": {}, "outputs": [ @@ -1244,7 +1299,7 @@ "[(1, '/'), (2, '/foo')]" ] }, - "execution_count": 45, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } @@ -1255,7 +1310,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 47, "id": "f32c43d5-19c8-4505-bf5c-9ac32bca51d0", "metadata": { "tags": [] @@ -1267,7 +1322,7 @@ "[(1, 'MurnaghanTask'), (2, 'AseMDTask')]" ] }, - "execution_count": 46, + "execution_count": 47, "metadata": {}, "output_type": "execute_result" } diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index 98740c291..33016c6cc 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -20,11 +20,17 @@ class AseInput(AbstractInput): calculator = StorageAttribute() + def _store(self, storage): + # if the calculator was attached to pyiron Atoms object, saving the calculator would fail, since it would be + # pickled, but our Atoms cannot be pickled. Therefore remove the reference here. If the task were to be + # re-executed after being loaded, the atoms would be reattached anyway. + self.calculator.atoms = None + super()._store(storage=storage) + class AseStaticInput(AseInput, StructureInput): pass - class AseStaticTask(AbstractTask): def _get_input(self): return AseStaticInput() diff --git a/pyiron_contrib/tinybase/storage.py b/pyiron_contrib/tinybase/storage.py index 524e30aa0..84e6da0ac 100644 --- a/pyiron_contrib/tinybase/storage.py +++ b/pyiron_contrib/tinybase/storage.py @@ -23,24 +23,25 @@ class GenericStorage(abc.ABC): Implementations must allow multiple objects of this class to refer to the same underlying storage group at the same time and access via the methods here must be atomic. - Mandatory overrides for all implementations are + Mandatory overrides for all implementations are - 1. :meth:`.__getitem__` to read values, + 1. :meth:`.__getitem__` to read values, 2. :meth:`._set` to write values, 3. :meth:`.create_group` to create sub groups, 4. :meth:`.list_nodes` and :meth:`.list_groups` to see the contained groups and nodes, 5. :attr:`.project` which is a back reference to the project that originally created this storage, 6. :attr:`.name` which is the name of the group that this object points to, e.g. `storage.create_group(name).name == name`. - If :meth:`_set` raises a `TypeError` indicating that it does not know how to store an object of a certain type, - :meth:`.__setitem__` will pickle it automatically and try to write it again. Such an object can be retrieved with - :meth:`.to_object`. - For values that implement :class:`.Storable` there is an intentional asymmetry in item writing and reading. Writing it calls :meth:`.Storable.store` automatically, but reading will return the :class:`.GenericStorage` group that was created during writing *without* calling :meth:`.Storable.restore` automatically. The original value can be obtained by calling :meth:`.GenericStorage.to_object` on the returned group. This is so that power users and developers can access sub objects efficiently without having to load all the containing objects first. + + If :meth:`_set` raises a `TypeError` indicating that it does not know how to store an object of a certain type, + :meth:`.__setitem__` will pickle it automatically and try to write it again. Such an object can be retrieved with + :meth:`.to_object`. The same is done for lists that contain elements which are not trivially storable in the + storage implementation, e.g. lists of :class:`.Atoms` or other complex objects. """ @abc.abstractmethod @@ -115,7 +116,10 @@ def __setitem__(self, item: str, value: Any): try: self._set(item, value) except TypeError: - self[item] = PickleStorable(value) + if isinstance(value, list): + self[item] = ListStorable(value) + else: + self[item] = PickleStorable(value) @abc.abstractmethod def create_group(self, name): @@ -156,6 +160,14 @@ def close(self) -> "GenericStorage": except AttributeError: return self + # compatibility with ProjectHDFio, so that implementations of GenericStorage can be used as a drop-in replacement + # for it + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + @abc.abstractmethod def list_nodes(self) -> list[str]: """ @@ -262,6 +274,13 @@ def project(self): def name(self): return self._hdf.name + def to_object(self): + try: + # Since we try to store object with _hdf[item] = value, which might trigger HasHDF functionality, we have to + # try here to restore the object via that functionality as well + return self._hdf.to_object() + except: + return super().to_object() class DataContainerAdapter(GenericStorage): """ @@ -408,3 +427,31 @@ def _store(self, storage): @classmethod def _restore(cls, storage, version): return pickle_load(storage["pickle"]) + +class ListStorable(Storable): + """ + Trivial implementation of :class:`.Storable` for lists with potentially complex objects inside. + + Used by :class:`.GenericStorage` as a fallback if storing the list with h5py/h5io as it is fails. + """ + def __init__(self, value): + self._value = value + + def _store(self, storage): + for i, v in enumerate(self._value): + storage[f"index_{i}"] = v + + @classmethod + def _restore(cls, storage, version): + keys = sorted( + [v for v in storage.list_nodes() if v.startswith("index_")] + + [v for v in storage.list_groups() if v.startswith("index_")], + key=lambda k: int(k.split("_")[1]) + ) + value = [] + for k in keys: + v = storage[k] + if isinstance(v, GenericStorage): + v = v.to_object() + value.append(v) + return value From ba7fdea05d291c8f0c2f446e61dacf8e10b2101c Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Wed, 12 Jul 2023 02:00:49 +0200 Subject: [PATCH 670/756] Catch ValueError as well FileHDFio raises also that --- pyiron_contrib/tinybase/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/storage.py b/pyiron_contrib/tinybase/storage.py index 84e6da0ac..34f739a5d 100644 --- a/pyiron_contrib/tinybase/storage.py +++ b/pyiron_contrib/tinybase/storage.py @@ -79,7 +79,7 @@ def get(self, item, default=None): """ try: value = self[item] - except KeyError: + except (KeyError, ValueError): if default is not None: return default else: From e1cb758c46f7374815bd1e409f74d85ef11c4517 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 25 Sep 2023 11:43:21 +0200 Subject: [PATCH 671/756] Update Basic Notebook --- notebooks/tinybase/Basic.ipynb | 209 +++++++++++++++++++++++---------- 1 file changed, 148 insertions(+), 61 deletions(-) diff --git a/notebooks/tinybase/Basic.ipynb b/notebooks/tinybase/Basic.ipynb index b0ce940cf..bdfddc021 100644 --- a/notebooks/tinybase/Basic.ipynb +++ b/notebooks/tinybase/Basic.ipynb @@ -45,7 +45,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "78241b2f48344bb6852122916cf3b573", + "model_id": "6c39844a5e564da6820d36bbba8a2060", "version_major": 2, "version_minor": 0 }, @@ -244,7 +244,7 @@ "data": { "text/plain": [ "(ReturnStatus(Code.DONE, None),\n", - " )" + " )" ] }, "execution_count": 12, @@ -631,16 +631,16 @@ { "data": { "text/plain": [ - "(,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " )" + "(,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " )" ] }, "execution_count": 35, @@ -744,16 +744,16 @@ { "data": { "text/plain": [ - "[,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ]" + "[,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ]" ] }, "execution_count": 40, @@ -844,7 +844,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 46, @@ -936,7 +936,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 51, "id": "b9807c98-6df8-450f-a8dd-1a53cb4ded35", "metadata": {}, "outputs": [], @@ -946,7 +946,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 52, "id": "ac2b9aa8-c118-4a1a-bf8b-96d6853b9be6", "metadata": {}, "outputs": [], @@ -956,30 +956,46 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 53, "id": "ef092015-5756-409a-bd1a-a31793c0b2b8", "metadata": {}, "outputs": [], "source": [ - "l.input.repeat(10, restart=lambda output, input: print(output.result))" + "l.input.repeat(10, restart=lambda output, input, scratch: print(output.result))" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 54, "id": "10b67618-f56e-4348-9fdc-35514d0e83a4", "metadata": { "tags": [] }, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.951652486500789\n", + "0.8479495911177689\n", + "0.39209981170147534\n", + "0.12626824643855517\n", + "0.1151548846424062\n", + "0.41394951366874244\n", + "0.32349310340372117\n", + "0.9985082815924705\n", + "0.036743594560547654\n", + "0.024840470009968807\n" + ] + }, { "data": { "text/plain": [ - "(ReturnStatus(Code.ABORTED, RepeatLoopControl._count_steps() takes 3 positional arguments but 4 were given),\n", - " )" + "(ReturnStatus(Code.DONE, None),\n", + " )" ] }, - "execution_count": 14, + "execution_count": 54, "metadata": {}, "output_type": "execute_result" } @@ -1000,15 +1016,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.009007595046278127\n", - "0.6981904244112043\n", - "0.09366484339630998\n", - "0.7249243225810421\n", - "0.9726438390471167\n", - "0.9140646976331244\n", - "0.5298676138281572\n", - "0.815698430213116\n", - "0.20660830939989228\n" + "0.4786223239196473\n", + "0.8186548613180863\n", + "0.028784009263032373\n", + "0.5010370915300685\n", + "0.9020777461434385\n", + "0.8501369870658283\n", + "0.20036590660650433\n", + "0.048401602265645605\n", + "0.7459809033150049\n", + "0.8064230025647129\n" ] } ], @@ -1029,7 +1046,7 @@ { "data": { "text/plain": [ - "0.2947900991153958" + "0.824491968944771" ] }, "execution_count": 56, @@ -1094,16 +1111,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.22463320545667087\n", - "0.31828116952079233\n", - "0.3147338930435303\n" + "0.2292513171117575\n", + "0.22555629178899173\n", + "0.9524191400881127\n" ] }, { "data": { "text/plain": [ "(ReturnStatus(Code.DONE, None),\n", - " )" + " )" ] }, "execution_count": 60, @@ -1120,12 +1137,22 @@ "id": "840944df-098e-4318-90c1-a66ec31dd513", "metadata": {}, "source": [ - "# Implementation Examples" + "# Implementation Examples\n", + "\n", + "For a much too simplified example, let's write a task that simply waits `n` times `time` seconds, where each of the `time` waits is a separate, independent task itself. In tinybase speak such a construct is a `TaskGenerator`, because it internally generates a sequence of atomic tasks that can be scheduled by an executor in whatever order. From a user's perspective however, a task generator behaves exactly like a task (and it implements the same internal interface).\n", + "\n", + "To write such a class, we need to\n", + "\n", + "1. define an input class;\n", + "2. define an output class\n", + "3. and combine them on the actual generator.\n", + "\n", + "For the waiting, we'll reuse the already introduces `FunctionTask` to wrap `time.sleep` from the standard library." ] }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 61, "id": "8ead2987-116c-4bba-a09a-4b28a71660f1", "metadata": { "tags": [] @@ -1137,29 +1164,52 @@ "import time\n", "\n", "class WaitInput(AbstractInput):\n", + " # this defines the input parameters\n", " time = StorageAttribute().type(float).default(10.0)\n", " n = StorageAttribute().type(int).default(10)\n", "\n", "class WaitOutput(AbstractOutput):\n", + " # we have no meaningful output, so we'll leave it empty.\n", " pass\n", "\n", "class WaitGenerator(TaskGenerator):\n", + " # here our task generator class, needs to advertise which input and output classes it is going to use\n", " def _get_input(self):\n", " return WaitInput()\n", " def _get_output(self):\n", " return WaitOutput()\n", " def __iter__(self):\n", + " # the main computation in a generator is defined in its __iter__ method.\n", + " # executors will iterate over the the results yielded here and inject back the results\n", + " # in each iteration the generator can dynamically return new tasks depending on the\n", + " # results that came back from an executor.\n", + "\n", + " # in our case we just have `n` independent waiting tasks, so we create them in a loop\n", + " # and yield them in one iteration; then discard their (anyway empty) outut and return\n", + " # our own return status\n", " tasks = []\n", " for _ in range(self.input.n):\n", - " tasks.append(t := FunctionTask(time.sleep))\n", + " t = FunctionTask(time.sleep)\n", + " tasks.append(t)\n", " t.input.args = [self.input.time]\n", " ret, out = zip(*(yield tasks))\n", " return ReturnStatus.done(), self._get_output()" ] }, + { + "cell_type": "markdown", + "id": "d4ed0b67-f732-4247-85b8-5aea5ef3c6dd", + "metadata": {}, + "source": [ + "Passing the `capture_exceptions` as `False` means tinybase will not catch any exceptions\n", + "and give us the direct stack trace where any exceptions occured. This is useful\n", + "for debugging a new implemention in a notebook like here. By default tinybase captures\n", + "exceptions and sets the return status to aborted automatically." + ] + }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 62, "id": "c4170017-0825-4e2c-87b2-ea4ddc14499e", "metadata": { "tags": [] @@ -1169,17 +1219,17 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 10.4 ms, sys: 0 ns, total: 10.4 ms\n", + "CPU times: user 0 ns, sys: 7.75 ms, total: 7.75 ms\n", "Wall time: 20 s\n" ] }, { "data": { "text/plain": [ - "(ReturnStatus(Code.DONE, None), <__main__.WaitOutput at 0x7fe47ecac3d0>)" + "(ReturnStatus(Code.DONE, None), <__main__.WaitOutput at 0x7fe13ab3b430>)" ] }, - "execution_count": 68, + "execution_count": 62, "metadata": {}, "output_type": "execute_result" } @@ -1188,12 +1238,22 @@ "%%time\n", "wait = WaitGenerator(capture_exceptions=False)\n", "wait.input.time = 2.0\n", + "wait.input.n = 10\n", "wait.execute()" ] }, + { + "cell_type": "markdown", + "id": "56971ff1-2af6-4b4d-a659-2e39d25b3d89", + "metadata": {}, + "source": [ + "Calling `execute` on a task generator will simply execute one task after the other.\n", + "We therefore expect the run time to be 2 * 10 s." + ] + }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 63, "id": "0ad95218-1e00-408e-8db3-858852d88e8f", "metadata": { "tags": [] @@ -1203,9 +1263,18 @@ "from pyiron_contrib.tinybase.executor import BackgroundExecutor" ] }, + { + "cell_type": "markdown", + "id": "b8a8ffc0-98da-46d4-99f5-42eae115b0db", + "metadata": {}, + "source": [ + "If we run with the process executor, but only give one core, we expect the run time\n", + "to stay the same." + ] + }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 64, "id": "dc30851f-ed76-4bde-979f-9b42286b1645", "metadata": { "tags": [] @@ -1215,7 +1284,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 37.8 ms, sys: 32.9 ms, total: 70.7 ms\n", + "CPU times: user 27.8 ms, sys: 32.7 ms, total: 60.5 ms\n", "Wall time: 20.1 s\n" ] } @@ -1227,9 +1296,18 @@ "exe.wait()" ] }, + { + "cell_type": "markdown", + "id": "41343290-a527-41e1-a1da-b2c3b77175df", + "metadata": {}, + "source": [ + "If we allow multiple cores to wait in parallel the run time naturally goes down accordingly\n", + "modulo overhead from the process pool." + ] + }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 65, "id": "d2c0e09b-bd43-4bd5-8c94-6ced8a12fa1a", "metadata": { "tags": [] @@ -1239,8 +1317,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 34.7 ms, sys: 31.9 ms, total: 66.6 ms\n", - "Wall time: 6.17 s\n" + "CPU times: user 20.1 ms, sys: 40.7 ms, total: 60.8 ms\n", + "Wall time: 6.08 s\n" ] } ], @@ -1251,9 +1329,18 @@ "exe.wait()" ] }, + { + "cell_type": "markdown", + "id": "f11b9d68-1632-450b-9360-e73ca4e92b03", + "metadata": {}, + "source": [ + "Since we are just waiting here, even running in separate threads gives the same speed up, \n", + "regardless of the GIL." + ] + }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 66, "id": "2bf18743-4760-4491-968c-49a7968ef6cf", "metadata": { "tags": [] @@ -1263,8 +1350,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 17.4 ms, sys: 0 ns, total: 17.4 ms\n", - "Wall time: 6.03 s\n" + "CPU times: user 21 ms, sys: 7.34 ms, total: 28.3 ms\n", + "Wall time: 6.04 s\n" ] } ], From ca68e9cbfbfe118dc66a35bc520462f99115aa00 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 25 Sep 2023 11:52:18 +0200 Subject: [PATCH 672/756] Update ASE notebook --- notebooks/tinybase/ASE.ipynb | 1108 +++++++++++++++++----------------- 1 file changed, 554 insertions(+), 554 deletions(-) diff --git a/notebooks/tinybase/ASE.ipynb b/notebooks/tinybase/ASE.ipynb index 2959baafa..76f4954ce 100644 --- a/notebooks/tinybase/ASE.ipynb +++ b/notebooks/tinybase/ASE.ipynb @@ -83,7 +83,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "f87e85a1403249f78fb6cbcbb0b58e70", + "model_id": "e525c8251f7d46c9b095738445c84ee3", "version_major": 2, "version_minor": 0 }, @@ -190,7 +190,7 @@ "data": { "text/plain": [ "(ReturnStatus(Code.DONE, None),\n", - " )" + " )" ] }, "execution_count": 11, @@ -310,7 +310,7 @@ "data": { "text/plain": [ "{'status': [ReturnStatus(Code.DONE, None)],\n", - " 'output': []}" + " 'output': []}" ] }, "execution_count": 18, @@ -356,7 +356,7 @@ { "data": { "text/plain": [ - "10.69577894699978" + "13.667204819008475" ] }, "execution_count": 20, @@ -379,7 +379,7 @@ { "data": { "text/plain": [ - "1.5048000932438299e-05" + "2.021901309490204e-05" ] }, "execution_count": 21, @@ -404,26 +404,26 @@ "text/plain": [ "[-303.20813267693006,\n", " -303.20813267693006,\n", - " -299.82357747361414,\n", - " -300.6835289315925,\n", - " -300.43053461438194,\n", - " -299.5151491098048,\n", - " -299.54357181795604,\n", - " -300.3822199456922,\n", - " -300.3933038182373,\n", - " -300.5594238753886,\n", - " -300.3492771122286,\n", - " -299.94782851220407,\n", - " -300.34637783367367,\n", - " -300.07711314103085,\n", - " -300.39471385080583,\n", - " -300.22032558954106,\n", - " -299.5895737832703,\n", - " -300.31533838531095,\n", - " -299.59517640853767,\n", - " -300.11269823052316,\n", - " -300.3264067673155,\n", - " -299.99353276482634]" + " -298.8085894149179,\n", + " -299.3766972106697,\n", + " -301.36059005048594,\n", + " -300.48700803508484,\n", + " -299.4514988001343,\n", + " -300.5461110484806,\n", + " -300.1525587459357,\n", + " -299.5459227092857,\n", + " -299.8837655656467,\n", + " -299.7924269457935,\n", + " -299.9374215650488,\n", + " -300.3039189066386,\n", + " -299.51393477344607,\n", + " -300.77468931627885,\n", + " -299.9168922163957,\n", + " -299.7884657059498,\n", + " -300.3987874842712,\n", + " -299.86105248449877,\n", + " -299.86928323862315,\n", + " -299.641272075462]" ] }, "execution_count": 22, @@ -445,7 +445,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -469,7 +469,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "ba0fa6e6b6f34818a1a1eea7b99067f9", + "model_id": "1058c675adf54d2f92c496f3d9b6fdd9", "version_major": 2, "version_minor": 0 }, @@ -553,7 +553,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "c9536e6d518f4c5faaaae8bb83ad3ba3", + "model_id": "ae1b14aef8564268a3172f8fe0cfa9dc", "version_major": 2, "version_minor": 0 }, @@ -620,10 +620,10 @@ "output_type": "stream", "text": [ " Step Time Energy fmax\n", - "GPMin: 0 15:23:56 11.122159 187.2462\n", - "GPMin: 1 15:23:56 -0.278268 1.5338\n", - "GPMin: 2 15:23:56 -0.996055 0.8010\n", - "GPMin: 3 15:23:56 -0.000000 0.0000\n" + "GPMin: 0 11:44:18 11.122159 187.2462\n", + "GPMin: 1 11:44:18 -0.278268 1.5338\n", + "GPMin: 2 11:44:18 -0.996055 0.8010\n", + "GPMin: 3 11:44:18 -0.000000 0.0000\n" ] } ], @@ -711,7 +711,7 @@ { "data": { "text/plain": [ - "0.05866858300214517" + "0.04294431797461584" ] }, "execution_count": 37, @@ -734,7 +734,7 @@ { "data": { "text/plain": [ - "1.803600025596097e-05" + "1.3895012671127915e-05" ] }, "execution_count": 38, @@ -807,7 +807,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "5c718a62abef46bab0002035afd1860f", + "model_id": "ecb4fc5738344545870545fc613b1e21", "version_major": 2, "version_minor": 0 }, @@ -878,7 +878,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 44, @@ -1088,7 +1088,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 54, "id": "89169376-be36-4ceb-9f4e-6e1f3247bc62", "metadata": {}, "outputs": [], @@ -1098,7 +1098,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 55, "id": "2ee9f1d4-5b14-4340-98d4-4bd293af89a4", "metadata": {}, "outputs": [], @@ -1110,17 +1110,17 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 56, "id": "3d73a9de-7b4e-476a-b50a-ac6a3957a7ab", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 10, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -1131,7 +1131,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 57, "id": "0f075d90-e636-49be-b1a6-741a56363f54", "metadata": {}, "outputs": [], @@ -1141,7 +1141,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 58, "id": "79c89012-5b28-4124-9681-2507e0690b49", "metadata": { "tags": [] @@ -1151,8 +1151,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 243 ms, sys: 146 ms, total: 389 ms\n", - "Wall time: 2.32 s\n" + "CPU times: user 353 ms, sys: 262 ms, total: 615 ms\n", + "Wall time: 3.09 s\n" ] } ], @@ -1165,7 +1165,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 59, "id": "004502ce-2022-476a-b634-4237a6009f43", "metadata": { "tags": [] @@ -1177,7 +1177,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 60, "id": "4c55e74b-5e7a-4c46-9eb1-1acf5dff2322", "metadata": { "tags": [] @@ -1197,8 +1197,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 3.38 s, sys: 1.8 s, total: 5.18 s\n", - "Wall time: 45.8 s\n" + "CPU times: user 4.76 s, sys: 2.6 s, total: 7.36 s\n", + "Wall time: 58.6 s\n" ] } ], @@ -1211,7 +1211,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 61, "id": "7fe57ead-1175-4a29-a398-9e0adf52973a", "metadata": { "tags": [] @@ -1223,7 +1223,7 @@ "[ReturnStatus(Code.DONE, None)]" ] }, - "execution_count": 15, + "execution_count": 61, "metadata": {}, "output_type": "execute_result" } @@ -1234,7 +1234,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 62, "id": "0d999302-6f1d-48b6-865e-4af578d35cf7", "metadata": { "tags": [] @@ -1321,1007 +1321,1007 @@ "output_type": "stream", "text": [ " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 3.991875 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 4.517693 0.0000\n", + "LBFGS: 0 11:45:22 4.789242 0.0000\n", + "LBFGS: 0 11:45:22 4.517693 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 4.789242 0.0000\n", + "LBFGS: 0 11:45:22 3.991875 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 4.251945 0.0000\n", + "LBFGS: 0 11:45:22 4.251945 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 3.488292 0.0000\n", + "LBFGS: 0 11:45:23 3.006013 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 3.244546 0.0000\n", + "LBFGS: 0 11:45:23 3.488292 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 3.006013 0.0000\n", + "LBFGS: 0 11:45:23 3.737364 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 3.737364 0.0000\n", + "LBFGS: 0 11:45:23 3.244546 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 2.772582 0.0000\n", + "LBFGS: 0 11:45:23 2.320604 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 2.544148 0.0000\n", + "LBFGS: 0 11:45:23 2.772582 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 2.320604 0.0000\n", + "LBFGS: 0 11:45:23 2.544148 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 2.101849 0.0000\n", + "LBFGS: 0 11:45:23 2.101849 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 1.887783 0.0000\n", + "LBFGS: 0 11:45:23 1.887783 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 1.678307 0.0000\n", + "LBFGS: 0 11:45:23 1.678307 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 1.473327 0.0000\n", + "LBFGS: 0 11:45:23 1.272749 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 1.272749 0.0000\n", + "LBFGS: 0 11:45:23 1.473327 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 1.076481 0.0000\n", + "LBFGS: 0 11:45:23 0.884435 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 0.884435 0.0000\n", + "LBFGS: 0 11:45:23 1.076481 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 0.512659 0.0000\n", + "LBFGS: 0 11:45:23 0.696523 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 0.696523 0.0000\n", + "LBFGS: 0 11:45:23 0.512659 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 0.332761 0.0000\n", + "LBFGS: 0 11:45:23 0.332761 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 0.156747 0.0000\n", + "LBFGS: 0 11:45:23 0.156747 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:47 -0.183946 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -0.015462 0.0000\n", + "LBFGS: 0 11:45:23 -0.183946 0.0000\n", + "LBFGS: 0 11:45:23 -0.015462 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -0.348779 0.0000\n", + "LBFGS: 0 11:45:23 -0.348779 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -0.822116 0.0000\n", + "LBFGS: 0 11:45:23 -0.510037 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -0.510037 0.0000\n", + "LBFGS: 0 11:45:23 -0.667792 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -0.973078 0.0000\n", + "LBFGS: 0 11:45:23 -0.822116 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -0.667792 0.0000\n", + "LBFGS: 0 11:45:23 -0.973078 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -1.406469 0.0000\n", + "LBFGS: 0 11:45:23 -1.120747 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -1.120747 0.0000\n", + "LBFGS: 0 11:45:23 -1.406469 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -1.544652 0.0000\n", + "LBFGS: 0 11:45:23 -1.544652 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -1.265189 0.0000\n", + "LBFGS: 0 11:45:23 -1.265189 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -1.679799 0.0000\n", + "LBFGS: 0 11:45:23 -1.679799 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -1.811973 0.0000\n", + "LBFGS: 0 11:45:23 -1.941232 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -2.067636 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -1.941232 0.0000\n", + "LBFGS: 0 11:45:23 -1.811973 0.0000\n", + "LBFGS: 0 11:45:23 -2.067636 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -2.312104 0.0000\n", + "LBFGS: 0 11:45:23 -2.191241 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -2.191241 0.0000\n", + "LBFGS: 0 11:45:23 -2.312104 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -2.430279 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -2.545820 0.0000\n", + "LBFGS: 0 11:45:23 -2.430279 0.0000\n", + "LBFGS: 0 11:45:23 -2.545820 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -2.658780 0.0000\n", + "LBFGS: 0 11:45:23 -2.658780 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -2.769210 0.0000\n", + "LBFGS: 0 11:45:23 -2.769210 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -2.877160 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -2.982680 0.0000\n", + "LBFGS: 0 11:45:23 -2.982680 0.0000\n", + "LBFGS: 0 11:45:23 -2.877160 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -3.186620 0.0000\n", + "LBFGS: 0 11:45:23 -3.085817 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -3.085817 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:23 -3.186620 0.0000\n", + "LBFGS: 0 11:45:23 -3.381404 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -3.285134 0.0000\n", - "LBFGS: 0 15:24:48 -3.381404 0.0000\n", + "LBFGS: 0 11:45:23 -3.285134 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -3.475475 0.0000\n", + "LBFGS: 0 11:45:23 -3.657192 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -3.567390 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -3.744922 0.0000\n", + "LBFGS: 0 11:45:23 -3.475475 0.0000\n", + "LBFGS: 0 11:45:23 -3.567390 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -3.830620 0.0000\n", + "LBFGS: 0 11:45:23 -3.744922 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -3.657192 0.0000\n", + "LBFGS: 0 11:45:24 -3.830620 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -3.914328 0.0000\n", + "LBFGS: 0 11:45:24 -3.914328 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -3.996084 0.0000\n", + "LBFGS: 0 11:45:24 -3.996084 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:48 -4.075925 0.0000\n", + "LBFGS: 0 11:45:24 -4.075925 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -4.153891 0.0000\n", + "LBFGS: 0 11:45:24 -4.153891 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -4.230016 0.0000\n", + "LBFGS: 0 11:45:24 -4.230016 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -4.304338 0.0000\n", + "LBFGS: 0 11:45:24 -4.304338 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -4.447711 0.0000\n", + "LBFGS: 0 11:45:24 -4.376892 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -4.516830 0.0000\n", + "LBFGS: 0 11:45:24 -4.516830 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -4.376892 0.0000\n", + "LBFGS: 0 11:45:24 -4.584282 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -4.584282 0.0000\n", + "LBFGS: 0 11:45:24 -4.447711 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -4.650099 0.0000\n", + "LBFGS: 0 11:45:24 -4.650099 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -4.776956 0.0000\n", + "LBFGS: 0 11:45:24 -4.838057 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:24 -4.714313 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -4.714313 0.0000\n", - "LBFGS: 0 15:24:49 -4.838057 0.0000\n", + "LBFGS: 0 11:45:24 -4.776956 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -4.955755 0.0000\n", + "LBFGS: 0 11:45:24 -4.955755 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -4.897647 0.0000\n", + "LBFGS: 0 11:45:24 -4.897647 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.012409 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.067638 0.0000\n", + "LBFGS: 0 11:45:24 -5.012409 0.0000\n", + "LBFGS: 0 11:45:24 -5.067638 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.173930 0.0000\n", + "LBFGS: 0 11:45:24 -5.121469 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:24 -5.225046 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.121469 0.0000\n", - "LBFGS: 0 15:24:49 -5.225046 0.0000\n", + "LBFGS: 0 11:45:24 -5.274844 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.323350 0.0000\n", + "LBFGS: 0 11:45:24 -5.173930 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.274844 0.0000\n", + "LBFGS: 0 11:45:24 -5.370587 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.416581 0.0000\n", + "LBFGS: 0 11:45:24 -5.323350 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.370587 0.0000\n", + "LBFGS: 0 11:45:24 -5.416581 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.461356 0.0000\n", + "LBFGS: 0 11:45:24 -5.461356 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.504934 0.0000\n", + "LBFGS: 0 11:45:24 -5.504934 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.547340 0.0000\n", + "LBFGS: 0 11:45:24 -5.547340 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.588595 0.0000\n", + "LBFGS: 0 11:45:24 -5.588595 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.667742 0.0000\n", + "LBFGS: 0 11:45:24 -5.667742 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.628722 0.0000\n", + "LBFGS: 0 11:45:24 -5.705677 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.705677 0.0000\n", + "LBFGS: 0 11:45:24 -5.628722 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.742548 0.0000\n", + "LBFGS: 0 11:45:24 -5.778375 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.813177 0.0000\n", + "LBFGS: 0 11:45:24 -5.742548 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:49 -5.778375 0.0000\n", + "LBFGS: 0 11:45:24 -5.813177 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -5.911636 0.0000\n", + "LBFGS: 0 11:45:24 -5.846976 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -5.879789 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -5.846976 0.0000\n", + "LBFGS: 0 11:45:24 -5.879789 0.0000\n", + "LBFGS: 0 11:45:24 -5.911636 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -5.942536 0.0000\n", + "LBFGS: 0 11:45:24 -5.942536 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -5.972506 0.0000\n", + "LBFGS: 0 11:45:24 -6.029730 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:24 -5.972506 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.057017 0.0000\n", - "LBFGS: 0 15:24:50 -6.001565 0.0000\n", + "LBFGS: 0 11:45:24 -6.001565 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.029730 0.0000\n", + "LBFGS: 0 11:45:24 -6.057017 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:24 -6.083445 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.109029 0.0000\n", - "LBFGS: 0 15:24:50 -6.133786 0.0000\n", + "LBFGS: 0 11:45:24 -6.109029 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.083445 0.0000\n", + "LBFGS: 0 11:45:24 -6.133786 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.180881 0.0000\n", + "LBFGS: 0 11:45:24 -6.157731 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.157731 0.0000\n", + "LBFGS: 0 11:45:24 -6.180881 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.203250 0.0000\n", + "LBFGS: 0 11:45:24 -6.203250 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.224853 0.0000\n", + "LBFGS: 0 11:45:25 -6.224853 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.265820 0.0000\n", + "LBFGS: 0 11:45:25 -6.245705 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.245705 0.0000\n", + "LBFGS: 0 11:45:25 -6.265820 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.303898 0.0000\n", + "LBFGS: 0 11:45:25 -6.303898 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.339196 0.0000\n", + "LBFGS: 0 11:45:25 -6.285213 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.321888 0.0000\n", + "LBFGS: 0 11:45:25 -6.321888 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:25 -6.371820 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.355836 0.0000\n", - "LBFGS: 0 15:24:50 -6.285213 0.0000\n", + "LBFGS: 0 11:45:25 -6.355836 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.371820 0.0000\n", + "LBFGS: 0 11:45:25 -6.339196 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.415965 0.0000\n", + "LBFGS: 0 11:45:25 -6.401872 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.401872 0.0000\n", + "LBFGS: 0 11:45:25 -6.415965 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.429451 0.0000\n", + "LBFGS: 0 11:45:25 -6.429451 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.387162 0.0000\n", + "LBFGS: 0 11:45:25 -6.387162 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.454650 0.0000\n", + "LBFGS: 0 11:45:25 -6.442342 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.442342 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.477561 0.0000\n", + "LBFGS: 0 11:45:25 -6.454650 0.0000\n", + "LBFGS: 0 11:45:25 -6.466386 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.488186 0.0000\n", + "LBFGS: 0 11:45:25 -6.488186 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:25 -6.498271 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.498271 0.0000\n", - "LBFGS: 0 15:24:50 -6.466386 0.0000\n", + "LBFGS: 0 11:45:25 -6.477561 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.507828 0.0000\n", + "LBFGS: 0 11:45:25 -6.507828 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.516865 0.0000\n", + "LBFGS: 0 11:45:25 -6.525395 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:25 -6.516865 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.533425 0.0000\n", - "LBFGS: 0 15:24:50 -6.525395 0.0000\n", + "LBFGS: 0 11:45:25 -6.533425 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.540966 0.0000\n", + "LBFGS: 0 11:45:25 -6.540966 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:25 -6.554620 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.548028 0.0000\n", - "LBFGS: 0 15:24:50 -6.560750 0.0000\n", + "LBFGS: 0 11:45:25 -6.548028 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.566429 0.0000\n", + "LBFGS: 0 11:45:25 -6.560750 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.554620 0.0000\n", + "LBFGS: 0 11:45:25 -6.566429 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.571664 0.0000\n", + "LBFGS: 0 11:45:25 -6.576465 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.576465 0.0000\n", + "LBFGS: 0 11:45:25 -6.571664 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.580840 0.0000\n", + "LBFGS: 0 11:45:25 -6.580840 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.584797 0.0000\n", + "LBFGS: 0 11:45:25 -6.588345 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.588345 0.0000\n", + "LBFGS: 0 11:45:25 -6.591492 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.591492 0.0000\n", + "LBFGS: 0 11:45:25 -6.584797 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.594245 0.0000\n", + "LBFGS: 0 11:45:25 -6.596612 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.596612 0.0000\n", + "LBFGS: 0 11:45:25 -6.598601 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.598601 0.0000\n", + "LBFGS: 0 11:45:25 -6.594245 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.602374 0.0000\n", + "LBFGS: 0 11:45:25 -6.600220 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:25 -6.602924 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.600220 0.0000\n", - "LBFGS: 0 15:24:50 -6.602924 0.0000\n", + "LBFGS: 0 11:45:25 -6.602374 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:50 -6.601475 0.0000\n", + "LBFGS: 0 11:45:25 -6.601475 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.603132 0.0000\n", + "LBFGS: 0 11:45:25 -6.603132 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:25 -6.603005 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.602549 0.0000\n", - "LBFGS: 0 15:24:51 -6.603005 0.0000\n", + "LBFGS: 0 11:45:25 -6.602549 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.601771 0.0000\n", + "LBFGS: 0 11:45:25 -6.601771 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.599275 0.0000\n", + "LBFGS: 0 11:45:25 -6.600678 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.600678 0.0000\n", + "LBFGS: 0 11:45:25 -6.597570 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.597570 0.0000\n", + "LBFGS: 0 11:45:25 -6.599275 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.595568 0.0000\n", + "LBFGS: 0 11:45:25 -6.595568 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.593275 0.0000\n", + "LBFGS: 0 11:45:25 -6.584710 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.590697 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:25 -6.593275 0.0000\n", + "LBFGS: 0 11:45:25 -6.590697 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.587840 0.0000\n", - "LBFGS: 0 15:24:51 -6.581312 0.0000\n", + "LBFGS: 0 11:45:25 -6.587840 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.584710 0.0000\n", + "LBFGS: 0 11:45:25 -6.577651 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.577651 0.0000\n", + "LBFGS: 0 11:45:25 -6.573734 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:25 -6.581312 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.569565 0.0000\n", - "LBFGS: 0 15:24:51 -6.565149 0.0000\n", + "LBFGS: 0 11:45:25 -6.569565 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.573734 0.0000\n", + "LBFGS: 0 11:45:25 -6.565149 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.555599 0.0000\n", + "LBFGS: 0 11:45:25 -6.555599 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:25 -6.560493 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.560493 0.0000\n", - "LBFGS: 0 15:24:51 -6.550475 0.0000\n", + "LBFGS: 0 11:45:25 -6.550475 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.545124 0.0000\n", + "LBFGS: 0 11:45:25 -6.533761 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.539551 0.0000\n", + "LBFGS: 0 11:45:25 -6.539551 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:25 -6.527759 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.533761 0.0000\n", - "LBFGS: 0 15:24:51 -6.527759 0.0000\n", + "LBFGS: 0 11:45:25 -6.545124 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.521548 0.0000\n", + "LBFGS: 0 11:45:25 -6.515134 0.0000\n", " Step Time Energy fmax\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.494713 0.0000\n", - "LBFGS: 0 15:24:51 -6.501712 0.0000\n", + "LBFGS: 0 11:45:25 -6.508521 0.0000\n", + "LBFGS: 0 11:45:25 -6.521548 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.515134 0.0000\n", + "LBFGS: 0 11:45:25 -6.501712 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.508521 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.480158 0.0000\n", + "LBFGS: 0 11:45:25 -6.487527 0.0000\n", + "LBFGS: 0 11:45:25 -6.494713 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.487527 0.0000\n", + "LBFGS: 0 11:45:25 -6.480158 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.472611 0.0000\n", + "LBFGS: 0 11:45:25 -6.472611 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:25 -6.464889 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.464889 0.0000\n", - "LBFGS: 0 15:24:51 -6.456996 0.0000\n", + "LBFGS: 0 11:45:25 -6.448936 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.448936 0.0000\n", + "LBFGS: 0 11:45:25 -6.456996 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.440712 0.0000\n", + "LBFGS: 0 11:45:25 -6.440712 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.432329 0.0000\n", + "LBFGS: 0 11:45:25 -6.432329 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.415096 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.423789 0.0000\n", + "LBFGS: 0 11:45:25 -6.415096 0.0000\n", + "LBFGS: 0 11:45:25 -6.423789 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.397266 0.0000\n", + "LBFGS: 0 11:45:25 -6.406254 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.388136 0.0000\n", + "LBFGS: 0 11:45:26 -6.397266 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.406254 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.378866 0.0000\n", + "LBFGS: 0 11:45:26 -6.378866 0.0000\n", + "LBFGS: 0 11:45:26 -6.388136 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.359921 0.0000\n", + "LBFGS: 0 11:45:26 -6.369460 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.369460 0.0000\n", + "LBFGS: 0 11:45:26 -6.340455 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.350251 0.0000\n", + "LBFGS: 0 11:45:26 -6.330535 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.340455 0.0000\n", + "LBFGS: 0 11:45:26 -6.359921 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.330535 0.0000\n", + "LBFGS: 0 11:45:26 -6.350251 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.320493 0.0000\n", + "LBFGS: 0 11:45:26 -6.320493 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.300058 0.0000\n", + "LBFGS: 0 11:45:26 -6.310333 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.310333 0.0000\n", + "LBFGS: 0 11:45:26 -6.300058 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.289669 0.0000\n", + "LBFGS: 0 11:45:26 -6.289669 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.279171 0.0000\n", + "LBFGS: 0 11:45:26 -6.279171 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.257855 0.0000\n", + "LBFGS: 0 11:45:26 -6.268565 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.268565 0.0000\n", + "LBFGS: 0 11:45:26 -6.257855 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.247042 0.0000\n", + "LBFGS: 0 11:45:26 -6.247042 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.214016 0.0000\n", + "LBFGS: 0 11:45:26 -6.236130 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.236130 0.0000\n", + "LBFGS: 0 11:45:26 -6.225120 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.225120 0.0000\n", + "LBFGS: 0 11:45:26 -6.214016 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.202819 0.0000\n", + "LBFGS: 0 11:45:26 -6.202819 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.180158 0.0000\n", + "LBFGS: 0 11:45:26 -6.180158 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.191532 0.0000\n", + "LBFGS: 0 11:45:26 -6.191532 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.168698 0.0000\n", + "LBFGS: 0 11:45:26 -6.168698 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.157154 0.0000\n", + "LBFGS: 0 11:45:26 -6.157154 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.145530 0.0000\n", + "LBFGS: 0 11:45:26 -6.145530 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.133827 0.0000\n", + "LBFGS: 0 11:45:26 -6.133827 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.122047 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.110193 0.0000\n", + "LBFGS: 0 11:45:26 -6.122047 0.0000\n", + "LBFGS: 0 11:45:26 -6.110193 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.098266 0.0000\n", + "LBFGS: 0 11:45:26 -6.098266 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.074202 0.0000\n", + "LBFGS: 0 11:45:26 -6.086268 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:26 -6.074202 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.086268 0.0000\n", - "LBFGS: 0 15:24:51 -6.062068 0.0000\n", + "LBFGS: 0 11:45:26 -6.062068 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.025288 0.0000\n", + "LBFGS: 0 11:45:26 -6.049871 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:51 -6.049871 0.0000\n", + "LBFGS: 0 11:45:26 -6.025288 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -6.012907 0.0000\n", + "LBFGS: 0 11:45:26 -6.037610 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -6.037610 0.0000\n", + "LBFGS: 0 11:45:26 -6.012907 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.987974 0.0000\n", + "LBFGS: 0 11:45:26 -6.000468 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -6.000468 0.0000\n", + "LBFGS: 0 11:45:26 -5.975426 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.962825 0.0000\n", + "LBFGS: 0 11:45:26 -5.950174 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.975426 0.0000\n", + "LBFGS: 0 11:45:26 -5.987974 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.950174 0.0000\n", + "LBFGS: 0 11:45:26 -5.962825 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.937473 0.0000\n", + "LBFGS: 0 11:45:26 -5.937473 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.924725 0.0000\n", + "LBFGS: 0 11:45:26 -5.911931 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.899093 0.0000\n", + "LBFGS: 0 11:45:26 -5.924725 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.911931 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.886212 0.0000\n", + "LBFGS: 0 11:45:26 -5.899093 0.0000\n", + "LBFGS: 0 11:45:26 -5.886212 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.873290 0.0000\n", + "LBFGS: 0 11:45:26 -5.873290 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.860327 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.847326 0.0000\n", + "LBFGS: 0 11:45:26 -5.847326 0.0000\n", + "LBFGS: 0 11:45:26 -5.860327 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.834289 0.0000\n", + "LBFGS: 0 11:45:26 -5.834289 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.821215 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.808107 0.0000\n", + "LBFGS: 0 11:45:26 -5.821215 0.0000\n", + "LBFGS: 0 11:45:26 -5.808107 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:26 -5.794966 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.781794 0.0000\n", - "LBFGS: 0 15:24:52 -5.794966 0.0000\n", + "LBFGS: 0 11:45:26 -5.781794 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.768591 0.0000\n", + "LBFGS: 0 11:45:26 -5.755359 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.742099 0.0000\n", + "LBFGS: 0 11:45:26 -5.768591 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.755359 0.0000\n", + "LBFGS: 0 11:45:26 -5.742099 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.728813 0.0000\n", + "LBFGS: 0 11:45:26 -5.728813 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.715501 0.0000\n", + "LBFGS: 0 11:45:26 -5.715501 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.688805 0.0000\n", + "LBFGS: 0 11:45:26 -5.702165 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.702165 0.0000\n", + "LBFGS: 0 11:45:27 -5.688805 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.675424 0.0000\n", + "LBFGS: 0 11:45:27 -5.675424 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.662022 0.0000\n", + "LBFGS: 0 11:45:27 -5.648600 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.635159 0.0000\n", + "LBFGS: 0 11:45:27 -5.662022 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.621701 0.0000\n", + "LBFGS: 0 11:45:27 -5.635159 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.648600 0.0000\n", + "LBFGS: 0 11:45:27 -5.621701 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.608226 0.0000\n", + "LBFGS: 0 11:45:27 -5.594735 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.594735 0.0000\n", + "LBFGS: 0 11:45:27 -5.608226 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:27 -5.567712 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.581230 0.0000\n", - "LBFGS: 0 15:24:52 -5.567712 0.0000\n", + "LBFGS: 0 11:45:27 -5.581230 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.554180 0.0000\n", + "LBFGS: 0 11:45:27 -5.540637 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.540637 0.0000\n", + "LBFGS: 0 11:45:27 -5.513519 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.513519 0.0000\n", + "LBFGS: 0 11:45:27 -5.527083 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.499947 0.0000\n", + "LBFGS: 0 11:45:27 -5.554180 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.527083 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.486366 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.459183 0.0000\n", + "LBFGS: 0 11:45:27 -5.472778 0.0000\n", + "LBFGS: 0 11:45:27 -5.499947 0.0000\n", + "LBFGS: 0 11:45:27 -5.486366 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.472778 0.0000\n", + "LBFGS: 0 11:45:27 -5.459183 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.445583 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.431978 0.0000\n", + "LBFGS: 0 11:45:27 -5.418369 0.0000\n", + "LBFGS: 0 11:45:27 -5.445583 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:27 -5.431978 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.391143 0.0000\n", - "LBFGS: 0 15:24:52 -5.404757 0.0000\n", + "LBFGS: 0 11:45:27 -5.391143 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.418369 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.377527 0.0000\n", + "LBFGS: 0 11:45:27 -5.404757 0.0000\n", + "LBFGS: 0 11:45:27 -5.377527 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.350293 0.0000\n", + "LBFGS: 0 11:45:27 -5.363910 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.363910 0.0000\n", + "LBFGS: 0 11:45:27 -5.336676 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.323061 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:27 -5.323061 0.0000\n", + "LBFGS: 0 11:45:27 -5.350293 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.295837 0.0000\n", - "LBFGS: 0 15:24:52 -5.336676 0.0000\n", + "LBFGS: 0 11:45:27 -5.309448 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.309448 0.0000\n", + "LBFGS: 0 11:45:27 -5.268625 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.268625 0.0000\n", + "LBFGS: 0 11:45:27 -5.282229 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.282229 0.0000\n", + "LBFGS: 0 11:45:27 -5.295837 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.255026 0.0000\n", + "LBFGS: 0 11:45:27 -5.255026 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.241432 0.0000\n", + "LBFGS: 0 11:45:27 -5.241432 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.227844 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.214262 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:27 -5.214262 0.0000\n", + "LBFGS: 0 11:45:27 -5.227844 0.0000\n", + "LBFGS: 0 11:45:27 -5.200687 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.173560 0.0000\n", - "LBFGS: 0 15:24:52 -5.187119 0.0000\n", + "LBFGS: 0 11:45:27 -5.160009 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.200687 0.0000\n", + "LBFGS: 0 11:45:27 -5.187119 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.160009 0.0000\n", + "LBFGS: 0 11:45:27 -5.173560 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.105903 0.0000\n", + "LBFGS: 0 11:45:27 -5.146468 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:27 -5.119414 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.132936 0.0000\n", - "LBFGS: 0 15:24:52 -5.119414 0.0000\n", + "LBFGS: 0 11:45:27 -5.132936 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.146468 0.0000\n", + "LBFGS: 0 11:45:27 -5.105903 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.092403 0.0000\n", + "LBFGS: 0 11:45:27 -5.092403 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.065440 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.078915 0.0000\n", + "LBFGS: 0 11:45:27 -5.078915 0.0000\n", + "LBFGS: 0 11:45:27 -5.065440 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.051977 0.0000\n", + "LBFGS: 0 11:45:27 -5.051977 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.038527 0.0000\n", + "LBFGS: 0 11:45:27 -5.038527 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.011668 0.0000\n", + "LBFGS: 0 11:45:27 -5.025090 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -5.025090 0.0000\n", + "LBFGS: 0 11:45:27 -5.011668 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -4.984867 0.0000\n", + "LBFGS: 0 11:45:27 -4.998260 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:52 -4.998260 0.0000\n", + "LBFGS: 0 11:45:27 -4.984867 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.958128 0.0000\n", + "LBFGS: 0 11:45:27 -4.971490 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.971490 0.0000\n", + "LBFGS: 0 11:45:27 -4.958128 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.944782 0.0000\n", + "LBFGS: 0 11:45:27 -4.944782 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.931453 0.0000\n", + "LBFGS: 0 11:45:28 -4.918140 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.918140 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.904845 0.0000\n", + "LBFGS: 0 11:45:28 -4.931453 0.0000\n", + "LBFGS: 0 11:45:28 -4.904845 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.891567 0.0000\n", + "LBFGS: 0 11:45:28 -4.891567 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:28 -4.851844 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.851844 0.0000\n", - "LBFGS: 0 15:24:53 -4.878308 0.0000\n", + "LBFGS: 0 11:45:28 -4.865067 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.865067 0.0000\n", + "LBFGS: 0 11:45:28 -4.878308 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.838641 0.0000\n", + "LBFGS: 0 11:45:28 -4.838641 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.812292 0.0000\n", + "LBFGS: 0 11:45:28 -4.812292 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:28 -4.825456 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.799147 0.0000\n", - "LBFGS: 0 15:24:53 -4.825456 0.0000\n", + "LBFGS: 0 11:45:28 -4.786023 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:28 -4.799147 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.772919 0.0000\n", - "LBFGS: 0 15:24:53 -4.786023 0.0000\n", + "LBFGS: 0 11:45:28 -4.772919 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.759837 0.0000\n", + "LBFGS: 0 11:45:28 -4.759837 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.746775 0.0000\n", + "LBFGS: 0 11:45:28 -4.746775 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:28 -4.733735 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.720716 0.0000\n", - "LBFGS: 0 15:24:53 -4.733735 0.0000\n", + "LBFGS: 0 11:45:28 -4.707720 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.707720 0.0000\n", + "LBFGS: 0 11:45:28 -4.720716 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.694745 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.655959 0.0000\n", + "LBFGS: 0 11:45:28 -4.694745 0.0000\n", + "LBFGS: 0 11:45:28 -4.681794 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.668865 0.0000\n", + "LBFGS: 0 11:45:28 -4.655959 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.681794 0.0000\n", + "LBFGS: 0 11:45:28 -4.668865 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.643076 0.0000\n", + "LBFGS: 0 11:45:28 -4.643076 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.617381 0.0000\n", + "LBFGS: 0 11:45:28 -4.630216 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.630216 0.0000\n", + "LBFGS: 0 11:45:28 -4.617381 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.604569 0.0000\n", + "LBFGS: 0 11:45:28 -4.604569 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.591781 0.0000\n", + "LBFGS: 0 11:45:28 -4.591781 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.579018 0.0000\n", + "LBFGS: 0 11:45:28 -4.579018 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.553565 0.0000\n", + "LBFGS: 0 11:45:28 -4.566279 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.566279 0.0000\n", + "LBFGS: 0 11:45:28 -4.553565 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.528212 0.0000\n", + "LBFGS: 0 11:45:28 -4.528212 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.502960 0.0000\n", + "LBFGS: 0 11:45:28 -4.515574 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.540876 0.0000\n", + "LBFGS: 0 11:45:28 -4.540876 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.515574 0.0000\n", + "LBFGS: 0 11:45:28 -4.502960 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.490373 0.0000\n", + "LBFGS: 0 11:45:28 -4.477811 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.477811 0.0000\n", + "LBFGS: 0 11:45:28 -4.465275 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:28 -4.490373 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.440283 0.0000\n", - "LBFGS: 0 15:24:53 -4.465275 0.0000\n", + "LBFGS: 0 11:45:28 -4.440283 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.427826 0.0000\n", + "LBFGS: 0 11:45:28 -4.452766 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.415396 0.0000\n", + "LBFGS: 0 11:45:28 -4.427826 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.452766 0.0000\n", + "LBFGS: 0 11:45:28 -4.415396 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.402992 0.0000\n", + "LBFGS: 0 11:45:28 -4.402992 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.365944 0.0000\n", + "LBFGS: 0 11:45:28 -4.390616 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.378267 0.0000\n", + "LBFGS: 0 11:45:28 -4.365944 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.390616 0.0000\n", + "LBFGS: 0 11:45:28 -4.378267 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.329142 0.0000\n", + "LBFGS: 0 11:45:28 -4.353649 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.353649 0.0000\n", + "LBFGS: 0 11:45:28 -4.341382 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.341382 0.0000\n", + "LBFGS: 0 11:45:28 -4.329142 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.316929 0.0000\n", + "LBFGS: 0 11:45:29 -4.304745 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:29 -4.316929 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.304745 0.0000\n", - "LBFGS: 0 15:24:53 -4.292588 0.0000\n", + "LBFGS: 0 11:45:29 -4.292588 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:29 -4.280459 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.268359 0.0000\n", - "LBFGS: 0 15:24:53 -4.280459 0.0000\n", + "LBFGS: 0 11:45:29 -4.256286 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:53 -4.256286 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.232226 0.0000\n", + "LBFGS: 0 11:45:29 -4.268359 0.0000\n", + "LBFGS: 0 11:45:29 -4.244242 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.244242 0.0000\n", + "LBFGS: 0 11:45:29 -4.232226 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.220239 0.0000\n", + "LBFGS: 0 11:45:29 -4.220239 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.208280 0.0000\n", + "LBFGS: 0 11:45:29 -4.208280 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.196350 0.0000\n", + "LBFGS: 0 11:45:29 -4.196350 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.184448 0.0000\n", + "LBFGS: 0 11:45:29 -4.172575 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.172575 0.0000\n", + "LBFGS: 0 11:45:29 -4.184448 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.148917 0.0000\n", + "LBFGS: 0 11:45:29 -4.160732 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:29 -4.137131 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.137131 0.0000\n", - "LBFGS: 0 15:24:54 -4.125374 0.0000\n", + "LBFGS: 0 11:45:29 -4.148917 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.160732 0.0000\n", + "LBFGS: 0 11:45:29 -4.113646 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.113646 0.0000\n", + "LBFGS: 0 11:45:29 -4.125374 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.101948 0.0000\n", + "LBFGS: 0 11:45:29 -4.090278 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.090278 0.0000\n", + "LBFGS: 0 11:45:29 -4.101948 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.078638 0.0000\n", + "LBFGS: 0 11:45:29 -4.078638 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.067028 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.055446 0.0000\n", + "LBFGS: 0 11:45:29 -4.055446 0.0000\n", + "LBFGS: 0 11:45:29 -4.067028 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.032372 0.0000\n", + "LBFGS: 0 11:45:29 -4.043894 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.043894 0.0000\n", + "LBFGS: 0 11:45:29 -4.020879 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -4.020879 0.0000\n", + "LBFGS: 0 11:45:29 -4.032372 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.986578 0.0000\n", + "LBFGS: 0 11:45:29 -4.009416 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:29 -3.986578 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.997982 0.0000\n", - "LBFGS: 0 15:24:54 -4.009416 0.0000\n", + "LBFGS: 0 11:45:29 -3.997982 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.975204 0.0000\n", + "LBFGS: 0 11:45:29 -3.975204 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.941259 0.0000\n", + "LBFGS: 0 11:45:29 -3.963859 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.930004 0.0000\n", + "LBFGS: 0 11:45:29 -3.952544 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.963859 0.0000\n", + "LBFGS: 0 11:45:29 -3.941259 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.952544 0.0000\n", + "LBFGS: 0 11:45:29 -3.930004 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.918778 0.0000\n", + "LBFGS: 0 11:45:29 -3.907582 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.896416 0.0000\n", + "LBFGS: 0 11:45:29 -3.885280 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.907582 0.0000\n", + "LBFGS: 0 11:45:29 -3.918778 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:29 -3.896416 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.885280 0.0000\n", - "LBFGS: 0 15:24:54 -3.874174 0.0000\n", + "LBFGS: 0 11:45:29 -3.863097 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.852051 0.0000\n", + "LBFGS: 0 11:45:29 -3.874174 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.863097 0.0000\n", + "LBFGS: 0 11:45:29 -3.852051 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.830047 0.0000\n", + "LBFGS: 0 11:45:29 -3.841034 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.841034 0.0000\n", + "LBFGS: 0 11:45:29 -3.808164 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.819091 0.0000\n", + "LBFGS: 0 11:45:29 -3.819091 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.786400 0.0000\n", + "LBFGS: 0 11:45:29 -3.830047 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.797267 0.0000\n", + "LBFGS: 0 11:45:29 -3.797267 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.775562 0.0000\n", + "LBFGS: 0 11:45:29 -3.764755 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.808164 0.0000\n", + "LBFGS: 0 11:45:29 -3.775562 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.764755 0.0000\n", + "LBFGS: 0 11:45:29 -3.786400 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.721825 0.0000\n", + "LBFGS: 0 11:45:29 -3.753978 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:29 -3.743230 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.753978 0.0000\n", - "LBFGS: 0 15:24:54 -3.732513 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.743230 0.0000\n", + "LBFGS: 0 11:45:29 -3.732513 0.0000\n", + "LBFGS: 0 11:45:29 -3.721825 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.679373 0.0000\n", + "LBFGS: 0 11:45:29 -3.711167 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.689941 0.0000\n", + "LBFGS: 0 11:45:29 -3.700539 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.700539 0.0000\n", + "LBFGS: 0 11:45:29 -3.689941 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.711167 0.0000\n", + "LBFGS: 0 11:45:29 -3.679373 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.658325 0.0000\n", + "LBFGS: 0 11:45:29 -3.668834 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.668834 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:54 -3.647846 0.0000\n", + "LBFGS: 0 11:45:30 -3.637397 0.0000\n", + "LBFGS: 0 11:45:30 -3.658325 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.637397 0.0000\n", + "LBFGS: 0 11:45:30 -3.647846 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.626978 0.0000\n", + "LBFGS: 0 11:45:30 -3.626978 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.606228 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.595897 0.0000\n", + "LBFGS: 0 11:45:30 -3.606228 0.0000\n", + "LBFGS: 0 11:45:30 -3.616588 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.616588 0.0000\n", + "LBFGS: 0 11:45:30 -3.595897 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.585597 0.0000\n", + "LBFGS: 0 11:45:30 -3.585597 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.575325 0.0000\n", + "LBFGS: 0 11:45:30 -3.565084 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.565084 0.0000\n", + "LBFGS: 0 11:45:30 -3.575325 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.554872 0.0000\n", + "LBFGS: 0 11:45:30 -3.554872 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.534536 0.0000\n", + "LBFGS: 0 11:45:30 -3.544689 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.544689 0.0000\n", + "LBFGS: 0 11:45:30 -3.534536 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.514318 0.0000\n", + "LBFGS: 0 11:45:30 -3.524412 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.524412 0.0000\n", + "LBFGS: 0 11:45:30 -3.504253 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.504253 0.0000\n", + "LBFGS: 0 11:45:30 -3.494218 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.494218 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.474235 0.0000\n", + "LBFGS: 0 11:45:30 -3.484212 0.0000\n", + "LBFGS: 0 11:45:30 -3.514318 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.464287 0.0000\n", + "LBFGS: 0 11:45:30 -3.474235 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.484212 0.0000\n", + "LBFGS: 0 11:45:30 -3.464287 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.424788 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:30 -3.454369 0.0000\n", + "LBFGS: 0 11:45:30 -3.434619 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.454369 0.0000\n", - "LBFGS: 0 15:24:55 -3.444480 0.0000\n", + "LBFGS: 0 11:45:30 -3.444480 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.434619 0.0000\n", + "LBFGS: 0 11:45:30 -3.424788 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.405213 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.414986 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.376068 0.0000\n", + "LBFGS: 0 11:45:30 -3.405213 0.0000\n", + "LBFGS: 0 11:45:30 -3.414986 0.0000\n", + "LBFGS: 0 11:45:30 -3.395469 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.385754 0.0000\n", + "LBFGS: 0 11:45:30 -3.385754 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.395469 0.0000\n", + "LBFGS: 0 11:45:30 -3.366410 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.366410 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.356781 0.0000\n", + "LBFGS: 0 11:45:30 -3.376068 0.0000\n", + "LBFGS: 0 11:45:30 -3.356781 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.337610 0.0000\n", + "LBFGS: 0 11:45:30 -3.347181 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.328067 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.318553 0.0000\n", + "LBFGS: 0 11:45:30 -3.328067 0.0000\n", + "LBFGS: 0 11:45:30 -3.337610 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.347181 0.0000\n", + "LBFGS: 0 11:45:30 -3.318553 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.290181 0.0000\n", + "LBFGS: 0 11:45:30 -3.309067 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.309067 0.0000\n", + "LBFGS: 0 11:45:30 -3.299610 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.299610 0.0000\n", + "LBFGS: 0 11:45:30 -3.280781 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.280781 0.0000\n", + "LBFGS: 0 11:45:30 -3.271409 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.271409 0.0000\n", + "LBFGS: 0 11:45:30 -3.252749 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.262065 0.0000\n", + "LBFGS: 0 11:45:30 -3.290181 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.252749 0.0000\n", + "LBFGS: 0 11:45:30 -3.262065 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.243462 0.0000\n", + "LBFGS: 0 11:45:30 -3.243462 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.224971 0.0000\n", + "LBFGS: 0 11:45:30 -3.234202 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.234202 0.0000\n", + "LBFGS: 0 11:45:30 -3.215768 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.215768 0.0000\n", + "LBFGS: 0 11:45:30 -3.224971 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.206592 0.0000\n", + "LBFGS: 0 11:45:30 -3.206592 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.197445 0.0000\n", + "LBFGS: 0 11:45:30 -3.197445 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.188325 0.0000\n", + "LBFGS: 0 11:45:30 -3.188325 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.179233 0.0000\n", + "LBFGS: 0 11:45:30 -3.170169 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.170169 0.0000\n", + "LBFGS: 0 11:45:30 -3.179233 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.152123 0.0000\n", + "LBFGS: 0 11:45:30 -3.161132 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.161132 0.0000\n", + "LBFGS: 0 11:45:30 -3.152123 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.143141 0.0000\n", + "LBFGS: 0 11:45:30 -3.134187 0.0000\n", " Step Time Energy fmax\n", + "LBFGS: 0 11:45:31 -3.143141 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:55 -3.125260 0.0000\n", - "LBFGS: 0 15:24:55 -3.134187 0.0000\n", + "LBFGS: 0 11:45:31 -3.125260 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -3.116361 0.0000\n", + "LBFGS: 0 11:45:31 -3.107488 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -3.107488 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -3.089825 0.0000\n", + "LBFGS: 0 11:45:31 -3.116361 0.0000\n", + "LBFGS: 0 11:45:31 -3.098643 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -3.081034 0.0000\n", + "LBFGS: 0 11:45:31 -3.089825 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -3.098643 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -3.072271 0.0000\n", + "LBFGS: 0 11:45:31 -3.081034 0.0000\n", + "LBFGS: 0 11:45:31 -3.063534 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -3.046140 0.0000\n", + "LBFGS: 0 11:45:31 -3.072271 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -3.037483 0.0000\n", + "LBFGS: 0 11:45:31 -3.054823 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -3.054823 0.0000\n", + "LBFGS: 0 11:45:31 -3.037483 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -3.063534 0.0000\n", + "LBFGS: 0 11:45:31 -3.028853 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -3.028853 0.0000\n", + "LBFGS: 0 11:45:31 -3.046140 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -3.011673 0.0000\n", + "LBFGS: 0 11:45:31 -3.011673 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.994599 0.0000\n", + "LBFGS: 0 11:45:31 -3.020250 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -3.003123 0.0000\n", + "LBFGS: 0 11:45:31 -2.994599 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -3.020250 0.0000\n", + "LBFGS: 0 11:45:31 -3.003123 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.969185 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.977630 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.986101 0.0000\n", + "LBFGS: 0 11:45:31 -2.986101 0.0000\n", + "LBFGS: 0 11:45:31 -2.960766 0.0000\n", + "LBFGS: 0 11:45:31 -2.977630 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.960766 0.0000\n", + "LBFGS: 0 11:45:31 -2.969185 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.952373 0.0000\n", + "LBFGS: 0 11:45:31 -2.935665 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.944006 0.0000\n", + "LBFGS: 0 11:45:31 -2.944006 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.935665 0.0000\n", + "LBFGS: 0 11:45:31 -2.952373 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.927350 0.0000\n", + "LBFGS: 0 11:45:31 -2.927350 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.919060 0.0000\n", + "LBFGS: 0 11:45:31 -2.919060 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.910796 0.0000\n", + "LBFGS: 0 11:45:31 -2.910796 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.894346 0.0000\n", + "LBFGS: 0 11:45:31 -2.902558 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.886159 0.0000\n", + "LBFGS: 0 11:45:31 -2.894346 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.902558 0.0000\n", + "LBFGS: 0 11:45:31 -2.869862 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.877998 0.0000\n", + "LBFGS: 0 11:45:31 -2.877998 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.869862 0.0000\n", + "LBFGS: 0 11:45:31 -2.886159 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.861751 0.0000\n", + "LBFGS: 0 11:45:31 -2.845605 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.837570 0.0000\n", + "LBFGS: 0 11:45:31 -2.861751 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.845605 0.0000\n", + "LBFGS: 0 11:45:31 -2.853666 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.853666 0.0000\n", + "LBFGS: 0 11:45:31 -2.837570 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.829560 0.0000\n", + "LBFGS: 0 11:45:31 -2.821575 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.821575 0.0000\n", + "LBFGS: 0 11:45:31 -2.813615 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.805679 0.0000\n", + "LBFGS: 0 11:45:31 -2.805679 0.0000\n", " Step Time Energy fmax\n", - "LBFGS: 0 15:24:56 -2.813615 0.0000\n", - "CPU times: user 1.65 s, sys: 1.22 s, total: 2.87 s\n", - "Wall time: 9.77 s\n" + "LBFGS: 0 11:45:31 -2.829560 0.0000\n", + "CPU times: user 2.17 s, sys: 1.49 s, total: 3.66 s\n", + "Wall time: 9.24 s\n" ] } ], From 188ed6bbb1a5d6e10d39e1957c00ba8482d04397 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 25 Sep 2023 11:53:39 +0200 Subject: [PATCH 673/756] Update Shell notebook --- notebooks/tinybase/Shell.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebooks/tinybase/Shell.ipynb b/notebooks/tinybase/Shell.ipynb index 0a263ba9f..f00c59e26 100644 --- a/notebooks/tinybase/Shell.ipynb +++ b/notebooks/tinybase/Shell.ipynb @@ -39,7 +39,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "68874afc69984bf092e93365122bb323", + "model_id": "8ea7a18fdcfb4fdf82823e525cda825e", "version_major": 2, "version_minor": 0 }, From b6dfd35b107e27efa2f5d1c7db830059e85cdc4a Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 25 Sep 2023 12:03:56 +0200 Subject: [PATCH 674/756] Update Lammps notebook --- notebooks/tinybase/Lammps.ipynb | 80 ++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/notebooks/tinybase/Lammps.ipynb b/notebooks/tinybase/Lammps.ipynb index 064e0bc8a..510090b53 100644 --- a/notebooks/tinybase/Lammps.ipynb +++ b/notebooks/tinybase/Lammps.ipynb @@ -16,10 +16,30 @@ " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" ] }, + { + "data": { + "text/html": [ + "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "e70d37ad42d44537b74635b5097c08a8",
+       "model_id": "ec92c068940744a38d1e3978182d3b57",
        "version_major": 2,
        "version_minor": 0
       },
@@ -85,7 +105,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 156,
+   "execution_count": 5,
    "id": "64c9b3d4-e6e1-4e21-86fc-318940c4c8e8",
    "metadata": {
     "tags": []
@@ -306,7 +326,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 157,
+   "execution_count": 15,
    "id": "cc50f529-e3bc-4f36-af63-f496f6c1405f",
    "metadata": {
     "tags": []
@@ -318,7 +338,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 44,
+   "execution_count": 16,
    "id": "1fb9a556-d882-4b21-99fa-0fd30023d3f4",
    "metadata": {
     "tags": []
@@ -330,7 +350,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 63,
+   "execution_count": 17,
    "id": "cab52837-17eb-4f33-8182-cb5e917d02ec",
    "metadata": {
     "tags": []
@@ -342,7 +362,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 151,
+   "execution_count": 18,
    "id": "c95da0c4-fbdb-48d7-8086-f890f82c7725",
    "metadata": {
     "tags": []
@@ -356,7 +376,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 155,
+   "execution_count": 19,
    "id": "edb93bf2-9e70-4717-9929-101a3101599b",
    "metadata": {
     "tags": []
@@ -368,7 +388,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 153,
+   "execution_count": 20,
    "id": "9eab17d8-380a-4199-88b0-50910b5e5296",
    "metadata": {
     "tags": []
@@ -378,19 +398,23 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "CPU times: user 49.1 ms, sys: 102 ms, total: 151 ms\n",
-      "Wall time: 3.67 s\n"
+      "CPU times: user 105 ms, sys: 96 ms, total: 201 ms\n",
+      "Wall time: 5.49 s\n"
      ]
     }
    ],
    "source": [
     "%%time\n",
-    "ret, output = ProcessExecutor(max_processes=4).run(m)"
+    "exe = ProcessExecutor(max_processes=4).submit([m])\n",
+    "exe.run()\n",
+    "exe.wait()\n",
+    "ret = exe.status[0]\n",
+    "output = exe.output[0]"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 154,
+   "execution_count": 21,
    "id": "02659463-8be4-4f76-b67c-3986eff09dd0",
    "metadata": {
     "tags": []
@@ -400,19 +424,23 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "CPU times: user 5.04 s, sys: 11.6 s, total: 16.7 s\n",
-      "Wall time: 11.4 s\n"
+      "CPU times: user 6.28 s, sys: 9.44 s, total: 15.7 s\n",
+      "Wall time: 13.8 s\n"
      ]
     }
    ],
    "source": [
     "%%time\n",
-    "ret, output = BackgroundExecutor(max_threads=10).run(m)"
+    "exe = BackgroundExecutor(max_threads=5).submit([m])\n",
+    "exe.run()\n",
+    "exe.wait()\n",
+    "ret = exe.status[0]\n",
+    "output = exe.output[0]"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 93,
+   "execution_count": 22,
    "id": "44ffaf29-f35c-451f-a3fd-9f342035569b",
    "metadata": {
     "tags": []
@@ -420,7 +448,7 @@
    "outputs": [
     {
      "data": {
-      "image/png": "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nnvuOY/l8zbncr52796tRYsWKSYmRlFRUbrgggtUWFjo+bBe4mznrLGxUffdd58yMjIUFhamMWPG6KWXXvpGxwzoIlJbW6sZM2YoODhYq1at0q5du/TrX/9asbGxZkfzSk899ZRefvllvfDCC9q9e7d++ctf6le/+pV++9vfmh3NKzQ1NSknJ0cvvPDCKV//5S9/qWeeeUYvvPCCvvrqK6WkpOiSSy7puadSIDrTOWtubtaWLVv0s5/9TFu2bNGbb76pffv2adGiRSYk9Q5n+xnrtnz5cn355ZdKS0vzUDLvdLbzdfDgQc2cOVOjR4/Wxx9/rPz8fP3sZz9TaGioh5N6j7Ods8WLF+v999/X66+/rt27d2vx4sX6/ve/r7fffrv/BzUC2AMPPGDMnDnT7Bg+44orrjDuuuuuXs9de+21xm233WZSIu8lyXjrrbd6vna5XEZKSorx5JNP9jzX0tJixMTEGC+//LIJCb3P18/ZqWzcuNGQZBw5csQzobzY6c5XcXGxkZ6ebuzYscMYPHiw8eyzz3o8mzc61fm66aab+PfrDE51zsaNG2c89thjvZ6bPHmy8fDDD/f7OAE9IrJixQpNnTpVN9xwg5KSkpSbm6s//OEPZsfyWjNnztS//vUv7du3T5KUn5+vTz/9VJdffrnJybzf4cOHVV5ervnz5/c8Z7fbNWfOHG3YsMHEZL6lvr5eFouFUcvTcLlcuv3227VkyRKNGzfO7DhezeVy6d1339XIkSO1YMECJSUladq0aWe83IXOz4EVK1aopKREhmFo7dq12rdvnxYsWNDv7xnQReTQoUN66aWXNGLECH3wwQe655579IMf/EB//etfzY7mlR544AHdcsstGj16tIKDg5Wbm6v7779ft9xyi9nRvF55ebkkKTk5udfzycnJPa/hzFpaWvTggw/q1ltv5SZlp/HUU08pKChIP/jBD8yO4vUqKyvV2NioJ598UpdeeqlWr16ta665Rtdee63WrVtndjyv9fzzz2vs2LHKyMhQSEiILr30Ur344ouaOXNmv7+nV999191cLpemTp2qJ554QpKUm5urnTt36qWXXtIdd9xhcjrv88Ybb+j111/X3/72N40bN055eXm6//77lZaWpu985ztmx/MJFoul19eGYZz0HE7W1tamm2++WS6XSy+++KLZcbzS5s2b9Zvf/EZbtmzhZ+ocdE+yv+qqq7R48WJJ0qRJk7Rhwwa9/PLLmjNnjpnxvNbzzz+vL774QitWrNDgwYP1ySef6N5771Vqaqouvvjifn3PgC4iqampGjt2bK/nxowZo2XLlpmUyLstWbJEDz74oG6++WZJ0oQJE3TkyBEtXbqUInIWKSkpkjpHRlJTU3uer6ysPGmUBL21tbXpxhtv1OHDh/XRRx8xGnIa69evV2VlpbKysnqe6+jo0H/8x3/oueeeU0FBgXnhvFBCQoKCgoJO+Rnw6aefmpTKux07dkw/+clP9NZbb+mKK66QJE2cOFF5eXl6+umn+11EAvrSzIwZM7R3795ez+3bt0+DBw82KZF3a25ultXa+0fGZrOxfPccZGdnKyUlRWvWrOl5rrW1VevWrdP06dNNTObdukvI/v379eGHHyo+Pt7sSF7r9ttv17Zt25SXl9fzSEtL05IlS/TBBx+YHc/rhISE6LzzzuMzoA/a2trU1tY24J8DAT0isnjxYk2fPl1PPPGEbrzxRm3cuFGvvPKKXnnlFbOjeaWFCxfq8ccfV1ZWlsaNG6etW7fqmWee0V133WV2NK/Q2NioAwcO9Hx9+PBh5eXlKS4uTllZWbr//vv1xBNPaMSIERoxYoSeeOIJhYeH69ZbbzUxtbnOdM7S0tJ0/fXXa8uWLXrnnXfU0dHRM58mLi5OISEhZsU2zdl+xr5e1IKDg5WSkqJRo0Z5OqpXONv5WrJkiW666SbNnj1b8+bN0/vvv6+VK1fq448/Ni+0yc52zubMmaMlS5YoLCxMgwcP1rp16/TXv/5VzzzzTP8P2u/1Nn5i5cqVxvjx4w273W6MHj3aeOWVV8yO5LUcDofxwx/+0MjKyjJCQ0ONoUOHGj/96U8Np9NpdjSvsHbtWkPSSY/vfOc7hmF0LuF95JFHjJSUFMNutxuzZ882tm/fbm5ok53pnB0+fPiUr0ky1q5da3Z0U5ztZ+zrAn357rmcrz/96U/G8OHDjdDQUCMnJ8dYvny5eYG9wNnOWVlZmXHnnXcaaWlpRmhoqDFq1Cjj17/+teFyufp9TIthGEb/awwAAED/BfQcEQAAYC6KCAAAMA1FBAAAmIYiAgAATEMRAQAApqGIAAAA01BEAACAaSgiAADANBQRAABgGooIAAAwDUUEAACYhiICAABM8/8DV4nmheaYlRMAAAAASUVORK5CYII=\n",
+      "image/png": "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Umtrq372s59p2LBhCg8P1/Dhw/XQQw/J4/GYjuYT1q5dq/nz58vlcslms2n58uVdXrcsSw8++KBcLpfCw8M1e/Zsbdu2zUxYH3Gyc9bS0qL77rtPEydO1KBBg+RyuXTLLbeouLjYXGDDTvU7drTvfOc7stlseuKJJ/otn6/pzvnasWOHLr/8cjmdTkVGRurss89Wfn5+/4f1Eac6Z7W1tfrud7+rlJQUhYeHa+zYsVq6dOlpHTOgi8jhw4c1c+ZMhYSEaMWKFdq+fbt+85vfKDo62nQ0n/SrX/1KTz/9tJ566int2LFDjz76qH7961/r97//veloPqGurk5ZWVl66qmnjvv6o48+qscff1xPPfWUPvvsMyUmJuqiiy7qvKdSIDrZOauvr1d2drbuv/9+ZWdn65VXXtHu3bt1+eWXG0jqG071O9Zh+fLl+uSTT+RyufopmW861fnKzc3VrFmzNGbMGK1evVqbN2/W/fffr7CwsH5O6jtOdc4WLFigt99+Wy+++KJ27NihBQsW6Hvf+55ee+213h/UCmD33XefNWvWLNMx/Mall15q3X777V2eu/LKK62bbrrJUCLfJcl69dVXO7/3eDxWYmKi9cgjj3Q+19jYaDmdTuvpp582kND3fPmcHc+nn35qSbIOHDjQP6F82InOV2FhoZWcnGxt3brVSk9Pt37729/2ezZfdLzzdd111/Hvr5M43jkbP3689dBDD3V5bsqUKdbPfvazXh8noK+IvP7665o2bZquueYaxcfHa/LkyfrjH/9oOpbPmjVrlt577z3t3r1bkrR582atW7dOl1xyieFkvi8vL0+lpaWaM2dO53MOh0Pnn3++1q9fbzCZf6murpbNZuOq5Ql4PB7dfPPNWrhwocaPH286jk/zeDx66623NHr0aF188cWKj4/X9OnTTzrchfbPgddff11FRUWyLEurVq3S7t27dfHFF/f6ZwZ0Edm3b5+WLl2qUaNG6Z133tGdd96p73//+3rhhRdMR/NJ9913n2644QaNGTNGISEhmjx5su655x7dcMMNpqP5vNLSUklSQkJCl+cTEhI6X8PJNTY26sc//rFuvPFGblJ2Ar/61a8UHBys73//+6aj+LyysjLV1tbqkUce0dy5c7Vy5Up9/etf15VXXqk1a9aYjueznnzySY0bN04pKSkKDQ3V3LlztWTJEs2aNavXP9On777rbR6PR9OmTdPDDz8sSZo8ebK2bdumpUuX6pZbbjGczvf84x//0Isvvqj/+7//0/jx45WTk6N77rlHLpdLt956q+l4fsFms3X53rKsY57DsVpaWnT99dfL4/FoyZIlpuP4pI0bN+p3v/udsrOz+Z3qho5J9ldccYUWLFggSZo0aZLWr1+vp59+Wueff77JeD7rySef1Mcff6zXX39d6enpWrt2re6++24lJSXpwgsv7NXPDOgikpSUpHHjxnV5buzYsVq2bJmhRL5t4cKF+vGPf6zrr79ekjRx4kQdOHBAixcvpoicQmJioqT2KyNJSUmdz5eVlR1zlQRdtbS06Nprr1VeXp7ef/99roacwAcffKCysjKlpaV1PtfW1qYf/vCHeuKJJ7R//35z4XxQXFycgoODj/sZsG7dOkOpfFtDQ4N+8pOf6NVXX9Wll14qScrMzFROTo4ee+yxXheRgB6amTlzpnbt2tXlud27dys9Pd1QIt9WX18vu73rr0xQUBDLd7th2LBhSkxM1Lvvvtv5XHNzs9asWaMZM2YYTObbOkrInj179O9//1uxsbGmI/msm2++WVu2bFFOTk7nw+VyaeHChXrnnXdMx/M5oaGhOvPMM/kM6IGWlha1tLT0+edAQF8RWbBggWbMmKGHH35Y1157rT799FM988wzeuaZZ0xH80nz58/XL3/5S6WlpWn8+PHatGmTHn/8cd1+++2mo/mE2tpa7d27t/P7vLw85eTkKCYmRmlpabrnnnv08MMPa9SoURo1apQefvhhRURE6MYbbzSY2qyTnTOXy6Wrr75a2dnZevPNN9XW1tY5nyYmJkahoaGmYhtzqt+xLxe1kJAQJSYm6owzzujvqD7hVOdr4cKFuu6663Teeefpggsu0Ntvv6033nhDq1evNhfasFOds/PPP18LFy5UeHi40tPTtWbNGr3wwgt6/PHHe3/QXq+3GSDeeOMNa8KECZbD4bDGjBljPfPMM6Yj+Sy322394Ac/sNLS0qywsDBr+PDh1k9/+lOrqanJdDSfsGrVKkvSMY9bb73Vsqz2JbwPPPCAlZiYaDkcDuu8886zPv/8c7OhDTvZOcvLyzvua5KsVatWmY5uxKl+x74s0Jfvdud8/elPf7JGjhxphYWFWVlZWdby5cvNBfYBpzpnJSUl1m233Wa5XC4rLCzMOuOMM6zf/OY3lsfj6fUxbZZlWb2vMQAAAL0X0HNEAACAWRQRAABgDEUEAAAYQxEBAADGUEQAAIAxFBEAAGAMRQQAABhDEQEAAMZQRAAAgDEUEQAAYAxFBAAAGEMRAQAAxvx/kPkRtNmxslQAAAAASUVORK5CYII=\n",
       "text/plain": [
        "
" ] @@ -435,17 +463,17 @@ }, { "cell_type": "code", - "execution_count": 94, + "execution_count": 23, "id": "c6d3f82b-7acb-47f1-ab0c-c0380382e7a7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "20216.45910790539" + "93594.71843533409" ] }, - "execution_count": 94, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -456,17 +484,17 @@ }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 24, "id": "f8911ebd-bcc6-4696-89f4-498c60aad544", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "20216.459107905404" + "93594.7184353341" ] }, - "execution_count": 95, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -477,17 +505,17 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 25, "id": "620c128d-fcd4-4842-8f49-cd6f7052b4c3", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Atoms(symbols='Fe1728', pbc=True, cell=[[-17.16122821739448, 17.16122821739448, 17.16122821739448], [17.16122821739448, -17.16122821739448, 17.16122821739448], [17.16122821739448, 17.16122821739448, -17.16122821739448]])" + "Atoms(symbols='Fe8000', pbc=True, cell=[[-28.602047063948746, 28.602047063948746, 28.602047063948746], [28.602047063948746, -28.602047063948746, 28.602047063948746], [28.602047063948746, 28.602047063948746, -28.602047063948746]])" ] }, - "execution_count": 96, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } From fe7e204f4833ac9a7983e2363c1fc67d9522aca4 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 25 Sep 2023 12:14:01 +0200 Subject: [PATCH 675/756] Fix small typo in cpu default --- pyiron_contrib/tinybase/creator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/tinybase/creator.py b/pyiron_contrib/tinybase/creator.py index 8e7bde530..d7e4836e3 100644 --- a/pyiron_contrib/tinybase/creator.py +++ b/pyiron_contrib/tinybase/creator.py @@ -22,7 +22,7 @@ try: from os import sched_getaffinity - cpu_count = lambda: len(sched_getaffinity) + cpu_count = lambda: len(sched_getaffinity(0)) except ImportError: from multiprocessing import cpu_count From aa819c259982336315d2767f31d99e06e21d6c58 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 25 Sep 2023 12:14:08 +0200 Subject: [PATCH 676/756] Update TinyJob notebook --- notebooks/tinybase/TinyJob.ipynb | 365 +++++-------------------------- 1 file changed, 54 insertions(+), 311 deletions(-) diff --git a/notebooks/tinybase/TinyJob.ipynb b/notebooks/tinybase/TinyJob.ipynb index e216c8ea3..3240f99f5 100644 --- a/notebooks/tinybase/TinyJob.ipynb +++ b/notebooks/tinybase/TinyJob.ipynb @@ -115,7 +115,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "8861a4841639439b85dad196355d30fb", + "model_id": "b6954634635d434a8a07606fcd87df98", "version_major": 2, "version_minor": 0 }, @@ -223,7 +223,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "f2e3ffa8f02e41928c74071f3793f3ab", + "model_id": "1c9f4a31d9c4468d83f600e6601fd152", "version_major": 2, "version_minor": 0 }, @@ -295,18 +295,6 @@ { "cell_type": "code", "execution_count": 15, - "id": "f9c60e94-ac8f-407f-b0c4-657c91581637", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "j.remove()" - ] - }, - { - "cell_type": "code", - "execution_count": 16, "id": "e7494fee-d565-45e3-a819-c77ab0d2c7f6", "metadata": { "scrolled": true, @@ -314,53 +302,10 @@ }, "outputs": [ { - "name": "stdout", + "name": "stderr", "output_type": "stream", "text": [ - " Step Time Energy fmax\n", - "LBFGS: 0 13:14:53 11.288146 189.5231\n", - "LBFGS: 1 13:14:53 1.168671 43.6957\n", - "LBFGS: 2 13:14:53 0.860403 38.6924\n", - "LBFGS: 3 13:14:53 0.362400 30.3554\n", - "LBFGS: 4 13:14:53 0.004806 24.0865\n", - "LBFGS: 5 13:14:53 -0.267437 19.0615\n", - "LBFGS: 6 13:14:53 -0.471646 15.0628\n", - "LBFGS: 7 13:14:53 -0.623506 11.8810\n", - "LBFGS: 8 13:14:53 -0.735237 9.3518\n", - "LBFGS: 9 13:14:53 -0.816458 7.3435\n", - "LBFGS: 10 13:14:53 -0.874705 5.7512\n", - "LBFGS: 11 13:14:53 -0.915849 4.4909\n", - "LBFGS: 12 13:14:53 -0.944435 3.4955\n", - "LBFGS: 13 13:14:53 -0.963943 2.7113\n", - "LBFGS: 14 13:14:53 -0.977006 2.0956\n", - "LBFGS: 15 13:14:53 -0.985585 1.6137\n", - "LBFGS: 16 13:14:53 -0.991109 1.2382\n", - "LBFGS: 17 13:14:53 -0.994598 0.9468\n", - "LBFGS: 18 13:14:53 -0.996763 0.7216\n", - "LBFGS: 19 13:14:53 -0.998083 0.5484\n", - "LBFGS: 20 13:14:53 -0.998876 0.4157\n", - "LBFGS: 21 13:14:53 -0.999347 0.3144\n", - "LBFGS: 22 13:14:53 -0.999623 0.2374\n", - "LBFGS: 23 13:14:53 -0.999784 0.1790\n", - "LBFGS: 24 13:14:53 -0.999877 0.1348\n", - "LBFGS: 25 13:14:53 -0.999930 0.1014\n", - "LBFGS: 26 13:14:53 -0.999960 0.0762\n", - "LBFGS: 27 13:14:53 -0.999977 0.0573\n", - "LBFGS: 28 13:14:53 -0.999987 0.0430\n", - "LBFGS: 29 13:14:53 -0.999993 0.0323\n", - "LBFGS: 30 13:14:53 -0.999996 0.0242\n", - "LBFGS: 31 13:14:53 -0.999998 0.0182\n", - "LBFGS: 32 13:14:53 -0.999999 0.0136\n", - "LBFGS: 33 13:14:53 -0.999999 0.0102\n", - "LBFGS: 34 13:14:53 -1.000000 0.0077\n", - "LBFGS: 35 13:14:53 -1.000000 0.0058\n", - "LBFGS: 36 13:14:53 -1.000000 0.0043\n", - "LBFGS: 37 13:14:53 -1.000000 0.0032\n", - "LBFGS: 38 13:14:53 -1.000000 0.0024\n", - "LBFGS: 39 13:14:53 -1.000000 0.0018\n", - "LBFGS: 40 13:14:53 -1.000000 0.0014\n", - "LBFGS: 41 13:14:53 -1.000000 0.0010\n", - "LBFGS: 42 13:14:53 -1.000000 0.0008\n" + "INFO:root:Job already finished!\n" ] } ], @@ -373,7 +318,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "id": "7c16a615-0913-4880-9694-2c125285babc", "metadata": { "tags": [] @@ -426,27 +371,27 @@ " \n", " \n", " 1\n", - " 3\n", + " 2\n", " pyiron\n", - " murn\n", - " 3\n", + " min\n", + " 2\n", " 1\n", - " 4\n", + " 2\n", " /home/poul/pyiron/contrib/notebooks/tinybase/t...\n", " finished\n", - " MurnaghanTask\n", + " AseMinimizeTask\n", " \n", " \n", " 2\n", - " 4\n", + " 3\n", " pyiron\n", - " min\n", - " 2\n", + " murn\n", + " 3\n", " 1\n", - " 5\n", + " 3\n", " /home/poul/pyiron/contrib/notebooks/tinybase/t...\n", - " collect\n", - " AseMinimizeTask\n", + " finished\n", + " MurnaghanTask\n", " \n", " \n", "\n", @@ -455,21 +400,21 @@ "text/plain": [ " id username name jobtype_id project_id status_id \\\n", "0 1 pyiron md 1 1 1 \n", - "1 3 pyiron murn 3 1 4 \n", - "2 4 pyiron min 2 1 5 \n", + "1 2 pyiron min 2 1 2 \n", + "2 3 pyiron murn 3 1 3 \n", "\n", " location status \\\n", "0 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", "1 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", - "2 /home/poul/pyiron/contrib/notebooks/tinybase/t... collect \n", + "2 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", "\n", " type \n", "0 AseMDTask \n", - "1 MurnaghanTask \n", - "2 AseMinimizeTask " + "1 AseMinimizeTask \n", + "2 MurnaghanTask " ] }, - "execution_count": 17, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -480,7 +425,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "id": "59ea5510-b6ce-4317-90c3-4af77db3d59a", "metadata": { "tags": [] @@ -503,7 +448,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "id": "3fb09d42-f800-46ee-9919-83180863e1ee", "metadata": { "tags": [] @@ -512,7 +457,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e1d35958a5b44caba8dc6719f6493844", + "model_id": "c775879f87fd4395b005043060a9bb4f", "version_major": 2, "version_minor": 0 }, @@ -538,7 +483,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "id": "d32508b9-2854-4076-9109-08ede1b52dc2", "metadata": { "tags": [] @@ -555,7 +500,7 @@ " -0.999999995888409]" ] }, - "execution_count": 20, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -566,7 +511,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "id": "db691097-72c6-45a4-89b1-6ec16018c8b8", "metadata": { "tags": [] @@ -589,14 +534,14 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "id": "23ce6822-b38b-41f3-9269-109dbb152ecf", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "38fea66abec4408c8ba29585d7bb2419", + "model_id": "f6d29a76f660478caba449c02e996830", "version_major": 2, "version_minor": 0 }, @@ -622,7 +567,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "id": "654ce992-b73f-42e3-a32e-2e0dafa7c952", "metadata": { "tags": [] @@ -634,7 +579,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "id": "253237f0-b338-470c-bc54-3c7400a757b7", "metadata": {}, "outputs": [], @@ -645,7 +590,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 24, "id": "c801093b-499e-48a7-8444-77602ed88a96", "metadata": {}, "outputs": [], @@ -655,7 +600,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 25, "id": "1e30b36e-11e6-47d1-836e-cffea7b73cdd", "metadata": {}, "outputs": [], @@ -665,7 +610,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 26, "id": "18b5305a-8950-44af-bc2e-c9734b059713", "metadata": {}, "outputs": [ @@ -680,8 +625,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 1.88 ms, sys: 845 µs, total: 2.73 ms\n", - "Wall time: 2.6 ms\n" + "CPU times: user 5.73 ms, sys: 0 ns, total: 5.73 ms\n", + "Wall time: 5.18 ms\n" ] } ], @@ -693,7 +638,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 27, "id": "836bb2ec-4295-4a3c-b976-7a35d04aad36", "metadata": {}, "outputs": [ @@ -724,7 +669,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 28, "id": "79a2bb61-0a5e-4a3a-b195-46d027738a0e", "metadata": {}, "outputs": [], @@ -734,7 +679,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 29, "id": "b4e6e0c9-a2c6-40ab-884e-a46e16c37b04", "metadata": {}, "outputs": [ @@ -772,7 +717,7 @@ "Index: []" ] }, - "execution_count": 30, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -783,7 +728,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 30, "id": "cef7c46f-551f-401e-96c2-214628e23967", "metadata": {}, "outputs": [ @@ -811,7 +756,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 31, "id": "e8a7ee30-d7a6-46fc-bf98-1b52c981470f", "metadata": {}, "outputs": [ @@ -872,7 +817,7 @@ "0 MurnaghanTask " ] }, - "execution_count": 32, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -883,7 +828,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 32, "id": "30871447-3e20-46ee-a58e-853d4f4cb5d9", "metadata": {}, "outputs": [ @@ -909,7 +854,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 33, "id": "e63d43c1-341f-4ec0-b0cf-9cd5ead51926", "metadata": {}, "outputs": [ @@ -967,7 +912,7 @@ " 1\n", " 2\n", " /\n", - " finished\n", + " collect\n", " AseMDTask\n", " \n", " \n", @@ -977,14 +922,14 @@ "text/plain": [ " id username name jobtype_id project_id status_id location status \\\n", "0 1 pyiron murn 1 1 1 / finished \n", - "1 2 pyiron md 2 1 2 / finished \n", + "1 2 pyiron md 2 1 2 / collect \n", "\n", " type \n", "0 MurnaghanTask \n", "1 AseMDTask " ] }, - "execution_count": 34, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -1003,7 +948,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 34, "id": "80da39e2-76d1-42e6-977f-241d2683188d", "metadata": {}, "outputs": [ @@ -1017,10 +962,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 35, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -1039,7 +984,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 35, "id": "a567f96a-cbb3-4d2d-95d1-6dcecee7ddb8", "metadata": {}, "outputs": [ @@ -1128,7 +1073,7 @@ "2 AseMDTask " ] }, - "execution_count": 36, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -1136,208 +1081,6 @@ "source": [ "pr.job_table()" ] - }, - { - "cell_type": "markdown", - "id": "d8a98b01-9999-428e-9211-c1df032e7ba3", - "metadata": {}, - "source": [ - "# Database Tests" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "a6fefefb-b09c-4cee-b632-29f88bccfeee", - "metadata": {}, - "outputs": [], - "source": [ - "db = j.project.database" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "3419f273-b94b-48cf-9373-7441baec2353", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "DatabaseEntry(name='murn', username='pyiron', project='/', status='finished', jobtype='MurnaghanTask')" - ] - }, - "execution_count": 38, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "db.get_item(1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a7521b67-12cd-4835-ac1d-fc42f80c01bb", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "cf75b2e8-ec15-4846-bda4-9b6661e8b5fa", - "metadata": {}, - "outputs": [], - "source": [ - "eng = db.engine" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "1b23728b-1050-47a4-bda0-bd5967ceed2e", - "metadata": {}, - "outputs": [], - "source": [ - "from sqlalchemy.orm import Session" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "4cd73be9-2c6d-4501-8a6c-0afc6083a4b9", - "metadata": {}, - "outputs": [], - "source": [ - "s = Session(eng)" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "e9d9a3e2-5c86-46b4-b6d0-bec3a9f448b0", - "metadata": {}, - "outputs": [], - "source": [ - "from pyiron_contrib.tinybase.database import Job, Project as DProject, JobStatus, JobType" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "id": "99059ff6-18bd-40b9-85fc-d76e1828f7ac", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 43, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "s.query(Job.id).where(Job.name == \"min\", ).all()" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "id": "cb4bb9fe-13bf-4736-aa68-662b980d4f00", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[(2, 'finished')]" - ] - }, - "execution_count": 44, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "s.query(JobStatus.__table__).select_from(Job).where(Job.id == 2, Job.status_id == JobStatus.id).all()" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "4641b048-b7c7-46a2-b67c-835c08916cb0", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[(1, 'finished'), (2, 'finished'), (3, 'finished')]" - ] - }, - "execution_count": 45, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "s.query(JobStatus.__table__).all()" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "94364e54-b980-48cc-995b-daf977437b1b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[(1, '/'), (2, '/foo')]" - ] - }, - "execution_count": 46, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "s.query(DProject.__table__).all()" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "f32c43d5-19c8-4505-bf5c-9ac32bca51d0", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "[(1, 'MurnaghanTask'), (2, 'AseMDTask')]" - ] - }, - "execution_count": 47, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "s.query(JobType.__table__).all()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2aa7afbb-add8-4bee-8151-5ffa8cc3a0b4", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From d7bae76095c28c3f0463d03ecbc6f54f02fe3ddf Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 25 Sep 2023 12:29:31 +0200 Subject: [PATCH 677/756] Do not use nglview in notebooks It is not available in the testing env --- notebooks/tinybase/ASE.ipynb | 1204 ++---------------------------- notebooks/tinybase/TinyJob.ipynb | 243 +++--- 2 files changed, 164 insertions(+), 1283 deletions(-) diff --git a/notebooks/tinybase/ASE.ipynb b/notebooks/tinybase/ASE.ipynb index 76f4954ce..5659687a2 100644 --- a/notebooks/tinybase/ASE.ipynb +++ b/notebooks/tinybase/ASE.ipynb @@ -83,7 +83,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e525c8251f7d46c9b095738445c84ee3", + "model_id": "d27ff0773fea4e2eb6c66ecab365bffb", "version_major": 2, "version_minor": 0 }, @@ -190,7 +190,7 @@ "data": { "text/plain": [ "(ReturnStatus(Code.DONE, None),\n", - " )" + " )" ] }, "execution_count": 11, @@ -310,7 +310,7 @@ "data": { "text/plain": [ "{'status': [ReturnStatus(Code.DONE, None)],\n", - " 'output': []}" + " 'output': []}" ] }, "execution_count": 18, @@ -356,7 +356,7 @@ { "data": { "text/plain": [ - "13.667204819008475" + "11.99578764900798" ] }, "execution_count": 20, @@ -379,7 +379,7 @@ { "data": { "text/plain": [ - "2.021901309490204e-05" + "2.5072979042306542e-05" ] }, "execution_count": 21, @@ -404,26 +404,26 @@ "text/plain": [ "[-303.20813267693006,\n", " -303.20813267693006,\n", - " -298.8085894149179,\n", - " -299.3766972106697,\n", - " -301.36059005048594,\n", - " -300.48700803508484,\n", - " -299.4514988001343,\n", - " -300.5461110484806,\n", - " -300.1525587459357,\n", - " -299.5459227092857,\n", - " -299.8837655656467,\n", - " -299.7924269457935,\n", - " -299.9374215650488,\n", - " -300.3039189066386,\n", - " -299.51393477344607,\n", - " -300.77468931627885,\n", - " -299.9168922163957,\n", - " -299.7884657059498,\n", - " -300.3987874842712,\n", - " -299.86105248449877,\n", - " -299.86928323862315,\n", - " -299.641272075462]" + " -299.50793416220245,\n", + " -300.5497430006916,\n", + " -300.2227526899692,\n", + " -299.9803776735141,\n", + " -300.4174233564278,\n", + " -300.47761929956704,\n", + " -300.9284829694475,\n", + " -300.7290011651538,\n", + " -300.13461682002395,\n", + " -299.6448595164918,\n", + " -300.7999265006333,\n", + " -300.61980282686807,\n", + " -299.87975459727386,\n", + " -300.00429868718913,\n", + " -300.03868103245054,\n", + " -300.10316958162093,\n", + " -300.94069501802824,\n", + " -300.33250647975046,\n", + " -300.6353629828311,\n", + " -300.6098904517182]" ] }, "execution_count": 22, @@ -445,7 +445,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -465,24 +465,10 @@ "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "1058c675adf54d2f92c496f3d9b6fdd9", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "NGLWidget(max_frame=21)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "exe.output[0].animate_structures()" + "# Testing env doesn't have nglview\n", + "# exe.output[0].animate_structures()" ] }, { @@ -553,7 +539,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "ae1b14aef8564268a3172f8fe0cfa9dc", + "model_id": "b5a8f7fe55124036a47dc318938cbc7d", "version_major": 2, "version_minor": 0 }, @@ -566,7 +552,8 @@ } ], "source": [ - "ase_to_pyiron(mi.input.structure).plot3d()" + "# Testing env doesn't have nglview\n", + "# ase_to_pyiron(mi.input.structure).plot3d()" ] }, { @@ -620,10 +607,10 @@ "output_type": "stream", "text": [ " Step Time Energy fmax\n", - "GPMin: 0 11:44:18 11.122159 187.2462\n", - "GPMin: 1 11:44:18 -0.278268 1.5338\n", - "GPMin: 2 11:44:18 -0.996055 0.8010\n", - "GPMin: 3 11:44:18 -0.000000 0.0000\n" + "GPMin: 0 12:25:12 11.122159 187.2462\n", + "GPMin: 1 12:25:12 -0.278268 1.5338\n", + "GPMin: 2 12:25:12 -0.996055 0.8010\n", + "GPMin: 3 12:25:12 -0.000000 0.0000\n" ] } ], @@ -711,7 +698,7 @@ { "data": { "text/plain": [ - "0.04294431797461584" + "0.04363919500610791" ] }, "execution_count": 37, @@ -734,7 +721,7 @@ { "data": { "text/plain": [ - "1.3895012671127915e-05" + "1.127301948145032e-05" ] }, "execution_count": 38, @@ -807,7 +794,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "ecb4fc5738344545870545fc613b1e21", + "model_id": "c3200a77ee174a69be92ace9fb8e5993", "version_major": 2, "version_minor": 0 }, @@ -878,7 +865,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 44, @@ -1117,7 +1104,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 56, @@ -1151,8 +1138,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 353 ms, sys: 262 ms, total: 615 ms\n", - "Wall time: 3.09 s\n" + "CPU times: user 490 ms, sys: 280 ms, total: 770 ms\n", + "Wall time: 3.56 s\n" ] } ], @@ -1177,7 +1164,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": null, "id": "4c55e74b-5e7a-4c46-9eb1-1acf5dff2322", "metadata": { "tags": [] @@ -1192,14 +1179,6 @@ "/home/poul/pyiron/contrib/pyiron_contrib/__init__.py:9: UserWarning: pyiron module not found, importing Project from pyiron_base\n", " warnings.warn(\"pyiron module not found, importing Project from pyiron_base\")\n" ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 4.76 s, sys: 2.6 s, total: 7.36 s\n", - "Wall time: 58.6 s\n" - ] } ], "source": [ @@ -1211,46 +1190,24 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": null, "id": "7fe57ead-1175-4a29-a398-9e0adf52973a", "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "text/plain": [ - "[ReturnStatus(Code.DONE, None)]" - ] - }, - "execution_count": 61, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "exe.status" ] }, { "cell_type": "code", - "execution_count": 62, + "execution_count": null, "id": "0d999302-6f1d-48b6-865e-4af578d35cf7", "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "image/png": "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lZXJZrNp3Lhx+s1vfhOlauPLUM/ts88+q8mTJystLU1FRUX6p3/6JzU1NUWp2vjx1ltvaf78+SouLpbFYtFLL710xtesX79eU6ZMkd1u19ixY/Uf//EfkS80Dg313P7xj3/UnDlzlJeXp8zMTE2fPl2vvfZadIqNM6H8ve0Tzu8ygkgIVq1apYULF+r+++/Xtm3bNHPmTM2bN08HDhw47evcbre++c1v6qqrropSpfEnlHP75S9/WX/5y1/05JNP6uOPP9Zzzz2niRMnRrHq+DDUc/v222/rm9/8pm655Rbt2LFDL7zwgjZv3qxbb701ypXHvra2Nk2ePFnLly8f1PHV1dW65pprNHPmTG3btk333XefvvOd7+gPf/hDhCuNP0M9t2+99ZbmzJmj1atXa+vWrZo9e7bmz5+vbdu2RbjS+DPUc9sn7N9lBobskksuMW6//fZ++yZOnGjce++9p33djTfeaPzwhz80HnjgAWPy5MkRrDB+DfXcvvrqq4bL5TKampqiUV5cG+q5/elPf2qMHTu2376f//znRklJScRqHAkkGS+++OJpj1m8eLExceLEfvtuu+0249JLL41gZfFvMOf2VM455xzjwQcfDH9BI8hQzm24v8toERmizs5Obd26VXPnzu23f+7cudq4ceOAr3vqqae0Z88ePfDAA5EuMW6Fcm5ffvllTZ06VY888ohGjx6tCRMm6Hvf+568Xm80So4boZzbGTNm6ODBg1q9erUMw9Dhw4f1+9//Xp/97GejUfKI9u677570Z/GZz3xGW7ZsUVdXl0lVjUx+v18tLS3Kzs42u5QRIRLfZTF90btY1NjYqJ6eHhUUFPTbX1BQoPr6+lO+pqqqSvfee682bNig5GRO+UBCObd79+7V22+/LbvdrhdffFGNjY264447dPToUcaJnCCUcztjxgw9++yzuvHGG9XR0aHu7m59/vOf1y9+8YtolDyi1dfXn/LPoru7W42NjSoqKjKpspHn0UcfVVtbm7785S+bXUrci9R3GS0iIbJYLP0eG4Zx0j5J6unp0U033aQHH3xQEyZMiFZ5cW2w51YK/LZjsVj07LPP6pJLLtE111yjxx57TE8//TStIqcwlHP74Ycf6jvf+Y5+9KMfaevWrVqzZo2qq6t1++23R6PUEe9Ufxan2o/QPffcc1q6dKlWrVql/Px8s8uJa5H8LuPX8yHKzc1VUlLSSb9FNjQ0nPQbjiS1tLRoy5Yt2rZtm+666y5JgS9PwzCUnJys119/XVdeeWVUao91Qz23klRUVKTRo0f3u9z02WefLcMwdPDgQVVWVka05ngRyrldtmyZPvWpT+n73/++JOn8889Xenq6Zs6cqZ/85Cf81j4MhYWFp/yzSE5OVk5OjklVjSyrVq3SLbfcohdeeEGf/vSnzS4n7kXyu4wWkSFKTU3VlClTtHbt2n77165dqxkzZpx0fGZmprZv3673338/uN1+++0666yz9P7772vatGnRKj3mDfXcStKnPvUp1dbWqrW1Nbhv165dslqtKikpiWi98SSUc9ve3i6rtf9/EUlJSZKO//aO0EyfPv2kP4vXX39dU6dOVUpKiklVjRzPPfecbr75Zq1cuZIxTWES0e+yYQ93TUDPP/+8kZKSYjz55JPGhx9+aCxcuNBIT0839u3bZxiGYdx7773GN77xjQFfz6yZgQ313La0tBglJSXGDTfcYOzYscNYv369UVlZadx6661m/Qgxa6jn9qmnnjKSk5ONJ554wtizZ4/x9ttvG1OnTjUuueQSs36EmNXS0mJs27bN2LZtmyHJeOyxx4xt27YZ+/fvNwzj5HO7d+9eIy0tzbjnnnuMDz/80HjyySeNlJQU4/e//71ZP0LMGuq5XblypZGcnGz88pe/NOrq6oJbc3OzWT9CzBrquf2kcH2XEURC9Mtf/tIoKyszUlNTjYsuushYv3598LkFCxYYV1xxxYCvJYic3lDP7c6dO41Pf/rThsPhMEpKSoxFixYZ7e3tUa46Pgz13P785z83zjnnHMPhcBhFRUXG1772NePgwYNRrjr2vfnmm4akk7YFCxYYhnHqc7tu3TrjwgsvNFJTU43y8nJjxYoV0S88Dgz13F5xxRWnPR7HhfL39kTh+i6zGAZtrAAAwByMEQEAAKYhiAAAANMQRAAAgGkIIgAAwDQEEQAAYBqCCAAAMA1BBAAAmIYgAgAATEMQAQAApiGIAAAA0xBEAACAaQgiAADANP8f+Ziut0B0zN0AAAAASUVORK5CYII=\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "exe.output[0].plot()" ] @@ -1265,7 +1222,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": null, "id": "149c52b5-a0ce-4e6b-ba55-d94d33aa2f8a", "metadata": { "tags": [] @@ -1277,7 +1234,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": null, "id": "aca24005-ea49-4389-bc26-f292fd0a75a2", "metadata": { "tags": [] @@ -1296,7 +1253,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": null, "id": "4ae990bd-af18-4dae-8500-779c9509f3f6", "metadata": { "tags": [] @@ -1309,1022 +1266,13 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": null, "id": "0925864e-4dd1-4f4e-ace4-aac09c55e787", "metadata": { "scrolled": true, "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:22 4.789242 0.0000\n", - "LBFGS: 0 11:45:22 4.517693 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:22 3.991875 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:22 4.251945 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 3.006013 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 3.488292 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 3.737364 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 3.244546 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 2.320604 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 2.772582 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 2.544148 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 2.101849 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 1.887783 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 1.678307 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 1.272749 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 1.473327 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 0.884435 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 1.076481 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 0.696523 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 0.512659 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 0.332761 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 0.156747 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -0.183946 0.0000\n", - "LBFGS: 0 11:45:23 -0.015462 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -0.348779 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -0.510037 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -0.667792 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -0.822116 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -0.973078 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -1.120747 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -1.406469 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -1.544652 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -1.265189 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -1.679799 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -1.941232 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -1.811973 0.0000\n", - "LBFGS: 0 11:45:23 -2.067636 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -2.191241 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -2.312104 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -2.430279 0.0000\n", - "LBFGS: 0 11:45:23 -2.545820 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -2.658780 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -2.769210 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -2.982680 0.0000\n", - "LBFGS: 0 11:45:23 -2.877160 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -3.085817 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -3.186620 0.0000\n", - "LBFGS: 0 11:45:23 -3.381404 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -3.285134 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -3.657192 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -3.475475 0.0000\n", - "LBFGS: 0 11:45:23 -3.567390 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:23 -3.744922 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -3.830620 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -3.914328 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -3.996084 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -4.075925 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -4.153891 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -4.230016 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -4.304338 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -4.376892 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -4.516830 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -4.584282 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -4.447711 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -4.650099 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -4.838057 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -4.714313 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -4.776956 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -4.955755 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -4.897647 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.012409 0.0000\n", - "LBFGS: 0 11:45:24 -5.067638 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.121469 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.225046 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.274844 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.173930 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.370587 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.323350 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.416581 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.461356 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.504934 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.547340 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.588595 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.667742 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.705677 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.628722 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.778375 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.742548 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.813177 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.846976 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.879789 0.0000\n", - "LBFGS: 0 11:45:24 -5.911636 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.942536 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -6.029730 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -5.972506 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -6.001565 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -6.057017 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -6.083445 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -6.109029 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -6.133786 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -6.157731 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -6.180881 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:24 -6.203250 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.224853 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.245705 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.265820 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.303898 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.285213 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.321888 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.371820 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.355836 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.339196 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.401872 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.415965 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.429451 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.387162 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.442342 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.454650 0.0000\n", - "LBFGS: 0 11:45:25 -6.466386 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.488186 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.498271 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.477561 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.507828 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.525395 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.516865 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.533425 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.540966 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.554620 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.548028 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.560750 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.566429 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.576465 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.571664 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.580840 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.588345 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.591492 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.584797 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.596612 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.598601 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.594245 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.600220 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.602924 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.602374 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.601475 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.603132 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.603005 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.602549 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.601771 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.600678 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.597570 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.599275 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.595568 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.584710 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.593275 0.0000\n", - "LBFGS: 0 11:45:25 -6.590697 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.587840 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.577651 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.573734 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.581312 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.569565 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.565149 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.555599 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.560493 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.550475 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.533761 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.539551 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.527759 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.545124 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.515134 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.508521 0.0000\n", - "LBFGS: 0 11:45:25 -6.521548 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.501712 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.487527 0.0000\n", - "LBFGS: 0 11:45:25 -6.494713 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.480158 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.472611 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.464889 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.448936 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.456996 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.440712 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.432329 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.415096 0.0000\n", - "LBFGS: 0 11:45:25 -6.423789 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:25 -6.406254 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.397266 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.378866 0.0000\n", - "LBFGS: 0 11:45:26 -6.388136 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.369460 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.340455 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.330535 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.359921 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.350251 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.320493 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.310333 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.300058 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.289669 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.279171 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.268565 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.257855 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.247042 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.236130 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.225120 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.214016 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.202819 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.180158 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.191532 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.168698 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.157154 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.145530 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.133827 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.122047 0.0000\n", - "LBFGS: 0 11:45:26 -6.110193 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.098266 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.086268 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.074202 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.062068 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.049871 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.025288 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.037610 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.012907 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -6.000468 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.975426 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.950174 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.987974 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.962825 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.937473 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.911931 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.924725 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.899093 0.0000\n", - "LBFGS: 0 11:45:26 -5.886212 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.873290 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.847326 0.0000\n", - "LBFGS: 0 11:45:26 -5.860327 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.834289 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.821215 0.0000\n", - "LBFGS: 0 11:45:26 -5.808107 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.794966 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.781794 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.755359 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.768591 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.742099 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.728813 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.715501 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:26 -5.702165 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.688805 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.675424 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.648600 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.662022 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.635159 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.621701 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.594735 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.608226 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.567712 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.581230 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.540637 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.513519 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.527083 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.554180 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.472778 0.0000\n", - "LBFGS: 0 11:45:27 -5.499947 0.0000\n", - "LBFGS: 0 11:45:27 -5.486366 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.459183 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.418369 0.0000\n", - "LBFGS: 0 11:45:27 -5.445583 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.431978 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.391143 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.404757 0.0000\n", - "LBFGS: 0 11:45:27 -5.377527 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.363910 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.336676 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.323061 0.0000\n", - "LBFGS: 0 11:45:27 -5.350293 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.309448 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.268625 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.282229 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.295837 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.255026 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.241432 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.214262 0.0000\n", - "LBFGS: 0 11:45:27 -5.227844 0.0000\n", - "LBFGS: 0 11:45:27 -5.200687 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.160009 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.187119 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.173560 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.146468 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.119414 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.132936 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.105903 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.092403 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.078915 0.0000\n", - "LBFGS: 0 11:45:27 -5.065440 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.051977 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.038527 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.025090 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -5.011668 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -4.998260 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -4.984867 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -4.971490 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -4.958128 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:27 -4.944782 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.918140 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.931453 0.0000\n", - "LBFGS: 0 11:45:28 -4.904845 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.891567 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.851844 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.865067 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.878308 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.838641 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.812292 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.825456 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.786023 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.799147 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.772919 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.759837 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.746775 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.733735 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.707720 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.720716 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.694745 0.0000\n", - "LBFGS: 0 11:45:28 -4.681794 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.655959 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.668865 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.643076 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.630216 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.617381 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.604569 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.591781 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.579018 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.566279 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.553565 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.528212 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.515574 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.540876 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.502960 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.477811 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.465275 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.490373 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.440283 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.452766 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.427826 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.415396 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.402992 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.390616 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.365944 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.378267 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.353649 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.341382 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:28 -4.329142 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.304745 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.316929 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.292588 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.280459 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.256286 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.268359 0.0000\n", - "LBFGS: 0 11:45:29 -4.244242 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.232226 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.220239 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.208280 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.196350 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.172575 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.184448 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.160732 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.137131 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.148917 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.113646 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.125374 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.090278 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.101948 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.078638 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.055446 0.0000\n", - "LBFGS: 0 11:45:29 -4.067028 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.043894 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.020879 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.032372 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -4.009416 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.986578 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.997982 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.975204 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.963859 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.952544 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.941259 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.930004 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.907582 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.885280 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.918778 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.896416 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.863097 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.874174 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.852051 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.841034 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.808164 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.819091 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.830047 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.797267 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.764755 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.775562 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.786400 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.753978 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.743230 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.732513 0.0000\n", - "LBFGS: 0 11:45:29 -3.721825 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.711167 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.700539 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.689941 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.679373 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:29 -3.668834 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.637397 0.0000\n", - "LBFGS: 0 11:45:30 -3.658325 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.647846 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.626978 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.606228 0.0000\n", - "LBFGS: 0 11:45:30 -3.616588 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.595897 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.585597 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.565084 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.575325 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.554872 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.544689 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.534536 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.524412 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.504253 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.494218 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.484212 0.0000\n", - "LBFGS: 0 11:45:30 -3.514318 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.474235 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.464287 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.454369 0.0000\n", - "LBFGS: 0 11:45:30 -3.434619 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.444480 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.424788 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.405213 0.0000\n", - "LBFGS: 0 11:45:30 -3.414986 0.0000\n", - "LBFGS: 0 11:45:30 -3.395469 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.385754 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.366410 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.376068 0.0000\n", - "LBFGS: 0 11:45:30 -3.356781 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.347181 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.328067 0.0000\n", - "LBFGS: 0 11:45:30 -3.337610 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.318553 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.309067 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.299610 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.280781 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.271409 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.252749 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.290181 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.262065 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.243462 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.234202 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.215768 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.224971 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.206592 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.197445 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.188325 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.170169 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.179233 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.161132 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.152123 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:30 -3.134187 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -3.143141 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -3.125260 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -3.107488 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -3.116361 0.0000\n", - "LBFGS: 0 11:45:31 -3.098643 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -3.089825 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -3.081034 0.0000\n", - "LBFGS: 0 11:45:31 -3.063534 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -3.072271 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -3.054823 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -3.037483 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -3.028853 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -3.046140 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -3.011673 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -3.020250 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.994599 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -3.003123 0.0000\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.986101 0.0000\n", - "LBFGS: 0 11:45:31 -2.960766 0.0000\n", - "LBFGS: 0 11:45:31 -2.977630 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.969185 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.935665 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.944006 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.952373 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.927350 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.919060 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.910796 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.902558 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.894346 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.869862 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.877998 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.886159 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.845605 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.861751 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.853666 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.837570 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.821575 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.813615 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.805679 0.0000\n", - " Step Time Energy fmax\n", - "LBFGS: 0 11:45:31 -2.829560 0.0000\n", - "CPU times: user 2.17 s, sys: 1.49 s, total: 3.66 s\n", - "Wall time: 9.24 s\n" - ] - } - ], + "outputs": [], "source": [ "%%time\n", "exe = ProcessExecutor(max_processes=4).submit([m])\n", @@ -2336,68 +1284,34 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": null, "id": "71bbb913-7d7a-4bb6-b775-3fbc8e7e1f35", "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "text/plain": [ - "ReturnStatus(Code.DONE, None)" - ] - }, - "execution_count": 67, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "ret" ] }, { "cell_type": "code", - "execution_count": 68, + "execution_count": null, "id": "4bf2df15-31dc-474c-b3df-f7c32b0fdaf2", "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "text/plain": [ - "array([4.78924238, 4.51769267, 4.25194477, 3.99187529, 3.7373637 ,\n", - " 3.48829227, 3.244546 , 3.00601254, 2.77258214, 2.54414756])" - ] - }, - "execution_count": 68, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "output.energies[:10]" ] }, { "cell_type": "code", - "execution_count": 69, + "execution_count": null, "id": "eb0a2daf-9dab-4174-bfee-0cd1ef8c474e", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "output.plot()" ] diff --git a/notebooks/tinybase/TinyJob.ipynb b/notebooks/tinybase/TinyJob.ipynb index 3240f99f5..c2ed74c9a 100644 --- a/notebooks/tinybase/TinyJob.ipynb +++ b/notebooks/tinybase/TinyJob.ipynb @@ -115,7 +115,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "b6954634635d434a8a07606fcd87df98", + "model_id": "fcabf727e97b4c06943de99599aaa7dd", "version_major": 2, "version_minor": 0 }, @@ -166,11 +166,14 @@ }, "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "INFO:root:Job already finished!\n" - ] + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -219,24 +222,10 @@ "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "1c9f4a31d9c4468d83f600e6601fd152", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "NGLWidget(max_frame=1)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "j.output.animate_structures()" + "# Testing env doesn't have nglview\n", + "# j.output.animate_structures()" ] }, { @@ -249,7 +238,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 23, "id": "7b807119-da8d-475c-9aa8-c8e8c9afa115", "metadata": { "tags": [] @@ -261,7 +250,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 24, "id": "3a8cda32-df2e-4884-8cfd-84e438c5be69", "metadata": { "tags": [] @@ -279,7 +268,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 25, "id": "d4b81c3f-4667-4b99-a2b3-08c7ee7e2c82", "metadata": { "tags": [] @@ -294,7 +283,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 26, "id": "e7494fee-d565-45e3-a819-c77ab0d2c7f6", "metadata": { "scrolled": true, @@ -302,10 +291,53 @@ }, "outputs": [ { - "name": "stderr", + "name": "stdout", "output_type": "stream", "text": [ - "INFO:root:Job already finished!\n" + " Step Time Energy fmax\n", + "LBFGS: 0 12:27:58 11.288146 189.5231\n", + "LBFGS: 1 12:27:58 1.168671 43.6957\n", + "LBFGS: 2 12:27:58 0.860403 38.6924\n", + "LBFGS: 3 12:27:58 0.362400 30.3554\n", + "LBFGS: 4 12:27:58 0.004806 24.0865\n", + "LBFGS: 5 12:27:58 -0.267437 19.0615\n", + "LBFGS: 6 12:27:58 -0.471646 15.0628\n", + "LBFGS: 7 12:27:58 -0.623506 11.8810\n", + "LBFGS: 8 12:27:58 -0.735237 9.3518\n", + "LBFGS: 9 12:27:58 -0.816458 7.3435\n", + "LBFGS: 10 12:27:58 -0.874705 5.7512\n", + "LBFGS: 11 12:27:58 -0.915849 4.4909\n", + "LBFGS: 12 12:27:58 -0.944435 3.4955\n", + "LBFGS: 13 12:27:58 -0.963943 2.7113\n", + "LBFGS: 14 12:27:58 -0.977006 2.0956\n", + "LBFGS: 15 12:27:58 -0.985585 1.6137\n", + "LBFGS: 16 12:27:58 -0.991109 1.2382\n", + "LBFGS: 17 12:27:58 -0.994598 0.9468\n", + "LBFGS: 18 12:27:58 -0.996763 0.7216\n", + "LBFGS: 19 12:27:58 -0.998083 0.5484\n", + "LBFGS: 20 12:27:58 -0.998876 0.4157\n", + "LBFGS: 21 12:27:58 -0.999347 0.3144\n", + "LBFGS: 22 12:27:58 -0.999623 0.2374\n", + "LBFGS: 23 12:27:58 -0.999784 0.1790\n", + "LBFGS: 24 12:27:58 -0.999877 0.1348\n", + "LBFGS: 25 12:27:58 -0.999930 0.1014\n", + "LBFGS: 26 12:27:58 -0.999960 0.0762\n", + "LBFGS: 27 12:27:58 -0.999977 0.0573\n", + "LBFGS: 28 12:27:58 -0.999987 0.0430\n", + "LBFGS: 29 12:27:58 -0.999993 0.0323\n", + "LBFGS: 30 12:27:58 -0.999996 0.0242\n", + "LBFGS: 31 12:27:58 -0.999998 0.0182\n", + "LBFGS: 32 12:27:58 -0.999999 0.0136\n", + "LBFGS: 33 12:27:58 -0.999999 0.0102\n", + "LBFGS: 34 12:27:58 -1.000000 0.0077\n", + "LBFGS: 35 12:27:58 -1.000000 0.0058\n", + "LBFGS: 36 12:27:58 -1.000000 0.0043\n", + "LBFGS: 37 12:27:58 -1.000000 0.0032\n", + "LBFGS: 38 12:27:58 -1.000000 0.0024\n", + "LBFGS: 39 12:27:58 -1.000000 0.0018\n", + "LBFGS: 40 12:27:58 -1.000000 0.0014\n", + "LBFGS: 41 12:27:58 -1.000000 0.0010\n", + "LBFGS: 42 12:27:58 -1.000000 0.0008\n" ] } ], @@ -318,7 +350,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 27, "id": "7c16a615-0913-4880-9694-2c125285babc", "metadata": { "tags": [] @@ -376,45 +408,30 @@ " min\n", " 2\n", " 1\n", - " 2\n", - " /home/poul/pyiron/contrib/notebooks/tinybase/t...\n", - " finished\n", - " AseMinimizeTask\n", - " \n", - " \n", - " 2\n", - " 3\n", - " pyiron\n", - " murn\n", - " 3\n", - " 1\n", " 3\n", " /home/poul/pyiron/contrib/notebooks/tinybase/t...\n", " finished\n", - " MurnaghanTask\n", + " AseMinimizeTask\n", " \n", " \n", "\n", "" ], "text/plain": [ - " id username name jobtype_id project_id status_id \\\n", - "0 1 pyiron md 1 1 1 \n", - "1 2 pyiron min 2 1 2 \n", - "2 3 pyiron murn 3 1 3 \n", + " id username name jobtype_id project_id status_id \\\n", + "0 1 pyiron md 1 1 1 \n", + "1 2 pyiron min 2 1 3 \n", "\n", " location status \\\n", "0 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", "1 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", - "2 /home/poul/pyiron/contrib/notebooks/tinybase/t... finished \n", "\n", " type \n", "0 AseMDTask \n", - "1 AseMinimizeTask \n", - "2 MurnaghanTask " + "1 AseMinimizeTask " ] }, - "execution_count": 16, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -425,7 +442,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 28, "id": "59ea5510-b6ce-4317-90c3-4af77db3d59a", "metadata": { "tags": [] @@ -448,29 +465,15 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 29, "id": "3fb09d42-f800-46ee-9919-83180863e1ee", "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "c775879f87fd4395b005043060a9bb4f", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "NGLWidget(max_frame=5)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "j.output.animate_structures()" + "# Testing env doesn't have nglview\n", + "# j.output.animate_structures()" ] }, { @@ -483,7 +486,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 30, "id": "d32508b9-2854-4076-9109-08ede1b52dc2", "metadata": { "tags": [] @@ -500,7 +503,7 @@ " -0.999999995888409]" ] }, - "execution_count": 19, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -511,7 +514,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 31, "id": "db691097-72c6-45a4-89b1-6ec16018c8b8", "metadata": { "tags": [] @@ -534,27 +537,13 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 32, "id": "23ce6822-b38b-41f3-9269-109dbb152ecf", "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "f6d29a76f660478caba449c02e996830", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "NGLWidget(max_frame=5)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "j.project.load(j.name).output.animate_structures()" + "# Testing env doesn't have nglview\n", + "# j.project.load(j.name).output.animate_structures()" ] }, { @@ -567,7 +556,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 33, "id": "654ce992-b73f-42e3-a32e-2e0dafa7c952", "metadata": { "tags": [] @@ -579,7 +568,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 34, "id": "253237f0-b338-470c-bc54-3c7400a757b7", "metadata": {}, "outputs": [], @@ -590,7 +579,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 35, "id": "c801093b-499e-48a7-8444-77602ed88a96", "metadata": {}, "outputs": [], @@ -600,7 +589,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 36, "id": "1e30b36e-11e6-47d1-836e-cffea7b73cdd", "metadata": {}, "outputs": [], @@ -610,23 +599,16 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 37, "id": "18b5305a-8950-44af-bc2e-c9734b059713", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "INFO:root:Job already finished!\n" - ] - }, { "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 5.73 ms, sys: 0 ns, total: 5.73 ms\n", - "Wall time: 5.18 ms\n" + "CPU times: user 1.08 s, sys: 415 ms, total: 1.5 s\n", + "Wall time: 11.9 s\n" ] } ], @@ -638,7 +620,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 38, "id": "836bb2ec-4295-4a3c-b976-7a35d04aad36", "metadata": {}, "outputs": [ @@ -669,7 +651,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 39, "id": "79a2bb61-0a5e-4a3a-b195-46d027738a0e", "metadata": {}, "outputs": [], @@ -679,7 +661,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 40, "id": "b4e6e0c9-a2c6-40ab-884e-a46e16c37b04", "metadata": {}, "outputs": [ @@ -717,7 +699,7 @@ "Index: []" ] }, - "execution_count": 29, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } @@ -728,7 +710,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 41, "id": "cef7c46f-551f-401e-96c2-214628e23967", "metadata": {}, "outputs": [ @@ -756,7 +738,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 42, "id": "e8a7ee30-d7a6-46fc-bf98-1b52c981470f", "metadata": {}, "outputs": [ @@ -817,7 +799,7 @@ "0 MurnaghanTask " ] }, - "execution_count": 31, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } @@ -828,18 +810,10 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 43, "id": "30871447-3e20-46ee-a58e-853d4f4cb5d9", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "DEBUG:pyiron_log:Not supported parameter used!\n" - ] - } - ], + "outputs": [], "source": [ "j = pr.create.job.AseMD('md')\n", "j.input.structure = pr.create.structure.bulk('Fe', a=1.2, cubic=True).repeat(2).to_ase() # since our Atoms cannot be pickled, but parallel execution needs that we still convert back here\n", @@ -854,7 +828,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 44, "id": "e63d43c1-341f-4ec0-b0cf-9cd5ead51926", "metadata": {}, "outputs": [ @@ -912,7 +886,7 @@ " 1\n", " 2\n", " /\n", - " collect\n", + " finished\n", " AseMDTask\n", " \n", " \n", @@ -922,14 +896,14 @@ "text/plain": [ " id username name jobtype_id project_id status_id location status \\\n", "0 1 pyiron murn 1 1 1 / finished \n", - "1 2 pyiron md 2 1 2 / collect \n", + "1 2 pyiron md 2 1 2 / finished \n", "\n", " type \n", "0 MurnaghanTask \n", "1 AseMDTask " ] }, - "execution_count": 33, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } @@ -948,24 +922,17 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 45, "id": "80da39e2-76d1-42e6-977f-241d2683188d", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "DEBUG:pyiron_log:Not supported parameter used!\n" - ] - }, { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 34, + "execution_count": 45, "metadata": {}, "output_type": "execute_result" } @@ -984,7 +951,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 46, "id": "a567f96a-cbb3-4d2d-95d1-6dcecee7ddb8", "metadata": {}, "outputs": [ @@ -1073,7 +1040,7 @@ "2 AseMDTask " ] }, - "execution_count": 35, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } From d03aa9d691bd3078a50e374ea261d4c58d8b51a0 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 25 Sep 2023 10:30:30 +0000 Subject: [PATCH 678/756] Format black --- pyiron_contrib/tinybase/ase.py | 1 + pyiron_contrib/tinybase/creator.py | 1 + pyiron_contrib/tinybase/storage.py | 12 ++++++++---- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pyiron_contrib/tinybase/ase.py b/pyiron_contrib/tinybase/ase.py index 33016c6cc..48114383d 100644 --- a/pyiron_contrib/tinybase/ase.py +++ b/pyiron_contrib/tinybase/ase.py @@ -31,6 +31,7 @@ def _store(self, storage): class AseStaticInput(AseInput, StructureInput): pass + class AseStaticTask(AbstractTask): def _get_input(self): return AseStaticInput() diff --git a/pyiron_contrib/tinybase/creator.py b/pyiron_contrib/tinybase/creator.py index d7e4836e3..ba9986739 100644 --- a/pyiron_contrib/tinybase/creator.py +++ b/pyiron_contrib/tinybase/creator.py @@ -174,6 +174,7 @@ def bulk(self, *args, **kwargs): atoms = Atoms + class ExecutorCreator(Creator): _DEFAULT_CPUS = min(int(0.5 * cpu_count()), 8) diff --git a/pyiron_contrib/tinybase/storage.py b/pyiron_contrib/tinybase/storage.py index 34f739a5d..bf87bdf27 100644 --- a/pyiron_contrib/tinybase/storage.py +++ b/pyiron_contrib/tinybase/storage.py @@ -248,7 +248,7 @@ def _set(self, item, value): else: try: self._hdf[item] = value - except TypeError: # HDF layer doesn't know how to write value + except TypeError: # HDF layer doesn't know how to write value # h5io bug, when triggering an error in the middle of a write # some residual data maybe left in the file del self._hdf[item] @@ -282,6 +282,7 @@ def to_object(self): except: return super().to_object() + class DataContainerAdapter(GenericStorage): """ Provides in memory location to store objects. @@ -385,6 +386,7 @@ def restore(cls, storage: GenericStorage, version: str) -> "Storable": except Exception as e: raise ValueError(f"Failed to restore object with: {e}") + class HasHDFAdapaterMixin(Storable): """ Implements :class:`.Storable` in terms of HasHDF. Make any sub class of it a subclass :class:`.Storable` as well by @@ -428,12 +430,14 @@ def _store(self, storage): def _restore(cls, storage, version): return pickle_load(storage["pickle"]) + class ListStorable(Storable): """ Trivial implementation of :class:`.Storable` for lists with potentially complex objects inside. Used by :class:`.GenericStorage` as a fallback if storing the list with h5py/h5io as it is fails. """ + def __init__(self, value): self._value = value @@ -444,9 +448,9 @@ def _store(self, storage): @classmethod def _restore(cls, storage, version): keys = sorted( - [v for v in storage.list_nodes() if v.startswith("index_")] - + [v for v in storage.list_groups() if v.startswith("index_")], - key=lambda k: int(k.split("_")[1]) + [v for v in storage.list_nodes() if v.startswith("index_")] + + [v for v in storage.list_groups() if v.startswith("index_")], + key=lambda k: int(k.split("_")[1]), ) value = [] for k in keys: From 8be6ca99924846ff975383ce5dabad54854e169a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jan=C3=9Fen?= Date: Sat, 30 Sep 2023 08:56:01 +0200 Subject: [PATCH 679/756] Remove nofiles classes --- pyiron_contrib/__init__.py | 9 - pyiron_contrib/nofiles/__init__.py | 0 pyiron_contrib/nofiles/elastic.py | 144 -------- pyiron_contrib/nofiles/lammps.py | 78 ---- pyiron_contrib/nofiles/master.py | 570 ----------------------------- pyiron_contrib/nofiles/murn.py | 175 --------- pyiron_contrib/nofiles/phonopy.py | 179 --------- pyiron_contrib/nofiles/sqs.py | 45 --- 8 files changed, 1200 deletions(-) delete mode 100644 pyiron_contrib/nofiles/__init__.py delete mode 100644 pyiron_contrib/nofiles/elastic.py delete mode 100644 pyiron_contrib/nofiles/lammps.py delete mode 100644 pyiron_contrib/nofiles/master.py delete mode 100644 pyiron_contrib/nofiles/murn.py delete mode 100644 pyiron_contrib/nofiles/phonopy.py delete mode 100644 pyiron_contrib/nofiles/sqs.py diff --git a/pyiron_contrib/__init__.py b/pyiron_contrib/__init__.py index 20a3667bf..308d7e04a 100644 --- a/pyiron_contrib/__init__.py +++ b/pyiron_contrib/__init__.py @@ -56,15 +56,6 @@ "PiMD": "pyiron_contrib.atomistics.ipi.ipi_jobs", "GleMD": "pyiron_contrib.atomistics.ipi.ipi_jobs", "PigletMD": "pyiron_contrib.atomistics.ipi.ipi_jobs", - "LammpsInteractiveWithoutOutput": "pyiron_contrib.nofiles.lammps", - "SQSJobWithoutOutput": "pyiron_contrib.nofiles.sqs", - "ElasticMatrixJobWithoutFiles": "pyiron_contrib.nofiles.elastic", - "MurnaghanWithoutFiles": "pyiron_contrib.nofiles.murn", - "PhonopyJobWithoutFiles": "pyiron_contrib.nofiles.phonopy", - "SQSMasterMPI": "pyiron_contrib.nofiles.master", - "LAMMPSMinimizeMPI": "pyiron_contrib.nofiles.master", - "LAMMPSElasticMPI": "pyiron_contrib.nofiles.master", - "LAMMPSMinimizeElasticMPI": "pyiron_contrib.nofiles.master", "FitsnapJob": "pyiron_contrib.atomistics.fitsnap.job", "QuasiHarmonicApproximation": "pyiron_contrib.atomistics.atomistics.master.qha", } diff --git a/pyiron_contrib/nofiles/__init__.py b/pyiron_contrib/nofiles/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/pyiron_contrib/nofiles/elastic.py b/pyiron_contrib/nofiles/elastic.py deleted file mode 100644 index 01bf9ae51..000000000 --- a/pyiron_contrib/nofiles/elastic.py +++ /dev/null @@ -1,144 +0,0 @@ -from pyiron_base import GenericMaster, state -from pyiron_gpl.elastic.elastic import ElasticMatrixJob - - -class ElasticMatrixJobWithoutFiles(ElasticMatrixJob): - def __init__(self, project, job_name): - if not state.database.database_is_disabled: - raise RuntimeError( - "To run a `Without` job, the database must first be disabled. Please " - "`from pyiron_base import state; " - "state.update({'disable_database': True})`, and try again." - ) - super(ElasticMatrixJobWithoutFiles, self).__init__(project, job_name) - self._interactive_disable_log_file = True - - @property - def child_project(self): - """ - :class:`.Project`: project which holds the created child jobs - """ - if not self._interactive_disable_log_file: - return super(ElasticMatrixJobWithoutFiles, self).child_project - else: - return self.project - - def to_hdf(self, hdf=None, group_name=None): - """ - - Args: - hdf: - group_name: - - Returns: - - """ - if not self._interactive_disable_log_file: - super(ElasticMatrixJobWithoutFiles, self).to_hdf( - hdf=hdf, group_name=group_name - ) - - def refresh_job_status(self): - if not self._interactive_disable_log_file: - super(ElasticMatrixJobWithoutFiles).refresh_job_status() - - def collect_output(self): - if not self._data: - self.from_hdf() - self.create_calculator() - - energies = {} - self._data["id"] = [] - if self.server.run_mode.interactive and not self._interactive_disable_log_file: - child_id = self.child_ids[0] - self._data["id"].append(child_id) - child_job = self.project_hdf5.inspect(child_id) - energies = { - job_name: energy - for job_name, energy in zip( - self.structure_dict.keys(), child_job["output/generic/energy_tot"] - ) - } - elif self.server.run_mode.interactive and self._interactive_disable_log_file: - energies = { - job_name: energy - for job_name, energy in zip( - self.structure_dict.keys(), - self.ref_job.interactive_cache["energy_tot"], - ) - } - else: - for job_id in self.child_ids: - ham = self.project_hdf5.inspect(job_id) - en = ham["output/generic/energy_tot"][-1] - energies[ham.job_name] = en - self._data["id"].append(ham.job_id) - - self.property_calculator.analyse_structures(energies) - self._data.update(self.property_calculator._data) - if not self._interactive_disable_log_file: - self.to_hdf() - - def append(self, job): - """ - Append a job to the GenericMaster - just like you would append an element to a list. - - Args: - job (GenericJob): job to append - """ - if self.status.initialized and not job.status.initialized: - raise ValueError( - "GenericMaster requires reference jobs to have status initialized, rather than ", - job.status.string, - ) - if job.server.cores >= self.server.cores: - self.server.cores = job.server.cores - if job.job_name not in self._job_name_lst: - self._job_name_lst.append(job.job_name) - if not self._interactive_disable_log_file: - self._child_job_update_hdf(parent_job=self, child_job=job) - - def pop(self, i=-1): - """ - Pop a job from the GenericMaster - just like you would pop an element from a list - - Args: - i (int): position of the job. (Default is last element, -1.) - - Returns: - GenericJob: job - """ - job_name_to_return = self._job_name_lst[i] - job_to_return = self._load_all_child_jobs( - self._load_job_from_cache(job_name_to_return) - ) - del self._job_name_lst[i] - if not self._interactive_disable_log_file: - with self.project_hdf5.open("input") as hdf5_input: - hdf5_input["job_list"] = self._job_name_lst - job_to_return.relocate_hdf5() - if isinstance(job_to_return, GenericMaster): - for sub_job in job_to_return._job_object_dict.values(): - self._child_job_update_hdf(parent_job=job_to_return, child_job=sub_job) - job_to_return.status.initialized = True - return job_to_return - - def _run_if_collect(self): - """ - Internal helper function the run if collect function is called when the job status is 'collect'. It collects - the simulation output using the standardized functions collect_output() and collect_logfiles(). Afterwards the - status is set to 'finished'. - """ - - if not self._interactive_disable_log_file: - super(ElasticMatrixJobWithoutFiles)._run_if_collect() - else: - self._logger.info( - "{}, status: {}, finished".format(self.job_info_str, self.status) - ) - self.collect_output() - self._logger.info( - "{}, status: {}, parallel master".format(self.job_info_str, self.status) - ) - self.update_master() - # self.send_to_database() diff --git a/pyiron_contrib/nofiles/lammps.py b/pyiron_contrib/nofiles/lammps.py deleted file mode 100644 index c337b1426..000000000 --- a/pyiron_contrib/nofiles/lammps.py +++ /dev/null @@ -1,78 +0,0 @@ -import os -import importlib -from pyiron_atomistics.lammps.interactive import LammpsInteractive -from pyiron_base import state - -try: # mpi4py is only supported on Linux and Mac Os X - from pylammpsmpi import LammpsLibrary -except ImportError: - pass - - -class LammpsInteractiveWithoutOutput(LammpsInteractive): - def __init__(self, project, job_name): - if not state.database.database_is_disabled: - raise RuntimeError( - "To run a `Without` job, the database must first be disabled. Please " - "`from pyiron_base import state; " - "state.update({'disable_database': True})`, and try again." - ) - super().__init__(project, job_name) - self._interactive_disable_log_file = True - - def to_hdf(self, hdf=None, group_name=None): - """ - - Args: - hdf: - group_name: - - Returns: - - """ - if not self._interactive_disable_log_file: - super(LammpsInteractiveWithoutOutput, self).to_hdf( - hdf=hdf, group_name=group_name - ) - - def interactive_flush(self, path="interactive", include_last_step=False): - if not self._interactive_disable_log_file: - super(LammpsInteractiveWithoutOutput, self).interactive_flush( - path=path, include_last_step=include_last_step - ) - - def interactive_initialize_interface(self): - if not self._interactive_disable_log_file: - self._create_working_directory() - if self.server.run_mode.interactive and self.server.cores == 1: - lammps = getattr(importlib.import_module("lammps"), "lammps") - if self._log_file is None: - self._log_file = os.path.join(self.working_directory, "log.lammps") - if not self._interactive_disable_log_file: - self._interactive_library = lammps( - cmdargs=["-screen", "none", "-log", self._log_file], - comm=self._interactive_mpi_communicator, - ) - else: - self._interactive_library = lammps( - cmdargs=["-screen", "none", "-log", "none"], - comm=self._interactive_mpi_communicator, - ) - else: - self._interactive_library = LammpsLibrary( - cores=self.server.cores, working_directory=self.working_directory - ) - if not all(self.structure.pbc): - self.input.control["boundary"] = " ".join( - ["p" if coord else "f" for coord in self.structure.pbc] - ) - self._reset_interactive_run_command() - self.interactive_structure_setter(self.structure) - - def interactive_close(self): - if not self._interactive_disable_log_file: - super().interactive_close() - - def refresh_job_status(self): - if not self._interactive_disable_log_file: - super().refresh_job_status() diff --git a/pyiron_contrib/nofiles/master.py b/pyiron_contrib/nofiles/master.py deleted file mode 100644 index 38af1d361..000000000 --- a/pyiron_contrib/nofiles/master.py +++ /dev/null @@ -1,570 +0,0 @@ -import pandas -from pympipool import Pool -from pyiron_base import DataContainer, GenericJob, state -from pyiron_atomistics.project import Project -from pyiron_atomistics.atomistics.structure.atoms import ( - Atoms, - pyiron_to_ase, - ase_to_pyiron, -) -from pyiron_atomistics.atomistics.job.atomistic import AtomisticGenericJob - - -class GenericJobNoFiles(GenericJob): - def __init__(self, project, job_name): - if not state.database.database_is_disabled: - raise RuntimeError( - "To run a `Without` job, the database must first be disabled. Please " - "`from pyiron_base import state; " - "state.update({'disable_database': True})`, and try again." - ) - super(GenericJobNoFiles, self).__init__(project, job_name) - - # internal variables - self._python_only_job = True - self._interactive_disable_log_file = True - - def refresh_job_status(self): - if not self._interactive_disable_log_file: - super(GenericJobNoFiles, self).refresh_job_status() - - def to_hdf(self, hdf=None, group_name=None): - """ - - Args: - hdf: - group_name: - - Returns: - - """ - if not self._interactive_disable_log_file: - super(GenericJobNoFiles, self).to_hdf(hdf=hdf, group_name=group_name) - - -class AtomisticGenericJobNoFiles(AtomisticGenericJob): - def __init__(self, project, job_name): - super(AtomisticGenericJobNoFiles, self).__init__(project, job_name) - - # internal variables - self._python_only_job = True - self._interactive_disable_log_file = False - - def refresh_job_status(self): - if not self._interactive_disable_log_file: - super(AtomisticGenericJobNoFiles, self).refresh_job_status() - - def to_hdf(self, hdf=None, group_name=None): - """ - - Args: - hdf: - group_name: - - Returns: - - """ - if not self._interactive_disable_log_file: - super(AtomisticGenericJobNoFiles, self).to_hdf( - hdf=hdf, group_name=group_name - ) - self._structure_to_hdf() - - -class AtomisticStructureMasterNoFiles(AtomisticGenericJobNoFiles): - def __init__(self, project, job_name): - super(AtomisticStructureMasterNoFiles, self).__init__(project, job_name) - self._lst_of_struct = [] - - @property - def list_of_structures(self): - return self._lst_of_struct - - def from_hdf(self, hdf=None, group_name=None): - super(AtomisticStructureMasterNoFiles, self).from_hdf( - hdf=hdf, group_name=group_name - ) - self._structure_from_hdf() - with self.project_hdf5.open("output/structures") as hdf5_output: - structure_names = hdf5_output.list_groups() - for group in structure_names: - with self.project_hdf5.open("output/structures/" + group) as hdf5_output: - self._lst_of_struct.append(Atoms().from_hdf(hdf5_output)) - - -class GenericStructureMasterNoFiles(GenericJobNoFiles): - def __init__(self, project, job_name): - super(GenericStructureMasterNoFiles, self).__init__(project, job_name) - self._lst_of_struct = [] - - @property - def list_of_structures(self): - return self._lst_of_struct - - def from_hdf(self, hdf=None, group_name=None): - super(GenericStructureMasterNoFiles, self).from_hdf( - hdf=hdf, group_name=group_name - ) - self._structure_from_hdf() - with self.project_hdf5.open("output/structures") as hdf5_output: - structure_names = hdf5_output.list_groups() - for group in structure_names: - with self.project_hdf5.open("output/structures/" + group) as hdf5_output: - self._lst_of_struct.append(Atoms().from_hdf(hdf5_output)) - - -class SQSMasterMPI(AtomisticStructureMasterNoFiles): - def __init__(self, project, job_name): - super(SQSMasterMPI, self).__init__(project, job_name) - - # input - self.input = DataContainer(table_name="custom_dict") - self.input.mole_fraction_dict_lst = [] - - def to_hdf(self, hdf=None, group_name=None): - """ - - Args: - hdf: - group_name: - - Returns: - - """ - if not self._interactive_disable_log_file: - super(SQSMasterMPI, self).to_hdf(hdf=hdf, group_name=group_name) - with self.project_hdf5.open("input") as h5in: - self.input.to_hdf(h5in) - - def run_static(self): - self.project_hdf5.create_working_directory() - input_para_lst = [ - [ - i, - mole_fraction_dict, - pyiron_to_ase(self.structure), - self.working_directory, - ] - for i, mole_fraction_dict in enumerate(self.input.mole_fraction_dict_lst) - ] - with Pool(cores=self.server.cores) as p: - list_of_structures = p.map( - function=generate_sqs_structures, lst=input_para_lst - ) - self._lst_of_struct = [ase_to_pyiron(s) for s in list_of_structures] - - if not self._interactive_disable_log_file: - for i, structure in enumerate(self._lst_of_struct): - with self.project_hdf5.open( - "output/structures/structure_" + str(i) - ) as h5: - structure.to_hdf(h5) - self.status.finished = True - self.project.db.item_update(self._runtime(), self.job_id) - - -class LAMMPSMinimizeMPI(GenericStructureMasterNoFiles): - def __init__(self, project, job_name): - super(LAMMPSMinimizeMPI, self).__init__(project, job_name) - - # input - self.input = DataContainer(table_name="custom_dict") - self.input.potential = "" - self._structure_lst = [] - - @property - def structure_lst(self): - return self._structure_lst - - @structure_lst.setter - def structure_lst(self, structure_lst): - self._structure_lst = structure_lst - - def to_hdf(self, hdf=None, group_name=None): - """ - - Args: - hdf: - group_name: - - Returns: - - """ - if not self._interactive_disable_log_file: - super(LAMMPSMinimizeMPI, self).to_hdf(hdf=hdf, group_name=group_name) - with self.project_hdf5.open("input") as h5in: - self.input.to_hdf(h5in) - with self.project_hdf5.open("input/structures") as hdf5_input: - for ind, struct in enumerate(self.structure_lst): - struct.to_hdf(hdf=hdf5_input, group_name="s_" + str(ind)) - - def run_static(self): - self.project_hdf5.create_working_directory() - input_para_lst = [ - [i, pyiron_to_ase(structure), self.input.potential, self.working_directory] - for i, structure in enumerate(self._structure_lst) - ] - with Pool(cores=self.server.cores) as p: - list_of_structures = p.map( - function=minimize_structure_with_lammps, lst=input_para_lst - ) - self._lst_of_struct = [ase_to_pyiron(s) for s in list_of_structures] - - if not self._interactive_disable_log_file: - for i, structure in enumerate(self._lst_of_struct): - with self.project_hdf5.open( - "output/structures/structure_" + str(i) - ) as h5: - structure.to_hdf(h5) - self.status.finished = True - self.project.db.item_update(self._runtime(), self.job_id) - - -class LAMMPSElasticMPI(GenericJobNoFiles): - def __init__(self, project, job_name): - super(LAMMPSElasticMPI, self).__init__(project, job_name) - - # input - self.input = DataContainer(table_name="custom_dict") - self.input.potential = "" - self.input.element_lst = [] - self._structure_lst = [] - self._results_df = None - - @property - def structure_lst(self): - return self._structure_lst - - @structure_lst.setter - def structure_lst(self, structure_lst): - self._structure_lst = structure_lst - - @property - def results(self): - return self._results_df - - def to_hdf(self, hdf=None, group_name=None): - """ - - Args: - hdf: - group_name: - - Returns: - - """ - if not self._interactive_disable_log_file: - super(LAMMPSElasticMPI, self).to_hdf(hdf=hdf, group_name=group_name) - with self.project_hdf5.open("input") as h5in: - self.input.to_hdf(h5in) - with self.project_hdf5.open("input/structures") as hdf5_input: - for ind, struct in enumerate(self.structure_lst): - struct.to_hdf(hdf=hdf5_input, group_name="s_" + str(ind)) - - def run_static(self): - self.project_hdf5.create_working_directory() - input_para_lst = [ - [ - i, - pyiron_to_ase(structure), - self.input.element_lst, - self.input.potential, - self.working_directory, - ] - for i, structure in enumerate(self._structure_lst) - ] - with Pool(cores=self.server.cores) as p: - results = p.map(function=get_elastic_constants, lst=input_para_lst) - - self._results_df = convert_elastic_constants_to_dataframe(results) - if not self._interactive_disable_log_file: - self._results_df.to_hdf( - self.project_hdf5._file_name, self.job_name + "/output/df" - ) - - def from_hdf(self, hdf=None, group_name=None): - super(LAMMPSElasticMPI, self).from_hdf(hdf=hdf, group_name=group_name) - with self.project_hdf5.open("output") as hdf5_output: - if "df" in hdf5_output.list_groups(): - self._results_df = pandas.read_hdf( - self.project_hdf5._file_name, self.job_name + "/output/df" - ) - - -class LAMMPSMinimizeElasticMPI(AtomisticStructureMasterNoFiles): - def __init__(self, project, job_name): - super(LAMMPSMinimizeElasticMPI, self).__init__(project, job_name) - - # input - self.input = DataContainer(table_name="custom_dict") - self.input.mole_fraction_dict_lst = [] - self.input.potential = "" - self.input.element_lst = [] - - @property - def results(self): - return self._results_df - - def to_hdf(self, hdf=None, group_name=None): - """ - - Args: - hdf: - group_name: - - Returns: - - """ - if not self._interactive_disable_log_file: - super(LAMMPSMinimizeElasticMPI, self).to_hdf(hdf=hdf, group_name=group_name) - with self.project_hdf5.open("input") as h5in: - self.input.to_hdf(h5in) - - def run_static(self): - self.project_hdf5.create_working_directory() - input_para_lst = [ - [ - i, - mole_fraction_dict, - pyiron_to_ase(self.structure), - self.input.element_lst, - self.input.potential, - self.working_directory, - ] - for i, mole_fraction_dict in enumerate(self.input.mole_fraction_dict_lst) - ] - with Pool(cores=self.server.cores) as p: - results = p.map(function=combined_function, lst=input_para_lst) - - self._results_df = convert_elastic_constants_to_dataframe(results) - if not self._interactive_disable_log_file: - self._results_df.to_hdf( - self.project_hdf5._file_name, self.job_name + "/output/df" - ) - - def from_hdf(self, hdf=None, group_name=None): - super(LAMMPSMinimizeElasticMPI, self).from_hdf(hdf=hdf, group_name=group_name) - with self.project_hdf5.open("output") as hdf5_output: - if "df" in hdf5_output.list_groups(): - self._results_df = pandas.read_hdf( - self.project_hdf5._file_name, self.job_name + "/output/df" - ) - - -def generate_sqs_structures(input_parameter): - i, mole_fraction_dict, structure_template, working_directory = input_parameter - - # import - import numpy as np - from pyiron_atomistics import Project, ase_to_pyiron, pyiron_to_ase - from pyiron_contrib.nofiles.sqs import SQSJobWithoutOutput - - # calculation - if len(mole_fraction_dict) > 1: - project = Project(working_directory) - job = project.create_job(SQSJobWithoutOutput, "sqs_" + str(i)) - job._interactive_disable_log_file = True - job.structure = ase_to_pyiron(structure_template) - job.input["mole_fractions"] = mole_fraction_dict - job.input["iterations"] = 1e6 - job.server.cores = 1 - job.run() - structure_next = pyiron_to_ase(job._lst_of_struct[-1]) - else: - # use ASE syntax - structure_next = structure_template.copy() - structure_next.symbols[:] = list(mole_fraction_dict.keys())[-1] - - # return value - return structure_next - - -def minimize_structure_with_lammps(input_parameter): - i, structure_next, potential, working_directory = input_parameter - - # import - from pyiron_atomistics import Project, ase_to_pyiron, pyiron_to_ase - from pyiron_contrib.nofiles.lammps import LammpsInteractiveWithoutOutput - from mpi4py import MPI - - # calculation - project = Project(working_directory) - lmp_mini1 = project.create_job( - LammpsInteractiveWithoutOutput, "lmp_mini_" + str(i), delete_existing_job=True - ) - lmp_mini1.structure = ase_to_pyiron(structure_next) - lmp_mini1.potential = potential - lmp_mini1.calc_minimize(pressure=0.0) - lmp_mini1.server.run_mode.interactive = True - lmp_mini1.interactive_mpi_communicator = MPI.COMM_SELF - lmp_mini1._interactive_disable_log_file = True # disable lammps.log - lmp_mini1.run() - lmp_mini1.interactive_close() - - # return value - return pyiron_to_ase(lmp_mini1.get_structure()) - - -def get_elastic_constants(input_para): - i, structure, element_lst, potential, working_directory = input_para - - # import - from pyiron_atomistics import Project, ase_to_pyiron - from pyiron_contrib.nofiles.lammps import LammpsInteractiveWithoutOutput - from pyiron_contrib.nofiles.elastic import ElasticMatrixJobWithoutFiles - from mpi4py import MPI - - # Elastic constants - project = Project(working_directory) - lmp_elastic = project.create_job( - LammpsInteractiveWithoutOutput, - "lmp_elastic_" + str(i), - delete_existing_job=True, - ) - lmp_elastic.structure = ase_to_pyiron(structure) - lmp_elastic.potential = potential - lmp_elastic.interactive_enforce_structure_reset = True - lmp_elastic.interactive_mpi_communicator = MPI.COMM_SELF - lmp_elastic.server.run_mode.interactive = True - lmp_elastic._interactive_disable_log_file = True # disable lammps.log - elastic = lmp_elastic.create_job( - ElasticMatrixJobWithoutFiles, "elastic_" + str(i), delete_existing_job=True - ) - elastic._interactive_disable_log_file = True # disable lammps.log - elastic.run() - - # return value - elastic_constants_lst = [ - elastic._data["C"][0][0], - elastic._data["C"][0][1], - elastic._data["C"][0][2], - elastic._data["C"][0][3], - elastic._data["C"][0][4], - elastic._data["C"][0][5], - elastic._data["C"][1][1], - elastic._data["C"][1][2], - elastic._data["C"][1][3], - elastic._data["C"][1][4], - elastic._data["C"][1][5], - elastic._data["C"][2][2], - elastic._data["C"][2][3], - elastic._data["C"][2][4], - elastic._data["C"][2][5], - elastic._data["C"][3][3], - elastic._data["C"][3][4], - elastic._data["C"][3][5], - elastic._data["C"][4][4], - elastic._data["C"][4][5], - elastic._data["C"][5][5], - ] - - conc_lst = [] - for el in element_lst: - if el in elastic.ref_job.structure.get_species_symbols(): - conc_lst.append( - sum( - elastic.ref_job.structure.indices - == elastic.ref_job.structure.get_species_symbols() - .tolist() - .index(el) - ) - / len(elastic.ref_job.structure.indices) - ) - else: - conc_lst.append(0.0) - - return elastic_constants_lst + conc_lst - - -def combined_function(input_parameter): - ( - i, - mole_fraction_dict, - structure_template, - element_lst, - potential, - working_directory, - ) = input_parameter - - # import - from pyiron_contrib.nofiles.master import ( - generate_sqs_structures, - minimize_structure_with_lammps, - get_elastic_constants, - ) - - # calculation - structure_next = generate_sqs_structures( - input_parameter=[i, mole_fraction_dict, structure_template, working_directory] - ) - structure = minimize_structure_with_lammps( - input_parameter=[i, structure_next, potential, working_directory] - ) - results = get_elastic_constants( - input_para=[i, structure, element_lst, potential, working_directory] - ) - - # return value - return results - - -def convert_elastic_constants_to_dataframe(results): - ( - c11_lst, - c12_lst, - c13_lst, - c14_lst, - c15_lst, - c16_lst, - c22_lst, - c23_lst, - c24_lst, - c25_lst, - c26_lst, - c33_lst, - c34_lst, - c35_lst, - c36_lst, - c44_lst, - c45_lst, - c46_lst, - c55_lst, - c56_lst, - c66_lst, - conc_Fe_lst, - conc_Ni_lst, - conc_Cr_lst, - conc_Co_lst, - conc_Cu_lst, - ) = zip(*results) - - return pandas.DataFrame( - { - "conc_Fe": conc_Fe_lst, - "conc_Ni": conc_Ni_lst, - "conc_Cr": conc_Cr_lst, - "conc_Co": conc_Co_lst, - "conc_Cu": conc_Cu_lst, - "C11": c11_lst, - "C12": c12_lst, - "C13": c13_lst, - "C14": c14_lst, - "C15": c15_lst, - "C16": c16_lst, - "C22": c22_lst, - "C23": c23_lst, - "C24": c24_lst, - "C25": c25_lst, - "C26": c26_lst, - "C33": c33_lst, - "C34": c34_lst, - "C35": c35_lst, - "C36": c36_lst, - "C44": c44_lst, - "C45": c45_lst, - "C46": c46_lst, - "C55": c55_lst, - "C56": c56_lst, - "C66": c66_lst, - } - ) diff --git a/pyiron_contrib/nofiles/murn.py b/pyiron_contrib/nofiles/murn.py deleted file mode 100644 index 751c5e3e3..000000000 --- a/pyiron_contrib/nofiles/murn.py +++ /dev/null @@ -1,175 +0,0 @@ -import pandas -import numpy as np -from pyiron_base import GenericMaster, state -from pyiron_atomistics.atomistics.master.murnaghan import Murnaghan - - -class MurnaghanWithoutFiles(Murnaghan): - def __init__(self, project, job_name): - if not state.database.database_is_disabled: - raise RuntimeError( - "To run a `Without` job, the database must first be disabled. Please " - "`from pyiron_base import state; " - "state.update({'disable_database': True})`, and try again." - ) - super(MurnaghanWithoutFiles, self).__init__(project, job_name) - self._interactive_disable_log_file = True - - @property - def child_project(self): - """ - :class:`.Project`: project which holds the created child jobs - """ - if not self._interactive_disable_log_file: - return super(MurnaghanWithoutFiles, self).child_project - else: - return self.project - - def to_hdf(self, hdf=None, group_name=None): - """ - Args: - hdf: - group_name: - Returns: - """ - if not self._interactive_disable_log_file: - super(MurnaghanWithoutFiles, self).to_hdf(hdf=hdf, group_name=group_name) - - def refresh_job_status(self): - if not self._interactive_disable_log_file: - super(MurnaghanWithoutFiles).refresh_job_status() - - def _store_fit_in_hdf(self, fit_dict): - # implemented in https://github.com/pyiron/pyiron_atomistics/pull/960 - with self.project_hdf5.open("input") as hdf5_input: - self.input.to_hdf(hdf5_input) - with self.project_hdf5.open("output") as hdf5: - hdf5["equilibrium_energy"] = fit_dict["energy_eq"] - hdf5["equilibrium_volume"] = fit_dict["volume_eq"] - hdf5["equilibrium_bulk_modulus"] = fit_dict["bulkmodul_eq"] - hdf5["equilibrium_b_prime"] = fit_dict["b_prime_eq"] - self._final_struct_to_hdf() - - def _fit_eos_general(self, vol_erg_dic=None, fittype="birchmurnaghan"): - self._set_fit_module(vol_erg_dic=vol_erg_dic) - fit_dict = self.fit_module.fit_eos_general(fittype=fittype) - self.input["fit_type"] = fit_dict["fit_type"] - self.input["fit_order"] = 0 - if not self._interactive_disable_log_file: - self._store_fit_in_hdf(fit_dict=fit_dict) - self.fit_dict = fit_dict - return fit_dict - - def poly_fit(self, fit_order=3, vol_erg_dic=None): - self._set_fit_module(vol_erg_dic=vol_erg_dic) - fit_dict = self.fit_module.fit_polynomial(fit_order=fit_order) - if fit_dict is None: - self._logger.warning("Minimum could not be found!") - elif not self._interactive_disable_log_file: - self.input["fit_type"] = fit_dict["fit_type"] - self.input["fit_order"] = fit_dict["fit_order"] - if not self._interactive_disable_log_file: - self._store_fit_in_hdf(fit_dict=fit_dict) - self.fit_dict = fit_dict - return fit_dict - - def collect_output(self): - if not self._interactive_disable_log_file: - super(MurnaghanWithoutFiles).collect_output() - elif self.ref_job.server.run_mode.interactive: - erg_lst = self.ref_job.output.energy_pot.copy() - vol_lst = self.ref_job.output.volume.copy() - arg_lst = np.argsort(vol_lst) - - self._output["volume"] = vol_lst[arg_lst] - self._output["energy"] = erg_lst[arg_lst] - if self.input["fit_type"] == "polynomial": - self.fit_polynomial(fit_order=self.input["fit_order"]) - else: - self._fit_eos_general(fittype=self.input["fit_type"]) - else: - raise ValueError("No files execution requires interactive jobs.") - - def _run_if_collect(self): - """ - Internal helper function the run if collect function is called when the job status is 'collect'. It collects - the simulation output using the standardized functions collect_output() and collect_logfiles(). Afterwards the - status is set to 'finished'. - """ - - if not self._interactive_disable_log_file: - super(MurnaghanWithoutFiles)._run_if_collect() - else: - self._logger.info( - "{}, status: {}, finished".format(self.job_info_str, self.status) - ) - self.collect_output() - self._logger.info( - "{}, status: {}, parallel master".format(self.job_info_str, self.status) - ) - self.update_master() - self.status.finished = True - # self.send_to_database() - - def append(self, job): - """ - Append a job to the GenericMaster - just like you would append an element to a list. - Args: - job (GenericJob): job to append - """ - if self.status.initialized and not job.status.initialized: - raise ValueError( - "GenericMaster requires reference jobs to have status initialized, rather than ", - job.status.string, - ) - if job.server.cores >= self.server.cores: - self.server.cores = job.server.cores - if job.job_name not in self._job_name_lst: - self._job_name_lst.append(job.job_name) - if not self._interactive_disable_log_file: - self._child_job_update_hdf(parent_job=self, child_job=job) - - def pop(self, i=-1): - """ - Pop a job from the GenericMaster - just like you would pop an element from a list - Args: - i (int): position of the job. (Default is last element, -1.) - Returns: - GenericJob: job - """ - job_name_to_return = self._job_name_lst[i] - job_to_return = self._load_all_child_jobs( - self._load_job_from_cache(job_name_to_return) - ) - del self._job_name_lst[i] - if not self._interactive_disable_log_file: - with self.project_hdf5.open("input") as hdf5_input: - hdf5_input["job_list"] = self._job_name_lst - job_to_return.relocate_hdf5() - if isinstance(job_to_return, GenericMaster): - for sub_job in job_to_return._job_object_dict.values(): - self._child_job_update_hdf(parent_job=job_to_return, child_job=sub_job) - job_to_return.status.initialized = True - return job_to_return - - def output_to_pandas(self, sort_by=None, h5_path="output"): - """ - Convert output of all child jobs to a pandas Dataframe object. - - Args: - sort_by (str): sort the output using pandas.DataFrame.sort_values(by=sort_by) - h5_path (str): select child output to include - default='output' - - Returns: - pandas.Dataframe: output as dataframe - """ - # TODO: The output to pandas function should no longer be required - if not self._interactive_disable_log_file: - super(MurnaghanWithoutFiles, self).output_to_pandas( - sort_by=sort_by, h5_path=h5_path - ) - else: - df = pandas.DataFrame(self._output) - if sort_by is not None: - df = df.sort_values(by=sort_by) - return df diff --git a/pyiron_contrib/nofiles/phonopy.py b/pyiron_contrib/nofiles/phonopy.py deleted file mode 100644 index 9a1ead993..000000000 --- a/pyiron_contrib/nofiles/phonopy.py +++ /dev/null @@ -1,179 +0,0 @@ -from pyiron_base import GenericMaster, state -from pyiron_atomistics.atomistics.master.phonopy import PhonopyJob - - -class PhonopyJobWithoutFiles(PhonopyJob): - def __init__(self, project, job_name): - if not state.database.database_is_disabled: - raise RuntimeError( - "To run a `Without` job, the database must first be disabled. Please " - "`from pyiron_base import state; " - "state.update({'disable_database': True})`, and try again." - ) - super(PhonopyJobWithoutFiles, self).__init__(project, job_name) - self._interactive_disable_log_file = True - - @property - def child_project(self): - """ - :class:`.Project`: project which holds the created child jobs - """ - if not self._interactive_disable_log_file: - return super(PhonopyJobWithoutFiles, self).child_project - else: - return self.project - - def to_hdf(self, hdf=None, group_name=None): - """ - Args: - hdf: - group_name: - Returns: - """ - if not self._interactive_disable_log_file: - super(PhonopyJobWithoutFiles, self).to_hdf(hdf=hdf, group_name=group_name) - - def refresh_job_status(self): - if not self._interactive_disable_log_file: - super(PhonopyJobWithoutFiles).refresh_job_status() - - def _run_if_collect(self): - """ - Internal helper function the run if collect function is called when the job status is 'collect'. It collects - the simulation output using the standardized functions collect_output() and collect_logfiles(). Afterwards the - status is set to 'finished'. - """ - - if not self._interactive_disable_log_file: - super(PhonopyJobWithoutFiles)._run_if_collect() - else: - self._logger.info( - "{}, status: {}, finished".format(self.job_info_str, self.status) - ) - self.collect_output() - self._logger.info( - "{}, status: {}, parallel master".format(self.job_info_str, self.status) - ) - self.update_master() - self.status.finished = True - # self.send_to_database() - - def collect_output(self): - """ - Returns: - """ - if ( - self.ref_job.server.run_mode.interactive - and self._interactive_disable_log_file - ): - forces_lst = self.ref_job.output.forces - elif self.ref_job.server.run_mode.interactive: - forces_lst = self.project_hdf5.inspect(self.child_ids[0])[ - "output/generic/forces" - ] - else: - pr_job = self.project_hdf5.project.open(self.job_name + "_hdf5") - forces_lst = [ - pr_job.inspect(job_name)["output/generic/forces"][-1] - for job_name in self._get_jobs_sorted() - ] - self.phonopy.set_forces(forces_lst) - self.phonopy.produce_force_constants( - fc_calculator=None if self.input["number_of_snapshots"] is None else "alm" - ) - self.phonopy.run_mesh(mesh=[self.input["dos_mesh"]] * 3) - mesh_dict = self.phonopy.get_mesh_dict() - self.phonopy.run_total_dos() - dos_dict = self.phonopy.get_total_dos_dict() - - if not self._interactive_disable_log_file: - self.to_hdf() - with self.project_hdf5.open("output") as hdf5_out: - hdf5_out["dos_total"] = dos_dict["total_dos"] - hdf5_out["dos_energies"] = dos_dict["frequency_points"] - hdf5_out["qpoints"] = mesh_dict["qpoints"] - hdf5_out["supercell_matrix"] = self._phonopy_supercell_matrix() - hdf5_out[ - "displacement_dataset" - ] = self.phonopy.get_displacement_dataset() - hdf5_out[ - "dynamical_matrix" - ] = self.phonopy.dynamical_matrix.get_dynamical_matrix() - hdf5_out["force_constants"] = self.phonopy.force_constants - - def append(self, job): - """ - Append a job to the GenericMaster - just like you would append an element to a list. - Args: - job (GenericJob): job to append - """ - if self.status.initialized and not job.status.initialized: - raise ValueError( - "GenericMaster requires reference jobs to have status initialized, rather than ", - job.status.string, - ) - if job.server.cores >= self.server.cores: - self.server.cores = job.server.cores - if job.job_name not in self._job_name_lst: - self._job_name_lst.append(job.job_name) - if not self._interactive_disable_log_file: - self._child_job_update_hdf(parent_job=self, child_job=job) - - def pop(self, i=-1): - """ - Pop a job from the GenericMaster - just like you would pop an element from a list - Args: - i (int): position of the job. (Default is last element, -1.) - Returns: - GenericJob: job - """ - job_name_to_return = self._job_name_lst[i] - job_to_return = self._load_all_child_jobs( - self._load_job_from_cache(job_name_to_return) - ) - del self._job_name_lst[i] - if not self._interactive_disable_log_file: - with self.project_hdf5.open("input") as hdf5_input: - hdf5_input["job_list"] = self._job_name_lst - job_to_return.relocate_hdf5() - if isinstance(job_to_return, GenericMaster): - for sub_job in job_to_return._job_object_dict.values(): - self._child_job_update_hdf(parent_job=job_to_return, child_job=sub_job) - job_to_return.status.initialized = True - return job_to_return - - def plot_dos(self, ax=None, *args, axis=None, **kwargs): - """ - Plot the DOS. - If "label" is present in `kwargs` a legend is added to the plot automatically. - Args: - axis (optional): matplotlib axis to use, if None create a new one - ax: deprecated alias for axis - *args: passed to `axis.plot` - **kwargs: passed to `axis.plot` - Returns: - matplotlib.axes._subplots.AxesSubplot: axis with the plot - """ - try: - import pylab as plt - except ImportError: - import matplotlib.pyplot as plt - if ax is not None and axis is None: - axis = ax - if axis is None: - _, axis = plt.subplots(1, 1) - if not self._interactive_disable_log_file: - axis.plot( - self["output/dos_energies"], self["output/dos_total"], *args, **kwargs - ) - else: - dos_dict = self.phonopy.get_total_dos_dict() - axis.plot( - dos_dict["frequency_points"], dos_dict["total_dos"], *args, **kwargs - ) - axis.set_xlabel("Frequency [THz]") - axis.set_ylabel("DOS") - axis.set_title("Phonon DOS vs Energy") - if "label" in kwargs: - axis.legend() - return ax diff --git a/pyiron_contrib/nofiles/sqs.py b/pyiron_contrib/nofiles/sqs.py deleted file mode 100644 index 4941cc1d6..000000000 --- a/pyiron_contrib/nofiles/sqs.py +++ /dev/null @@ -1,45 +0,0 @@ -from pyiron_atomistics.atomistics.job.sqs import SQSJob, get_sqs_structures -from pyiron_base import state - - -class SQSJobWithoutOutput(SQSJob): - def __init__(self, project, job_name): - if not state.database.database_is_disabled: - raise RuntimeError( - "To run a `Without` job, the database must first be disabled. Please " - "`from pyiron_base import state; " - "state.update({'disable_database': True})`, and try again." - ) - super(SQSJobWithoutOutput, self).__init__(project, job_name) - self._interactive_disable_log_file = True - - def to_hdf(self, hdf=None, group_name=None): - """ - - Args: - hdf: - group_name: - - Returns: - - """ - if not self._interactive_disable_log_file: - super(SQSJobWithoutOutput, self).to_hdf(hdf=hdf, group_name=group_name) - - def run_static(self): - if not self._interactive_disable_log_file: - super(SQSJobWithoutOutput, self).run_static() - else: - self._lst_of_struct, decmp, iterations, cycle_time = get_sqs_structures( - structure=self.structure, - mole_fractions={k: v for k, v in self.input.mole_fractions.items()}, - weights=self.input.weights, - objective=self.input.objective, - iterations=self.input.iterations, - output_structures=self.input.n_output_structures, - num_threads=self.server.cores, - ) - - def refresh_job_status(self): - if not self._interactive_disable_log_file: - super(SQSJobWithoutOutput, self).refresh_job_status() From 42967aef4479e9b64a2d29174f0050681552dc35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jan=C3=9Fen?= Date: Sat, 30 Sep 2023 09:02:09 +0200 Subject: [PATCH 680/756] remove tests --- tests/unit/no_files/__init__.py | 0 tests/unit/no_files/test_lammps.py | 16 ---------------- 2 files changed, 16 deletions(-) delete mode 100644 tests/unit/no_files/__init__.py delete mode 100644 tests/unit/no_files/test_lammps.py diff --git a/tests/unit/no_files/__init__.py b/tests/unit/no_files/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/unit/no_files/test_lammps.py b/tests/unit/no_files/test_lammps.py deleted file mode 100644 index 18edd048a..000000000 --- a/tests/unit/no_files/test_lammps.py +++ /dev/null @@ -1,16 +0,0 @@ -from pyiron_base._tests import TestWithCleanProject -import pyiron_contrib - - -class LammpsInteractiveWithoutOutput(TestWithCleanProject): - def test_clean_database_failure(self): - og_status = self.project.state.settings.configuration["disable_database"] - self.project.state.update({"disable_database": False}) - - with self.assertRaises(RuntimeError): - self.project.create.job.LammpsInteractiveWithoutOutput("foo") - - self.project.state.update({"disable_database": True}) - self.project.create.job.LammpsInteractiveWithoutOutput("foo") - - self.project.state.update({"disable_database": og_status}) From 3fb557d0af7c681f36dcc7a3e533674619132ca3 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Wed, 4 Oct 2023 08:50:56 +0200 Subject: [PATCH 681/756] versioneer update to 0.29 --- pyiron_contrib/_version.py | 319 +++++++--- versioneer.py | 1135 +++++++++++++++++++++++++----------- 2 files changed, 1034 insertions(+), 420 deletions(-) diff --git a/pyiron_contrib/_version.py b/pyiron_contrib/_version.py index aeb42d879..3dd52323a 100644 --- a/pyiron_contrib/_version.py +++ b/pyiron_contrib/_version.py @@ -4,8 +4,9 @@ # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. -# This file is released into the public domain. Generated by -# versioneer-0.18 (https://github.com/warner/python-versioneer) +# This file is released into the public domain. +# Generated by versioneer-0.29 +# https://github.com/python-versioneer/python-versioneer """Git implementation of _version.py.""" @@ -14,9 +15,11 @@ import re import subprocess import sys +from typing import Any, Callable, Dict, List, Optional, Tuple +import functools -def get_keywords(): +def get_keywords() -> Dict[str, str]: """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must @@ -32,8 +35,15 @@ def get_keywords(): class VersioneerConfig: """Container for Versioneer configuration parameters.""" + VCS: str + style: str + tag_prefix: str + parentdir_prefix: str + versionfile_source: str + verbose: bool -def get_config(): + +def get_config() -> VersioneerConfig: """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py @@ -41,7 +51,6 @@ def get_config(): cfg.VCS = "git" cfg.style = "pep440-pre" cfg.tag_prefix = "pyiron_contrib-" - cfg.parentdir_prefix = "pyiron_contrib" cfg.versionfile_source = "pyiron_contrib/_version.py" cfg.verbose = False @@ -52,14 +61,14 @@ class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" -LONG_VERSION_PY = {} -HANDLERS = {} +LONG_VERSION_PY: Dict[str, str] = {} +HANDLERS: Dict[str, Dict[str, Callable]] = {} -def register_vcs_handler(vcs, method): # decorator - """Decorator to mark a method as the handler for a particular VCS.""" +def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator + """Create decorator to mark a method as the handler of a VCS.""" - def decorate(f): + def decorate(f: Callable) -> Callable: """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} @@ -69,24 +78,39 @@ def decorate(f): return decorate -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): +def run_command( + commands: List[str], + args: List[str], + cwd: Optional[str] = None, + verbose: bool = False, + hide_stderr: bool = False, + env: Optional[Dict[str, str]] = None, +) -> Tuple[Optional[str], Optional[int]]: """Call the given command(s).""" assert isinstance(commands, list) - p = None - for c in commands: + process = None + + popen_kwargs: Dict[str, Any] = {} + if sys.platform == "win32": + # This hides the console window if pythonw.exe is used + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + popen_kwargs["startupinfo"] = startupinfo + + for command in commands: try: - dispcmd = str([c] + args) + dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen( - [c] + args, + process = subprocess.Popen( + [command] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None), + **popen_kwargs, ) break - except EnvironmentError: - e = sys.exc_info()[1] + except OSError as e: if e.errno == errno.ENOENT: continue if verbose: @@ -97,18 +121,20 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env= if verbose: print("unable to find command, tried %s" % (commands,)) return None, None - stdout = p.communicate()[0].strip() - if sys.version_info[0] >= 3: - stdout = stdout.decode() - if p.returncode != 0: + stdout = process.communicate()[0].strip().decode() + if process.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) - return None, p.returncode - return stdout, p.returncode + return None, process.returncode + return stdout, process.returncode -def versions_from_parentdir(parentdir_prefix, root, verbose): +def versions_from_parentdir( + parentdir_prefix: str, + root: str, + verbose: bool, +) -> Dict[str, Any]: """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both @@ -117,7 +143,7 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): """ rootdirs = [] - for i in range(3): + for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return { @@ -127,9 +153,8 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): "error": None, "date": None, } - else: - rootdirs.append(root) - root = os.path.dirname(root) # up a level + rootdirs.append(root) + root = os.path.dirname(root) # up a level if verbose: print( @@ -140,41 +165,48 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): @register_vcs_handler("git", "get_keywords") -def git_get_keywords(versionfile_abs): +def git_get_keywords(versionfile_abs: str) -> Dict[str, str]: """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. - keywords = {} + keywords: Dict[str, str] = {} try: - f = open(versionfile_abs, "r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["date"] = mo.group(1) - f.close() - except EnvironmentError: + with open(versionfile_abs, "r") as fobj: + for line in fobj: + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["date"] = mo.group(1) + except OSError: pass return keywords @register_vcs_handler("git", "keywords") -def git_versions_from_keywords(keywords, tag_prefix, verbose): +def git_versions_from_keywords( + keywords: Dict[str, str], + tag_prefix: str, + verbose: bool, +) -> Dict[str, Any]: """Get version information from git keywords.""" - if not keywords: - raise NotThisMethod("no keywords at all, weird") + if "refnames" not in keywords: + raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] + # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because @@ -187,11 +219,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = set([r.strip() for r in refnames.strip("()").split(",")]) + refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = set([r[len(TAG) :] for r in refs if r.startswith(TAG)]) + tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d @@ -200,7 +232,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r"\d", r)]) + tags = {r for r in refs if re.search(r"\d", r)} if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: @@ -209,6 +241,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix) :] + # Filter out refs that exactly match prefix or that don't start + # with a number once the prefix is stripped (mostly a concern + # when prefix is '') + if not re.match(r"\d", r): + continue if verbose: print("picking %s" % r) return { @@ -231,7 +268,9 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): @register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): +def git_pieces_from_vcs( + tag_prefix: str, root: str, verbose: bool, runner: Callable = run_command +) -> Dict[str, Any]: """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* @@ -242,7 +281,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) + # GIT_DIR can interfere with correct operation of Versioneer. + # It may be intended to be passed to the Versioneer-versioned project, + # but that should not change where we get our version from. + env = os.environ.copy() + env.pop("GIT_DIR", None) + runner = functools.partial(runner, env=env) + + _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %s not under git control" % root) @@ -250,7 +296,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = run_command( + describe_out, rc = runner( GITS, [ "describe", @@ -259,7 +305,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): "--always", "--long", "--match", - "%s*" % tag_prefix, + f"{tag_prefix}[[:digit:]]*", ], cwd=root, ) @@ -267,16 +313,48 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() - full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() - pieces = {} + pieces: Dict[str, Any] = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None + branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root) + # --abbrev-ref was added in git-1.6.3 + if rc != 0 or branch_name is None: + raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") + branch_name = branch_name.strip() + + if branch_name == "HEAD": + # If we aren't exactly on a branch, pick a branch which represents + # the current commit. If all else fails, we are on a branchless + # commit. + branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) + # --contains was added in git-1.5.4 + if rc != 0 or branches is None: + raise NotThisMethod("'git branch --contains' returned error") + branches = branches.split("\n") + + # Remove the first line if we're running detached + if "(" in branches[0]: + branches.pop(0) + + # Strip off the leading "* " from the list of branches. + branches = [branch[2:] for branch in branches] + if "master" in branches: + branch_name = "master" + elif not branches: + branch_name = None + else: + # Pick the first branch that is returned. Good or bad. + branch_name = branches[0] + + pieces["branch"] = branch_name + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out @@ -293,7 +371,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # TAG-NUM-gHEX mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe) if not mo: - # unparseable. Maybe git-describe is misbehaving? + # unparsable. Maybe git-describe is misbehaving? pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out return pieces @@ -319,26 +397,27 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) - pieces["distance"] = int(count_out) # total number of commits + out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) + pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[ - 0 - ].strip() + date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces -def plus_or_dot(pieces): +def plus_or_dot(pieces: Dict[str, Any]) -> str: """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" -def render_pep440(pieces): +def render_pep440(pieces: Dict[str, Any]) -> str: """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you @@ -362,23 +441,70 @@ def render_pep440(pieces): return rendered -def render_pep440_pre(pieces): - """TAG[.post.devDISTANCE] -- No -dirty. +def render_pep440_branch(pieces: Dict[str, Any]) -> str: + """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . + + The ".dev0" means not master branch. Note that .dev0 sorts backwards + (a feature branch will appear "older" than the master branch). Exceptions: - 1: no tags. 0.post.devDISTANCE + 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0" + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]: + """Split pep440 version string at the post-release segment. + + Returns the release segments before the post-release and the + post-release version number (or -1 if no post-release segment is present). + """ + vc = str.split(ver, ".post") + return vc[0], int(vc[1] or 0) if len(vc) == 2 else None + + +def render_pep440_pre(pieces: Dict[str, Any]) -> str: + """TAG[.postN.devDISTANCE] -- No -dirty. + + Exceptions: + 1: no tags. 0.post0.devDISTANCE + """ + if pieces["closest-tag"]: if pieces["distance"]: - rendered += ".post.dev%d" % pieces["distance"] + # update the post release segment + tag_version, post_version = pep440_split_post(pieces["closest-tag"]) + rendered = tag_version + if post_version is not None: + rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) + else: + rendered += ".post0.dev%d" % (pieces["distance"]) + else: + # no commits, use the tag as the version + rendered = pieces["closest-tag"] else: # exception #1 - rendered = "0.post.dev%d" % pieces["distance"] + rendered = "0.post0.dev%d" % pieces["distance"] return rendered -def render_pep440_post(pieces): +def render_pep440_post(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards @@ -405,12 +531,41 @@ def render_pep440_post(pieces): return rendered -def render_pep440_old(pieces): +def render_pep440_post_branch(pieces: Dict[str, Any]) -> str: + """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . + + The ".dev0" means not master branch. + + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%s" % pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def render_pep440_old(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. - Eexceptions: + Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: @@ -427,7 +582,7 @@ def render_pep440_old(pieces): return rendered -def render_git_describe(pieces): +def render_git_describe(pieces: Dict[str, Any]) -> str: """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. @@ -447,7 +602,7 @@ def render_git_describe(pieces): return rendered -def render_git_describe_long(pieces): +def render_git_describe_long(pieces: Dict[str, Any]) -> str: """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. @@ -467,7 +622,7 @@ def render_git_describe_long(pieces): return rendered -def render(pieces, style): +def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]: """Render the given version pieces into the requested style.""" if pieces["error"]: return { @@ -483,10 +638,14 @@ def render(pieces, style): if style == "pep440": rendered = render_pep440(pieces) + elif style == "pep440-branch": + rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) + elif style == "pep440-post-branch": + rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": @@ -505,7 +664,7 @@ def render(pieces, style): } -def get_versions(): +def get_versions() -> Dict[str, Any]: """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some @@ -525,7 +684,7 @@ def get_versions(): # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. - for i in cfg.versionfile_source.split("/"): + for _ in cfg.versionfile_source.split("/"): root = os.path.dirname(root) except NameError: return { diff --git a/versioneer.py b/versioneer.py index 88c91fb80..1e3753e63 100644 --- a/versioneer.py +++ b/versioneer.py @@ -1,5 +1,5 @@ -# Version: 0.18 +# Version: 0.29 """The Versioneer - like a rocketeer, but for versions. @@ -7,18 +7,14 @@ ============== * like a rocketeer, but for versions! -* https://github.com/warner/python-versioneer +* https://github.com/python-versioneer/python-versioneer * Brian Warner -* License: Public Domain -* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy -* [![Latest Version] -(https://pypip.in/version/versioneer/badge.svg?style=flat) -](https://pypi.python.org/pypi/versioneer/) -* [![Build Status] -(https://travis-ci.org/warner/python-versioneer.png?branch=master) -](https://travis-ci.org/warner/python-versioneer) - -This is a tool for managing a recorded version number in distutils-based +* License: Public Domain (Unlicense) +* Compatible with: Python 3.7, 3.8, 3.9, 3.10, 3.11 and pypy3 +* [![Latest Version][pypi-image]][pypi-url] +* [![Build Status][travis-image]][travis-url] + +This is a tool for managing a recorded version number in setuptools-based python projects. The goal is to remove the tedious and error-prone "update the embedded version string" step from your release process. Making a new release should be as easy as recording a new tag in your version-control @@ -27,9 +23,38 @@ ## Quick Install -* `pip install versioneer` to somewhere to your $PATH -* add a `[versioneer]` section to your setup.cfg (see below) -* run `versioneer install` in your source tree, commit the results +Versioneer provides two installation modes. The "classic" vendored mode installs +a copy of versioneer into your repository. The experimental build-time dependency mode +is intended to allow you to skip this step and simplify the process of upgrading. + +### Vendored mode + +* `pip install versioneer` to somewhere in your $PATH + * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is + available, so you can also use `conda install -c conda-forge versioneer` +* add a `[tool.versioneer]` section to your `pyproject.toml` or a + `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md)) + * Note that you will need to add `tomli; python_version < "3.11"` to your + build-time dependencies if you use `pyproject.toml` +* run `versioneer install --vendor` in your source tree, commit the results +* verify version information with `python setup.py version` + +### Build-time dependency mode + +* `pip install versioneer` to somewhere in your $PATH + * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is + available, so you can also use `conda install -c conda-forge versioneer` +* add a `[tool.versioneer]` section to your `pyproject.toml` or a + `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md)) +* add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`) + to the `requires` key of the `build-system` table in `pyproject.toml`: + ```toml + [build-system] + requires = ["setuptools", "versioneer[toml]"] + build-backend = "setuptools.build_meta" + ``` +* run `versioneer install --no-vendor` in your source tree, commit the results +* verify version information with `python setup.py version` ## Version Identifiers @@ -61,7 +86,7 @@ for example `git describe --tags --dirty --always` reports things like "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has -uncommitted changes. +uncommitted changes). The version identifier is used for multiple purposes: @@ -166,7 +191,7 @@ Some situations are known to cause problems for Versioneer. This details the most significant ones. More can be found on Github -[issues page](https://github.com/warner/python-versioneer/issues). +[issues page](https://github.com/python-versioneer/python-versioneer/issues). ### Subprojects @@ -180,7 +205,7 @@ `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI distributions (and upload multiple independently-installable tarballs). * Source trees whose main purpose is to contain a C library, but which also - provide bindings to Python (and perhaps other langauges) in subdirectories. + provide bindings to Python (and perhaps other languages) in subdirectories. Versioneer will look for `.git` in parent directories, and most operations should get the right version string. However `pip` and `setuptools` have bugs @@ -194,9 +219,9 @@ Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in some later version. -[Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking +[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking this issue. The discussion in -[PR #61](https://github.com/warner/python-versioneer/pull/61) describes the +[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the issue from the Versioneer side in more detail. [pip PR#3176](https://github.com/pypa/pip/pull/3176) and [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve @@ -224,31 +249,20 @@ cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into a different virtualenv), so this can be surprising. -[Bug #83](https://github.com/warner/python-versioneer/issues/83) describes +[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes this one, but upgrading to a newer version of setuptools should probably resolve it. -### Unicode version strings - -While Versioneer works (and is continually tested) with both Python 2 and -Python 3, it is not entirely consistent with bytes-vs-unicode distinctions. -Newer releases probably generate unicode version strings on py2. It's not -clear that this is wrong, but it may be surprising for applications when then -write these strings to a network connection or include them in bytes-oriented -APIs like cryptographic checksums. - -[Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates -this question. - ## Updating Versioneer To upgrade your project to a new release of Versioneer, do the following: * install the new Versioneer (`pip install -U versioneer` or equivalent) -* edit `setup.cfg`, if necessary, to include any new configuration settings - indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details. -* re-run `versioneer install` in your source tree, to replace +* edit `setup.cfg` and `pyproject.toml`, if necessary, + to include any new configuration settings indicated by the release notes. + See [UPGRADING](./UPGRADING.md) for details. +* re-run `versioneer install --[no-]vendor` in your source tree, to replace `SRC/_version.py` * commit any changed files @@ -265,35 +279,70 @@ direction and include code from all supported VCS systems, reducing the number of intermediate scripts. +## Similar projects + +* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time + dependency +* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of + versioneer +* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools + plugin ## License To make Versioneer easier to embed, all its code is dedicated to the public domain. The `_version.py` that it creates is also in the public domain. -Specifically, both are released under the Creative Commons "Public Domain -Dedication" license (CC0-1.0), as described in -https://creativecommons.org/publicdomain/zero/1.0/ . +Specifically, both are released under the "Unlicense", as described in +https://unlicense.org/. + +[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg +[pypi-url]: https://pypi.python.org/pypi/versioneer/ +[travis-image]: +https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg +[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer """ +# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring +# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements +# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error +# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with +# pylint:disable=attribute-defined-outside-init,too-many-arguments -from __future__ import print_function -try: - import configparser -except ImportError: - import ConfigParser as configparser +import configparser import errno import json import os import re import subprocess import sys +from pathlib import Path +from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union +from typing import NoReturn +import functools + +have_tomllib = True +if sys.version_info >= (3, 11): + import tomllib +else: + try: + import tomli as tomllib + except ImportError: + have_tomllib = False class VersioneerConfig: """Container for Versioneer configuration parameters.""" + VCS: str + style: str + tag_prefix: str + versionfile_source: str + versionfile_build: Optional[str] + parentdir_prefix: Optional[str] + verbose: Optional[bool] + -def get_root(): +def get_root() -> str: """Get the project root directory. We require that all commands are run from the project root, i.e. the @@ -301,13 +350,23 @@ def get_root(): """ root = os.path.realpath(os.path.abspath(os.getcwd())) setup_py = os.path.join(root, "setup.py") + pyproject_toml = os.path.join(root, "pyproject.toml") versioneer_py = os.path.join(root, "versioneer.py") - if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): + if not ( + os.path.exists(setup_py) + or os.path.exists(pyproject_toml) + or os.path.exists(versioneer_py) + ): # allow 'python path/to/setup.py COMMAND' root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) setup_py = os.path.join(root, "setup.py") + pyproject_toml = os.path.join(root, "pyproject.toml") versioneer_py = os.path.join(root, "versioneer.py") - if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): + if not ( + os.path.exists(setup_py) + or os.path.exists(pyproject_toml) + or os.path.exists(versioneer_py) + ): err = ("Versioneer was unable to run the project root directory. " "Versioneer requires setup.py to be executed from " "its immediate directory (like 'python setup.py COMMAND'), " @@ -321,43 +380,62 @@ def get_root(): # module-import table will cache the first one. So we can't use # os.path.dirname(__file__), as that will find whichever # versioneer.py was first imported, even in later projects. - me = os.path.realpath(os.path.abspath(__file__)) - me_dir = os.path.normcase(os.path.splitext(me)[0]) + my_path = os.path.realpath(os.path.abspath(__file__)) + me_dir = os.path.normcase(os.path.splitext(my_path)[0]) vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) - if me_dir != vsr_dir: + if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals(): print("Warning: build in %s is using versioneer.py from %s" - % (os.path.dirname(me), versioneer_py)) + % (os.path.dirname(my_path), versioneer_py)) except NameError: pass return root -def get_config_from_root(root): +def get_config_from_root(root: str) -> VersioneerConfig: """Read the project setup.cfg file to determine Versioneer config.""" - # This might raise EnvironmentError (if setup.cfg is missing), or + # This might raise OSError (if setup.cfg is missing), or # configparser.NoSectionError (if it lacks a [versioneer] section), or # configparser.NoOptionError (if it lacks "VCS="). See the docstring at # the top of versioneer.py for instructions on writing your setup.cfg . - setup_cfg = os.path.join(root, "setup.cfg") - parser = configparser.SafeConfigParser() - with open(setup_cfg, "r") as f: - parser.readfp(f) - VCS = parser.get("versioneer", "VCS") # mandatory - - def get(parser, name): - if parser.has_option("versioneer", name): - return parser.get("versioneer", name) - return None + root_pth = Path(root) + pyproject_toml = root_pth / "pyproject.toml" + setup_cfg = root_pth / "setup.cfg" + section: Union[Dict[str, Any], configparser.SectionProxy, None] = None + if pyproject_toml.exists() and have_tomllib: + try: + with open(pyproject_toml, 'rb') as fobj: + pp = tomllib.load(fobj) + section = pp['tool']['versioneer'] + except (tomllib.TOMLDecodeError, KeyError) as e: + print(f"Failed to load config from {pyproject_toml}: {e}") + print("Try to load it from setup.cfg") + if not section: + parser = configparser.ConfigParser() + with open(setup_cfg) as cfg_file: + parser.read_file(cfg_file) + parser.get("versioneer", "VCS") # raise error if missing + + section = parser["versioneer"] + + # `cast`` really shouldn't be used, but its simplest for the + # common VersioneerConfig users at the moment. We verify against + # `None` values elsewhere where it matters + cfg = VersioneerConfig() - cfg.VCS = VCS - cfg.style = get(parser, "style") or "" - cfg.versionfile_source = get(parser, "versionfile_source") - cfg.versionfile_build = get(parser, "versionfile_build") - cfg.tag_prefix = get(parser, "tag_prefix") - if cfg.tag_prefix in ("''", '""'): + cfg.VCS = section['VCS'] + cfg.style = section.get("style", "") + cfg.versionfile_source = cast(str, section.get("versionfile_source")) + cfg.versionfile_build = section.get("versionfile_build") + cfg.tag_prefix = cast(str, section.get("tag_prefix")) + if cfg.tag_prefix in ("''", '""', None): cfg.tag_prefix = "" - cfg.parentdir_prefix = get(parser, "parentdir_prefix") - cfg.verbose = get(parser, "verbose") + cfg.parentdir_prefix = section.get("parentdir_prefix") + if isinstance(section, configparser.SectionProxy): + # Make sure configparser translates to bool + cfg.verbose = section.getboolean("verbose") + else: + cfg.verbose = section.get("verbose") + return cfg @@ -366,37 +444,48 @@ class NotThisMethod(Exception): # these dictionaries contain VCS-specific tools -LONG_VERSION_PY = {} -HANDLERS = {} +LONG_VERSION_PY: Dict[str, str] = {} +HANDLERS: Dict[str, Dict[str, Callable]] = {} -def register_vcs_handler(vcs, method): # decorator - """Decorator to mark a method as the handler for a particular VCS.""" - def decorate(f): +def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator + """Create decorator to mark a method as the handler of a VCS.""" + def decorate(f: Callable) -> Callable: """Store f in HANDLERS[vcs][method].""" - if vcs not in HANDLERS: - HANDLERS[vcs] = {} - HANDLERS[vcs][method] = f + HANDLERS.setdefault(vcs, {})[method] = f return f return decorate -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, - env=None): +def run_command( + commands: List[str], + args: List[str], + cwd: Optional[str] = None, + verbose: bool = False, + hide_stderr: bool = False, + env: Optional[Dict[str, str]] = None, +) -> Tuple[Optional[str], Optional[int]]: """Call the given command(s).""" assert isinstance(commands, list) - p = None - for c in commands: + process = None + + popen_kwargs: Dict[str, Any] = {} + if sys.platform == "win32": + # This hides the console window if pythonw.exe is used + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + popen_kwargs["startupinfo"] = startupinfo + + for command in commands: try: - dispcmd = str([c] + args) + dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) + process = subprocess.Popen([command] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None), **popen_kwargs) break - except EnvironmentError: - e = sys.exc_info()[1] + except OSError as e: if e.errno == errno.ENOENT: continue if verbose: @@ -407,26 +496,25 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, if verbose: print("unable to find command, tried %s" % (commands,)) return None, None - stdout = p.communicate()[0].strip() - if sys.version_info[0] >= 3: - stdout = stdout.decode() - if p.returncode != 0: + stdout = process.communicate()[0].strip().decode() + if process.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) - return None, p.returncode - return stdout, p.returncode + return None, process.returncode + return stdout, process.returncode -LONG_VERSION_PY['git'] = ''' +LONG_VERSION_PY['git'] = r''' # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. -# This file is released into the public domain. Generated by -# versioneer-0.18 (https://github.com/warner/python-versioneer) +# This file is released into the public domain. +# Generated by versioneer-0.29 +# https://github.com/python-versioneer/python-versioneer """Git implementation of _version.py.""" @@ -435,9 +523,11 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, import re import subprocess import sys +from typing import Any, Callable, Dict, List, Optional, Tuple +import functools -def get_keywords(): +def get_keywords() -> Dict[str, str]: """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must @@ -453,8 +543,15 @@ def get_keywords(): class VersioneerConfig: """Container for Versioneer configuration parameters.""" + VCS: str + style: str + tag_prefix: str + parentdir_prefix: str + versionfile_source: str + verbose: bool + -def get_config(): +def get_config() -> VersioneerConfig: """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py @@ -472,13 +569,13 @@ class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" -LONG_VERSION_PY = {} -HANDLERS = {} +LONG_VERSION_PY: Dict[str, str] = {} +HANDLERS: Dict[str, Dict[str, Callable]] = {} -def register_vcs_handler(vcs, method): # decorator - """Decorator to mark a method as the handler for a particular VCS.""" - def decorate(f): +def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator + """Create decorator to mark a method as the handler of a VCS.""" + def decorate(f: Callable) -> Callable: """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} @@ -487,22 +584,35 @@ def decorate(f): return decorate -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, - env=None): +def run_command( + commands: List[str], + args: List[str], + cwd: Optional[str] = None, + verbose: bool = False, + hide_stderr: bool = False, + env: Optional[Dict[str, str]] = None, +) -> Tuple[Optional[str], Optional[int]]: """Call the given command(s).""" assert isinstance(commands, list) - p = None - for c in commands: + process = None + + popen_kwargs: Dict[str, Any] = {} + if sys.platform == "win32": + # This hides the console window if pythonw.exe is used + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + popen_kwargs["startupinfo"] = startupinfo + + for command in commands: try: - dispcmd = str([c] + args) + dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) + process = subprocess.Popen([command] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None), **popen_kwargs) break - except EnvironmentError: - e = sys.exc_info()[1] + except OSError as e: if e.errno == errno.ENOENT: continue if verbose: @@ -513,18 +623,20 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, if verbose: print("unable to find command, tried %%s" %% (commands,)) return None, None - stdout = p.communicate()[0].strip() - if sys.version_info[0] >= 3: - stdout = stdout.decode() - if p.returncode != 0: + stdout = process.communicate()[0].strip().decode() + if process.returncode != 0: if verbose: print("unable to run %%s (error)" %% dispcmd) print("stdout was %%s" %% stdout) - return None, p.returncode - return stdout, p.returncode + return None, process.returncode + return stdout, process.returncode -def versions_from_parentdir(parentdir_prefix, root, verbose): +def versions_from_parentdir( + parentdir_prefix: str, + root: str, + verbose: bool, +) -> Dict[str, Any]: """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both @@ -533,15 +645,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): """ rootdirs = [] - for i in range(3): + for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} - else: - rootdirs.append(root) - root = os.path.dirname(root) # up a level + rootdirs.append(root) + root = os.path.dirname(root) # up a level if verbose: print("Tried directories %%s but none started with prefix %%s" %% @@ -550,41 +661,48 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): @register_vcs_handler("git", "get_keywords") -def git_get_keywords(versionfile_abs): +def git_get_keywords(versionfile_abs: str) -> Dict[str, str]: """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. - keywords = {} + keywords: Dict[str, str] = {} try: - f = open(versionfile_abs, "r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["date"] = mo.group(1) - f.close() - except EnvironmentError: + with open(versionfile_abs, "r") as fobj: + for line in fobj: + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["date"] = mo.group(1) + except OSError: pass return keywords @register_vcs_handler("git", "keywords") -def git_versions_from_keywords(keywords, tag_prefix, verbose): +def git_versions_from_keywords( + keywords: Dict[str, str], + tag_prefix: str, + verbose: bool, +) -> Dict[str, Any]: """Get version information from git keywords.""" - if not keywords: - raise NotThisMethod("no keywords at all, weird") + if "refnames" not in keywords: + raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] + # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because @@ -597,11 +715,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = set([r.strip() for r in refnames.strip("()").split(",")]) + refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %%d @@ -610,7 +728,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) + tags = {r for r in refs if re.search(r'\d', r)} if verbose: print("discarding '%%s', no digits" %% ",".join(refs - tags)) if verbose: @@ -619,6 +737,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] + # Filter out refs that exactly match prefix or that don't start + # with a number once the prefix is stripped (mostly a concern + # when prefix is '') + if not re.match(r'\d', r): + continue if verbose: print("picking %%s" %% r) return {"version": r, @@ -634,7 +757,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): @register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): +def git_pieces_from_vcs( + tag_prefix: str, + root: str, + verbose: bool, + runner: Callable = run_command +) -> Dict[str, Any]: """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* @@ -645,8 +773,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) + # GIT_DIR can interfere with correct operation of Versioneer. + # It may be intended to be passed to the Versioneer-versioned project, + # but that should not change where we get our version from. + env = os.environ.copy() + env.pop("GIT_DIR", None) + runner = functools.partial(runner, env=env) + + _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %%s not under git control" %% root) @@ -654,24 +789,57 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%%s*" %% tag_prefix], - cwd=root) + describe_out, rc = runner(GITS, [ + "describe", "--tags", "--dirty", "--always", "--long", + "--match", f"{tag_prefix}[[:digit:]]*" + ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() - full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() - pieces = {} + pieces: Dict[str, Any] = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None + branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], + cwd=root) + # --abbrev-ref was added in git-1.6.3 + if rc != 0 or branch_name is None: + raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") + branch_name = branch_name.strip() + + if branch_name == "HEAD": + # If we aren't exactly on a branch, pick a branch which represents + # the current commit. If all else fails, we are on a branchless + # commit. + branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) + # --contains was added in git-1.5.4 + if rc != 0 or branches is None: + raise NotThisMethod("'git branch --contains' returned error") + branches = branches.split("\n") + + # Remove the first line if we're running detached + if "(" in branches[0]: + branches.pop(0) + + # Strip off the leading "* " from the list of branches. + branches = [branch[2:] for branch in branches] + if "master" in branches: + branch_name = "master" + elif not branches: + branch_name = None + else: + # Pick the first branch that is returned. Good or bad. + branch_name = branches[0] + + pieces["branch"] = branch_name + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out @@ -688,7 +856,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: - # unparseable. Maybe git-describe is misbehaving? + # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%%s'" %% describe_out) return pieces @@ -713,26 +881,27 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) - pieces["distance"] = int(count_out) # total number of commits + out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) + pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"], - cwd=root)[0].strip() + date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip() + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces -def plus_or_dot(pieces): +def plus_or_dot(pieces: Dict[str, Any]) -> str: """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" -def render_pep440(pieces): +def render_pep440(pieces: Dict[str, Any]) -> str: """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you @@ -757,23 +926,71 @@ def render_pep440(pieces): return rendered -def render_pep440_pre(pieces): - """TAG[.post.devDISTANCE] -- No -dirty. +def render_pep440_branch(pieces: Dict[str, Any]) -> str: + """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . + + The ".dev0" means not master branch. Note that .dev0 sorts backwards + (a feature branch will appear "older" than the master branch). Exceptions: - 1: no tags. 0.post.devDISTANCE + 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0" + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+untagged.%%d.g%%s" %% (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]: + """Split pep440 version string at the post-release segment. + + Returns the release segments before the post-release and the + post-release version number (or -1 if no post-release segment is present). + """ + vc = str.split(ver, ".post") + return vc[0], int(vc[1] or 0) if len(vc) == 2 else None + + +def render_pep440_pre(pieces: Dict[str, Any]) -> str: + """TAG[.postN.devDISTANCE] -- No -dirty. + + Exceptions: + 1: no tags. 0.post0.devDISTANCE + """ + if pieces["closest-tag"]: if pieces["distance"]: - rendered += ".post.dev%%d" %% pieces["distance"] + # update the post release segment + tag_version, post_version = pep440_split_post(pieces["closest-tag"]) + rendered = tag_version + if post_version is not None: + rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"]) + else: + rendered += ".post0.dev%%d" %% (pieces["distance"]) + else: + # no commits, use the tag as the version + rendered = pieces["closest-tag"] else: # exception #1 - rendered = "0.post.dev%%d" %% pieces["distance"] + rendered = "0.post0.dev%%d" %% pieces["distance"] return rendered -def render_pep440_post(pieces): +def render_pep440_post(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards @@ -800,12 +1017,41 @@ def render_pep440_post(pieces): return rendered -def render_pep440_old(pieces): +def render_pep440_post_branch(pieces: Dict[str, Any]) -> str: + """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . + + The ".dev0" means not master branch. + + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%%d" %% pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%%s" %% pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0.post%%d" %% pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+g%%s" %% pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def render_pep440_old(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. - Eexceptions: + Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: @@ -822,7 +1068,7 @@ def render_pep440_old(pieces): return rendered -def render_git_describe(pieces): +def render_git_describe(pieces: Dict[str, Any]) -> str: """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. @@ -842,7 +1088,7 @@ def render_git_describe(pieces): return rendered -def render_git_describe_long(pieces): +def render_git_describe_long(pieces: Dict[str, Any]) -> str: """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. @@ -862,7 +1108,7 @@ def render_git_describe_long(pieces): return rendered -def render(pieces, style): +def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]: """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", @@ -876,10 +1122,14 @@ def render(pieces, style): if style == "pep440": rendered = render_pep440(pieces) + elif style == "pep440-branch": + rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) + elif style == "pep440-post-branch": + rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": @@ -894,7 +1144,7 @@ def render(pieces, style): "date": pieces.get("date")} -def get_versions(): +def get_versions() -> Dict[str, Any]: """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some @@ -915,7 +1165,7 @@ def get_versions(): # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. - for i in cfg.versionfile_source.split('/'): + for _ in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, @@ -942,41 +1192,48 @@ def get_versions(): @register_vcs_handler("git", "get_keywords") -def git_get_keywords(versionfile_abs): +def git_get_keywords(versionfile_abs: str) -> Dict[str, str]: """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. - keywords = {} + keywords: Dict[str, str] = {} try: - f = open(versionfile_abs, "r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["date"] = mo.group(1) - f.close() - except EnvironmentError: + with open(versionfile_abs, "r") as fobj: + for line in fobj: + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["date"] = mo.group(1) + except OSError: pass return keywords @register_vcs_handler("git", "keywords") -def git_versions_from_keywords(keywords, tag_prefix, verbose): +def git_versions_from_keywords( + keywords: Dict[str, str], + tag_prefix: str, + verbose: bool, +) -> Dict[str, Any]: """Get version information from git keywords.""" - if not keywords: - raise NotThisMethod("no keywords at all, weird") + if "refnames" not in keywords: + raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] + # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because @@ -989,11 +1246,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = set([r.strip() for r in refnames.strip("()").split(",")]) + refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d @@ -1002,7 +1259,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) + tags = {r for r in refs if re.search(r'\d', r)} if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: @@ -1011,6 +1268,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] + # Filter out refs that exactly match prefix or that don't start + # with a number once the prefix is stripped (mostly a concern + # when prefix is '') + if not re.match(r'\d', r): + continue if verbose: print("picking %s" % r) return {"version": r, @@ -1026,7 +1288,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): @register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): +def git_pieces_from_vcs( + tag_prefix: str, + root: str, + verbose: bool, + runner: Callable = run_command +) -> Dict[str, Any]: """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* @@ -1037,8 +1304,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) + # GIT_DIR can interfere with correct operation of Versioneer. + # It may be intended to be passed to the Versioneer-versioned project, + # but that should not change where we get our version from. + env = os.environ.copy() + env.pop("GIT_DIR", None) + runner = functools.partial(runner, env=env) + + _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %s not under git control" % root) @@ -1046,24 +1320,57 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%s*" % tag_prefix], - cwd=root) + describe_out, rc = runner(GITS, [ + "describe", "--tags", "--dirty", "--always", "--long", + "--match", f"{tag_prefix}[[:digit:]]*" + ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() - full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() - pieces = {} + pieces: Dict[str, Any] = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None + branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], + cwd=root) + # --abbrev-ref was added in git-1.6.3 + if rc != 0 or branch_name is None: + raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") + branch_name = branch_name.strip() + + if branch_name == "HEAD": + # If we aren't exactly on a branch, pick a branch which represents + # the current commit. If all else fails, we are on a branchless + # commit. + branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) + # --contains was added in git-1.5.4 + if rc != 0 or branches is None: + raise NotThisMethod("'git branch --contains' returned error") + branches = branches.split("\n") + + # Remove the first line if we're running detached + if "(" in branches[0]: + branches.pop(0) + + # Strip off the leading "* " from the list of branches. + branches = [branch[2:] for branch in branches] + if "master" in branches: + branch_name = "master" + elif not branches: + branch_name = None + else: + # Pick the first branch that is returned. Good or bad. + branch_name = branches[0] + + pieces["branch"] = branch_name + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out @@ -1080,7 +1387,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: - # unparseable. Maybe git-describe is misbehaving? + # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces @@ -1105,19 +1412,20 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) - pieces["distance"] = int(count_out) # total number of commits + out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) + pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], - cwd=root)[0].strip() + date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() + # Use only the last line. Previous lines may contain GPG signature + # information. + date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces -def do_vcs_install(manifest_in, versionfile_source, ipy): +def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None: """Git-specific installation logic for Versioneer. For Git, this means creating/changing .gitattributes to mark _version.py @@ -1126,36 +1434,40 @@ def do_vcs_install(manifest_in, versionfile_source, ipy): GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - files = [manifest_in, versionfile_source] + files = [versionfile_source] if ipy: files.append(ipy) - try: - me = __file__ - if me.endswith(".pyc") or me.endswith(".pyo"): - me = os.path.splitext(me)[0] + ".py" - versioneer_file = os.path.relpath(me) - except NameError: - versioneer_file = "versioneer.py" - files.append(versioneer_file) + if "VERSIONEER_PEP518" not in globals(): + try: + my_path = __file__ + if my_path.endswith((".pyc", ".pyo")): + my_path = os.path.splitext(my_path)[0] + ".py" + versioneer_file = os.path.relpath(my_path) + except NameError: + versioneer_file = "versioneer.py" + files.append(versioneer_file) present = False try: - f = open(".gitattributes", "r") - for line in f.readlines(): - if line.strip().startswith(versionfile_source): - if "export-subst" in line.strip().split()[1:]: - present = True - f.close() - except EnvironmentError: + with open(".gitattributes", "r") as fobj: + for line in fobj: + if line.strip().startswith(versionfile_source): + if "export-subst" in line.strip().split()[1:]: + present = True + break + except OSError: pass if not present: - f = open(".gitattributes", "a+") - f.write("%s export-subst\n" % versionfile_source) - f.close() + with open(".gitattributes", "a+") as fobj: + fobj.write(f"{versionfile_source} export-subst\n") files.append(".gitattributes") run_command(GITS, ["add", "--"] + files) -def versions_from_parentdir(parentdir_prefix, root, verbose): +def versions_from_parentdir( + parentdir_prefix: str, + root: str, + verbose: bool, +) -> Dict[str, Any]: """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both @@ -1164,15 +1476,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): """ rootdirs = [] - for i in range(3): + for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} - else: - rootdirs.append(root) - root = os.path.dirname(root) # up a level + rootdirs.append(root) + root = os.path.dirname(root) # up a level if verbose: print("Tried directories %s but none started with prefix %s" % @@ -1181,7 +1492,7 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): SHORT_VERSION_PY = """ -# This file was generated by 'versioneer.py' (0.18) from +# This file was generated by 'versioneer.py' (0.29) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. @@ -1198,12 +1509,12 @@ def get_versions(): """ -def versions_from_file(filename): +def versions_from_file(filename: str) -> Dict[str, Any]: """Try to determine the version from _version.py if present.""" try: with open(filename) as f: contents = f.read() - except EnvironmentError: + except OSError: raise NotThisMethod("unable to read _version.py") mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) @@ -1215,9 +1526,8 @@ def versions_from_file(filename): return json.loads(mo.group(1)) -def write_to_version_file(filename, versions): +def write_to_version_file(filename: str, versions: Dict[str, Any]) -> None: """Write the given version number to the given _version.py file.""" - os.unlink(filename) contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": ")) with open(filename, "w") as f: @@ -1226,14 +1536,14 @@ def write_to_version_file(filename, versions): print("set %s to '%s'" % (filename, versions["version"])) -def plus_or_dot(pieces): +def plus_or_dot(pieces: Dict[str, Any]) -> str: """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" -def render_pep440(pieces): +def render_pep440(pieces: Dict[str, Any]) -> str: """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you @@ -1258,23 +1568,71 @@ def render_pep440(pieces): return rendered -def render_pep440_pre(pieces): - """TAG[.post.devDISTANCE] -- No -dirty. +def render_pep440_branch(pieces: Dict[str, Any]) -> str: + """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . + + The ".dev0" means not master branch. Note that .dev0 sorts backwards + (a feature branch will appear "older" than the master branch). Exceptions: - 1: no tags. 0.post.devDISTANCE + 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0" + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]: + """Split pep440 version string at the post-release segment. + + Returns the release segments before the post-release and the + post-release version number (or -1 if no post-release segment is present). + """ + vc = str.split(ver, ".post") + return vc[0], int(vc[1] or 0) if len(vc) == 2 else None + + +def render_pep440_pre(pieces: Dict[str, Any]) -> str: + """TAG[.postN.devDISTANCE] -- No -dirty. + + Exceptions: + 1: no tags. 0.post0.devDISTANCE + """ + if pieces["closest-tag"]: if pieces["distance"]: - rendered += ".post.dev%d" % pieces["distance"] + # update the post release segment + tag_version, post_version = pep440_split_post(pieces["closest-tag"]) + rendered = tag_version + if post_version is not None: + rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) + else: + rendered += ".post0.dev%d" % (pieces["distance"]) + else: + # no commits, use the tag as the version + rendered = pieces["closest-tag"] else: # exception #1 - rendered = "0.post.dev%d" % pieces["distance"] + rendered = "0.post0.dev%d" % pieces["distance"] return rendered -def render_pep440_post(pieces): +def render_pep440_post(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards @@ -1301,12 +1659,41 @@ def render_pep440_post(pieces): return rendered -def render_pep440_old(pieces): +def render_pep440_post_branch(pieces: Dict[str, Any]) -> str: + """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . + + The ".dev0" means not master branch. + + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%s" % pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def render_pep440_old(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. - Eexceptions: + Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: @@ -1323,7 +1710,7 @@ def render_pep440_old(pieces): return rendered -def render_git_describe(pieces): +def render_git_describe(pieces: Dict[str, Any]) -> str: """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. @@ -1343,7 +1730,7 @@ def render_git_describe(pieces): return rendered -def render_git_describe_long(pieces): +def render_git_describe_long(pieces: Dict[str, Any]) -> str: """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. @@ -1363,7 +1750,7 @@ def render_git_describe_long(pieces): return rendered -def render(pieces, style): +def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]: """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", @@ -1377,10 +1764,14 @@ def render(pieces, style): if style == "pep440": rendered = render_pep440(pieces) + elif style == "pep440-branch": + rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) + elif style == "pep440-post-branch": + rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": @@ -1399,7 +1790,7 @@ class VersioneerBadRootError(Exception): """The project root directory is unknown or missing key files.""" -def get_versions(verbose=False): +def get_versions(verbose: bool = False) -> Dict[str, Any]: """Get the project version from whatever source is available. Returns dict with two keys: 'version' and 'full'. @@ -1414,7 +1805,7 @@ def get_versions(verbose=False): assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" handlers = HANDLERS.get(cfg.VCS) assert handlers, "unrecognized VCS '%s'" % cfg.VCS - verbose = verbose or cfg.verbose + verbose = verbose or bool(cfg.verbose) # `bool()` used to avoid `None` assert cfg.versionfile_source is not None, \ "please set versioneer.versionfile_source" assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" @@ -1475,13 +1866,17 @@ def get_versions(verbose=False): "date": None} -def get_version(): +def get_version() -> str: """Get the short version string for this project.""" return get_versions()["version"] -def get_cmdclass(): - """Get the custom setuptools/distutils subclasses used by Versioneer.""" +def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None): + """Get the custom setuptools subclasses used by Versioneer. + + If the package uses a different cmdclass (e.g. one from numpy), it + should be provide as an argument. + """ if "versioneer" in sys.modules: del sys.modules["versioneer"] # this fixes the "python setup.py develop" case (also 'install' and @@ -1495,25 +1890,25 @@ def get_cmdclass(): # parent is protected against the child's "import versioneer". By # removing ourselves from sys.modules here, before the child build # happens, we protect the child from the parent's versioneer too. - # Also see https://github.com/warner/python-versioneer/issues/52 + # Also see https://github.com/python-versioneer/python-versioneer/issues/52 - cmds = {} + cmds = {} if cmdclass is None else cmdclass.copy() - # we add "version" to both distutils and setuptools - from distutils.core import Command + # we add "version" to setuptools + from setuptools import Command class cmd_version(Command): description = "report generated version string" - user_options = [] - boolean_options = [] + user_options: List[Tuple[str, str, str]] = [] + boolean_options: List[str] = [] - def initialize_options(self): + def initialize_options(self) -> None: pass - def finalize_options(self): + def finalize_options(self) -> None: pass - def run(self): + def run(self) -> None: vers = get_versions(verbose=True) print("Version: %s" % vers["version"]) print(" full-revisionid: %s" % vers.get("full-revisionid")) @@ -1523,7 +1918,7 @@ def run(self): print(" error: %s" % vers["error"]) cmds["version"] = cmd_version - # we override "build_py" in both distutils and setuptools + # we override "build_py" in setuptools # # most invocation pathways end up running build_py: # distutils/build -> build_py @@ -1538,18 +1933,25 @@ def run(self): # then does setup.py bdist_wheel, or sometimes setup.py install # setup.py egg_info -> ? + # pip install -e . and setuptool/editable_wheel will invoke build_py + # but the build_py command is not expected to copy any files. + # we override different "build_py" commands for both environments - if "setuptools" in sys.modules: - from setuptools.command.build_py import build_py as _build_py + if 'build_py' in cmds: + _build_py: Any = cmds['build_py'] else: - from distutils.command.build_py import build_py as _build_py + from setuptools.command.build_py import build_py as _build_py class cmd_build_py(_build_py): - def run(self): + def run(self) -> None: root = get_root() cfg = get_config_from_root(root) versions = get_versions() _build_py.run(self) + if getattr(self, "editable_mode", False): + # During editable installs `.py` and data files are + # not copied to build_lib + return # now locate _version.py in the new build/ directory and replace # it with an updated value if cfg.versionfile_build: @@ -1559,8 +1961,40 @@ def run(self): write_to_version_file(target_versionfile, versions) cmds["build_py"] = cmd_build_py + if 'build_ext' in cmds: + _build_ext: Any = cmds['build_ext'] + else: + from setuptools.command.build_ext import build_ext as _build_ext + + class cmd_build_ext(_build_ext): + def run(self) -> None: + root = get_root() + cfg = get_config_from_root(root) + versions = get_versions() + _build_ext.run(self) + if self.inplace: + # build_ext --inplace will only build extensions in + # build/lib<..> dir with no _version.py to write to. + # As in place builds will already have a _version.py + # in the module dir, we do not need to write one. + return + # now locate _version.py in the new build/ directory and replace + # it with an updated value + if not cfg.versionfile_build: + return + target_versionfile = os.path.join(self.build_lib, + cfg.versionfile_build) + if not os.path.exists(target_versionfile): + print(f"Warning: {target_versionfile} does not exist, skipping " + "version update. This can happen if you are running build_ext " + "without first running build_py.") + return + print("UPDATING %s" % target_versionfile) + write_to_version_file(target_versionfile, versions) + cmds["build_ext"] = cmd_build_ext + if "cx_Freeze" in sys.modules: # cx_freeze enabled? - from cx_Freeze.dist import build_exe as _build_exe + from cx_Freeze.dist import build_exe as _build_exe # type: ignore # nczeczulin reports that py2exe won't like the pep440-style string # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. # setup(console=[{ @@ -1569,7 +2003,7 @@ def run(self): # ... class cmd_build_exe(_build_exe): - def run(self): + def run(self) -> None: root = get_root() cfg = get_config_from_root(root) versions = get_versions() @@ -1593,12 +2027,12 @@ def run(self): if 'py2exe' in sys.modules: # py2exe enabled? try: - from py2exe.distutils_buildexe import py2exe as _py2exe # py3 + from py2exe.setuptools_buildexe import py2exe as _py2exe # type: ignore except ImportError: - from py2exe.build_exe import py2exe as _py2exe # py2 + from py2exe.distutils_buildexe import py2exe as _py2exe # type: ignore class cmd_py2exe(_py2exe): - def run(self): + def run(self) -> None: root = get_root() cfg = get_config_from_root(root) versions = get_versions() @@ -1619,14 +2053,51 @@ def run(self): }) cmds["py2exe"] = cmd_py2exe + # sdist farms its file list building out to egg_info + if 'egg_info' in cmds: + _egg_info: Any = cmds['egg_info'] + else: + from setuptools.command.egg_info import egg_info as _egg_info + + class cmd_egg_info(_egg_info): + def find_sources(self) -> None: + # egg_info.find_sources builds the manifest list and writes it + # in one shot + super().find_sources() + + # Modify the filelist and normalize it + root = get_root() + cfg = get_config_from_root(root) + self.filelist.append('versioneer.py') + if cfg.versionfile_source: + # There are rare cases where versionfile_source might not be + # included by default, so we must be explicit + self.filelist.append(cfg.versionfile_source) + self.filelist.sort() + self.filelist.remove_duplicates() + + # The write method is hidden in the manifest_maker instance that + # generated the filelist and was thrown away + # We will instead replicate their final normalization (to unicode, + # and POSIX-style paths) + from setuptools import unicode_utils + normalized = [unicode_utils.filesys_decode(f).replace(os.sep, '/') + for f in self.filelist.files] + + manifest_filename = os.path.join(self.egg_info, 'SOURCES.txt') + with open(manifest_filename, 'w') as fobj: + fobj.write('\n'.join(normalized)) + + cmds['egg_info'] = cmd_egg_info + # we override different "sdist" commands for both environments - if "setuptools" in sys.modules: - from setuptools.command.sdist import sdist as _sdist + if 'sdist' in cmds: + _sdist: Any = cmds['sdist'] else: - from distutils.command.sdist import sdist as _sdist + from setuptools.command.sdist import sdist as _sdist class cmd_sdist(_sdist): - def run(self): + def run(self) -> None: versions = get_versions() self._versioneer_generated_versions = versions # unless we update this, the command will keep using the old @@ -1634,7 +2105,7 @@ def run(self): self.distribution.metadata.version = versions["version"] return _sdist.run(self) - def make_release_tree(self, base_dir, files): + def make_release_tree(self, base_dir: str, files: List[str]) -> None: root = get_root() cfg = get_config_from_root(root) _sdist.make_release_tree(self, base_dir, files) @@ -1687,21 +2158,26 @@ def make_release_tree(self, base_dir, files): """ -INIT_PY_SNIPPET = """ +OLD_SNIPPET = """ from ._version import get_versions __version__ = get_versions()['version'] del get_versions """ +INIT_PY_SNIPPET = """ +from . import {0} +__version__ = {0}.get_versions()['version'] +""" -def do_setup(): - """Main VCS-independent setup function for installing Versioneer.""" + +def do_setup() -> int: + """Do main VCS-independent setup function for installing Versioneer.""" root = get_root() try: cfg = get_config_from_root(root) - except (EnvironmentError, configparser.NoSectionError, + except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e: - if isinstance(e, (EnvironmentError, configparser.NoSectionError)): + if isinstance(e, (OSError, configparser.NoSectionError)): print("Adding sample versioneer config to setup.cfg", file=sys.stderr) with open(os.path.join(root, "setup.cfg"), "a") as f: @@ -1721,62 +2197,37 @@ def do_setup(): ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py") + maybe_ipy: Optional[str] = ipy if os.path.exists(ipy): try: with open(ipy, "r") as f: old = f.read() - except EnvironmentError: + except OSError: old = "" - if INIT_PY_SNIPPET not in old: + module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0] + snippet = INIT_PY_SNIPPET.format(module) + if OLD_SNIPPET in old: + print(" replacing boilerplate in %s" % ipy) + with open(ipy, "w") as f: + f.write(old.replace(OLD_SNIPPET, snippet)) + elif snippet not in old: print(" appending to %s" % ipy) with open(ipy, "a") as f: - f.write(INIT_PY_SNIPPET) + f.write(snippet) else: print(" %s unmodified" % ipy) else: print(" %s doesn't exist, ok" % ipy) - ipy = None - - # Make sure both the top-level "versioneer.py" and versionfile_source - # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so - # they'll be copied into source distributions. Pip won't be able to - # install the package without this. - manifest_in = os.path.join(root, "MANIFEST.in") - simple_includes = set() - try: - with open(manifest_in, "r") as f: - for line in f: - if line.startswith("include "): - for include in line.split()[1:]: - simple_includes.add(include) - except EnvironmentError: - pass - # That doesn't cover everything MANIFEST.in can do - # (http://docs.python.org/2/distutils/sourcedist.html#commands), so - # it might give some false negatives. Appending redundant 'include' - # lines is safe, though. - if "versioneer.py" not in simple_includes: - print(" appending 'versioneer.py' to MANIFEST.in") - with open(manifest_in, "a") as f: - f.write("include versioneer.py\n") - else: - print(" 'versioneer.py' already in MANIFEST.in") - if cfg.versionfile_source not in simple_includes: - print(" appending versionfile_source ('%s') to MANIFEST.in" % - cfg.versionfile_source) - with open(manifest_in, "a") as f: - f.write("include %s\n" % cfg.versionfile_source) - else: - print(" versionfile_source already in MANIFEST.in") + maybe_ipy = None # Make VCS-specific changes. For git, this means creating/changing # .gitattributes to mark _version.py for export-subst keyword # substitution. - do_vcs_install(manifest_in, cfg.versionfile_source, ipy) + do_vcs_install(cfg.versionfile_source, maybe_ipy) return 0 -def scan_setup_py(): +def scan_setup_py() -> int: """Validate the contents of setup.py against Versioneer's expectations.""" found = set() setters = False @@ -1813,10 +2264,14 @@ def scan_setup_py(): return errors +def setup_command() -> NoReturn: + """Set up Versioneer and exit with appropriate error code.""" + errors = do_setup() + errors += scan_setup_py() + sys.exit(1 if errors else 0) + + if __name__ == "__main__": cmd = sys.argv[1] if cmd == "setup": - errors = do_setup() - errors += scan_setup_py() - if errors: - sys.exit(1) \ No newline at end of file + setup_command() From 785dc471d1d2f66623663b30edacd77babcdc25f Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Thu, 5 Oct 2023 17:42:09 +0200 Subject: [PATCH 682/756] Update setup.py --- setup.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/setup.py b/setup.py index 21015d616..77d8a4f7e 100644 --- a/setup.py +++ b/setup.py @@ -30,17 +30,17 @@ keywords='pyiron', packages=find_packages(exclude=["*tests*"]), install_requires=[ - 'matplotlib==3.7.2', - 'numpy==1.24.3', - 'pyiron_base==0.6.3', - 'scipy==1.11.2', + 'matplotlib==3.8.0', + 'numpy==1.26.0', + 'pyiron_base==0.6.7', + 'scipy==1.11.3', 'seaborn==0.12.2', 'pyparsing==3.0.9', ], extras_require={ 'atomistic': [ 'ase==3.22.1', - 'pyiron_atomistics==0.3.0', + 'pyiron_atomistics==0.3.4', 'pycp2k==0.2.2', ], 'executors': [ @@ -52,19 +52,19 @@ ], 'image': ['scikit-image==0.21.0'], 'generic': [ - 'boto3==1.28.25', - 'moto==4.1.14' + 'boto3==1.28.60', + 'moto==4.2.5' ], 'workflow': [ 'cloudpickle', 'python>=3.10', 'graphviz', 'toposort', - 'typeguard==4.1.0' + 'typeguard==4.1.5' ], 'tinybase': [ - 'distributed==2023.8.0', - 'pympipool==0.6.2' + 'distributed==2023.9.3', + 'pympipool==0.7.1' ] }, cmdclass=versioneer.get_cmdclass(), From 91183dbbb19e9170d02d2c5b0259fd23d12ea2aa Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Thu, 5 Oct 2023 17:43:30 +0200 Subject: [PATCH 683/756] Update environment.yml --- .ci_support/environment.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index c45c993b1..f64e67ca0 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -6,21 +6,21 @@ dependencies: - coveralls - coverage - codacy-coverage -- matplotlib =3.7.2 -- numpy =1.24.3 -- pyiron_base =0.6.3 -- pyiron_atomistics =0.3.0 +- matplotlib =3.8.0 +- numpy =1.26.0 +- pyiron_base =0.6.7 +- pyiron_atomistics =0.3.4 - pyparsing =3.0.9 -- scipy =1.11.2 +- scipy =1.11.3 - seaborn =0.12.2 - scikit-image =0.21.0 - toposort - randspg =0.0.1 -- boto3 =1.28.25 -- moto =4.1.14 +- boto3 =1.28.60 +- moto =4.2.5 - pycp2k =0.2.2 - python-graphviz -- typeguard =4.1.0 +- typeguard =4.1.5 - aws-sam-translator =1.71.0 -- pympipool =0.6.2 -- distributed =2023.8.0 +- pympipool =0.7.1 +- distributed =2023.9.3 From 4c800c59a17ddb5b2de995cb5485dd395de2920d Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Thu, 5 Oct 2023 15:44:47 +0000 Subject: [PATCH 684/756] [dependabot skip] Update env file --- .binder/environment.yml | 20 ++++++++++---------- docs/environment.yml | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 88bec2393..eeeb5c60d 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -6,23 +6,23 @@ dependencies: - coveralls - coverage - codacy-coverage -- matplotlib =3.7.2 -- numpy =1.24.3 -- pyiron_base =0.6.3 -- pyiron_atomistics =0.3.0 +- matplotlib =3.8.0 +- numpy =1.26.0 +- pyiron_base =0.6.7 +- pyiron_atomistics =0.3.4 - pyparsing =3.0.9 -- scipy =1.11.2 +- scipy =1.11.3 - seaborn =0.12.2 - scikit-image =0.21.0 - toposort - randspg =0.0.1 -- boto3 =1.28.25 -- moto =4.1.14 +- boto3 =1.28.60 +- moto =4.2.5 - pycp2k =0.2.2 - python-graphviz -- typeguard =4.1.0 +- typeguard =4.1.5 - aws-sam-translator =1.71.0 -- pympipool =0.6.2 -- distributed =2023.8.0 +- pympipool =0.7.1 +- distributed =2023.9.3 - python >= 3.10 - lammps diff --git a/docs/environment.yml b/docs/environment.yml index 43d1dd2c4..e59c0dcfa 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -8,21 +8,21 @@ dependencies: - coveralls - coverage - codacy-coverage -- matplotlib =3.7.2 -- numpy =1.24.3 -- pyiron_base =0.6.3 -- pyiron_atomistics =0.3.0 +- matplotlib =3.8.0 +- numpy =1.26.0 +- pyiron_base =0.6.7 +- pyiron_atomistics =0.3.4 - pyparsing =3.0.9 -- scipy =1.11.2 +- scipy =1.11.3 - seaborn =0.12.2 - scikit-image =0.21.0 - toposort - randspg =0.0.1 -- boto3 =1.28.25 -- moto =4.1.14 +- boto3 =1.28.60 +- moto =4.2.5 - pycp2k =0.2.2 - python-graphviz -- typeguard =4.1.0 +- typeguard =4.1.5 - aws-sam-translator =1.71.0 -- pympipool =0.6.2 -- distributed =2023.8.0 +- pympipool =0.7.1 +- distributed =2023.9.3 From 0995f8f9c7cd5329f5f75fc52a0f1df48fb609fd Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Thu, 5 Oct 2023 17:53:04 +0200 Subject: [PATCH 685/756] Update environment.yml --- .binder/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index eeeb5c60d..d76998cec 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -21,7 +21,7 @@ dependencies: - pycp2k =0.2.2 - python-graphviz - typeguard =4.1.5 -- aws-sam-translator =1.71.0 +- aws-sam-translator =1.75.0 - pympipool =0.7.1 - distributed =2023.9.3 - python >= 3.10 From a3074b23ee7a3f8a1e3d6f7d2f97065b322a2426 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Thu, 5 Oct 2023 15:53:28 +0000 Subject: [PATCH 686/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index d76998cec..eeeb5c60d 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -21,7 +21,7 @@ dependencies: - pycp2k =0.2.2 - python-graphviz - typeguard =4.1.5 -- aws-sam-translator =1.75.0 +- aws-sam-translator =1.71.0 - pympipool =0.7.1 - distributed =2023.9.3 - python >= 3.10 From fa0646b1730d809f238cc4a8530598b419a52b32 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Thu, 5 Oct 2023 18:12:29 +0200 Subject: [PATCH 687/756] Update environment.yml --- .binder/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index eeeb5c60d..9fda0bddc 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -12,7 +12,7 @@ dependencies: - pyiron_atomistics =0.3.4 - pyparsing =3.0.9 - scipy =1.11.3 -- seaborn =0.12.2 +- seaborn =0.13.0 - scikit-image =0.21.0 - toposort - randspg =0.0.1 From f532a841c365e99c4a2989d9e001afea3329a6f2 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Thu, 5 Oct 2023 18:12:49 +0200 Subject: [PATCH 688/756] Update environment.yml --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index f64e67ca0..c35852eb7 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -12,7 +12,7 @@ dependencies: - pyiron_atomistics =0.3.4 - pyparsing =3.0.9 - scipy =1.11.3 -- seaborn =0.12.2 +- seaborn =0.13.0 - scikit-image =0.21.0 - toposort - randspg =0.0.1 From 4d6dc276feccad8997740a2f580095d08941f466 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Thu, 5 Oct 2023 18:13:05 +0200 Subject: [PATCH 689/756] Update environment.yml --- docs/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/environment.yml b/docs/environment.yml index e59c0dcfa..d4e7bf873 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -14,7 +14,7 @@ dependencies: - pyiron_atomistics =0.3.4 - pyparsing =3.0.9 - scipy =1.11.3 -- seaborn =0.12.2 +- seaborn =0.13.0 - scikit-image =0.21.0 - toposort - randspg =0.0.1 From 588dbf2b8135921984b4a6ea9023e2d03a5d5174 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Thu, 5 Oct 2023 18:13:24 +0200 Subject: [PATCH 690/756] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 77d8a4f7e..a55695452 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ 'numpy==1.26.0', 'pyiron_base==0.6.7', 'scipy==1.11.3', - 'seaborn==0.12.2', + 'seaborn==0.13.0', 'pyparsing==3.0.9', ], extras_require={ From ea99ada8e522b7ef91ca4d29edf3365ec5b45295 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Fri, 6 Oct 2023 20:50:43 +0200 Subject: [PATCH 691/756] Update environment.yml --- .binder/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 9fda0bddc..729573fef 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -21,7 +21,7 @@ dependencies: - pycp2k =0.2.2 - python-graphviz - typeguard =4.1.5 -- aws-sam-translator =1.71.0 +- aws-sam-translator =1.77.0 - pympipool =0.7.1 - distributed =2023.9.3 - python >= 3.10 From 53f996e85092c7013f0f168d37f045c6fb9ea68b Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Fri, 6 Oct 2023 20:51:01 +0200 Subject: [PATCH 692/756] Update environment.yml --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index c35852eb7..16556506a 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -21,6 +21,6 @@ dependencies: - pycp2k =0.2.2 - python-graphviz - typeguard =4.1.5 -- aws-sam-translator =1.71.0 +- aws-sam-translator =1.77.0 - pympipool =0.7.1 - distributed =2023.9.3 From 86a697afc894e6104bd15bdcb33a9d6286b8e789 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Fri, 6 Oct 2023 20:51:15 +0200 Subject: [PATCH 693/756] Update environment.yml --- docs/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/environment.yml b/docs/environment.yml index d4e7bf873..1badbe003 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -23,6 +23,6 @@ dependencies: - pycp2k =0.2.2 - python-graphviz - typeguard =4.1.5 -- aws-sam-translator =1.71.0 +- aws-sam-translator =1.77.0 - pympipool =0.7.1 - distributed =2023.9.3 From 040d4db90a5e96386e1894bdf88146b3541896d1 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Fri, 6 Oct 2023 21:22:50 +0200 Subject: [PATCH 694/756] Update executor.py --- pyiron_contrib/tinybase/executor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/tinybase/executor.py b/pyiron_contrib/tinybase/executor.py index 981cb0efc..5a0a7a202 100644 --- a/pyiron_contrib/tinybase/executor.py +++ b/pyiron_contrib/tinybase/executor.py @@ -289,13 +289,13 @@ def submit(self, tasks): return FuturesExecutionContext(self._client, tasks) -from pympipool import PoolExecutor +from pympipool.mpi import PyMPIExecutor class PyMPIExecutor(Executor): def __init__(self, max_workers, **kwargs): self._max_workers = max_workers - self._pool = PoolExecutor(max_workers=max_workers, **kwargs) + self._pool = PyMPIExecutor(max_workers=max_workers, **kwargs) def submit(self, tasks): return FuturesExecutionContext(self._pool, tasks) From e1f3eb07c381ae37d023d008f2ca555386a31f22 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Fri, 6 Oct 2023 22:05:01 +0200 Subject: [PATCH 695/756] Update environment-notebooks.yml --- .ci_support/environment-notebooks.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.ci_support/environment-notebooks.yml b/.ci_support/environment-notebooks.yml index 6b8c0b09d..add0d8644 100644 --- a/.ci_support/environment-notebooks.yml +++ b/.ci_support/environment-notebooks.yml @@ -2,4 +2,5 @@ channels: - conda-forge dependencies: - python >= 3.10 - - lammps \ No newline at end of file + - lammps + - nglview >=3.0.8 From 9b2464ac49cc8643e5af2a6679a6d4684b6c0e42 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Fri, 6 Oct 2023 20:05:27 +0000 Subject: [PATCH 696/756] [dependabot skip] Update env file --- .binder/environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.binder/environment.yml b/.binder/environment.yml index 729573fef..edd2e947a 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -26,3 +26,4 @@ dependencies: - distributed =2023.9.3 - python >= 3.10 - lammps +- nglview >=3.0.8 From 0edf02dbf8c33656b1d63edf9188f1adf53fe5cd Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Fri, 6 Oct 2023 22:25:21 +0200 Subject: [PATCH 697/756] Update Shell.ipynb --- notebooks/tinybase/Shell.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebooks/tinybase/Shell.ipynb b/notebooks/tinybase/Shell.ipynb index f00c59e26..933432b6a 100644 --- a/notebooks/tinybase/Shell.ipynb +++ b/notebooks/tinybase/Shell.ipynb @@ -691,7 +691,7 @@ }, "outputs": [], "source": [ - "state.settings.resource_paths.insert(0, '/home/poul/pyiron/contrib/notebooks/tinybase/resources')" + "state.settings.resource_paths.insert(0, '/home/runner/work/pyiron_contrib/pyiron_contrib/notebooks/tinybase/resources')" ] }, { From a5673d74c56c444118f91a21f94343189171c74b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Oct 2023 20:45:42 +0000 Subject: [PATCH 698/756] Bump pyparsing from 3.0.9 to 3.1.1 Bumps [pyparsing](https://github.com/pyparsing/pyparsing) from 3.0.9 to 3.1.1. - [Release notes](https://github.com/pyparsing/pyparsing/releases) - [Changelog](https://github.com/pyparsing/pyparsing/blob/master/CHANGES) - [Commits](https://github.com/pyparsing/pyparsing/compare/pyparsing_3.0.9...3.1.1) --- updated-dependencies: - dependency-name: pyparsing dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a55695452..d29cb3df1 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ 'pyiron_base==0.6.7', 'scipy==1.11.3', 'seaborn==0.13.0', - 'pyparsing==3.0.9', + 'pyparsing==3.1.1', ], extras_require={ 'atomistic': [ From e8d99a536b800c457febd473928cd970f39f04a0 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Fri, 6 Oct 2023 20:50:39 +0000 Subject: [PATCH 699/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 16556506a..d884eef43 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -10,7 +10,7 @@ dependencies: - numpy =1.26.0 - pyiron_base =0.6.7 - pyiron_atomistics =0.3.4 -- pyparsing =3.0.9 +- pyparsing =3.1.1 - scipy =1.11.3 - seaborn =0.13.0 - scikit-image =0.21.0 From be8e686f35cb1a206cbdfcc1e184c3aedfa97a30 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Fri, 6 Oct 2023 20:51:17 +0000 Subject: [PATCH 700/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index edd2e947a..a07145d46 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -10,7 +10,7 @@ dependencies: - numpy =1.26.0 - pyiron_base =0.6.7 - pyiron_atomistics =0.3.4 -- pyparsing =3.0.9 +- pyparsing =3.1.1 - scipy =1.11.3 - seaborn =0.13.0 - scikit-image =0.21.0 diff --git a/docs/environment.yml b/docs/environment.yml index 1badbe003..a7006a021 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -12,7 +12,7 @@ dependencies: - numpy =1.26.0 - pyiron_base =0.6.7 - pyiron_atomistics =0.3.4 -- pyparsing =3.0.9 +- pyparsing =3.1.1 - scipy =1.11.3 - seaborn =0.13.0 - scikit-image =0.21.0 From 1af3d446919e011e017eb9aaca1fc0b93d5898b0 Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 6 Oct 2023 14:39:52 -0700 Subject: [PATCH 701/756] Remove code now living in pyiron_worklfow --- .ci_support/environment.yml | 4 - pyiron_contrib/executors/README.md | 3 - pyiron_contrib/executors/__init__.py | 7 - .../executors/cloudpickleprocesspool.py | 216 ------ pyiron_contrib/workflow/__init__.py | 1 - pyiron_contrib/workflow/channels.py | 497 ------------- pyiron_contrib/workflow/composite.py | 415 ----------- pyiron_contrib/workflow/draw.py | 366 ---------- pyiron_contrib/workflow/files.py | 99 --- pyiron_contrib/workflow/function.py | 680 ------------------ pyiron_contrib/workflow/has_channel.py | 27 - pyiron_contrib/workflow/has_to_dict.py | 17 - pyiron_contrib/workflow/interfaces.py | 111 --- pyiron_contrib/workflow/io.py | 272 ------- pyiron_contrib/workflow/macro.py | 224 ------ pyiron_contrib/workflow/meta.py | 326 --------- pyiron_contrib/workflow/node.py | 395 ---------- .../workflow/node_library/__init__.py | 0 .../workflow/node_library/atomistics.py | 202 ------ .../workflow/node_library/standard.py | 58 -- pyiron_contrib/workflow/node_package.py | 43 -- pyiron_contrib/workflow/output_parser.py | 98 --- pyiron_contrib/workflow/type_hinting.py | 94 --- pyiron_contrib/workflow/util.py | 34 - pyiron_contrib/workflow/workflow.py | 227 ------ setup.py | 10 - tests/integration/test_workflow.py | 173 ----- tests/unit/executors/__init__.py | 0 tests/unit/executors/test_cloudprocesspool.py | 169 ----- tests/unit/workflow/__init__.py | 0 tests/unit/workflow/test_channels.py | 210 ------ tests/unit/workflow/test_files.py | 50 -- tests/unit/workflow/test_function.py | 542 -------------- tests/unit/workflow/test_io.py | 181 ----- tests/unit/workflow/test_macro.py | 242 ------- tests/unit/workflow/test_node_package.py | 68 -- tests/unit/workflow/test_output_parser.py | 90 --- tests/unit/workflow/test_type_hinting.py | 82 --- tests/unit/workflow/test_util.py | 14 - tests/unit/workflow/test_workflow.py | 343 --------- 40 files changed, 6590 deletions(-) delete mode 100644 pyiron_contrib/executors/README.md delete mode 100644 pyiron_contrib/executors/__init__.py delete mode 100644 pyiron_contrib/executors/cloudpickleprocesspool.py delete mode 100644 pyiron_contrib/workflow/__init__.py delete mode 100644 pyiron_contrib/workflow/channels.py delete mode 100644 pyiron_contrib/workflow/composite.py delete mode 100644 pyiron_contrib/workflow/draw.py delete mode 100644 pyiron_contrib/workflow/files.py delete mode 100644 pyiron_contrib/workflow/function.py delete mode 100644 pyiron_contrib/workflow/has_channel.py delete mode 100644 pyiron_contrib/workflow/has_to_dict.py delete mode 100644 pyiron_contrib/workflow/interfaces.py delete mode 100644 pyiron_contrib/workflow/io.py delete mode 100644 pyiron_contrib/workflow/macro.py delete mode 100644 pyiron_contrib/workflow/meta.py delete mode 100644 pyiron_contrib/workflow/node.py delete mode 100644 pyiron_contrib/workflow/node_library/__init__.py delete mode 100644 pyiron_contrib/workflow/node_library/atomistics.py delete mode 100644 pyiron_contrib/workflow/node_library/standard.py delete mode 100644 pyiron_contrib/workflow/node_package.py delete mode 100644 pyiron_contrib/workflow/output_parser.py delete mode 100644 pyiron_contrib/workflow/type_hinting.py delete mode 100644 pyiron_contrib/workflow/util.py delete mode 100644 pyiron_contrib/workflow/workflow.py delete mode 100644 tests/integration/test_workflow.py delete mode 100644 tests/unit/executors/__init__.py delete mode 100644 tests/unit/executors/test_cloudprocesspool.py delete mode 100644 tests/unit/workflow/__init__.py delete mode 100644 tests/unit/workflow/test_channels.py delete mode 100644 tests/unit/workflow/test_files.py delete mode 100644 tests/unit/workflow/test_function.py delete mode 100644 tests/unit/workflow/test_io.py delete mode 100644 tests/unit/workflow/test_macro.py delete mode 100644 tests/unit/workflow/test_node_package.py delete mode 100644 tests/unit/workflow/test_output_parser.py delete mode 100644 tests/unit/workflow/test_type_hinting.py delete mode 100644 tests/unit/workflow/test_util.py delete mode 100644 tests/unit/workflow/test_workflow.py diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 16556506a..614ffb947 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -2,7 +2,6 @@ channels: - conda-forge dependencies: - ase =3.22.1 -- cloudpickle - coveralls - coverage - codacy-coverage @@ -14,13 +13,10 @@ dependencies: - scipy =1.11.3 - seaborn =0.13.0 - scikit-image =0.21.0 -- toposort - randspg =0.0.1 - boto3 =1.28.60 - moto =4.2.5 - pycp2k =0.2.2 -- python-graphviz -- typeguard =4.1.5 - aws-sam-translator =1.77.0 - pympipool =0.7.1 - distributed =2023.9.3 diff --git a/pyiron_contrib/executors/README.md b/pyiron_contrib/executors/README.md deleted file mode 100644 index e96e8b751..000000000 --- a/pyiron_contrib/executors/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Executors - -This sub-module holds custom children of `concurrent.futures.Executor` for use in other parts of pyiron (e.g. `pyiron_contrib.workflow`). \ No newline at end of file diff --git a/pyiron_contrib/executors/__init__.py b/pyiron_contrib/executors/__init__.py deleted file mode 100644 index c9a0659bc..000000000 --- a/pyiron_contrib/executors/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -This module holds customized children of `concurrent.futures.Executor`. -""" - -from pyiron_contrib.executors.cloudpickleprocesspool import ( - CloudpickleProcessPoolExecutor, -) diff --git a/pyiron_contrib/executors/cloudpickleprocesspool.py b/pyiron_contrib/executors/cloudpickleprocesspool.py deleted file mode 100644 index 7ef13cb7d..000000000 --- a/pyiron_contrib/executors/cloudpickleprocesspool.py +++ /dev/null @@ -1,216 +0,0 @@ -from concurrent.futures import Future, ProcessPoolExecutor -from concurrent.futures.process import _global_shutdown, _WorkItem, BrokenProcessPool -from sys import version_info - -import cloudpickle - - -class CloudLoadsFuture(Future): - def result(self, timeout=None): - result = super().result(timeout=timeout) - if isinstance(result, bytes): - result = cloudpickle.loads(result) - return result - - -class _CloudPickledCallable: - def __init__(self, fnc: callable): - self.fnc_serial = cloudpickle.dumps(fnc) - - def __call__(self, /, dumped_args, dumped_kwargs): - fnc = cloudpickle.loads(self.fnc_serial) - args = cloudpickle.loads(dumped_args) - kwargs = cloudpickle.loads(dumped_kwargs) - return cloudpickle.dumps(fnc(*args, **kwargs)) - - @classmethod - def dumps(cls, stuff): - return cloudpickle.dumps(stuff) - - -class CloudpickleProcessPoolExecutor(ProcessPoolExecutor): - """ - This class wraps `concurrent.futures.ProcessPoolExecutor` such that the submitted - callable, its arguments, and its return value are all pickled using `cloudpickle`. - In this way, the executor extends support to all objects which are cloud-pickleable, - e.g. dynamically defined or decorated classes. - - To accomplish this, the underlying `concurrent.futures.Future` class used is - replaced with our `CloudLoadsFuture`, which is identical except that calls to - `result()` will first try to `cloudpickle.loads` and `bytes` results found. - - Examples: - Consider a class created from a function dynamically with a decorator. - These are not normally pickleable, so in this example we should how this class - allows us to submit a method from such a class, that both takes as an argument - and returns such an unpickleable class. - Actions such as registering callbacks and waiting for results behave just like - normal. - >>> from functools import partialmethod - >>> - >>> from pyiron_contrib.executors import CloudpickleProcessPoolExecutor - >>> - >>> class Foo: - ... ''' - ... A base class to be dynamically modified for testing our executor. - ... ''' - ... def __init__(self, fnc: callable): - ... self.fnc = fnc - ... self.result = None - ... - ... @property - ... def run(self): - ... return self.fnc - ... - ... def process_result(self, future): - ... self.result = future.result() - >>> - >>> - >>> def dynamic_foo(): - ... ''' - ... A decorator for dynamically modifying the Foo class. - ... - ... Overrides the `fnc` input of `Foo` with the decorated function. - ... ''' - ... def as_dynamic_foo(fnc: callable): - ... return type( - ... "DynamicFoo", - ... (Foo,), # Define parentage - ... { - ... "__init__": partialmethod( - ... Foo.__init__, - ... fnc - ... ) - ... }, - ... ) - ... - ... return as_dynamic_foo - >>> - >>> @dynamic_foo() - >>> def UnpicklableCallable(unpicklable_arg): - ... unpicklable_arg.result = "This was an arg" - ... return unpicklable_arg - >>> - >>> - >>> instance = UnpicklableCallable() - >>> arg = UnpicklableCallable() - >>> executor = CloudpickleProcessPoolExecutor() - >>> fs = executor.submit(instance.run, arg) - >>> fs.add_done_callback(instance.process_result) - >>> print(fs.done()) - False - - >>> print(fs.result().__class__.__name__) - DynamicFoo - - >>> print(fs.done()) - True - - >>> print(instance.result.result) - This was an arg - """ - - def submit(self, fn, /, *args, **kwargs): - return self._submit( - _CloudPickledCallable(fn), - _CloudPickledCallable.dumps(args), - _CloudPickledCallable.dumps(kwargs), - ) - - submit.__doc__ = ProcessPoolExecutor.submit.__doc__ - - def _submit(self, fn, /, *args, **kwargs): - """ - We override the regular `concurrent.futures.ProcessPoolExecutor` to use our - custom future that unpacks cloudpickled results. - - This approach is simple, but the brute-force nature of it means we manually - accommodate different implementations of `ProcessPoolExecutor` in different - python versions. - """ - if version_info.major != 3: - raise RuntimeError( - f"{self.__class__} is only built for python3, but got " - f"{version_info.major}" - ) - - if version_info.minor == 8: - return self._submit_3_8(fn, *args, **kwargs) - elif version_info.minor >= 9: - return self._submit_3_gt9(fn, *args, **kwargs) - else: - raise RuntimeError( - f"{self.__class__} is only built for python 3.8+, but got " - f"{version_info.major}.{version_info.minor}." - ) - - def _submit_3_gt9(self, fn, /, *args, **kwargs): - with self._shutdown_lock: - if self._broken: - raise BrokenProcessPool(self._broken) - if self._shutdown_thread: - raise RuntimeError("cannot schedule new futures after shutdown") - if _global_shutdown: - raise RuntimeError( - "cannot schedule new futures after " "interpreter shutdown" - ) - - f = CloudLoadsFuture() - w = _WorkItem(f, fn, args, kwargs) - - self._pending_work_items[self._queue_count] = w - self._work_ids.put(self._queue_count) - self._queue_count += 1 - # Wake up queue management thread - self._executor_manager_thread_wakeup.wakeup() - - if self._safe_to_dynamically_spawn_children: - self._adjust_process_count() - self._start_executor_manager_thread() - return f - - def _submit_3_8(*args, **kwargs): - if len(args) >= 2: - self, fn, *args = args - elif not args: - raise TypeError( - "descriptor 'submit' of 'ProcessPoolExecutor' object " - "needs an argument" - ) - elif "fn" in kwargs: - fn = kwargs.pop("fn") - self, *args = args - import warnings - - warnings.warn( - "Passing 'fn' as keyword argument is deprecated", - DeprecationWarning, - stacklevel=2, - ) - else: - raise TypeError( - "submit expected at least 1 positional argument, " - "got %d" % (len(args) - 1) - ) - - with self._shutdown_lock: - if self._broken: - raise BrokenProcessPool(self._broken) - if self._shutdown_thread: - raise RuntimeError("cannot schedule new futures after shutdown") - if _global_shutdown: - raise RuntimeError( - "cannot schedule new futures after " "interpreter shutdown" - ) - - f = CloudLoadsFuture() - w = _WorkItem(f, fn, args, kwargs) - - self._pending_work_items[self._queue_count] = w - self._work_ids.put(self._queue_count) - self._queue_count += 1 - # Wake up queue management thread - self._queue_management_thread_wakeup.wakeup() - - self._start_queue_management_thread() - return f diff --git a/pyiron_contrib/workflow/__init__.py b/pyiron_contrib/workflow/__init__.py deleted file mode 100644 index 8185347c1..000000000 --- a/pyiron_contrib/workflow/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from pyiron_contrib.workflow.workflow import Workflow diff --git a/pyiron_contrib/workflow/channels.py b/pyiron_contrib/workflow/channels.py deleted file mode 100644 index a241802e2..000000000 --- a/pyiron_contrib/workflow/channels.py +++ /dev/null @@ -1,497 +0,0 @@ -""" -Channels are access points for information to flow into and out of nodes. - -Data channels carry, unsurprisingly, data. -Output data channels will attempt to push their new value to all their connected input -data channels on update, while input data channels will reject any updates if their -parent node is running. -In this way, data channels facilitate forward propagation of data through a graph. -They hold data persistently. - -Signal channels are tools for procedurally exposing functionality on nodes. -Input signal channels are connected to a callback function which gets invoked when the -channel is updated. -Output signal channels must be accessed by the owning node directly, and then trigger -all the input signal channels to which they are connected. -In this way, signal channels can force behaviour (node method calls) to propagate -forwards through a graph. -They do not hold any data, but rather fire for an effect. -""" - -from __future__ import annotations - -import typing -from abc import ABC, abstractmethod -from warnings import warn - -from pyiron_contrib.workflow.has_channel import HasChannel -from pyiron_contrib.workflow.has_to_dict import HasToDict -from pyiron_contrib.workflow.type_hinting import ( - valid_value, - type_hint_is_as_or_more_specific_than, -) - -if typing.TYPE_CHECKING: - from pyiron_contrib.workflow.composite import Composite - from pyiron_contrib.workflow.node import Node - - -class Channel(HasChannel, HasToDict, ABC): - """ - Channels facilitate the flow of information (data or control signals) into and - out of nodes. - They must have a label and belong to a node. - - Input/output channels can be (dis)connected from other output/input channels, and - store all of their current connections in a list. - This connection information is duplicated in that it is stored on _both_ channels - that form the connection. - - Child classes must define a string representation, `__str__`, and what to do on an - attempted connection, `connect`. - - Attributes: - label (str): The name of the channel. - node (pyiron_contrib.workflow.node.Node): The node to which the channel - belongs. - connections (list[Channel]): Other channels to which this channel is connected. - """ - - def __init__( - self, - label: str, - node: Node, - ): - """ - Make a new channel. - - Args: - label (str): A name for the channel. - node (pyiron_contrib.workflow.node.Node): The node to which the - channel belongs. - """ - self.label: str = label - self.node: Node = node - self.connections: list[Channel] = [] - - @abstractmethod - def __str__(self): - pass - - @abstractmethod - def connect(self, *others: Channel) -> None: - """ - How to handle connections to other channels. - - Args: - *others (Channel): The other channel objects to attempt to connect with. - """ - pass - - def disconnect(self, *others: Channel) -> list[tuple[Channel, Channel]]: - """ - If currently connected to any others, removes this and the other from eachothers - respective connections lists. - - Args: - *others (Channel): The other channels to disconnect from. - - Returns: - [list[tuple[Channel, Channel]]]: A list of the pairs of channels that no - longer participate in a connection. - """ - destroyed_connections = [] - for other in others: - if other in self.connections: - self.connections.remove(other) - other.disconnect(self) - destroyed_connections.append((self, other)) - else: - warn( - f"The channel {self.label} was not connected to {other.label}, and" - f"thus could not disconnect from it." - ) - return destroyed_connections - - def disconnect_all(self) -> list[tuple[Channel, Channel]]: - """ - Disconnect from all other channels currently in the connections list. - """ - return self.disconnect(*self.connections) - - @property - def connected(self) -> bool: - """ - Has at least one connection. - """ - return len(self.connections) > 0 - - def _already_connected(self, other: Channel) -> bool: - return other in self.connections - - def __iter__(self): - return self.connections.__iter__() - - def __len__(self): - return len(self.connections) - - @property - def channel(self) -> Channel: - return self - - def to_dict(self) -> dict: - return { - "label": self.label, - "connected": self.connected, - "connections": [f"{c.node.label}.{c.label}" for c in self.connections], - } - - -class NotData: - """ - This class exists purely to initialize data channel values where no default value - is provided; it lets the channel know that it has _no data in it_ and thus should - not identify as ready. - """ - - @classmethod - def __repr__(cls): - # We use the class directly (not instances of it) where there is not yet data - # So give it a decent repr, even as just a class - return cls.__name__ - - -class DataChannel(Channel, ABC): - """ - Data channels control the flow of data on the graph. - They store this data in a `value` attribute. - They may optionally have a type hint. - They have a `ready` attribute which tells whether their value matches their type - hint (if one is provided, else `True`). - (In the future they may optionally have a storage priority.) - (In the future they may optionally have a storage history limit.) - (In the future they may optionally have an ontological type.) - - The `value` held by a channel can be manually assigned, but should normally be set - by the `update` method. - In neither case is the type hint strictly enforced. - - Type hinting is strictly enforced in one situation: when making connections to - other channels and at least one data channel has a non-None value for its type hint. - In this case, we insist that the output type hint be _as or more more specific_ than - the input type hint, to ensure that the input always receives output of a type it - expects. This behaviour can be disabled and all connections allowed by setting - `strict_connections = False` on the relevant input channel. - - For simple type hints like `int` or `str`, type hint comparison is trivial. - However, some hints take arguments, e.g. `dict[str, int]` to specify key and value - types; `tuple[int, int, str]` to specify a tuple with certain values; - `typing.Literal['a', 'b', 'c']` to specify particular choices; - `typing.Callable[[float, float], str]` to specify a callable that takes particular - argument types and has a return type; etc. - For hints with the origin `dict`, `tuple`, and `typing.Callable`, the two hints must - have _exactly the same arguments_ for one two qualify as "as or more specific". - E.g. `tuple[int, int|float]` is as or more specific than - `tuple[int|float, int|float]`, but not `tuple[int, int|float, str]`. - For _all other hints_, we demand that the output hint arguments be a _subset_ of - the input. - E.g. `Literal[1, 2]` is as or more specific that both `Literal[1, 2]` and - `Literal[1, 2, "three"]`. - - The data `value` will initialize to an instance of `NotData` by default. - The channel will identify as `ready` when the value is _not_ an instance of - `NotData`, and when the value conforms to type hints (if any). - - Warning: - Type hinting in python is quite complex, and determining when a hint is - "more specific" can be tricky. For instance, in python 3.11 you can now type - hint a tuple with a mixture of fixed elements of fixed type, followed by an - arbitrary elements of arbitrary type. This and other complex scenarios are not - yet included in our test suite and behaviour is not guaranteed. - """ - - def __init__( - self, - label: str, - node: Node, - default: typing.Optional[typing.Any] = NotData, - type_hint: typing.Optional[typing.Any] = None, - ): - super().__init__(label=label, node=node) - self.default = default - self.value = default - self.type_hint = type_hint - - @property - def ready(self) -> bool: - """ - Check if the currently stored value satisfies the channel's type hint. - - Returns: - (bool): Whether the value matches the type hint. - """ - if self.type_hint is not None: - return self._value_is_data and valid_value(self.value, self.type_hint) - else: - return self._value_is_data - - @property - def _value_is_data(self): - return self.value is not NotData - - def update(self, value) -> None: - """ - Store a new value and trigger before- and after-update routines. - - Args: - value: The value to store. - """ - self._before_update() - self.value = value - self._after_update() - - def _before_update(self) -> None: - """ - A tool for child classes to do things before the value changed during an update. - """ - pass - - def _after_update(self) -> None: - """ - A tool for child classes to do things after the value changed during an update. - """ - pass - - def connect(self, *others: DataChannel) -> None: - """ - For all others for which the connection is valid (one input, one output, both - data channels), adds this to the other's list of connections and the other to - this list of connections. - Then the input channel gets updated with the output channel's current value. - - Args: - *others (DataChannel): - - Raises: - TypeError: When one of others is not a `DataChannel` - """ - for other in others: - if self._valid_connection(other): - self.connections.append(other) - other.connections.append(self) - out, inp = self._figure_out_who_is_who(other) - if out.value is not NotData: - inp.update(out.value) - else: - if isinstance(other, DataChannel): - warn( - f"{self.label} ({self.__class__.__name__}) and {other.label} " - f"({other.__class__.__name__}) were not a valid connection" - ) - else: - raise TypeError( - f"Can only connect two channels, but {self.label} " - f"({self.__class__.__name__}) got a {other} ({type(other)})" - ) - - def _valid_connection(self, other) -> bool: - if self._is_IO_pair(other) and not self._already_connected(other): - if self._both_typed(other): - out, inp = self._figure_out_who_is_who(other) - if not inp.strict_connections: - return True - else: - return type_hint_is_as_or_more_specific_than( - out.type_hint, inp.type_hint - ) - else: - # If either is untyped, don't do type checking - return True - else: - return False - - def _is_IO_pair(self, other: DataChannel) -> bool: - return isinstance(other, DataChannel) and not isinstance(other, self.__class__) - - def _both_typed(self, other: DataChannel) -> bool: - return self.type_hint is not None and other.type_hint is not None - - def _figure_out_who_is_who(self, other: DataChannel) -> (OutputData, InputData): - return (self, other) if isinstance(self, OutputData) else (other, self) - - def __str__(self): - return str(self.value) - - def to_dict(self) -> dict: - d = super().to_dict() - d["value"] = repr(self.value) - d["ready"] = self.ready - return d - - -class InputData(DataChannel): - """ - On `update`, Input channels will only `update` if their parent node is not - `running`. - - The `strict_connections` parameter controls whether connections are subject to - type checking requirements. - I.e., they may set `strict_connections` to `False` (`True` -- default) at - instantiation or later with `(de)activate_strict_connections()` to prevent (enable) - data type checking when making connections with `OutputData` channels. - """ - - def __init__( - self, - label: str, - node: Node, - default: typing.Optional[typing.Any] = NotData, - type_hint: typing.Optional[typing.Any] = None, - strict_connections: bool = True, - ): - super().__init__( - label=label, - node=node, - default=default, - type_hint=type_hint, - ) - self.strict_connections = strict_connections - - def _before_update(self) -> None: - if self.node.running: - raise RuntimeError( - f"Parent node {self.node.label} of {self.label} is running, so value " - f"cannot be updated." - ) - - def activate_strict_connections(self) -> None: - self.strict_connections = True - - def deactivate_strict_connections(self) -> None: - self.strict_connections = False - - -class OutputData(DataChannel): - """ - On `update`, Output channels propagate their value (as long as it's actually data) - to all the input channels to which they are connected by invoking their `update` - method. - """ - - def _after_update(self) -> None: - if self._value_is_data: - for inp in self.connections: - inp.update(self.value) - - -class SignalChannel(Channel, ABC): - """ - Signal channels give the option control execution flow by triggering callback - functions. - - Output channels can be called to trigger the callback functions of all input - channels to which they are connected. - - Signal channels support `>` as syntactic sugar for their connections, i.e. - `some_output > some_input` is equivalent to `some_input.connect(some_output)`. - (This is also interoperable with `Node` objects, cf. the `Node` docs.) - """ - - @abstractmethod - def __call__(self) -> None: - pass - - def connect(self, *others: SignalChannel) -> None: - """ - For all others for which the connection is valid (one input, one output, both - data channels), adds this to the other's list of connections and the other to - this list of connections. - - Args: - *others (SignalChannel): The other channels to attempt a connection to - - Raises: - TypeError: When one of others is not a `SignalChannel` - """ - for other in others: - if self._valid_connection(other): - self.connections.append(other) - other.connections.append(self) - else: - if isinstance(other, SignalChannel): - warn( - f"{self.label} ({self.__class__.__name__}) and {other.label} " - f"({other.__class__.__name__}) were not a valid connection" - ) - else: - raise TypeError( - f"Can only connect two signal channels, but {self.label} " - f"({self.__class__.__name__}) got a {other} ({type(other)})" - ) - - def _valid_connection(self, other) -> bool: - return self._is_IO_pair(other) and not self._already_connected(other) - - def _is_IO_pair(self, other) -> bool: - return isinstance(other, SignalChannel) and not isinstance( - other, self.__class__ - ) - - def connect_output_signal(self, signal: OutputSignal): - self.connect(signal) - - -class InputSignal(SignalChannel): - """ - Invokes a callback when called. - """ - - def __init__( - self, - label: str, - node: Node, - callback: callable, - ): - """ - Make a new input signal channel. - - Args: - label (str): A name for the channel. - node (pyiron_contrib.workflow.node.Node): The node to which the - channel belongs. - callback (callable): An argument-free callback to invoke when calling this - object. - """ - super().__init__(label=label, node=node) - self.callback: callable = callback - - def __call__(self) -> None: - self.callback() - - def __str__(self): - return f"{self.label} runs {self.callback.__name__}" - - def to_dict(self) -> dict: - d = super().to_dict() - d["callback"] = self.callback.__name__ - return d - - -class OutputSignal(SignalChannel): - """ - Calls all the input signal objects in its connections list when called. - """ - - def __call__(self) -> None: - for c in self.connections: - c() - - def __str__(self): - return ( - f"{self.label} activates " - f"{[f'{c.node.label}.{c.label}' for c in self.connections]}" - ) - - def __gt__(self, other: InputSignal | Node): - other.connect_output_signal(self) - return True diff --git a/pyiron_contrib/workflow/composite.py b/pyiron_contrib/workflow/composite.py deleted file mode 100644 index 20ca3ae88..000000000 --- a/pyiron_contrib/workflow/composite.py +++ /dev/null @@ -1,415 +0,0 @@ -""" -A base class for nodal objects that have internal structure -- i.e. they hold a -sub-graph -""" - -from __future__ import annotations - -from abc import ABC -from functools import partial -from typing import Literal, Optional, TYPE_CHECKING - -from toposort import toposort_flatten, CircularDependencyError - -from pyiron_contrib.workflow.interfaces import Creator, Wrappers -from pyiron_contrib.workflow.io import Outputs, Inputs -from pyiron_contrib.workflow.node import Node -from pyiron_contrib.workflow.node_package import NodePackage -from pyiron_contrib.workflow.util import logger, DotDict, SeabornColors - -if TYPE_CHECKING: - from pyiron_contrib.workflow.channels import Channel - - -class Composite(Node, ABC): - """ - A base class for nodes that have internal structure -- i.e. they hold a sub-graph. - - Item and attribute access is modified to give access to owned nodes. - Adding a node with the `add` functionality or by direct attribute assignment sets - this object as the parent of that node. - - Guarantees that each owned node is unique, and does not belong to any other parents. - - Offers a class method (`wrap_as`) to give easy access to the node-creating - decorators. - - Offers a creator (the `create` method) which allows instantiation of other workflow - objects. - This method behaves _differently_ on the composite class and its instances -- on - instances, any created nodes get their `parent` attribute automatically set to the - composite instance being used. - - Specifies the required `on_run()` to call `run()` on a subset of owned - `starting_nodes`nodes to kick-start computation on the owned sub-graph. - Both the specification of these starting nodes and specifying execution signals to - propagate execution through the graph is left to the user/child classes. - In the case of non-cyclic workflows (i.e. DAGs in terms of data flow), both - starting nodes and execution flow can be specified by invoking `` - - The `run()` method (and `update()`, and calling the workflow) 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. - - Attributes: - nodes (DotDict[pyiron_contrib.workflow.node.Node]): The owned nodes that - form the composite subgraph. - strict_naming (bool): When true, repeated assignment of a new node to an - existing node label will raise an error, otherwise the label gets appended - with an index and the assignment proceeds. (Default is true: disallow assigning - to existing labels.) - create (Creator): A tool for adding new nodes to this subgraph. - starting_nodes (None | list[pyiron_contrib.workflow.node.Node]): A subset - of the owned nodes to be used on running. Only necessary if the execution graph - has been manually specified with `run` signals. (Default is an empty list.) - wrap_as (Wrappers): A tool for accessing node-creating decorators - - Methods: - add(node: Node): Add the node instance to this subgraph. - remove(node: Node): Break all connections the node has, remove it from this - subgraph, and set its parent to `None`. - - TODO: - Wrap node registration at the class level so we don't need to do - `X.create.register` but can just do `X.register` - """ - - wrap_as = Wrappers() - create = Creator() - - def __init__( - self, - label: str, - *args, - parent: Optional[Composite] = None, - strict_naming: bool = True, - inputs_map: Optional[dict] = None, - outputs_map: Optional[dict] = None, - **kwargs, - ): - super().__init__(*args, label=label, parent=parent, **kwargs) - self.strict_naming: bool = strict_naming - self.inputs_map = inputs_map - self.outputs_map = outputs_map - self.nodes: DotDict[str:Node] = DotDict() - self.starting_nodes: list[Node] = [] - self._creator = self.create - self.create = self._owned_creator # Override the create method from the class - - @property - def _owned_creator(self): - """ - A misdirection so that the `create` method behaves differently on the class - and on instances (in the latter case, created nodes should get the instance as - their parent). - """ - return OwnedCreator(self, self._creator) - - @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, - "nodes": {n.label: n.to_dict() for n in self.nodes.values()}, - } - - @property - def on_run(self): - return self.run_graph - - @staticmethod - def run_graph(self): - for node in self.starting_nodes: - node.run() - return DotDict(self.outputs.to_value_dict()) - - def disconnect_run(self) -> list[tuple[Channel, Channel]]: - """ - Disconnect all `signals.input.run` connections on all child nodes. - - Returns: - list[tuple[Channel, Channel]]: Any disconnected pairs. - """ - disconnected_pairs = [] - for node in self.nodes.values(): - disconnected_pairs.extend(node.signals.disconnect_run()) - return disconnected_pairs - - def set_run_signals_to_dag_execution(self): - """ - Disconnects all `signals.input.run` connections among children and attempts to - reconnect these according to the DAG flow of the data. - - Raises: - ValueError: When the data connections do not form a DAG. - """ - self.disconnect_run() - self._set_run_connections_and_starting_nodes_according_to_linear_dag() - # TODO: Replace this linear setup with something more powerful - - def _set_run_connections_and_starting_nodes_according_to_linear_dag(self): - # This is the most primitive sort of topological exploitation we can do - # It is not efficient if the nodes have executors and can run in parallel - try: - # Topological sorting ensures that all input dependencies have been - # executed before the node depending on them gets run - # The flattened part is just that we don't care about topological - # generations that are mutually independent (inefficient but easier for now) - execution_order = toposort_flatten(self.get_data_digraph()) - except CircularDependencyError as e: - raise ValueError( - f"Detected a cycle in the data flow topology, unable to automate the " - f"execution of non-DAGs: cycles found among {e.data}" - ) - - for i, label in enumerate(execution_order[:-1]): - next_node = execution_order[i + 1] - self.nodes[label] > self.nodes[next_node] - self.starting_nodes = [self.nodes[execution_order[0]]] - - def get_data_digraph(self) -> dict[str, set[str]]: - """ - Builds a directed graph of node labels based on data connections between nodes - directly owned by this composite -- i.e. does not worry about data connections - which are entirely internal to an owned sub-graph. - - Returns: - dict[str, set[str]]: A dictionary of nodes and the nodes they depend on for - data. - - Raises: - ValueError: When a node appears in its own input. - """ - digraph = {} - - for node in self.nodes.values(): - node_dependencies = [] - for channel in node.inputs: - locally_scoped_dependencies = [] - for upstream in channel.connections: - if upstream.node.parent is self: - locally_scoped_dependencies.append(upstream.node.label) - elif channel.node.get_first_shared_parent(upstream.node) is self: - locally_scoped_dependencies.append( - upstream.node.get_parent_proximate_to(self).label - ) - node_dependencies.extend(locally_scoped_dependencies) - node_dependencies = set(node_dependencies) - if node.label in node_dependencies: - # the toposort library has a - # [known issue](https://gitlab.com/ericvsmith/toposort/-/issues/3) - # That self-dependency isn't caught, so we catch it manually here. - raise ValueError( - f"Detected a cycle in the data flow topology, unable to automate " - f"the execution of non-DAGs: {node.label} appears in its own input." - ) - digraph[node.label] = node_dependencies - - return digraph - - @property - def run_args(self) -> dict: - return {"self": self} - - def _build_io( - self, - io: Inputs | Outputs, - target: Literal["inputs", "outputs"], - key_map: dict[str, str] | None, - ) -> Inputs | Outputs: - key_map = {} if key_map is None else key_map - for node in self.nodes.values(): - panel = getattr(node, target) - for channel_label in panel.labels: - channel = panel[channel_label] - default_key = f"{node.label}__{channel_label}" - try: - if key_map[default_key] is not None: - io[key_map[default_key]] = channel - except KeyError: - if not channel.connected: - io[default_key] = channel - return io - - def _build_inputs(self) -> Inputs: - return self._build_io(Inputs(), "inputs", self.inputs_map) - - def _build_outputs(self) -> Outputs: - return self._build_io(Outputs(), "outputs", self.outputs_map) - - def add(self, node: Node, label: Optional[str] = None) -> None: - """ - Assign a node to the parent. Optionally provide a new label for that node. - - Args: - node (pyiron_contrib.workflow.node.Node): The node to add. - label (Optional[str]): The label for this node. - - Raises: - TypeError: If the - """ - if not isinstance(node, Node): - raise TypeError( - f"Only new node instances may be added, but got {type(node)}." - ) - self._ensure_node_has_no_other_parent(node) - label = self._get_unique_label(node.label if label is None else label) - self._ensure_node_is_not_duplicated(node, label) - - self.nodes[label] = node - node.label = label - node.parent = self - return node - - def _get_unique_label(self, label): - if label in self.__dir__(): - if isinstance(getattr(self, label), Node): - if self.strict_naming: - raise AttributeError( - f"{label} is already the label for a node. Please remove it " - f"before assigning another node to this label." - ) - else: - label = self._add_suffix_to_label(label) - else: - raise AttributeError( - f"{label} is an attribute or method of the {self.__class__} class, " - f"and cannot be used as a node label." - ) - return label - - def _add_suffix_to_label(self, label): - i = 0 - new_label = label - while new_label in self.nodes.keys(): - new_label = f"{label}{i}" - i += 1 - if new_label != label: - logger.info( - f"{label} is already a node; appending an index to the " - f"node label instead: {new_label}" - ) - return new_label - - def _ensure_node_has_no_other_parent(self, node: Node): - if node.parent is not None and node.parent is not self: - raise ValueError( - f"The node ({node.label}) already belongs to the parent " - f"{node.parent.label}. Please remove it there before trying to " - f"add it to this parent ({self.label})." - ) - - def _ensure_node_is_not_duplicated(self, node: Node, label: str): - if ( - node.parent is self - and label != node.label - and self.nodes[node.label] is node - ): - logger.info( - f"Reassigning the node {node.label} to the label {label} when " - f"adding it to the parent {self.label}." - ) - del self.nodes[node.label] - - def remove(self, node: Node | str): - if isinstance(node, Node): - node.parent = None - node.disconnect() - del self.nodes[node.label] - else: - del self.nodes[node] - - def __setattr__(self, key: str, node: Node): - if isinstance(node, Node) and key != "parent": - self.add(node, label=key) - else: - super().__setattr__(key, node) - - def __getattr__(self, key): - try: - return self.nodes[key] - except KeyError: - # Raise an attribute error from getattr to make sure hasattr works well! - raise AttributeError( - f"Could not find attribute {key} on {self.label} " - f"({self.__class__.__name__}) or in its nodes ({self.nodes.keys()})" - ) - - def __getitem__(self, item): - return self.__getattr__(item) - - def __setitem__(self, key, value): - self.__setattr__(key, value) - - def __iter__(self): - return self.nodes.values().__iter__() - - def __len__(self): - return len(self.nodes) - - def __dir__(self): - return set(super().__dir__() + list(self.nodes.keys())) - - @property - def color(self) -> str: - """For drawing the graph""" - return SeabornColors.brown - - -class OwnedCreator: - """ - A creator that overrides the `parent` arg of all accessed nodes to its own parent. - - Necessary so that `Workflow.create.Function(...)` returns an unowned function node, - while `some_workflow_instance.create.Function(...)` returns a function node owned - by the workflow instance. - """ - - def __init__(self, parent: Composite, creator: Creator): - self._parent = parent - self._creator = creator - - def __getattr__(self, item): - value = getattr(self._creator, item) - - try: - is_node_class = issubclass(value, Node) - except TypeError: - # issubclass complains if the value isn't even a class - is_node_class = False - - if is_node_class: - value = partial(value, parent=self._parent) - elif isinstance(value, NodePackage): - value = OwnedNodePackage(self._parent, value) - - return value - - -class OwnedNodePackage: - """ - A wrapper for node packages so that accessed node classes can have their parent - value automatically filled. - """ - - def __init__(self, parent: Composite, node_package: NodePackage): - self._parent = parent - self._node_package = node_package - - def __getattr__(self, item): - value = getattr(self._node_package, item) - if issubclass(value, Node): - value = partial(value, parent=self._parent) - return value diff --git a/pyiron_contrib/workflow/draw.py b/pyiron_contrib/workflow/draw.py deleted file mode 100644 index 636b2cdb7..000000000 --- a/pyiron_contrib/workflow/draw.py +++ /dev/null @@ -1,366 +0,0 @@ -""" -Functions for drawing the graph. -""" - -from __future__ import annotations - -from abc import ABC, abstractmethod -from typing import Literal, Optional, TYPE_CHECKING - -import graphviz -from matplotlib.colors import to_hex, to_rgb - -from pyiron_contrib.workflow.util import SeabornColors - -if TYPE_CHECKING: - from pyiron_contrib.workflow.channels import Channel as WorkflowChannel - from pyiron_contrib.workflow.io import DataIO, SignalIO - from pyiron_contrib.workflow.node import Node as WorkflowNode - - -def directed_graph(name, label, rankdir, color_start, color_end, gradient_angle): - """A shortcut method for instantiating the type of graphviz graph we want""" - digraph = graphviz.graphs.Digraph(name=name) - digraph.attr( - label=label, - compound="true", - rankdir=rankdir, - style="filled", - fillcolor=f"{color_start}:{color_end}", - gradientangle=gradient_angle, - ) - return digraph - - -def reverse_rankdir(rankdir: Literal["LR", "TB"]): - if rankdir == "LR": - return "TB" - elif rankdir == "TB": - return "LR" - else: - raise ValueError(f"Expected rankdir of 'LR' or 'TB' but got {rankdir}") - - -def blend_colours(color_a, color_b, fraction_a=0.5): - """Blends two hex code colours together""" - return to_hex( - tuple( - fraction_a * a + (1 - fraction_a) * b - for (a, b) in zip(to_rgb(color_a), to_rgb(color_b)) - ) - ) - - -def lighten_hex_color(color, lightness=0.7): - """Blends the given hex code color with pure white.""" - return blend_colours(SeabornColors.white, color, fraction_a=lightness) - - -class WorkflowGraphvizMap(ABC): - """ - A parent class defining the interface for the graphviz representation of all our - workflow objects. - """ - - @property - @abstractmethod - def parent(self) -> WorkflowGraphvizMap | None: - pass - - @property - @abstractmethod - def name(self) -> str: - pass - - @property - @abstractmethod - def label(self) -> str: - pass - - @property - @abstractmethod - def graph(self) -> graphviz.graphs.Digraph: - pass - - @property - @abstractmethod - def color(self) -> str: - pass - - -class _Channel(WorkflowGraphvizMap, ABC): - """ - An abstract representation for channel objects, which are "nodes" in graphviz - parlance. - """ - - def __init__(self, parent: _IO, channel: WorkflowChannel): - self.channel = channel - self._parent = parent - self._name = self.parent.name + self.channel.label - self._label = self._build_label() - self.channel: WorkflowChannel = channel - - self.graph.node( - name=self.name, - label=self.label, - shape=self.shape, - color=self.color, - style="filled", - ) - - @property - @abstractmethod - def shape(self) -> str: - pass - - def _build_label(self): - label = self.channel.label - try: - if self.channel.type_hint is not None: - label += ": " + self.channel.type_hint.__name__ - except AttributeError: - pass # Signals have no type - return label - - @property - def parent(self) -> _IO | None: - return self._parent - - @property - def name(self) -> str: - return self._name - - @property - def label(self) -> str: - return self._label - - @property - def graph(self) -> graphviz.graphs.Digraph: - return self.parent.graph - - -class DataChannel(_Channel): - @property - def color(self) -> str: - return SeabornColors.orange - - @property - def shape(self) -> str: - return "oval" - - -class SignalChannel(_Channel): - @property - def color(self) -> str: - return SeabornColors.blue - - @property - def shape(self) -> str: - return "cds" - - -class _IO(WorkflowGraphvizMap, ABC): - """ - An abstract class for IO panels, which are represented as a "subgraph" in graphviz - parlance. - """ - - def __init__(self, parent: Node): - self._parent = parent - self.node: WorkflowNode = self.parent.node - self.data_io, self.signals_io = self._get_node_io() - self._name = self.parent.name + self.data_io.__class__.__name__ - self._label = self.data_io.__class__.__name__ - self._graph = directed_graph( - self.name, - self.label, - rankdir=reverse_rankdir(self.parent.rankdir), - color_start=self.color, - color_end=lighten_hex_color(self.color), - gradient_angle=self.gradient_angle, - ) - - self.channels = [ - SignalChannel(self, channel) for channel in self.signals_io - ] + [DataChannel(self, channel) for channel in self.data_io] - - self.parent.graph.subgraph(self.graph) - - @abstractmethod - def _get_node_io(self) -> tuple[DataIO, SignalIO]: - pass - - @property - @abstractmethod - def gradient_angle(self) -> str: - """Background fill colour angle in degrees""" - - @property - def parent(self) -> Node: - return self._parent - - @property - def name(self) -> str: - return self._name - - @property - def label(self) -> str: - return self._label - - @property - def graph(self) -> graphviz.graphs.Digraph: - return self._graph - - @property - def color(self) -> str: - return SeabornColors.gray - - def __len__(self): - return len(self.channels) - - -class Inputs(_IO): - def _get_node_io(self) -> tuple[DataIO, SignalIO]: - return self.node.inputs, self.node.signals.input - - @property - def gradient_angle(self) -> str: - return "0" - - -class Outputs(_IO): - def _get_node_io(self) -> tuple[DataIO, SignalIO]: - return self.node.outputs, self.node.signals.output - - @property - def gradient_angle(self) -> str: - return "180" - - -class Node(WorkflowGraphvizMap): - """ - A wrapper class to connect graphviz to our workflow nodes. The nodes are - represented by a "graph" or "subgraph" in graphviz parlance (depending on whether - the node being visualized is the top-most node or not). - - Visualized nodes show their label and type, and IO panels with label and type. - Colors and shapes are exploited to differentiate various node classes, input/output, - and data/signal channels. - - If the node is composite in nature and the `depth` argument is at least `1`, owned - children are also visualized (recursively with `depth = depth - 1`) inside the scope - of this node. - - Args: - node (pyiron_contrib.workflow.node.Node): The node to visualize. - parent (Optional[pyiron_contrib.workflow.draw.Node]): The visualization that - owns this visualization (if any). - depth (int): How deeply to decompose any child nodes beyond showing their IO. - rankdir ("LR" | "TB"): Use left-right or top-bottom graphviz `rankdir`. - """ - - def __init__( - self, - node: WorkflowNode, - parent: Optional[Node] = None, - depth: int = 1, - rankdir: Literal["LR", "TB"] = "LR", - ): - self.node = node - self._parent = parent - self._name = self.build_node_name() - self._label = self.node.label + ": " + self.node.__class__.__name__ - self.rankdir: Literal["LR", "TB"] = rankdir - self._graph = directed_graph( - self.name, - self.label, - rankdir=self.rankdir, - color_start=self.color, - color_end=lighten_hex_color(self.color), - gradient_angle="90", - ) - - self.inputs = Inputs(self) - self.outputs = Outputs(self) - self.graph.edge( - self.inputs.channels[0].name, self.outputs.channels[0].name, style="invis" - ) - - if depth > 0: - try: - self._connect_owned_nodes(depth) - except AttributeError: - # Only composite nodes have their own nodes attribute - pass - - if self.parent is not None: - self.parent.graph.subgraph(self.graph) - - def _channel_bicolor(self, start_channel, end_channel): - return f"{start_channel.color};0.5:{end_channel.color};0.5" - - def _connect_owned_nodes(self, depth): - nodes = [Node(node, self, depth - 1) for node in self.node.nodes.values()] - internal_inputs = [ - channel for node in nodes for channel in node.inputs.channels - ] - internal_outputs = [ - channel for node in nodes for channel in node.outputs.channels - ] - - # Loop to check for internal node output --> internal node input connections - for output_channel in internal_outputs: - for input_channel in internal_inputs: - if input_channel.channel in output_channel.channel.connections: - self.graph.edge( - output_channel.name, - input_channel.name, - color=self._channel_bicolor(output_channel, input_channel), - ) - - # Loop to check for macro input --> internal node input connections - self._connect_matching(self.inputs.channels, internal_inputs) - # Loop to check for macro input --> internal node input connections - self._connect_matching(internal_outputs, self.outputs.channels) - - def _connect_matching(self, sources: list[_Channel], destinations: list[_Channel]): - """ - Draw an edge between two graph channels whose workflow channels are the same - """ - for source in sources: - for destination in destinations: - if source.channel is destination.channel: - self.graph.edge( - source.name, - destination.name, - color=self._channel_bicolor(source, destination), - ) - - def build_node_name(self, suffix=""): - if self.parent is not None: - # Recursively prepend parent labels to get a totally unique label string - # (inside the scope of this graph) - return self.parent.build_node_name(suffix=suffix + self.node.label) - else: - return "cluster" + self.node.label + suffix - - @property - def parent(self) -> Node | None: - return self._parent - - @property - def name(self) -> str: - return self._name - - @property - def label(self) -> str: - return self._label - - @property - def graph(self) -> graphviz.graphs.Digraph: - return self._graph - - @property - def color(self) -> str: - return self.node.color diff --git a/pyiron_contrib/workflow/files.py b/pyiron_contrib/workflow/files.py deleted file mode 100644 index 05c6af038..000000000 --- a/pyiron_contrib/workflow/files.py +++ /dev/null @@ -1,99 +0,0 @@ -from pathlib import Path - - -def delete_files_and_directories_recursively(path): - if not path.exists(): - return - for item in path.rglob("*"): - if item.is_file(): - item.unlink() - else: - delete_files_and_directories_recursively(item) - path.rmdir() - - -def categorize_folder_items(folder_path): - types = [ - "dir", - "file", - "mount", - "symlink", - "block_device", - "char_device", - "fifo", - "socket", - ] - results = {t: [] for t in types} - - for item in folder_path.iterdir(): - for tt in types: - try: - if getattr(item, f"is_{tt}")(): - results[tt].append(str(item)) - except NotImplementedError: - pass - return results - - -class DirectoryObject: - def __init__(self, directory): - self.path = Path(directory) - self.create() - - def create(self): - self.path.mkdir(parents=True, exist_ok=True) - - def delete(self): - delete_files_and_directories_recursively(self.path) - - def list_content(self): - return categorize_folder_items(self.path) - - def __len__(self): - return sum([len(cc) for cc in self.list_content().values()]) - - def __repr__(self): - return f"DirectoryObject(directory='{self.path}')\n{self.list_content()}" - - def get_path(self, file_name): - return self.path / file_name - - def file_exists(self, file_name): - return self.get_path(file_name).is_file() - - def write(self, file_name, content, mode="w"): - with self.get_path(file_name).open(mode=mode) as f: - f.write(content) - - def create_subdirectory(self, path): - return DirectoryObject(self.path / path) - - def create_file(self, file_name): - return FileObject(file_name, self) - - -class FileObject: - def __init__(self, file_name: str, directory: DirectoryObject): - self.directory = directory - self._file_name = file_name - - @property - def file_name(self): - return self._file_name - - @property - def path(self): - return self.directory.path / Path(self._file_name) - - def write(self, content, mode="x"): - self.directory.write(file_name=self.file_name, content=content, mode=mode) - - def read(self, mode="r"): - with open(self.path, mode=mode) as f: - return f.read() - - def is_file(self): - return self.directory.file_exists(self.file_name) - - def delete(self): - self.path.unlink() diff --git a/pyiron_contrib/workflow/function.py b/pyiron_contrib/workflow/function.py deleted file mode 100644 index cacc1b291..000000000 --- a/pyiron_contrib/workflow/function.py +++ /dev/null @@ -1,680 +0,0 @@ -from __future__ import annotations - -import inspect -import warnings -from functools import partialmethod -from typing import get_args, get_type_hints, Optional, TYPE_CHECKING - -from pyiron_contrib.workflow.channels import InputData, OutputData, NotData -from pyiron_contrib.workflow.has_channel import HasChannel -from pyiron_contrib.workflow.io import Inputs, Outputs, Signals -from pyiron_contrib.workflow.node import Node -from pyiron_contrib.workflow.output_parser import ParseOutput -from pyiron_contrib.workflow.util import SeabornColors - -if TYPE_CHECKING: - from pyiron_contrib.workflow.composite import Composite - from pyiron_contrib.workflow.workflow import Workflow - - -class Function(Node): - """ - Function nodes wrap an arbitrary python function. - Node IO, including type hints, is generated automatically from the provided - function. - Input data for the wrapped function can be provided as any valid combination of - `*arg` and `**kwarg` at both initialization and on calling the node. - - On running, the function node executes this wrapped function with its current input - and uses the results to populate the node output. - - Function nodes must be instantiated with a callable to deterimine their function, - and a string to name each returned value of that callable. (If you really want to - return a tuple, just have multiple return values but only one output label -- there - is currently no way to mix-and-match, i.e. to have multiple return values at least - one of which is a tuple.) - - The node label (unless otherwise provided), IO channel names, IO types, and input - defaults for the node are produced _automatically_ from introspection of the node - function. - Explicit output labels can be provided to modify the number of return values (from - $N$ to 1 in case you _want_ a tuple returned) and to dodge constraints on the - automatic scraping routine (namely, that there be _at most_ one `return` - expression). - (Additional properties like storage priority and ontological type are forthcoming - as kwarg dictionaries with keys corresponding to the channel labels (i.e. the node - arguments of the node function, or the output labels provided).) - - Actual function node instances can either be instances of the base node class, in - which case the callable node function *must* be provided OR they can be instances - of children of this class. - Those children may define some or all of the node behaviour at the class level, and - modify their signature accordingly so this is not available for alteration by the - user, e.g. the node function and output labels may be hard-wired. - - Although not strictly enforced, it is a best-practice that where possible, function - nodes should be both functional (always returning the same output given the same - input) and idempotent (not modifying input data in-place, but creating copies where - necessary and returning new objects as output). - Further, functions with multiple return branches that return different types or - numbers of return values may or may not work smoothly, depending on the details. - - Output is updated in the `process_run_result` inside the parent class `finish_run` - call, such that output data gets pushed after the node stops running but before - then `ran` signal fires: run, process and push result, ran. - - After a node is instantiated, its input can be updated as `*args` and/or `**kwargs` - on call. - `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, in the case of `update()`, `None` if it is not `ready` to run. - - Args: - node_function (callable): The function determining the behaviour of the node. - label (str): The node's label. (Defaults to the node function's name.) - output_labels (Optional[str | list[str] | tuple[str]]): A name for each return - value of the node function OR a single label. (Default is None, which - scrapes output labels automatically from the source code of the wrapped - function.) This can be useful when returned values are not well named, e.g. - to make the output channel dot-accessible if it would otherwise have a label - that requires item-string-based access. Additionally, specifying a _single_ - label for a wrapped function that returns a tuple of values ensures that a - _single_ output channel (holding the tuple) is created, instead of one - channel for each return value. The default approach of extracting labels - from the function source code also requires that the function body contain - _at most_ one `return` expression, so providing explicit labels can be used - to circumvent this (at your own risk). - **kwargs: Any additional keyword arguments whose keyword matches the label of an - input channel will have their value assigned to that channel. - - Attributes: - inputs (Inputs): A collection of input data channels. - outputs (Outputs): A collection of output data channels. - signals (Signals): A holder for input and output collections of signal channels. - ready (bool): All input reports ready, node is not running or failed. - running (bool): Currently running. - failed (bool): An exception was thrown when executing the node function. - connected (bool): Any IO channel has at least one connection. - fully_connected (bool): Every IO channel has at least one connection. - - Methods: - update: If your input is ready, will run the engine. - run: Parse and process the input, execute the engine, process the results and - update the output. - disconnect: Disconnect all data and signal IO connections. - update_input: Allows input channels' values to be updated without any running. - - Examples: - At the most basic level, to use nodes all we need to do is provide the - `Function` class with a function and labels for its output, like so: - >>> from pyiron_contrib.workflow.function import Function - >>> - >>> def mwe(x, y): - ... return x+1, y-1 - >>> - >>> plus_minus_1 = Function(mwe) - >>> - >>> print(plus_minus_1.outputs["x+1"]) - - - There is no output because we haven't given our function any input, it has - no defaults, and we never ran it! So outputs have the channel default value of - `NotData` -- a special non-data class (since `None` is sometimes a meaningful - value in python). - - We'll run into a hiccup if we try to set only one of the inputs and force the - run: - >>> plus_minus_1.inputs.x = 2 - >>> plus_minus_1.run() - TypeError: unsupported operand type(s) for -: 'type' and 'int' - - This is because the second input (`y`) still has no input value, so we can't do - the sum between `NotData` and `2`. - - Once we update `y`, all the input is ready we will be allowed to proceed to a - `run()` call, which succeeds and updates the output. - The final thing we need to do is disable the `failed` status we got from our - last run call - >>> plus_minus_1.failed = False - >>> plus_minus_1.inputs.y = 3 - >>> plus_minus_1.run() - >>> plus_minus_1.outputs.to_value_dict() - {'x+1': 3, 'y-1': 2} - - We can also, optionally, provide initial values for some or all of the input and - labels for the output: - >>> plus_minus_1 = Function(mwe, output_labels=("p1", "m1"), x=1) - >>> plus_minus_1.inputs.y = 2 - >>> out = plus_minus_1.run() - >>> out - (2, 1) - - Input data can be provided to both initialization and on call as ordered args - 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) - (3, 2) - - We can make our node even more sensible by adding type - hints (and, optionally, default values) when defining the function that the node - wraps. - The node will automatically figure out defaults and type hints for the IO - channels from inspection of the wrapped function. - - In this example, note the mixture of old-school (`typing.Union`) and new (`|`) - type hints as well as nested hinting with a union-type inside the tuple for the - return hint. - Our treatment of type hints is **not infinitely robust**, but covers a wide - variety of common use cases. - Note that getting "good" (i.e. dot-accessible) output labels can be achieved by - using good variable names and returning those variables instead of using - `output_labels`. - If we force the node to `run()` (or call it) with bad types, it will raise an - error. - But, if we use the gentler `update()`, it will check types first and simply - return `None` if the input is not all `ready`. - >>> from typing import Union - >>> - >>> def hinted_example( - ... x: Union[int, float], - ... y: int | float = 1 - ... ) -> tuple[int, int | float]: - ... p1, m1 = x+1, y-1 - ... return p1, m1 - >>> - >>> plus_minus_1 = Function(hinted_example, x="not an int") - >>> plus_minus_1.update() - >>> plus_minus_1.outputs.to_value_dict() - {'p1': , - 'm1': } - - Here, even though all the input has data, the node sees that some of it is the - wrong type and so the automatic updates don't proceed all the way to a run. - Note that the type hinting doesn't actually prevent us from assigning bad values - directly to the channel (although it will, by default, prevent connections - _between_ type-hinted channels with incompatible hints), but it _does_ stop the - node from running and throwing an error because it sees that the channel (and - thus node) is not ready - >>> plus_minus_1.inputs.x.value - 'not an int' - - >>> plus_minus_1.ready, plus_minus_1.inputs.x.ready, plus_minus_1.inputs.y.ready - (False, False, True) - - In these examples, we've instantiated nodes directly from the base `Function` - class, and populated their input directly with data. - In practice, these nodes are meant to be part of complex workflows; that means - both that you are likely to have particular nodes that get heavily re-used, and - that you need the nodes to pass data to each other. - - For reusable nodes, we want to create a sub-class of `Function` that fixes some - of the node behaviour -- usually the `node_function` and `output_labels`. - - This can be done most easily with the `node` decorator, which takes a function - and returns a node class: - >>> from pyiron_contrib.workflow.function import function_node - >>> - >>> @function_node(output_labels=("p1", "m1")) - ... def my_mwe_node( - ... x: int | float, y: int | float = 1 - ... ) -> tuple[int | float, int | float]: - ... return x+1, y-1 - >>> - >>> node_instance = my_mwe_node(x=0) - >>> node_instance(y=0) - (1, -1) - - Where we've passed the output labels and class arguments to the decorator, - and inital values to the newly-created node class (`my_mwe_node`) at - instantiation. - Because we provided a good initial value for `x`, we get our result right away. - - Using the decorator is the recommended way to create new node classes, but this - magic is just equivalent to these two more verbose ways of defining a new class. - The first is to override the `__init__` method directly: - >>> from typing import Literal, Optional - >>> - >>> class AlphabetModThree(Function): - ... def __init__( - ... self, - ... label: Optional[str] = None, - ... **kwargs - ... ): - ... super().__init__( - ... self.alphabet_mod_three, - ... label=label, - ... **kwargs - ... ) - ... - ... @staticmethod - ... def alphabet_mod_three(i: int) -> Literal["a", "b", "c"]: - ... letter = ["a", "b", "c"][i % 3] - ... return letter - - The second effectively does the same thing, but leverages python's - `functools.partialmethod` to do so much more succinctly. - In this example, note that the function is declared _before_ `__init__` is set, - so that it is available in the correct scope (above, we could place it - afterwards because we were accessing it through self). - >>> from functools import partialmethod - >>> - >>> class Adder(Function): - ... @staticmethod - ... def adder(x: int = 0, y: int = 0) -> int: - ... sum = x + y - ... return sum - ... - ... __init__ = partialmethod( - ... Function.__init__, - ... adder, - ... ) - - Finally, let's put it all together by using both of these nodes at once. - Instead of setting input to a particular data value, we'll set it to - be another node's output channel, thus forming a connection. - Then we need to define the corresponding execution flow, which can be done - by directly connecting `.signals.input.run` and `.signals.output.ran` channels - just like we connect data channels, but can also be accomplished with some - syntactic sugar using the `>` operator. - When we update the upstream node, we'll see the result passed downstream: - >>> adder = Adder() - >>> alpha = AlphabetModThree(i=adder.outputs.sum) - >>> adder > alpha - >>> - >>> adder(x=1) - >>> print(alpha.outputs.letter) - "b" - >>> adder(y=1) - >>> print(alpha.outputs.letter) - "c" - >>> adder.inputs.x = 0 - >>> adder.inputs.y = 0 - >>> adder() - >>> print(alpha.outputs.letter) - "a" - - To see more details on how to use many nodes together, look at the - `Workflow` class. - - Comments: - - If you use the function argument `self` in the first position, the - whole node object is inserted there: - - >>> def with_self(self, x): - >>> ... - >>> return x - - For this function, you don't have the freedom to choose `self`, because - pyiron automatically sets the node object there (which is also the - reason why you do not see `self` in the list of inputs). - """ - - def __init__( - self, - node_function: callable, - *args, - label: Optional[str] = None, - parent: Optional[Composite] = None, - output_labels: Optional[str | list[str] | tuple[str]] = None, - **kwargs, - ): - super().__init__( - label=label if label is not None else node_function.__name__, - parent=parent, - # **kwargs, - ) - - self.node_function = node_function - - self._inputs = None - self._outputs = None - self._output_labels = self._get_output_labels(output_labels) - # TODO: Parse output labels from the node function in case output_labels is None - - self.signals = self._build_signal_channels() - self.update_input(*args, **kwargs) - - def _get_output_labels(self, output_labels: str | list[str] | tuple[str] | None): - """ - If output labels are provided, turn convert them to a list if passed as a - string and return them, else scrape them from the source channel. - - Note: When the user explicitly provides output channels, they are taking - responsibility that these are correct, e.g. in terms of quantity, order, etc. - """ - if output_labels is None: - return self._scrape_output_labels() - elif isinstance(output_labels, str): - return [output_labels] - else: - return output_labels - - def _scrape_output_labels(self): - """ - Inspect the source code to scrape out strings representing the returned values. - _Only_ works for functions with a single `return` expression in their body. - - Will return expressions and function calls just fine, thus best practice is to - create well-named variables and return those so that the output labels stay - dot-accessible. - """ - parsed_outputs = ParseOutput(self.node_function).output - return [] if parsed_outputs is None else parsed_outputs - - @property - def _input_args(self): - return inspect.signature(self.node_function).parameters - - @property - def inputs(self) -> Inputs: - if self._inputs is None: - self._inputs = Inputs(*self._build_input_channels()) - return self._inputs - - @property - def outputs(self) -> Outputs: - if self._outputs is None: - self._outputs = Outputs(*self._build_output_channels(*self._output_labels)) - return self._outputs - - def _build_input_channels(self): - channels = [] - type_hints = get_type_hints(self.node_function) - - for ii, (label, value) in enumerate(self._input_args.items()): - is_self = False - if label == "self": # `self` is reserved for the node object - if ii == 0: - is_self = True - else: - warnings.warn( - "`self` is used as an argument but not in the first" - " position, so it is treated as a normal function" - " argument. If it is to be treated as the node object," - " use it as a first argument" - ) - if label in self._init_keywords: - # We allow users to parse arbitrary kwargs as channel initialization - # So don't let them choose bad channel names - raise ValueError( - f"The Input channel name {label} is not valid. Please choose a " - f"name _not_ among {self._init_keywords}" - ) - - try: - type_hint = type_hints[label] - if is_self: - warnings.warn("type hint for self ignored") - except KeyError: - type_hint = None - - default = NotData # The standard default in DataChannel - if value.default is not inspect.Parameter.empty: - if is_self: - warnings.warn("default value for self ignored") - else: - default = value.default - - if not is_self: - channels.append( - InputData( - label=label, - node=self, - default=default, - type_hint=type_hint, - ) - ) - return channels - - @property - def _init_keywords(self): - return list(inspect.signature(self.__init__).parameters.keys()) - - def _build_output_channels(self, *return_labels: str): - try: - type_hints = get_type_hints(self.node_function)["return"] - if len(return_labels) > 1: - type_hints = get_args(type_hints) - if not isinstance(type_hints, tuple): - raise TypeError( - f"With multiple return labels expected to get a tuple of type " - f"hints, but got type {type(type_hints)}" - ) - if len(type_hints) != len(return_labels): - raise ValueError( - f"Expected type hints and return labels to have matching " - f"lengths, but got {len(type_hints)} hints and " - f"{len(return_labels)} labels: {type_hints}, {return_labels}" - ) - else: - # If there's only one hint, wrap it in a tuple so we can zip it with - # *return_labels and iterate over both at once - type_hints = (type_hints,) - except KeyError: - type_hints = [None] * len(return_labels) - - channels = [] - for label, hint in zip(return_labels, type_hints): - channels.append( - OutputData( - label=label, - node=self, - type_hint=hint, - ) - ) - - return channels - - @property - def on_run(self): - return self.node_function - - @property - 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 - - def process_run_result(self, function_output): - """ - Take the results of the node function, and use them to update the node output. - - By extracting this as a separate method, we allow the node to pass the actual - execution off to another entity and release the python process to do other - things. In such a case, this function should be registered as a callback - so that the node can finishing "running" and push its data forward when that - execution is finished. - """ - if len(self.outputs) == 0: - return - elif len(self.outputs) == 1: - function_output = (function_output,) - - for out, value in zip(self.outputs, function_output): - out.update(value) - - def _convert_input_args_and_kwargs_to_input_kwargs(self, *args, **kwargs): - reverse_keys = list(self._input_args.keys())[::-1] - if len(args) > len(reverse_keys): - raise ValueError( - f"Received {len(args)} positional arguments, but the node {self.label}" - f"only accepts {len(reverse_keys)} inputs." - ) - - positional_keywords = reverse_keys[-len(args) :] if len(args) > 0 else [] # -0: - if len(set(positional_keywords).intersection(kwargs.keys())) > 0: - raise ValueError( - f"Cannot use {set(positional_keywords).intersection(kwargs.keys())} " - f"as both positional _and_ keyword arguments; args {args}, kwargs " - f"{kwargs}, reverse_keys {reverse_keys}, positional_keyworkds " - f"{positional_keywords}" - ) - - for arg in args: - key = positional_keywords.pop() - kwargs[key] = arg - - return kwargs - - def update_input(self, *args, **kwargs) -> None: - """ - Match positional and keyword arguments to input channels and update input - values. - - Args: - *args: Interpreted in the same order as node function arguments. - **kwargs: input label - input value (including channels for connection) - pairs. - """ - kwargs = self._convert_input_args_and_kwargs_to_input_kwargs(*args, **kwargs) - return super().update_input(**kwargs) - - def __call__(self, *args, **kwargs) -> None: - kwargs = self._convert_input_args_and_kwargs_to_input_kwargs(*args, **kwargs) - return super().__call__(**kwargs) - - def to_dict(self): - return { - "label": self.label, - "ready": self.ready, - "connected": self.connected, - "fully_connected": self.fully_connected, - "inputs": self.inputs.to_dict(), - "outputs": self.outputs.to_dict(), - "signals": self.signals.to_dict(), - } - - @property - def color(self) -> str: - """For drawing the graph""" - return SeabornColors.green - - -class SingleValue(Function, HasChannel): - """ - A node that _must_ return only a single value. - - Attribute and item access is modified to finally attempt access on the output value. - Note that this means any attributes/method available on the output value become - available directly at the node level (at least those which don't conflict with the - existing node namespace). - """ - - def __init__( - self, - node_function: callable, - *args, - label: Optional[str] = None, - parent: Optional[Workflow] = None, - output_labels: Optional[str | list[str] | tuple[str]] = None, - **kwargs, - ): - super().__init__( - node_function, - *args, - label=label, - parent=parent, - output_labels=output_labels, - **kwargs, - ) - - def _get_output_labels(self, output_labels: str | list[str] | tuple[str] | None): - output_labels = super()._get_output_labels(output_labels) - if len(output_labels) > 1: - raise ValueError( - f"{self.__class__.__name__} must only have a single return value, but " - f"got multiple output labels: {output_labels}" - ) - return output_labels - - @property - def single_value(self): - return self.outputs[self.outputs.labels[0]].value - - @property - def channel(self) -> OutputData: - """The channel for the single output""" - return list(self.outputs.channel_dict.values())[0] - - @property - def color(self) -> str: - """For drawing the graph""" - return SeabornColors.cyan - - def __getitem__(self, item): - return self.single_value.__getitem__(item) - - def __getattr__(self, item): - return getattr(self.single_value, item) - - def __repr__(self): - return self.single_value.__repr__() - - def __str__(self): - return f"{self.label} ({self.__class__.__name__}) output single-value: " + str( - self.single_value - ) - - -def function_node(output_labels=None): - """ - A decorator for dynamically creating node classes from functions. - - Decorates a function. - Returns a `Function` subclass whose name is the camel-case version of the function - node, and whose signature is modified to exclude the node function and output labels - (which are explicitly defined in the process of using the decorator). - - Optionally takes any keyword arguments of `Function`. - """ - - def as_node(node_function: callable): - return type( - node_function.__name__.title().replace("_", ""), # fnc_name to CamelCase - (Function,), # Define parentage - { - "__init__": partialmethod( - Function.__init__, - node_function, - output_labels=output_labels, - ) - }, - ) - - return as_node - - -def single_value_node(output_labels=None): - """ - A decorator for dynamically creating fast node classes from functions. - - Unlike normal nodes, fast nodes _must_ have default values set for all their inputs. - - Optionally takes any keyword arguments of `SingleValueNode`. - """ - - def as_single_value_node(node_function: callable): - return type( - node_function.__name__.title().replace("_", ""), # fnc_name to CamelCase - (SingleValue,), # Define parentage - { - "__init__": partialmethod( - SingleValue.__init__, - node_function, - output_labels=output_labels, - ) - }, - ) - - return as_single_value_node diff --git a/pyiron_contrib/workflow/has_channel.py b/pyiron_contrib/workflow/has_channel.py deleted file mode 100644 index f395cfad0..000000000 --- a/pyiron_contrib/workflow/has_channel.py +++ /dev/null @@ -1,27 +0,0 @@ -# coding: utf-8 -# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department -# Distributed under the terms of "New BSD License", see the LICENSE file. - -from __future__ import annotations - -from abc import ABC, abstractmethod -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from pyiron_contrib.workflow.channels import Channel - - -class HasChannel(ABC): - """ - A mix-in class for use with the `Channel` class. - A `Channel` is able to (attempt to) connect to any child instance of `HasConnection` - by looking at its `connection` attribute. - - This is useful for letting channels attempt to connect to non-channel objects - directly by pointing them to some channel that object holds. - """ - - @property - @abstractmethod - def channel(self) -> Channel: - pass diff --git a/pyiron_contrib/workflow/has_to_dict.py b/pyiron_contrib/workflow/has_to_dict.py deleted file mode 100644 index a78bc4271..000000000 --- a/pyiron_contrib/workflow/has_to_dict.py +++ /dev/null @@ -1,17 +0,0 @@ -from abc import ABC, abstractmethod -from json import dumps - - -class HasToDict(ABC): - @abstractmethod - def to_dict(self): - pass - - def _repr_json_(self): - return self.to_dict() - - def info(self): - print(dumps(self.to_dict(), indent=2)) - - def __str__(self): - return str(self.to_dict()) diff --git a/pyiron_contrib/workflow/interfaces.py b/pyiron_contrib/workflow/interfaces.py deleted file mode 100644 index e604588c2..000000000 --- a/pyiron_contrib/workflow/interfaces.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -Container classes for giving access to various workflow objects and tools -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -from pyiron_base.interfaces.singleton import Singleton - -from pyiron_contrib.executors import CloudpickleProcessPoolExecutor -from pyiron_contrib.workflow.function import ( - Function, - SingleValue, - function_node, - single_value_node, -) - -if TYPE_CHECKING: - from pyiron_contrib.workflow.node import Node - - -class Creator(metaclass=Singleton): - """ - A container class for providing access to various workflow objects. - Handles the registration of new node packages and, by virtue of being a singleton, - makes them available to all composite nodes holding a creator. - """ - - def __init__(self): - self.CloudpickleProcessPoolExecutor = CloudpickleProcessPoolExecutor - - self.Function = Function - self.SingleValue = SingleValue - - # Avoid circular imports by delaying import for children of Composite - self._macro = None - self._workflow = None - self._meta = None - - @property - def Macro(self): - if self._macro is None: - from pyiron_contrib.workflow.macro import Macro - - self._macro = Macro - return self._macro - - @property - def Workflow(self): - if self._workflow is None: - from pyiron_contrib.workflow.workflow import Workflow - - self._workflow = Workflow - return self._workflow - - @property - def standard(self): - try: - return self._standard - except AttributeError: - from pyiron_contrib.workflow.node_library.standard import nodes - - self.register("_standard", *nodes) - return self._standard - - @property - def atomistics(self): - try: - return self._atomistics - except AttributeError: - from pyiron_contrib.workflow.node_library.atomistics import nodes - - self.register("_atomistics", *nodes) - return self._atomistics - - @property - def meta(self): - if self._meta is None: - from pyiron_contrib.workflow.meta import meta_nodes - - self._meta = meta_nodes - return self._meta - - def register(self, domain: str, *nodes: list[type[Node]]): - if domain in self.__dir__(): - raise AttributeError(f"{domain} is already an attribute of {self}") - from pyiron_contrib.workflow.node_package import NodePackage - - setattr(self, domain, NodePackage(*nodes)) - - -class Wrappers(metaclass=Singleton): - """ - A container class giving access to the decorators that transform functions to nodes. - """ - - def __init__(self): - self.function_node = function_node - self.single_value_node = single_value_node - - # Avoid circular imports by delaying import when wrapping children of Composite - self._macro_node = None - - @property - def macro_node(self): - if self._macro_node is None: - from pyiron_contrib.workflow.macro import macro_node - - self._macro_node = macro_node - return self._macro_node diff --git a/pyiron_contrib/workflow/io.py b/pyiron_contrib/workflow/io.py deleted file mode 100644 index 442dee7eb..000000000 --- a/pyiron_contrib/workflow/io.py +++ /dev/null @@ -1,272 +0,0 @@ -""" -Collections of channel objects. -""" - -from __future__ import annotations - -from abc import ABC, abstractmethod - -from pyiron_contrib.workflow.channels import ( - Channel, - DataChannel, - InputData, - OutputData, - SignalChannel, - InputSignal, - OutputSignal, -) -from pyiron_contrib.workflow.has_channel import HasChannel -from pyiron_contrib.workflow.has_to_dict import HasToDict -from pyiron_contrib.workflow.util import DotDict, logger - - -class IO(HasToDict, ABC): - """ - IO is a convenience layer for holding and accessing multiple input/output channels. - It allows key and dot-based access to the underlying channels. - Channels can also be iterated over, and there are a number of helper functions to - alter the properties of or check the status of all the channels at once. - - A new channel can be assigned as an attribute of an IO collection, as long as it - matches the channel's type (e.g. `OutputChannel` for `Outputs`, `InputChannel` - for `Inputs`, etc...). - - When assigning something to an attribute holding an existing channel, if the - assigned object is a `Channel`, then an attempt is made to make a `connection` - between the two channels, otherwise we fall back on a value assignment that must - be defined in child classes under `_assign_value_to_existing_channel`, i.e. - >>> some_io.some_existing_channel = 5 - - is equivalent to - >>> some_io._assign_value_to_existing_channel( - ... some_io["some_existing_channel"], 5 - ... ) - - and - >>> some_io.some_existing_channel = some_other_channel - - is equivalent to - >>> some_io.some_existing_channel.connect(some_other_channel) - """ - - def __init__(self, *channels: Channel): - self.__dict__["channel_dict"] = DotDict( - { - channel.label: channel - for channel in channels - if isinstance(channel, self._channel_class) - } - ) - - @property - @abstractmethod - def _channel_class(self) -> type(Channel): - pass - - @abstractmethod - def _assign_a_non_channel_value(self, channel: Channel, value) -> None: - """What to do when some non-channel value gets assigned to a channel""" - pass - - def __getattr__(self, item) -> Channel: - try: - return self.channel_dict[item] - except KeyError: - # Raise an attribute error from getattr to make sure hasattr works well! - raise AttributeError( - f"Could not find attribute {item} on {self.__class__.__name__} object " - f"nor in its channels ({self.labels})" - ) - - def __setattr__(self, key, value): - if key in self.channel_dict.keys(): - self._assign_value_to_existing_channel(self.channel_dict[key], value) - elif isinstance(value, self._channel_class): - if key != value.label: - logger.info( - f"Assigning a channel with the label {value.label} to the io key " - f"{key}" - ) - self.channel_dict[key] = value - else: - raise TypeError( - f"Can only set Channel object or connect to existing channels, but the " - f"attribute {key} got assigned {value} of type {type(value)}" - ) - - def _assign_value_to_existing_channel(self, channel: Channel, value) -> None: - if isinstance(value, HasChannel): - channel.connect(value.channel) - else: - self._assign_a_non_channel_value(channel, value) - - def __getitem__(self, item) -> Channel: - return self.__getattr__(item) - - def __setitem__(self, key, value): - self.__setattr__(key, value) - - @property - def connections(self) -> list[Channel]: - """All the unique connections across all channels""" - return list( - set([connection for channel in self for connection in channel.connections]) - ) - - @property - def connected(self): - return any([c.connected for c in self]) - - @property - def fully_connected(self): - return all([c.connected for c in self]) - - def disconnect(self) -> list[tuple[Channel, Channel]]: - """ - Disconnect all connections that owned channels have. - - Returns: - [list[tuple[Channel, Channel]]]: A list of the pairs of channels that no - longer participate in a connection. - """ - destroyed_connections = [] - for c in self: - destroyed_connections.extend(c.disconnect_all()) - return destroyed_connections - - @property - def labels(self): - return list(self.channel_dict.keys()) - - def items(self): - return self.channel_dict.items() - - def __iter__(self): - return self.channel_dict.values().__iter__() - - def __len__(self): - return len(self.channel_dict) - - def __dir__(self): - return set(super().__dir__() + self.labels) - - def __str__(self): - return f"{self.__class__.__name__} {self.labels}" - - def to_dict(self): - return { - "label": self.__class__.__name__, - "connected": self.connected, - "fully_connected": self.fully_connected, - "channels": {l: c.to_dict() for l, c in self.channel_dict.items()}, - } - - -class DataIO(IO, ABC): - """ - Extends the base IO class with helper methods relevant to data channels. - """ - - def _assign_a_non_channel_value(self, channel: DataChannel, value) -> None: - channel.update(value) - - def to_value_dict(self): - return {label: channel.value for label, channel in self.channel_dict.items()} - - @property - def ready(self): - return all([c.ready for c in self]) - - def to_dict(self): - d = super().to_dict() - d["ready"] = self.ready - return d - - -class Inputs(DataIO): - @property - def _channel_class(self) -> type(InputData): - return InputData - - def activate_strict_connections(self): - [c.activate_strict_connections() for c in self] - - def deactivate_strict_connections(self): - [c.deactivate_strict_connections() for c in self] - - -class Outputs(DataIO): - @property - def _channel_class(self) -> type(OutputData): - return OutputData - - -class SignalIO(IO, ABC): - def _assign_a_non_channel_value(self, channel: SignalChannel, value) -> None: - raise TypeError( - f"Tried to assign {value} ({type(value)} to the {channel.label}, which is " - f"already a {type(channel)}. Only other signal channels may be connected " - f"in this way." - ) - - -class InputSignals(SignalIO): - @property - def _channel_class(self) -> type(InputSignal): - return InputSignal - - def disconnect_run(self) -> list[tuple[Channel, Channel]]: - try: - return self.run.disconnect_all() - except AttributeError: - return [] - - -class OutputSignals(SignalIO): - @property - def _channel_class(self) -> type(OutputSignal): - return OutputSignal - - -class Signals: - """ - A meta-container for input and output signal IO containers. - - Attributes: - input (InputSignals): An empty input signals IO container. - output (OutputSignals): An empty input signals IO container. - """ - - def __init__(self): - self.input = InputSignals() - self.output = OutputSignals() - - def disconnect(self) -> list[tuple[Channel, Channel]]: - """ - Disconnect all connections in input and output signals. - - Returns: - [list[tuple[Channel, Channel]]]: A list of the pairs of channels that no - longer participate in a connection. - """ - return self.input.disconnect() + self.output.disconnect() - - def disconnect_run(self) -> list[tuple[Channel, Channel]]: - return self.input.disconnect_run() - - @property - def connected(self): - return self.input.connected or self.output.connected - - @property - def fully_connected(self): - return self.input.fully_connected and self.output.fully_connected - - def to_dict(self): - return { - "input": self.input.to_dict(), - "output": self.output.to_dict(), - } - - def __str__(self): - return f"{str(self.input)}\n{str(self.output)}" diff --git a/pyiron_contrib/workflow/macro.py b/pyiron_contrib/workflow/macro.py deleted file mode 100644 index 36babd071..000000000 --- a/pyiron_contrib/workflow/macro.py +++ /dev/null @@ -1,224 +0,0 @@ -""" -A base class for macro nodes, which are composite like workflows but have a static -interface and are not intended to be internally modified after instantiation. -""" - -from __future__ import annotations - -from functools import partialmethod -from typing import Optional - -from pyiron_contrib.workflow.composite import Composite -from pyiron_contrib.workflow.io import Outputs, Inputs - - -class Macro(Composite): - """ - A macro is a composite node that holds a graph with a fixed interface, like a - pre-populated workflow that is the same every time you instantiate it. - - At instantiation, the macro uses a provided callable to build and wire the graph, - then builds a static IO interface for this graph. (By default, unconnected IO is - passed using the same formalism as workflows to combine node and channel names, but - this can be overriden to rename the channels in the IO panel and/or to expose - channels that already have an internal connection.) - - Like function nodes, initial values for input can be set using kwargs, and the node - will (by default) attempt to update at the end of the instantiation process. - - It is intended that subclasses override the initialization signature and provide - the graph creation directly from their own method. - - As with workflows, all DAG macros will determine their execution flow automatically, - if you have cycles in your data flow, or otherwise want more control over the - execution, all you need to do is specify the `node.signals.input.run` connections - and `starting_nodes` list yourself. - If only _one_ of these is specified, you'll get an error, but if you've provided - both then no further checks of their validity/reasonableness are performed, so be - careful. - - Examples: - Let's consider the simplest case of macros that just consecutively add 1 to - their input: - >>> from pyiron_contrib.workflow.macro import Macro - >>> - >>> def add_one(x): - ... result = x + 1 - ... return result - >>> - >>> def add_three_macro(macro): - ... macro.one = macro.create.SingleValue(add_one) - ... macro.two = macro.create.SingleValue(add_one, macro.one) - ... macro.three = macro.create.SingleValue(add_one, macro.two) - ... macro.one > macro.two > macro.three - ... macro.starting_nodes = [macro.one] - - In this case we had _no need_ to specify the execution order and starting nodes - --it's just an extremely simple DAG after all! -- but it's done here to - demonstrate the syntax. - - We can make a macro by passing this graph-building function (that takes a macro - as its first argument, i.e. `self` from the macro's perspective) to the `Macro` - class. Then, we can use it like a regular node! Just like a workflow, the - io is constructed from unconnected owned-node IO by combining node and channel - labels. - >>> macro = Macro(add_three_macro) - >>> out = macro(one__x=3) - >>> out.three__result - 6 - - If there's a particular macro we're going to use again and again, we might want - to consider making a new child class of `Macro` that overrides the - `graph_creator` arg such that the same graph is always created. We could - override `__init__` the normal way, but it's even faster to just use - `partialmethod`: - >>> from functools import partialmethod - >>> class AddThreeMacro(Macro): - ... def build_graph(self): - ... add_three_macro(self) - ... - ... __init__ = partialmethod( - ... Macro.__init__, - ... build_graph, - ... ) - >>> - >>> macro = AddThreeMacro() - >>> macro(one__x=0).three__result - 3 - - We can also nest macros, rename their IO, and provide access to - internally-connected IO by inputs and outputs maps: - >>> def nested_macro(macro): - ... macro.a = macro.create.SingleValue(add_one) - ... macro.b = macro.create.Macro(add_three_macro, one__x=macro.a) - ... macro.c = macro.create.SingleValue( - ... add_one, x=macro.b.outputs.three__result - ... ) - >>> - >>> macro = Macro( - ... nested_macro, - ... inputs_map={"a__x": "inp"}, - ... outputs_map={"c__result": "out", "b__three__result": "intermediate"}, - ... ) - >>> macro(inp=1) - {'intermediate': 5, 'out': 6} - - Macros and workflows automatically generate execution flows when their data - is acyclic. - Let's build a simple macro with two independent tracks: - >>> def modified_flow_macro(macro): - ... macro.a = macro.create.SingleValue(add_one, x=0) - ... macro.b = macro.create.SingleValue(add_one, x=0) - ... macro.c = macro.create.SingleValue(add_one, x=0) - >>> - >>> m = Macro(modified_start_macro) - >>> m.outputs.to_value_dict() - >>> m(a__x=1, b__x=2, c__x=3) - {'a__result': 2, 'b__result': 3, 'c__result': 4} - - We can override which nodes get used to start by specifying the `starting_nodes` - property. - If we do this we also need to provide at least one connection among the run - signals, but beyond that the code doesn't hold our hands. - Let's use this and then observe how the `a` sub-node no longer gets run: - >>> m.starting_nodes = [m.b] # At least one starting node - >>> m.b > m.c # At least one run signal - >>> m(a__x=1000, b__x=2000, c__x=3000) - {'a__result': 2, 'b__result': 2001, 'c__result': 3001} - - Note how the `a` node is no longer getting run, so the output is not updated! - Manually controlling execution flow is necessary for cyclic graphs (cf. the - while loop meta-node), but best to avoid when possible as it's easy to miss - intended connections in complex graphs. - """ - - def __init__( - self, - graph_creator: callable[[Macro], None], - label: Optional[str] = None, - parent: Optional[Composite] = None, - strict_naming: bool = True, - inputs_map: Optional[dict] = None, - outputs_map: Optional[dict] = None, - **kwargs, - ): - self._parent = None - super().__init__( - label=label if label is not None else graph_creator.__name__, - parent=parent, - strict_naming=strict_naming, - inputs_map=inputs_map, - outputs_map=outputs_map, - ) - graph_creator(self) - self._configure_graph_execution() - - self._inputs: Inputs = self._build_inputs() - self._outputs: Outputs = self._build_outputs() - - self.update_input(**kwargs) - - @property - def inputs(self) -> Inputs: - return self._inputs - - @property - def outputs(self) -> Outputs: - return self._outputs - - def _configure_graph_execution(self): - run_signals = self.disconnect_run() - - has_signals = len(run_signals) > 0 - has_starters = len(self.starting_nodes) > 0 - - if has_signals and has_starters: - # Assume the user knows what they're doing - self._reconnect_run(run_signals) - elif not has_signals and not has_starters: - # Automate construction of the execution graph - self.set_run_signals_to_dag_execution() - else: - raise ValueError( - f"The macro '{self.label}' has {len(run_signals)} run signals " - f"internally and {len(self.starting_nodes)} starting nodes. Either " - f"the entire execution graph must be specified manually, or both run " - f"signals and starting nodes must be left entirely unspecified for " - f"automatic construction of the execution graph." - ) - - def _reconnect_run(self, run_signal_pairs_to_restore): - self.disconnect_run() - for pairs in run_signal_pairs_to_restore: - pairs[0].connect(pairs[1]) - - def to_workfow(self): - raise NotImplementedError - - -def macro_node(**node_class_kwargs): - """ - A decorator for dynamically creating macro classes from graph-creating functions. - - Decorates a function. - Returns a `Macro` subclass whose name is the camel-case version of the - graph-creating function, and whose signature is modified to exclude this function - and provided kwargs. - - Optionally takes any keyword arguments of `Macro`. - """ - - def as_node(graph_creator: callable[[Macro], None]): - return type( - graph_creator.__name__.title().replace("_", ""), # fnc_name to CamelCase - (Macro,), # Define parentage - { - "__init__": partialmethod( - Macro.__init__, - graph_creator, - **node_class_kwargs, - ) - }, - ) - - return as_node diff --git a/pyiron_contrib/workflow/meta.py b/pyiron_contrib/workflow/meta.py deleted file mode 100644 index 9c0bd3d6e..000000000 --- a/pyiron_contrib/workflow/meta.py +++ /dev/null @@ -1,326 +0,0 @@ -""" -Meta nodes are callables that create a node class instead of a node instance. -""" - -from __future__ import annotations - -from typing import Optional - -from pyiron_contrib.workflow.function import ( - Function, - SingleValue, - function_node, - single_value_node, -) -from pyiron_contrib.workflow.macro import Macro, macro_node -from pyiron_contrib.workflow.node import Node -from pyiron_contrib.workflow.util import DotDict - - -def list_to_output(length: int, **node_class_kwargs) -> type[Function]: - """ - A meta-node that returns a node class with `length` input channels and - maps these to a single output channel with type `list`. - """ - - def _list_to_many(length: int): - template = f""" -def __list_to_many(l: list): - {"; ".join([f"out{i} = l[{i}]" for i in range(length)])} - return [{", ".join([f"out{i}" for i in range(length)])}] - """ - exec(template) - return locals()["__list_to_many"] - - return function_node(**node_class_kwargs)(_list_to_many(length=length)) - - -def input_to_list(length: int, **node_class_kwargs) -> type[SingleValue]: - """ - A meta-node that returns a node class with `length` output channels and - maps an input list to these. - """ - - def _many_to_list(length: int): - template = f""" -def __many_to_list({", ".join([f"inp{i}=None" for i in range(length)])}): - return [{", ".join([f"inp{i}" for i in range(length)])}] - """ - exec(template) - return locals()["__many_to_list"] - - return single_value_node(**node_class_kwargs)(_many_to_list(length=length)) - - -def for_loop( - loop_body_class: type[Node], - length: int, - iterate_on: str | tuple[str] | list[str], - # TODO: -) -> type[Macro]: - """ - An _extremely rough_ first draft of a for-loop meta-node. - - Takes a node class, how long the loop should be, and which input(s) of the provided - node class should be looped over (given as strings of the channel labels) and - builds a macro that - - Makes copies of the provided node class, i.e. the "body node" - - For each input channel specified to "loop over", creates a list-to-many node and - connects each of its outputs to their respective body node inputs - - For all other inputs, makes a 1:1 node and connects its output to _all_ of the - body nodes - - Relables the macro IO to match the passed node class IO so that list-ified IO - (i.e. the specified input and all output) is all caps - - Examples: - >>> import numpy as np - >>> from pyiron_contrib.workflow import Workflow - >>> - >>> bulk_loop = Workflow.create.meta.for_loop( - ... Workflow.create.atomistics.Bulk, - ... 5, - ... iterate_on = ("a",), - ... )() - >>> - >>> [ - ... struct.cell.volume for struct in bulk_loop( - ... name="Al", # Sent equally to each body node - ... A=np.linspace(3.9, 4.1, 5).tolist(), # Distributed across body nodes - ... ).STRUCTURE - ... ] - [14.829749999999995, - 15.407468749999998, - 15.999999999999998, - 16.60753125, - 17.230249999999995] - - TODO: - - Refactor like crazy, it's super hard to read and some stuff is too hard-coded - - Give some sort of access to flow control?? - - How to handle passing executors to the children? Maybe this is more - generically a Macro question? - - Is it possible to somehow dynamically adapt the held graph depending on the - length of the input values being iterated over? Tricky to keep IO well defined - - Allow a different mode, or make a different meta node, that makes all possible - pairs of body nodes given the input being looped over instead of just `length` - - Provide enter and exit magic methods so we can `for` or `with` this fancy-like - """ - iterate_on = [iterate_on] if isinstance(iterate_on, str) else iterate_on - - def make_loop(macro): - macro.inputs_map = {} - macro.outputs_map = {} - body_nodes = [] - - # Parallelize over body nodes - for n in range(length): - body_nodes.append( - macro.add(loop_body_class(label=f"{loop_body_class.__name__}_{n}")) - ) - - # Make input interface - for label, inp in body_nodes[0].inputs.items(): - # Don't rely on inp.label directly, since inputs may be a Composite IO - # panel that has a different key for this input channel than its label - - # Scatter a list of inputs to each node separately - if label in iterate_on: - interface = list_to_output(length)( - parent=macro, - label=label.upper(), - output_labels=[ - f"{loop_body_class.__name__}__{inp.label}_{i}" - for i in range(length) - ], - l=[inp.default] * length, - ) - # Connect each body node input to the input interface's respective output - for body_node, out in zip(body_nodes, interface.outputs): - body_node.inputs[label] = out - macro.inputs_map[f"{interface.label}__l"] = interface.label - # TODO: Don't hardcode __l - # Or distribute the same input to each node equally - else: - interface = macro.create.standard.UserInput( - label=label, output_labels=label, user_input=inp.default - ) - for body_node in body_nodes: - body_node.inputs[label] = interface - macro.inputs_map[f"{interface.label}__user_input"] = interface.label - # TODO: Don't hardcode __user_input - - # Make output interface: outputs to lists - for label, out in body_nodes[0].outputs.items(): - interface = input_to_list(length)( - parent=macro, - label=label.upper(), - output_labels=f"{loop_body_class.__name__}__{label}", - ) - # Connect each body node output to the output interface's respective input - for body_node, inp in zip(body_nodes, interface.inputs): - inp.connect(body_node.outputs[label]) - if body_node.executor is not None: - raise NotImplementedError( - "Right now the output interface gets run after each body node," - "if the body nodes can run asynchronously we need something " - "more clever than that!" - ) - macro.outputs_map[ - f"{interface.label}__{loop_body_class.__name__}__{label}" - ] = interface.label - # TODO: Don't manually copy the output label construction - - return macro_node()(make_loop) - - -def while_loop( - loop_body_class: type[Node], - condition_class: type[SingleValue], - internal_connection_map: dict[str, str], - inputs_map: Optional[dict[str, str]] = None, - outputs_map: Optional[dict[str, str]] = None, -) -> type[Macro]: - """ - An _extremely rough_ first draft of a for-loop meta-node. - - Takes body and condition node classes and builds a macro that makes a cyclic signal - connection between them and an "if" switch, i.e. when the body node finishes it - runs the condtion, which runs the switch, and as long as the condition result was - `True`, the switch loops back to run the body again. - We additionally allow four-tuples of (input node, input channel, output node, - output channel) labels to wire data connections inside the macro, e.g. to pass data - from the body to the condition. This is beastly syntax, but it will suffice for now. - Finally, you can set input and output maps as normal. - - Args: - loop_body_class (type[pyiron_contrib.workflow.node.Node]): The class for the - body of the while-loop. - condition_class (type[pyiron_contrib.workflow.function.SingleValue]): A single - value node returning a `bool` controlling the while loop exit condition - (exits on False) - internal_connection_map (list[tuple[str, str, str, str]]): String tuples - giving (input node, input channel, output node, output channel) labels - connecting channel pairs inside the macro. - inputs_map Optional[dict[str, str]]: The inputs map as usual for a macro. - outputs_map Optional[dict[str, str]]: The outputs map as usual for a macro. - Examples: - >>> from pyiron_contrib.workflow import Workflow - >>> - >>> @Workflow.wrap_as.single_value_node() - >>> def add(a, b): - ... print(f"{a} + {b} = {a + b}") - ... return a + b - >>> - >>> @Workflow.wrap_as.single_value_node() - >>> def less_than_ten(value): - ... return value < 10 - >>> - >>> AddWhile = Workflow.create.meta.while_loop( - ... loop_body_class=add, - ... condition_class=less_than_ten, - ... internal_connection_map=[ - ... ("Add", "a + b", "LessThanTen", "value"), - ... ("Add", "a + b", "Add", "a") - ... ], - ... inputs_map={"Add__a": "a", "Add__b": "b"}, - ... outputs_map={"Add__a + b": "total"} - ... ) - >>> - >>> wf = Workflow("do_while") - >>> wf.add_while = AddWhile() - >>> - >>> wf.inputs_map = { - ... "add_while__a": "a", - ... "add_while__b": "b" - ... } - >>> wf.outputs_map = {"add_while__total": "total"} - >>> - >>> print(f"Finally, {wf(a=1, b=2).total}") - 1 + 2 = 3 - 3 + 2 = 5 - 5 + 2 = 7 - 7 + 2 = 9 - 9 + 2 = 11 - Finally, 11 - - >>> import numpy as np - >>> from pyiron_contrib.workflow import Workflow - >>> - >>> np.random.seed(0) - >>> - >>> @Workflow.wrap_as.single_value_node("random") - >>> def random(length: int | None = None): - ... return np.random.random(length) - >>> - >>> @Workflow.wrap_as.single_value_node() - >>> def greater_than(x: float, threshold: float): - ... gt = x > threshold - ... symbol = ">" if gt else "<=" - ... print(f"{x:.3f} {symbol} {threshold}") - ... return gt - >>> - >>> RandomWhile = Workflow.create.meta.while_loop( - ... loop_body_class=random, - ... condition_class=greater_than, - ... internal_connection_map=[("Random", "random", "GreaterThan", "x")], - ... outputs_map={"Random__random": "capped_result"} - ... ) - >>> - >>> # Define workflow - >>> - >>> wf = Workflow("random_until_small_enough") - >>> - >>> ## Wire together the while loop and its condition - >>> - >>> wf.random_while = RandomWhile() - >>> - >>> ## Give convenient labels - >>> wf.inputs_map = {"random_while__GreaterThan__threshold": "threshold"} - >>> wf.outputs_map = {"random_while__capped_result": "capped_result"} - >>> - >>> # Set a threshold and run - >>> print(f"Finally {wf(threshold=0.1).capped_result:.3f}") - 0.549 > 0.1 - 0.715 > 0.1 - 0.603 > 0.1 - 0.545 > 0.1 - 0.424 > 0.1 - 0.646 > 0.1 - 0.438 > 0.1 - 0.892 > 0.1 - 0.964 > 0.1 - 0.383 > 0.1 - 0.792 > 0.1 - 0.529 > 0.1 - 0.568 > 0.1 - 0.926 > 0.1 - 0.071 <= 0.1 - Finally 0.071 - """ - - def make_loop(macro): - body_node = macro.add(loop_body_class(label=loop_body_class.__name__)) - condition_node = macro.add(condition_class(label=condition_class.__name__)) - switch = macro.create.standard.If(label="switch") - - switch.inputs.condition = condition_node - for out_n, out_c, in_n, in_c in internal_connection_map: - macro.nodes[in_n].inputs[in_c] = macro.nodes[out_n].outputs[out_c] - - switch.signals.output.true > body_node > condition_node > switch - macro.starting_nodes = [body_node] - - macro.inputs_map = {} if inputs_map is None else inputs_map - macro.outputs_map = {} if outputs_map is None else outputs_map - - return macro_node()(make_loop) - - -meta_nodes = DotDict( - { - for_loop.__name__: for_loop, - input_to_list.__name__: input_to_list, - list_to_output.__name__: list_to_output, - while_loop.__name__: while_loop, - } -) diff --git a/pyiron_contrib/workflow/node.py b/pyiron_contrib/workflow/node.py deleted file mode 100644 index 49d6cff2b..000000000 --- a/pyiron_contrib/workflow/node.py +++ /dev/null @@ -1,395 +0,0 @@ -""" -A base class for objects that can form nodes in the graph representation of a -computational workflow. -""" - -from __future__ import annotations - -import warnings -from abc import ABC, abstractmethod -from concurrent.futures import Future -from typing import Any, Literal, Optional, TYPE_CHECKING - -from pyiron_contrib.executors import CloudpickleProcessPoolExecutor -from pyiron_contrib.workflow.draw import Node as GraphvizNode -from pyiron_contrib.workflow.files import DirectoryObject -from pyiron_contrib.workflow.has_to_dict import HasToDict -from pyiron_contrib.workflow.io import Signals, InputSignal, OutputSignal -from pyiron_contrib.workflow.util import SeabornColors - -if TYPE_CHECKING: - import graphviz - - from pyiron_base.jobs.job.extension.server.generic import Server - - from pyiron_contrib.workflow.composite import Composite - from pyiron_contrib.workflow.io import Inputs, Outputs - - -class Node(HasToDict, ABC): - """ - Nodes are elements of a computational graph. - They have input and output data channels that interface with the outside - world, and a callable that determines what they actually compute, and input and - output signal channels that can be used to customize the execution flow of their - graph; - Together these channels represent edges on the dual data and execution computational - graphs. - - Nodes can be run to force their computation, or more gently updated, which will - trigger a run only if all of the input is ready (i.e. channel values conform to - any type hints provided). - - Nodes may have a `parent` node that owns them as part of a sub-graph. - - Every node must be named with a `label`, and may use this label to attempt to create - a working directory in memory for itself if requested. - These labels also help to identify nodes in the wider context of (potentially - nested) computational graphs. - - 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. - These signal connections can be made manually by reference to the node signals - channel, or with the `>` symbol to indicate a flow of execution. This syntactic - sugar can be mixed between actual signal channels (output signal > input signal), - or nodes, but when referring to nodes it is always a shortcut to the `run`/`ran` - channels. - - 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`. - - 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 `run()`. - 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 flag attributes. - Their value is controlled automatically in the defined `run` and `finish_run` - methods. - - Nodes can be run on the main python process that owns them, or by assigning an - appropriate executor to their `executor` attribute. - In case they are run with an executor, their `future` attribute will be populated - with the resulting future object. - WARNING: Executors are currently only working when the node executable function does - not use `self`. - - This is an abstract class. - Children *must* define how `inputs` and `outputs` are constructed, and what will - happen `on_run`. - They may also override the `run_args` property to specify input passed to the - defined `on_run` method, and may add additional signal channels to the signals IO. - - # TODO: Everything with (de)serialization and executors for running on something - # other than the main python process. - - Attributes: - connected (bool): Whether _any_ of the IO (including signals) are connected. - failed (bool): Whether the node raised an error calling `run`. (Default - is False.) - fully_connected (bool): whether _all_ of the IO (including signals) are - connected. - future (concurrent.futures.Future | None): A futures object, if the node is - currently running or has already run using an executor. - inputs (pyiron_contrib.workflow.io.Inputs): **Abstract.** Children must define - a property returning an `Inputs` object. - label (str): A name for the node. - outputs (pyiron_contrib.workflow.io.Outputs): **Abstract.** Children must define - a property returning an `Outputs` object. - parent (pyiron_contrib.workflow.composite.Composite | None): The parent object - owning this, if any. - ready (bool): Whether the inputs are all ready and the node is neither - already running nor already failed. - running (bool): Whether the node has called `run` and has not yet - received output from this call. (Default is False.) - server (Optional[pyiron_base.jobs.job.extension.server.generic.Server]): A - server object for computing things somewhere else. Default (and currently - _only_) behaviour is to compute things on the main python process owning - the node. - signals (pyiron_contrib.workflow.io.Signals): A container for input and output - signals, which are channels for controlling execution flow. By default, has - a `signals.inputs.run` channel which has a callback to the `run` method, - and `signals.outputs.ran` which should be called at when the `run` method - is finished. - Additional signal channels in derived classes can be added to - `signals.inputs` and `signals.outputs` after this mixin class is - initialized. - - Methods: - disconnect: Remove all connections, including signals. - draw: Use graphviz to visualize the node, its IO and, if composite in nature, - its internal structure. - on_run: **Abstract.** Do the thing. - run: A wrapper to handle all the infrastructure around executing `on_run`. - """ - - def __init__( - self, - label: str, - *args, - parent: Optional[Composite] = None, - **kwargs, - ): - """ - A mixin class for objects that can form nodes in the graph representation of a - computational workflow. - - Args: - label (str): A name for this node. - *args: Arguments passed on with `super`. - **kwargs: Keyword arguments passed on with `super`. - """ - super().__init__(*args, **kwargs) - self.label: str = label - self.parent = parent - if parent is not None: - parent.add(self) - self.running = False - self.failed = False - # TODO: Replace running and failed with a state object - self._server: Server | None = ( - None # Or "task_manager" or "executor" -- we'll see what's best - ) - # TODO: Move from a traditional "sever" to a tinybase "executor" - # TODO: Provide support for actually computing stuff with the server/executor - self.signals = self._build_signal_channels() - self._working_directory = None - self.executor: None | CloudpickleProcessPoolExecutor = None - self.future: None | Future = None - - @property - @abstractmethod - def inputs(self) -> Inputs: - pass - - @property - @abstractmethod - def outputs(self) -> Outputs: - pass - - @property - @abstractmethod - def on_run(self) -> callable[..., Any | tuple]: - """ - What the node actually does! - """ - pass - - @property - def run_args(self) -> dict: - """ - Any data needed for `on_run`, will be passed as **kwargs. - """ - return {} - - def process_run_result(self, run_output: Any | tuple) -> None: - """ - What to _do_ with the results of `on_run` once you have them. - - Args: - run_output (tuple): The results of a `self.on_run(self.run_args)` call. - """ - pass - - 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 - computing resources. - """ - if self.running: - raise RuntimeError(f"{self.label} is already running") - - self.running = True - self.failed = False - - if self.executor is None: - try: - run_output = self.on_run(**self.run_args) - except Exception as e: - self.running = False - self.failed = True - raise e - 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 " - "the main python process or with a " - "pyiron_contrib.workflow.util.CloudpickleProcessPoolExecutor." - ) - - def finish_run(self, run_output: tuple | Future) -> Any | tuple: - """ - Switch the node status, process the run result, then fire the ran signal. - - By extracting this as a separate method, we allow the node to pass the actual - execution off to another entity and release the python process to do other - things. In such a case, this function should be registered as a callback - so that the node can finish "running" and, e.g. push its data forward when that - execution is finished. In such a case, a `concurrent.futures.Future` object is - expected back and must be unpacked. - """ - if isinstance(run_output, Future): - run_output = run_output.result() - - self.running = False - try: - self.process_run_result(run_output) - self.signals.output.ran() - return run_output - except Exception as e: - self.failed = True - raise e - - def _build_signal_channels(self) -> Signals: - signals = Signals() - signals.input.run = InputSignal("run", self, self.run) - signals.output.ran = OutputSignal("ran", self) - return signals - - def update(self) -> Any | tuple | Future | None: - if self.ready: - return self.run() - - @property - def working_directory(self): - if self._working_directory is None: - if self.parent is not None and hasattr(self.parent, "working_directory"): - parent_dir = self.parent.working_directory - self._working_directory = parent_dir.create_subdirectory(self.label) - else: - self._working_directory = DirectoryObject(self.label) - return self._working_directory - - @property - def server(self) -> Server | None: - return self._server - - @server.setter - def server(self, server: Server | None): - self._server = server - - def disconnect(self): - """ - Disconnect all connections belonging to inputs, outputs, and signals channels. - - Returns: - [list[tuple[Channel, Channel]]]: A list of the pairs of channels that no - longer participate in a connection. - """ - destroyed_connections = [] - destroyed_connections.extend(self.inputs.disconnect()) - destroyed_connections.extend(self.outputs.disconnect()) - destroyed_connections.extend(self.signals.disconnect()) - return destroyed_connections - - @property - def ready(self) -> bool: - return not (self.running or self.failed) and self.inputs.ready - - @property - def connected(self) -> bool: - return self.inputs.connected or self.outputs.connected or self.signals.connected - - @property - def fully_connected(self): - return ( - self.inputs.fully_connected - and self.outputs.fully_connected - and self.signals.fully_connected - ) - - def update_input(self, **kwargs) -> None: - """ - Match keywords to input channel labels and update input values. - - Args: - **kwargs: input label - input value (including channels for connection) - pairs. - """ - for k, v in kwargs.items(): - if k in self.inputs.labels: - self.inputs[k] = v - else: - warnings.warn( - f"The keyword '{k}' was not found among input labels. If you are " - f"trying to update a node keyword, please use attribute assignment " - f"directly instead of calling" - ) - - def __call__(self, **kwargs) -> None: - self.update_input(**kwargs) - return self.run() - - @property - def color(self) -> str: - """A hex code color for use in drawing.""" - return SeabornColors.white - - def draw( - self, depth: int = 1, rankdir: Literal["LR", "TB"] = "LR" - ) -> graphviz.graphs.Digraph: - """ - Draw the node structure. - - Args: - depth (int): How deeply to decompose the representation of composite nodes - to reveal their inner structure. (Default is 1, which will show owned - nodes if _this_ is a composite node, but all children will be drawn - at the level of showing their IO only.) A depth value greater than the - max depth of the node will have no adverse side effects. - rankdir ("LR" | "TB"): Use left-right or top-bottom graphviz `rankdir` to - orient the flow of the graph. - - Returns: - (graphviz.graphs.Digraph): The resulting graph object. - - Note: - The graphviz docs will elucidate all the possibilities of what to do with - the returned object, but the thing you are most likely to need is the - `render` method, which allows you to save the resulting graph as an image. - E.g. `self.draw().render(filename="my_node", format="png")`. - """ - return GraphvizNode(self, depth=depth, rankdir=rankdir).graph - - def __str__(self): - return ( - f"{self.label} ({self.__class__.__name__}):\n" - f"{str(self.inputs)}\n" - f"{str(self.outputs)}\n" - f"{str(self.signals)}" - ) - - def connect_output_signal(self, signal: OutputSignal): - self.signals.input.run.connect(signal) - - def __gt__(self, other: InputSignal | Node): - """ - Allows users to connect run and ran signals like: `first_node > second_node`. - """ - other.connect_output_signal(self.signals.output.ran) - return True - - def get_parent_proximate_to(self, composite: Composite) -> Composite | None: - parent = self.parent - while parent is not None and parent.parent is not composite: - parent = parent.parent - return parent - - def get_first_shared_parent(self, other: Node) -> Composite | None: - our, their = self, other - while our.parent is not None: - while their.parent is not None: - if our.parent is their.parent: - return our.parent - their = their.parent - our = our.parent - their = other - return None diff --git a/pyiron_contrib/workflow/node_library/__init__.py b/pyiron_contrib/workflow/node_library/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/pyiron_contrib/workflow/node_library/atomistics.py b/pyiron_contrib/workflow/node_library/atomistics.py deleted file mode 100644 index 0c8e8e530..000000000 --- a/pyiron_contrib/workflow/node_library/atomistics.py +++ /dev/null @@ -1,202 +0,0 @@ -from __future__ import annotations - -from typing import Literal, Optional - -from pyiron_atomistics import Project, _StructureFactory -from pyiron_atomistics.atomistics.job.atomistic import AtomisticGenericJob -from pyiron_atomistics.atomistics.structure.atoms import Atoms -from pyiron_atomistics.lammps.lammps import Lammps as LammpsJob - -from pyiron_contrib.workflow.function import function_node, single_value_node - - -Bulk = single_value_node(output_labels="structure")(_StructureFactory().bulk) - - -@single_value_node(output_labels="job") -def lammps(structure: Optional[Atoms] = None) -> LammpsJob: - pr = Project(".") - job = pr.atomistics.job.Lammps("NOTAREALNAME") - job.structure = structure if structure is not None else _StructureFactory().bulk() - job.potential = job.list_potentials()[0] - return job - - -def _run_and_remove_job(job, modifier: Optional[callable] = None, **modifier_kwargs): - """ - Extracts the commonalities for all the "calc" methods for running a Lammps engine. - Will need to be extended/updated once we support other engines so that more output - can be parsed. Output may wind up more concretely packaged, e.g. as `CalcOutput` or - `MDOutput`, etc., ala Joerg's suggestion later, so for the time being we don't put - too much effort into this. - - Warning: - Jobs are created in a dummy project with a dummy name and are all removed at the - end; this works fine for serial workflows, but will need to be revisited -- - probably with naming based on the parantage of node/workflow labels -- once - other non-serial execution is introduced. - """ - job_name = "JUSTAJOBNAME" - pr = Project("WORKFLOWNAMEPROJECT") - job = job.copy_to(project=pr, new_job_name=job_name, delete_existing_job=True) - if modifier is not None: - job = modifier(job, **modifier_kwargs) - job.run() - - cells = job.output.cells - displacements = job.output.displacements - energy_pot = job.output.energy_pot - energy_tot = job.output.energy_tot - force_max = job.output.force_max - forces = job.output.forces - indices = job.output.indices - positions = job.output.positions - pressures = job.output.pressures - steps = job.output.steps - temperature = job.output.temperature - total_displacements = job.output.total_displacements - unwrapped_positions = job.output.unwrapped_positions - volume = job.output.volume - - job.remove() - pr.remove(enable=True) - - return ( - cells, - displacements, - energy_pot, - energy_tot, - force_max, - forces, - indices, - positions, - pressures, - steps, - temperature, - total_displacements, - unwrapped_positions, - volume, - ) - - -@function_node( - output_labels=[ - "cells", - "displacements", - "energy_pot", - "energy_tot", - "force_max", - "forces", - "indices", - "positions", - "pressures", - "steps", - "temperature", - "total_displacements", - "unwrapped_positions", - "volume", - ] -) -def calc_static( - job: AtomisticGenericJob, -): - return _run_and_remove_job(job=job) - - -@function_node( - output_labels=[ - "cells", - "displacements", - "energy_pot", - "energy_tot", - "force_max", - "forces", - "indices", - "positions", - "pressures", - "steps", - "temperature", - "total_displacements", - "unwrapped_positions", - "volume", - ] -) -def calc_md( - job: AtomisticGenericJob, - n_ionic_steps: int = 1000, - n_print: int = 100, - temperature: int | float = 300.0, - pressure: float - | tuple[float, float, float] - | tuple[float, float, float, float, float, float] - | None = None, -): - def calc_md(job, n_ionic_steps, n_print, temperature, pressure): - job.calc_md( - n_ionic_steps=n_ionic_steps, - n_print=n_print, - temperature=temperature, - pressure=pressure, - ) - return job - - return _run_and_remove_job( - job=job, - modifier=calc_md, - n_ionic_steps=n_ionic_steps, - n_print=n_print, - temperature=temperature, - pressure=pressure, - ) - - -@function_node( - output_labels=[ - "cells", - "displacements", - "energy_pot", - "energy_tot", - "force_max", - "forces", - "indices", - "positions", - "pressures", - "steps", - "total_displacements", - "unwrapped_positions", - "volume", - ] -) -def calc_min( - job: AtomisticGenericJob, - n_ionic_steps: int = 1000, - n_print: int = 100, - pressure: float - | tuple[float, float, float] - | tuple[float, float, float, float, float, float] - | None = None, -): - def calc_min(job, n_ionic_steps, n_print, pressure): - job.calc_minimize( - max_iter=n_ionic_steps, # Calc minimize uses a different var than MD - n_print=n_print, - pressure=pressure, - ) - return job - - return _run_and_remove_job( - job=job, - modifier=calc_min, - n_ionic_steps=n_ionic_steps, - n_print=n_print, - pressure=pressure, - ) - - -nodes = [ - Bulk, - calc_md, - calc_min, - calc_static, - lammps, -] diff --git a/pyiron_contrib/workflow/node_library/standard.py b/pyiron_contrib/workflow/node_library/standard.py deleted file mode 100644 index 5c9ac3125..000000000 --- a/pyiron_contrib/workflow/node_library/standard.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import annotations - -from inspect import isclass -from typing import Optional - -import numpy as np -from matplotlib import pyplot as plt - -from pyiron_contrib.workflow.channels import NotData, OutputSignal -from pyiron_contrib.workflow.function import SingleValue, single_value_node - - -@single_value_node(output_labels="fig") -def scatter( - x: Optional[list | np.ndarray] = None, y: Optional[list | np.ndarray] = None -): - return plt.scatter(x, y) - - -@single_value_node() -def user_input(user_input): - return user_input - - -class If(SingleValue): - """ - Has two extra signal channels: true and false. Evaluates the input as a boolean and - fires the corresponding output signal after running. - """ - - def __init__(self, **kwargs): - super().__init__(self.if_, output_labels="truth", **kwargs) - self.signals.output.true = OutputSignal("true", self) - self.signals.output.false = OutputSignal("false", self) - - @staticmethod - def if_(condition): - if isclass(condition) and issubclass(condition, NotData): - raise TypeError(f"Logic 'If' node expected data but got NotData as input.") - return bool(condition) - - def process_run_result(self, function_output): - """ - Process the output as usual, then fire signals accordingly. - """ - super().process_run_result(function_output) - - if self.outputs.truth.value: - self.signals.output.true() - else: - self.signals.output.false() - - -nodes = [ - scatter, - user_input, - If, -] diff --git a/pyiron_contrib/workflow/node_package.py b/pyiron_contrib/workflow/node_package.py deleted file mode 100644 index 56c990a9b..000000000 --- a/pyiron_contrib/workflow/node_package.py +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -from pyiron_contrib.workflow.node import Node -from pyiron_contrib.workflow.util import DotDict - - -class NodePackage(DotDict): - """ - A collection of node classes. - - Node classes are accessible by their _class name_ by item or attribute access. - - Can be extended by adding node classes to new names with an item or attribute set, - but to update an existing node the `update` method must be used. - """ - - def __init__(self, *node_classes: Node): - super().__init__() - for node in node_classes: - self[node.__name__] = node - - def __setitem__(self, key, value): - if key in self.keys(): - raise KeyError(f"The name {key} is already a stored node class.") - elif key in self.__dir__(): - raise KeyError( - f"The name {key} is already an attribute of this " - f"{self.__class__.__name__} instance." - ) - if not isinstance(value, type) or not issubclass(value, Node): - raise TypeError( - f"Can only set members that are (sub)classes of {Node.__name__}, " - f"but got {type(value)}" - ) - super().__setitem__(key, value) - - def update(self, *node_classes): - replacing = set(self.keys()).intersection([n.__name__ for n in node_classes]) - for name in replacing: - del self[name] - - for node in node_classes: - self[node.__name__] = node diff --git a/pyiron_contrib/workflow/output_parser.py b/pyiron_contrib/workflow/output_parser.py deleted file mode 100644 index 2f88e71e2..000000000 --- a/pyiron_contrib/workflow/output_parser.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -Inspects code to automatically parse return values as strings -""" - -import ast -import inspect -import re -from textwrap import dedent - - -def _remove_spaces_until_character(string): - pattern = r"\s+(?=\s)" - modified_string = re.sub(pattern, "", string) - return modified_string - - -class ParseOutput: - """ - Given a function with at most one `return` expression, inspects the source code and - parses a list of strings containing the returned values. - If the function returns `None`, the parsed value is also `None`. - This parsed value is evaluated at instantiation and stored in the `output` - attribute. - In case more than one `return` expression is found, a `ValueError` is raised. - """ - - def __init__(self, function): - self._func = function - self._source = None - self._output = self.get_parsed_output() - - @property - def func(self): - return self._func - - @property - def dedented_source_string(self): - return dedent(inspect.getsource(self.func)) - - @property - def node_return(self): - tree = ast.parse(self.dedented_source_string) - returns = [] - for node in ast.walk(tree): - if isinstance(node, ast.Return): - returns.append(node) - - if len(returns) > 1: - raise ValueError( - f"{self.__class__.__name__} can only parse callables with at most one " - f"return value, but ast.walk found {len(returns)}." - ) - - try: - return returns[0] - except IndexError: - return None - - @property - def source(self): - if self._source is None: - self._source = self.dedented_source_string.split("\n")[:-1] - return self._source - - def get_string(self, node): - string = "" - for ll in range(node.lineno - 1, node.end_lineno): - if ll == node.lineno - 1 == node.end_lineno - 1: - string += _remove_spaces_until_character( - self.source[ll][node.col_offset : node.end_col_offset] - ) - elif ll == node.lineno - 1: - string += _remove_spaces_until_character( - self.source[ll][node.col_offset :] - ) - elif ll == node.end_lineno - 1: - string += _remove_spaces_until_character( - self.source[ll][: node.end_col_offset] - ) - else: - string += _remove_spaces_until_character(self.source[ll]) - return string - - @property - def output(self): - return self._output - - def get_parsed_output(self): - if self.node_return is None or self.node_return.value is None: - return - elif isinstance(self.node_return.value, ast.Tuple): - return [self.get_string(s) for s in self.node_return.value.dims] - else: - out = [self.get_string(self.node_return.value)] - if out == ["None"]: - return - else: - return out diff --git a/pyiron_contrib/workflow/type_hinting.py b/pyiron_contrib/workflow/type_hinting.py deleted file mode 100644 index 31efaa530..000000000 --- a/pyiron_contrib/workflow/type_hinting.py +++ /dev/null @@ -1,94 +0,0 @@ -""" -This module provides helper functions for evaluating data relative to type hints, and -type hints relative to each other. -""" - -import types -import typing -from collections.abc import Callable - -from typeguard import check_type, TypeCheckError - - -def valid_value(value, type_hint) -> bool: - try: - return isinstance(value, type_hint) - except TypeError: - # Subscripted generics cannot be used with class and instance checks - try: - # typeguard handles this case - check_type(value, type_hint) - return True - except TypeCheckError: - # typeguard raises an error on a failed check - return False - - -def type_hint_to_tuple(type_hint) -> tuple: - if isinstance(type_hint, (types.UnionType, typing._UnionGenericAlias)): - return typing.get_args(type_hint) - else: - return (type_hint,) - - -def type_hint_is_as_or_more_specific_than(hint, other) -> bool: - hint_origin = typing.get_origin(hint) - other_origin = typing.get_origin(other) - if set([hint_origin, other_origin]) & set([types.UnionType, typing.Union]): - # If either hint is a union, turn both into tuples and call recursively - return all( - [ - any( - [ - type_hint_is_as_or_more_specific_than(h, o) - for o in type_hint_to_tuple(other) - ] - ) - for h in type_hint_to_tuple(hint) - ] - ) - elif hint_origin is None and other_origin is None: - # Once both are raw classes, just do a subclass test - try: - return issubclass(hint, other) - except TypeError: - return hint == other - elif hint_origin == other_origin: - # If they both have an origin, break into arguments and treat cases - hint_args = typing.get_args(hint) - other_args = typing.get_args(other) - if len(hint_args) == 0 and len(other_args) > 0: - # Failing to specify anything is not being more specific - return False - elif hint_origin in [dict, tuple, Callable]: - # for these origins the order of arguments matters - if len(other_args) == 0: - # If the other doesn't specify _any_ arguments, we must be more specific - return True - elif len(other_args) == len(hint_args): - # If they both specify arguments, they should be more specific 1:1 - return all( - [ - type_hint_is_as_or_more_specific_than(h, o) - for o, h in zip(other_args, hint_args) - ] - ) - else: - # Otherwise they both specify but a mis-matching number of args - return False - else: - # Otherwise order doesn't matter so make sure the arguments are a subset - return all( - [ - any( - [ - type_hint_is_as_or_more_specific_than(h, o) - for o in other_args - ] - ) - for h in hint_args - ] - ) - else: - # Lastly, if they both have origins, but different ones, fail - return False diff --git a/pyiron_contrib/workflow/util.py b/pyiron_contrib/workflow/util.py deleted file mode 100644 index 61dae6c7c..000000000 --- a/pyiron_contrib/workflow/util.py +++ /dev/null @@ -1,34 +0,0 @@ -from pyiron_base import state - -logger = state.logger - - -class DotDict(dict): - def __getattr__(self, item): - return self.__getitem__(item) - - def __setattr__(self, key, value): - self[key] = value - - def __dir__(self): - return set(super().__dir__() + list(self.keys())) - - -class SeabornColors: - """ - Hex codes for the ten `seaborn.color_palette()` colors (plus pure white and black), - recreated to avoid adding an entire dependency. - """ - - blue = "#1f77b4" - orange = "#ff7f0e" - green = "#2ca02c" - red = "#d62728" - purple = "#9467bd" - brown = "#8c564b" - pink = "#e377c2" - gray = "#7f7f7f" - olive = "#bcbd22" - cyan = "#17becf" - white = "#ffffff" - black = "#000000" diff --git a/pyiron_contrib/workflow/workflow.py b/pyiron_contrib/workflow/workflow.py deleted file mode 100644 index 35d4b2a11..000000000 --- a/pyiron_contrib/workflow/workflow.py +++ /dev/null @@ -1,227 +0,0 @@ -""" -Provides the main workhorse class for creating and running workflows. - -This class is intended as the single point of entry for users making an import. -""" - -from __future__ import annotations - -from typing import Optional, TYPE_CHECKING - -from pyiron_contrib.workflow.composite import Composite -from pyiron_contrib.workflow.io import Inputs, Outputs - - -if TYPE_CHECKING: - from pyiron_contrib.workflow.node import Node - - -class Workflow(Composite): - """ - Workflows are a dynamic composite node -- i.e. they hold and run a collection of - nodes (a subgraph) which can be dynamically modified (adding and removing nodes, - and modifying their connections). - - Nodes can be added to the workflow at instantiation or with dot-assignment later on. - They are then accessible either under the `nodes` dot-dictionary, or just directly - by dot-access on the workflow object itself. - - Using the `input` and `output` attributes, the workflow gives access to all the - IO channels among its nodes which are currently unconnected. - - The `Workflow` class acts as a single-point-of-import for us; - Directly from the class we can use the `create` method to instantiate workflow - objects. - When called from a workflow _instance_, any created nodes get their parent set to - the workflow instance being used. - - Examples: - We allow adding nodes to workflows in five equivalent ways: - >>> from pyiron_contrib.workflow.workflow import Workflow - >>> - >>> def fnc(x=0): - ... return x + 1 - >>> - >>> # (1) As *args at instantiation - >>> n1 = Workflow.create.Function(fnc, label="n1") - >>> wf = Workflow("my_workflow", n1) - >>> - >>> # (2) Being passed to the `add` method - >>> wf.add(Workflow.create.Function(fnc, label="n2")) - >>> - >>> # (3) Calling `create` from the _workflow instance_ that will own the node - >>> wf.create.Function(fnc, label="n3") # Instantiating from add - >>> - >>> # (4) By attribute assignment (here the node can be created from the - >>> # workflow class or instance and the end result is the same - >>> wf.n4 = wf.create.Function(fnc, label="anyhow_n4_gets_used") - >>> - >>> # (5) By creating from the workflow class but specifying the parent kwarg - >>> Workflow.create.Function(fnc, label="n5", parent=wf) - - By default, the node naming scheme is strict, so if you try to add a node to a - label that already exists, you will get an error. This behaviour can be changed - at instantiation with the `strict_naming` kwarg, or afterwards by assigning a - bool to this property. When deactivated, repeated assignments to the same label - just get appended with an index: - >>> wf.strict_naming = False - >>> wf.my_node = wf.create.Function(fnc, x=0) - >>> wf.my_node = wf.create.Function(fnc, x=1) - >>> wf.my_node = wf.create.Function(fnc, x=2) - >>> print(wf.my_node.inputs.x, wf.my_node0.inputs.x, wf.my_node1.inputs.x) - 0, 1, 2 - - The `Workflow` class is designed as a single point of entry for workflows, so - you can also access decorators to define new node classes right from the - workflow (cf. the `Node` docs for more detail on the node types). - Let's use these to explore a workflow's input and output, which are dynamically - generated from the unconnected IO of its nodes: - >>> @Workflow.wrap_as.function_node("y") - >>> def plus_one(x: int = 0): - ... return x + 1 - >>> - >>> wf = Workflow("io_workflow") - >>> wf.first = plus_one() - >>> wf.second = plus_one() - >>> print(len(wf.inputs), len(wf.outputs)) - 2 2 - - If we connect the output of one node to the input of the other, there are fewer - dangling channels for the workflow IO to find: - >>> wf.second.inputs.x = wf.first.outputs.y - >>> print(len(wf.inputs), len(wf.outputs)) - 1 1 - - Then we just run the workflow - >>> out = wf.run() - - The workflow joins node lavels and channel labels with a `_` character to - provide direct access to the output: - >>> print(wf.outputs.second__y.value) - 2 - - 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: - >>> 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 - namespaces, e.g. - >>> wf = Workflow("with_prebuilt") - >>> - >>> wf.structure = wf.create.atomistics.Bulk( - ... cubic=True, - ... name="Al" - ... ) - >>> wf.engine = wf.create.atomistics.Lammps(structure=wf.structure) - >>> wf.calc = wf.create.atomistics.CalcMd( - ... job=wf.engine, - ... ) - >>> wf.plot = wf.create.standard.Scatter( - ... x=wf.calc.outputs.steps, - ... y=wf.calc.outputs.temperature - ... ) - - We can give more convenient names to IO, and even access IO that would normally - be hidden (because it's connected) by specifying an `inputs_map` and/or - `outputs_map`. In the example above, let's make the resulting figure a bit - easier to find: - >>> wf.outputs_map = {"plot__fig": "fig"} - >>> wf().fig - - Workflows can be visualized in the notebook using graphviz: - >>> wf.draw() - - The resulting object can be saved as an image, e.g. - >>> wf.draw().render(filename="demo", format="png") - - When your workflow's data follows a directed-acyclic pattern, it will determine - the execution flow automatically. - If you want or need more control, you can set the `automate_execution` flag to - `False` and manually specify an execution flow. - Cf. the - - TODO: Workflows can be serialized. - - TODO: Once you're satisfied with how a workflow is structured, you can export it - as a macro node for use in other workflows. (Maybe we should allow for nested - workflows without exporting to a node? I was concerned then what happens to the - nesting abstraction if, instead of accessing IO through the workflow's IO flags, - a user manually connects IO from individual nodes from two different, nested or - sibling workflows when those connections were _previously internal to their own - workflow_. This seems very unsafe. Maybe there is something like a lock we can - apply that falls short of a full export, but still guarantees the internal - integrity of workflows when they're used somewhere else? - """ - - def __init__( - self, - label: str, - *nodes: Node, - strict_naming: bool = True, - inputs_map: Optional[dict] = None, - outputs_map: Optional[dict] = None, - automate_execution: bool = True, - ): - super().__init__( - label=label, - parent=None, - strict_naming=strict_naming, - inputs_map=inputs_map, - outputs_map=outputs_map, - ) - self.automate_execution = automate_execution - - for node in nodes: - self.add(node) - - @property - def inputs(self) -> Inputs: - return self._build_inputs() - - @property - def outputs(self) -> Outputs: - return self._build_outputs() - - @staticmethod - def run_graph(self): - if self.automate_execution: - self.set_run_signals_to_dag_execution() - return super().run_graph(self) - - def to_node(self): - """ - Export the workflow to a macro node, with the currently exposed IO mapped to - new IO channels, and the workflow mapped into the node_function. - """ - raise NotImplementedError - - # (De)serialization is necessary throughout these classes, but not implemented here - def serialize(self): - raise NotImplementedError - - def deserialize(self, source): - raise NotImplementedError - - @property - def parent(self) -> None: - return None - - @parent.setter - def parent(self, new_parent: None): - # Currently workflows are not allowed to have a parent -- maybe we want to - # change our minds on this in the future? If we do, we can just expose `parent` - # as a kwarg and roll back this private var/property/setter protection and let - # the super call in init handle everything - if new_parent is not None: - raise TypeError( - f"{self.__class__} may only take None as a parent but got " - f"{type(new_parent)}" - ) diff --git a/setup.py b/setup.py index a55695452..352cfb586 100644 --- a/setup.py +++ b/setup.py @@ -43,9 +43,6 @@ 'pyiron_atomistics==0.3.4', 'pycp2k==0.2.2', ], - 'executors': [ - 'cloudpickle', - ], 'fenics': [ 'fenics==2019.1.0', 'mshr==2019.1.0', @@ -55,13 +52,6 @@ 'boto3==1.28.60', 'moto==4.2.5' ], - 'workflow': [ - 'cloudpickle', - 'python>=3.10', - 'graphviz', - 'toposort', - 'typeguard==4.1.5' - ], 'tinybase': [ 'distributed==2023.9.3', 'pympipool==0.7.1' diff --git a/tests/integration/test_workflow.py b/tests/integration/test_workflow.py deleted file mode 100644 index 013201d0c..000000000 --- a/tests/integration/test_workflow.py +++ /dev/null @@ -1,173 +0,0 @@ -import unittest - -import numpy as np - -from pyiron_contrib.workflow.channels import OutputSignal -from pyiron_contrib.workflow.function import Function -from pyiron_contrib.workflow.workflow import Workflow - - -class TestTopology(unittest.TestCase): - def test_manually_constructed_cyclic_graph(self): - """ - Check that cyclic graphs run. - """ - - @Workflow.wrap_as.single_value_node() - def numpy_randint(low=0, high=20): - rand = np.random.randint(low=low, high=high) - print(f"Generating random number between {low} and {high}...{rand}!") - return rand - - class GreaterThanLimitSwitch(Function): - """ - A switch class for sending signal output depending on a '>' check - applied to input - """ - - def __init__(self, **kwargs): - super().__init__( - self.greater_than, - output_labels="value_gt_limit", - **kwargs - ) - self.signals.output.true = OutputSignal("true", self) - self.signals.output.false = OutputSignal("false", self) - - @staticmethod - def greater_than(value, limit=10): - return value > limit - - def process_run_result(self, function_output): - """ - Process the output as usual, then fire signals accordingly. - """ - super().process_run_result(function_output) - - if self.outputs.value_gt_limit.value: - print(f"{self.inputs.value.value} > {self.inputs.limit.value}") - self.signals.output.true() - else: - print(f"{self.inputs.value.value} <= {self.inputs.limit.value}") - self.signals.output.false() - - @Workflow.wrap_as.single_value_node() - def numpy_sqrt(value=0): - sqrt = np.sqrt(value) - print(f"sqrt({value}) = {sqrt}") - return sqrt - - wf = Workflow("rand_until_big_then_sqrt", automate_execution=False) - - wf.rand = numpy_randint() - - wf.gt_switch = GreaterThanLimitSwitch() - wf.gt_switch.inputs.value = wf.rand - - wf.sqrt = numpy_sqrt() - wf.sqrt.inputs.value = wf.rand - - wf.gt_switch.signals.output.false > wf.rand > wf.gt_switch # Loop on false - wf.gt_switch.signals.output.true > wf.sqrt # On true break to sqrt node - wf.starting_nodes = [wf.rand] - - wf.run() - self.assertAlmostEqual( - np.sqrt(wf.rand.outputs.rand.value), wf.sqrt.outputs.sqrt.value, 6 - ) - - def test_for_loop(self): - n = 5 - - bulk_loop = Workflow.create.meta.for_loop( - Workflow.create.atomistics.Bulk, - n, - iterate_on=("a",), - )() - - out = bulk_loop( - name="Al", # Sent equally to each body node - A=np.linspace(3.9, 4.1, n).tolist(), # Distributed across body nodes - ) - - self.assertTrue( - np.allclose( - [struct.cell.volume for struct in out.STRUCTURE], - [ - 14.829749999999995, - 15.407468749999998, - 15.999999999999998, - 16.60753125, - 17.230249999999995 - ] - ) - ) - - def test_while_loop(self): - with self.subTest("Random"): - np.random.seed(0) - - @Workflow.wrap_as.single_value_node("random") - def random(length: int | None = None): - return np.random.random(length) - - @Workflow.wrap_as.single_value_node("gt") - def greater_than(x: float, threshold: float): - return x > threshold - - RandomWhile = Workflow.create.meta.while_loop( - loop_body_class=random, - condition_class=greater_than, - internal_connection_map=[("Random", "random", "GreaterThan", "x")], - outputs_map={"Random__random": "capped_result"} - ) - - # Define workflow - - wf = Workflow("random_until_small_enough") - - ## Wire together the while loop and its condition - - wf.random_while = RandomWhile() - - ## Give convenient labels - wf.inputs_map = {"random_while__GreaterThan__threshold": "threshold"} - wf.outputs_map = {"random_while__capped_result": "capped_result"} - - self.assertAlmostEqual( - wf(threshold=0.1).capped_result, - 0.07103605819788694, # For this reason we set the random seed - ) - - with self.subTest("Self-data-loop"): - - @Workflow.wrap_as.single_value_node() - def add(a, b): - return a + b - - @Workflow.wrap_as.single_value_node() - def less_than_ten(value): - return value < 10 - - AddWhile = Workflow.create.meta.while_loop( - loop_body_class=add, - condition_class=less_than_ten, - internal_connection_map=[ - ("Add", "a + b", "LessThanTen", "value"), - ("Add", "a + b", "Add", "a") - ], - inputs_map={"Add__a": "a", "Add__b": "b"}, - outputs_map={"Add__a + b": "total"} - ) - - wf = Workflow("do_while") - wf.add_while = AddWhile() - - wf.inputs_map = { - "add_while__a": "a", - "add_while__b": "b" - } - wf.outputs_map = {"add_while__total": "total"} - - out = wf(a=1, b=2) - self.assertEqual(out.total, 11) diff --git a/tests/unit/executors/__init__.py b/tests/unit/executors/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/unit/executors/test_cloudprocesspool.py b/tests/unit/executors/test_cloudprocesspool.py deleted file mode 100644 index f8df6fd78..000000000 --- a/tests/unit/executors/test_cloudprocesspool.py +++ /dev/null @@ -1,169 +0,0 @@ -from functools import partialmethod -from concurrent.futures import TimeoutError -from time import sleep -import unittest - -from pyiron_contrib.executors.cloudpickleprocesspool import ( - CloudpickleProcessPoolExecutor -) - - -class Foo: - """ - A base class to be dynamically modified for testing CloudpickleProcessPoolExecutor. - """ - def __init__(self, fnc: callable): - self.fnc = fnc - self.result = None - - @property - def run(self): - return self.fnc - - def process_result(self, future): - self.result = future.result() - - -def dynamic_foo(): - """ - A decorator for dynamically modifying the Foo class to test - CloudpickleProcessPoolExecutor. - - Overrides the `fnc` input of `Foo` with the decorated function. - """ - def as_dynamic_foo(fnc: callable): - return type( - "DynamicFoo", - (Foo,), # Define parentage - { - "__init__": partialmethod( - Foo.__init__, - fnc - ) - }, - ) - - return as_dynamic_foo - - -class TestCloudpickleProcessPoolExecutor(unittest.TestCase): - - def test_unpickleable_callable(self): - """ - We should be able to use an unpickleable callable -- in this case, a method of - a dynamically defined class. - """ - fortytwo = 42 # No magic numbers; we use it in a couple places so give it a var - - @dynamic_foo() - def slowly_returns_42(): - sleep(0.1) - return fortytwo - - dynamic_42 = slowly_returns_42() # Instantiate the dynamically defined class - self.assertIsInstance( - dynamic_42, - Foo, - msg="Just a sanity check that the test is set up right" - ) - self.assertIsNone( - dynamic_42.result, - msg="Just a sanity check that the test is set up right" - ) - executor = CloudpickleProcessPoolExecutor() - fs = executor.submit(dynamic_42.run) - fs.add_done_callback(dynamic_42.process_result) - self.assertFalse(fs.done(), msg="Should be running on the executor") - self.assertEqual(fortytwo, fs.result(), msg="Future must complete") - self.assertEqual(fortytwo, dynamic_42.result, msg="Callback must get called") - - def test_unpickleable_return(self): - """ - We should be able to use an unpickleable return value -- in this case, a - method of a dynamically defined class. - """ - - @dynamic_foo() - def does_nothing(): - return - - @dynamic_foo() - def slowly_returns_unpickleable(): - """ - Returns a complex, dynamically defined variable - """ - sleep(0.1) - inside_variable = does_nothing() - inside_variable.result = "it was an inside job!" - return inside_variable - - dynamic_dynamic = slowly_returns_unpickleable() - executor = CloudpickleProcessPoolExecutor() - fs = executor.submit(dynamic_dynamic.run) - self.assertIsInstance( - fs.result(), - Foo, - msg="The custom future should be unpickling the result" - ) - self.assertEqual(fs.result().result, "it was an inside job!") - - def test_unpickleable_args(self): - """ - We should be able to use an unpickleable return value -- in this case, a - method of a dynamically defined class. - """ - - @dynamic_foo() - def does_nothing(): - return - - @dynamic_foo() - def slowly_returns_unpickleable(unpickleable_arg): - """ - Returns a complex, dynamically defined variable - """ - sleep(0.1) - unpickleable_arg.result = "input updated" - return unpickleable_arg - - dynamic_dynamic = slowly_returns_unpickleable() - executor = CloudpickleProcessPoolExecutor() - unpicklable_object = does_nothing() - fs = executor.submit(dynamic_dynamic.run, unpicklable_object) - self.assertEqual(fs.result().result, "input updated") - - def test_exception(self): - @dynamic_foo() - def raise_error(): - raise RuntimeError - - re = raise_error() - executor = CloudpickleProcessPoolExecutor() - fs = executor.submit(re.run) - with self.assertRaises(RuntimeError): - fs.result() - - def test_timeout(self): - fortytwo = 42 - - @dynamic_foo() - def slow(): - sleep(0.1) - return fortytwo - - f = slow() - executor = CloudpickleProcessPoolExecutor() - fs = executor.submit(f.run) - self.assertEqual( - fs.result(timeout=30), - fortytwo, - msg="waiting long enough should get the result" - ) - - with self.assertRaises(TimeoutError): - fs = executor.submit(f.run) - fs.result(timeout=0.0001) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/unit/workflow/__init__.py b/tests/unit/workflow/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/unit/workflow/test_channels.py b/tests/unit/workflow/test_channels.py deleted file mode 100644 index e8b1dc0b0..000000000 --- a/tests/unit/workflow/test_channels.py +++ /dev/null @@ -1,210 +0,0 @@ -from unittest import TestCase, skipUnless -from sys import version_info - -from pyiron_contrib.workflow.channels import ( - InputData, OutputData, InputSignal, OutputSignal, NotData -) - - -class DummyNode: - def __init__(self): - self.foo = [0] - self.running = False - self.label = "node_label" - - def update(self): - self.foo.append(self.foo[-1] + 1) - - -@skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestDataChannels(TestCase): - - def setUp(self) -> None: - self.ni1 = InputData(label="numeric", node=DummyNode(), default=1, type_hint=int | float) - self.ni2 = InputData(label="numeric", node=DummyNode(), default=1, type_hint=int | float) - self.no = OutputData(label="numeric", node=DummyNode(), default=0, type_hint=int | float) - - self.so1 = OutputData(label="list", node=DummyNode(), default=["foo"], type_hint=list) - self.so2 = OutputData(label="list", node=DummyNode(), default=["foo"], type_hint=list) - - def test_mutable_defaults(self): - self.so1.default.append("bar") - self.assertEqual( - len(self.so2.default), - len(self.so1.default) - 1, - msg="Mutable defaults should avoid sharing between instances" - ) - - def test_connections(self): - - with self.subTest("Test connection reflexivity and value updating"): - self.assertEqual(self.no.value, 0) - self.ni1.connect(self.no) - self.assertIn(self.no, self.ni1.connections) - self.assertIn(self.ni1, self.no.connections) - self.assertEqual(self.no.value, self.ni1.value) - - with self.subTest("Test disconnection"): - disconnected = self.ni2.disconnect(self.no) - self.assertEqual( - len(disconnected), - 0, - msg="There were no connections to begin with, nothing should be there" - ) - disconnected = self.ni1.disconnect(self.no) - self.assertEqual( - [], self.ni1.connections, msg="No connections should be left" - ) - self.assertEqual( - [], - self.no.connections, - msg="Disconnection should also have been reflexive" - ) - self.assertListEqual( - disconnected, - [(self.ni1, self.no)], - msg="Expected a list of the disconnected pairs." - ) - - with self.subTest("Test multiple connections"): - self.no.connect(self.ni1, self.ni2) - self.assertEqual(2, len(self.no.connections), msg="Should connect to all") - - with self.subTest("Test iteration"): - self.assertTrue(all([con in self.no.connections for con in self.no])) - - with self.subTest("Don't push NotData"): - self.no.disconnect_all() - self.no.value = NotData - self.ni1.value = 1 - self.ni1.connect(self.no) - self.assertEqual( - self.ni1.value, - 1, - msg="NotData should not be getting pushed on connection" - ) - self.ni2.value = 2 - self.no.value = 3 - self.ni2.connect(self.no) - self.assertEqual( - self.ni2.value, - 3, - msg="Actual data should be getting pushed" - ) - self.no.update(NotData) - self.assertEqual( - self.ni2.value, - 3, - msg="NotData should not be getting pushed on updates" - ) - - def test_connection_validity_tests(self): - self.ni1.type_hint = int | float | bool # Override with a larger set - self.ni2.type_hint = int # Override with a smaller set - - with self.assertRaises(TypeError): - self.ni1.connect("Not a channel at all") - - self.no.connect(self.ni1) - self.assertIn( - self.no, - self.ni1.connections, - "Input types should be allowed to be a super-set of output types" - ) - - self.no.connect(self.ni2) - self.assertNotIn( - self.no, - self.ni2.connections, - "Input types should not be allowed to be a sub-set of output types" - ) - - self.so1.connect(self.ni2) - self.assertNotIn( - self.so1, - self.ni2.connections, - "Totally different types should not allow connections" - ) - - self.ni2.strict_connections = False - self.so1.connect(self.ni2) - self.assertIn( - self.so1, - self.ni2.connections, - "With strict connections turned off, we should allow type-violations" - ) - - def test_ready(self): - with self.subTest("Test defaults and not-data"): - without_default = InputData(label="without_default", node=DummyNode()) - self.assertIs( - without_default.value, - NotData, - msg=f"Without a default, spec is to have a NotData value but got " - f"{type(without_default.value)}" - ) - self.assertFalse( - without_default.ready, - msg="Even without type hints, readiness should be false when the value" - "is NotData" - ) - - self.ni1.value = 1 - self.assertTrue(self.ni1.ready) - - self.ni1.value = "Not numeric at all" - self.assertFalse(self.ni1.ready) - - def test_update(self): - self.no.connect(self.ni1, self.ni2) - self.no.update(42) - for inp in self.no.connections: - self.assertEqual( - self.no.value, - inp.value, - msg="Value should have been passed downstream" - ) - - self.ni1.node.running = True - with self.assertRaises(RuntimeError): - self.no.update(42) - - -class TestSignalChannels(TestCase): - def setUp(self) -> None: - node = DummyNode() - self.inp = InputSignal(label="inp", node=node, callback=node.update) - self.out = OutputSignal(label="out", node=DummyNode()) - - def test_connections(self): - with self.subTest("Good connection"): - self.inp.connect(self.out) - self.assertEqual(self.inp.connections, [self.out]) - self.assertEqual(self.out.connections, [self.inp]) - - with self.subTest("Ignore repeated connection"): - self.out.connect(self.inp) - self.assertEqual(len(self.inp), 1) - self.assertEqual(len(self.out), 1) - - with self.subTest("Check disconnection"): - self.out.disconnect_all() - self.assertEqual(len(self.inp), 0) - self.assertEqual(len(self.out), 0) - - with self.subTest("No connections to non-SignalChannels"): - bad = InputData(label="numeric", node=DummyNode(), default=1, type_hint=int) - with self.assertRaises(TypeError): - self.inp.connect(bad) - - with self.subTest("Test syntactic sugar"): - self.out.disconnect_all() - self.out > self.inp - self.assertIn(self.out, self.inp.connections) - - def test_calls(self): - self.out.connect(self.inp) - self.out() - self.assertListEqual(self.inp.node.foo, [0, 1]) - self.inp() - self.assertListEqual(self.inp.node.foo, [0, 1, 2]) diff --git a/tests/unit/workflow/test_files.py b/tests/unit/workflow/test_files.py deleted file mode 100644 index 304f6a6cb..000000000 --- a/tests/unit/workflow/test_files.py +++ /dev/null @@ -1,50 +0,0 @@ -import unittest -from pyiron_contrib.workflow.files import DirectoryObject, FileObject -from pathlib import Path - - -class TestFiles(unittest.TestCase): - def setUp(cls): - cls.directory = DirectoryObject("test") - - def tearDown(cls): - cls.directory.delete() - - def test_directory_exists(self): - self.assertTrue(Path("test").exists() and Path("test").is_dir()) - - def test_write(self): - self.directory.write(file_name="test.txt", content="something") - self.assertTrue(self.directory.file_exists("test.txt")) - self.assertTrue( - "test/test.txt" in [ - ff.replace("\\", "/") - for ff in self.directory.list_content()['file'] - ] - ) - self.assertEqual(len(self.directory), 1) - - def test_create_subdirectory(self): - self.directory.create_subdirectory("another_test") - self.assertTrue(Path("test/another_test").exists()) - - def test_path(self): - f = FileObject("test.txt", self.directory) - self.assertEqual(str(f.path).replace("\\", "/"), "test/test.txt") - - def test_read_and_write(self): - f = FileObject("test.txt", self.directory) - f.write("something") - self.assertEqual(f.read(), "something") - - def test_is_file(self): - f = FileObject("test.txt", self.directory) - self.assertFalse(f.is_file()) - f.write("something") - self.assertTrue(f.is_file()) - f.delete() - self.assertFalse(f.is_file()) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/unit/workflow/test_function.py b/tests/unit/workflow/test_function.py deleted file mode 100644 index 529217c67..000000000 --- a/tests/unit/workflow/test_function.py +++ /dev/null @@ -1,542 +0,0 @@ -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 -from pyiron_contrib.workflow.channels import NotData -from pyiron_contrib.workflow.files import DirectoryObject -from pyiron_contrib.workflow.function import ( - Function, SingleValue, function_node, single_value_node -) - - -def throw_error(x: Optional[int] = None): - raise RuntimeError - - -def plus_one(x=1) -> Union[int, float]: - y = x + 1 - return y - - -def no_default(x, y): - return x + y + 1 - - -def returns_multiple(x, y): - return x, y, x + y - - -def void(): - pass - - -def multiple_branches(x): - if x < 10: - return True - else: - return False - - -@unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestFunction(unittest.TestCase): - def test_instantiation(self): - with self.subTest("Void function is allowable"): - void_node = Function(void) - self.assertEqual(len(void_node.outputs), 0) - - with self.subTest("Args and kwargs at initialization"): - node = Function(plus_one) - self.assertIs( - node.outputs.y.value, - NotData, - msg="Nodes should not run at instantiation", - ) - node.inputs.x = 10 - self.assertIs( - node.outputs.y.value, - NotData, - msg="Nodes should not run on input updates", - ) - node.run() - self.assertEqual( - node.outputs.y.value, - 11, - msg=f"Slow nodes should still run when asked! Expected 11 but got " - f"{node.outputs.y.value}" - ) - - node = Function(no_default, 1, y=2, output_labels="output") - node.run() - self.assertEqual( - no_default(1, 2), - node.outputs.output.value, - msg="Nodes should allow input initialization by arg and kwarg" - ) - node(2, y=3) - node.run() - self.assertEqual( - no_default(2, 3), - node.outputs.output.value, - msg="Nodes should allow input update on call by arg and kwarg" - ) - - with self.assertRaises(ValueError): - # Can't pass more args than the function takes - Function(returns_multiple, 1, 2, 3) - - with self.subTest("Initializing with connections"): - node = Function(plus_one, x=2) - node2 = Function(plus_one, x=node.outputs.y) - self.assertIs( - node2.inputs.x.connections[0], - node.outputs.y, - msg="Should be able to make a connection at initialization" - ) - node > node2 - node.run() - self.assertEqual(4, node2.outputs.y.value, msg="Initialize from connection") - - def test_defaults(self): - with_defaults = Function(plus_one) - self.assertEqual( - with_defaults.inputs.x.value, - 1, - msg=f"Expected to get the default provided in the underlying function but " - f"got {with_defaults.inputs.x.value}", - ) - without_defaults = Function(no_default) - self.assertIs( - without_defaults.inputs.x.value, - NotData, - msg=f"Expected values with no default specified to start as {NotData} but " - f"got {without_defaults.inputs.x.value}", - ) - self.assertFalse( - without_defaults.ready, - msg="I guess we should test for behaviour and not implementation... Without" - "defaults, the node should not be ready!" - ) - - def test_label_choices(self): - with self.subTest("Automatically scrape output labels"): - n = Function(plus_one) - self.assertListEqual(n.outputs.labels, ["y"]) - - with self.subTest("Allow overriding them"): - n = Function(no_default, output_labels=("sum_plus_one",)) - self.assertListEqual(n.outputs.labels, ["sum_plus_one"]) - - with self.subTest("Allow forcing _one_ output channel"): - n = Function(returns_multiple, output_labels="its_a_tuple") - self.assertListEqual(n.outputs.labels, ["its_a_tuple"]) - - with self.subTest("Fail on multiple return values"): - with self.assertRaises(ValueError): - # Can't automatically parse output labels from a function with multiple - # return expressions - Function(multiple_branches) - - with self.subTest("Override output label scraping"): - switch = Function(multiple_branches, output_labels="bool") - self.assertListEqual(switch.outputs.labels, ["bool"]) - - def test_signals(self): - @function_node() - def linear(x): - return x - - @function_node() - def times_two(y): - return 2 * y - - l = linear(x=1) - t2 = times_two( - output_labels=["double"], - y=l.outputs.x - ) - self.assertIs( - t2.outputs.double.value, - NotData, - msg=f"Without updates, expected the output to be {NotData} but got " - f"{t2.outputs.double.value}" - ) - - # Nodes should _all_ have the run and ran signals - t2.signals.input.run = l.signals.output.ran - l.run() - self.assertEqual( - t2.outputs.double.value, 2, - msg="Running the upstream node should trigger a run here" - ) - - with self.subTest("Test syntactic sugar"): - t2.signals.input.run.disconnect_all() - l > t2 - self.assertIn( - l.signals.output.ran, - t2.signals.input.run.connections, - msg="> should be equivalent to run/ran connection" - ) - - t2.signals.input.run.disconnect_all() - l > t2.signals.input.run - self.assertIn( - l.signals.output.ran, - t2.signals.input.run.connections, - msg="> should allow us to mix and match nodes and signal channels" - ) - - t2.signals.input.run.disconnect_all() - l.signals.output.ran > t2 - self.assertIn( - l.signals.output.ran, - t2.signals.input.run.connections, - msg="Mixing and matching should work both directions" - ) - - t2.signals.input.run.disconnect_all() - l > t2 > l - self.assertTrue( - l.signals.input.run.connections[0] is t2.signals.output.ran - and t2.signals.input.run.connections[0] is l.signals.output.ran, - msg="> should allow chaining signal connections" - ) - - def test_statuses(self): - n = Function(plus_one) - self.assertTrue(n.ready) - self.assertFalse(n.running) - self.assertFalse(n.failed) - - # Can't really test "running" until we have a background executor, so fake a bit - n.running = True - with self.assertRaises(RuntimeError): - # Running nodes can't be run - n.run() - n.running = False - - n.inputs.x = "Can't be added together with an int" - with self.assertRaises(TypeError): - # The function error should get passed up - n.run() - self.assertFalse(n.ready) - # self.assertFalse(n.running) - self.assertTrue(n.failed) - - n.inputs.x = 1 - self.assertFalse( - n.ready, - msg="Should not be ready while it has failed status" - ) - - n.run() - self.assertTrue( - n.ready, - msg="A manual run() call bypasses checks, so readiness should reset" - ) - self.assertTrue(n.ready) - # self.assertFalse(n.running) - self.assertFalse(n.failed, msg="Re-running should reset failed status") - - def test_with_self(self): - def with_self(self, x: float) -> float: - # Note: Adding internal state to the node like this goes against the best - # practice of keeping nodes "functional". Following python's paradigm of - # giving users lots of power, we want to guarantee that this behaviour is - # _possible_. - # TODO: update this test with a better-conforming example of this power at - # a future date. - if hasattr(self, "some_counter"): - self.some_counter += 1 - else: - self.some_counter = 1 - return x + 0.1 - - node = Function(with_self, output_labels="output") - self.assertTrue( - "x" in node.inputs.labels, - msg=f"Expected to find function input 'x' in the node input but got " - f"{node.inputs.labels}" - ) - self.assertFalse( - "self" in node.inputs.labels, - msg="Expected 'self' to be filtered out of node input, but found it in the " - "input labels" - ) - node.inputs.x = 1 - node.run() - self.assertEqual( - node.outputs.output.value, - 1.1, - msg="Basic node functionality appears to have failed" - ) - self.assertEqual( - node.some_counter, - 1, - 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 - - with warnings.catch_warnings(record=True) as warning_list: - node = Function(with_messed_self) - self.assertTrue("self" in node.inputs.labels) - - self.assertEqual(len(warning_list), 1) - - def test_call(self): - node = Function(no_default, output_labels="output") - - with self.subTest("Ensure desired failures occur"): - with self.assertRaises(ValueError): - # More input args than there are input channels - node(1, 2, 3) - - with self.assertRaises(ValueError): - # Using input as an arg _and_ a kwarg - node(1, y=2, x=3) - - with self.subTest("Make sure data updates work as planned"): - node(1, y=2) - self.assertEqual( - node.inputs.x.value, - 1, - msg="__call__ should accept args to update input" - ) - self.assertEqual( - node.inputs.y.value, - 2, - msg="__call__ should accept kwargs to update input" - ) - self.assertEqual( - node.outputs.output.value, 1 + 2 + 1, msg="__call__ should run things" - ) - - node(3) # Implicitly test partial update - self.assertEqual( - no_default(3, 2), - node.outputs.output.value, - msg="__call__ should allow updating only _some_ input before running" - ) - - with self.subTest("Check that bad kwargs don't stop good ones"): - with self.assertWarns(Warning): - original_label = node.label - node(4, label="won't get read", y=5, foobar="not a kwarg of any sort") - - self.assertEqual( - node.label, - original_label, - msg="You should only be able to update input on a call, that's " - "what the warning is for!" - ) - self.assertTupleEqual( - (node.inputs.x.value, node.inputs.y.value), - (4, 5), - msg="The warning should not prevent other data from being parsed" - ) - - with self.assertWarns(Warning): - # It's also fine if you just have a typo in your kwarg or whatever, - # 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" - ) - - node.inputs.x = 2 - 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() - - 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 - - -@unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestSingleValue(unittest.TestCase): - def test_instantiation(self): - node = SingleValue(no_default, 1, y=2, output_labels="output") - node.run() - self.assertEqual( - no_default(1, 2), - node.outputs.output.value, - msg="Single value node should allow function input by arg and kwarg" - ) - - with self.assertRaises(ValueError): - # Too many labels - SingleValue(plus_one, output_labels=["z", "excess_label"]) - - def test_item_and_attribute_access(self): - class Foo: - some_attribute = "exists" - connected = True # Overlaps with an attribute of the node - - def __getitem__(self, item): - if item == 0: - return True - else: - return False - - def returns_foo() -> Foo: - return Foo() - - svn = SingleValue(returns_foo, output_labels="foo") - svn.run() - - self.assertEqual( - svn.some_attribute, - "exists", - msg="Should fall back to looking on the single value" - ) - - self.assertEqual( - svn.connected, - False, - msg="Should return the _node_ attribute, not the single value attribute" - ) - - with self.assertRaises(AttributeError): - svn.doesnt_exists_anywhere - - self.assertEqual( - svn[0], - True, - msg="Should fall back to looking on the single value" - ) - - self.assertEqual( - svn["some other key"], - False, - msg="Should fall back to looking on the single value" - ) - - def test_repr(self): - with self.subTest("Filled data"): - svn = SingleValue(plus_one) - svn.run() - self.assertEqual( - svn.__repr__(), svn.outputs.y.value.__repr__(), - msg="SingleValueNodes should have their output as their representation" - ) - - with self.subTest("Not data"): - svn = SingleValue(no_default, output_labels="output") - self.assertIs(svn.outputs.output.value, NotData) - self.assertTrue( - svn.__repr__().endswith(NotData.__name__), - msg="When the output is still not data, the representation should " - "indicate this" - ) - - def test_str(self): - svn = SingleValue(plus_one) - svn.run() - self.assertTrue( - str(svn).endswith(str(svn.single_value)), - msg="SingleValueNodes should have their output as a string in their string " - "representation (e.g., perhaps with a reminder note that this is " - "actually still a Function and not just the value you're seeing.)" - ) - - def test_easy_output_connection(self): - svn = SingleValue(plus_one) - regular = Function(plus_one) - - regular.inputs.x = svn - - self.assertIn( - svn.outputs.y, regular.inputs.x.connections, - msg="SingleValueNodes should be able to make connections between their " - "output and another node's input by passing themselves" - ) - - svn > regular - svn.run() - self.assertEqual( - regular.outputs.y.value, 3, - msg="SingleValue connections should pass data just like usual; in this " - "case default->plus_one->plus_one = 1 + 1 +1 = 3" - ) - - at_instantiation = Function(plus_one, x=svn) - self.assertIn( - svn.outputs.y, at_instantiation.inputs.x.connections, - msg="The parsing of SingleValue output as a connection should also work" - "from assignment at instantiation" - ) - - def test_working_directory(self): - n_f = Function(plus_one) - self.assertTrue(n_f._working_directory is None) - self.assertIsInstance(n_f.working_directory, DirectoryObject) - self.assertTrue(str(n_f.working_directory.path).endswith(n_f.label)) - n_f.working_directory.delete() - - def test_disconnection(self): - n1 = Function(no_default, output_labels="out") - n2 = Function(no_default, output_labels="out") - n3 = Function(no_default, output_labels="out") - n4 = Function(plus_one) - - n3.inputs.x = n1.outputs.out - n3.inputs.y = n2.outputs.out - n4.inputs.x = n3.outputs.out - n2 > n3 > n4 - disconnected = n3.disconnect() - self.assertListEqual( - disconnected, - [ - # Inputs - (n3.inputs.x, n1.outputs.out), - (n3.inputs.y, n2.outputs.out), - # Outputs - (n3.outputs.out, n4.inputs.x), - # Signals (inputs, then output) - (n3.signals.input.run, n2.signals.output.ran), - (n3.signals.output.ran, n4.signals.input.run), - ], - msg="Expected to find pairs (starting with the node disconnect was called " - "on) of all broken connections among input, output, and signals." - ) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/unit/workflow/test_io.py b/tests/unit/workflow/test_io.py deleted file mode 100644 index e32c3815c..000000000 --- a/tests/unit/workflow/test_io.py +++ /dev/null @@ -1,181 +0,0 @@ -from unittest import TestCase, skipUnless -from sys import version_info - -from pyiron_contrib.workflow.channels import ( - InputData, InputSignal, OutputData, OutputSignal -) -from pyiron_contrib.workflow.io import Inputs, Outputs, Signals - - -class DummyNode: - def __init__(self): - self.running = False - self.label = "node_label" - - def update(self): - pass - - -@skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestDataIO(TestCase): - - @classmethod - def setUp(self) -> None: - node = DummyNode() - self.inputs = [ - InputData(label="x", node=node, default=0, type_hint=float), - InputData(label="y", node=node, default=1, type_hint=float) - ] - outputs = [ - OutputData(label="a", node=node, type_hint=float), - ] - - self.post_facto_output = OutputData(label="b", node=node, type_hint=float) - - self.input = Inputs(*self.inputs) - self.output = Outputs(*outputs) - - def test_access(self): - self.assertEqual(self.input.x, self.input["x"]) - - def test_assignment(self): - with self.assertRaises(TypeError): - self.input.foo = "not an input channel" - - with self.assertRaises(TypeError): - # Right label, and a channel, but wrong type of channel - self.input.b = self.post_facto_output - - with self.subTest("Successful channel assignment"): - self.output.b = self.post_facto_output - - with self.subTest("Can assign to a key that is not the label"): - label_before_assignment = self.post_facto_output.label - self.output.not_this_channels_name = self.post_facto_output - self.assertIs( - self.output.not_this_channels_name, - self.post_facto_output, - msg="Expected channel to get assigned" - ) - self.assertEqual( - self.post_facto_output.label, - label_before_assignment, - msg="Labels should not get updated on assignment of channels to IO " - "collections" - ) - - def test_connection(self): - self.input.x = self.input.y - self.assertEqual( - 0, - len(self.input.x.connections), - msg="Shouldn't be allowed to connect two inputs, but only passes warning" - ) - - self.input.x = self.output.a - self.assertIn( - self.input.x, - self.output.a.connections, - msg="Should be able to create connections by assignment" - ) - - self.input.x = 7 - self.assertEqual(self.input.x.value, 7) - - self.input.y = self.output.a - disconnected = self.input.disconnect() - self.assertListEqual( - disconnected, - [ - (self.input.x, self.output.a), - (self.input.y, self.output.a) - ], - msg="Disconnecting the panel should disconnect all children" - ) - - def test_conversion(self): - converted = self.input.to_value_dict() - for template in self.inputs: - self.assertEqual(template.default, converted[template.label]) - self.assertEqual( - len(self.inputs), - len(converted), - msg="And it shouldn't have any extra items either" - ) - - def test_iteration(self): - self.assertTrue(all([c.label in self.input.labels for c in self.input])) - - def test_connections_property(self): - self.assertEqual( - len(self.input.connections), - 0, - msg="Sanity check expectations about self.input" - ) - self.assertEqual( - len(self.output.connections), - 0, - msg="Sanity check expectations about self.input" - ) - - for inp in self.input: - inp.connect(self.output.a) - - self.assertEqual( - len(self.output.connections), - len(self.input), - msg="Expected to find all the channels in the input" - ) - self.assertEqual( - len(self.input.connections), - 1, - msg="Each unique connection should appear only once" - ) - self.assertIs( - self.input.connections[0], - self.input.x.connections[0], - msg="The IO connection found should be the same object as the channel " - "connection" - ) - -@skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestDataIO(TestCase): - def setUp(self) -> None: - node = DummyNode() - - def do_nothing(): - pass - - signals = Signals() - signals.input.run = InputSignal("run", node, do_nothing) - signals.input.foo = InputSignal("foo", node, do_nothing) - signals.output.ran = OutputSignal("ran", node) - signals.output.bar = OutputSignal("bar", node) - - signals.output.ran > signals.input.run - signals.output.ran > signals.input.foo - signals.output.bar > signals.input.run - signals.output.bar > signals.input.foo - - self.signals = signals - - def test_disconnect(self): - self.assertEqual( - 4, - len(self.signals.disconnect()), - msg="Disconnect should disconnect all on panels and the Signals super-panel" - ) - - def test_disconnect_run(self): - self.assertEqual( - 2, - len(self.signals.disconnect_run()), - msg="Should disconnect exactly everything connected to run" - ) - - no_run_signals = Signals() - self.assertEqual( - 0, - len(no_run_signals.disconnect_run()), - msg="If there is no run channel, the list of disconnections should be empty" - ) diff --git a/tests/unit/workflow/test_macro.py b/tests/unit/workflow/test_macro.py deleted file mode 100644 index e4c45d466..000000000 --- a/tests/unit/workflow/test_macro.py +++ /dev/null @@ -1,242 +0,0 @@ -from functools import partialmethod -import unittest -from sys import version_info - -from pyiron_contrib.workflow.channels import NotData -from pyiron_contrib.workflow.function import SingleValue -from pyiron_contrib.workflow.macro import Macro - - -def add_one(x): - result = x + 1 - return result - - -def add_three_macro(macro): - macro.one = SingleValue(add_one) - SingleValue(add_one, macro.one, label="two", parent=macro) - macro.add(SingleValue(add_one, macro.two, label="three")) - # Cover a handful of addition methods, - # although these are more thoroughly tested in Workflow tests - - -@unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestMacro(unittest.TestCase): - - def test_labels(self): - m = Macro(add_three_macro) - self.assertEqual( - m.label, - add_three_macro.__name__, - msg="Label should be automatically generated" - ) - label = "custom_name" - m2 = Macro(add_three_macro, label=label) - self.assertEqual(m2.label, label, msg="Should be able to specify a label") - - def test_wrapper_function(self): - m = Macro(add_three_macro) - - self.assertIs( - m.outputs.three__result.value, - NotData, - msg="Output should be accessible with the usual naming convention, but we " - "have not run yet so there shouldn't be any data" - ) - - input_x = 1 - expected_value = add_one(add_one(add_one(input_x))) - out = m(one__x=input_x) # Take kwargs to set input at runtime - - self.assertEqual( - out.three__result, - expected_value, - msg="Macros should return the output, just like other nodes" - ) - self.assertEqual( - m.outputs.three__result.value, - expected_value, - msg="Macros should get output updated, just like other nodes" - ) - - def test_subclass(self): - class MyMacro(Macro): - def build_graph(self): - add_three_macro(self) - - __init__ = partialmethod( - Macro.__init__, - build_graph, - ) - - x = 0 - m = MyMacro(one__x=x) - m.run() - self.assertEqual( - m.outputs.three__result.value, - add_one(add_one(add_one(x))), - msg="Subclasses should be able to simply override the graph_creator arg" - ) - - def test_key_map(self): - m = Macro( - add_three_macro, - inputs_map={"one__x": "my_input"}, - outputs_map={ - "three__result": "my_output", - "two__result": "intermediate" - }, - ) - self.assertSetEqual( - set(m.inputs.labels), - set(("my_input",)), - msg="Input should be relabelled, but not added to or taken away from" - ) - self.assertSetEqual( - set(m.outputs.labels), - set(("my_output", "intermediate")), - msg="Output should be relabelled and expanded" - ) - - with self.subTest("Make new names can be used as usual"): - x = 0 - out = m(my_input=x) - self.assertEqual( - out.my_output, - add_one(add_one(add_one(x))), - msg="Expected output but relabeled should be accessible" - ) - self.assertEqual( - out.intermediate, - add_one(add_one(x)), - msg="New, internally connected output that was specifically requested " - "should be accessible" - ) - - with self.subTest("IO can be disabled"): - m = Macro( - add_three_macro, - inputs_map={"one__x": None}, - outputs_map={"three__result": None}, - ) - self.assertEqual( - len(m.inputs.labels), - 0, - msg="Only inputs should have been disabled" - ) - self.assertEqual( - len(m.outputs.labels), - 0, - msg="Only outputs should have been disabled" - ) - - def test_nesting(self): - def nested_macro(macro): - macro.a = SingleValue(add_one) - macro.b = Macro( - add_three_macro, - one__x=macro.a, - outputs_map={"two__result": "intermediate_result"} - ) - macro.c = Macro( - add_three_macro, - one__x=macro.b.outputs.three__result, - outputs_map={"two__result": "intermediate_result"} - ) - macro.d = SingleValue( - add_one, - x=macro.c.outputs.three__result, - ) - macro.a > macro.b > macro.c > macro.d - macro.starting_nodes = [macro.a] - # This definition of the execution graph is not strictly necessary in this - # simple DAG case; we just do it to make sure nesting definied/automatic - # macros works ok - macro.outputs_map = {"b__intermediate_result": "deep_output"} - - m = Macro(nested_macro) - self.assertEqual(m(a__x=0).d__result, 8) - - m2 = Macro(nested_macro) - - with self.subTest("Test Node.get_parent_proximate_to"): - self.assertIs( - m.b, - m.b.two.get_parent_proximate_to(m), - msg="Should return parent closest to the passed composite" - ) - - self.assertIsNone( - m.b.two.get_parent_proximate_to(m2), - msg="Should return None when composite is not in parentage" - ) - - with self.subTest("Test Node.get_first_shared_parent"): - self.assertIs( - m.b, - m.b.two.get_first_shared_parent(m.b.three), - msg="Should get the parent when parents are the same" - ) - self.assertIs( - m, - m.b.two.get_first_shared_parent(m.c.two), - msg="Should find first matching object in parentage" - ) - self.assertIs( - m, - m.b.two.get_first_shared_parent(m.d), - msg="Should work when depth is not equal" - ) - self.assertIsNone( - m.b.two.get_first_shared_parent(m2.b.two), - msg="Should return None when no shared parent exists" - ) - self.assertIsNone( - m.get_first_shared_parent(m.b), - msg="Should return None when parent is None" - ) - - def test_execution_automation(self): - fully_automatic = add_three_macro - - def fully_defined(macro): - add_three_macro(macro) - macro.one > macro.two > macro.three - macro.starting_nodes = [macro.one] - - def only_order(macro): - add_three_macro(macro) - macro.two > macro.three - - def only_starting(macro): - add_three_macro(macro) - macro.starting_nodes = [macro.one] - - m_auto = Macro(fully_automatic) - m_user = Macro(fully_defined) - - x = 0 - expected = add_one(add_one(add_one(x))) - self.assertEqual( - m_auto(one__x=x).three__result, - expected, - "DAG macros should run fine without user specification of execution." - ) - self.assertEqual( - m_user(one__x=x).three__result, - expected, - "Macros should run fine if the user nicely specifies the exeuction graph." - ) - - with self.subTest("Partially specified execution should fail"): - # We don't yet check for _crappy_ user-defined execution, - # But we should make sure it's at least valid in principle - with self.assertRaises(ValueError): - Macro(only_order) - - with self.assertRaises(ValueError): - Macro(only_starting) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/unit/workflow/test_node_package.py b/tests/unit/workflow/test_node_package.py deleted file mode 100644 index 0d0c727e5..000000000 --- a/tests/unit/workflow/test_node_package.py +++ /dev/null @@ -1,68 +0,0 @@ -from unittest import TestCase, skipUnless -from sys import version_info - -from pyiron_contrib.workflow.node_package import NodePackage -from pyiron_contrib.workflow.function import function_node - - -@function_node() -def dummy(x: int = 0): - return x - - -@skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestNodePackage(TestCase): - def setUp(self) -> None: - self.package = NodePackage(dummy) - - def test_init(self): - self.assertTrue( - hasattr(self.package, dummy.__name__), - msg="Classes should be added at instantiation" - ) - - def test_access(self): - node = self.package.Dummy() - self.assertIsInstance(node, dummy) - - def test_update(self): - with self.assertRaises(KeyError): - self.package.Dummy = "This is already a node class name" - - with self.assertRaises(KeyError): - self.package.update = "This is already a method" - - with self.assertRaises(TypeError): - self.package.available_name = "But we can still only assign node classes" - - @function_node("y") - def add(x: int = 0): - return x + 1 - - self.package.node_class_and_free_key = add # Should work! - - with self.assertRaises(KeyError): - # This is already occupied by another node class - self.package.Dummy = add - - old_dummy_instance = self.package.Dummy(label="old_dummy_instance") - - @function_node() - def dummy(x: int = 0): - y = x + 1 - return y - - self.package.update(dummy) - - self.assertEqual(len(self.package), 2, msg="Update should replace, not extend") - - new_dummy_instance = self.package.Dummy(label="new_dummy_instance") - - old_dummy_instance.run() - new_dummy_instance.run() - self.assertEqual( - old_dummy_instance.outputs.x.value, 0, msg="Should have old functionality" - ) - self.assertEqual( - new_dummy_instance.outputs.y.value, 1, msg="Should have new functionality" - ) diff --git a/tests/unit/workflow/test_output_parser.py b/tests/unit/workflow/test_output_parser.py deleted file mode 100644 index 84b63b3de..000000000 --- a/tests/unit/workflow/test_output_parser.py +++ /dev/null @@ -1,90 +0,0 @@ -from sys import version_info -import unittest - -import numpy as np - -from pyiron_contrib.workflow.output_parser import ParseOutput - - -@unittest.skipUnless( - version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+" -) -class TestParseOutput(unittest.TestCase): - def test_parsing(self): - with self.subTest("Single return"): - def identity(x): - return x - self.assertListEqual(ParseOutput(identity).output, ["x"]) - - with self.subTest("Expression return"): - def add(x, y): - return x + y - self.assertListEqual(ParseOutput(add).output, ["x + y"]) - - with self.subTest("Weird whitespace"): - def add_with_whitespace(x, y): - return x + y - self.assertListEqual(ParseOutput(add_with_whitespace).output, ["x + y"]) - - with self.subTest("Multiple expressions"): - def add_and_subtract(x, y): - return x + y, x - y - self.assertListEqual( - ParseOutput(add_and_subtract).output, - ["x + y", "x - y"] - ) - - with self.subTest("Best-practice (well-named return vars)"): - def md(job): - temperature = job.output.temperature - energy = job.output.energy - return temperature, energy - self.assertListEqual(ParseOutput(md).output, ["temperature", "energy"]) - - with self.subTest("Function call returns"): - def function_return(i, j): - return ( - np.arange( - i, dtype=int - ), - np.shape(i, j) - ) - self.assertListEqual( - ParseOutput(function_return).output, - ["np.arange( i, dtype=int )", "np.shape(i, j)"] - ) - - with self.subTest("Methods too"): - class Foo: - def add(self, x, y): - return x + y - self.assertListEqual(ParseOutput(Foo.add).output, ["x + y"]) - - def test_void(self): - with self.subTest("No return"): - def no_return(): - pass - self.assertIsNone(ParseOutput(no_return).output) - - with self.subTest("Empty return"): - def empty_return(): - return - self.assertIsNone(ParseOutput(empty_return).output) - - with self.subTest("Return None explicitly"): - def none_return(): - return None - self.assertIsNone(ParseOutput(none_return).output) - - def test_multiple_branches(self): - def bifurcating(x): - if x > 5: - return True - else: - return False - with self.assertRaises(ValueError): - ParseOutput(bifurcating) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/unit/workflow/test_type_hinting.py b/tests/unit/workflow/test_type_hinting.py deleted file mode 100644 index ade570e5a..000000000 --- a/tests/unit/workflow/test_type_hinting.py +++ /dev/null @@ -1,82 +0,0 @@ -import typing -from unittest import TestCase, skipUnless -from sys import version_info - -from pyiron_contrib.workflow.type_hinting import ( - type_hint_is_as_or_more_specific_than, valid_value -) - - -@skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestTypeHinting(TestCase): - def test_value_validation(self): - class Foo: - pass - - class Bar: - def __call__(self): - return None - - for hint, good, bad in ( - (int | float, 1, "foo"), - (typing.Union[int, float], 2.0, "bar"), - (typing.Literal[1, 2], 2, 3), - (typing.Literal[1, 2], 1, "baz"), - (Foo, Foo(), Foo), - (typing.Type[Bar], Bar, Bar()), - # (callable, Bar(), Foo()), # Misses the bad! - # Can't hint args and returns without typing.Callable anyhow, so that's - # what people should be using regardless - (typing.Callable, Bar(), Foo()), - (tuple[int, float], (1, 1.1), ("fo", 0)), - (dict[str, int], {'a': 1}, {'a': 'b'}), - ): - with self.subTest(msg=f"Good {good} vs hint {hint}"): - self.assertTrue(valid_value(good, hint)) - with self.subTest(msg=f"Bad {bad} vs hint {hint}"): - self.assertFalse(valid_value(bad, hint)) - - def test_hint_comparisons(self): - # Standard types and typing types should be interoperable - # tuple, dict, and typing.Callable care about the exact matching of args - # Everyone else just needs to have args be a subset (e.g. typing.Literal) - - for target, reference, is_more_specific in [ - (int, int | float, True), - (int | float, int, False), - (typing.Literal[1, 2], typing.Literal[1, 2, 3], True), - (typing.Literal[1, 2, 3], typing.Literal[1, 2], False), - (tuple[str, int], typing.Tuple[str, int], True), - (typing.Tuple[int, str], tuple[str, int], False), - (tuple[str, int], typing.Tuple[str, int | float], True), - (typing.Tuple[str, int | float], tuple[str, int], False), - (tuple[str, int], typing.Tuple, True), - (tuple[str, int], tuple[str, int, float], False), - (list[int], typing.List[int], True), - (typing.List, list[int], False), - (dict[str, int], typing.Dict[str, int], True), - (dict[int, str], typing.Dict[str, int], False), - (typing.Callable[[int, float], None], typing.Callable, True), - ( - typing.Callable[[int, float], None], - typing.Callable[[float, int], None], - False - ), - ( - typing.Callable[[int, float], float], - typing.Callable[[int, float], float | str], - True - ), - ( - typing.Callable[[int, float, str], float], - typing.Callable[[int, float], float], - False - ), - ]: - with self.subTest( - target=target, reference=reference, expected=is_more_specific - ): - self.assertEqual( - type_hint_is_as_or_more_specific_than(target, reference), - is_more_specific - ) \ No newline at end of file diff --git a/tests/unit/workflow/test_util.py b/tests/unit/workflow/test_util.py deleted file mode 100644 index c48e8e72a..000000000 --- a/tests/unit/workflow/test_util.py +++ /dev/null @@ -1,14 +0,0 @@ -from unittest import TestCase, skipUnless -from sys import version_info - -import pyiron_contrib.workflow.util as util - - -@skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestUtil(TestCase): - def test_dot_dict(self): - dd = util.DotDict({'foo': 42}) - - self.assertEqual(dd['foo'], dd.foo, msg="Dot access should be equivalent.") - dd.bar = "towel" - self.assertEqual("towel", dd["bar"], msg="Dot assignment should be equivalent.") \ No newline at end of file diff --git a/tests/unit/workflow/test_workflow.py b/tests/unit/workflow/test_workflow.py deleted file mode 100644 index 2becf7818..000000000 --- a/tests/unit/workflow/test_workflow.py +++ /dev/null @@ -1,343 +0,0 @@ -import unittest -from sys import version_info -from time import sleep - -from pyiron_contrib.workflow.channels import NotData -from pyiron_contrib.workflow.files import DirectoryObject -from pyiron_contrib.workflow.util import DotDict -from pyiron_contrib.workflow.workflow import Workflow - - -def plus_one(x=0): - y = x + 1 - return y - - -@unittest.skipUnless(version_info[0] == 3 and version_info[1] >= 10, "Only supported for 3.10+") -class TestWorkflow(unittest.TestCase): - - def test_node_addition(self): - wf = Workflow("my_workflow") - - # Validate the four ways to add a node - wf.add(Workflow.create.Function(plus_one, label="foo")) - wf.create.Function(plus_one, label="bar") - wf.baz = wf.create.Function(plus_one, label="whatever_baz_gets_used") - Workflow.create.Function(plus_one, label="qux", parent=wf) - self.assertListEqual(list(wf.nodes.keys()), ["foo", "bar", "baz", "qux"]) - wf.boa = wf.qux - self.assertListEqual( - list(wf.nodes.keys()), - ["foo", "bar", "baz", "boa"], - msg="Reassignment should remove the original instance" - ) - - wf.strict_naming = False - # Validate name incrementation - wf.add(Workflow.create.Function(plus_one, label="foo")) - wf.create.Function(plus_one, label="bar") - wf.baz = wf.create.Function( - plus_one, - label="without_strict_you_can_override_by_assignment" - ) - Workflow.create.Function(plus_one, label="boa", parent=wf) - self.assertListEqual( - list(wf.nodes.keys()), - [ - "foo", "bar", "baz", "boa", - "foo0", "bar0", "baz0", "boa0", - ] - ) - - with self.subTest("Make sure strict naming causes a bunch of attribute errors"): - wf.strict_naming = True - # Validate name preservation - with self.assertRaises(AttributeError): - wf.add(wf.create.Function(plus_one, label="foo")) - - with self.assertRaises(AttributeError): - wf.create.Function(plus_one, label="bar") - - with self.assertRaises(AttributeError): - wf.baz = wf.create.Function(plus_one, label="whatever_baz_gets_used") - - with self.assertRaises(AttributeError): - Workflow.create.Function(plus_one, label="boa", parent=wf) - - def test_node_packages(self): - wf = Workflow("my_workflow") - - # Test invocation - wf.create.atomistics.Bulk(cubic=True, element="Al") - # Test invocation with attribute assignment - wf.engine = wf.create.atomistics.Lammps(structure=wf.bulk) - - self.assertSetEqual( - set(wf.nodes.keys()), - set(["bulk", "engine"]), - msg=f"Expected one node label generated automatically from the class and " - f"the other from the attribute assignment, but got {wf.nodes.keys()}" - ) - - def test_double_workfloage_and_node_removal(self): - wf1 = Workflow("one") - wf1.create.Function(plus_one, label="node1") - node2 = Workflow.create.Function( - plus_one, label="node2", parent=wf1, x=wf1.node1.outputs.y - ) - self.assertTrue(node2.connected) - - wf2 = Workflow("two") - with self.assertRaises(ValueError): - # Can't belong to two workflows at once - wf2.add(node2) - wf1.remove(node2) - wf2.add(node2) - self.assertEqual(node2.parent, wf2) - self.assertFalse(node2.connected) - - def test_workflow_io(self): - wf = Workflow("wf") - wf.create.Function(plus_one, label="n1") - wf.create.Function(plus_one, label="n2") - wf.create.Function(plus_one, label="n3") - - with self.subTest("Workflow IO should be drawn from its nodes"): - self.assertEqual(len(wf.inputs), 3) - self.assertEqual(len(wf.outputs), 3) - - wf.n3.inputs.x = wf.n2.outputs.y - wf.n2.inputs.x = wf.n1.outputs.y - - with self.subTest("Only unconnected channels should count"): - self.assertEqual(len(wf.inputs), 1) - self.assertEqual(len(wf.outputs), 1) - - with self.subTest( - "IO should be re-mappable, including exposing internally connected " - "channels" - ): - wf.inputs_map = {"n1__x": "inp"} - wf.outputs_map = {"n3__y": "out", "n2__y": "intermediate"} - out = wf(inp=0) - self.assertEqual(out.out, 3) - self.assertEqual(out.intermediate, 2) - - def test_node_decorator_access(self): - @Workflow.wrap_as.function_node("y") - def plus_one(x: int = 0) -> int: - return x + 1 - - self.assertEqual(plus_one().run(), 1) - - def test_working_directory(self): - wf = Workflow("wf") - self.assertTrue(wf._working_directory is None) - self.assertIsInstance(wf.working_directory, DirectoryObject) - self.assertTrue(str(wf.working_directory.path).endswith(wf.label)) - wf.create.Function(plus_one) - self.assertTrue( - str(wf.plus_one.working_directory.path).endswith(wf.plus_one.label) - ) - wf.working_directory.delete() - - def test_no_parents(self): - wf = Workflow("wf") - wf2 = Workflow("wf2") - wf2.parent = None # Is already the value and should ignore this - with self.assertRaises(TypeError): - # We currently specify workflows shouldn't get parents, this just verifies - # the spec. If that spec changes, test instead that you _can_ set parents! - wf2.parent = "not None" - - with self.assertRaises(TypeError): - # Setting a non-None value to parent raises the type error from the setter - wf2.parent = wf - - 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): - wf = Workflow("wf") - - @Workflow.wrap_as.single_value_node() - def five(sleep_time=0.): - sleep(sleep_time) - five = 5 - return five - - @Workflow.wrap_as.single_value_node("sum") - def sum(a, b): - return a + b - - wf.slow = five(sleep_time=1) - wf.fast = five() - wf.sum = sum(a=wf.fast, b=wf.slow) - - wf.slow.executor = wf.create.CloudpickleProcessPoolExecutor() - - wf.slow.run() - wf.fast.run() - self.assertTrue( - wf.slow.running, - msg="The slow node should still be running" - ) - self.assertEqual( - wf.fast.outputs.five.value, - 5, - msg="The slow node should not prohibit the completion of the fast node" - ) - self.assertEqual( - wf.sum.outputs.sum.value, - NotData, - msg="The slow node _should_ hold up the downstream node to which it inputs" - ) - - while wf.slow.future.running(): - sleep(0.1) - - wf.sum.run() - self.assertEqual( - wf.sum.outputs.sum.value, - 5 + 5, - msg="After the slow node completes, its output should be updated as a " - "callback, and downstream nodes should proceed" - ) - - def test_call(self): - wf = Workflow("wf") - - wf.a = wf.create.SingleValue(plus_one) - wf.b = wf.create.SingleValue(plus_one) - - @Workflow.wrap_as.single_value_node("sum") - def sum_(a, b): - return a + b - - wf.sum = sum_(wf.a, wf.b) - wf.run() - self.assertEqual( - wf.a.outputs.y.value + wf.b.outputs.y.value, - wf.sum.outputs.sum.value, - msg="Sanity check" - ) - wf(a__x=42, b__x=42) - self.assertEqual( - plus_one(42) + plus_one(42), - wf.sum.outputs.sum.value, - msg="Workflow should accept input channel kwargs and update inputs " - "accordingly" - # Since the nodes run automatically, there is no need for wf.run() here - ) - - with self.assertRaises(TypeError): - # IO is not ordered, so args make no sense for a workflow call - # We _must_ use kwargs - wf(42, 42) - - def test_return_value(self): - wf = Workflow("wf") - wf.a = wf.create.SingleValue(plus_one) - wf.b = wf.create.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" - ) - - wf.inputs.a__x = 2 - 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 - - def test_execution_automation(self): - @Workflow.wrap_as.single_value_node("out") - def foo(x, y): - return x + y - - def make_workflow(): - wf = Workflow("dag") - wf.n1l = foo(0, 1) - wf.n1r = foo(2, 0) - wf.n2l = foo(-10, wf.n1l) - wf.n2m = foo(wf.n1l, wf.n1r) - wf.n2r = foo(wf.n1r, 10) - return wf - - def matches_expectations(results): - expected = {'n2l__out': -9, 'n2m__out': 3, 'n2r__out': 12} - return all(expected[k] == v for k, v in results.items()) - - auto = make_workflow() - self.assertTrue( - matches_expectations(auto()), - msg="DAGs should run automatically" - ) - - user = make_workflow() - user.automate_execution = False - user.n1l > user.n1r > user.n2l - user.n1r > user.n2m - user.n1r > user.n2r - user.starting_nodes = [user.n1l] - self.assertTrue( - matches_expectations(user()), - msg="Users shoudl be allowed to ask to run things manually" - ) - - self.assertIn( - user.n1r.signals.output.ran, - user.n2r.signals.input.run.connections, - msg="Expected execution signals as manually defined" - ) - user.automate_execution = True - self.assertTrue( - matches_expectations(user()), - msg="Users should be able to switch back to automatic execution" - ) - self.assertNotIn( - user.n1r.signals.output.ran, - user.n2r.signals.input.run.connections, - msg="Expected old execution signals to be overwritten" - ) - self.assertIn( - user.n2m.signals.output.ran, - user.n2r.signals.input.run.connections, - msg="At time of writing tests, automation makes a linear execution flow " - "based on node topology and initialized by the order of appearance in " - "the nodes list, so for a simple DAG like this the final node should " - "be getting triggered by the penultimate node." - "If this test failed, maybe you've written more sophisticated " - "automation." - ) - - with self.subTest("Make sure automated cyclic graphs throw an error"): - trivially_cyclic = make_workflow() - trivially_cyclic.n1l.inputs.y = trivially_cyclic.n1l - with self.assertRaises(ValueError): - trivially_cyclic() - - cyclic = make_workflow() - cyclic.n1l.inputs.y = cyclic.n2l - with self.assertRaises(ValueError): - cyclic() - - -if __name__ == '__main__': - unittest.main() From 61fda8850fad5b805dfef88db617cbb6c194cd1c Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Fri, 6 Oct 2023 21:40:55 +0000 Subject: [PATCH 702/756] [dependabot skip] Update env file --- .binder/environment.yml | 4 ---- docs/environment.yml | 4 ---- 2 files changed, 8 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index edd2e947a..3d1287ff5 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -2,7 +2,6 @@ channels: - conda-forge dependencies: - ase =3.22.1 -- cloudpickle - coveralls - coverage - codacy-coverage @@ -14,13 +13,10 @@ dependencies: - scipy =1.11.3 - seaborn =0.13.0 - scikit-image =0.21.0 -- toposort - randspg =0.0.1 - boto3 =1.28.60 - moto =4.2.5 - pycp2k =0.2.2 -- python-graphviz -- typeguard =4.1.5 - aws-sam-translator =1.77.0 - pympipool =0.7.1 - distributed =2023.9.3 diff --git a/docs/environment.yml b/docs/environment.yml index 1badbe003..70d62a63b 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -4,7 +4,6 @@ dependencies: - ipykernel - nbsphinx - ase =3.22.1 -- cloudpickle - coveralls - coverage - codacy-coverage @@ -16,13 +15,10 @@ dependencies: - scipy =1.11.3 - seaborn =0.13.0 - scikit-image =0.21.0 -- toposort - randspg =0.0.1 - boto3 =1.28.60 - moto =4.2.5 - pycp2k =0.2.2 -- python-graphviz -- typeguard =4.1.5 - aws-sam-translator =1.77.0 - pympipool =0.7.1 - distributed =2023.9.3 From d3bed88e80fb3aa18bbdd969b270acaa181f9d6c Mon Sep 17 00:00:00 2001 From: liamhuber Date: Fri, 6 Oct 2023 15:24:32 -0700 Subject: [PATCH 703/756] Remember the demo notebook --- notebooks/workflow_example.ipynb | 3106 ------------------------------ 1 file changed, 3106 deletions(-) delete mode 100644 notebooks/workflow_example.ipynb diff --git a/notebooks/workflow_example.ipynb b/notebooks/workflow_example.ipynb deleted file mode 100644 index ed278dbad..000000000 --- a/notebooks/workflow_example.ipynb +++ /dev/null @@ -1,3106 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "5edfe456-c5b8-4347-a74f-1fb19fdff91b", - "metadata": {}, - "source": [ - "# Pyiron workflows: Introduction and Syntax\n", - "\n", - "Here we will highlight:\n", - "- How to instantiate a node\n", - "- How to make reusable node classes\n", - "- How to connect node inputs and outputs together\n", - "- SingleValue nodes and syntactic sugar\n", - "- Workflows: keeping your computational graphs organized\n", - "- Using pre-defined nodes \n", - "- Macro nodes" - ] - }, - { - "cell_type": "markdown", - "id": "f4e75528-3ea7-4feb-8167-533d439f798d", - "metadata": {}, - "source": [ - "## Instantiating a node\n", - "\n", - "Simple nodes can be defined on-the-fly by passing any callable to the `Function(Node)` class. This transforms the function into a node instance which has input and output, can be connected to other nodes in a workflow, and can run the function it stores.\n", - "\n", - "Input and output channels are _automatically_ extracted from the signature and return value(s) of the function. (Note: \"Nodized\" functions must have _at most_ one `return` expression!)" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "8aca3b9b-9ba6-497a-ba9e-abdb15a6a5df", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "89ec887909114967be06c171de9e83c6", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from pyiron_contrib.workflow.function import Function" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "2502bc6b-d961-43d1-b2d9-66b20e2740d7", - "metadata": {}, - "outputs": [], - "source": [ - "def plus_minus_one(x):\n", - " return x+1, x-1\n", - "\n", - "pm_node = Function(plus_minus_one)" - ] - }, - { - "cell_type": "markdown", - "id": "5d15f0c2-b36d-4960-86b3-40d769f78528", - "metadata": {}, - "source": [ - "This has automatically created a node with input and output data channels whose labels are gathered by inspecting the function:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "840f4c07-4b21-4bcc-b15c-9847c6c1b048", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['x'] ['x+1', 'x-1']\n" - ] - } - ], - "source": [ - "print(pm_node.inputs.labels, pm_node.outputs.labels)" - ] - }, - { - "cell_type": "markdown", - "id": "22ee2a49-47d1-4cec-bb25-8441ea01faf7", - "metadata": {}, - "source": [ - "The output is still empty (`NotData`) because we haven't `run()` the node:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "613a90fa-66ed-49f8-ba8c-2f83a54253cd", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'x+1': , 'x-1': }\n" - ] - } - ], - "source": [ - "print(pm_node.outputs.to_value_dict())" - ] - }, - { - "cell_type": "markdown", - "id": "0374e277-55ab-45d2-8058-b06365bd07af", - "metadata": {}, - "source": [ - "If we try that now though, we'll just get a type error because the input is not set! " - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "05196cd8-97c7-4f08-ae3a-ad6a076512f7", - "metadata": {}, - "outputs": [], - "source": [ - "# pm_node.run()\n", - "pm_node.update()" - ] - }, - { - "cell_type": "markdown", - "id": "48b0db5a-548e-4195-8361-76763ddf0474", - "metadata": {}, - "source": [ - "Using the softer `update()` call checks to make sure the input is `ready` before moving on to `run()`, avoiding this error. In this case, `update()` sees we have no input an aborts by returning `None`.\n", - "\n", - "(Note: If you _do_ swap `update()` to `run()` in this cell, not only will you get the expected error, but `pm_node` will also set its `failed` attribute to `True` -- this will prevent it from being `ready` again until you manually reset `pm_node.failed = False`.)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "b6c00a4e-0c39-4283-ac00-53c3d07f7f10", - "metadata": {}, - "outputs": [], - "source": [ - "# pm_node.failed = False" - ] - }, - { - "cell_type": "markdown", - "id": "84af4b04-79b4-4944-a4c9-131af915d254", - "metadata": {}, - "source": [ - "If we update the input, we'll give the node enough data to work with:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "b1500a40-f4f2-4c06-ad78-aaebcf3e9a50", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'x+1': 6, 'x-1': 4}\n" - ] - } - ], - "source": [ - "pm_node.inputs.x = 5\n", - "pm_node.run()\n", - "print(pm_node.outputs.to_value_dict())" - ] - }, - { - "cell_type": "markdown", - "id": "c54a691e-a075-4d41-bc0f-3a990857a27a", - "metadata": {}, - "source": [ - "Alternatively, the `run()` command (and `update()` when it proceeds to execution) just return the function's return value:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "e845843c-61f4-4e5c-ac1a-d005787c2841", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(6, 4)" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "out = pm_node.run()\n", - "out" - ] - }, - { - "cell_type": "markdown", - "id": "df4520d7-856e-4bc8-817f-5b2e22c1ddce", - "metadata": {}, - "source": [ - "We can give our function defaults so that it's ready to go from the beginning. Let's also take the opportunity to give our output channel a better name so we can get it by dot-access." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "ab1ac28a-6e69-491f-882f-da4a43162dd7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def adder(x: int = 0, y: int = 1) -> int:\n", - " sum_ = x + y\n", - " return sum_\n", - "\n", - "adder_node = Function(adder)\n", - "adder_node.run()\n", - "adder_node.outputs.sum_.value # We use `value` to see the data the channel holds" - ] - }, - { - "cell_type": "markdown", - "id": "58ed9b25-6dde-488d-9582-d49d405793c6", - "metadata": {}, - "source": [ - "This node also exploits type hinting! `run()` will always force the execution, but `update()` will not only check if the data is there, but also if it is the right type:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "ac0fe993-6c82-48c8-a780-cbd0c97fc386", - "metadata": {}, - "outputs": [], - "source": [ - "adder_node.inputs.x = \"not an integer\"\n", - "adder_node.inputs.x.type_hint, type(adder_node.inputs.x.value)\n", - "adder_node.update()\n", - "# No error because the update doesn't trigger a run since the type hint is not satisfied" - ] - }, - { - "cell_type": "markdown", - "id": "2737de39-6e75-44e1-b751-6315afe5c676", - "metadata": {}, - "source": [ - "Since the execution never happened, the output is unchanged" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "bcbd17f1-a3e4-44f0-bde1-cbddc51c5d73", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "adder_node.outputs.sum_.value" - ] - }, - { - "cell_type": "markdown", - "id": "263f5b24-113f-45d9-82cc-0475c59da587", - "metadata": {}, - "source": [ - "Note that assigning data to channels with `=` is actually just syntactic sugar for calling the `update` method of the underlying channel:" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "15742a49-4c23-4d4a-84d9-9bf19677544c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "3" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "adder_node.inputs.x.update(2)\n", - "adder_node.update()" - ] - }, - { - "cell_type": "markdown", - "id": "416ba898-21ee-4638-820f-0f04a98a6706", - "metadata": {}, - "source": [ - "We can also set new input as any valid combination of kwargs and/or args at both instantiation or on call:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "0c8f09a7-67c4-4c6c-a021-e3fea1a16576", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "30" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "adder_node(10, y=20)\n", - "adder_node.run()" - ] - }, - { - "cell_type": "markdown", - "id": "c0997630-c053-42bb-8c0d-332f8bc26216", - "metadata": {}, - "source": [ - "Finally, we can update input and then `run` together by calling an already-instantiated node. Just like at node instantiation, the input for `Function` nodes can be set by positional and/or keyword argument. Here we'll use two positional args:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "69b59737-9e09-4b4b-a0e2-76a09de02c08", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "31" - ] - }, - "execution_count": 14, - "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", - "metadata": {}, - "source": [ - "## Reusable node classes\n", - "\n", - "If we're going to use a node many times, we may want to define a new sub-class of `Function` to handle this.\n", - "\n", - "The can be done directly by inheriting from `Function` and overriding it's `__init__` function so that the core functionality of the node (i.e. the node function and output labels) are set in stone, but even easier is to use the `function_node` decorator to do this for you! \n", - "\n", - "The decorator also lets us explicitly choose the names of our output channels by passing the `output_labels` argument to the decorator -- as a string to create a single channel for the returned values, or as a list of strings equal to the number of returned values in a returned tuple." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "61b43a9b-8dad-48b7-9194-2045e465793b", - "metadata": {}, - "outputs": [], - "source": [ - "from pyiron_contrib.workflow.function import function_node" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "647360a9-c971-4272-995c-aa01e5f5bb83", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "class name = SubtractNode\n", - "label = subtract_node\n", - "default output = 1\n" - ] - } - ], - "source": [ - "@function_node(\"diff\")\n", - "def subtract_node(x: int | float = 2, y: int | float = 1) -> int | float:\n", - " return x - y\n", - "\n", - "sn = subtract_node()\n", - "print(\"class name =\", sn.__class__.__name__)\n", - "print(\"label =\", sn.label)\n", - "\n", - "sn() # Runs without updating input data, but we have defaults so that's fine\n", - "print(\"default output =\", sn.outputs.diff.value)" - ] - }, - { - "cell_type": "markdown", - "id": "9b9220b0-833d-4c6a-9929-5dfa60a47d14", - "metadata": { - "tags": [] - }, - "source": [ - "# Connecting nodes and controlling flow\n", - "\n", - "Multiple nodes can be used together to build a computational graph, with each node performing a particular operation in the overall workflow:\n", - "\n", - "The input and output of nodes can be chained together by connecting their data channels. When a node runs, its output channels will push their new value to each input node to whom they are connected. In this way, data propagates forwards\n", - "\n", - "In addition to input and output data channels, nodes also have \"signal\" channels available. Input signals are bound to a callback function (typically one of its node's methods), and output signals trigger the callbacks for all the input signal channels they're connected to.\n", - "\n", - "Standard nodes have a `run` input signal (which is, unsurprisingly, bound to the `run` method), and a `ran` output signal (which, again, hopefully with no great surprise, is triggered at the end of the `run` method.)\n", - "\n", - "In the example below we see how this works for a super-simple toy graph:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "2e418abf-7059-4e1e-9b9f-b3dc0a4b5e35", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1 2\n" - ] - } - ], - "source": [ - "@function_node()\n", - "def linear(x):\n", - " return x\n", - "\n", - "@function_node(\"double\")\n", - "def times_two(x):\n", - " return 2 * x\n", - "\n", - "l = linear()\n", - "t2 = times_two()\n", - "\n", - "l.inputs.x = 1\n", - "t2.inputs.x = l.outputs.x\n", - "t2.signals.input.run = l.signals.output.ran\n", - "\n", - "l.run()\n", - "print(t2.inputs.x, t2.outputs.double)" - ] - }, - { - "cell_type": "markdown", - "id": "5da1ecfc-7145-4fb2-b5c0-417f050c5de4", - "metadata": {}, - "source": [ - "We can use a couple pieces of syntactic sugar to make this faster.\n", - "\n", - "First: data connections can be made with keyword arguments just like other input data definitions.\n", - "\n", - "Second: the `>` is a shortcut for creating connections between the left-hand node's `signals.output.ran` channel and the right-hand node's `signals.input.run` channel.\n", - "\n", - "With both of these together, we can write:" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "59c29856-c77e-48a1-9f17-15d4c58be588", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "10 20\n" - ] - } - ], - "source": [ - "l = linear(x=10)\n", - "t2 = times_two(x=l.outputs.x)\n", - "l > t2\n", - "l.run()\n", - "print(t2.inputs.x, t2.outputs.double)" - ] - }, - { - "cell_type": "markdown", - "id": "e5c531a3-77e4-48ad-a189-fed619e79baa", - "metadata": {}, - "source": [ - "## Single Value nodes\n", - "\n", - "Many functions return just a single value. In this case, we can take advantage of the `SingleValue` node class which employs a bunch of syntactic tricks to make our lives easier.\n", - "\n", - "The main difference between this and it's parent the `Function` class is that attribute and item access fall back to looking for attributes and items of this single output value.\n", - "\n", - "Let's look at a use case:" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "1a4e9693-0980-4435-aecc-3331d8b608dd", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "\n", - "from pyiron_contrib.workflow.function import SingleValue" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "7c4d314b-33bb-4a67-bfb9-ed77fba3949c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "[0.02040816 0.04081633 0.06122449]\n", - "0.5\n" - ] - } - ], - "source": [ - "def linspace_node(\n", - " start: int | float = 0, stop: int | float = 1, num: int = 50\n", - "):\n", - " linspace = np.linspace(start=start, stop=stop, num=num)\n", - " return linspace\n", - "\n", - "lin = SingleValue(linspace_node)\n", - "lin.run()\n", - "\n", - "print(type(lin.outputs.linspace.value)) # Output is just what we expect\n", - "print(lin[1:4]) # Gets items from the output\n", - "print(lin.mean()) # Finds the method on the output -- a special feature of SingleValueNode" - ] - }, - { - "cell_type": "markdown", - "id": "eef23cb0-6192-4fe6-b9cc-007e261e347a", - "metadata": {}, - "source": [ - "The other advantage is that single value nodes can also be connected directly to input, since there is only one possible data connection. Of course it has a construction decorator just like `Function`, so let's replace `@function_node` with `@single_value_node` in one of our examples above to see how it tightens up the syntax a bit:" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "61ae572f-197b-4a60-8d3e-e19c1b9cc6e2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "times_two (TimesTwo) output single-value: 4\n" - ] - } - ], - "source": [ - "from pyiron_contrib.workflow.function import single_value_node\n", - "\n", - "@single_value_node()\n", - "def linear(x):\n", - " return x\n", - "\n", - "@single_value_node(\"double\")\n", - "def times_two(x):\n", - " return 2 * x\n", - "\n", - "l = linear(x=2)\n", - "t2 = times_two(x=l) # Just takes the whole `l` node!\n", - "l > t2\n", - "l.run()\n", - "print(t2)" - ] - }, - { - "cell_type": "markdown", - "id": "b2e56a64-d053-4127-bb8c-069777c1c6b5", - "metadata": {}, - "source": [ - "Nodes can take input from multiple sources, and we can chain together these execution orders:" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "6569014a-815b-46dd-8b47-4e1cd4584b3b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([0.45174171, 0.42157923, 0.505547 , 0.47028098, 0.43732173,\n", - " 0.50225988, 0.9376775 , 0.61550209, 0.81934053, 0.32220586])" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "@single_value_node()\n", - "def noise(length: int = 1):\n", - " array = np.random.rand(length)\n", - " return array\n", - "\n", - "@function_node()\n", - "def plot(x, y):\n", - " fig = plt.scatter(x, y)\n", - " return fig\n", - "\n", - "x = noise(length=10)\n", - "y = noise(length=10)\n", - "f = plot(x=x, y=y)\n", - "x > y > f\n", - "x()" - ] - }, - { - "cell_type": "markdown", - "id": "5dc12164-b663-405b-872f-756996f628bd", - "metadata": {}, - "source": [ - "# Workflows\n", - "\n", - "The case where we have groups of connected nodes working together is our normal, intended use case.\n", - "We offer a formal way to group these objects together as a `Workflow(Node)` object.\n", - "`Workflow` also offers us a single point of entry to the codebase -- i.e. most of the time you shouldn't need the node imports used above, because the decorators are available right on the workflow class.\n", - "\n", - "We will also see here that we can our node output channels using the `output_labels: Optional[str | list[str] | tuple[str]` kwarg, in case they don't have a convenient name to start with.\n", - "This way we can always have convenient dot-based access (and tab completion) instead of having to access things by string-based keys.\n", - "\n", - "Finally, when a workflow is run, unless its `automate_execution` flag has been set to `False` or the data connections form a cyclic graph, it will _automatically_ build the necessary run signals! That means for all directed acyclic graph (DAG) workflows, all we typically need to worry about is the data connections." - ] - }, - { - "cell_type": "markdown", - "id": "9b9d3881-3584-4d6f-8068-5eed05760c36", - "metadata": {}, - "source": [ - "Here is an example showing how `Workflow` can be used as a single-point-of-import for defining new nodes:" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "1cd000bd-9b24-4c39-9cac-70a3291d0660", - "metadata": {}, - "outputs": [], - "source": [ - "from pyiron_contrib.workflow import Workflow\n", - "\n", - "@Workflow.wrap_as.single_value_node(\"is_greater\")\n", - "def greater_than_half(x: int | float | bool = 0) -> bool:\n", - " \"\"\"The functionality doesn't matter here, it's just an example\"\"\"\n", - " return x > 0.5" - ] - }, - { - "cell_type": "markdown", - "id": "8f17751c-f5bf-4b13-8275-0685d8a1629e", - "metadata": {}, - "source": [ - "## Adding nodes to a workflow\n", - "\n", - "Each node can belong to exactly one workflow...but how to we create a workflow and add nodes to it\n", - "\n", - "All five of the approaches below are equivalent ways to add a node to a workflow. Note that when `create` is called from the workflow _class_ it just gives you access to the class being created; when it is called from a workflow _instance_, it wraps this class so that the created node has its parent value automatically set to the workflow instance that's creating it." - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "7964df3c-55af-4c25-afc5-9e07accb606a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "n1 == n1) 0.0 > 0.5 False\n", - "n2 == n2) 0.2 > 0.5 False\n", - "n3 == n3) 0.4 > 0.5 False\n", - "n4 == n4) 0.6 > 0.5 True\n", - "n5 == n5) 0.8 > 0.5 True\n" - ] - } - ], - "source": [ - "n1 = greater_than_half(label=\"n1\")\n", - "\n", - "wf = Workflow(\"my_wf\", n1) # As args at init\n", - "wf.create.SingleValue(n1.node_function, output_labels=\"p1\", label=\"n2\") # Instantiating from the class with a function\n", - "wf.add(greater_than_half(label=\"n3\")) # Instantiating then passing to node adder\n", - "wf.n4 = greater_than_half(label=\"will_get_overwritten_with_n4\") # Set attribute to instance\n", - "greater_than_half(label=\"n5\", parent=wf) # By passing the workflow to the node\n", - "\n", - "for i, (label, node) in enumerate(wf.nodes.items()):\n", - " x = i / len(wf)\n", - " node(x=x)\n", - " print(f\"{label} == {node.label}) {x} > 0.5 {node.single_value}\")" - ] - }, - { - "cell_type": "markdown", - "id": "dd5768a4-1810-4675-9389-bceb053cddfa", - "metadata": {}, - "source": [ - "Workflows have inputs and outputs just like function nodes, but these are dynamically created to map to all _unconnected_ input and output for their underlying graph. They automatically get named by connecting the node label and channel label with a double underscore, but this can be overriden by providing an `inputs_map` and/or an `outputs_map` -- these maps can also let you expose data channels that would otherwise be hidden because they have a connection!" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "809178a5-2e6b-471d-89ef-0797db47c5ad", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['ax', 'b__x'] ['ay', 'a + b + 2']\n" - ] - } - ], - "source": [ - "wf = Workflow(\"simple\")\n", - "\n", - "@Workflow.wrap_as.single_value_node()\n", - "def add_one(x):\n", - " y = x + 1\n", - " return y\n", - "\n", - "@Workflow.wrap_as.single_value_node(\"sum\")\n", - "def add_node(x, y):\n", - " return x + y\n", - "\n", - "wf.a = add_one(0)\n", - "wf.b = add_one(0)\n", - "wf.sum = add_node(wf.a, wf.b) \n", - "wf.inputs_map = {\"a__x\": \"ax\"}\n", - "wf.outputs_map = {\"a__y\": \"ay\", \"sum__sum\": \"a + b + 2\"}\n", - "# Remember, with single value nodes we can pass the whole node instead of an output channel!\n", - "\n", - "print(wf.inputs.labels, wf.outputs.labels)" - ] - }, - { - "cell_type": "markdown", - "id": "848a45a9-dfcc-4b9e-aec5-e879d88325a2", - "metadata": {}, - "source": [ - "When `run()` is called on a workflow, it will call `run()` on each node in its `starting_nodes` list and rely on these to propagate the execution with their run signals. If your data flow is DAG-like, all of this gets handled automatically so you just need to call `run()` on the workflow.\n", - "\n", - "If you do have cyclic data flows, or just want more control, you are still free to set the `starting_nodes` and run signals yourself, just don't forget to set `automate_execution=False` on the workflow." - ] - }, - { - "cell_type": "markdown", - "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. Runs of the workflow then return a dot-accessible dictionary based on the output channels:" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "52c48d19-10a2-4c48-ae81-eceea4129a60", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'ay': 3, 'a + b + 2': 7}" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "out = wf(ax=2, b__x=3)\n", - "out" - ] - }, - { - "cell_type": "markdown", - "id": "e3f4b51b-7c28-47f7-9822-b4755e12bd4d", - "metadata": {}, - "source": [ - "We can see now why we've been trying to givesuccinct string labels to our `Function` node outputs instead of just arbitrary expressions! The expressions are typically not dot-accessible:" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "bb35ba3e-602d-4c9c-b046-32da9401dd1c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(7, 3)" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "out[\"a + b + 2\"], out.ay" - ] - }, - { - "cell_type": "markdown", - "id": "c67ddcd9-cea0-4f3f-96aa-491da0a4c459", - "metadata": {}, - "source": [ - "We can also look at our graph:" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "2b0d2c85-9049-417b-8739-8a8432a1efbe", - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clustersimple\n", - "\n", - "simple: Workflow\n", - "\n", - "clustersimpleInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clustersimpleOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clustersimplea\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "a: AddOne\n", - "\n", - "\n", - "clustersimpleaInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clustersimpleaOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clustersimpleb\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "b: AddOne\n", - "\n", - "\n", - "clustersimplebInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clustersimplebOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clustersimplesum\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "sum: AddNode\n", - "\n", - "\n", - "clustersimplesumInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clustersimplesumOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "\n", - "clustersimpleInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clustersimpleOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clustersimpleInputsx\n", - "\n", - "x\n", - "\n", - "\n", - "\n", - "clustersimpleaInputsx\n", - "\n", - "x\n", - "\n", - "\n", - "\n", - "clustersimpleInputsx->clustersimpleaInputsx\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clustersimplebInputsx\n", - "\n", - "x\n", - "\n", - "\n", - "\n", - "clustersimpleInputsx->clustersimplebInputsx\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clustersimpleOutputsy\n", - "\n", - "y\n", - "\n", - "\n", - "\n", - "clustersimpleOutputssum\n", - "\n", - "sum\n", - "\n", - "\n", - "\n", - "clustersimpleaInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clustersimpleaOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clustersimplebInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clustersimpleaOutputsran->clustersimplebInputsrun\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clustersimpleaOutputsy\n", - "\n", - "y\n", - "\n", - "\n", - "\n", - "clustersimpleaOutputsy->clustersimpleOutputsy\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clustersimplesumInputsx\n", - "\n", - "x\n", - "\n", - "\n", - "\n", - "clustersimpleaOutputsy->clustersimplesumInputsx\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clustersimplebOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clustersimplesumInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clustersimplebOutputsran->clustersimplesumInputsrun\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clustersimplebOutputsy\n", - "\n", - "y\n", - "\n", - "\n", - "\n", - "clustersimplesumInputsy\n", - "\n", - "y\n", - "\n", - "\n", - "\n", - "clustersimplebOutputsy->clustersimplesumInputsy\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clustersimplesumOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clustersimplesumOutputssum\n", - "\n", - "sum\n", - "\n", - "\n", - "\n", - "clustersimplesumOutputssum->clustersimpleOutputssum\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "wf.draw()" - ] - }, - { - "cell_type": "markdown", - "id": "2671dc36-42a4-466b-848d-067ef7bd1d1d", - "metadata": {}, - "source": [ - "# Example with pre-built nodes\n", - "\n", - "Currently we have a handfull of pre-build nodes available for import from the `nodes` package. Let's use these to quickly put together a workflow for looking at some MD data." - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "ae500d5e-e55b-432c-8b5f-d5892193cdf5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The job JUSTAJOBNAME was saved and received the ID: 9558\n" - ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "wf = Workflow(\"with_prebuilt\")\n", - "\n", - "wf.structure = wf.create.atomistics.Bulk(cubic=True, name=\"Al\")\n", - "wf.engine = wf.create.atomistics.Lammps(structure=wf.structure)\n", - "wf.calc = wf.create.atomistics.CalcMd(job=wf.engine)\n", - "wf.plot = wf.create.standard.Scatter(\n", - " x=wf.calc.outputs.steps, \n", - " y=wf.calc.outputs.temperature\n", - ")\n", - "wf.structure > wf.engine > wf.calc > wf.plot\n", - "\n", - "out = wf.run()\n", - "out.plot__fig" - ] - }, - { - "cell_type": "markdown", - "id": "43c09aa8-8229-4636-aaeb-9214b723c2fc", - "metadata": {}, - "source": [ - "In case you want to see more or less of the inner workings of the nodes when visualizing a workflow, you can modify the `depth` parameter, which controls how deeply child nodes are decomposed. E.g. we can force our workflow to only show us it's basic IO by setting `depth=0`:" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "2114d0c3-cdad-43c7-9ffa-50c36d56d18f", - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuilt\n", - "\n", - "with_prebuilt: Workflow\n", - "\n", - "clusterwith_prebuiltInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterwith_prebuiltOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsname\n", - "\n", - "name\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputscrystalstructure\n", - "\n", - "crystalstructure\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsa\n", - "\n", - "a\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsc\n", - "\n", - "c\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputscovera\n", - "\n", - "covera\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsu\n", - "\n", - "u\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsorthorhombic\n", - "\n", - "orthorhombic\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputscubic\n", - "\n", - "cubic\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputsn_print\n", - "\n", - "n_print: int\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputstemperature\n", - "\n", - "temperature\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltInputspressure\n", - "\n", - "pressure\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputscells\n", - "\n", - "cells\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsdisplacements\n", - "\n", - "displacements\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsenergy_pot\n", - "\n", - "energy_pot\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsenergy_tot\n", - "\n", - "energy_tot\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsforce_max\n", - "\n", - "force_max\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsforces\n", - "\n", - "forces\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsindices\n", - "\n", - "indices\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputspositions\n", - "\n", - "positions\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputspressures\n", - "\n", - "pressures\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputstotal_displacements\n", - "\n", - "total_displacements\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsvolume\n", - "\n", - "volume\n", - "\n", - "\n", - "\n", - "clusterwith_prebuiltOutputsfig\n", - "\n", - "fig\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "wf.draw(depth=0)" - ] - }, - { - "cell_type": "markdown", - "id": "d1f3b308-28b2-466b-8cf5-6bfd806c08ca", - "metadata": {}, - "source": [ - "# Macros\n", - "\n", - "Once you have a workflow that you're happy with, you may want to store it as a macro so it can be stored in a human-readable way, reused, and shared. Automated conversion of an existing `Workflow` instance into a `Macro` subclass is still on the TODO list, but defining a new macro is pretty easy: they are just composite nodes that have a function defining their graph setup:" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "c71a8308-f8a1-4041-bea0-1c841e072a6d", - "metadata": {}, - "outputs": [], - "source": [ - "from pyiron_contrib.workflow.macro import Macro" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "2b9bb21a-73cd-444e-84a9-100e202aa422", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "13" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "@Workflow.wrap_as.single_value_node(\"result\")\n", - "def add_one(x):\n", - " return x + 1\n", - "\n", - "def add_three_macro(macro: Macro) -> None:\n", - " \"\"\"\n", - " The graph constructor a Macro expects must take the macro as its only argument\n", - " (i.e. \"self\" from the macro's perspective) and return nothing.\n", - " Inside, it should add nodes to the macro, wire their connections, etc.\n", - " \"\"\"\n", - " macro.add_one = add_one(0)\n", - " macro.add_two = add_one(macro.add_one)\n", - " macro.add_three = add_one(macro.add_two)\n", - " # Just like workflows, for simple DAG macros we don't _need_\n", - " # to set signals and starting nodes -- the macro will build them\n", - " # automatically. But, if you do set both then the macro will use them\n", - " macro.add_one > macro.add_two > macro.add_three\n", - " macro.starting_nodes = [macro.add_one] \n", - " \n", - "macro = Macro(add_three_macro)\n", - "macro(add_one__x=10).add_three__result" - ] - }, - { - "cell_type": "markdown", - "id": "bd5099c4-1c01-4a45-a5bb-e5087595db9f", - "metadata": {}, - "source": [ - "Of course, we can also use a decorator like for other node types. This is shown below, along with an example of how exploit label maps to give our macro IO easier-to-use names (and expose IO that would be skipped by default because it's internally connected):" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "3668f9a9-adca-48a4-84ea-13add965897c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'intermediate': 102, 'plus_three': 103}" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "@Workflow.wrap_as.macro_node()\n", - "def add_three_macro(macro: Macro) -> None:\n", - " \"\"\"\n", - " The graph constructor a Macro expects must take the macro as its only argument\n", - " (i.e. \"self\" from the macro's perspective) and return nothing.\n", - " Inside, it should add nodes to the macro, wire their connections, etc.\n", - " \"\"\"\n", - " macro.add_one = add_one(0)\n", - " macro.add_two = add_one(macro.add_one)\n", - " macro.add_three = add_one(macro.add_two)\n", - " macro.inputs_map = {\"add_one__x\": \"x\"}\n", - " macro.outputs_map = {\"add_three__result\": \"plus_three\", \"add_two__result\": \"intermediate\"}\n", - " \n", - "macro = add_three_macro()\n", - "macro(x=100)\n", - "macro.outputs.to_value_dict()" - ] - }, - { - "cell_type": "markdown", - "id": "22d2fdcf-0206-497d-9344-a71e3472a2c0", - "metadata": {}, - "source": [ - "## Nesting\n", - "\n", - "Composite nodes can be nested to abstract workflows into simpler components -- i.e. macros can be added to workflows, and macros can be used inside of macros.\n", - "\n", - "For our final example, let's define a macro for doing Lammps minimizations, then use this in a workflow to compare energies between different phases." - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "9aaeeec0-5f88-4c94-a6cc-45b56d2f0111", - "metadata": {}, - "outputs": [], - "source": [ - "@Workflow.wrap_as.macro_node()\n", - "def lammps_minimize(macro):\n", - " macro.structure = macro.create.atomistics.Bulk()\n", - " macro.engine = macro.create.atomistics.Lammps(structure=macro.structure)\n", - " macro.calc = macro.create.atomistics.CalcMin(job=macro.engine, pressure=0)\n", - " \n", - " macro.inputs_map = {\n", - " \"structure__name\": \"element\", \n", - " \"structure__crystalstructure\": \"crystalstructure\",\n", - " \"structure__a\": \"lattice_guess\",\n", - " }\n", - " macro.outputs_map = {\n", - " \"calc__energy_pot\": \"energy\",\n", - " \"structure__structure\": \"structure\",\n", - " }\n", - "\n", - "@Workflow.wrap_as.single_value_node()\n", - "def per_atom_energy_difference(structure1, energy1, structure2, energy2):\n", - " de = (energy2[-1]/len(structure2)) - (energy1[-1]/len(structure1))\n", - " return de" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "a832e552-b3cc-411a-a258-ef21574fc439", - "metadata": {}, - "outputs": [], - "source": [ - "wf = Workflow(\"phase_preference\")\n", - "wf.element = wf.create.standard.UserInput()\n", - "wf.min_phase1 = lammps_minimize(element=wf.element)\n", - "wf.min_phase2 = lammps_minimize(element=wf.element)\n", - "wf.compare = per_atom_energy_difference(\n", - " wf.min_phase1.outputs.structure,\n", - " wf.min_phase1.outputs.energy,\n", - " wf.min_phase2.outputs.structure,\n", - " wf.min_phase2.outputs.energy,\n", - ")\n", - "\n", - "wf.inputs_map = {\n", - " \"element__user_input\": \"element\",\n", - " \"min_phase1__crystalstructure\": \"phase1\",\n", - " \"min_phase2__crystalstructure\": \"phase2\",\n", - " \"min_phase1__lattice_guess\": \"lattice_guess1\",\n", - " \"min_phase2__lattice_guess\": \"lattice_guess2\",\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "b764a447-236f-4cb7-952a-7cba4855087d", - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preference\n", - "\n", - "phase_preference: Workflow\n", - "\n", - "clusterphase_preferenceInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterphase_preferenceOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clusterphase_preferenceelement\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "element: UserInput\n", - "\n", - "\n", - "clusterphase_preferenceelementInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterphase_preferenceelementOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "min_phase1: LammpsMinimize\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Inputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "min_phase2: LammpsMinimize\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Inputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "clusterphase_preferencecompare\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "compare: PerAtomEnergyDifference\n", - "\n", - "\n", - "clusterphase_preferencecompareInputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Inputs\n", - "\n", - "\n", - "clusterphase_preferencecompareOutputs\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Outputs\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsuser_input\n", - "\n", - "user_input\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceelementInputsuser_input\n", - "\n", - "user_input\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsuser_input->clusterphase_preferenceelementInputsuser_input\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputscrystalstructure\n", - "\n", - "crystalstructure\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Inputscrystalstructure\n", - "\n", - "crystalstructure\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputscrystalstructure->clusterphase_preferencemin_phase1Inputscrystalstructure\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Inputscrystalstructure\n", - "\n", - "crystalstructure\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputscrystalstructure->clusterphase_preferencemin_phase2Inputscrystalstructure\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsa\n", - "\n", - "a\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Inputsa\n", - "\n", - "a\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsa->clusterphase_preferencemin_phase1Inputsa\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Inputsa\n", - "\n", - "a\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsa->clusterphase_preferencemin_phase2Inputsa\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsc\n", - "\n", - "c\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Inputsc\n", - "\n", - "c\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsc->clusterphase_preferencemin_phase1Inputsc\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Inputsc\n", - "\n", - "c\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsc->clusterphase_preferencemin_phase2Inputsc\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputscovera\n", - "\n", - "covera\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Inputscovera\n", - "\n", - "covera\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputscovera->clusterphase_preferencemin_phase1Inputscovera\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Inputscovera\n", - "\n", - "covera\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputscovera->clusterphase_preferencemin_phase2Inputscovera\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsu\n", - "\n", - "u\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Inputsu\n", - "\n", - "u\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsu->clusterphase_preferencemin_phase1Inputsu\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Inputsu\n", - "\n", - "u\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsu->clusterphase_preferencemin_phase2Inputsu\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsorthorhombic\n", - "\n", - "orthorhombic\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Inputsorthorhombic\n", - "\n", - "orthorhombic\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsorthorhombic->clusterphase_preferencemin_phase1Inputsorthorhombic\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Inputsorthorhombic\n", - "\n", - "orthorhombic\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsorthorhombic->clusterphase_preferencemin_phase2Inputsorthorhombic\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputscubic\n", - "\n", - "cubic\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Inputscubic\n", - "\n", - "cubic\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputscubic->clusterphase_preferencemin_phase1Inputscubic\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Inputscubic\n", - "\n", - "cubic\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputscubic->clusterphase_preferencemin_phase2Inputscubic\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Inputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsn_ionic_steps->clusterphase_preferencemin_phase1Inputsn_ionic_steps\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Inputsn_ionic_steps\n", - "\n", - "n_ionic_steps: int\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsn_ionic_steps->clusterphase_preferencemin_phase2Inputsn_ionic_steps\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsn_print\n", - "\n", - "n_print: int\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Inputsn_print\n", - "\n", - "n_print: int\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsn_print->clusterphase_preferencemin_phase1Inputsn_print\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Inputsn_print\n", - "\n", - "n_print: int\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputsn_print->clusterphase_preferencemin_phase2Inputsn_print\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputspressure\n", - "\n", - "pressure\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Inputspressure\n", - "\n", - "pressure\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputspressure->clusterphase_preferencemin_phase1Inputspressure\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Inputspressure\n", - "\n", - "pressure\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceInputspressure->clusterphase_preferencemin_phase2Inputspressure\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceOutputscells\n", - "\n", - "cells\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceOutputsdisplacements\n", - "\n", - "displacements\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "energy_tot\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceOutputsforce_max\n", - "\n", - "force_max\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceOutputsforces\n", - "\n", - "forces\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceOutputsindices\n", - "\n", - "indices\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceOutputspositions\n", - "\n", - "positions\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceOutputspressures\n", - "\n", - "pressures\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceOutputssteps\n", - "\n", - "steps\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "total_displacements\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceOutputsvolume\n", - "\n", - "volume\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceOutputsde\n", - "\n", - "de\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceelementInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceelementOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceelementOutputsuser_input\n", - "\n", - "user_input\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Inputsname\n", - "\n", - "name\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceelementOutputsuser_input->clusterphase_preferencemin_phase1Inputsname\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Inputsname\n", - "\n", - "name\n", - "\n", - "\n", - "\n", - "clusterphase_preferenceelementOutputsuser_input->clusterphase_preferencemin_phase2Inputsname\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Inputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsstructure\n", - "\n", - "structure\n", - "\n", - "\n", - "\n", - "clusterphase_preferencecompareInputsstructure1\n", - "\n", - "structure1\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsstructure->clusterphase_preferencecompareInputsstructure1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputscells\n", - "\n", - "cells\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputscells->clusterphase_preferenceOutputscells\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsdisplacements\n", - "\n", - "displacements\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsdisplacements->clusterphase_preferenceOutputsdisplacements\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsenergy_pot\n", - "\n", - "energy_pot\n", - "\n", - "\n", - "\n", - "clusterphase_preferencecompareInputsenergy1\n", - "\n", - "energy1\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsenergy_pot->clusterphase_preferencecompareInputsenergy1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsenergy_tot\n", - "\n", - "energy_tot\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsenergy_tot->clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsforce_max\n", - "\n", - "force_max\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsforce_max->clusterphase_preferenceOutputsforce_max\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsforces\n", - "\n", - "forces\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsforces->clusterphase_preferenceOutputsforces\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsindices\n", - "\n", - "indices\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsindices->clusterphase_preferenceOutputsindices\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputspositions\n", - "\n", - "positions\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputspositions->clusterphase_preferenceOutputspositions\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputspressures\n", - "\n", - "pressures\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputspressures->clusterphase_preferenceOutputspressures\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputssteps\n", - "\n", - "steps\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputssteps->clusterphase_preferenceOutputssteps\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputstotal_displacements\n", - "\n", - "total_displacements\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputstotal_displacements->clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsunwrapped_positions->clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsvolume\n", - "\n", - "volume\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase1Outputsvolume->clusterphase_preferenceOutputsvolume\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Inputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsstructure\n", - "\n", - "structure\n", - "\n", - "\n", - "\n", - "clusterphase_preferencecompareInputsstructure2\n", - "\n", - "structure2\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsstructure->clusterphase_preferencecompareInputsstructure2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputscells\n", - "\n", - "cells\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputscells->clusterphase_preferenceOutputscells\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsdisplacements\n", - "\n", - "displacements\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsdisplacements->clusterphase_preferenceOutputsdisplacements\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsenergy_pot\n", - "\n", - "energy_pot\n", - "\n", - "\n", - "\n", - "clusterphase_preferencecompareInputsenergy2\n", - "\n", - "energy2\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsenergy_pot->clusterphase_preferencecompareInputsenergy2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsenergy_tot\n", - "\n", - "energy_tot\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsenergy_tot->clusterphase_preferenceOutputsenergy_tot\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsforce_max\n", - "\n", - "force_max\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsforce_max->clusterphase_preferenceOutputsforce_max\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsforces\n", - "\n", - "forces\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsforces->clusterphase_preferenceOutputsforces\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsindices\n", - "\n", - "indices\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsindices->clusterphase_preferenceOutputsindices\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputspositions\n", - "\n", - "positions\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputspositions->clusterphase_preferenceOutputspositions\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputspressures\n", - "\n", - "pressures\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputspressures->clusterphase_preferenceOutputspressures\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputssteps\n", - "\n", - "steps\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputssteps->clusterphase_preferenceOutputssteps\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputstotal_displacements\n", - "\n", - "total_displacements\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputstotal_displacements->clusterphase_preferenceOutputstotal_displacements\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsunwrapped_positions\n", - "\n", - "unwrapped_positions\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsunwrapped_positions->clusterphase_preferenceOutputsunwrapped_positions\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsvolume\n", - "\n", - "volume\n", - "\n", - "\n", - "\n", - "clusterphase_preferencemin_phase2Outputsvolume->clusterphase_preferenceOutputsvolume\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencecompareInputsrun\n", - "\n", - "run\n", - "\n", - "\n", - "\n", - "clusterphase_preferencecompareOutputsran\n", - "\n", - "ran\n", - "\n", - "\n", - "\n", - "\n", - "clusterphase_preferencecompareOutputsde\n", - "\n", - "de\n", - "\n", - "\n", - "\n", - "clusterphase_preferencecompareOutputsde->clusterphase_preferenceOutputsde\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "wf.draw()" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "b51bef25-86c5-4d57-80c1-ab733e703caf", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The job JUSTAJOBNAME was saved and received the ID: 9558\n", - "The job JUSTAJOBNAME was saved and received the ID: 9558\n", - "Al: E(hcp) - E(fcc) = 1.17 eV/atom\n" - ] - } - ], - "source": [ - "out = wf(element=\"Al\", phase1=\"fcc\", phase2=\"hcp\", lattice_guess1=4, lattice_guess2=4)\n", - "print(f\"{wf.inputs.element.value}: E({wf.inputs.phase2.value}) - E({wf.inputs.phase1.value}) = {out.compare__de:.2f} eV/atom\")" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "091e2386-0081-436c-a736-23d019bd9b91", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The job JUSTAJOBNAME was saved and received the ID: 9558\n", - "The job JUSTAJOBNAME was saved and received the ID: 9558\n", - "Mg: E(hcp) - E(fcc) = -4.54 eV/atom\n" - ] - } - ], - "source": [ - "out = wf(element=\"Mg\", phase1=\"fcc\", phase2=\"hcp\", lattice_guess1=3, lattice_guess2=3)\n", - "print(f\"{wf.inputs.element.value}: E({wf.inputs.phase2.value}) - E({wf.inputs.phase1.value}) = {out.compare__de:.2f} eV/atom\")" - ] - }, - { - "cell_type": "markdown", - "id": "f447531e-3e8c-4c7e-a579-5f9c56b75a5b", - "metadata": {}, - "source": [ - "# Here be dragons\n", - "\n", - "While everything in the workflows sub-module is under development, the following complex features are _even more likely_ to see substantial modifications to their interface and behaviour. Nonetheless, they're fun so let's look at them." - ] - }, - { - "cell_type": "markdown", - "id": "069cc8e8-f8b9-4382-a424-b3b2dd2bf739", - "metadata": {}, - "source": [ - "## Parallelization\n", - "\n", - "You can currently run _some_ nodes (namely, `Function` nodes that don't take `self` as an argument) in a background process by setting an `executor` of the right type.\n", - "Cf. the `Workflow` class tests in the source code for an example.\n", - "\n", - "Right now our treatment of DAGs is quite rudimentary, and the data flow is (unless cyclic) converted into a _linear_ execution pattern. \n", - "This is practical and robust, but highly inefficient when combined with nodes that can run in parallel, i.e. with \"executors\".\n", - "Going forward, we will exploit the same infrastructure of data flow DAGs and run signals to build up more sophisticated execution patterns which support parallelization." - ] - }, - { - "cell_type": "markdown", - "id": "1f29fde8-1645-444e-99dc-3ec465461c7e", - "metadata": {}, - "source": [ - "## Serialization and node libraries\n", - "\n", - "Serialization doesn't exist yet.\n", - "\n", - "What you _can_ do is `register` new lists of nodes (including macros) with the workflow, so feel free to build up your own `.py` files containing nodes you like to use for easy re-use.\n", - "\n", - "Serialization of workflows is still forthcoming, while for node registration flexibility and documentation is forthcoming but the basics are here already." - ] - }, - { - "cell_type": "markdown", - "id": "1f012460-19af-45f7-98aa-a0ad5b8e6faa", - "metadata": {}, - "source": [ - "## Meta-nodes and flow control\n", - "\n", - "A meta-node is a function that produces a node _class_ instedad of a node _instance_.\n", - "Right now, these are used to produce parameterized flow-control nodes, which take an node class as input and return a new macro class that builds some graph using the passed node class, e.g. for- and while-loops.\n", - "\n", - "### For-loops\n", - "\n", - "One meta node is a for-loop builder, which creates a macro with $n$ internal instances of the \"loop body\" node class, and a new IO interface.\n", - "The new input allows you to specify which input channels are being looped over -- such that the macro input for this channel is interpreted as list-like and distributed to all the copies of the nodes separately --, and which is _not_ being looped over -- and thus interpreted as the loop body node would normally interpret the input and passed to all copies equally.\n", - "All of the loop body outputs are then collected as a list of length $n$.\n", - "\n", - "We follow a convention that inputs and outputs being looped over are indicated by their channel labels being ALL CAPS.\n", - "\n", - "In the example below, we loop over the bulk structure node to create structures with different lattice constants:" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "0b373764-b389-4c24-8086-f3d33a4f7fd7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[14.829749999999995,\n", - " 15.407468749999998,\n", - " 15.999999999999998,\n", - " 16.60753125,\n", - " 17.230249999999995]" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "n = 5\n", - "\n", - "bulk_loop = Workflow.create.meta.for_loop(\n", - " Workflow.create.atomistics.Bulk,\n", - " n,\n", - " iterate_on=(\"a\",),\n", - ")()\n", - "\n", - "out = bulk_loop(\n", - " name=\"Al\", # Sent equally to each body node\n", - " A=np.linspace(3.9, 4.1, n).tolist(), # Distributed across body nodes\n", - ")\n", - "\n", - "[struct.cell.volume for struct in out.STRUCTURE] \n", - "# output is a list collected from copies of the body node, as indicated by CAPS label" - ] - }, - { - "cell_type": "markdown", - "id": "4e7ed210-dbc2-4afa-825e-b91168baff25", - "metadata": {}, - "source": [ - "## While-loops\n", - "\n", - "We can also create a while-loop, which takes both a body node and a condition node. The condition node must be a `SingleValue` returning a `bool` type. Instead of creating copies of the body node, the body node gets re-run until the condition node returns `False`.\n", - "\n", - "You _must_ specify the data connection so that the body node passes information to the condition node. You may optionally also loop output of the body node back to input of the body node to change the input at each iteration. Right now this is done with horribly ugly string tuples, but we're still working on it." - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "0dd04b4c-e3e7-4072-ad34-58f2c1e4f596", - "metadata": {}, - "outputs": [], - "source": [ - "@Workflow.wrap_as.single_value_node()\n", - "def add(a, b):\n", - " print(f\"{a} + {b} = {a + b}\")\n", - " return a + b\n", - "\n", - "@Workflow.wrap_as.single_value_node()\n", - "def less_than_ten(value):\n", - " return value < 10\n", - "\n", - "AddWhile = Workflow.create.meta.while_loop(\n", - " loop_body_class=add,\n", - " condition_class=less_than_ten,\n", - " internal_connection_map=[\n", - " (\"Add\", \"a + b\", \"LessThanTen\", \"value\"),\n", - " (\"Add\", \"a + b\", \"Add\", \"a\")\n", - " ],\n", - " inputs_map={\"Add__a\": \"a\", \"Add__b\": \"b\"},\n", - " outputs_map={\"Add__a + b\": \"total\"}\n", - ")\n", - "\n", - "wf = Workflow(\"do_while\")\n", - "wf.add_while = AddWhile()\n", - "\n", - "wf.inputs_map = {\n", - " \"add_while__a\": \"a\",\n", - " \"add_while__b\": \"b\"\n", - "}\n", - "wf.outputs_map = {\n", - " \"add_while__total\": \"total\", # Rename this output\n", - " \"add_while__switch__truth\": None # Disable this output\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "eb810e1e-4d13-4cb1-94cc-6d191b8c568d", - "metadata": {}, - "source": [ - "Note that initializing the `a` and `b` input to numeric values when we call the workflow below does not destroy the connection made between the body node input and output -- so the first run of the body node uses the initial value passed, but then it updates its own input for subsequent calls!" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "2dfb967b-41ac-4463-b606-3e315e617f2a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1 + 2 = 3\n", - "3 + 2 = 5\n", - "5 + 2 = 7\n", - "7 + 2 = 9\n", - "9 + 2 = 11\n", - "Finally {'total': 11}\n" - ] - } - ], - "source": [ - "response = wf(a=1, b=2)\n", - "print(\"Finally\", response)" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "2e87f858-b327-4f6b-9237-c8a557f29aeb", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.499 > 0.2\n", - "0.879 > 0.2\n", - "0.993 > 0.2\n", - "0.606 > 0.2\n", - "0.126 <= 0.2\n", - "Finally 0.126\n" - ] - } - ], - "source": [ - "@Workflow.wrap_as.single_value_node(\"random\")\n", - "def random(length: int | None = None):\n", - " return np.random.random(length)\n", - "\n", - "@Workflow.wrap_as.single_value_node()\n", - "def greater_than(x: float, threshold: float):\n", - " gt = x > threshold\n", - " symbol = \">\" if gt else \"<=\"\n", - " print(f\"{x:.3f} {symbol} {threshold}\")\n", - " return gt\n", - "\n", - "RandomWhile = Workflow.create.meta.while_loop(\n", - " loop_body_class=random,\n", - " condition_class=greater_than,\n", - " internal_connection_map=[(\"Random\", \"random\", \"GreaterThan\", \"x\")],\n", - " outputs_map={\"Random__random\": \"capped_result\"}\n", - ")\n", - "\n", - "# Define workflow\n", - "\n", - "wf = Workflow(\"random_until_small_enough\")\n", - "\n", - "## Wire together the while loop and its condition\n", - "\n", - "wf.random_while = RandomWhile()\n", - "\n", - "## Give convenient labels\n", - "wf.inputs_map = {\"random_while__GreaterThan__threshold\": \"threshold\"}\n", - "wf.outputs_map = {\"random_while__capped_result\": \"capped_result\"}\n", - "\n", - "print(f\"Finally {wf(threshold=0.2).capped_result:.3f}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f40bfd6f-3fbf-4c2b-aeee-534ed4bcc970", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 2cc21563f5e0374ae8892cc125ffa291f54c8ba1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 7 Oct 2023 07:31:12 +0000 Subject: [PATCH 704/756] Bump boto3 from 1.28.60 to 1.28.62 Bumps [boto3](https://github.com/boto/boto3) from 1.28.60 to 1.28.62. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.28.60...1.28.62) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d29cb3df1..bbc599f15 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ ], 'image': ['scikit-image==0.21.0'], 'generic': [ - 'boto3==1.28.60', + 'boto3==1.28.62', 'moto==4.2.5' ], 'workflow': [ From f4cf2fded8deee51cb23df4e4afe61d0c28726d5 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Sat, 7 Oct 2023 07:31:33 +0000 Subject: [PATCH 705/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index d884eef43..12994f8c1 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -16,7 +16,7 @@ dependencies: - scikit-image =0.21.0 - toposort - randspg =0.0.1 -- boto3 =1.28.60 +- boto3 =1.28.62 - moto =4.2.5 - pycp2k =0.2.2 - python-graphviz From fed33b9de86d976d6da7ca6f5f2a1a4df80dc606 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Sat, 7 Oct 2023 07:32:00 +0000 Subject: [PATCH 706/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index a07145d46..790a1037f 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -16,7 +16,7 @@ dependencies: - scikit-image =0.21.0 - toposort - randspg =0.0.1 -- boto3 =1.28.60 +- boto3 =1.28.62 - moto =4.2.5 - pycp2k =0.2.2 - python-graphviz diff --git a/docs/environment.yml b/docs/environment.yml index a7006a021..873bed2f4 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -18,7 +18,7 @@ dependencies: - scikit-image =0.21.0 - toposort - randspg =0.0.1 -- boto3 =1.28.60 +- boto3 =1.28.62 - moto =4.2.5 - pycp2k =0.2.2 - python-graphviz From 6802dc6f10401f57d92652680eb9dc2ca74700ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 11:34:56 +0000 Subject: [PATCH 707/756] Bump pyiron-base from 0.6.7 to 0.6.8 Bumps [pyiron-base](https://github.com/pyiron/pyiron_base) from 0.6.7 to 0.6.8. - [Release notes](https://github.com/pyiron/pyiron_base/releases) - [Changelog](https://github.com/pyiron/pyiron_base/blob/main/CHANGELOG.md) - [Commits](https://github.com/pyiron/pyiron_base/compare/pyiron_base-0.6.7...pyiron_base-0.6.8) --- updated-dependencies: - dependency-name: pyiron-base dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b28905dab..b74db4ed3 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ install_requires=[ 'matplotlib==3.8.0', 'numpy==1.26.0', - 'pyiron_base==0.6.7', + 'pyiron_base==0.6.8', 'scipy==1.11.3', 'seaborn==0.13.0', 'pyparsing==3.1.1', From a2a86b3010848acca35357e45264b69911b70cd9 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 16 Oct 2023 11:35:22 +0000 Subject: [PATCH 708/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index a73980a01..3fde8fc8f 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -7,7 +7,7 @@ dependencies: - codacy-coverage - matplotlib =3.8.0 - numpy =1.26.0 -- pyiron_base =0.6.7 +- pyiron_base =0.6.8 - pyiron_atomistics =0.3.4 - pyparsing =3.1.1 - scipy =1.11.3 From d2e7fb2919370d0ccef55f2210dcf57511c23159 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 16 Oct 2023 11:35:47 +0000 Subject: [PATCH 709/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 22fb7761f..5c8a88f4e 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -7,7 +7,7 @@ dependencies: - codacy-coverage - matplotlib =3.8.0 - numpy =1.26.0 -- pyiron_base =0.6.7 +- pyiron_base =0.6.8 - pyiron_atomistics =0.3.4 - pyparsing =3.1.1 - scipy =1.11.3 diff --git a/docs/environment.yml b/docs/environment.yml index 5d73ccee3..d2159214b 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -9,7 +9,7 @@ dependencies: - codacy-coverage - matplotlib =3.8.0 - numpy =1.26.0 -- pyiron_base =0.6.7 +- pyiron_base =0.6.8 - pyiron_atomistics =0.3.4 - pyparsing =3.1.1 - scipy =1.11.3 From 0b3d59155a0a449b14e3874679578e22ea2aa8e6 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Tue, 24 Oct 2023 13:32:38 +0200 Subject: [PATCH 710/756] Update environment.yml --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 3fde8fc8f..d28123a16 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -8,7 +8,7 @@ dependencies: - matplotlib =3.8.0 - numpy =1.26.0 - pyiron_base =0.6.8 -- pyiron_atomistics =0.3.4 +- pyiron_atomistics =0.3.5 - pyparsing =3.1.1 - scipy =1.11.3 - seaborn =0.13.0 From 74e6b4111563adbcd0e9152e2fa12cad03a74fbe Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Tue, 24 Oct 2023 13:32:54 +0200 Subject: [PATCH 711/756] Update environment.yml --- .binder/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 5c8a88f4e..23fb92b87 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -8,7 +8,7 @@ dependencies: - matplotlib =3.8.0 - numpy =1.26.0 - pyiron_base =0.6.8 -- pyiron_atomistics =0.3.4 +- pyiron_atomistics =0.3.5 - pyparsing =3.1.1 - scipy =1.11.3 - seaborn =0.13.0 From aaf0ebff8991a9ae75fca5e6f0256baa1012075b Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 24 Oct 2023 11:33:03 +0000 Subject: [PATCH 712/756] [dependabot skip] Update env file --- docs/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/environment.yml b/docs/environment.yml index d2159214b..74fbeed37 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -10,7 +10,7 @@ dependencies: - matplotlib =3.8.0 - numpy =1.26.0 - pyiron_base =0.6.8 -- pyiron_atomistics =0.3.4 +- pyiron_atomistics =0.3.5 - pyparsing =3.1.1 - scipy =1.11.3 - seaborn =0.13.0 From bf9ffd071949e156b968abaa16b77d1283772e62 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Tue, 24 Oct 2023 13:33:06 +0200 Subject: [PATCH 713/756] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b74db4ed3..7c573f52c 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ extras_require={ 'atomistic': [ 'ase==3.22.1', - 'pyiron_atomistics==0.3.4', + 'pyiron_atomistics==0.3.5', 'pycp2k==0.2.2', ], 'fenics': [ From 0fc4ef88f9fe6aceafa9c2c1e4c1f434a318e487 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Tue, 24 Oct 2023 13:49:13 +0200 Subject: [PATCH 714/756] Update environment-notebooks.yml --- .ci_support/environment-notebooks.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.ci_support/environment-notebooks.yml b/.ci_support/environment-notebooks.yml index add0d8644..7ec09ee16 100644 --- a/.ci_support/environment-notebooks.yml +++ b/.ci_support/environment-notebooks.yml @@ -4,3 +4,4 @@ dependencies: - python >= 3.10 - lammps - nglview >=3.0.8 + - pyiron-data >=0.0.25 From 59f2857bd34c2b9a3abf4b32d405294c4993e5d6 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 24 Oct 2023 11:49:37 +0000 Subject: [PATCH 715/756] [dependabot skip] Update env file --- .binder/environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.binder/environment.yml b/.binder/environment.yml index 23fb92b87..52e887b25 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -23,3 +23,4 @@ dependencies: - python >= 3.10 - lammps - nglview >=3.0.8 +- pyiron-data >=0.0.25 From 04343fb5aca6fae816142c598f81d83a65d722fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 12:56:39 +0000 Subject: [PATCH 716/756] Bump moto from 4.2.5 to 4.2.6 Bumps [moto](https://github.com/getmoto/moto) from 4.2.5 to 4.2.6. - [Release notes](https://github.com/getmoto/moto/releases) - [Changelog](https://github.com/getmoto/moto/blob/master/CHANGELOG.md) - [Commits](https://github.com/getmoto/moto/compare/4.2.5...4.2.6) --- updated-dependencies: - dependency-name: moto dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7c573f52c..fa9f735b1 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ 'image': ['scikit-image==0.21.0'], 'generic': [ 'boto3==1.28.62', - 'moto==4.2.5' + 'moto==4.2.6' ], 'tinybase': [ 'distributed==2023.9.3', From fa04c1b1d825ad18d44b62e573273df59b1ef368 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 24 Oct 2023 12:57:01 +0000 Subject: [PATCH 717/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index d28123a16..e219b25d9 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -15,7 +15,7 @@ dependencies: - scikit-image =0.21.0 - randspg =0.0.1 - boto3 =1.28.62 -- moto =4.2.5 +- moto =4.2.6 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 - pympipool =0.7.1 From 5b81f22b39b4f981c37c1c293fcdbf906d2f88cd Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 24 Oct 2023 12:57:27 +0000 Subject: [PATCH 718/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 52e887b25..2bd5d2218 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -15,7 +15,7 @@ dependencies: - scikit-image =0.21.0 - randspg =0.0.1 - boto3 =1.28.62 -- moto =4.2.5 +- moto =4.2.6 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 - pympipool =0.7.1 diff --git a/docs/environment.yml b/docs/environment.yml index 74fbeed37..6857d0a67 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -17,7 +17,7 @@ dependencies: - scikit-image =0.21.0 - randspg =0.0.1 - boto3 =1.28.62 -- moto =4.2.5 +- moto =4.2.6 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 - pympipool =0.7.1 From 671c6b1d021913494a4c7057c9df40e5457258ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:29:46 +0000 Subject: [PATCH 719/756] Bump distributed from 2023.9.3 to 2023.10.0 Bumps [distributed](https://github.com/dask/distributed) from 2023.9.3 to 2023.10.0. - [Changelog](https://github.com/dask/distributed/blob/main/docs/release-procedure.md) - [Commits](https://github.com/dask/distributed/compare/2023.9.3...2023.10.0) --- updated-dependencies: - dependency-name: distributed dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fa9f735b1..2d3c9918c 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ 'moto==4.2.6' ], 'tinybase': [ - 'distributed==2023.9.3', + 'distributed==2023.10.0', 'pympipool==0.7.1' ] }, From 4528428b9de485796f094c137a12fd067e719364 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 24 Oct 2023 13:30:07 +0000 Subject: [PATCH 720/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index e219b25d9..4fad2a889 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -19,4 +19,4 @@ dependencies: - pycp2k =0.2.2 - aws-sam-translator =1.77.0 - pympipool =0.7.1 -- distributed =2023.9.3 +- distributed =2023.10.0 From 5ecd12f27f66c3ab0558d72fbfe8e61161e80a3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:30:20 +0000 Subject: [PATCH 721/756] Bump scikit-image from 0.21.0 to 0.22.0 Bumps [scikit-image](https://github.com/scikit-image/scikit-image) from 0.21.0 to 0.22.0. - [Release notes](https://github.com/scikit-image/scikit-image/releases) - [Changelog](https://github.com/scikit-image/scikit-image/blob/main/RELEASE.txt) - [Commits](https://github.com/scikit-image/scikit-image/compare/v0.21.0...v0.22.0) --- updated-dependencies: - dependency-name: scikit-image dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fa9f735b1..1c79fb31a 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ 'fenics==2019.1.0', 'mshr==2019.1.0', ], - 'image': ['scikit-image==0.21.0'], + 'image': ['scikit-image==0.22.0'], 'generic': [ 'boto3==1.28.62', 'moto==4.2.6' From 435f478eb08d28a3c448d0dd714c484dbaceb0ca Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 24 Oct 2023 13:30:31 +0000 Subject: [PATCH 722/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 2bd5d2218..ea388fc38 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -19,7 +19,7 @@ dependencies: - pycp2k =0.2.2 - aws-sam-translator =1.77.0 - pympipool =0.7.1 -- distributed =2023.9.3 +- distributed =2023.10.0 - python >= 3.10 - lammps - nglview >=3.0.8 diff --git a/docs/environment.yml b/docs/environment.yml index 6857d0a67..7b5bba592 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -21,4 +21,4 @@ dependencies: - pycp2k =0.2.2 - aws-sam-translator =1.77.0 - pympipool =0.7.1 -- distributed =2023.9.3 +- distributed =2023.10.0 From ce3661f39ab32ea477c94463ce1606c5d937d4db Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 24 Oct 2023 13:30:41 +0000 Subject: [PATCH 723/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index e219b25d9..962836b5d 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -12,7 +12,7 @@ dependencies: - pyparsing =3.1.1 - scipy =1.11.3 - seaborn =0.13.0 -- scikit-image =0.21.0 +- scikit-image =0.22.0 - randspg =0.0.1 - boto3 =1.28.62 - moto =4.2.6 From 59fe0dd2d5cfb5d5df3b0d2520aaeab17749a46a Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 24 Oct 2023 13:33:49 +0000 Subject: [PATCH 724/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 2bd5d2218..7ab4b7c75 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -12,7 +12,7 @@ dependencies: - pyparsing =3.1.1 - scipy =1.11.3 - seaborn =0.13.0 -- scikit-image =0.21.0 +- scikit-image =0.22.0 - randspg =0.0.1 - boto3 =1.28.62 - moto =4.2.6 diff --git a/docs/environment.yml b/docs/environment.yml index 6857d0a67..98fbd0f10 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -14,7 +14,7 @@ dependencies: - pyparsing =3.1.1 - scipy =1.11.3 - seaborn =0.13.0 -- scikit-image =0.21.0 +- scikit-image =0.22.0 - randspg =0.0.1 - boto3 =1.28.62 - moto =4.2.6 From 5c728c83994bfb655a0e303004adaec1e3b7bf56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 14:02:38 +0000 Subject: [PATCH 725/756] Bump boto3 from 1.28.62 to 1.28.69 Bumps [boto3](https://github.com/boto/boto3) from 1.28.62 to 1.28.69. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.28.62...1.28.69) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index eeabd2911..a617d7965 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ ], 'image': ['scikit-image==0.22.0'], 'generic': [ - 'boto3==1.28.62', + 'boto3==1.28.69', 'moto==4.2.6' ], 'tinybase': [ From e0ff98126e8c3c0301806901e5717be3bb8817d0 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 24 Oct 2023 14:06:09 +0000 Subject: [PATCH 726/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index a25b5279f..07c4681b5 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -14,7 +14,7 @@ dependencies: - seaborn =0.13.0 - scikit-image =0.22.0 - randspg =0.0.1 -- boto3 =1.28.62 +- boto3 =1.28.69 - moto =4.2.6 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 From 4806260fcd8d82aea9bd679ff86e67c8e61d39a1 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 24 Oct 2023 14:06:45 +0000 Subject: [PATCH 727/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index ca6df5a2a..7a6810008 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -14,7 +14,7 @@ dependencies: - seaborn =0.13.0 - scikit-image =0.22.0 - randspg =0.0.1 -- boto3 =1.28.62 +- boto3 =1.28.69 - moto =4.2.6 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 diff --git a/docs/environment.yml b/docs/environment.yml index 94a18c438..e98f3d1f2 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -16,7 +16,7 @@ dependencies: - seaborn =0.13.0 - scikit-image =0.22.0 - randspg =0.0.1 -- boto3 =1.28.62 +- boto3 =1.28.69 - moto =4.2.6 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 From a40fce298b2da5355e63ac1738d28d8c97e762e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:47:19 +0000 Subject: [PATCH 728/756] Bump moto from 4.2.6 to 4.2.7 Bumps [moto](https://github.com/getmoto/moto) from 4.2.6 to 4.2.7. - [Release notes](https://github.com/getmoto/moto/releases) - [Changelog](https://github.com/getmoto/moto/blob/master/CHANGELOG.md) - [Commits](https://github.com/getmoto/moto/compare/4.2.6...4.2.7) --- updated-dependencies: - dependency-name: moto dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a617d7965..6eebc961e 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ 'image': ['scikit-image==0.22.0'], 'generic': [ 'boto3==1.28.69', - 'moto==4.2.6' + 'moto==4.2.7' ], 'tinybase': [ 'distributed==2023.10.0', From 689f8aa089487071cfaa5346812af716b3dfedba Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 30 Oct 2023 11:47:42 +0000 Subject: [PATCH 729/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 07c4681b5..0bf9fe158 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -15,7 +15,7 @@ dependencies: - scikit-image =0.22.0 - randspg =0.0.1 - boto3 =1.28.69 -- moto =4.2.6 +- moto =4.2.7 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 - pympipool =0.7.1 From c1cfd2110ec5ecddd5f6fd46cc4688555579e8b9 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 30 Oct 2023 11:48:10 +0000 Subject: [PATCH 730/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 7a6810008..97b65ce43 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -15,7 +15,7 @@ dependencies: - scikit-image =0.22.0 - randspg =0.0.1 - boto3 =1.28.69 -- moto =4.2.6 +- moto =4.2.7 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 - pympipool =0.7.1 diff --git a/docs/environment.yml b/docs/environment.yml index e98f3d1f2..066cf21eb 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -17,7 +17,7 @@ dependencies: - scikit-image =0.22.0 - randspg =0.0.1 - boto3 =1.28.69 -- moto =4.2.6 +- moto =4.2.7 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 - pympipool =0.7.1 From 00b9a138efe0b5810714a4061c877287c19bdd94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:33:47 +0000 Subject: [PATCH 731/756] Bump distributed from 2023.10.0 to 2023.10.1 Bumps [distributed](https://github.com/dask/distributed) from 2023.10.0 to 2023.10.1. - [Changelog](https://github.com/dask/distributed/blob/main/docs/release-procedure.md) - [Commits](https://github.com/dask/distributed/compare/2023.10.0...2023.10.1) --- updated-dependencies: - dependency-name: distributed dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6eebc961e..8c3dcdd74 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ 'moto==4.2.7' ], 'tinybase': [ - 'distributed==2023.10.0', + 'distributed==2023.10.1', 'pympipool==0.7.1' ] }, From 780954f9bae25d780316b0b57b6fb653edb456bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:35:57 +0000 Subject: [PATCH 732/756] Bump boto3 from 1.28.69 to 1.28.73 Bumps [boto3](https://github.com/boto/boto3) from 1.28.69 to 1.28.73. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.28.69...1.28.73) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6eebc961e..880d310e7 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ ], 'image': ['scikit-image==0.22.0'], 'generic': [ - 'boto3==1.28.69', + 'boto3==1.28.73', 'moto==4.2.7' ], 'tinybase': [ From ec53a05ffd7528090028843d7a45463e636a3c2d Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 30 Oct 2023 13:37:11 +0000 Subject: [PATCH 733/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 0bf9fe158..b1204d450 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -19,4 +19,4 @@ dependencies: - pycp2k =0.2.2 - aws-sam-translator =1.77.0 - pympipool =0.7.1 -- distributed =2023.10.0 +- distributed =2023.10.1 From 4f4921ff500182d12991fa249101224a2e5edebe Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 30 Oct 2023 13:37:37 +0000 Subject: [PATCH 734/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 0bf9fe158..5a1835ff4 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -14,7 +14,7 @@ dependencies: - seaborn =0.13.0 - scikit-image =0.22.0 - randspg =0.0.1 -- boto3 =1.28.69 +- boto3 =1.28.73 - moto =4.2.7 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 From 395c011fdd5f4a3f70d8335d384b786674e430b7 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 30 Oct 2023 13:38:12 +0000 Subject: [PATCH 735/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 97b65ce43..81b07ec8d 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -19,7 +19,7 @@ dependencies: - pycp2k =0.2.2 - aws-sam-translator =1.77.0 - pympipool =0.7.1 -- distributed =2023.10.0 +- distributed =2023.10.1 - python >= 3.10 - lammps - nglview >=3.0.8 diff --git a/docs/environment.yml b/docs/environment.yml index 066cf21eb..ba9d2d232 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -21,4 +21,4 @@ dependencies: - pycp2k =0.2.2 - aws-sam-translator =1.77.0 - pympipool =0.7.1 -- distributed =2023.10.0 +- distributed =2023.10.1 From 0d31ca211b380417e6ec2a5dff602a70d0dc537f Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 30 Oct 2023 13:38:14 +0000 Subject: [PATCH 736/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 97b65ce43..66c983383 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -14,7 +14,7 @@ dependencies: - seaborn =0.13.0 - scikit-image =0.22.0 - randspg =0.0.1 -- boto3 =1.28.69 +- boto3 =1.28.73 - moto =4.2.7 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 diff --git a/docs/environment.yml b/docs/environment.yml index 066cf21eb..04ac69b80 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -16,7 +16,7 @@ dependencies: - seaborn =0.13.0 - scikit-image =0.22.0 - randspg =0.0.1 -- boto3 =1.28.69 +- boto3 =1.28.73 - moto =4.2.7 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 From e4cbdc5c8479c6da676a9aa97f026d69a473c96e Mon Sep 17 00:00:00 2001 From: Niklas Leimeroth Date: Fri, 3 Nov 2023 15:02:59 +0100 Subject: [PATCH 737/756] add randomization of parameters --- .../atomistics/atomicrex/function_factory.py | 16 ++++++++++++++++ .../atomistics/atomicrex/potential_factory.py | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/pyiron_contrib/atomistics/atomicrex/function_factory.py b/pyiron_contrib/atomistics/atomicrex/function_factory.py index 852b6b92c..eb848fe80 100644 --- a/pyiron_contrib/atomistics/atomicrex/function_factory.py +++ b/pyiron_contrib/atomistics/atomicrex/function_factory.py @@ -345,6 +345,11 @@ def lock_parameters(self, filter_func=None): for param in self.parameters.values(): param.lock(filter_func=filter_func) + def randomize_parameters(self, rng, filter_func=None): + for param in self.parameters.values(): + param.randomize(rng=rng, filter_func=filter_func) + + def set_max_values(self, constant=None, factor=None, filter_func=None): """ Convenience function so set max values for all parameters at once. @@ -412,6 +417,10 @@ def lock_parameters(self, filter_func=None): for f in self.functions.values(): f.lock_parameters(filter_func=filter_func) + def randomize_parameters(self, rng, filter_func=None): + for f in self.functions.values(): + f.radomize_parameters(rng=rng, filter_func=filter_func) + def set_max_values(self, constant=None, factor=None, filter_func=None): """ Convenience function so set max values for all parameters at once. @@ -1459,6 +1468,13 @@ def lock(self, filter_func=None): return self.enabled = False + def randomize(self, rng, filter_func=None,): + if filter_func is not None: + if not filter_func(self): + return + if self.min_val is None or self.max_val is None: + raise ValueError(f"Min and/or max val not set for {self.param}, can't randomize") + self.start_val = rng.random(self.min_val, self.max_val) class FunctionParameterList(DataContainer): def __init__(self): diff --git a/pyiron_contrib/atomistics/atomicrex/potential_factory.py b/pyiron_contrib/atomistics/atomicrex/potential_factory.py index f26c14bc4..d404501b4 100644 --- a/pyiron_contrib/atomistics/atomicrex/potential_factory.py +++ b/pyiron_contrib/atomistics/atomicrex/potential_factory.py @@ -114,6 +114,18 @@ def _potential_as_pd_df(self, job): def _plot_final_potential(self): raise NotImplementedError("Should be implemented in the subclass") + def randomize_parameters(self, rng, filter_func=None): + """ + Randomize values of potential parameters + to sample global parameter space in a very simple manner. + Only applies to cases where filer_func returns True if set. + Requires a numpy.random.Generator (np.random.default_rng(seed)) to generate values. + """ + self._randomize_parameters(self, rng=rng, filter_func=filter_func) + + def _randomize_parameters(self, rng, filter_func=None): + raise NotImplementedError("Should be implemented in the subclass") + class BOPAbstract(AbstractPotential): def __init__(self, init=None, elements=None, export_file=None, identifier=None): @@ -560,6 +572,11 @@ def copy_final_to_initial_params(self, filter_func=None): for functions in self._function_tuple: for f in functions.values(): f.copy_final_to_initial_params(filter_func=filter_func) + + def _randomize_parameters(self, rng, filter_func=None): + for functions in self._function_tuple: + for f in functions.values(): + f.randomize_parameters(rng=rng, filter_func=filter_func) def lock_parameters(self, filter_func=None): for functions in self._function_tuple: From 55b2b36747ea6af59860d0781cd999956d0b474f Mon Sep 17 00:00:00 2001 From: Niklas Leimeroth Date: Fri, 3 Nov 2023 15:14:32 +0100 Subject: [PATCH 738/756] remove self in call to func --- pyiron_contrib/atomistics/atomicrex/potential_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/atomistics/atomicrex/potential_factory.py b/pyiron_contrib/atomistics/atomicrex/potential_factory.py index d404501b4..1be5075e5 100644 --- a/pyiron_contrib/atomistics/atomicrex/potential_factory.py +++ b/pyiron_contrib/atomistics/atomicrex/potential_factory.py @@ -121,7 +121,7 @@ def randomize_parameters(self, rng, filter_func=None): Only applies to cases where filer_func returns True if set. Requires a numpy.random.Generator (np.random.default_rng(seed)) to generate values. """ - self._randomize_parameters(self, rng=rng, filter_func=filter_func) + self._randomize_parameters(rng=rng, filter_func=filter_func) def _randomize_parameters(self, rng, filter_func=None): raise NotImplementedError("Should be implemented in the subclass") From 489b6efc7a16b5954dcf1619aec1b9ecc9a8235b Mon Sep 17 00:00:00 2001 From: Niklas Leimeroth Date: Fri, 3 Nov 2023 15:31:27 +0100 Subject: [PATCH 739/756] Inheritance --- .../atomistics/atomicrex/potential_factory.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pyiron_contrib/atomistics/atomicrex/potential_factory.py b/pyiron_contrib/atomistics/atomicrex/potential_factory.py index 1be5075e5..add37c02b 100644 --- a/pyiron_contrib/atomistics/atomicrex/potential_factory.py +++ b/pyiron_contrib/atomistics/atomicrex/potential_factory.py @@ -120,12 +120,11 @@ def randomize_parameters(self, rng, filter_func=None): to sample global parameter space in a very simple manner. Only applies to cases where filer_func returns True if set. Requires a numpy.random.Generator (np.random.default_rng(seed)) to generate values. - """ - self._randomize_parameters(rng=rng, filter_func=filter_func) - - def _randomize_parameters(self, rng, filter_func=None): - raise NotImplementedError("Should be implemented in the subclass") - + """ + try: + self._randomize_parameters(rng=rng, filter_func=filter_func) + except NotImplemented: + raise NotImplementedError('Subclass needs to implement _randomize_parameters') class BOPAbstract(AbstractPotential): def __init__(self, init=None, elements=None, export_file=None, identifier=None): From 88d3196655dc4c4286d25135aa4d93c5607df1af Mon Sep 17 00:00:00 2001 From: Niklas Leimeroth Date: Fri, 3 Nov 2023 15:49:17 +0100 Subject: [PATCH 740/756] just except --- pyiron_contrib/atomistics/atomicrex/potential_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/atomistics/atomicrex/potential_factory.py b/pyiron_contrib/atomistics/atomicrex/potential_factory.py index add37c02b..c27a2c05c 100644 --- a/pyiron_contrib/atomistics/atomicrex/potential_factory.py +++ b/pyiron_contrib/atomistics/atomicrex/potential_factory.py @@ -123,7 +123,7 @@ def randomize_parameters(self, rng, filter_func=None): """ try: self._randomize_parameters(rng=rng, filter_func=filter_func) - except NotImplemented: + except: raise NotImplementedError('Subclass needs to implement _randomize_parameters') class BOPAbstract(AbstractPotential): From b42c80d017eace60fe660d6f17d5ec6a2a2b318a Mon Sep 17 00:00:00 2001 From: Niklas Leimeroth Date: Fri, 3 Nov 2023 15:49:54 +0100 Subject: [PATCH 741/756] spelling --- pyiron_contrib/atomistics/atomicrex/function_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/atomistics/atomicrex/function_factory.py b/pyiron_contrib/atomistics/atomicrex/function_factory.py index eb848fe80..b331a3786 100644 --- a/pyiron_contrib/atomistics/atomicrex/function_factory.py +++ b/pyiron_contrib/atomistics/atomicrex/function_factory.py @@ -419,7 +419,7 @@ def lock_parameters(self, filter_func=None): def randomize_parameters(self, rng, filter_func=None): for f in self.functions.values(): - f.radomize_parameters(rng=rng, filter_func=filter_func) + f.randomize_parameters(rng=rng, filter_func=filter_func) def set_max_values(self, constant=None, factor=None, filter_func=None): """ From 9b35b3d0ecb64e336e1369b688b9015de64886ec Mon Sep 17 00:00:00 2001 From: Niklas Leimeroth Date: Fri, 3 Nov 2023 15:56:02 +0100 Subject: [PATCH 742/756] more inheritance --- .../atomistics/atomicrex/potential_factory.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/pyiron_contrib/atomistics/atomicrex/potential_factory.py b/pyiron_contrib/atomistics/atomicrex/potential_factory.py index c27a2c05c..6d555eafc 100644 --- a/pyiron_contrib/atomistics/atomicrex/potential_factory.py +++ b/pyiron_contrib/atomistics/atomicrex/potential_factory.py @@ -113,18 +113,7 @@ def _potential_as_pd_df(self, job): def _plot_final_potential(self): raise NotImplementedError("Should be implemented in the subclass") - - def randomize_parameters(self, rng, filter_func=None): - """ - Randomize values of potential parameters - to sample global parameter space in a very simple manner. - Only applies to cases where filer_func returns True if set. - Requires a numpy.random.Generator (np.random.default_rng(seed)) to generate values. - """ - try: - self._randomize_parameters(rng=rng, filter_func=filter_func) - except: - raise NotImplementedError('Subclass needs to implement _randomize_parameters') + class BOPAbstract(AbstractPotential): def __init__(self, init=None, elements=None, export_file=None, identifier=None): @@ -572,7 +561,7 @@ def copy_final_to_initial_params(self, filter_func=None): for f in functions.values(): f.copy_final_to_initial_params(filter_func=filter_func) - def _randomize_parameters(self, rng, filter_func=None): + def randomize_parameters(self, rng, filter_func=None): for functions in self._function_tuple: for f in functions.values(): f.randomize_parameters(rng=rng, filter_func=filter_func) From 4733695d7ca6c30a3bbd94012a62fd0afc7cec2e Mon Sep 17 00:00:00 2001 From: Niklas Leimeroth Date: Fri, 3 Nov 2023 16:01:14 +0100 Subject: [PATCH 743/756] check if enabled --- pyiron_contrib/atomistics/atomicrex/function_factory.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/atomistics/atomicrex/function_factory.py b/pyiron_contrib/atomistics/atomicrex/function_factory.py index b331a3786..9f0e3672d 100644 --- a/pyiron_contrib/atomistics/atomicrex/function_factory.py +++ b/pyiron_contrib/atomistics/atomicrex/function_factory.py @@ -1472,9 +1472,12 @@ def randomize(self, rng, filter_func=None,): if filter_func is not None: if not filter_func(self): return - if self.min_val is None or self.max_val is None: - raise ValueError(f"Min and/or max val not set for {self.param}, can't randomize") - self.start_val = rng.random(self.min_val, self.max_val) + + if self.enabled: + if self.min_val is None or self.max_val is None: + raise ValueError(f"Min and/or max val not set for {self.param}, can't randomize") + + self.start_val = rng.random(self.min_val, self.max_val) class FunctionParameterList(DataContainer): def __init__(self): From b8480845eff07a5bfbc7a4254cbe14d8dc9eb09b Mon Sep 17 00:00:00 2001 From: Niklas Leimeroth Date: Fri, 3 Nov 2023 16:25:56 +0100 Subject: [PATCH 744/756] uniform function --- pyiron_contrib/atomistics/atomicrex/function_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_contrib/atomistics/atomicrex/function_factory.py b/pyiron_contrib/atomistics/atomicrex/function_factory.py index 9f0e3672d..0d868b08b 100644 --- a/pyiron_contrib/atomistics/atomicrex/function_factory.py +++ b/pyiron_contrib/atomistics/atomicrex/function_factory.py @@ -1477,7 +1477,7 @@ def randomize(self, rng, filter_func=None,): if self.min_val is None or self.max_val is None: raise ValueError(f"Min and/or max val not set for {self.param}, can't randomize") - self.start_val = rng.random(self.min_val, self.max_val) + self.start_val = rng.uniform(self.min_val, self.max_val) class FunctionParameterList(DataContainer): def __init__(self): From 1b291fcf5607d5d0a6451eb6d51bf43db5429bcc Mon Sep 17 00:00:00 2001 From: Leimeroth Date: Mon, 6 Nov 2023 08:43:13 +0100 Subject: [PATCH 745/756] whitespace --- pyiron_contrib/atomistics/atomicrex/potential_factory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyiron_contrib/atomistics/atomicrex/potential_factory.py b/pyiron_contrib/atomistics/atomicrex/potential_factory.py index 6d555eafc..d025b2319 100644 --- a/pyiron_contrib/atomistics/atomicrex/potential_factory.py +++ b/pyiron_contrib/atomistics/atomicrex/potential_factory.py @@ -113,7 +113,7 @@ def _potential_as_pd_df(self, job): def _plot_final_potential(self): raise NotImplementedError("Should be implemented in the subclass") - + class BOPAbstract(AbstractPotential): def __init__(self, init=None, elements=None, export_file=None, identifier=None): @@ -560,7 +560,7 @@ def copy_final_to_initial_params(self, filter_func=None): for functions in self._function_tuple: for f in functions.values(): f.copy_final_to_initial_params(filter_func=filter_func) - + def randomize_parameters(self, rng, filter_func=None): for functions in self._function_tuple: for f in functions.values(): From 81a4c8786a418a89f978c09ee78bf3a4b6aa02d2 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 6 Nov 2023 07:44:09 +0000 Subject: [PATCH 746/756] Format black --- .../atomistics/atomicrex/function_factory.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pyiron_contrib/atomistics/atomicrex/function_factory.py b/pyiron_contrib/atomistics/atomicrex/function_factory.py index 0d868b08b..9a617ba61 100644 --- a/pyiron_contrib/atomistics/atomicrex/function_factory.py +++ b/pyiron_contrib/atomistics/atomicrex/function_factory.py @@ -349,7 +349,6 @@ def randomize_parameters(self, rng, filter_func=None): for param in self.parameters.values(): param.randomize(rng=rng, filter_func=filter_func) - def set_max_values(self, constant=None, factor=None, filter_func=None): """ Convenience function so set max values for all parameters at once. @@ -1468,17 +1467,24 @@ def lock(self, filter_func=None): return self.enabled = False - def randomize(self, rng, filter_func=None,): + def randomize( + self, + rng, + filter_func=None, + ): if filter_func is not None: if not filter_func(self): return if self.enabled: if self.min_val is None or self.max_val is None: - raise ValueError(f"Min and/or max val not set for {self.param}, can't randomize") + raise ValueError( + f"Min and/or max val not set for {self.param}, can't randomize" + ) self.start_val = rng.uniform(self.min_val, self.max_val) + class FunctionParameterList(DataContainer): def __init__(self): super().__init__(table_name="FunctionParameterList") From 1a92e0a386da9824c86924aa50ac94a9ba62bc40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 11:35:40 +0000 Subject: [PATCH 747/756] Bump matplotlib from 3.8.0 to 3.8.1 Bumps [matplotlib](https://github.com/matplotlib/matplotlib) from 3.8.0 to 3.8.1. - [Release notes](https://github.com/matplotlib/matplotlib/releases) - [Commits](https://github.com/matplotlib/matplotlib/compare/v3.8.0...v3.8.1) --- updated-dependencies: - dependency-name: matplotlib dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3de6782a8..9fb73ae0c 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ keywords='pyiron', packages=find_packages(exclude=["*tests*"]), install_requires=[ - 'matplotlib==3.8.0', + 'matplotlib==3.8.1', 'numpy==1.26.0', 'pyiron_base==0.6.8', 'scipy==1.11.3', From ef89b0a8c650f3f724043421944ef59724ebadfa Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 6 Nov 2023 11:36:00 +0000 Subject: [PATCH 748/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 344c2027e..b7f16ef03 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -5,7 +5,7 @@ dependencies: - coveralls - coverage - codacy-coverage -- matplotlib =3.8.0 +- matplotlib =3.8.1 - numpy =1.26.0 - pyiron_base =0.6.8 - pyiron_atomistics =0.3.5 From 04486a00ba2b24f7d7e6d5c5731990f0eea250f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 11:36:03 +0000 Subject: [PATCH 749/756] Bump pympipool from 0.7.1 to 0.7.2 Bumps [pympipool](https://github.com/jan-janssen/pympipool) from 0.7.1 to 0.7.2. - [Release notes](https://github.com/jan-janssen/pympipool/releases) - [Commits](https://github.com/jan-janssen/pympipool/compare/pympipool-0.7.1...pympipool-0.7.2) --- updated-dependencies: - dependency-name: pympipool dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3de6782a8..05e6a32f1 100644 --- a/setup.py +++ b/setup.py @@ -54,7 +54,7 @@ ], 'tinybase': [ 'distributed==2023.10.1', - 'pympipool==0.7.1' + 'pympipool==0.7.2' ] }, cmdclass=versioneer.get_cmdclass(), From 644c4ae5e6111b997b373aa35e06113a6061db5c Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 6 Nov 2023 11:36:22 +0000 Subject: [PATCH 750/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 627a3ac29..b23ea781a 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -5,7 +5,7 @@ dependencies: - coveralls - coverage - codacy-coverage -- matplotlib =3.8.0 +- matplotlib =3.8.1 - numpy =1.26.0 - pyiron_base =0.6.8 - pyiron_atomistics =0.3.5 diff --git a/docs/environment.yml b/docs/environment.yml index 46153b743..0ca8c9ae6 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -7,7 +7,7 @@ dependencies: - coveralls - coverage - codacy-coverage -- matplotlib =3.8.0 +- matplotlib =3.8.1 - numpy =1.26.0 - pyiron_base =0.6.8 - pyiron_atomistics =0.3.5 From 298bf4efd07dd35f536af8ca6afe5eb44a0ab36a Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 6 Nov 2023 11:36:27 +0000 Subject: [PATCH 751/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 344c2027e..6e866c673 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -18,5 +18,5 @@ dependencies: - moto =4.2.7 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 -- pympipool =0.7.1 +- pympipool =0.7.2 - distributed =2023.10.1 From 6ec1d2be1170ff280d2666653775b9a8774a5bc7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 11:36:35 +0000 Subject: [PATCH 752/756] Bump boto3 from 1.28.73 to 1.28.78 Bumps [boto3](https://github.com/boto/boto3) from 1.28.73 to 1.28.78. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.28.73...1.28.78) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3de6782a8..f489e3dbb 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ ], 'image': ['scikit-image==0.22.0'], 'generic': [ - 'boto3==1.28.73', + 'boto3==1.28.78', 'moto==4.2.7' ], 'tinybase': [ From e9ec723307f5a05134f46e03ec3abc8c5259dc54 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 6 Nov 2023 11:37:36 +0000 Subject: [PATCH 753/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 627a3ac29..c00b03038 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -18,7 +18,7 @@ dependencies: - moto =4.2.7 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 -- pympipool =0.7.1 +- pympipool =0.7.2 - distributed =2023.10.1 - python >= 3.10 - lammps diff --git a/docs/environment.yml b/docs/environment.yml index 46153b743..6aa5c87f0 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -20,5 +20,5 @@ dependencies: - moto =4.2.7 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 -- pympipool =0.7.1 +- pympipool =0.7.2 - distributed =2023.10.1 From e382a29cd69541fd764132c7d710a4d99b023c05 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 6 Nov 2023 11:37:51 +0000 Subject: [PATCH 754/756] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 344c2027e..2587640d1 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -14,7 +14,7 @@ dependencies: - seaborn =0.13.0 - scikit-image =0.22.0 - randspg =0.0.1 -- boto3 =1.28.73 +- boto3 =1.28.78 - moto =4.2.7 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 From 1ed40dfc95678da1a266148a0b3f41fb8b41cbe2 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 6 Nov 2023 11:45:54 +0000 Subject: [PATCH 755/756] [dependabot skip] Update env file --- .binder/environment.yml | 2 +- docs/environment.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.binder/environment.yml b/.binder/environment.yml index 627a3ac29..f04af9f84 100644 --- a/.binder/environment.yml +++ b/.binder/environment.yml @@ -14,7 +14,7 @@ dependencies: - seaborn =0.13.0 - scikit-image =0.22.0 - randspg =0.0.1 -- boto3 =1.28.73 +- boto3 =1.28.78 - moto =4.2.7 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 diff --git a/docs/environment.yml b/docs/environment.yml index 46153b743..2019a187f 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -16,7 +16,7 @@ dependencies: - seaborn =0.13.0 - scikit-image =0.22.0 - randspg =0.0.1 -- boto3 =1.28.73 +- boto3 =1.28.78 - moto =4.2.7 - pycp2k =0.2.2 - aws-sam-translator =1.77.0 From 6ba596c553bee31b98767c5377d97a3cc5ed9977 Mon Sep 17 00:00:00 2001 From: Leimeroth Date: Mon, 13 Nov 2023 08:38:53 +0100 Subject: [PATCH 756/756] add tight layout --- pyiron_contrib/atomistics/atomicrex/potential_factory.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyiron_contrib/atomistics/atomicrex/potential_factory.py b/pyiron_contrib/atomistics/atomicrex/potential_factory.py index d025b2319..f22a9953c 100644 --- a/pyiron_contrib/atomistics/atomicrex/potential_factory.py +++ b/pyiron_contrib/atomistics/atomicrex/potential_factory.py @@ -823,9 +823,13 @@ def plot_final_potential(self, job, filename=None): ylim = (np.min(y) - 0.1, 2) ax[i * 3 + k, V_count].plot(xdata, y) ax[i * 3 + k, V_count].set( - ylim=ylim, title=f"{el} {pot}", xlabel=xlabel + ylim=ylim, + title=f"{el} {pot}", + xlabel=xlabel, ) V_count += 1 + + fig.tight_layout() return fig, ax def count_local_extrema(