Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.

Commit 7a38f6e

Browse files
authored
Merge pull request #162 from pyiron/onto_codevelopment
Onto codevelopment
2 parents 1b133b5 + 469646a commit 7a38f6e

27 files changed

+2128
-77
lines changed

.binder/environment.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ dependencies:
77
- matplotlib
88
- nglview
99
- numpy
10+
- owlready2
1011
- pandas
1112
- pyiron_base
1213
- pyiron_atomistics >= 0.2.57
1314
- pyiron_gui >= 0.0.8
15+
- pyiron_ontology == 0.1.1
1416
- ryvencore
1517
- seaborn
1618
- traitlets

.ci_support/environment.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ dependencies:
77
- matplotlib
88
- nglview
99
- numpy
10+
- owlready2
1011
- pandas
1112
- pyiron_base
1213
- pyiron_atomistics >= 0.2.57
1314
- pyiron_gui >= 0.0.8
15+
- pyiron_ontology == 0.1.1
1416
- ryvencore
1517
- seaborn
1618
- traitlets

README.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ If there is a particular use-case you'd like to see, or if one of our nodes is n
1818

1919
![](docs/_static/screenshot.png)
2020

21+
In its current form, ironflow has some UI performance issues, especially when placing new nodes with many ports, or moving nodes around in a big graph.
22+
(You can look at the movie demonstrating ontological typing below to get a sense of the delay for placing larger nodes.)
23+
This is a [known issue](https://github.com/pyiron/ironflow/issues/143) and performance enhancements are currently our top priority -- both in terms of UI performance and underlying computations, e.g. we would like to exploit the latest pyiron developments for [running lammps without writing any files](https://github.com/pyiron/pyiron_lammps) in calculation nodes.
24+
2125
## Usage
2226

2327
The main gui can be imported directly from `ironflow`.
@@ -58,7 +62,7 @@ Ironflow is built on top of ryvencore 0.3.1.1.
5862
There are a number of minor differences between ryven nodes and ironflow nodes discussed in the next section, but at a
5963
high level there are two significant differences:
6064

61-
### Typing
65+
### Data typing
6266

6367
All node ports are typed, and connection perform type-checking to ensure validity prior to establishing a connection.
6468
By default, a special `Untyped` data type is used, which performs *all* validity checks by value, and thus does not allow pre-wiring of a graph without full data.
@@ -71,6 +75,29 @@ An output port can be connected to an input port as long as its valid classes ar
7175
This type checking is still under development and may be somewhat brittle.
7276
Our goal is to extend this system to be dynamically informed by an ontology on top of the graph: instead of statically insisting that input be of type `float`, we instead demand that the ontological type of the energy be `surface energy` _dynamically_ because the output value of that port is used, e.g., to calculate a grain boundary interface energy.
7377

78+
### Ontological typing
79+
80+
Nodes can also optionally carry an "ontological type" (otype).
81+
Leaning on the [pyiron_ontology](https://github.com/pyiron/pyiron_ontology) library for representing knowledge in computational workflows, otypes give a rich _graph dependent_ representation of the data and facilitate guided workflow design.
82+
This is fully demonstrated in the `bulk_modulus.ipynb` and `surface_energy.ipynb` notebooks, but a quick demo is also provided in the video below.
83+
84+
We see that there is a "recommended" tab for nodes.
85+
After selecting this menu, clicking on the `CalcMurnaghan.engine` port populates the tab with nodes that have valid output for this port.
86+
We can double-click to place the new node (`Lammps`) and repeat the process, e.g. for the `Lammps.structure` input.
87+
Here we see there are two possibilities -- `BulkStructure` and `SlabStructure` -- and place both.
88+
(Note, as mentioned at the head of the readme, there is some lag in ironflow right now; you can see this in the delay between the double-click and the placement of these larger nodes.)
89+
Not only do we get recommendations for nodes to place in the graph, but we also get specific recommendations of which ports make valid connections!
90+
Below we again select the `Lammps.structure` input port, and see that the output ports on both the structure nodes is highlighted.
91+
Similarly, if we click the `Lammps.engine` output port, we see that all the valid input ports on our graph get highlighted; in this case, `CalcMurnaghan.input`.
92+
Finally, we see the real power of otypes -- by connecting the two `engine` ports, the `Lammps` node now has access to the _ontological requirements_ of the `CalcMurnaghan` node!
93+
In particular, `CalcMurnaghan` produces _bulk moduli_ and thus only works for calculations on _bulk structures_.
94+
After these are connected, when we once again select the `Lammps.structure` input, _only_ the `BulkStructure` node gets highlighted, and _only_ `BulkStructure` appears in the recommended nodes window.
95+
96+
![ironflow_ontology.mov](docs/_static/ironflow_ontology.mov)
97+
98+
Of course, not all ports in ironflow are otyped, and indeed not all should be -- e.g. it doesn't make sense to ontologically-type the output of the `Linspace` node, as it is just providing numbers which may be useful in many contexts.
99+
However, for nodes which specifically produce and require physically-/ontologically-meaningful data, otyping is a powerful tool for understanding workflows and guiding their design.
100+
74101
### Batching
75102

76103
Many ports can be "batched" by selecting them to open the node controller window and pressing the "batched" button.
@@ -117,12 +144,13 @@ class My_Node(Node):
117144
gui.register_node(My_Node)
118145
```
119146

120-
Ironflow nodes differ from standard ryven (version 0.3.1.1) nodes in four ways:
147+
Ironflow nodes differ from standard ryven (version 0.3.1.1) nodes in five ways:
121148
- There is a new helper method `output` analogous to the existing `input` method that lets you more easily access output values, i.e. just a quality-of-life difference.
122149
- Input/output ports and the port values are directly accessible as attributes *if* those ports were labeled, e.g. `node.inputs.ports.foo` or `node.outputs.values.bar`.
123150
- They have a `representation` dictionary, which is used by the IPython gui front-end to give a richer look at nodes. By default, this includes all the outputs and the source code for the node, but you can append to or overwrite these values by specifying an `extra_representations` dictionary on your custom nodes.
124151
- They have two new events: `before_update` and `after_update`, to which you can connect (e.g. `node.after_update.connect`) or disconnect (`...disconnect`) methods to fire before and/or after updates occur -- such methods must take the node instance itself as the first argument, and the canonical input integer (specifying which input value it is that's updating) as the second argument. (You can see an example of this in our base `Node` class, where we use it to force an update of the `representation` attribute after each node update.)
125152
- It is strongly advised to specify a `dtype` for each of your nodes from among `node_tools.dtypes`.
153+
- Ports have an additional `otype` field to facilitate ontologically-informed port and node suggestions.
126154

127155
Otherwise, they are just standard ryven nodes, and all the ryven documentation applies.
128156

docs/_static/ironflow_ontology.mov

4.9 MB
Binary file not shown.

docs/environment.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ dependencies:
99
- matplotlib
1010
- nglview
1111
- numpy
12+
- owlready2
1213
- pandas
1314
- pyiron_base
1415
- pyiron_atomistics >= 0.2.57
1516
- pyiron_gui >= 0.0.8
17+
- pyiron_ontology == 0.1.1
1618
- ryvencore
1719
- seaborn
1820
- traitlets

ironflow/gui/gui.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
if TYPE_CHECKING:
2121
from ironflow.model.node import Node
22+
from ironflow.model.port import NodeInput, NodeOutput
2223

2324

2425
class GUI(HasSession, DrawsWidgets):
@@ -85,7 +86,7 @@ def __init__(
8586
**kwargs,
8687
)
8788

88-
self.workflows = WorkflowsGUI(model=self)
89+
self.workflows = WorkflowsGUI(gui=self)
8990
self.browser = BrowserGUI()
9091

9192
try:
@@ -158,6 +159,14 @@ def log_to_display(self):
158159
def log_to_stdout(self):
159160
self.log.log_to_stdout()
160161

162+
def build_recommendations(self, port: NodeInput | NodeOutput):
163+
self.recommend_nodes(port)
164+
self.workflows.flow_box.update_nodes(self.nodes_dictionary)
165+
166+
def clear_recommendations(self):
167+
self.clear_recommended_nodes()
168+
self.workflows.flow_box.update_nodes(self.nodes_dictionary)
169+
161170
def draw(self):
162171
return self.widget
163172

ironflow/gui/workflows/boxes/flow.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def nodes_options(self) -> list[str]:
6161
def update(self, nodes_dictionary: dict) -> None:
6262
self._nodes_dictionary = nodes_dictionary
6363
self.modules_dropdown.options = self.module_options
64+
self.node_selector.options = self.nodes_options
6465

6566

6667
class FlowBox(DrawsWidgets):

ironflow/gui/workflows/canvas_widgets/base.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from ironflow.gui.workflows.canvas_widgets.flow import FlowCanvas
1717
from ironflow.gui.workflows.screen import WorkflowsGUI
1818
from ironflow.model.flow import Flow
19+
from ironflow.model.model import HasSession
1920
from ironflow.gui.workflows.canvas_widgets.layouts import Layout
2021

2122

@@ -74,6 +75,10 @@ def y(self) -> Number:
7475
def canvas(self) -> Canvas:
7576
return self.parent.canvas
7677

78+
@property
79+
def gui(self) -> HasSession:
80+
return self.parent.gui
81+
7782
@property
7883
def screen(self) -> WorkflowsGUI:
7984
return self.parent.screen

ironflow/gui/workflows/canvas_widgets/flow.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
from ipycanvas import Canvas, hold_canvas
1515
from IPython.display import display
1616

17+
from ironflow.model.port import NodeInput, NodeOutput
1718
from ironflow.gui.workflows.canvas_widgets.base import CanvasWidget
1819
from ironflow.gui.workflows.canvas_widgets.layouts import NodeLayout
1920
from ironflow.gui.workflows.canvas_widgets.nodes import NodeWidget
2021
from ironflow.gui.workflows.canvas_widgets.ports import PortWidget
2122

2223
if TYPE_CHECKING:
24+
from ironflow.gui.gui import GUI
2325
from ironflow.gui.workflows.canvas_widgets.base import Number
2426
from ironflow.gui.workflows.screen import WorkflowsGUI
2527
from ironflow.model.flow import Flow
@@ -88,6 +90,7 @@ def __init__(self, screen: WorkflowsGUI, flow: Flow):
8890
)
8991

9092
self._object_to_gui_dict = {}
93+
self._highlighted_ports: list[PortWidget] = []
9194

9295
@property
9396
def canvas(self):
@@ -97,6 +100,10 @@ def canvas(self):
97100
def flow_canvas(self) -> FlowCanvas:
98101
return self
99102

103+
@property
104+
def gui(self) -> GUI:
105+
return self.screen.gui
106+
100107
@property
101108
def title(self) -> str:
102109
return self.flow.script.title
@@ -253,3 +260,52 @@ def zoom_in(self) -> None:
253260

254261
def zoom_out(self) -> None:
255262
self._zoom(min(self._zoom_index + 1, len(self._zoom_factors) - 1))
263+
264+
def highlight_compatible_ports(self, selected: PortWidget):
265+
if selected.port.otype is None:
266+
return
267+
268+
compatible_port_widgets = self._get_port_widgets_ontologically_compatible_with(
269+
selected.port
270+
)
271+
272+
for port_widget in compatible_port_widgets:
273+
port_widget.highlight()
274+
self._highlighted_ports = compatible_port_widgets
275+
276+
def _get_port_widgets_ontologically_compatible_with(self, port):
277+
if isinstance(port, NodeInput):
278+
input_tree = port.otype.get_source_tree(
279+
additional_requirements=port.get_downstream_requirements()
280+
)
281+
return [
282+
subwidget
283+
for subwidget in self._port_widgets
284+
if isinstance(subwidget.port, NodeOutput)
285+
and subwidget.port.all_connections_found_in(input_tree)
286+
]
287+
elif isinstance(port, NodeOutput):
288+
return [
289+
subwidget
290+
for subwidget in self._port_widgets
291+
if subwidget.port.otype is not None # Progressively expensive checks
292+
and port.otype in subwidget.port.otype.get_sources()
293+
and subwidget.port.workflow_tree_contains_connections_of(port)
294+
]
295+
else:
296+
raise TypeError(
297+
f"Expected a {NodeInput} or {NodeOutput} but got {type(port)}"
298+
)
299+
300+
@property
301+
def _port_widgets(self):
302+
return [
303+
subwidget
304+
for node_widget in self.objects_to_draw
305+
for subwidget in node_widget.objects_to_draw
306+
if isinstance(subwidget, PortWidget)
307+
]
308+
309+
def clear_port_highlighting(self):
310+
for port_widget in self._highlighted_ports:
311+
port_widget.dehighlight()

ironflow/gui/workflows/canvas_widgets/layouts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class PortLayout(Layout, ABC):
4141
width: int = 20
4242
height: int = 20
4343
max_title_chars: int = 10
44+
highlight_color = "white"
4445

4546

4647
@dataclass

0 commit comments

Comments
 (0)