Repository: pyiron/ironflow (language: python)

Issue:
Surface energy
- [x] Add otyping to the other nodes that evolve jobs (static, min, MD)
- [x] Introduce an otyped surface energy node
- [x] Debug port suggestions for energies (currently surface energy input seems to ignore the structure requirements. Double check in the pure ontology first, as I think it might be an issue with `get_downstream_requirements` rather than the onto reasoning/labeling)
- [x] Add demo notebook


Discussion / hints:

Fix patch:
diff --git a/.binder/environment.yml b/.binder/environment.yml
index 14d95109..7ec1f2f0 100644
--- a/.binder/environment.yml
+++ b/.binder/environment.yml
@@ -7,10 +7,12 @@ dependencies:
 - matplotlib
 - nglview
 - numpy
+- owlready2
 - pandas
 - pyiron_base
 - pyiron_atomistics >= 0.2.57
 - pyiron_gui >= 0.0.8
+- pyiron_ontology == 0.1.1
 - ryvencore
 - seaborn
 - traitlets
diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml
index 2386e9c8..8ab9528e 100644
--- a/.ci_support/environment.yml
+++ b/.ci_support/environment.yml
@@ -7,10 +7,12 @@ dependencies:
   - matplotlib
   - nglview
   - numpy
+  - owlready2
   - pandas
   - pyiron_base
   - pyiron_atomistics >= 0.2.57
   - pyiron_gui >= 0.0.8
+  - pyiron_ontology == 0.1.1
   - ryvencore
   - seaborn
   - traitlets
diff --git a/README.md b/README.md
index b7ed5564..5d7049a9 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,10 @@ If there is a particular use-case you'd like to see, or if one of our nodes is n
 
 ![](docs/_static/screenshot.png)
 
+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.
+(You can look at the movie demonstrating ontological typing below to get a sense of the delay for placing larger nodes.)
+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.
+
 ## Usage
 
 The main gui can be imported directly from `ironflow`.
@@ -58,7 +62,7 @@ Ironflow is built on top of ryvencore 0.3.1.1.
 There are a number of minor differences between ryven nodes and ironflow nodes discussed in the next section, but at a 
 high level there are two significant differences:
 
-### Typing
+### Data typing
 
 All node ports are typed, and connection perform type-checking to ensure validity prior to establishing a connection.
 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
 This type checking is still under development and may be somewhat brittle.
 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.
 
+### Ontological typing
+
+Nodes can also optionally carry an "ontological type" (otype). 
+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.
+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.
+
+We see that there is a "recommended" tab for nodes.
+After selecting this menu, clicking on the `CalcMurnaghan.engine` port populates the tab with nodes that have valid output for this port.
+We can double-click to place the new node (`Lammps`) and repeat the process, e.g. for the `Lammps.structure` input.
+Here we see there are two possibilities -- `BulkStructure` and `SlabStructure` -- and place both.
+(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.)
+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!
+Below we again select the `Lammps.structure` input port, and see that the output ports on both the structure nodes is highlighted.
+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`.
+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!
+In particular, `CalcMurnaghan` produces _bulk moduli_ and thus only works for calculations on _bulk structures_.
+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.
+
+![ironflow_ontology.mov](docs/_static/ironflow_ontology.mov)
+
+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.
+However, for nodes which specifically produce and require physically-/ontologically-meaningful data, otyping is a powerful tool for understanding workflows and guiding their design.
+
 ### Batching
 
 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):
 gui.register_node(My_Node)
 ```
 
