Skip to content

Commit 9f5e2bb

Browse files
authored
Trajectory viewer (#18)
1 parent c2a83b8 commit 9f5e2bb

4 files changed

Lines changed: 47 additions & 6 deletions

File tree

src/alc_aiidalab_widgets/viewers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# AiiDA data type labels
1515
"data.core.structure.StructureData.": StructureViewWidget,
1616
"data.core.singlefile.SinglefileData.": SinglefileDataViewer,
17+
"data.core.array.trajectory.TrajectoryData.": StructureViewWidget,
1718
# Custom redirections
1819
"xyz_table": XYZArrayDataTableWidget,
1920
}

src/alc_aiidalab_widgets/widgets/file_handling.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ def _on_file_upload(self, _):
6565
self.file = self.get_aiida_file_object()
6666
else:
6767
self.file_handle.value = ""
68-
print(self.file_dict)
6968
return
7069

7170
def get_file_contents(self) -> BytesIO | None:

src/alc_aiidalab_widgets/widgets/structure.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from pathlib import Path
44
from tempfile import NamedTemporaryFile
55

6-
from aiida.orm import Node, SinglefileData, StructureData
6+
from aiida.orm import Node, SinglefileData, StructureData, TrajectoryData
77
from ase import Atoms
88
from ase import io as ase_io
99
from ipywidgets import HTML, VBox
@@ -25,6 +25,8 @@ def __init__(self, node: Node | None = None, **kwargs):
2525
self.assign_structure_from_structuredata(node)
2626
elif isinstance(node, SinglefileData):
2727
self.assign_structure_from_file(node.filename, node.content)
28+
elif isinstance(node, TrajectoryData):
29+
self.assign_structure_from_trajectorydata(node)
2830
elif node:
2931
self.message.value = (
3032
"<p>AiiDA Node type not supported by the structure viewer."
@@ -61,13 +63,13 @@ def assign_structure_from_file(self, fname: str, content: bytes) -> None:
6163
]
6264
return
6365

64-
def assign_structure_from_ase(self, structure: Atoms) -> None:
66+
def assign_structure_from_ase(self, structure: Atoms | list[Atoms]) -> None:
6567
"""Visualise the given ASE structure.
6668
6769
Parameters
6870
----------
69-
structure: Atoms
70-
The ASE atoms structure object.
71+
structure: Atoms | list[Atoms]
72+
The ASE atoms structure(s) object.
7173
"""
7274
self.viewer = WeasWidget()
7375
self.viewer.from_ase(structure)
@@ -86,3 +88,22 @@ def assign_structure_from_structuredata(self, structure: StructureData) -> None:
8688
"""
8789
self.assign_structure_from_ase(structure._get_object_ase())
8890
return
91+
92+
def assign_structure_from_trajectorydata(self, trajectory: TrajectoryData) -> None:
93+
"""
94+
Visualise a series of structures contained in an AiiDA TrajectoryData node.
95+
96+
Parameters
97+
----------
98+
trajectory: TrajectoryData
99+
The AiiDA TrajectoryData node containing the structure series to visualise.
100+
"""
101+
symbols = trajectory.symbols
102+
positions = trajectory.get_positions()
103+
nsteps = trajectory.numsteps
104+
atoms = []
105+
for i in range(nsteps):
106+
step = Atoms(symbols=symbols, positions=positions[i])
107+
atoms.append(step)
108+
self.assign_structure_from_ase(atoms)
109+
return

tests/test_structure.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from pathlib import Path
44
from tempfile import NamedTemporaryFile
55

6-
from aiida.orm import SinglefileData, StructureData
6+
from aiida.orm import SinglefileData, StructureData, TrajectoryData
77
from ipywidgets import HTML
88
from weas_widget import WeasWidget
99

@@ -69,6 +69,26 @@ def test_structure_initialise_from_singlefiledata():
6969
assert structure_ase.get_atomic_numbers()[2] == 1
7070

7171

72+
def test_structure_initialise_from_trajectorydata():
73+
"""Create a WeasWidget viewer initialised with an AiiDA TrajectoryData node."""
74+
structure_1 = StructureData()
75+
with open(Path(__file__).resolve().parent / "data/water.xyz") as f:
76+
structure_1._parse_xyz(f.read())
77+
structure_2 = StructureData()
78+
with open(Path(__file__).resolve().parent / "data/water.xyz") as f:
79+
structure_2._parse_xyz(f.read())
80+
trajectory = TrajectoryData()
81+
trajectory.set_structurelist([structure_1, structure_2])
82+
widget = StructureViewWidget(trajectory)
83+
assert isinstance(widget.viewer, WeasWidget)
84+
assert len(widget.children) == 1
85+
structure_ase = widget.viewer.to_ase()
86+
assert len(structure_ase) == 2
87+
assert structure_ase[0].get_atomic_numbers()[0] == 8
88+
assert structure_ase[0].get_atomic_numbers()[1] == 1
89+
assert structure_ase[0].get_atomic_numbers()[2] == 1
90+
91+
7292
def test_invalid_file_type():
7393
"""Load an invalid filetype into a StructureViewWidget."""
7494
widget = StructureViewWidget()

0 commit comments

Comments
 (0)