-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Graph-based pyiron workflows #577
Conversation
We can replace this with a real tool later
In anticipation of having Input and Output classes be entire collections of channels
And refactor the conneciton validity tests to be more readable and robust
And refactor access
Just let it catch the key or attribute error farther down the stack. We can catch it here with a more informative message later if we want
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One feature that would be really nice to have would be a nice representation of __repr__
. Right now, what we see is <pyiron_contrib.workflow.node.Node at 0x7f5366bf97b0>
for a node. Something like this:
class Node:
...
def to_dict(self):
return {
"label": self.label,
"inputs": {
tag: {
"type": str(channel.type_hint),
"connected": channel.ready,
"value": str(channel.value),
}
for tag, channel in self.inputs.channel_dict.items()
},
"outputs": {
tag: {
"type": str(channel.type_hint),
"value": str(channel.value),
}
for tag, channel in self.outputs.channel_dict.items()
}
}
def _repr_json_(self):
return self.to_dict()
def __repr__(self):
return json.dumps(self.to_dict(), indent=2)
Obviously it's only a suggestion, so feel free to change it.
"try:\n", | ||
" node = Node(plus_minus_one, (\"p1\", \"m1\"))\n", | ||
"except TypeError:\n", | ||
" print(\"This won't quite work yet!\")" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find it confusing that this notebook starts with an example which does not work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fair. I don't think I'll fix it this PR though.
I presume the following behaviour is not desired? def f(a: int) -> int:
return a + 1
def g(b: int) -> int:
return b - 2
nd_f = Node(f, 'f')
nd_g = Node(g, 'g')
nd_f.inputs.a = nd_g.outputs.g
print(nd_f.inputs.a.ready) Output: False Is it possible that it's because |
This is exactly why. I realized last night that that's what was bugging me about the pre-constructed nodes example, but didn't get a chance to fix it/add it to the todo list yet. I want to see if it introduces any weird side effects in the tests, but right now I plan to introduce updating-on-connection. |
I actually like to be able to see the memory address fairly often, so I think I'll leave the |
In agreement with @samwaseda
I created a full fledged example of pseudo Murnaghan: from pyiron_contrib.workflow.node import Node
from pyiron_contrib.workflow.workflow import Workflow
from pyiron_atomistics import Project
import numpy as np
from hashlib import sha1
from pyiron_atomistics.atomistics.structure.atoms import Atoms
pr = Project('WORKFLOW')
potential = '1995--Angelo-J-E--Ni-Al-H--LAMMPS--ipr1'
def get_energy(structure: Atoms, potential: str=potential, pr: Project=pr) -> tuple[float, float]:
lmp = pr.create.job.Lammps(('lmp', sha1((structure.__repr__() + potential).encode()).hexdigest()))
if lmp.status.initialized:
lmp.structure = structure
lmp.potential = potential
lmp.calc_static()
lmp.run()
return lmp.output.energy_pot[-1]
def get_strain(min_strain: float=-0.1, max_strain: float=0.1, n_points: int=11) -> np.ndarray:
return np.linspace(min_strain, max_strain, n_points)
def get_min_lattice(strain: np.ndarray, pr: Project=pr) -> Atoms:
structure = pr.create.structure.bulk('Al', cubic=True)
e_lst = [get_energy(structure.apply_strain(s, return_box=True)) for s in strain]
coeff = np.polyfit(strain, e_lst, 3)
s_range = np.linspace(np.min(strain), np.max(strain), 1001)
return structure.apply_strain(s_range[np.polyval(coeff, s_range).argmin()], return_box=True)
nd_min_lattice = Node(get_min_lattice, 'min_lattice')
nd_strain = Node(get_strain, 'strain')
nd_min_lattice.inputs.strain.connect(nd_strain.outputs.strain) But I was not really sure how to make a workflow out of the inner loop, i.e. currently |
Rad! Note that you can typically avoid passing
Yeah, this is a great question. I see three approaches:
(1) will always be acceptable. At the moment I lean towards trying to get really tight flow control nodes to make (2) as easy as possible rather than porting over my work in ironflow for (3), just because of the type-checking headaches.
|
As discussed on the teams page.
An example of Joerg's syntax running.
SessionThis can actually probably just stayProject
SerializationThis can be a separate PRname
andengine
, as these may frequently conflict with channel namesinput
andoutput
toinputs
andoutputs
to avoid conflicting with the verbsfor connection in channel
;for channel in inputs
;for node in workflow
DotDict
so they inheritkeys(),
values()and
items()`.A tool using the inspect module to help node designers quickly check for naming consistencies between input/pre/engine/post/output (the error messages are not so opaque, but this could be faster and easier).inspect is used to generate the channels to start with nowAllow callables forOpted to skip this for now since serialization would require saving antypes
that can facilitate some custom type checking from the userexec
ing more raw code.ChannelTemplate
at all (i.e. scrape types from annotations fo the node function)nodes
module so child classes are created by modifying__init__
instead of overriding class attributesHere:
Big things not here:
QoL feedback, especially visualizationLet's add some quality -- TODOs updatedto_node()
converter on the workflows, which dynamically generates you all the code needed to make a node with your current workflow's functionality. The big advantage is that it locks in what IO is open and what IO is internally connected within the workflow, which is the sort of robustness I'd like to see. I also think it may wind up really helping with "sharability", as then you have a light-weight python code snippet that other people can register with and use in their workflows. But I didn't want to bother implementing anything until we'd had some discussion.