-Ironflow nodes differ from standard ryven (version 0.3.1.1) nodes in four ways:
+Ironflow nodes differ from standard ryven (version 0.3.1.1) nodes in five ways:
 - 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.
 - 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`.
 - 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.
 - 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.)
 - It is strongly advised to specify a `dtype` for each of your nodes from among `node_tools.dtypes`.
+- Ports have an additional `otype` field to facilitate ontologically-informed port and node suggestions.
 
 Otherwise, they are just standard ryven nodes, and all the ryven documentation applies.
 
diff --git a/docs/_static/ironflow_ontology.mov b/docs/_static/ironflow_ontology.mov
new file mode 100644
index 00000000..50955b9a
Binary files /dev/null and b/docs/_static/ironflow_ontology.mov differ
diff --git a/docs/environment.yml b/docs/environment.yml
index b07e815c..56f5902b 100644
--- a/docs/environment.yml
+++ b/docs/environment.yml
@@ -9,10 +9,12 @@ dependencies:
 - matplotlib
 - nglview
 - numpy
+- owlready2
 - pandas
 - pyiron_base
 - pyiron_atomistics >= 0.2.57
 - pyiron_gui >= 0.0.8
+- pyiron_ontology == 0.1.1
 - ryvencore
 - seaborn
 - traitlets
diff --git a/ironflow/gui/gui.py b/ironflow/gui/gui.py
index 47384d4e..40882ad2 100644
--- a/ironflow/gui/gui.py
+++ b/ironflow/gui/gui.py
@@ -19,6 +19,7 @@
 
 if TYPE_CHECKING:
     from ironflow.model.node import Node
+    from ironflow.model.port import NodeInput, NodeOutput
 
 
 class GUI(HasSession, DrawsWidgets):
@@ -85,7 +86,7 @@ def __init__(
             **kwargs,
         )
 
-        self.workflows = WorkflowsGUI(model=self)
+        self.workflows = WorkflowsGUI(gui=self)
         self.browser = BrowserGUI()
 
         try:
@@ -158,6 +159,14 @@ def log_to_display(self):
     def log_to_stdout(self):
         self.log.log_to_stdout()
 
+    def build_recommendations(self, port: NodeInput | NodeOutput):
+        self.recommend_nodes(port)
+        self.workflows.flow_box.update_nodes(self.nodes_dictionary)
+
+    def clear_recommendations(self):
+        self.clear_recommended_nodes()
+        self.workflows.flow_box.update_nodes(self.nodes_dictionary)
+
     def draw(self):
         return self.widget
 
diff --git a/ironflow/gui/workflows/boxes/flow.py b/ironflow/gui/workflows/boxes/flow.py
index b3a8e812..9fb44f9b 100644
--- a/ironflow/gui/workflows/boxes/flow.py
+++ b/ironflow/gui/workflows/boxes/flow.py
@@ -61,6 +61,7 @@ def nodes_options(self) -> list[str]:
     def update(self, nodes_dictionary: dict) -> None:
         self._nodes_dictionary = nodes_dictionary
         self.modules_dropdown.options = self.module_options
+        self.node_selector.options = self.nodes_options
 
 
 class FlowBox(DrawsWidgets):
diff --git a/ironflow/gui/workflows/canvas_widgets/base.py b/ironflow/gui/workflows/canvas_widgets/base.py
index 47846ef3..63653169 100644
--- a/ironflow/gui/workflows/canvas_widgets/base.py
+++ b/ironflow/gui/workflows/canvas_widgets/base.py
@@ -16,6 +16,7 @@
     from ironflow.gui.workflows.canvas_widgets.flow import FlowCanvas
     from ironflow.gui.workflows.screen import WorkflowsGUI
     from ironflow.model.flow import Flow
+    from ironflow.model.model import HasSession
     from ironflow.gui.workflows.canvas_widgets.layouts import Layout
 
 
@@ -74,6 +75,10 @@ def y(self) -> Number:
     def canvas(self) -> Canvas:
         return self.parent.canvas
 
+    @property
+    def gui(self) -> HasSession:
+        return self.parent.gui
+
     @property
     def screen(self) -> WorkflowsGUI:
         return self.parent.screen
diff --git a/ironflow/gui/workflows/canvas_widgets/flow.py b/ironflow/gui/workflows/canvas_widgets/flow.py
index a32da1a2..4e57c707 100644
--- a/ironflow/gui/workflows/canvas_widgets/flow.py
+++ b/ironflow/gui/workflows/canvas_widgets/flow.py
@@ -14,12 +14,14 @@
 from ipycanvas import Canvas, hold_canvas
 from IPython.display import display
 
+from ironflow.model.port import NodeInput, NodeOutput
 from ironflow.gui.workflows.canvas_widgets.base import CanvasWidget
 from ironflow.gui.workflows.canvas_widgets.layouts import NodeLayout
 from ironflow.gui.workflows.canvas_widgets.nodes import NodeWidget
 from ironflow.gui.workflows.canvas_widgets.ports import PortWidget
 
 if TYPE_CHECKING:
+    from ironflow.gui.gui import GUI
     from ironflow.gui.workflows.canvas_widgets.base import Number
     from ironflow.gui.workflows.screen import WorkflowsGUI
     from ironflow.model.flow import Flow
@@ -88,6 +90,7 @@ def __init__(self, screen: WorkflowsGUI, flow: Flow):
         )
 
         self._object_to_gui_dict = {}
+        self._highlighted_ports: list[PortWidget] = []
 
     @property
     def canvas(self):
@@ -97,6 +100,10 @@ def canvas(self):
     def flow_canvas(self) -> FlowCanvas:
         return self
 
+    @property
+    def gui(self) -> GUI:
+        return self.screen.gui
+
     @property
     def title(self) -> str:
         return self.flow.script.title
@@ -253,3 +260,52 @@ def zoom_in(self) -> None:
 
     def zoom_out(self) -> None:
         self._zoom(min(self._zoom_index + 1, len(self._zoom_factors) - 1))
+
+    def highlight_compatible_ports(self, selected: PortWidget):
+        if selected.port.otype is None:
+            return
+
+        compatible_port_widgets = self._get_port_widgets_ontologically_compatible_with(
+            selected.port
+        )
+
+        for port_widget in compatible_port_widgets:
+            port_widget.highlight()
+        self._highlighted_ports = compatible_port_widgets
+
+    def _get_port_widgets_ontologically_compatible_with(self, port):
+        if isinstance(port, NodeInput):
+            input_tree = port.otype.get_source_tree(
+                additional_requirements=port.get_downstream_requirements()
+            )
+            return [
+                subwidget
+                for subwidget in self._port_widgets
+                if isinstance(subwidget.port, NodeOutput)
+                and subwidget.port.all_connections_found_in(input_tree)
+            ]
+        elif isinstance(port, NodeOutput):
+            return [
+                subwidget
+                for subwidget in self._port_widgets
+                if subwidget.port.otype is not None  # Progressively expensive checks
+                and port.otype in subwidget.port.otype.get_sources()
+                and subwidget.port.workflow_tree_contains_connections_of(port)
+            ]
+        else:
+            raise TypeError(
+                f"Expected a {NodeInput} or {NodeOutput} but got {type(port)}"
+            )
+
+    @property
+    def _port_widgets(self):
+        return [
+            subwidget
+            for node_widget in self.objects_to_draw
+            for subwidget in node_widget.objects_to_draw
+            if isinstance(subwidget, PortWidget)
+        ]
+
+    def clear_port_highlighting(self):
+        for port_widget in self._highlighted_ports:
+            port_widget.dehighlight()
diff --git a/ironflow/gui/workflows/canvas_widgets/layouts.py b/ironflow/gui/workflows/canvas_widgets/layouts.py
index e77a10f5..3dca337a 100644
--- a/ironflow/gui/workflows/canvas_widgets/layouts.py
+++ b/ironflow/gui/workflows/canvas_widgets/layouts.py
@@ -41,6 +41,7 @@ class PortLayout(Layout, ABC):
     width: int = 20
     height: int = 20
     max_title_chars: int = 10
+    highlight_color = "white"
 
 
 @dataclass
diff --git a/ironflow/gui/workflows/canvas_widgets/nodes.py b/ironflow/gui/workflows/canvas_widgets/nodes.py
index a4ce1dac..d38e41e6 100644
--- a/ironflow/gui/workflows/canvas_widgets/nodes.py
+++ b/ironflow/gui/workflows/canvas_widgets/nodes.py
@@ -30,7 +30,7 @@
 if TYPE_CHECKING:
     from ironflow.gui.workflows.canvas_widgets.flow import FlowCanvas
     from ironflow.gui.workflows.canvas_widgets.base import Number
-    from ironflow.model import NodeInputBP, NodeOutputBP
+    from ironflow.model.port import NodeInputBP, NodeOutputBP
     from ironflow.model.node import Node
 
 
diff --git a/ironflow/gui/workflows/canvas_widgets/ports.py b/ironflow/gui/workflows/canvas_widgets/ports.py
index a9a55420..1d973d8a 100644
--- a/ironflow/gui/workflows/canvas_widgets/ports.py
+++ b/ironflow/gui/workflows/canvas_widgets/ports.py
@@ -47,6 +47,7 @@ def __init__(
         self.radius = radius
         self.port = port
         self.title_alignment = title_alignment
+        self.highlighted = False
 
     def on_click(
         self, last_selected_object: Optional[CanvasWidget]
@@ -66,7 +67,9 @@ def on_click(
 
     @property
     def _current_color(self):
-        if self.port.valid_val:
+        if self.highlighted:
+            color = self.layout.highlight_color
+        elif self.port.valid_val:
             if self.selected:
                 color = self.layout.valid_selected_color
             else:
@@ -105,3 +108,18 @@ def draw_title(self) -> None:
 
     def _is_at_xy(self, x_in: Number, y_in: Number) -> bool:
         return (x_in - self.x) ** 2 + (y_in - self.y) ** 2 < self.radius**2
+
+    def select(self) -> None:
+        super().select()
+        self.gui.build_recommendations(self.port)
+        self.flow_canvas.highlight_compatible_ports(self)
+
+    def deselect(self) -> None:
+        super().deselect()
+        self.flow_canvas.clear_port_highlighting()
+
+    def highlight(self):
+        self.highlighted = True
+
+    def dehighlight(self):
+        self.highlighted = False
diff --git a/ironflow/gui/workflows/screen.py b/ironflow/gui/workflows/screen.py
index ee2e5cd1..96cfda1c 100644
--- a/ironflow/gui/workflows/screen.py
+++ b/ironflow/gui/workflows/screen.py
@@ -22,22 +22,23 @@
 from ironflow.utils import display_string
 
 if TYPE_CHECKING:
-    from ironflow.model.model import HasSession
+    from ironflow.gui.gui import GUI
     from ironflow.gui.workflows.canvas_widgets.nodes import NodeWidget
     from ironflow.model.flow import Flow
     from ironflow.model.node import Node
+    from ironflow.model.port import NodeInput, NodeOutput
 
 
 class WorkflowsGUI(DrawsWidgets):
     main_widget_class = widgets.VBox
 
-    def __new__(cls, model, *args, **kwargs):
+    def __new__(cls, gui, *args, **kwargs):
         return super().__new__(cls, *args, **kwargs)
 
-    def __init__(self, model: HasSession, *args, **kwargs):
+    def __init__(self, gui: GUI, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
-        self.model = model
+        self.gui = gui
         self.flow_canvases = []
 
         self.toolbar = Toolbar()
@@ -45,7 +46,7 @@ def __init__(self, model: HasSession, *args, **kwargs):
         self.node_presenter = NodePresenter()
         self.text_out = TextOut()
         self.input = UserInput()
-        self.flow_box = FlowBox(self.model.nodes_dictionary)
+        self.flow_box = FlowBox(self.gui.nodes_dictionary)
 
         self.toolbar.alg_mode_dropdown.observe(
             self._change_alg_mode_dropdown, names="value"
@@ -77,7 +78,7 @@ def new_node_class(self):
 
     @property
     def flow_canvas(self):
-        return self.flow_canvases[self.model.active_script_index]
+        return self.flow_canvases[self.gui.active_script_index]
 
     @property
     def selected_node(self) -> Node | None:
@@ -88,7 +89,7 @@ def update_tabs(self):
         self.flow_box.update_tabs(
             outputs=[fc.output for fc in self.flow_canvases],
             titles=[fc.title for fc in self.flow_canvases],
-            active_index=self.model.active_script_index,
+            active_index=self.gui.active_script_index,
         )
         for fc in self.flow_canvases:
             fc.display()
@@ -100,7 +101,7 @@ def add_flow(self, flow: Flow):
     @draws_widgets
     def load_from_data(self, data: dict):
         self.flow_canvases = []
-        for i_script, script in enumerate(self.model.session.scripts):
+        for i_script, script in enumerate(self.gui.session.scripts):
             flow_canvas = FlowCanvas(screen=self, flow=script.flow)
             all_data = data["scripts"][i_script]["flow"]["nodes"]
             for i_node, node in enumerate(script.flow.nodes):
@@ -154,21 +155,21 @@ def delete_flow(self, i: int):
     def _change_alg_mode_dropdown(self, change: dict) -> None:
         # Current behaviour: Updates the flow mode for all scripts
         # Todo: Change only for the active script, and update the dropdown on tab (script) switching
-        for script in self.model.session.scripts:
+        for script in self.gui.session.scripts:
             script.flow.set_algorithm_mode(self.toolbar.alg_mode_dropdown.value)
 
     def _click_save(self, change: dict) -> None:
         self.input.open_text(
             "Save file",
             self._click_confirm_save,
-            self.model.session_title,
+            self.gui.session_title,
             description_tooltip="Save to file name (omit the file extension, .json)",
         )
         self.print("Choose a file name to save to (omit the file extension, .json)")
 
     def _click_confirm_save(self, change: dict) -> None:
         file_name = self.input.text
-        self.model.save(f"{file_name}.json")
+        self.gui.save(f"{file_name}.json")
         self.print(f"Session saved to {file_name}.json")
         self.input.clear()
 
@@ -176,14 +177,14 @@ def _click_load(self, change: dict) -> None:
         self.input.open_text(
             "Load file",
             self._click_confirm_load,
-            self.model.session_title,
+            self.gui.session_title,
             description_tooltip="Load from file name (omit the file extension, .json).",
         )
         self.print("Choose a file name to load (omit the file extension, .json)")
 
     def _click_confirm_load(self, change: dict) -> None:
         file_name = self.input.text
-        self.model.load(f"{file_name}.json")
+        self.gui.load(f"{file_name}.json")
         self.update_tabs()
         self.node_presenter.clear()
         self.print(f"Session loaded from {file_name}.json")
@@ -203,42 +204,40 @@ def _click_delete_node(self, change: dict) -> None:
         self.flow_canvas.delete_selected()
 
     def _click_create_script(self, change: dict) -> None:
-        self.model.create_script()
+        self.gui.create_script()
         self.update_tabs()
 
     def _click_rename_script(self, change: dict) -> None:
         self.input.open_text(
             "New name",
             self._click_confirm_rename,
-            self.model.script.title,
+            self.gui.script.title,
             description_tooltip="New script name",
         )
         self.print("Choose a new name for the current script")
 
     def _click_confirm_rename(self, change: dict) -> None:
         new_name = self.input.text
-        old_name = self.model.script.title
-        rename_success = self.model.rename_script(new_name)
+        old_name = self.gui.script.title
+        rename_success = self.gui.rename_script(new_name)
         if rename_success:
-            self.flow_box.script_tabs.set_title(
-                self.model.active_script_index, new_name
-            )
+            self.flow_box.script_tabs.set_title(self.gui.active_script_index, new_name)
             self.print(f"Script '{old_name}' renamed '{new_name}'")
         else:
             self.print(
-                f"INVALID NAME: Failed to rename script '{self.model.script.title}' to '{new_name}'."
+                f"INVALID NAME: Failed to rename script '{self.gui.script.title}' to '{new_name}'."
             )
 
     def _click_delete_script(self, change: dict) -> None:
         self.input.open_bool(
-            f"Delete the entire script {self.model.script.title}?",
+            f"Delete the entire script {self.gui.script.title}?",
             self._click_confirm_delete_script,
         )
 
     def _click_confirm_delete_script(self, change: dict) -> None:
-        script_name = self.model.script.title
-        self.delete_flow(self.model.active_script_index)
-        self.model.delete_script()
+        script_name = self.gui.script.title
+        self.delete_flow(self.gui.active_script_index)
+        self.gui.delete_script()
         self.update_tabs()
         self.print(f"Script {script_name} deleted")
 
@@ -261,13 +260,11 @@ def _change_script_tabs(self, change: dict):
         if change["name"] == "selected_index" and change["new"] is not None:
             self.input.clear()
             self.flow_canvas.deselect_all()
-            if self.flow_box.script_tabs.selected_index == self.model.n_scripts:
-                self.model.create_script()
+            if self.flow_box.script_tabs.selected_index == self.gui.n_scripts:
+                self.gui.create_script()
                 self.update_tabs()
             else:
-                self.model.active_script_index = (
-                    self.flow_box.script_tabs.selected_index
-                )
+                self.gui.active_script_index = self.flow_box.script_tabs.selected_index
 
     def close(self):
         self.toolbar.close()
diff --git a/ironflow/model/__init__.py b/ironflow/model/__init__.py
index 23f9d461..3974e505 100644
--- a/ironflow/model/__init__.py
+++ b/ironflow/model/__init__.py
@@ -11,5 +11,4 @@
 making connections.
 """
 
-from ryvencore import NodeInputBP
 from ryvencore.NodePort import NodePort
diff --git a/ironflow/model/dtypes.py b/ironflow/model/dtypes.py
index 00edd64d..4f211ace 100644
--- a/ironflow/model/dtypes.py
+++ b/ironflow/model/dtypes.py
@@ -36,7 +36,7 @@ def isiterable(obj):
 
 
 def other_classes_are_subset(other, reference):
-    return all([any([issubclass(o, ref) for ref in reference]) for o in other])
+    return all(any(issubclass(o, ref) for ref in reference) for o in other)
 
 
 class DType(DTypeCore, ABC):
@@ -233,11 +233,11 @@ def _accepts_dtype(self, other: DType):
             return False
 
     def _accepts_non_none_instance(self, val: Any):
-        return any([isinstance(val, c) for c in self.valid_classes])
+        return any(isinstance(val, c) for c in self.valid_classes)
 
     def _batch_accepts_instance(self, val: Any):
         if hasattr(val, "__iter__"):
-            if any([v is None for v in val]) and not self.allow_none:
+            if any(v is None for v in val) and not self.allow_none:
                 return False
             else:
                 return self._classes_are_subset(
@@ -409,7 +409,7 @@ def _accepts_non_none_instance(self, val: Any):
 
     def _batch_accepts_instance(self, val: Any):
         return isinstance(val, (list, np.ndarray)) and all(
-            [self._accepts_non_none_instance(v) for v in val]
+            self._accepts_non_none_instance(v) for v in val
         )
 
 
@@ -475,10 +475,10 @@ def _accepts_dtype(self, other: DType):
 
     def _accepts_non_none_instance(self, val: Any):
         return isiterable(val) and all(
-            [any([isinstance(v, c) for c in self.valid_classes]) for v in val]
+            any(isinstance(v, c) for c in self.valid_classes) for v in val
         )
 
     def _batch_accepts_instance(self, val: Any):
         return isiterable(val) and all(
-            [self._accepts_none(v) or self._accepts_non_none_instance(v) for v in val]
+            self._accepts_none(v) or self._accepts_non_none_instance(v) for v in val
         )
diff --git a/ironflow/model/model.py b/ironflow/model/model.py
index f1eaae74..d1aa73d1 100644
--- a/ironflow/model/model.py
+++ b/ironflow/model/model.py
@@ -17,6 +17,7 @@
 from typing import Optional, Type
 
 from ironflow.model.node import Node
+from ironflow.model.port import NodeInput, NodeOutput
 from ironflow.model.session import Session
 from ironflow.model.flow import Flow
 from ironflow.model.script import Script
@@ -41,7 +42,7 @@ def __init__(
         if enable_ryven_log:
             self.session.info_messenger().enable()
 
-        self.nodes_dictionary = {}
+        self.nodes_dictionary = {"recommended": {}}
         from ironflow.nodes import built_in
         from ironflow.nodes.pyiron import atomistics_nodes
         from ironflow.nodes.std import (
@@ -170,7 +171,7 @@ def register_node(
 
         Example:
             >>> from ironflow import GUI
-            >>> from ironflow.custom_nodes import Node, NodeInputBP, NodeOutputBP, dtypes, input_widgets
+            >>> from ironflow.node_tools import Node, NodeInputBP, NodeOutputBP, dtypes, input_widgets
             >>> gui = GUI(script_title='foo')
             >>>
             >>> class MyNode(Node):
@@ -260,7 +261,39 @@ def register_nodes(
         elif isinstance(source, types.ModuleType):
             self.register_nodes_from_module(source, node_group=node_group)
         elif isinstance(source, (list, tuple)) and all(
-            [issubclass(item, Node) for item in source]
+            issubclass(item, Node) for item in source
         ):
             for node_class in source:
                 self.register_node(node_class, node_group=node_group)
+
+    def recommend_nodes(self, port: NodeInput | NodeOutput):
+        recommendations = {}
+        if port.otype is not None:
+            if isinstance(port, NodeInput):
+                recommendations = self._get_nodes_giving_matching_output(port)
+            elif isinstance(port, NodeOutput):
+                recommendations = self._get_nodes_taking_matching_input(port)
+        self.nodes_dictionary["recommended"] = recommendations
+
+    def _get_nodes_giving_matching_output(self, port: NodeInput):
+        requirements = port.get_downstream_requirements()
+        sources = port.otype.get_sources(requirements)
+        return {
+            node.title: node
+            for node in self.session.nodes
+            if any(out.otype in sources for out in node.init_outputs)
+        }
+
+    def _get_nodes_taking_matching_input(self, port: NodeOutput):
+        return {
+            node.title: node
+            for node in self.session.nodes
+            if any(
+                port.otype in inp.otype.get_sources()
+                for inp in node.init_inputs
+                if inp.otype is not None
+            )
+        }
+
+    def clear_recommended_nodes(self):
+        self.nodes_dictionary["recommended"] = {}
diff --git a/ironflow/model/node.py b/ironflow/model/node.py
index e6718b8e..cd145a83 100644
--- a/ironflow/model/node.py
+++ b/ironflow/model/node.py
@@ -8,18 +8,19 @@
 from typing import Callable, Optional
 
 import numpy as np
+from owlready2 import Thing
 from pyiron_atomistics import Project
 from pyiron_base import GenericJob
 from ryvencore import Node as NodeCore
 from ryvencore.Base import Event
 from ryvencore.InfoMsgs import InfoMsgs
 from ryvencore.NodePort import NodePort
-from ryvencore.NodePortBP import NodeInputBP
 from ryvencore.utils import deserialize
 
 from ironflow.gui.workflows.canvas_widgets.nodes import NodeWidget
 from ironflow.model import dtypes
-from ironflow.model.port import NodeInput, NodeOutput, NodeOutputBP
+from ironflow.model.otypes import otype_from_str
+from ironflow.model.port import NodeInput, NodeInputBP, NodeOutput, NodeOutputBP
 from ironflow.utils import display_string
 
 
@@ -156,11 +157,17 @@ def create_input(
         label: str = "",
         add_data: Optional[dict] = None,
         dtype: Optional[dtypes.DType] = None,
+        otype: Optional[Thing] = None,
         insert: Optional[int] = None,
     ):
         """Creates and add a new input port"""
         inp = NodeInput(
-            node=self, type_=type_, label_str=label, add_data=add_data, dtype=dtype
+            node=self,
+            type_=type_,
+            label_str=label,
+            add_data=add_data,
+            dtype=dtype,
+            otype=otype,
         )
         self._add_io(self.inputs, inp, insert=insert)
 
@@ -177,12 +184,25 @@ def create_output(
         type_: str = "data",
         label: str = "",
         dtype: Optional[dtypes.DType] = None,
+        otype: Optional[Thing] = None,
         insert: Optional[int] = None,
     ):
         """Create and add a new output port"""
-        out = NodeOutput(node=self, type_=type_, label_str=label, dtype=dtype)
+        out = NodeOutput(
+            node=self,
+            type_=type_,
+            label_str=label,
+            dtype=dtype,
+            otype=otype,
+        )
         self._add_io(self.outputs, out, insert=insert)
 
+    def _load_otype(self, data: dict):
+        try:
+            return otype_from_str(data["otype_namespace"], data["otype_name"])
+        except KeyError:
+            return None
+
     def setup_ports(self, inputs_data=None, outputs_data=None):
         # A streamlined version of the ryvencore method which exploits our NodeInput
         # and NodeOutput classes instead, and for which all ports have a dtype
@@ -194,11 +214,17 @@ def setup_ports(self, inputs_data=None, outputs_data=None):
                     label=inp.label,
                     add_data=inp.add_data,
                     dtype=inp.dtype,
+                    otype=inp.otype,
                 )
 
             for o in range(len(self.init_outputs)):
                 out = self.init_outputs[o]
-                self.create_output(type_=out.type_, label=out.label, dtype=out.dtype)
+                self.create_output(
+                    type_=out.type_,
+                    label=out.label,
+                    dtype=out.dtype,
+                    otype=out.otype,
+                )
 
         else:
             # load from data
@@ -209,7 +235,11 @@ def setup_ports(self, inputs_data=None, outputs_data=None):
                     _load_state=deserialize(inp["dtype state"])
                 )
                 self.create_input(
-                    type_=inp["type"], label=inp["label"], add_data=inp, dtype=dtype
+                    type_=inp["type"],
+                    label=inp["label"],
+                    add_data=inp,
+                    dtype=dtype,
+                    otype=self._load_otype(inp),
                 )
 
                 if "val" in inp:
@@ -223,11 +253,16 @@ def setup_ports(self, inputs_data=None, outputs_data=None):
                 dtype = dtypes.DType.from_str(out["dtype"])(
                     _load_state=deserialize(out["dtype state"])
                 )
-                self.create_output(type_=out["type"], label=out["label"], dtype=dtype)
+                self.create_output(
+                    type_=out["type"],
+                    label=out["label"],
+                    dtype=dtype,
+                    otype=self._load_otype(out),
+                )
 
     @property
     def all_input_is_valid(self):
-        return all([p.valid_val for p in self.inputs.ports])
+        return all(p.valid_val for p in self.inputs.ports)
 
     def place_event(self):
         # place_event() is executed *before* the connections are built
diff --git a/ironflow/model/otypes.py b/ironflow/model/otypes.py
new file mode 100644
index 00000000..c09b7907
--- /dev/null
+++ b/ironflow/model/otypes.py
@@ -0,0 +1,27 @@
+# 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.
+"""
+Provides lazy-loading of `pyiron_ontology` ontologies for de-serializing otypes.
+
+TODO: This is not easily extensible; it needs a system for users to register their
+      ontology with the session so that they can use their own otypes alongside
+      modules of their own custom nodes.
+"""
+
+
+class OTypeLoader:
+    _atomistics = None
+
+    @classmethod
+    def atomistics(cls, item):
+        # Note: public attribute name must match saved ontology namespace
+        if cls._atomistics is None:
+            from pyiron_ontology import AtomisticsOntology
+
+            cls._atomistics = AtomisticsOntology().onto
+        return cls._atomistics[item]
+
+
+def otype_from_str(namespace, item):
+    return getattr(OTypeLoader, namespace)(item)
diff --git a/ironflow/model/port.py b/ironflow/model/port.py
index 510852d1..89c9e4c0 100644
--- a/ironflow/model/port.py
+++ b/ironflow/model/port.py
@@ -10,8 +10,12 @@
 from copy import deepcopy
 from typing import Optional, TYPE_CHECKING
 
+from numpy import argwhere
 from ryvencore.NodePort import NodeInput as NodeInputCore, NodeOutput as NodeOutputCore
-from ryvencore.NodePortBP import NodeOutputBP as NodeOutputBPCore
+from ryvencore.NodePortBP import (
+    NodeOutputBP as NodeOutputBPCore,
+    NodeInputBP as NodeInputBPCore,
+)
 from ryvencore.utils import serialize
 
 from ironflow.model.dtypes import DType, Untyped
@@ -20,11 +24,30 @@
     from ironflow.model.node import Node
 
 
-class HasDType:
+class TypeHaver:
+    """
+    A parent class for the has-type classes to facilitate super calls, regardless of
+    the order these havers appear as mixins to other classes.
+    """
+
+    @property
+    def valid_val(self):
+        try:
+            other_type_checks = super().valid_val
+        except AttributeError:
+            other_type_checks = True
+        return other_type_checks
+
+
+class HasDType(TypeHaver):
     """A mixin to add the valid value check property"""
 
     @property
     def valid_val(self):
+        return self._dtype_ok and super().valid_val
+
+    @property
+    def _dtype_ok(self):
         if self.dtype is not None:
             if self.val is not None:
                 return self.dtype.valid_val(self.val)
@@ -34,7 +57,81 @@ def valid_val(self):
             return True
 
 
-class NodeInput(NodeInputCore, HasDType):
+class HasOType(TypeHaver):
+    """A mixin to add the valid value check to properties with an ontology type"""
+
+    @property
+    def valid_val(self):
+        return self._otype_ok and super().valid_val
+
+    @property
+    def _otype_ok(self):
+        if self.otype is not None:
+            if isinstance(self, NodeInput):
+                input_tree = self.otype.get_source_tree(
+                    additional_requirements=self.get_downstream_requirements()
+                )
+                return all(
+                    con.out.all_connections_found_in(input_tree)
+                    for con in self.connections
+                    if con.out.otype is not None
+                )
+            else:
+                return all(
+                    con.inp.workflow_tree_contains_connections_of(self)
+                    for con in self.connections
+                    if con.inp.otype is not None
+                )
+        else:
+            return True
+
+    def _output_graph_is_represented_in_workflow_tree(self, output_port, input_tree):
+        try:
+            output_index = argwhere(
+                [output_port.otype == source.value for source in input_tree.children]
+            )[0][0]
+            upstream_inputs = [
+                inp
+                for inp in output_port.node.inputs
+                if inp.otype is not None and len(inp.connections) > 0
+            ]
+            for usi in upstream_inputs:
+                input_branches = input_tree.children[output_index].children[0].children
+                # input/generic->outputs->function->inputs
+
+                input_index = argwhere(
+                    [usi.otype == source.value for source in input_branches]
+                )[0][0]
+
+                for con in usi.connections:
+                    if (
+                        con.out.otype is not None
+                        and not self._output_graph_is_represented_in_workflow_tree(
+                            con.out, input_branches[input_index]
+                        )
+                    ):
+                        return False
+            return True
+        except IndexError:
+            # Can't slice argwhere if it finds nothing
+            return False
+
+    def get_downstream_requirements(self):
+        downstream_requirements = []
+        for out in self.node.outputs:
+            if out.otype is not None:
+                for downstream_inp in [conn.inp for conn in out.connections]:
+                    if downstream_inp.otype is not None:
+                        downstream_requirements += (
+                            downstream_inp.get_downstream_requirements()
+                        )
+        try:
+            return self.otype.get_requirements(list(set(downstream_requirements)))
+        except AttributeError:
+            return list(set(downstream_requirements))
+
+
+class NodeInput(NodeInputCore, HasDType, HasOType):
     def __init__(
         self,
         node: Node,
@@ -42,6 +139,7 @@ def __init__(
         label_str: str = "",
         add_data: Optional[dict] = None,
         dtype: Optional[DType] = None,
+        otype=None,
     ):
         super().__init__(
             node=node,
@@ -50,6 +148,7 @@ def __init__(
             add_data=add_data if add_data is not None else {},
             dtype=Untyped() if dtype is None else deepcopy(dtype),
         )
+        self.otype = otype
 
     def _update_node(self):
         self.node.update(self.node.inputs.index(self))
@@ -68,11 +167,34 @@ def unbatch(self):
                 self.val = self.val[-1]
             self._update_node()
 
+    def data(self) -> dict:
+        data = super().data()
+
+        if self.otype is not None:
+            data["otype_namespace"] = self.otype.namespace.name
+            data["otype_name"] = self.otype.name
 
-class NodeOutput(NodeOutputCore, HasDType):
-    def __init__(self, node, type_="data", label_str="", dtype: Optional[DType] = None):
+        return data
+
+    def workflow_tree_contains_connections_of(self, port: NodeOutput):
+        tree = self.otype.get_source_tree(
+            additional_requirements=self.get_downstream_requirements()
+        )
+        return self._output_graph_is_represented_in_workflow_tree(port, tree)
+
+
+class NodeOutput(NodeOutputCore, HasDType, HasOType):
+    def __init__(
+        self,
+        node,
+        type_="data",
+        label_str="",
+        dtype: Optional[DType] = None,
+        otype=None,
+    ):
         super().__init__(node=node, type_=type_, label_str=label_str)
         self.dtype = Untyped() if dtype is None else deepcopy(dtype)
+        self.otype = otype
 
     def data(self) -> dict:
         data = super().data()
@@ -81,12 +203,41 @@ def data(self) -> dict:
             data["dtype"] = str(self.dtype)
             data["dtype state"] = serialize(self.dtype.get_state())
 
+        if self.otype is not None:
+            data["otype_namespace"] = self.otype.namespace.name
+            data["otype_name"] = self.otype.name
+
         return data
 
+    def all_connections_found_in(self, tree):
+        """
+        Checks to see if actual ontologically typed connections match with all
+        ontologically possible workflows for an input port.
+        """
+        return self._output_graph_is_represented_in_workflow_tree(self, tree)
+
+
+class NodeInputBP(NodeInputBPCore):
+    def __init__(
+        self,
+        label: str = "",
+        type_: str = "data",
+        dtype: DType = None,
+        add_data={},
+        otype=None,
+    ):
+        super().__init__(label=label, type_=type_, dtype=dtype, add_data=add_data)
+        self.otype = otype
+
 
 class NodeOutputBP(NodeOutputBPCore):
     def __init__(
-        self, label: str = "", type_: str = "data", dtype: Optional[DType] = None
+        self,
+        label: str = "",
+        type_: str = "data",
+        dtype: Optional[DType] = None,
+        otype=None,
     ):
         super().__init__(label=label, type_=type_)
         self.dtype = dtype
+        self.otype = otype
diff --git a/ironflow/node_tools/__init__.py b/ironflow/node_tools/__init__.py
index 62dbfe1d..fc4f1722 100644
--- a/ironflow/node_tools/__init__.py
+++ b/ironflow/node_tools/__init__.py
@@ -27,13 +27,15 @@
 
 import ironflow.node_tools.input_widgets
 import ironflow.node_tools.main_widgets
-from ironflow.model import dtypes, NodeInputBP
+from ironflow.model import dtypes
 from ironflow.model.node import (
     Node,
     PlaceholderWidgetsContainer,
     PortList,
+    BatchingNode,
     DataNode,
     JobMaker,
+    JobNode,
     JobTaker,
 )
-from ironflow.model.port import NodeOutputBP
+from ironflow.model.port import NodeInputBP, NodeOutputBP
diff --git a/ironflow/nodes/pyiron/atomistics_nodes.py b/ironflow/nodes/pyiron/atomistics_nodes.py
index 07cacbaa..42b78fbf 100644
--- a/ironflow/nodes/pyiron/atomistics_nodes.py
+++ b/ironflow/nodes/pyiron/atomistics_nodes.py
@@ -11,6 +11,7 @@
 import json
 import pickle
 from abc import ABC, abstractmethod
+from copy import deepcopy
 from io import BytesIO
 from typing import Type, TYPE_CHECKING
 
@@ -24,6 +25,7 @@
 from ryvencore.InfoMsgs import InfoMsgs
 
 import pyiron_base
+import pyiron_ontology
 from pyiron_atomistics import Project, Atoms
 import pyiron_atomistics.atomistics.master.murnaghan
 from pyiron_atomistics.atomistics.structure.factory import StructureFactory
@@ -40,6 +42,7 @@
     DataNode,
     dtypes,
     JobMaker,
+    JobNode,
     JobTaker,
     main_widgets,
     Node,
@@ -54,6 +57,8 @@
 
 STRUCTURE_FACTORY = StructureFactory()
 NUMERIC_TYPES = [int, float, np.number]
+ONTO = pyiron_ontology.AtomisticsOntology().onto
+REASONER = pyiron_ontology.AtomisticsReasoner(ONTO)
 
 
 class BeautifulHasGroups:
@@ -104,7 +109,11 @@ class Project_Node(DataNode):
         NodeInputBP(dtype=dtypes.String(default="."), label="name"),
     ]
     init_outputs = [
-        NodeOutputBP(label="project", dtype=dtypes.Data(valid_classes=Project)),
+        NodeOutputBP(
+            label="project",
+            dtype=dtypes.Data(valid_classes=Project),
+            otype=ONTO.project_output_atomistics_project,
+        ),
     ]
     color = "#aabb44"
 
@@ -129,7 +138,11 @@ class JobTable_Node(Node):
     title = "JobTable"
     init_inputs = [
         NodeInputBP(type_="exec", label="refresh"),
-        NodeInputBP(dtype=dtypes.Data(valid_classes=Project), label="project"),
+        NodeInputBP(
+            dtype=dtypes.Data(valid_classes=Project),
+            label="project",
+            # otype=ONTO...  # Needs an individual of type Input with generic Project
+        ),
     ]
     init_outputs = [NodeOutputBP(label="Table")]
     color = "#aabb44"
@@ -188,7 +201,11 @@ class BulkStructure_Node(OutputsOnlyAtoms):
 
     title = "BulkStructure"
     init_inputs = [
-        NodeInputBP(dtype=dtypes.String(default="Fe"), label="element"),
+        NodeInputBP(
+            label="element",
+            dtype=dtypes.String(default="Fe"),
+            otype=ONTO.bulk_structure_input_element,
+        ),
         NodeInputBP(
             dtype=dtypes.Choice(
                 default=None,
@@ -219,6 +236,14 @@ class BulkStructure_Node(OutputsOnlyAtoms):
         NodeInputBP(dtype=dtypes.Boolean(default=False), label="cubic"),
     ]
 
+    init_outputs = [
+        NodeOutputBP(
+            label="structure",
+            dtype=dtypes.Data(valid_classes=Atoms),
+            otype=ONTO.bulk_structure_output_structure,
+        ),
+    ]
+
     def node_function(
         self,
         element,
@@ -245,6 +270,106 @@ def node_function(
         }
 
 
+class SlabStructure_Node(OutputsOnlyAtoms):
+    """
+    Generate a surface based on the ase.build.surface module.
+
+    Args:
+        element (str): The atomic symbol for the desired atoms. (Default is "Fe".)
+        surface_type (str): The string specifying the surface type generators available
+            through ase (fcc111, hcp0001 etc.)
+        size (tuple): Three-tuple of integers to give size (repetitions) of the surface
+        vacuum (float): Length of vacuum layer added to the surface along the z
+            direction
+        center (bool): Tells if the surface layers have to be at the center or at one
+            end along the z-direction.
+        orthogonal (bool): Construct orthogonal cell.
+        a (float | None): Lattice constant.
+
+    Returns:
+        pyiron_atomistics.atomistics.structure.atoms.Atoms instance: Requested surface
+    """
+
+    # this __doc__ string will be displayed as tooltip in the editor
+
+    title = "SlabStructure"
+    init_inputs = [
+        NodeInputBP(
+            label="element",
+            dtype=dtypes.String(default="Fe"),
+            otype=ONTO["CreateStructureBulk/input/element"],
+        ),
+        NodeInputBP(
+            label="surface_type",
+            dtype=dtypes.Choice(
+                default="bcc100",
+                items=[
+                    # "add_adsorbate",
+                    # "add_vacuum",
+                    "bcc100",
+                    "bcc110",
+                    "bcc111",
+                    "diamond100",
+                    "diamond111",
+                    "fcc100",
+                    "fcc110",
+                    "fcc111",
+                    "fcc211",
+                    "hcp0001",
+                    "hcp10m10",
+                    # "mx2",
+                    # "hcp0001_root",
+                    # "fcc111_root",
+                    # "bcc111_root",
+                    # "root_surface",
+                    # "root_surface_analysis",
+                    # "ase_surf",
+                ],
+                allow_none=True,
+            ),
+        ),
+        NodeInputBP(label="size_a", dtype=dtypes.Integer(default=1)),
+        NodeInputBP(label="size_b", dtype=dtypes.Integer(default=1)),
+        NodeInputBP(label="size_c", dtype=dtypes.Integer(default=1)),
+        NodeInputBP(label="vacuum", dtype=dtypes.Float(10.0)),
+        NodeInputBP(label="center", dtype=dtypes.Boolean(default=False)),
+        NodeInputBP(label="orthogonal", dtype=dtypes.Boolean(default=True)),
+        NodeInputBP(dtype=dtypes.Float(default=None, allow_none=True), label="a"),
+    ]
+
+    init_outputs = [
+        NodeOutputBP(
+            label="structure",
+            dtype=dtypes.Data(valid_classes=Atoms),
+            otype=ONTO.surface_structure_output_structure,
+        ),
+    ]
+
+    def node_function(
+        self,
+        element,
+        surface_type,
+        size_a,
+        size_b,
+        size_c,
+        vacuum,
+        center,
+        orthogonal,
+        a,
+    ) -> dict:
+        return {
+            "structure": STRUCTURE_FACTORY.surface(
+                element=element,
+                surface_type=surface_type,
+                size=(size_a, size_b, size_c),
+                vacuum=vacuum,
+                center=center,
+                orthogonal=orthogonal,
+                a=a,
+            )
+        }
+
+
 class Repeat_Node(OutputsOnlyAtoms):
     """
     Repeat atomic structure supercell.
@@ -295,17 +420,24 @@ class AtomisticTaker(JobTaker, ABC):
     valid_job_classes = [Lammps]
     init_outputs = JobTaker.init_outputs + [
         NodeOutputBP(
-            dtype=dtypes.List(valid_classes=[float, np.floating]), label="energy_pot"
+            label="energy_pot",
+            dtype=dtypes.Float(),
+            otype=ONTO.atomistic_taker_output_energy_pot,
         ),
         NodeOutputBP(
-            dtype=dtypes.List(valid_classes=[float, np.floating]), label="forces"
+            label="forces",
+            dtype=dtypes.List(valid_classes=[float, np.floating]),
+            # Still not working because it's an nx3 matrix, not an n-long list
+            otype=ONTO.atomistic_taker_output_forces,
         ),
     ]
+    init_inputs = deepcopy(JobTaker.init_inputs)
+    init_inputs[3].otype = ONTO.atomistic_taker_job
 
     def _get_output_from_job(self, finished_job: Lammps, **kwargs):
         return {
-            "energy_pot": finished_job.output.energy_pot,
-            "forces": finished_job.output.forces,
+            "energy_pot": finished_job.output.energy_pot[-1],
+            "forces": finished_job.output.forces[-1],
         }
 
     @property
@@ -436,13 +568,20 @@ def _modify_job(
         return copied_job
 
 
-class CalcMurnaghan_Node(JobMaker):
+class CalcMurnaghan_Node(JobNode):
     title = "CalcMurnaghan"
     valid_job_classes = [pyiron_atomistics.atomistics.master.murnaghan.Murnaghan]
 
-    init_inputs = list(JobMaker.init_inputs) + [
+    init_inputs = list(JobNode.init_inputs) + [
         NodeInputBP(
-            label="engine", dtype=dtypes.Data(valid_classes=AtomisticGenericJob)
+            label="project",
+            dtype=dtypes.Data(valid_classes=Project),
+            otype=ONTO.murnaghan_input_project,
+        ),
+        NodeInputBP(
+            label="engine",
+            dtype=dtypes.Data(valid_classes=AtomisticGenericJob),
+            otype=ONTO.murnaghan_input_job,
         ),
         NodeInputBP(label="num_points", dtype=dtypes.Integer(default=11)),
         NodeInputBP(
@@ -467,8 +606,16 @@ class CalcMurnaghan_Node(JobMaker):
     init_outputs = list(JobMaker.init_outputs) + [
         NodeOutputBP(label="eq_energy", dtype=dtypes.Float()),
         NodeOutputBP(label="eq_volume", dtype=dtypes.Float()),
-        NodeOutputBP(label="eq_bulk_modulus", dtype=dtypes.Float()),
-        NodeOutputBP(label="eq_b_prime", dtype=dtypes.Float()),
+        NodeOutputBP(
+            label="eq_bulk_modulus",
+            dtype=dtypes.Float(),
+            otype=ONTO.murnaghan_output_bulk_modulus,
+        ),
+        NodeOutputBP(
+            label="eq_b_prime",
+            dtype=dtypes.Float(),
+            otype=ONTO.murnaghan_output_b_prime,
+        ),
         NodeOutputBP(label="volumes", dtype=dtypes.List(valid_classes=float)),
         NodeOutputBP(label="energies", dtype=dtypes.List(valid_classes=float)),
     ]
@@ -507,6 +654,50 @@ def _get_output_from_job(
         }
 
 
+class SurfaceEnergy_Node(DataNode):
+    title = "SurfaceEnergy"
+
+    init_inputs = [
+        NodeInputBP(
+            dtype=dtypes.Data(valid_classes=Atoms),
+            label="bulk_structure",
+            otype=ONTO.surface_energy_input_bulk_structure,
+        ),
+        NodeInputBP(
+            dtype=dtypes.Float(),
+            label="bulk_energy",
+            otype=ONTO.surface_energy_input_bulk_energy,
+        ),
+        NodeInputBP(
+            dtype=dtypes.Data(valid_classes=Atoms),
+            label="surface_structure",
+            otype=ONTO.surface_energy_input_slab_structure,
+        ),
+        NodeInputBP(
+            dtype=dtypes.Float(),
+            label="surface_energy",
+            otype=ONTO.surface_energy_input_slab_energy,
+        ),
+    ]
+    init_outputs = [
+        NodeOutputBP(
+            label="surface_energy",
+            dtype=dtypes.Float(),
+            otype=ONTO.surface_energy_output_surface_energy,
+        ),
+    ]
+
+    def node_function(
+        self, bulk_structure, bulk_energy, surface_structure, surface_energy, **kwargs
+    ) -> dict:
+        n_bulk = len(bulk_structure)
+        n_surface = len(surface_structure)
+        energy_difference = surface_energy - (n_surface / n_bulk) * bulk_energy
+        a, b, c = surface_structure.cell.array
+        area = np.dot(np.cross(a, b), c / np.linalg.norm(c))
+        return {"surface_energy": energy_difference / (2 * area)}
+
+
 class PyironTable_Node(JobMaker):
     title = "PyironTable"
     valid_job_classes = [pyiron_base.TableJob]
@@ -564,8 +755,16 @@ class Lammps_Node(Engine):
     title = "Lammps"
     version = "v0.2"
     init_inputs = [
-        NodeInputBP(dtype=dtypes.Data(valid_classes=Project), label="project"),
-        NodeInputBP(dtype=dtypes.Data(valid_classes=Atoms), label="structure"),
+        NodeInputBP(
+            dtype=dtypes.Data(valid_classes=Project),
+            label="project",
+            otype=ONTO.lammps_input_project,
+        ),
+        NodeInputBP(
+            label="structure",
+            dtype=dtypes.Data(valid_classes=Atoms),
+            otype=ONTO.lammps_input_structure,
+        ),
         NodeInputBP(
             dtype=dtypes.Choice(
                 default=None,
@@ -576,7 +775,11 @@ class Lammps_Node(Engine):
         ),
     ]
     init_outputs = [
-        NodeOutputBP(label="engine", dtype=dtypes.Data(valid_classes=Lammps)),
+        NodeOutputBP(
+            label="engine",
+            dtype=dtypes.Data(valid_classes=Lammps),
+            otype=ONTO.lammps_output_job,
+        ),
     ]
 
     def _get_potentials(self):
@@ -1157,6 +1360,39 @@ def update_event(self, inp=-1):
         self.exec_output(0)
 
 
+class MaterialProperty_Node(DataNode):
+    title = "MaterialProperty"
+
+    init_inputs = [
+        NodeInputBP(
+            label="property",
+            dtype=dtypes.Choice(
+                items=[o.name for o in ONTO.MaterialProperty.descendants()],
+                valid_classes=str,
+                default="MaterialProperty",
+            ),
+        ),
+        NodeInputBP(label="source", dtype=dtypes.Float(default=None), otype=None),
+    ]
+
+    init_outputs = [NodeOutputBP(label="value", dtype=dtypes.Float(), otype=None)]
+
+    def _update_otypes(self):
+        otype = getattr(ONTO, self.inputs.values.property)
+        self.inputs.ports.source.otype = otype()
+        self.outputs.ports.value.otype = otype()
+
+    def update_event(self, inp=-1):
+        if inp == 0:
+            self._update_otypes()
+        super().update_event(inp=inp)
+
+    def node_function(self, property, source, *args, **kwargs) -> dict:
+        # upstream_otype = self.inputs.ports.source.connections[0].out.otype
+        # conversion = REASONER.convert_unit(upstream_otype)
+        return {"value": source}  # * conversion if source is not None else None}
+
+
 nodes = [
     Project_Node,
     BulkStructure_Node,
diff --git a/notebooks/bulk_modulus.ipynb b/notebooks/bulk_modulus.ipynb
new file mode 100644
index 00000000..effa52d8
--- /dev/null
+++ b/notebooks/bulk_modulus.ipynb
@@ -0,0 +1,171 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "d617da0f-740a-4635-8299-99691418ba9b",
+   "metadata": {},
+   "source": [
+    "Here, we will demonstrate how we can use ontological typing for guided workflow design.\n",
+    "\n",
+    "This is an interactive example, with a series of steps to follow so that you can see how the guidance process works. At the end, there is a serialized workflow you can load for comparison"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "970367bb-6922-48da-b193-91b8aac6afcf",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from ironflow import GUI"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "485e37a9-8f8b-4451-b4ab-da2c6333bd47",
+   "metadata": {},
+   "source": [
+    "# Steps\n",
+    "\n",
+    "1. Start up the GUI\n",
+    "2. Select `MaterialProperty` from the `pyiron_atomistics` node list and add it to the graph.\n",
+    "3. Click on the new node to open its node controller, and select `Bulk_modulus` from the `property` drop-down menu.\n",
+    "4. Click on the node-selector dropdown menu and change it from `atomistics_nodes` to `recommended`\n",
+    "5. Click on the red `source` port on the `MaterialProperty` node\n",
+    "   - Obeserve: The list of recommended ports is now populated by a node that has appropriate output for this port!\n",
+    "6. Double-click to place the recommended node on the graph.\n",
+    "7. Click on the `MaterialProperty` node `source` input port again.\n",
+    "   - Observe: The corresponding port on `CalcMurnaghan` that has ontologically-valid output is highlighted white!\n",
+    "8. Connect `source` to the highlighted `eq_bulk_modulus` port.\n",
+    "   - Observe: The port status is still red. This connection is permissible by the ports' respective data types, and recommended by their respective ontological types, but our `CalcMurnaghan` node still has not been run, so the actual _values_ of these ports are still bad, hence the red color-coding.\n",
+    "9. Starting with the `engine` input for `CalcMurnaghan`, follow the same process again:\n",
+    "   1. Click the port to populate the recommended node list\n",
+    "   2. Place a recommended node\n",
+    "   3. Click the port to highlight where on the newly place node you should connect it to.\n",
+    "   - Repeat this for engine &rarr; Lammps&rarr;engine; structure&rarr;BulkStructure&rarr;structure, and project (for both the Murnaghan and Lammps nodes)\n",
+    "   - Observe: Without knowing a-priori which nodes we need to use, we simply follow recommendations to populate our graph\n",
+    "   - Observe: When `Lammps.outputs.engine` is connected to `CalcMurnaghan.inputs.engine`, the only recommendation for `Lammps.inputs.structure` is `BulkStructure` -- but when we disconnect these, Lammps sees both `BulkStructure` _and_ `SlabStructure`! This is because the requirements of the Murnaghan job to be calculated on a bulk structure get propogated upstream by the graph and processed by the ontology reasoner. \n",
+    "11. Run the `CalcMurnaghan` job\n",
+    "   - Observe: If you use `SHOW` to investigate the `CalcMurnaghan` and `MaterialProperty` node outputs, you'll see that the bulk modulus gets converted from the Murnaghan units of GPa to the ontology's units of MPa.\n",
+    "12. Select `MaterialProperty` again, and choose `B_prime` from the `property` dropdown.\n",
+    "   - Observe: The `eq_bulk_modulus` output of `CalcMurnaghan` still has a green (valid) output port, but the `source` input port of `MaterialProperty` is now red (invalid) -- this is because the provided ontological type no longer matches the requested property.\n",
+    "13. Click on the `source` input port again; disconnect it from `eq_bulk_modulus` and reconnect it to the recommended port."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "0d1ce2a3-acd0-4729-ada5-339bf0b14215",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "gui = GUI('live_bulk_modulus')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "4ec38f26-e2ee-45fb-a65b-65e239b587da",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "gui.draw()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "19f12818-c081-494c-af17-7dd5cbdbbd1b",
+   "metadata": {},
+   "source": [
+    "This process was pretty simple, since there was only one node choice for each step. \n",
+    "\n",
+    "To see the benefit of the ontology, select the `Lammps` node, un-comment and execute the cells below:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "4b7ab514-1411-4df4-9094-e41ebe04ea47",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# gui.selected_node.inputs.ports.structure.get_downstream_requirements()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9cff47f4-4194-4cc9-aa92-c896400c46ed",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# gui.selected_node.inputs.ports.structure.get_downstream_requirements()[0].is_a"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "addf541e-8b9e-4d7f-b0ba-93190cc93677",
+   "metadata": {},
+   "source": [
+    "You should see that the structure input has inherited a condition. This is an individual from the ontology; the name may be automatically generated and so not very meaningful, but we can always look at the `is_a` field for any individual to see its parents (extremely similar to parent classes). In this case, we see that the requirement is a structure that is bulk and 3D! Of course Lammps can take almost any type of atomic structure; this requirement that the input here be bulk-like was passed down through the graph from the Murnaghan node. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "19bd09bd-887d-4145-a8a2-54ba7dd1efa8",
+   "metadata": {},
+   "source": [
+    "While this particular example is fairly simple, we hope it demonstrates the power of ontologically guided workflow design."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a9c326a8-2c90-49d5-986f-214a6eb874af",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pre_made = GUI('premade_bulk_modulus')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "08484123-4bdd-446e-9a9a-9ecaff285b6a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pre_made.draw()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "d9a8d5f0-d5b8-4a73-ae7a-e362bf7d69f8",
+   "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"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/premade_bulk_modulus.json b/notebooks/premade_bulk_modulus.json
new file mode 100644
index 00000000..199a1219
--- /dev/null
+++ b/notebooks/premade_bulk_modulus.json
@@ -0,0 +1,402 @@
+{
+    "scripts": [
+        {
+            "title": "script_0",
+            "variables": {},
+            "flow": {
+                "algorithm mode": "data",
+                "nodes": [
+                    {
+                        "identifier": "MaterialProperty_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {},
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "property",
+                                "GID": 9,
+                                "val": "gASVDwAAAAAAAACMC0J1bGtNb2R1bHVzlC4=",
+                                "dtype": "DType.Choice",
+                                "dtype state": "gASVsQAAAAAAAAB9lCiMB2RlZmF1bHSUjBBNYXRlcmlhbFByb3BlcnR5lIwDdmFslGgCjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlIwIYnVpbHRpbnOUjANzdHKUk5RhjAphbGxvd19ub25llImMB2JhdGNoZWSUiYwFaXRlbXOUXZQoaAKMDVN1cmZhY2VFbmVyZ3mUjAtCdWxrTW9kdWx1c5SMBkJQcmltZZRldS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "source",
+                                "GID": 10,
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVmQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjAVmbG9hdJSTlIwFbnVtcHmUjAhmbG9hdGluZ5STlGWMCmFsbG93X25vbmWUiYwHYmF0Y2hlZJSJjARzaXpllIwBbZSMCGRlY2ltYWxzlEsKdS4=",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "bulkmodulus2"
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "value",
+                                "GID": 11,
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURwAAAAAAAAAAjAN2YWyURwAAAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu"
+                            }
+                        ],
+                        "GID": 8,
+                        "pos x": 1404.4182452927373,
+                        "pos y": 282.14285714285717
+                    },
+                    {
+                        "identifier": "CalcMurnaghan_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {},
+                        "inputs": [
+                            {
+                                "type": "exec",
+                                "label": "run",
+                                "GID": 13,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Untyped",
+                                "dtype state": "gASVVQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAphbGxvd19ub25llIiMB2JhdGNoZWSUiXUu"
+                            },
+                            {
+                                "type": "exec",
+                                "label": "remove",
+                                "GID": 14,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Untyped",
+                                "dtype state": "gASVVQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAphbGxvd19ub25llIiMB2JhdGNoZWSUiXUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "name",
+                                "GID": 15,
+                                "val": "gASVCAAAAAAAAACMBGNhbGOULg==",
+                                "dtype": "DType.String",
+                                "dtype state": "gASVjQAAAAAAAAB9lCiMB2RlZmF1bHSUjARjYWxjlIwDdmFslGgCjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwDc3RylJOUjAVudW1weZSMBHN0cl+Uk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UdS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "project",
+                                "GID": 16,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASViQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjBlweWlyb25fYXRvbWlzdGljcy5wcm9qZWN0lIwHUHJvamVjdJSTlGGMCmFsbG93X25vbmWUiYwHYmF0Y2hlZJSJjARzaXpllIwBbZR1Lg==",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "murnaghan_input_project"
+                            },
+                            {
+                                "type": "data",
+                                "label": "engine",
+                                "GID": 17,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVpgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjCpweWlyb25fYXRvbWlzdGljcy5hdG9taXN0aWNzLmpvYi5hdG9taXN0aWOUjBNBdG9taXN0aWNHZW5lcmljSm9ilJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "murnaghan_input_job"
+                            },
+                            {
+                                "type": "data",
+                                "label": "num_points",
+                                "GID": 18,
+                                "val": "gARLCy4=",
+                                "dtype": "DType.Integer",
+                                "dtype state": "gASViwAAAAAAAAB9lCiMB2RlZmF1bHSUSwuMA3ZhbJRLC4wDZG9jlIwAlIwGYm91bmRzlE6MDXZhbGlkX2NsYXNzZXOUXZQojAhidWlsdGluc5SMA2ludJSTlIwFbnVtcHmUjAdpbnRlZ2VylJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "fit_type",
+                                "GID": 19,
+                                "val": "gASVDgAAAAAAAACMCnBvbHlub21pYWyULg==",
+                                "dtype": "DType.Choice",
+                                "dtype state": "gASVsAAAAAAAAAB9lCiMB2RlZmF1bHSUjApwb2x5bm9taWFslIwDdmFslGgCjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlIwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBWl0ZW1zlF2UKGgCjAViaXJjaJSMDmJpcmNobXVybmFnaGFulIwJbXVybmFnaGFulIwQcG91cmllcnRhcmFudG9sYZSMBXZpbmV0lGV1Lg=="
+                            },
+                            {
+                                "type": "data",
+                                "label": "fit_order",
+                                "GID": 20,
+                                "val": "gARLAy4=",
+                                "dtype": "DType.Integer",
+                                "dtype state": "gASViwAAAAAAAAB9lCiMB2RlZmF1bHSUSwOMA3ZhbJRLA4wDZG9jlIwAlIwGYm91bmRzlE6MDXZhbGlkX2NsYXNzZXOUXZQojAhidWlsdGluc5SMA2ludJSTlIwFbnVtcHmUjAdpbnRlZ2VylJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "vol_range_fraction",
+                                "GID": 21,
+                                "val": "gASVCgAAAAAAAABHP7mZmZmZmZou",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURz+5mZmZmZmajAN2YWyURz+5mZmZmZmajANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu"
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "exec",
+                                "label": "ran",
+                                "GID": 22,
+                                "dtype": "DType.Untyped",
+                                "dtype state": "gASVVQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAphbGxvd19ub25llIiMB2JhdGNoZWSUiXUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "job",
+                                "GID": 23,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVnwAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjC1weWlyb25fYXRvbWlzdGljcy5hdG9taXN0aWNzLm1hc3Rlci5tdXJuYWdoYW6UjAlNdXJuYWdoYW6Uk5RhjAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UdS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "eq_energy",
+                                "GID": 24,
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURwAAAAAAAAAAjAN2YWyURwAAAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "eq_volume",
+                                "GID": 25,
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURwAAAAAAAAAAjAN2YWyURwAAAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "eq_bulk_modulus",
+                                "GID": 26,
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURwAAAAAAAAAAjAN2YWyURwAAAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "murnaghan_output_bulk_modulus"
+                            },
+                            {
+                                "type": "data",
+                                "label": "eq_b_prime",
+                                "GID": 27,
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURwAAAAAAAAAAjAN2YWyURwAAAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "murnaghan_output_b_prime"
+                            },
+                            {
+                                "type": "data",
+                                "label": "volumes",
+                                "GID": 28,
+                                "dtype": "DType.List",
+                                "dtype state": "gASVawAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAhidWlsdGluc5SMBWZsb2F0lJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklIl1Lg=="
+                            },
+                            {
+                                "type": "data",
+                                "label": "energies",
+                                "GID": 29,
+                                "dtype": "DType.List",
+                                "dtype state": "gASVawAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAhidWlsdGluc5SMBWZsb2F0lJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklIl1Lg=="
+                            }
+                        ],
+                        "GID": 12,
+                        "pos x": 1059.3830481314028,
+                        "pos y": 194.23076923076928
+                    },
+                    {
+                        "identifier": "Project_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {},
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "name",
+                                "GID": 31,
+                                "val": "gASVGAAAAAAAAACMFHByZW1hZGVfYnVsa19tb2R1bHVzlC4=",
+                                "dtype": "DType.String",
+                                "dtype state": "gASVigAAAAAAAAB9lCiMB2RlZmF1bHSUjAEulIwDdmFslGgCjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwDc3RylJOUjAVudW1weZSMBHN0cl+Uk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UdS4="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "project",
+                                "GID": 32,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASViQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjBlweWlyb25fYXRvbWlzdGljcy5wcm9qZWN0lIwHUHJvamVjdJSTlGGMCmFsbG93X25vbmWUiYwHYmF0Y2hlZJSJjARzaXpllIwBbZR1Lg==",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "project_output_atomistics_project"
+                            }
+                        ],
+                        "GID": 30,
+                        "pos x": 255.0334802266354,
+                        "pos y": 88.73626373626378
+                    },
+                    {
+                        "identifier": "Lammps_Node",
+                        "version": "v0.2",
+                        "state data": "gAR9lC4=",
+                        "additional data": {},
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "project",
+                                "GID": 34,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASViQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjBlweWlyb25fYXRvbWlzdGljcy5wcm9qZWN0lIwHUHJvamVjdJSTlGGMCmFsbG93X25vbmWUiYwHYmF0Y2hlZJSJjARzaXpllIwBbZR1Lg==",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "lammps_input_project"
+                            },
+                            {
+                                "type": "data",
+                                "label": "structure",
+                                "GID": 35,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVmgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjCxweWlyb25fYXRvbWlzdGljcy5hdG9taXN0aWNzLnN0cnVjdHVyZS5hdG9tc5SMBUF0b21zlJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "lammps_input_structure"
+                            },
+                            {
+                                "type": "data",
+                                "label": "potential",
+                                "GID": 36,
+                                "val": "gASVJwAAAAAAAACMIzE5OTctLUFja2xhbmQtRy1KLS1GZS0tTEFNTVBTLS1pcHIxlC4=",
+                                "dtype": "DType.Choice",
+                                "dtype state": "gASVLxkAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAhidWlsdGluc5SMA3N0cpSTlGGMCmFsbG93X25vbmWUiYwHYmF0Y2hlZJSJjAVpdGVtc5RdlCiMIzE5OTctLUFja2xhbmQtRy1KLS1GZS0tTEFNTVBTLS1pcHIxlIwfMTk5OC0tTWV5ZXItUi0tRmUtLUxBTU1QUy0taXByMZSMHzIwMDEtLUxlZS1CLUotLUZlLS1MQU1NUFMtLWlwcjGUjCIyMDAxLS1MZWUtQi1KLS1GZS1Dci0tTEFNTVBTLS1pcHIxlIwmMjAwMy0tTWVuZGVsZXYtTS1JLS1GZS0yLS1MQU1NUFMtLWlwcjOUjCYyMDAzLS1NZW5kZWxldi1NLUktLUZlLTUtLUxBTU1QUy0taXByMZSMJTIwMDQtLUFja2xhbmQtRy1KLS1GZS1QLS1MQU1NUFMtLWlwcjGUjCAyMDA0LS1aaG91LVgtVy0tRmUtLUxBTU1QUy0taXByMpSMIjIwMDUtLUxlZS1CLUotLUZlLUN1LS1MQU1NUFMtLWlwcjGUjCcyMDA1LS1NZW5kZWxldi1NLUktLUFsLUZlLS1MQU1NUFMtLWlwcjGUjCEyMDA2LS1DaGFtYXRpLUgtLUZlLS1MQU1NUFMtLWlwcjGUjCAyMDA2LS1LaW0tSi0tRmUtUHQtLUxBTU1QUy0taXByMZSMITIwMDYtLUxlZS1CLUotLUZlLUMtLUxBTU1QUy0taXByMZSMITIwMDYtLUxlZS1CLUotLUZlLU4tLUxBTU1QUy0taXByMZSMITIwMDctLUxlZS1CLUotLUZlLUgtLUxBTU1QUy0taXByMZSMJjIwMDctLU1lbmRlbGV2LU0tSS0tVi1GZS0tTEFNTVBTLS1pcHIxlIwlMjAwOC0tSGVwYnVybi1ELUotLUZlLUMtLUxBTU1QUy0taXByMZSMHzIwMDgtLVNhLUktLUZlLU5iLS1MQU1NUFMtLWlwcjGUjB8yMDA4LS1TYS1JLS1GZS1UaS0tTEFNTVBTLS1pcHIxlIwlMjAwOS0tQm9ubnktRy0tRmUtQ3UtTmktLUxBTU1QUy0taXByMZSMIjIwMDktLUJvbm55LUctLUZlLU5pLS1MQU1NUFMtLWlwcjGUjCQyMDA5LS1LaW0tSC1LLS1GZS1UaS1DLS1MQU1NUFMtLWlwcjKUjCIyMDA5LS1LaW0tWS1NLS1GZS1Nbi0tTEFNTVBTLS1pcHIxlIwkMjAwOS0tT2xzc29uLVAtQS1ULS1GZS0tTEFNTVBTLS1pcHIxlIwmMjAwOS0tU3R1a293c2tpLUEtLUZlLUNyLS1MQU1NUFMtLWlwcjGUjCQyMDEwLS1LaW0tSC1LLS1GZS1OYi1DLS1MQU1NUFMtLWlwcjGUjCAyMDEwLS1MZWUtRS0tRmUtQWwtLUxBTU1QUy0taXByMZSMITIwMTAtLU1hbGVyYmEtTC0tRmUtLUxBTU1QUy0taXByMZSMIjIwMTEtLUJvbm55LUctLUZlLUNyLS1MQU1NUFMtLWlwcjKUjCIyMDExLS1Cb25ueS1HLS1GZS1Dci0tTEFNTVBTLS1pcHIzlIwlMjAxMS0tQm9ubnktRy0tRmUtTmktQ3ItLUxBTU1QUy0taXByMZSMJTIwMTEtLUJvbm55LUctLUZlLU5pLUNyLS1MQU1NUFMtLWlwcjKUjCMyMDExLS1DaGllc2EtUy0tRmUtMzMtLUxBTU1QUy0taXByMZSMLTIwMTItLUplbGluZWstQi0tQWwtU2ktTWctQ3UtRmUtLUxBTU1QUy0taXByMpSMIDIwMTItLUtvLVctUy0tRmUtUC0tTEFNTVBTLS1pcHIxlIwiMjAxMi0tUHJvdmlsbGUtTC0tRmUtLUxBTU1QUy0taXByMZSMJDIwMTMtLUJvbm55LUctLUZlLUNyLVctLUxBTU1QUy0taXByMpSMJDIwMTMtLUJvbm55LUctLUZlLUNyLVctLUxBTU1QUy0taXByM5SMJTIwMTMtLUJvbm55LUctLUZlLU5pLUNyLS1MQU1NUFMtLWlwcjGUjCEyMDEzLS1Cb25ueS1HLS1GZS1XLS1MQU1NUFMtLWlwcjGUjCoyMDEzLS1IZW5yaWtzc29uLUstTy1FLS1GZS1DLS1MQU1NUFMtLWlwcjGUjCgyMDE0LS1MaXlhbmFnZS1MLVMtSS0tRmUtQy0tTEFNTVBTLS1pcHIylIwfMjAxNS0tQXNhZGktRS0tRmUtLUxBTU1QUy0taXByMZSMIzIwMTUtLUVpY2gtUy1NLS1GZS1Dci0tTEFNTVBTLS1pcHIxlIwoMjAxNy0tQmVsYW5kLUwtSy0tRmUtTmktQ3ItLUxBTU1QUy0taXByMZSMIzIwMTctLUNob2ktVy1NLS1Dby1GZS0tTEFNTVBTLS1pcHIxlIwiMjAxNy0tV3UtQy0tTmktQ3ItRmUtLUxBTU1QUy0taXByMZSMHzIwMTctLVd1LUMtLU5pLUZlLS1MQU1NUFMtLWlwcjGUjCwyMDE4LS1DaG9pLVctTS0tQ28tTmktQ3ItRmUtTW4tLUxBTU1QUy0taXByMZSMIzIwMTgtLUV0ZXNhbWktUy1BLS1GZS0tTEFNTVBTLS1pcHIxlIwsMjAxOC0tRmFya2FzLUQtLUZlLU5pLUNyLUNvLUN1LS1MQU1NUFMtLWlwcjKUjCQyMDE4LS1KZW9uZy1HLVUtLVBkLUZlLS1MQU1NUFMtLWlwcjGUjCYyMDE4LS1aaG91LVgtVy0tRmUtTmktQ3ItLUxBTU1QUy0taXByMZSMJjIwMTgtLVpob3UtWC1XLS1GZS1OaS1Dci0tTEFNTVBTLS1pcHIylIwnMjAxOS0tQXNsYW0tSS0tRmUtTW4tU2ktQy0tTEFNTVBTLS1pcHIxlIwmMjAxOS0tQnlnZ21hc3Rhci1KLS1GZS1PLS1MQU1NUFMtLWlwcjGUjCoyMDE5LS1NZW5kZWxldi1NLUktLUZlLU5pLUNyLS1MQU1NUFMtLWlwcjGUjCQyMDIwLS1CeWdnbWFzdGFyLUotLUZlLS1MQU1NUFMtLWlwcjGUjCwyMDIwLS1GYXJrYXMtRC0tRmUtTmktQ3ItQ28tQWwtLUxBTU1QUy0taXByMZSMLDIwMjAtLUdyb2dlci1SLS1Dby1Dci1GZS1Nbi1OaS0tTEFNTVBTLS1pcHIxlIweMjAyMC0tTW9yaS1ILS1GZS0tTEFNTVBTLS1pcHIxlIwvMjAyMS0tRGVsdWlnaS1PLVItLUZlLU5pLUNyLUNvLUN1LS1MQU1NUFMtLWlwcjGUjCIyMDIxLS1TdGFyaWtvdi1TLS1GZS0tTEFNTVBTLS1pcHIxlIwiMjAyMS0tU3Rhcmlrb3YtUy0tRmUtLUxBTU1QUy0taXByMpSMHzIwMjEtLVdlbi1NLS1GZS1ILS1MQU1NUFMtLWlwcjGUjCMyMDIyLS1NYWhhdGEtQS0tQWwtRmUtLUxBTU1QUy0taXByMZSMJzIwMjItLVN0YXJpa292LVMtLUZlLUNyLUgtLUxBTU1QUy0taXByMZSMHTIwMjItLVN1bi1ZLS1GZS0tTEFNTVBTLS1pcHIxlIw6RUFNX0R5bmFtb19BY2tsYW5kQmFjb25DYWxkZXJfMTk5N19GZV9fTU9fMTQyNzk5NzE3NTE2XzAwNZSMQUVBTV9EeW5hbW9fQWNrbGFuZE1lbmRlbGV2U3JvbG92aXR6XzIwMDRfRmVQX19NT184ODQzNDMxNDYzMTBfMDA1lIw7RUFNX0R5bmFtb19Cb25ueUNhc3RpbkJ1bGxlbnNfMjAxM19GZVdfX01PXzczNzU2NzI0MjYzMV8wMDCUjEBFQU1fRHluYW1vX0Jvbm55Q2FzdGluVGVyZW50eWV2XzIwMTNfRmVOaUNyX19NT183NjMxOTc5NDEwMzlfMDAwlIw/RUFNX0R5bmFtb19Cb25ueVBhc2lhbm90Q2FzdGluXzIwMDlfRmVDdU5pX19NT180NjkzNDM5NzMxNzFfMDA1lIw+RUFNX0R5bmFtb19Cb25ueVBhc2lhbm90TWFsZXJiYV8yMDA5X0ZlTmlfX01PXzI2NzcyMTQwODkzNF8wMDWUjEFFQU1fRHluYW1vX0NoYW1hdGlQYXBhbmljb2xhb3VNaXNoaW5fMjAwNl9GZV9fTU9fOTYwNjk5NTEzNDI0XzAwMJSMN0VBTV9EeW5hbW9fSGVwYnVybkFja2xhbmRfMjAwOF9GZUNfX01PXzE0Mzk3NzE1MjcyOF8wMDWUjDBFQU1fRHluYW1vX01hcmluaWNhXzIwMDdfRmVfX01PXzQ2NjgwODg3NzEzMF8wMDCUjDBFQU1fRHluYW1vX01hcmluaWNhXzIwMTFfRmVfX01PXzI1NTMxNTQwNzkxMF8wMDCUjD1FQU1fRHluYW1vX01lbmRlbGV2Qm9yb3Zpa292XzIwMjBfRmVOaUNyX19NT185MjIzNjMzNDA1NzBfMDAwlIw3RUFNX0R5bmFtb19NZW5kZWxldkhhblNvbl8yMDA3X1ZGZV9fTU9fMjQ5NzA2ODEwNTI3XzAwNZSMRkVBTV9EeW5hbW9fTWVuZGVsZXZIYW5Tcm9sb3ZpdHpfMjAwM1BvdGVudGlhbDJfRmVfX01PXzc2OTU4MjM2MzQzOV8wMDWUjEZFQU1fRHluYW1vX01lbmRlbGV2SGFuU3JvbG92aXR6XzIwMDNQb3RlbnRpYWw1X0ZlX19NT185NDI0MjA3MDY4NThfMDA1lIw8RUFNX0R5bmFtb19NZW5kZWxldkhhblNyb2xvdml0el8yMDAzX0ZlX19NT184MDc5OTc4MjY0NDlfMDAwlIxCRUFNX0R5bmFtb19NZW5kZWxldlNyb2xvdml0ekFja2xhbmRfMjAwNV9BbEZlX19NT181Nzc0NTM4OTE5NDFfMDA1lIwwRUFNX0R5bmFtb19NZW5kZWxldl8yMDAzX0ZlX19NT181NDY2NzM1NDkwODVfMDAwlIxJRUFNX0R5bmFtb19aaG91Sm9obnNvbldhZGxleV8yMDA0TklTVHJldGFidWxhdGlvbl9GZV9fTU9fNjgxMDg4Mjk4MjA4XzAwMJSMOUVBTV9EeW5hbW9fWmhvdUpvaG5zb25XYWRsZXlfMjAwNF9GZV9fTU9fNjUwMjc5OTA1MjMwXzAwNZSMRkVBTV9NYWduZXRpYzJHUXVpbnRpY19DaGllc2FEZXJsZXREdWRhcmV2XzIwMTFfRmVfX01PXzE0MDQ0NDMyMTYwN18wMDKUjDxFQU1fTWFnbmV0aWNDdWJpY19EdWRhcmV2RGVybGV0XzIwMDVfRmVfX01PXzEzNTAzNDIyOTI4Ml8wMDKUjENFQU1fTWFnbmV0aWNDdWJpY19NZW5kZWxldkhhblNyb2xvdml0el8yMDAzX0ZlX19NT184NTYyOTU5NTI0MjVfMDAylIw8TUVBTV9MQU1NUFNfQXNhZGlaYWVlbU5vdXJhbmlhbl8yMDE1X0ZlX19NT180OTIzMTA4OTg3NzlfMDAwlIw7TUVBTV9MQU1NUFNfQ2hvaUpvU29obl8yMDE4X0NvTmlDckZlTW5fX01PXzExNTQ1NDc0NzUwM18wMDCUjDZNRUFNX0xBTU1QU19DaG9pS2ltU2VvbF8yMDE3X0NvRmVfX01PXzE3OTE1ODI1NzE4MF8wMDCUjDVNRUFNX0xBTU1QU19FdGVzYW1pQXNhZGlfMjAxOF9GZV9fTU9fNTQ5OTAwMjg3NDIxXzAwMJSMR01FQU1fTEFNTVBTX0plbGluZWtHcm9oSG9yc3RlbWV5ZXJfMjAxMl9BbFNpTWdDdUZlX19NT18yNjI1MTk1MjA2NzhfMDAwlIw2TUVBTV9MQU1NUFNfSmVvbmdQYXJrRG9fMjAxOF9QZEZlX19NT185MjQ3MzY2MjIyMDNfMDAwlIw2TUVBTV9MQU1NUFNfS2ltSnVuZ0xlZV8yMDA5X0ZlVGlDX19NT18xMTAxMTkyMDQ3MjNfMDAwlIw2TUVBTV9MQU1NUFNfS2ltSnVuZ0xlZV8yMDEwX0ZlTmJDX19NT18wNzI2ODk3MTg2MTZfMDAwlIwxTUVBTV9MQU1NUFNfS2ltTGVlXzIwMDZfUHRGZV9fTU9fMzQzMTY4MTAxNDkwXzAwMJSMNU1FQU1fTEFNTVBTX0tpbVNoaW5MZWVfMjAwOV9GZU1uX19NT18wNTg3MzU0MDA0NjJfMDAwlIwyTUVBTV9MQU1NUFNfS29KaW1MZWVfMjAxMl9GZVBfX01PXzE3OTQyMDM2Mzk0NF8wMDCUjDFNRUFNX0xBTU1QU19MZWVKYW5nXzIwMDdfRmVIX19NT18wOTU2MTA5NTE5NTdfMDAwlIwzTUVBTV9MQU1NUFNfTGVlTGVlS2ltXzIwMDZfRmVOX19NT180MzI4NjE3NjY3MzhfMDAwlIwxTUVBTV9MQU1NUFNfTGVlTGVlXzIwMTBfRmVBbF9fTU9fMzMyMjExNTIyMDUwXzAwMJSMN01FQU1fTEFNTVBTX0xlZVdpcnRoU2hpbV8yMDA1X0ZlQ3VfX01PXzA2MzYyNjA2NTQzN18wMDCUjC1NRUFNX0xBTU1QU19MZWVfMjAwNl9GZUNfX01PXzg1Njk1NjE3ODY2OV8wMDCUjDpNRUFNX0xBTU1QU19MaXlhbmFnZUtpbUhvdXplXzIwMTRfRmVDX19NT18wNzUyNzk4MDAxOTVfMDAwlIwwTUVBTV9MQU1NUFNfU2FMZWVfMjAwOF9GZVRpX19NT18yNjA1NDY5Njc3OTNfMDAwlIwwTUVBTV9MQU1NUFNfU2FMZWVfMjAwOF9OYkZlX19NT18xNjIwMzYxNDEyNjFfMDAwlIw0TUVBTV9MQU1NUFNfV3VMZWVTdV8yMDE3X05pQ3JGZV9fTU9fOTEyNjM2MTA3MTA4XzAwMJSMMk1FQU1fTEFNTVBTX1d1TGVlU3VfMjAxN19OaUZlX19NT18zMjEyMzMxNzY0OThfMDAwlIwxTUpfTW9ycmlzQWdhTGV2YXNob3ZfMjAwOF9GZV9fTU9fODU3MjgyNzU0MzA3XzAwM5SMRE1vcnNlX1NoaWZ0ZWRfR2lyaWZhbGNvV2VpemVyXzE5NTlIaWdoQ3V0b2ZmX0ZlX19NT18xNDc2MDMxMjg0MzdfMDA0lIxDTW9yc2VfU2hpZnRlZF9HaXJpZmFsY29XZWl6ZXJfMTk1OUxvd0N1dG9mZl9GZV9fTU9fMzMxMjg1NDk1NjE3XzAwNJSMQ01vcnNlX1NoaWZ0ZWRfR2lyaWZhbGNvV2VpemVyXzE5NTlNZWRDdXRvZmZfRmVfX01PXzk4NDM1ODM0NDE5Nl8wMDSUjD1UZXJzb2ZmX0xBTU1QU19NdWVsbGVyRXJoYXJ0QWxiZV8yMDA3X0ZlX19NT18xMzc5NjQzMTA3MDJfMDAzlIxFU2ltX0xBTU1QU19FQU1DRF9TdHVrb3dza2lTYWRpZ2hFcmhhcnRfMjAwOV9GZUNyX19TTV83NzU1NjQ0OTk1MTNfMDAwlIxBU2ltX0xBTU1QU19FQU1fQm9ubnlDYXN0aW5CdWxsZW5zXzIwMTNfRmVDcldfX1NNXzY5OTI1NzM1MDcwNF8wMDCUjERTaW1fTEFNTVBTX0VBTV9Cb25ueVBhc2lhbm90VGVyZW50eWV2XzIwMTFfRmVDcl9fU01fMjM3MDg5Mjk4NDYzXzAwMJSMQFNpbV9MQU1NUFNfTUVBTV9Bc2FkaVphZWVtTm91cmFuaWFuXzIwMTVfRmVfX1NNXzA0MjYzMDY4MDk5M18wMDCUjDlTaW1fTEFNTVBTX01FQU1fRXRlc2FtaUFzYWRpXzIwMThfRmVfX1NNXzI2NzAxNjYwODc1NV8wMDCUjEtTaW1fTEFNTVBTX01FQU1fSmVsaW5la0dyb2hIb3JzdGVtZXllcl8yMDEyX0FsU2lNZ0N1RmVfX1NNXzY1NjUxNzM1MjQ4NV8wMDCUjDpTaW1fTEFNTVBTX01FQU1fS2ltSnVuZ0xlZV8yMDA5X0ZlVGlDX19TTV81MzEwMzgyNzQ0NzFfMDAwlIw+U2ltX0xBTU1QU19NRUFNX0xpeWFuYWdlS2ltSG91emVfMjAxNF9GZUNfX1NNXzY1MjQyNTc3NzgwOF8wMDCUjEhTaW1fTEFNTVBTX1JlYXhGRl9BcnlhbnBvdXJWYW5EdWluS3ViaWNraV8yMDEwX0ZlSE9fX1NNXzIyMjk2NDIxNjAwMV8wMDGUjEVTaW1fTEFNTVBTX1RlcnNvZmZaQkxfQnlnZ21hc3RhckdyYW5iZXJnXzIwMjBfRmVfX1NNXzk1ODg2Mzg5NTIzNF8wMDCUjE1TaW1fTEFNTVBTX1RlcnNvZmZaQkxfSGVucmlrc3NvbkJqb3JrYXNOb3JkbHVuZF8yMDEzX0ZlQ19fU01fNDczNDYzNDk4MjY5XzAwMJRldS4="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "engine",
+                                "GID": 37,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVjgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjB9weWlyb25fYXRvbWlzdGljcy5sYW1tcHMubGFtbXBzlIwGTGFtbXBzlJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "lammps_output_job"
+                            }
+                        ],
+                        "GID": 33,
+                        "pos x": 648.4175585188576,
+                        "pos y": 315.10989010989016
+                    },
+                    {
+                        "identifier": "BulkStructure_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {},
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "element",
+                                "GID": 39,
+                                "val": "gASVBgAAAAAAAACMAkZllC4=",
+                                "dtype": "DType.String",
+                                "dtype state": "gASViwAAAAAAAAB9lCiMB2RlZmF1bHSUjAJGZZSMA3ZhbJRoAowDZG9jlIwAlIwGYm91bmRzlE6MDXZhbGlkX2NsYXNzZXOUXZQojAhidWlsdGluc5SMA3N0cpSTlIwFbnVtcHmUjARzdHJflJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "bulk_structure_input_element"
+                            },
+                            {
+                                "type": "data",
+                                "label": "crystal_structure",
+                                "GID": 40,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Choice",
+                                "dtype state": "gASVwgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAphbGxvd19ub25llIiMB2JhdGNoZWSUiYwFaXRlbXOUXZQoTowCc2OUjANmY2OUjANiY2OUjANoY3CUjAdkaWFtb25klIwKemluY2JsZW5kZZSMCHJvY2tzYWx0lIwOY2VzaXVtY2hsb3JpZGWUjAhmbHVvcml0ZZSMCHd1cnR6aXRllGV1Lg=="
+                            },
+                            {
+                                "type": "data",
+                                "label": "a",
+                                "GID": 41,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVmQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjAVmbG9hdJSTlIwFbnVtcHmUjAhmbG9hdGluZ5STlGWMCmFsbG93X25vbmWUiIwHYmF0Y2hlZJSJjARzaXpllIwBbZSMCGRlY2ltYWxzlEsKdS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "c",
+                                "GID": 42,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVmQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjAVmbG9hdJSTlIwFbnVtcHmUjAhmbG9hdGluZ5STlGWMCmFsbG93X25vbmWUiIwHYmF0Y2hlZJSJjARzaXpllIwBbZSMCGRlY2ltYWxzlEsKdS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "c_over_a",
+                                "GID": 43,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVmQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjAVmbG9hdJSTlIwFbnVtcHmUjAhmbG9hdGluZ5STlGWMCmFsbG93X25vbmWUiIwHYmF0Y2hlZJSJjARzaXpllIwBbZSMCGRlY2ltYWxzlEsKdS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "u",
+                                "GID": 44,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVmQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjAVmbG9hdJSTlIwFbnVtcHmUjAhmbG9hdGluZ5STlGWMCmFsbG93X25vbmWUiIwHYmF0Y2hlZJSJjARzaXpllIwBbZSMCGRlY2ltYWxzlEsKdS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "orthorhombic",
+                                "GID": 45,
+                                "val": "gASJLg==",
+                                "dtype": "DType.Boolean",
+                                "dtype state": "gASViAAAAAAAAAB9lCiMB2RlZmF1bHSUiYwDdmFslImMA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjARib29slJOUjAVudW1weZSMBWJvb2xflJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "cubic",
+                                "GID": 46,
+                                "val": "gASJLg==",
+                                "dtype": "DType.Boolean",
+                                "dtype state": "gASViAAAAAAAAAB9lCiMB2RlZmF1bHSUiYwDdmFslImMA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjARib29slJOUjAVudW1weZSMBWJvb2xflJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "structure",
+                                "GID": 47,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVmgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjCxweWlyb25fYXRvbWlzdGljcy5hdG9taXN0aWNzLnN0cnVjdHVyZS5hdG9tc5SMBUF0b21zlJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "bulk_structure_output_structure"
+                            }
+                        ],
+                        "GID": 38,
+                        "pos x": 250.6381273965547,
+                        "pos y": 260.1648351648352
+                    }
+                ],
+                "connections": [
+                    {
+                        "GID": 48,
+                        "parent node index": 1,
+                        "output port index": 4,
+                        "connected node": 0,
+                        "connected input port index": 1
+                    },
+                    {
+                        "GID": 49,
+                        "parent node index": 2,
+                        "output port index": 0,
+                        "connected node": 1,
+                        "connected input port index": 3
+                    },
+                    {
+                        "GID": 50,
+                        "parent node index": 2,
+                        "output port index": 0,
+                        "connected node": 3,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 51,
+                        "parent node index": 3,
+                        "output port index": 0,
+                        "connected node": 1,
+                        "connected input port index": 4
+                    },
+                    {
+                        "GID": 52,
+                        "parent node index": 4,
+                        "output port index": 0,
+                        "connected node": 3,
+                        "connected input port index": 1
+                    }
+                ],
+                "GID": 7
+            },
+            "GID": 1
+        }
+    ]
+}
\ No newline at end of file
diff --git a/notebooks/premade_surface_energy.json b/notebooks/premade_surface_energy.json
new file mode 100644
index 00000000..f98358c9
--- /dev/null
+++ b/notebooks/premade_surface_energy.json
@@ -0,0 +1,751 @@
+{
+    "scripts": [
+        {
+            "title": "surface_ontology",
+            "variables": {},
+            "flow": {
+                "algorithm mode": "data",
+                "nodes": [
+                    {
+                        "identifier": "MaterialProperty_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {},
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "property",
+                                "GID": 9,
+                                "val": "gASVEQAAAAAAAACMDVN1cmZhY2VFbmVyZ3mULg==",
+                                "dtype": "DType.Choice",
+                                "dtype state": "gASVsQAAAAAAAAB9lCiMB2RlZmF1bHSUjBBNYXRlcmlhbFByb3BlcnR5lIwDdmFslGgCjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlIwIYnVpbHRpbnOUjANzdHKUk5RhjAphbGxvd19ub25llImMB2JhdGNoZWSUiYwFaXRlbXOUXZQojAtCdWxrTW9kdWx1c5SMDVN1cmZhY2VFbmVyZ3mUaAKMBkJQcmltZZRldS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "source",
+                                "GID": 10,
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVmQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjAVmbG9hdJSTlIwFbnVtcHmUjAhmbG9hdGluZ5STlGWMCmFsbG93X25vbmWUiYwHYmF0Y2hlZJSJjARzaXpllIwBbZSMCGRlY2ltYWxzlEsKdS4=",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "surfaceenergy2"
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "value",
+                                "GID": 11,
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURwAAAAAAAAAAjAN2YWyURwAAAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "surfaceenergy3"
+                            }
+                        ],
+                        "GID": 8,
+                        "pos x": 1375.848451897213,
+                        "pos y": 301.0989010989011
+                    },
+                    {
+                        "identifier": "SurfaceEnergy_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {},
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "bulk_structure",
+                                "GID": 13,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVmgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjCxweWlyb25fYXRvbWlzdGljcy5hdG9taXN0aWNzLnN0cnVjdHVyZS5hdG9tc5SMBUF0b21zlJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "surface_energy_input_bulk_structure"
+                            },
+                            {
+                                "type": "data",
+                                "label": "bulk_energy",
+                                "GID": 14,
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURwAAAAAAAAAAjAN2YWyURwAAAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "surface_energy_input_bulk_energy"
+                            },
+                            {
+                                "type": "data",
+                                "label": "surface_structure",
+                                "GID": 15,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVmgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjCxweWlyb25fYXRvbWlzdGljcy5hdG9taXN0aWNzLnN0cnVjdHVyZS5hdG9tc5SMBUF0b21zlJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "surface_energy_input_slab_structure"
+                            },
+                            {
+                                "type": "data",
+                                "label": "surface_energy",
+                                "GID": 16,
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURwAAAAAAAAAAjAN2YWyURwAAAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "surface_energy_input_slab_energy"
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "surface_energy",
+                                "GID": 17,
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURwAAAAAAAAAAjAN2YWyURwAAAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "surface_energy_output_surface_energy"
+                            }
+                        ],
+                        "GID": 12,
+                        "pos x": 1085.755165111887,
+                        "pos y": 281.31868131868134
+                    },
+                    {
+                        "identifier": "CalcMinimize_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {},
+                        "inputs": [
+                            {
+                                "type": "exec",
+                                "label": "run",
+                                "GID": 19,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Untyped",
+                                "dtype state": "gASVVQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAphbGxvd19ub25llIiMB2JhdGNoZWSUiXUu"
+                            },
+                            {
+                                "type": "exec",
+                                "label": "remove",
+                                "GID": 20,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Untyped",
+                                "dtype state": "gASVVQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAphbGxvd19ub25llIiMB2JhdGNoZWSUiXUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "name",
+                                "GID": 21,
+                                "val": "gASVCAAAAAAAAACMBGJ1bGuULg==",
+                                "dtype": "DType.String",
+                                "dtype state": "gASVjQAAAAAAAAB9lCiMB2RlZmF1bHSUjARjYWxjlIwDdmFslGgCjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwDc3RylJOUjAVudW1weZSMBHN0cl+Uk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UdS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "job",
+                                "GID": 22,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVjgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjB9weWlyb25fYXRvbWlzdGljcy5sYW1tcHMubGFtbXBzlIwGTGFtbXBzlJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "atomistic_taker_job"
+                            },
+                            {
+                                "type": "data",
+                                "label": "ionic_energy_tolerance",
+                                "GID": 23,
+                                "val": "gASVCgAAAAAAAABHAAAAAAAAAAAu",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURwAAAAAAAAAAjAN2YWyURwAAAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "ionic_force_tolerance",
+                                "GID": 24,
+                                "val": "gASVCgAAAAAAAABHPxo24uscQy0u",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURz8aNuLrHEMtjAN2YWyURz8aNuLrHEMtjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "max_iter",
+                                "GID": 25,
+                                "val": "gASVBgAAAAAAAABKoIYBAC4=",
+                                "dtype": "DType.Integer",
+                                "dtype state": "gASVkQAAAAAAAAB9lCiMB2RlZmF1bHSUSqCGAQCMA3ZhbJRKoIYBAIwDZG9jlIwAlIwGYm91bmRzlE6MDXZhbGlkX2NsYXNzZXOUXZQojAhidWlsdGluc5SMA2ludJSTlIwFbnVtcHmUjAdpbnRlZ2VylJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "pressure",
+                                "GID": 26,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVlgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjAVmbG9hdJSTlGgIjARsaXN0lJOUjAVudW1weZSMB25kYXJyYXmUk5RljAphbGxvd19ub25llIiMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UdS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "n_print",
+                                "GID": 27,
+                                "val": "gARLZC4=",
+                                "dtype": "DType.Integer",
+                                "dtype state": "gASViwAAAAAAAAB9lCiMB2RlZmF1bHSUS2SMA3ZhbJRLZIwDZG9jlIwAlIwGYm91bmRzlE6MDXZhbGlkX2NsYXNzZXOUXZQojAhidWlsdGluc5SMA2ludJSTlIwFbnVtcHmUjAdpbnRlZ2VylJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "style",
+                                "GID": 28,
+                                "val": "gASVBgAAAAAAAACMAmNnlC4=",
+                                "dtype": "DType.Choice",
+                                "dtype state": "gASVZwAAAAAAAAB9lCiMB2RlZmF1bHSUjAJjZ5SMA3ZhbJRoAowDZG9jlIwAlIwGYm91bmRzlE6MDXZhbGlkX2NsYXNzZXOUXZSMCmFsbG93X25vbmWUiYwHYmF0Y2hlZJSJjAVpdGVtc5RdlGgCYXUu"
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "exec",
+                                "label": "ran",
+                                "GID": 29,
+                                "dtype": "DType.Untyped",
+                                "dtype state": "gASVVQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAphbGxvd19ub25llIiMB2JhdGNoZWSUiXUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "job",
+                                "GID": 30,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVjgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjB9weWlyb25fYXRvbWlzdGljcy5sYW1tcHMubGFtbXBzlIwGTGFtbXBzlJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "energy_pot",
+                                "GID": 31,
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURwAAAAAAAAAAjAN2YWyURwAAAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "atomistic_taker_output_energy_pot"
+                            },
+                            {
+                                "type": "data",
+                                "label": "forces",
+                                "GID": 32,
+                                "dtype": "DType.List",
+                                "dtype state": "gASVgQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjAVmbG9hdJSTlIwFbnVtcHmUjAhmbG9hdGluZ5STlGWMCmFsbG93X25vbmWUiYwHYmF0Y2hlZJSJdS4=",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "atomistic_taker_output_forces"
+                            }
+                        ],
+                        "GID": 18,
+                        "pos x": 742.9176443655927,
+                        "pos y": 92.3076923076923
+                    },
+                    {
+                        "identifier": "CalcMinimize_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {},
+                        "inputs": [
+                            {
+                                "type": "exec",
+                                "label": "run",
+                                "GID": 35,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Untyped",
+                                "dtype state": "gASVVQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAphbGxvd19ub25llIiMB2JhdGNoZWSUiXUu"
+                            },
+                            {
+                                "type": "exec",
+                                "label": "remove",
+                                "GID": 36,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Untyped",
+                                "dtype state": "gASVVQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAphbGxvd19ub25llIiMB2JhdGNoZWSUiXUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "name",
+                                "GID": 37,
+                                "val": "gASVCAAAAAAAAACMBHN1cmaULg==",
+                                "dtype": "DType.String",
+                                "dtype state": "gASVjQAAAAAAAAB9lCiMB2RlZmF1bHSUjARjYWxjlIwDdmFslGgCjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwDc3RylJOUjAVudW1weZSMBHN0cl+Uk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UdS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "job",
+                                "GID": 38,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVjgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjB9weWlyb25fYXRvbWlzdGljcy5sYW1tcHMubGFtbXBzlIwGTGFtbXBzlJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "atomistic_taker_job"
+                            },
+                            {
+                                "type": "data",
+                                "label": "ionic_energy_tolerance",
+                                "GID": 39,
+                                "val": "gASVCgAAAAAAAABHAAAAAAAAAAAu",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURwAAAAAAAAAAjAN2YWyURwAAAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "ionic_force_tolerance",
+                                "GID": 40,
+                                "val": "gASVCgAAAAAAAABHPxo24uscQy0u",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURz8aNuLrHEMtjAN2YWyURz8aNuLrHEMtjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "max_iter",
+                                "GID": 41,
+                                "val": "gASVBgAAAAAAAABKoIYBAC4=",
+                                "dtype": "DType.Integer",
+                                "dtype state": "gASVkQAAAAAAAAB9lCiMB2RlZmF1bHSUSqCGAQCMA3ZhbJRKoIYBAIwDZG9jlIwAlIwGYm91bmRzlE6MDXZhbGlkX2NsYXNzZXOUXZQojAhidWlsdGluc5SMA2ludJSTlIwFbnVtcHmUjAdpbnRlZ2VylJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "pressure",
+                                "GID": 42,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVlgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjAVmbG9hdJSTlGgIjARsaXN0lJOUjAVudW1weZSMB25kYXJyYXmUk5RljAphbGxvd19ub25llIiMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UdS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "n_print",
+                                "GID": 43,
+                                "val": "gARLZC4=",
+                                "dtype": "DType.Integer",
+                                "dtype state": "gASViwAAAAAAAAB9lCiMB2RlZmF1bHSUS2SMA3ZhbJRLZIwDZG9jlIwAlIwGYm91bmRzlE6MDXZhbGlkX2NsYXNzZXOUXZQojAhidWlsdGluc5SMA2ludJSTlIwFbnVtcHmUjAdpbnRlZ2VylJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "style",
+                                "GID": 44,
+                                "val": "gASVBgAAAAAAAACMAmNnlC4=",
+                                "dtype": "DType.Choice",
+                                "dtype state": "gASVZwAAAAAAAAB9lCiMB2RlZmF1bHSUjAJjZ5SMA3ZhbJRoAowDZG9jlIwAlIwGYm91bmRzlE6MDXZhbGlkX2NsYXNzZXOUXZSMCmFsbG93X25vbmWUiYwHYmF0Y2hlZJSJjAVpdGVtc5RdlGgCYXUu"
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "exec",
+                                "label": "ran",
+                                "GID": 45,
+                                "dtype": "DType.Untyped",
+                                "dtype state": "gASVVQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAphbGxvd19ub25llIiMB2JhdGNoZWSUiXUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "job",
+                                "GID": 46,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVjgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjB9weWlyb25fYXRvbWlzdGljcy5sYW1tcHMubGFtbXBzlIwGTGFtbXBzlJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "energy_pot",
+                                "GID": 47,
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSURwAAAAAAAAAAjAN2YWyURwAAAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "atomistic_taker_output_energy_pot"
+                            },
+                            {
+                                "type": "data",
+                                "label": "forces",
+                                "GID": 48,
+                                "dtype": "DType.List",
+                                "dtype state": "gASVgQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjAVmbG9hdJSTlIwFbnVtcHmUjAhmbG9hdGluZ5STlGWMCmFsbG93X25vbmWUiYwHYmF0Y2hlZJSJdS4=",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "atomistic_taker_output_forces"
+                            }
+                        ],
+                        "GID": 34,
+                        "pos x": 747.3129971956733,
+                        "pos y": 493.13186813186815
+                    },
+                    {
+                        "identifier": "Lammps_Node",
+                        "version": "v0.2",
+                        "state data": "gAR9lC4=",
+                        "additional data": {},
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "project",
+                                "GID": 51,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASViQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjBlweWlyb25fYXRvbWlzdGljcy5wcm9qZWN0lIwHUHJvamVjdJSTlGGMCmFsbG93X25vbmWUiYwHYmF0Y2hlZJSJjARzaXpllIwBbZR1Lg==",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "lammps_input_project"
+                            },
+                            {
+                                "type": "data",
+                                "label": "structure",
+                                "GID": 52,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVmgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjCxweWlyb25fYXRvbWlzdGljcy5hdG9taXN0aWNzLnN0cnVjdHVyZS5hdG9tc5SMBUF0b21zlJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "lammps_input_structure"
+                            },
+                            {
+                                "type": "data",
+                                "label": "potential",
+                                "GID": 53,
+                                "val": "gASVJwAAAAAAAACMIzE5OTctLUFja2xhbmQtRy1KLS1GZS0tTEFNTVBTLS1pcHIxlC4=",
+                                "dtype": "DType.Choice",
+                                "dtype state": "gASVLxkAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAhidWlsdGluc5SMA3N0cpSTlGGMCmFsbG93X25vbmWUiYwHYmF0Y2hlZJSJjAVpdGVtc5RdlCiMIzE5OTctLUFja2xhbmQtRy1KLS1GZS0tTEFNTVBTLS1pcHIxlIwfMTk5OC0tTWV5ZXItUi0tRmUtLUxBTU1QUy0taXByMZSMHzIwMDEtLUxlZS1CLUotLUZlLS1MQU1NUFMtLWlwcjGUjCIyMDAxLS1MZWUtQi1KLS1GZS1Dci0tTEFNTVBTLS1pcHIxlIwmMjAwMy0tTWVuZGVsZXYtTS1JLS1GZS0yLS1MQU1NUFMtLWlwcjOUjCYyMDAzLS1NZW5kZWxldi1NLUktLUZlLTUtLUxBTU1QUy0taXByMZSMJTIwMDQtLUFja2xhbmQtRy1KLS1GZS1QLS1MQU1NUFMtLWlwcjGUjCAyMDA0LS1aaG91LVgtVy0tRmUtLUxBTU1QUy0taXByMpSMIjIwMDUtLUxlZS1CLUotLUZlLUN1LS1MQU1NUFMtLWlwcjGUjCcyMDA1LS1NZW5kZWxldi1NLUktLUFsLUZlLS1MQU1NUFMtLWlwcjGUjCEyMDA2LS1DaGFtYXRpLUgtLUZlLS1MQU1NUFMtLWlwcjGUjCAyMDA2LS1LaW0tSi0tRmUtUHQtLUxBTU1QUy0taXByMZSMITIwMDYtLUxlZS1CLUotLUZlLUMtLUxBTU1QUy0taXByMZSMITIwMDYtLUxlZS1CLUotLUZlLU4tLUxBTU1QUy0taXByMZSMITIwMDctLUxlZS1CLUotLUZlLUgtLUxBTU1QUy0taXByMZSMJjIwMDctLU1lbmRlbGV2LU0tSS0tVi1GZS0tTEFNTVBTLS1pcHIxlIwlMjAwOC0tSGVwYnVybi1ELUotLUZlLUMtLUxBTU1QUy0taXByMZSMHzIwMDgtLVNhLUktLUZlLU5iLS1MQU1NUFMtLWlwcjGUjB8yMDA4LS1TYS1JLS1GZS1UaS0tTEFNTVBTLS1pcHIxlIwlMjAwOS0tQm9ubnktRy0tRmUtQ3UtTmktLUxBTU1QUy0taXByMZSMIjIwMDktLUJvbm55LUctLUZlLU5pLS1MQU1NUFMtLWlwcjGUjCQyMDA5LS1LaW0tSC1LLS1GZS1UaS1DLS1MQU1NUFMtLWlwcjKUjCIyMDA5LS1LaW0tWS1NLS1GZS1Nbi0tTEFNTVBTLS1pcHIxlIwkMjAwOS0tT2xzc29uLVAtQS1ULS1GZS0tTEFNTVBTLS1pcHIxlIwmMjAwOS0tU3R1a293c2tpLUEtLUZlLUNyLS1MQU1NUFMtLWlwcjGUjCQyMDEwLS1LaW0tSC1LLS1GZS1OYi1DLS1MQU1NUFMtLWlwcjGUjCAyMDEwLS1MZWUtRS0tRmUtQWwtLUxBTU1QUy0taXByMZSMITIwMTAtLU1hbGVyYmEtTC0tRmUtLUxBTU1QUy0taXByMZSMIjIwMTEtLUJvbm55LUctLUZlLUNyLS1MQU1NUFMtLWlwcjKUjCIyMDExLS1Cb25ueS1HLS1GZS1Dci0tTEFNTVBTLS1pcHIzlIwlMjAxMS0tQm9ubnktRy0tRmUtTmktQ3ItLUxBTU1QUy0taXByMZSMJTIwMTEtLUJvbm55LUctLUZlLU5pLUNyLS1MQU1NUFMtLWlwcjKUjCMyMDExLS1DaGllc2EtUy0tRmUtMzMtLUxBTU1QUy0taXByMZSMLTIwMTItLUplbGluZWstQi0tQWwtU2ktTWctQ3UtRmUtLUxBTU1QUy0taXByMpSMIDIwMTItLUtvLVctUy0tRmUtUC0tTEFNTVBTLS1pcHIxlIwiMjAxMi0tUHJvdmlsbGUtTC0tRmUtLUxBTU1QUy0taXByMZSMJDIwMTMtLUJvbm55LUctLUZlLUNyLVctLUxBTU1QUy0taXByMpSMJDIwMTMtLUJvbm55LUctLUZlLUNyLVctLUxBTU1QUy0taXByM5SMJTIwMTMtLUJvbm55LUctLUZlLU5pLUNyLS1MQU1NUFMtLWlwcjGUjCEyMDEzLS1Cb25ueS1HLS1GZS1XLS1MQU1NUFMtLWlwcjGUjCoyMDEzLS1IZW5yaWtzc29uLUstTy1FLS1GZS1DLS1MQU1NUFMtLWlwcjGUjCgyMDE0LS1MaXlhbmFnZS1MLVMtSS0tRmUtQy0tTEFNTVBTLS1pcHIylIwfMjAxNS0tQXNhZGktRS0tRmUtLUxBTU1QUy0taXByMZSMIzIwMTUtLUVpY2gtUy1NLS1GZS1Dci0tTEFNTVBTLS1pcHIxlIwoMjAxNy0tQmVsYW5kLUwtSy0tRmUtTmktQ3ItLUxBTU1QUy0taXByMZSMIzIwMTctLUNob2ktVy1NLS1Dby1GZS0tTEFNTVBTLS1pcHIxlIwiMjAxNy0tV3UtQy0tTmktQ3ItRmUtLUxBTU1QUy0taXByMZSMHzIwMTctLVd1LUMtLU5pLUZlLS1MQU1NUFMtLWlwcjGUjCwyMDE4LS1DaG9pLVctTS0tQ28tTmktQ3ItRmUtTW4tLUxBTU1QUy0taXByMZSMIzIwMTgtLUV0ZXNhbWktUy1BLS1GZS0tTEFNTVBTLS1pcHIxlIwsMjAxOC0tRmFya2FzLUQtLUZlLU5pLUNyLUNvLUN1LS1MQU1NUFMtLWlwcjKUjCQyMDE4LS1KZW9uZy1HLVUtLVBkLUZlLS1MQU1NUFMtLWlwcjGUjCYyMDE4LS1aaG91LVgtVy0tRmUtTmktQ3ItLUxBTU1QUy0taXByMZSMJjIwMTgtLVpob3UtWC1XLS1GZS1OaS1Dci0tTEFNTVBTLS1pcHIylIwnMjAxOS0tQXNsYW0tSS0tRmUtTW4tU2ktQy0tTEFNTVBTLS1pcHIxlIwmMjAxOS0tQnlnZ21hc3Rhci1KLS1GZS1PLS1MQU1NUFMtLWlwcjGUjCoyMDE5LS1NZW5kZWxldi1NLUktLUZlLU5pLUNyLS1MQU1NUFMtLWlwcjGUjCQyMDIwLS1CeWdnbWFzdGFyLUotLUZlLS1MQU1NUFMtLWlwcjGUjCwyMDIwLS1GYXJrYXMtRC0tRmUtTmktQ3ItQ28tQWwtLUxBTU1QUy0taXByMZSMLDIwMjAtLUdyb2dlci1SLS1Dby1Dci1GZS1Nbi1OaS0tTEFNTVBTLS1pcHIxlIweMjAyMC0tTW9yaS1ILS1GZS0tTEFNTVBTLS1pcHIxlIwvMjAyMS0tRGVsdWlnaS1PLVItLUZlLU5pLUNyLUNvLUN1LS1MQU1NUFMtLWlwcjGUjCIyMDIxLS1TdGFyaWtvdi1TLS1GZS0tTEFNTVBTLS1pcHIxlIwiMjAyMS0tU3Rhcmlrb3YtUy0tRmUtLUxBTU1QUy0taXByMpSMHzIwMjEtLVdlbi1NLS1GZS1ILS1MQU1NUFMtLWlwcjGUjCMyMDIyLS1NYWhhdGEtQS0tQWwtRmUtLUxBTU1QUy0taXByMZSMJzIwMjItLVN0YXJpa292LVMtLUZlLUNyLUgtLUxBTU1QUy0taXByMZSMHTIwMjItLVN1bi1ZLS1GZS0tTEFNTVBTLS1pcHIxlIw6RUFNX0R5bmFtb19BY2tsYW5kQmFjb25DYWxkZXJfMTk5N19GZV9fTU9fMTQyNzk5NzE3NTE2XzAwNZSMQUVBTV9EeW5hbW9fQWNrbGFuZE1lbmRlbGV2U3JvbG92aXR6XzIwMDRfRmVQX19NT184ODQzNDMxNDYzMTBfMDA1lIw7RUFNX0R5bmFtb19Cb25ueUNhc3RpbkJ1bGxlbnNfMjAxM19GZVdfX01PXzczNzU2NzI0MjYzMV8wMDCUjEBFQU1fRHluYW1vX0Jvbm55Q2FzdGluVGVyZW50eWV2XzIwMTNfRmVOaUNyX19NT183NjMxOTc5NDEwMzlfMDAwlIw/RUFNX0R5bmFtb19Cb25ueVBhc2lhbm90Q2FzdGluXzIwMDlfRmVDdU5pX19NT180NjkzNDM5NzMxNzFfMDA1lIw+RUFNX0R5bmFtb19Cb25ueVBhc2lhbm90TWFsZXJiYV8yMDA5X0ZlTmlfX01PXzI2NzcyMTQwODkzNF8wMDWUjEFFQU1fRHluYW1vX0NoYW1hdGlQYXBhbmljb2xhb3VNaXNoaW5fMjAwNl9GZV9fTU9fOTYwNjk5NTEzNDI0XzAwMJSMN0VBTV9EeW5hbW9fSGVwYnVybkFja2xhbmRfMjAwOF9GZUNfX01PXzE0Mzk3NzE1MjcyOF8wMDWUjDBFQU1fRHluYW1vX01hcmluaWNhXzIwMDdfRmVfX01PXzQ2NjgwODg3NzEzMF8wMDCUjDBFQU1fRHluYW1vX01hcmluaWNhXzIwMTFfRmVfX01PXzI1NTMxNTQwNzkxMF8wMDCUjD1FQU1fRHluYW1vX01lbmRlbGV2Qm9yb3Zpa292XzIwMjBfRmVOaUNyX19NT185MjIzNjMzNDA1NzBfMDAwlIw3RUFNX0R5bmFtb19NZW5kZWxldkhhblNvbl8yMDA3X1ZGZV9fTU9fMjQ5NzA2ODEwNTI3XzAwNZSMRkVBTV9EeW5hbW9fTWVuZGVsZXZIYW5Tcm9sb3ZpdHpfMjAwM1BvdGVudGlhbDJfRmVfX01PXzc2OTU4MjM2MzQzOV8wMDWUjEZFQU1fRHluYW1vX01lbmRlbGV2SGFuU3JvbG92aXR6XzIwMDNQb3RlbnRpYWw1X0ZlX19NT185NDI0MjA3MDY4NThfMDA1lIw8RUFNX0R5bmFtb19NZW5kZWxldkhhblNyb2xvdml0el8yMDAzX0ZlX19NT184MDc5OTc4MjY0NDlfMDAwlIxCRUFNX0R5bmFtb19NZW5kZWxldlNyb2xvdml0ekFja2xhbmRfMjAwNV9BbEZlX19NT181Nzc0NTM4OTE5NDFfMDA1lIwwRUFNX0R5bmFtb19NZW5kZWxldl8yMDAzX0ZlX19NT181NDY2NzM1NDkwODVfMDAwlIxJRUFNX0R5bmFtb19aaG91Sm9obnNvbldhZGxleV8yMDA0TklTVHJldGFidWxhdGlvbl9GZV9fTU9fNjgxMDg4Mjk4MjA4XzAwMJSMOUVBTV9EeW5hbW9fWmhvdUpvaG5zb25XYWRsZXlfMjAwNF9GZV9fTU9fNjUwMjc5OTA1MjMwXzAwNZSMRkVBTV9NYWduZXRpYzJHUXVpbnRpY19DaGllc2FEZXJsZXREdWRhcmV2XzIwMTFfRmVfX01PXzE0MDQ0NDMyMTYwN18wMDKUjDxFQU1fTWFnbmV0aWNDdWJpY19EdWRhcmV2RGVybGV0XzIwMDVfRmVfX01PXzEzNTAzNDIyOTI4Ml8wMDKUjENFQU1fTWFnbmV0aWNDdWJpY19NZW5kZWxldkhhblNyb2xvdml0el8yMDAzX0ZlX19NT184NTYyOTU5NTI0MjVfMDAylIw8TUVBTV9MQU1NUFNfQXNhZGlaYWVlbU5vdXJhbmlhbl8yMDE1X0ZlX19NT180OTIzMTA4OTg3NzlfMDAwlIw7TUVBTV9MQU1NUFNfQ2hvaUpvU29obl8yMDE4X0NvTmlDckZlTW5fX01PXzExNTQ1NDc0NzUwM18wMDCUjDZNRUFNX0xBTU1QU19DaG9pS2ltU2VvbF8yMDE3X0NvRmVfX01PXzE3OTE1ODI1NzE4MF8wMDCUjDVNRUFNX0xBTU1QU19FdGVzYW1pQXNhZGlfMjAxOF9GZV9fTU9fNTQ5OTAwMjg3NDIxXzAwMJSMR01FQU1fTEFNTVBTX0plbGluZWtHcm9oSG9yc3RlbWV5ZXJfMjAxMl9BbFNpTWdDdUZlX19NT18yNjI1MTk1MjA2NzhfMDAwlIw2TUVBTV9MQU1NUFNfSmVvbmdQYXJrRG9fMjAxOF9QZEZlX19NT185MjQ3MzY2MjIyMDNfMDAwlIw2TUVBTV9MQU1NUFNfS2ltSnVuZ0xlZV8yMDA5X0ZlVGlDX19NT18xMTAxMTkyMDQ3MjNfMDAwlIw2TUVBTV9MQU1NUFNfS2ltSnVuZ0xlZV8yMDEwX0ZlTmJDX19NT18wNzI2ODk3MTg2MTZfMDAwlIwxTUVBTV9MQU1NUFNfS2ltTGVlXzIwMDZfUHRGZV9fTU9fMzQzMTY4MTAxNDkwXzAwMJSMNU1FQU1fTEFNTVBTX0tpbVNoaW5MZWVfMjAwOV9GZU1uX19NT18wNTg3MzU0MDA0NjJfMDAwlIwyTUVBTV9MQU1NUFNfS29KaW1MZWVfMjAxMl9GZVBfX01PXzE3OTQyMDM2Mzk0NF8wMDCUjDFNRUFNX0xBTU1QU19MZWVKYW5nXzIwMDdfRmVIX19NT18wOTU2MTA5NTE5NTdfMDAwlIwzTUVBTV9MQU1NUFNfTGVlTGVlS2ltXzIwMDZfRmVOX19NT180MzI4NjE3NjY3MzhfMDAwlIwxTUVBTV9MQU1NUFNfTGVlTGVlXzIwMTBfRmVBbF9fTU9fMzMyMjExNTIyMDUwXzAwMJSMN01FQU1fTEFNTVBTX0xlZVdpcnRoU2hpbV8yMDA1X0ZlQ3VfX01PXzA2MzYyNjA2NTQzN18wMDCUjC1NRUFNX0xBTU1QU19MZWVfMjAwNl9GZUNfX01PXzg1Njk1NjE3ODY2OV8wMDCUjDpNRUFNX0xBTU1QU19MaXlhbmFnZUtpbUhvdXplXzIwMTRfRmVDX19NT18wNzUyNzk4MDAxOTVfMDAwlIwwTUVBTV9MQU1NUFNfU2FMZWVfMjAwOF9GZVRpX19NT18yNjA1NDY5Njc3OTNfMDAwlIwwTUVBTV9MQU1NUFNfU2FMZWVfMjAwOF9OYkZlX19NT18xNjIwMzYxNDEyNjFfMDAwlIw0TUVBTV9MQU1NUFNfV3VMZWVTdV8yMDE3X05pQ3JGZV9fTU9fOTEyNjM2MTA3MTA4XzAwMJSMMk1FQU1fTEFNTVBTX1d1TGVlU3VfMjAxN19OaUZlX19NT18zMjEyMzMxNzY0OThfMDAwlIwxTUpfTW9ycmlzQWdhTGV2YXNob3ZfMjAwOF9GZV9fTU9fODU3MjgyNzU0MzA3XzAwM5SMRE1vcnNlX1NoaWZ0ZWRfR2lyaWZhbGNvV2VpemVyXzE5NTlIaWdoQ3V0b2ZmX0ZlX19NT18xNDc2MDMxMjg0MzdfMDA0lIxDTW9yc2VfU2hpZnRlZF9HaXJpZmFsY29XZWl6ZXJfMTk1OUxvd0N1dG9mZl9GZV9fTU9fMzMxMjg1NDk1NjE3XzAwNJSMQ01vcnNlX1NoaWZ0ZWRfR2lyaWZhbGNvV2VpemVyXzE5NTlNZWRDdXRvZmZfRmVfX01PXzk4NDM1ODM0NDE5Nl8wMDSUjD1UZXJzb2ZmX0xBTU1QU19NdWVsbGVyRXJoYXJ0QWxiZV8yMDA3X0ZlX19NT18xMzc5NjQzMTA3MDJfMDAzlIxFU2ltX0xBTU1QU19FQU1DRF9TdHVrb3dza2lTYWRpZ2hFcmhhcnRfMjAwOV9GZUNyX19TTV83NzU1NjQ0OTk1MTNfMDAwlIxBU2ltX0xBTU1QU19FQU1fQm9ubnlDYXN0aW5CdWxsZW5zXzIwMTNfRmVDcldfX1NNXzY5OTI1NzM1MDcwNF8wMDCUjERTaW1fTEFNTVBTX0VBTV9Cb25ueVBhc2lhbm90VGVyZW50eWV2XzIwMTFfRmVDcl9fU01fMjM3MDg5Mjk4NDYzXzAwMJSMQFNpbV9MQU1NUFNfTUVBTV9Bc2FkaVphZWVtTm91cmFuaWFuXzIwMTVfRmVfX1NNXzA0MjYzMDY4MDk5M18wMDCUjDlTaW1fTEFNTVBTX01FQU1fRXRlc2FtaUFzYWRpXzIwMThfRmVfX1NNXzI2NzAxNjYwODc1NV8wMDCUjEtTaW1fTEFNTVBTX01FQU1fSmVsaW5la0dyb2hIb3JzdGVtZXllcl8yMDEyX0FsU2lNZ0N1RmVfX1NNXzY1NjUxNzM1MjQ4NV8wMDCUjDpTaW1fTEFNTVBTX01FQU1fS2ltSnVuZ0xlZV8yMDA5X0ZlVGlDX19TTV81MzEwMzgyNzQ0NzFfMDAwlIw+U2ltX0xBTU1QU19NRUFNX0xpeWFuYWdlS2ltSG91emVfMjAxNF9GZUNfX1NNXzY1MjQyNTc3NzgwOF8wMDCUjEhTaW1fTEFNTVBTX1JlYXhGRl9BcnlhbnBvdXJWYW5EdWluS3ViaWNraV8yMDEwX0ZlSE9fX1NNXzIyMjk2NDIxNjAwMV8wMDGUjEVTaW1fTEFNTVBTX1RlcnNvZmZaQkxfQnlnZ21hc3RhckdyYW5iZXJnXzIwMjBfRmVfX1NNXzk1ODg2Mzg5NTIzNF8wMDCUjE1TaW1fTEFNTVBTX1RlcnNvZmZaQkxfSGVucmlrc3NvbkJqb3JrYXNOb3JkbHVuZF8yMDEzX0ZlQ19fU01fNDczNDYzNDk4MjY5XzAwMJRldS4="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "engine",
+                                "GID": 54,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVjgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjB9weWlyb25fYXRvbWlzdGljcy5sYW1tcHMubGFtbXBzlIwGTGFtbXBzlJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "lammps_output_job"
+                            }
+                        ],
+                        "GID": 50,
+                        "pos x": 444.0336519201053,
+                        "pos y": 82.14285714285714
+                    },
+                    {
+                        "identifier": "BulkStructure_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {},
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "element",
+                                "GID": 57,
+                                "val": "gASVBgAAAAAAAACMAkZllC4=",
+                                "dtype": "DType.String",
+                                "dtype state": "gASViwAAAAAAAAB9lCiMB2RlZmF1bHSUjAJGZZSMA3ZhbJRoAowDZG9jlIwAlIwGYm91bmRzlE6MDXZhbGlkX2NsYXNzZXOUXZQojAhidWlsdGluc5SMA3N0cpSTlIwFbnVtcHmUjARzdHJflJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "bulk_structure_input_element"
+                            },
+                            {
+                                "type": "data",
+                                "label": "crystal_structure",
+                                "GID": 58,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Choice",
+                                "dtype state": "gASVwgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAphbGxvd19ub25llIiMB2JhdGNoZWSUiYwFaXRlbXOUXZQoTowCc2OUjANmY2OUjANiY2OUjANoY3CUjAdkaWFtb25klIwKemluY2JsZW5kZZSMCHJvY2tzYWx0lIwOY2VzaXVtY2hsb3JpZGWUjAhmbHVvcml0ZZSMCHd1cnR6aXRllGV1Lg=="
+                            },
+                            {
+                                "type": "data",
+                                "label": "a",
+                                "GID": 59,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVmQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjAVmbG9hdJSTlIwFbnVtcHmUjAhmbG9hdGluZ5STlGWMCmFsbG93X25vbmWUiIwHYmF0Y2hlZJSJjARzaXpllIwBbZSMCGRlY2ltYWxzlEsKdS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "c",
+                                "GID": 60,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVmQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjAVmbG9hdJSTlIwFbnVtcHmUjAhmbG9hdGluZ5STlGWMCmFsbG93X25vbmWUiIwHYmF0Y2hlZJSJjARzaXpllIwBbZSMCGRlY2ltYWxzlEsKdS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "c_over_a",
+                                "GID": 61,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVmQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjAVmbG9hdJSTlIwFbnVtcHmUjAhmbG9hdGluZ5STlGWMCmFsbG93X25vbmWUiIwHYmF0Y2hlZJSJjARzaXpllIwBbZSMCGRlY2ltYWxzlEsKdS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "u",
+                                "GID": 62,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVmQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjAVmbG9hdJSTlIwFbnVtcHmUjAhmbG9hdGluZ5STlGWMCmFsbG93X25vbmWUiIwHYmF0Y2hlZJSJjARzaXpllIwBbZSMCGRlY2ltYWxzlEsKdS4="
+                            },
+                            {
+                                "type": "data",
+                                "label": "orthorhombic",
+                                "GID": 63,
+                                "val": "gASJLg==",
+                                "dtype": "DType.Boolean",
+                                "dtype state": "gASViAAAAAAAAAB9lCiMB2RlZmF1bHSUiYwDdmFslImMA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjARib29slJOUjAVudW1weZSMBWJvb2xflJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "cubic",
+                                "GID": 64,
+                                "val": "gASJLg==",
+                                "dtype": "DType.Boolean",
+                                "dtype state": "gASViAAAAAAAAAB9lCiMB2RlZmF1bHSUiYwDdmFslImMA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjARib29slJOUjAVudW1weZSMBWJvb2xflJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "structure",
+                                "GID": 65,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVmgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjCxweWlyb25fYXRvbWlzdGljcy5hdG9taXN0aWNzLnN0cnVjdHVyZS5hdG9tc5SMBUF0b21zlJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "bulk_structure_output_structure"
+                            }
+                        ],
+                        "GID": 56,
+                        "pos x": 107.78916041893207,
+                        "pos y": 128.2967032967033
+                    },
+                    {
+                        "identifier": "Lammps_Node",
+                        "version": "v0.2",
+                        "state data": "gAR9lC4=",
+                        "additional data": {},
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "project",
+                                "GID": 68,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASViQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjBlweWlyb25fYXRvbWlzdGljcy5wcm9qZWN0lIwHUHJvamVjdJSTlGGMCmFsbG93X25vbmWUiYwHYmF0Y2hlZJSJjARzaXpllIwBbZR1Lg==",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "lammps_input_project"
+                            },
+                            {
+                                "type": "data",
+                                "label": "structure",
+                                "GID": 69,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVmgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjCxweWlyb25fYXRvbWlzdGljcy5hdG9taXN0aWNzLnN0cnVjdHVyZS5hdG9tc5SMBUF0b21zlJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "lammps_input_structure"
+                            },
+                            {
+                                "type": "data",
+                                "label": "potential",
+                                "GID": 70,
+                                "val": "gASVJwAAAAAAAACMIzE5OTctLUFja2xhbmQtRy1KLS1GZS0tTEFNTVBTLS1pcHIxlC4=",
+                                "dtype": "DType.Choice",
+                                "dtype state": "gASVLxkAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAhidWlsdGluc5SMA3N0cpSTlGGMCmFsbG93X25vbmWUiYwHYmF0Y2hlZJSJjAVpdGVtc5RdlCiMIzE5OTctLUFja2xhbmQtRy1KLS1GZS0tTEFNTVBTLS1pcHIxlIwfMTk5OC0tTWV5ZXItUi0tRmUtLUxBTU1QUy0taXByMZSMHzIwMDEtLUxlZS1CLUotLUZlLS1MQU1NUFMtLWlwcjGUjCIyMDAxLS1MZWUtQi1KLS1GZS1Dci0tTEFNTVBTLS1pcHIxlIwmMjAwMy0tTWVuZGVsZXYtTS1JLS1GZS0yLS1MQU1NUFMtLWlwcjOUjCYyMDAzLS1NZW5kZWxldi1NLUktLUZlLTUtLUxBTU1QUy0taXByMZSMJTIwMDQtLUFja2xhbmQtRy1KLS1GZS1QLS1MQU1NUFMtLWlwcjGUjCAyMDA0LS1aaG91LVgtVy0tRmUtLUxBTU1QUy0taXByMpSMIjIwMDUtLUxlZS1CLUotLUZlLUN1LS1MQU1NUFMtLWlwcjGUjCcyMDA1LS1NZW5kZWxldi1NLUktLUFsLUZlLS1MQU1NUFMtLWlwcjGUjCEyMDA2LS1DaGFtYXRpLUgtLUZlLS1MQU1NUFMtLWlwcjGUjCAyMDA2LS1LaW0tSi0tRmUtUHQtLUxBTU1QUy0taXByMZSMITIwMDYtLUxlZS1CLUotLUZlLUMtLUxBTU1QUy0taXByMZSMITIwMDYtLUxlZS1CLUotLUZlLU4tLUxBTU1QUy0taXByMZSMITIwMDctLUxlZS1CLUotLUZlLUgtLUxBTU1QUy0taXByMZSMJjIwMDctLU1lbmRlbGV2LU0tSS0tVi1GZS0tTEFNTVBTLS1pcHIxlIwlMjAwOC0tSGVwYnVybi1ELUotLUZlLUMtLUxBTU1QUy0taXByMZSMHzIwMDgtLVNhLUktLUZlLU5iLS1MQU1NUFMtLWlwcjGUjB8yMDA4LS1TYS1JLS1GZS1UaS0tTEFNTVBTLS1pcHIxlIwlMjAwOS0tQm9ubnktRy0tRmUtQ3UtTmktLUxBTU1QUy0taXByMZSMIjIwMDktLUJvbm55LUctLUZlLU5pLS1MQU1NUFMtLWlwcjGUjCQyMDA5LS1LaW0tSC1LLS1GZS1UaS1DLS1MQU1NUFMtLWlwcjKUjCIyMDA5LS1LaW0tWS1NLS1GZS1Nbi0tTEFNTVBTLS1pcHIxlIwkMjAwOS0tT2xzc29uLVAtQS1ULS1GZS0tTEFNTVBTLS1pcHIxlIwmMjAwOS0tU3R1a293c2tpLUEtLUZlLUNyLS1MQU1NUFMtLWlwcjGUjCQyMDEwLS1LaW0tSC1LLS1GZS1OYi1DLS1MQU1NUFMtLWlwcjGUjCAyMDEwLS1MZWUtRS0tRmUtQWwtLUxBTU1QUy0taXByMZSMITIwMTAtLU1hbGVyYmEtTC0tRmUtLUxBTU1QUy0taXByMZSMIjIwMTEtLUJvbm55LUctLUZlLUNyLS1MQU1NUFMtLWlwcjKUjCIyMDExLS1Cb25ueS1HLS1GZS1Dci0tTEFNTVBTLS1pcHIzlIwlMjAxMS0tQm9ubnktRy0tRmUtTmktQ3ItLUxBTU1QUy0taXByMZSMJTIwMTEtLUJvbm55LUctLUZlLU5pLUNyLS1MQU1NUFMtLWlwcjKUjCMyMDExLS1DaGllc2EtUy0tRmUtMzMtLUxBTU1QUy0taXByMZSMLTIwMTItLUplbGluZWstQi0tQWwtU2ktTWctQ3UtRmUtLUxBTU1QUy0taXByMpSMIDIwMTItLUtvLVctUy0tRmUtUC0tTEFNTVBTLS1pcHIxlIwiMjAxMi0tUHJvdmlsbGUtTC0tRmUtLUxBTU1QUy0taXByMZSMJDIwMTMtLUJvbm55LUctLUZlLUNyLVctLUxBTU1QUy0taXByMpSMJDIwMTMtLUJvbm55LUctLUZlLUNyLVctLUxBTU1QUy0taXByM5SMJTIwMTMtLUJvbm55LUctLUZlLU5pLUNyLS1MQU1NUFMtLWlwcjGUjCEyMDEzLS1Cb25ueS1HLS1GZS1XLS1MQU1NUFMtLWlwcjGUjCoyMDEzLS1IZW5yaWtzc29uLUstTy1FLS1GZS1DLS1MQU1NUFMtLWlwcjGUjCgyMDE0LS1MaXlhbmFnZS1MLVMtSS0tRmUtQy0tTEFNTVBTLS1pcHIylIwfMjAxNS0tQXNhZGktRS0tRmUtLUxBTU1QUy0taXByMZSMIzIwMTUtLUVpY2gtUy1NLS1GZS1Dci0tTEFNTVBTLS1pcHIxlIwoMjAxNy0tQmVsYW5kLUwtSy0tRmUtTmktQ3ItLUxBTU1QUy0taXByMZSMIzIwMTctLUNob2ktVy1NLS1Dby1GZS0tTEFNTVBTLS1pcHIxlIwiMjAxNy0tV3UtQy0tTmktQ3ItRmUtLUxBTU1QUy0taXByMZSMHzIwMTctLVd1LUMtLU5pLUZlLS1MQU1NUFMtLWlwcjGUjCwyMDE4LS1DaG9pLVctTS0tQ28tTmktQ3ItRmUtTW4tLUxBTU1QUy0taXByMZSMIzIwMTgtLUV0ZXNhbWktUy1BLS1GZS0tTEFNTVBTLS1pcHIxlIwsMjAxOC0tRmFya2FzLUQtLUZlLU5pLUNyLUNvLUN1LS1MQU1NUFMtLWlwcjKUjCQyMDE4LS1KZW9uZy1HLVUtLVBkLUZlLS1MQU1NUFMtLWlwcjGUjCYyMDE4LS1aaG91LVgtVy0tRmUtTmktQ3ItLUxBTU1QUy0taXByMZSMJjIwMTgtLVpob3UtWC1XLS1GZS1OaS1Dci0tTEFNTVBTLS1pcHIylIwnMjAxOS0tQXNsYW0tSS0tRmUtTW4tU2ktQy0tTEFNTVBTLS1pcHIxlIwmMjAxOS0tQnlnZ21hc3Rhci1KLS1GZS1PLS1MQU1NUFMtLWlwcjGUjCoyMDE5LS1NZW5kZWxldi1NLUktLUZlLU5pLUNyLS1MQU1NUFMtLWlwcjGUjCQyMDIwLS1CeWdnbWFzdGFyLUotLUZlLS1MQU1NUFMtLWlwcjGUjCwyMDIwLS1GYXJrYXMtRC0tRmUtTmktQ3ItQ28tQWwtLUxBTU1QUy0taXByMZSMLDIwMjAtLUdyb2dlci1SLS1Dby1Dci1GZS1Nbi1OaS0tTEFNTVBTLS1pcHIxlIweMjAyMC0tTW9yaS1ILS1GZS0tTEFNTVBTLS1pcHIxlIwvMjAyMS0tRGVsdWlnaS1PLVItLUZlLU5pLUNyLUNvLUN1LS1MQU1NUFMtLWlwcjGUjCIyMDIxLS1TdGFyaWtvdi1TLS1GZS0tTEFNTVBTLS1pcHIxlIwiMjAyMS0tU3Rhcmlrb3YtUy0tRmUtLUxBTU1QUy0taXByMpSMHzIwMjEtLVdlbi1NLS1GZS1ILS1MQU1NUFMtLWlwcjGUjCMyMDIyLS1NYWhhdGEtQS0tQWwtRmUtLUxBTU1QUy0taXByMZSMJzIwMjItLVN0YXJpa292LVMtLUZlLUNyLUgtLUxBTU1QUy0taXByMZSMHTIwMjItLVN1bi1ZLS1GZS0tTEFNTVBTLS1pcHIxlIw6RUFNX0R5bmFtb19BY2tsYW5kQmFjb25DYWxkZXJfMTk5N19GZV9fTU9fMTQyNzk5NzE3NTE2XzAwNZSMQUVBTV9EeW5hbW9fQWNrbGFuZE1lbmRlbGV2U3JvbG92aXR6XzIwMDRfRmVQX19NT184ODQzNDMxNDYzMTBfMDA1lIw7RUFNX0R5bmFtb19Cb25ueUNhc3RpbkJ1bGxlbnNfMjAxM19GZVdfX01PXzczNzU2NzI0MjYzMV8wMDCUjEBFQU1fRHluYW1vX0Jvbm55Q2FzdGluVGVyZW50eWV2XzIwMTNfRmVOaUNyX19NT183NjMxOTc5NDEwMzlfMDAwlIw/RUFNX0R5bmFtb19Cb25ueVBhc2lhbm90Q2FzdGluXzIwMDlfRmVDdU5pX19NT180NjkzNDM5NzMxNzFfMDA1lIw+RUFNX0R5bmFtb19Cb25ueVBhc2lhbm90TWFsZXJiYV8yMDA5X0ZlTmlfX01PXzI2NzcyMTQwODkzNF8wMDWUjEFFQU1fRHluYW1vX0NoYW1hdGlQYXBhbmljb2xhb3VNaXNoaW5fMjAwNl9GZV9fTU9fOTYwNjk5NTEzNDI0XzAwMJSMN0VBTV9EeW5hbW9fSGVwYnVybkFja2xhbmRfMjAwOF9GZUNfX01PXzE0Mzk3NzE1MjcyOF8wMDWUjDBFQU1fRHluYW1vX01hcmluaWNhXzIwMDdfRmVfX01PXzQ2NjgwODg3NzEzMF8wMDCUjDBFQU1fRHluYW1vX01hcmluaWNhXzIwMTFfRmVfX01PXzI1NTMxNTQwNzkxMF8wMDCUjD1FQU1fRHluYW1vX01lbmRlbGV2Qm9yb3Zpa292XzIwMjBfRmVOaUNyX19NT185MjIzNjMzNDA1NzBfMDAwlIw3RUFNX0R5bmFtb19NZW5kZWxldkhhblNvbl8yMDA3X1ZGZV9fTU9fMjQ5NzA2ODEwNTI3XzAwNZSMRkVBTV9EeW5hbW9fTWVuZGVsZXZIYW5Tcm9sb3ZpdHpfMjAwM1BvdGVudGlhbDJfRmVfX01PXzc2OTU4MjM2MzQzOV8wMDWUjEZFQU1fRHluYW1vX01lbmRlbGV2SGFuU3JvbG92aXR6XzIwMDNQb3RlbnRpYWw1X0ZlX19NT185NDI0MjA3MDY4NThfMDA1lIw8RUFNX0R5bmFtb19NZW5kZWxldkhhblNyb2xvdml0el8yMDAzX0ZlX19NT184MDc5OTc4MjY0NDlfMDAwlIxCRUFNX0R5bmFtb19NZW5kZWxldlNyb2xvdml0ekFja2xhbmRfMjAwNV9BbEZlX19NT181Nzc0NTM4OTE5NDFfMDA1lIwwRUFNX0R5bmFtb19NZW5kZWxldl8yMDAzX0ZlX19NT181NDY2NzM1NDkwODVfMDAwlIxJRUFNX0R5bmFtb19aaG91Sm9obnNvbldhZGxleV8yMDA0TklTVHJldGFidWxhdGlvbl9GZV9fTU9fNjgxMDg4Mjk4MjA4XzAwMJSMOUVBTV9EeW5hbW9fWmhvdUpvaG5zb25XYWRsZXlfMjAwNF9GZV9fTU9fNjUwMjc5OTA1MjMwXzAwNZSMRkVBTV9NYWduZXRpYzJHUXVpbnRpY19DaGllc2FEZXJsZXREdWRhcmV2XzIwMTFfRmVfX01PXzE0MDQ0NDMyMTYwN18wMDKUjDxFQU1fTWFnbmV0aWNDdWJpY19EdWRhcmV2RGVybGV0XzIwMDVfRmVfX01PXzEzNTAzNDIyOTI4Ml8wMDKUjENFQU1fTWFnbmV0aWNDdWJpY19NZW5kZWxldkhhblNyb2xvdml0el8yMDAzX0ZlX19NT184NTYyOTU5NTI0MjVfMDAylIw8TUVBTV9MQU1NUFNfQXNhZGlaYWVlbU5vdXJhbmlhbl8yMDE1X0ZlX19NT180OTIzMTA4OTg3NzlfMDAwlIw7TUVBTV9MQU1NUFNfQ2hvaUpvU29obl8yMDE4X0NvTmlDckZlTW5fX01PXzExNTQ1NDc0NzUwM18wMDCUjDZNRUFNX0xBTU1QU19DaG9pS2ltU2VvbF8yMDE3X0NvRmVfX01PXzE3OTE1ODI1NzE4MF8wMDCUjDVNRUFNX0xBTU1QU19FdGVzYW1pQXNhZGlfMjAxOF9GZV9fTU9fNTQ5OTAwMjg3NDIxXzAwMJSMR01FQU1fTEFNTVBTX0plbGluZWtHcm9oSG9yc3RlbWV5ZXJfMjAxMl9BbFNpTWdDdUZlX19NT18yNjI1MTk1MjA2NzhfMDAwlIw2TUVBTV9MQU1NUFNfSmVvbmdQYXJrRG9fMjAxOF9QZEZlX19NT185MjQ3MzY2MjIyMDNfMDAwlIw2TUVBTV9MQU1NUFNfS2ltSnVuZ0xlZV8yMDA5X0ZlVGlDX19NT18xMTAxMTkyMDQ3MjNfMDAwlIw2TUVBTV9MQU1NUFNfS2ltSnVuZ0xlZV8yMDEwX0ZlTmJDX19NT18wNzI2ODk3MTg2MTZfMDAwlIwxTUVBTV9MQU1NUFNfS2ltTGVlXzIwMDZfUHRGZV9fTU9fMzQzMTY4MTAxNDkwXzAwMJSMNU1FQU1fTEFNTVBTX0tpbVNoaW5MZWVfMjAwOV9GZU1uX19NT18wNTg3MzU0MDA0NjJfMDAwlIwyTUVBTV9MQU1NUFNfS29KaW1MZWVfMjAxMl9GZVBfX01PXzE3OTQyMDM2Mzk0NF8wMDCUjDFNRUFNX0xBTU1QU19MZWVKYW5nXzIwMDdfRmVIX19NT18wOTU2MTA5NTE5NTdfMDAwlIwzTUVBTV9MQU1NUFNfTGVlTGVlS2ltXzIwMDZfRmVOX19NT180MzI4NjE3NjY3MzhfMDAwlIwxTUVBTV9MQU1NUFNfTGVlTGVlXzIwMTBfRmVBbF9fTU9fMzMyMjExNTIyMDUwXzAwMJSMN01FQU1fTEFNTVBTX0xlZVdpcnRoU2hpbV8yMDA1X0ZlQ3VfX01PXzA2MzYyNjA2NTQzN18wMDCUjC1NRUFNX0xBTU1QU19MZWVfMjAwNl9GZUNfX01PXzg1Njk1NjE3ODY2OV8wMDCUjDpNRUFNX0xBTU1QU19MaXlhbmFnZUtpbUhvdXplXzIwMTRfRmVDX19NT18wNzUyNzk4MDAxOTVfMDAwlIwwTUVBTV9MQU1NUFNfU2FMZWVfMjAwOF9GZVRpX19NT18yNjA1NDY5Njc3OTNfMDAwlIwwTUVBTV9MQU1NUFNfU2FMZWVfMjAwOF9OYkZlX19NT18xNjIwMzYxNDEyNjFfMDAwlIw0TUVBTV9MQU1NUFNfV3VMZWVTdV8yMDE3X05pQ3JGZV9fTU9fOTEyNjM2MTA3MTA4XzAwMJSMMk1FQU1fTEFNTVBTX1d1TGVlU3VfMjAxN19OaUZlX19NT18zMjEyMzMxNzY0OThfMDAwlIwxTUpfTW9ycmlzQWdhTGV2YXNob3ZfMjAwOF9GZV9fTU9fODU3MjgyNzU0MzA3XzAwM5SMRE1vcnNlX1NoaWZ0ZWRfR2lyaWZhbGNvV2VpemVyXzE5NTlIaWdoQ3V0b2ZmX0ZlX19NT18xNDc2MDMxMjg0MzdfMDA0lIxDTW9yc2VfU2hpZnRlZF9HaXJpZmFsY29XZWl6ZXJfMTk1OUxvd0N1dG9mZl9GZV9fTU9fMzMxMjg1NDk1NjE3XzAwNJSMQ01vcnNlX1NoaWZ0ZWRfR2lyaWZhbGNvV2VpemVyXzE5NTlNZWRDdXRvZmZfRmVfX01PXzk4NDM1ODM0NDE5Nl8wMDSUjD1UZXJzb2ZmX0xBTU1QU19NdWVsbGVyRXJoYXJ0QWxiZV8yMDA3X0ZlX19NT18xMzc5NjQzMTA3MDJfMDAzlIxFU2ltX0xBTU1QU19FQU1DRF9TdHVrb3dza2lTYWRpZ2hFcmhhcnRfMjAwOV9GZUNyX19TTV83NzU1NjQ0OTk1MTNfMDAwlIxBU2ltX0xBTU1QU19FQU1fQm9ubnlDYXN0aW5CdWxsZW5zXzIwMTNfRmVDcldfX1NNXzY5OTI1NzM1MDcwNF8wMDCUjERTaW1fTEFNTVBTX0VBTV9Cb25ueVBhc2lhbm90VGVyZW50eWV2XzIwMTFfRmVDcl9fU01fMjM3MDg5Mjk4NDYzXzAwMJSMQFNpbV9MQU1NUFNfTUVBTV9Bc2FkaVphZWVtTm91cmFuaWFuXzIwMTVfRmVfX1NNXzA0MjYzMDY4MDk5M18wMDCUjDlTaW1fTEFNTVBTX01FQU1fRXRlc2FtaUFzYWRpXzIwMThfRmVfX1NNXzI2NzAxNjYwODc1NV8wMDCUjEtTaW1fTEFNTVBTX01FQU1fSmVsaW5la0dyb2hIb3JzdGVtZXllcl8yMDEyX0FsU2lNZ0N1RmVfX1NNXzY1NjUxNzM1MjQ4NV8wMDCUjDpTaW1fTEFNTVBTX01FQU1fS2ltSnVuZ0xlZV8yMDA5X0ZlVGlDX19TTV81MzEwMzgyNzQ0NzFfMDAwlIw+U2ltX0xBTU1QU19NRUFNX0xpeWFuYWdlS2ltSG91emVfMjAxNF9GZUNfX1NNXzY1MjQyNTc3NzgwOF8wMDCUjEhTaW1fTEFNTVBTX1JlYXhGRl9BcnlhbnBvdXJWYW5EdWluS3ViaWNraV8yMDEwX0ZlSE9fX1NNXzIyMjk2NDIxNjAwMV8wMDGUjEVTaW1fTEFNTVBTX1RlcnNvZmZaQkxfQnlnZ21hc3RhckdyYW5iZXJnXzIwMjBfRmVfX1NNXzk1ODg2Mzg5NTIzNF8wMDCUjE1TaW1fTEFNTVBTX1RlcnNvZmZaQkxfSGVucmlrc3NvbkJqb3JrYXNOb3JkbHVuZF8yMDEzX0ZlQ19fU01fNDczNDYzNDk4MjY5XzAwMJRldS4="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "engine",
+                                "GID": 71,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVjgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjB9weWlyb25fYXRvbWlzdGljcy5sYW1tcHMubGFtbXBzlIwGTGFtbXBzlJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "lammps_output_job"
+                            }
+                        ],
+                        "GID": 67,
+                        "pos x": 450.62668116522633,
+                        "pos y": 519.5054945054945
+                    },
+                    {
+                        "identifier": "SlabStructure_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {},
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "element",
+                                "GID": 74,
+                                "val": "gASVBgAAAAAAAACMAkZllC4=",
+                                "dtype": "DType.String",
+                                "dtype state": "gASViwAAAAAAAAB9lCiMB2RlZmF1bHSUjAJGZZSMA3ZhbJRoAowDZG9jlIwAlIwGYm91bmRzlE6MDXZhbGlkX2NsYXNzZXOUXZQojAhidWlsdGluc5SMA3N0cpSTlIwFbnVtcHmUjARzdHJflJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "surface_type",
+                                "GID": 75,
+                                "val": "gASVCgAAAAAAAACMBmJjYzEwMJQu",
+                                "dtype": "DType.Choice",
+                                "dtype state": "gASV0QAAAAAAAAB9lCiMB2RlZmF1bHSUjAZiY2MxMDCUjAN2YWyUaAKMA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjAphbGxvd19ub25llIiMB2JhdGNoZWSUiYwFaXRlbXOUXZQoaAKMBmJjYzExMJSMBmJjYzExMZSMCmRpYW1vbmQxMDCUjApkaWFtb25kMTExlIwGZmNjMTAwlIwGZmNjMTEwlIwGZmNjMTExlIwGZmNjMjExlIwHaGNwMDAwMZSMCGhjcDEwbTEwlGV1Lg=="
+                            },
+                            {
+                                "type": "data",
+                                "label": "size_a",
+                                "GID": 76,
+                                "val": "gARLAS4=",
+                                "dtype": "DType.Integer",
+                                "dtype state": "gASViwAAAAAAAAB9lCiMB2RlZmF1bHSUSwGMA3ZhbJRLAYwDZG9jlIwAlIwGYm91bmRzlE6MDXZhbGlkX2NsYXNzZXOUXZQojAhidWlsdGluc5SMA2ludJSTlIwFbnVtcHmUjAdpbnRlZ2VylJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "size_b",
+                                "GID": 77,
+                                "val": "gARLAS4=",
+                                "dtype": "DType.Integer",
+                                "dtype state": "gASViwAAAAAAAAB9lCiMB2RlZmF1bHSUSwGMA3ZhbJRLAYwDZG9jlIwAlIwGYm91bmRzlE6MDXZhbGlkX2NsYXNzZXOUXZQojAhidWlsdGluc5SMA2ludJSTlIwFbnVtcHmUjAdpbnRlZ2VylJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "size_c",
+                                "GID": 78,
+                                "val": "gARLAS4=",
+                                "dtype": "DType.Integer",
+                                "dtype state": "gASViwAAAAAAAAB9lCiMB2RlZmF1bHSUSwGMA3ZhbJRLAYwDZG9jlIwAlIwGYm91bmRzlE6MDXZhbGlkX2NsYXNzZXOUXZQojAhidWlsdGluc5SMA2ludJSTlIwFbnVtcHmUjAdpbnRlZ2VylJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "vacuum",
+                                "GID": 79,
+                                "val": "gASVCgAAAAAAAABHQCQAAAAAAAAu",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVqQAAAAAAAAB9lCiMB2RlZmF1bHSUR0AkAAAAAAAAjAN2YWyUR0AkAAAAAAAAjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwFZmxvYXSUk5SMBW51bXB5lIwIZmxvYXRpbmeUk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UjAhkZWNpbWFsc5RLCnUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "center",
+                                "GID": 80,
+                                "val": "gASJLg==",
+                                "dtype": "DType.Boolean",
+                                "dtype state": "gASViAAAAAAAAAB9lCiMB2RlZmF1bHSUiYwDdmFslImMA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjARib29slJOUjAVudW1weZSMBWJvb2xflJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "orthogonal",
+                                "GID": 81,
+                                "val": "gASILg==",
+                                "dtype": "DType.Boolean",
+                                "dtype state": "gASViAAAAAAAAAB9lCiMB2RlZmF1bHSUiIwDdmFslIiMA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjARib29slJOUjAVudW1weZSMBWJvb2xflJOUZYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu"
+                            },
+                            {
+                                "type": "data",
+                                "label": "a",
+                                "GID": 82,
+                                "val": "gAROLg==",
+                                "dtype": "DType.Float",
+                                "dtype state": "gASVmQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UKIwIYnVpbHRpbnOUjAVmbG9hdJSTlIwFbnVtcHmUjAhmbG9hdGluZ5STlGWMCmFsbG93X25vbmWUiIwHYmF0Y2hlZJSJjARzaXpllIwBbZSMCGRlY2ltYWxzlEsKdS4="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "structure",
+                                "GID": 83,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASVmgAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjCxweWlyb25fYXRvbWlzdGljcy5hdG9taXN0aWNzLnN0cnVjdHVyZS5hdG9tc5SMBUF0b21zlJOUYYwKYWxsb3dfbm9uZZSJjAdiYXRjaGVklImMBHNpemWUjAFtlHUu",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "surface_structure_output_structure"
+                            }
+                        ],
+                        "GID": 73,
+                        "pos x": 114.38218966405312,
+                        "pos y": 482.14285714285717
+                    },
+                    {
+                        "identifier": "Project_Node",
+                        "version": null,
+                        "state data": "gAR9lC4=",
+                        "additional data": {},
+                        "inputs": [
+                            {
+                                "type": "data",
+                                "label": "name",
+                                "GID": 86,
+                                "val": "gASVCwAAAAAAAACMB3N1cmZhY2WULg==",
+                                "dtype": "DType.String",
+                                "dtype state": "gASVigAAAAAAAAB9lCiMB2RlZmF1bHSUjAEulIwDdmFslGgCjANkb2OUjACUjAZib3VuZHOUTowNdmFsaWRfY2xhc3Nlc5RdlCiMCGJ1aWx0aW5zlIwDc3RylJOUjAVudW1weZSMBHN0cl+Uk5RljAphbGxvd19ub25llImMB2JhdGNoZWSUiYwEc2l6ZZSMAW2UdS4="
+                            }
+                        ],
+                        "outputs": [
+                            {
+                                "type": "data",
+                                "label": "project",
+                                "GID": 87,
+                                "dtype": "DType.Data",
+                                "dtype state": "gASViQAAAAAAAAB9lCiMB2RlZmF1bHSUTowDdmFslE6MA2RvY5SMAJSMBmJvdW5kc5ROjA12YWxpZF9jbGFzc2VzlF2UjBlweWlyb25fYXRvbWlzdGljcy5wcm9qZWN0lIwHUHJvamVjdJSTlGGMCmFsbG93X25vbmWUiYwHYmF0Y2hlZJSJjARzaXpllIwBbZR1Lg==",
+                                "otype_namespace": "atomistics",
+                                "otype_name": "project_output_atomistics_project"
+                            }
+                        ],
+                        "GID": 85,
+                        "pos x": 120.97521890917416,
+                        "pos y": 22.802197802197803
+                    }
+                ],
+                "connections": [
+                    {
+                        "GID": 90,
+                        "parent node index": 1,
+                        "output port index": 0,
+                        "connected node": 0,
+                        "connected input port index": 1
+                    },
+                    {
+                        "GID": 33,
+                        "parent node index": 2,
+                        "output port index": 2,
+                        "connected node": 1,
+                        "connected input port index": 1
+                    },
+                    {
+                        "GID": 49,
+                        "parent node index": 3,
+                        "output port index": 2,
+                        "connected node": 1,
+                        "connected input port index": 3
+                    },
+                    {
+                        "GID": 55,
+                        "parent node index": 4,
+                        "output port index": 0,
+                        "connected node": 2,
+                        "connected input port index": 3
+                    },
+                    {
+                        "GID": 66,
+                        "parent node index": 5,
+                        "output port index": 0,
+                        "connected node": 4,
+                        "connected input port index": 1
+                    },
+                    {
+                        "GID": 91,
+                        "parent node index": 5,
+                        "output port index": 0,
+                        "connected node": 1,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 72,
+                        "parent node index": 6,
+                        "output port index": 0,
+                        "connected node": 3,
+                        "connected input port index": 3
+                    },
+                    {
+                        "GID": 84,
+                        "parent node index": 7,
+                        "output port index": 0,
+                        "connected node": 6,
+                        "connected input port index": 1
+                    },
+                    {
+                        "GID": 92,
+                        "parent node index": 7,
+                        "output port index": 0,
+                        "connected node": 1,
+                        "connected input port index": 2
+                    },
+                    {
+                        "GID": 88,
+                        "parent node index": 8,
+                        "output port index": 0,
+                        "connected node": 4,
+                        "connected input port index": 0
+                    },
+                    {
+                        "GID": 89,
+                        "parent node index": 8,
+                        "output port index": 0,
+                        "connected node": 6,
+                        "connected input port index": 0
+                    }
+                ],
+                "GID": 7
+            },
+            "GID": 1
+        }
+    ]
+}
\ No newline at end of file
diff --git a/notebooks/surface_energy.ipynb b/notebooks/surface_energy.ipynb
new file mode 100644
index 00000000..fd06b5ea
--- /dev/null
+++ b/notebooks/surface_energy.ipynb
@@ -0,0 +1,120 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "fa19547f-83b4-43fa-8170-04a65d362d0e",
+   "metadata": {},
+   "source": [
+    "This is the second tutorial on interactive workflows; if you haven't gone through the bulk modulus example, start there."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "0baf925b-224c-415d-9d5d-6613d68af0f5",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from ironflow import GUI"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "4cda3c84-8a13-4a57-8ff1-5275cdc8870b",
+   "metadata": {},
+   "source": [
+    "# Steps\n",
+    "\n",
+    "This example is very similar to the example for bulk modulus:\n",
+    "\n",
+    "1) Start by placing a `MaterialProperty` node from the `atomistics_nodes` menu; this time we'll select `SurfaceEnergy` as the property.\n",
+    "2) Switch the node placement menu to `recommended`, and follow the ontological guidance to place the remaining nodes.\n",
+    "    - Make sure to connect the inputs of your downstream nodes to the suggested outputs of the upstream nodes as you, this way all the necessary downstream ontological requirements will get passed upstream to give you the most specific node recommendations possible.\n",
+    "    - There are three things to note when choosing the node to generate the `bulk_energy` and `surface_energy` input for the `SurfaceEnergy` node: \n",
+    "        - First, you have a variety of choices for what type of calculation to use -- for this example use `CalcMinimize`\n",
+    "        - Second, you will need to place _two_ instances of this node -- one for each energy you want to generate. Once their energy output is connected to an energy input on `SurfaceEnergy`, the additional requirements will be passed upstream when placing further nodes.\n",
+    "        - Finally, because you have two instances of `CalcMinimize`, we'll need to update the `name` used by at least one of them so they don't step on each others' toes. Use whatever names you like, as long as they're different.\n",
+    "3) Before running any jobs, you can play around with the ontological recommendations; e.g. if you connect both of the energy inputs for `SurfaceEnergy` to the `energy_pot` output of the same `CalcMinimize`, you will see all the upstream ontologically-typed structure ports turn red since the transitive requirements from both energy input ports cannot be satisfied at the same time!\n",
+    "4) After running the calculation, `remove` both calculations, and adjust the thickness of the slab (`size_c`) or type of surface and recalculate\n",
+    "    - Note: some of the surfaces require particular `size` values -- if the surface doesn't generate automatically you can check the log tab to see what the problem was and fix it by adjusting your input values."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "90415f81-3f7a-484e-84c6-d061882e7523",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "gui = GUI('live_surface_energy')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e5ddc2f0-ea11-443b-a674-585ad0e4573a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "gui.draw()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "19437580-1a55-4226-9b25-54f476a0b85a",
+   "metadata": {},
+   "source": [
+    "As before, you can also load a pre-constructed graph"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "182d27d6-5c29-4b27-948a-b30125950cd6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "premade_gui = GUI('premade_surface_energy')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "77a3c464-6917-4a64-8a6c-ed23f9a996f1",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "premade_gui.draw()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "eb203ecd-5a83-4afa-a842-cd6c62c6c0f4",
+   "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"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/setup.py b/setup.py
index 5256a1dd..d85dff34 100644
--- a/setup.py
+++ b/setup.py
@@ -34,9 +34,11 @@
         'matplotlib',
         'nglview',
         'numpy',
+        'owlready2',
         'pyiron_base',
         'pyiron_atomistics',
         'pyiron_gui >= 0.0.8',
+        'pyiron_ontology == 0.1.1',
         'ryvencore',
         'seaborn',
         'traitlets',


Test patch:
diff --git a/tests/integration/test_mutability.py b/tests/integration/test_mutability.py
index a9f712a8..ccb09bc8 100644
--- a/tests/integration/test_mutability.py
+++ b/tests/integration/test_mutability.py
@@ -4,9 +4,10 @@
 
 from unittest import TestCase
 
-from ironflow.model import dtypes, NodeInputBP
+from ironflow.model import dtypes
 from ironflow.model.model import HasSession
 from ironflow.model.node import Node
+from ironflow.model.port import NodeInputBP
 
 
 class Choice_Node(Node):