Skip to content

Commit b294468

Browse files
committed
draft gui for constraints
1 parent fae0013 commit b294468

File tree

3 files changed

+187
-1
lines changed

3 files changed

+187
-1
lines changed

src/aiidalab_qe/app/structure/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from aiidalab_qe.app.structure.model import StructureStepModel
1111
from aiidalab_qe.app.utils import get_entry_items
1212
from aiidalab_qe.common import (
13+
AddingFixedAtomsEditor,
1314
AddingTagsEditor,
1415
LazyLoadedOptimade,
1516
LazyLoadedStructureBrowser,
@@ -106,6 +107,7 @@ def _render(self):
106107
BasicCellEditor(title="Edit cell"),
107108
BasicStructureEditor(title="Edit structure"),
108109
AddingTagsEditor(title="Edit atom tags"),
110+
AddingFixedAtomsEditor(title="Edit fix. atoms"),
109111
PeriodicityEditor(title="Edit periodicity"),
110112
ShakeNBreakEditor(title="ShakeNBreak"),
111113
]

src/aiidalab_qe/common/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
from .node_view import CalcJobNodeViewerWidget # noqa: F401
33
from .process import QeAppWorkChainSelector, WorkChainSelector
44
from .widgets import (
5+
AddingFixedAtomsEditor,
56
AddingTagsEditor,
67
LazyLoadedOptimade,
78
LazyLoadedStructureBrowser,
89
PeriodicityEditor,
910
ShakeNBreakEditor,
1011
)
11-
12+
1213
__all__ = [
14+
"AddingFixedAtomsEditor",
1315
"AddingTagsEditor",
1416
"LazyLoadedOptimade",
1517
"LazyLoadedStructureBrowser",

src/aiidalab_qe/common/widgets.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,188 @@ def _reset_all_tags(self, _=None):
625625
self.input_selection = None
626626
self.input_selection = deepcopy(self.selection)
627627

628+
class AddingFixedAtomsEditor(ipw.VBox):
629+
"""Editor for adding tags to atoms."""
630+
631+
structure = traitlets.Instance(ase.Atoms, allow_none=True)
632+
selection = traitlets.List(traitlets.Int(), allow_none=True)
633+
input_selection = traitlets.List(traitlets.Int(), allow_none=True)
634+
structure_node = traitlets.Instance(orm_Data, allow_none=True, read_only=True)
635+
636+
def __init__(self, title="", **kwargs):
637+
self.title = title
638+
639+
self._status_message = StatusHTML()
640+
self.atom_selection = ipw.Text(
641+
placeholder="e.g. 1..5 8 10",
642+
description="Index of atoms",
643+
value="",
644+
style={"description_width": "100px"},
645+
layout={"width": "initial"},
646+
)
647+
self.from_selection = ipw.Button(description="From selection")
648+
self.from_selection.on_click(self._from_selection)
649+
self.fixed = ipw.BoundedIntText(
650+
description="Fixed atoms", value=1, min=0, max=11, layout={"width": "initial"}
651+
)
652+
self.add_fixed = ipw.Button(
653+
description="Update fixed atoms",
654+
button_style="primary",
655+
layout={"width": "initial"},
656+
)
657+
658+
self.reset_fixed = ipw.Button(
659+
description="Reset fixed atoms",
660+
button_style="primary",
661+
layout={"width": "initial"},
662+
)
663+
self.reset_all_fixed = ipw.Button(
664+
description="Reset all fixed atoms",
665+
button_style="warning",
666+
layout={"width": "initial"},
667+
)
668+
self.scroll_note = ipw.HTML(
669+
value="<p style='font-style: italic;'>Note: The table is scrollable.</p>",
670+
layout={"visibility": "hidden"},
671+
)
672+
self.fixed_display = ipw.Output()
673+
self.add_fixed.on_click(self._add_fixed)
674+
self.reset_fixed.on_click(self._reset_fixed)
675+
self.reset_all_fixed.on_click(self._reset_all_fixed)
676+
self.atom_selection.observe(self._display_table, "value")
677+
self.add_fixed.on_click(self._display_table)
678+
self.reset_fixed.on_click(self._display_table)
679+
self.reset_all_fixed.on_click(self._display_table)
680+
681+
super().__init__(
682+
children=[
683+
ipw.HTML(
684+
"""
685+
<p>
686+
Fix x,y,z for selected atoms. <br>
687+
For example, 0 1 0 for a given atoms means the atom can move only in y.
688+
</p>
689+
<p style="font-weight: bold; color: #1f77b4;">NOTE:</p>
690+
<ul style="padding-left: 2em; list-style-type: disc;">
691+
<li>Atom indices start from 1, not 0. This means that the first atom in the list is numbered 1, the second atom is numbered 2, and so on.</li>
692+
</ul>
693+
</p>
694+
"""
695+
),
696+
ipw.HBox(
697+
[
698+
self.atom_selection,
699+
self.from_selection,
700+
self.fixed,
701+
]
702+
),
703+
self.fixed_display,
704+
self.scroll_note,
705+
ipw.HBox([self.add_fixed, self.reset_fixed, self.reset_all_fixed]),
706+
self._status_message,
707+
],
708+
**kwargs,
709+
)
710+
711+
def _display_table(self, _=None):
712+
"""Function to control fixed_display
713+
When given a list of atom in selection it will display a HTML table with Index, Element and fixed coordinates
714+
"""
715+
selection = string_range_to_list(self.atom_selection.value)[0]
716+
selection = [s for s in selection if s < len(self.structure)]
717+
current_fixed = self.structure.get_fixed()
718+
chemichal_symbols = self.structure.get_chemical_symbols()
719+
720+
if selection and (min(selection) >= 0):
721+
table_data = []
722+
for index in selection:
723+
fixed = current_fixed[index]
724+
symbol = chemichal_symbols[index]
725+
if fixed == 0:
726+
fixed = ""
727+
table_data.append([f"{index + 1}", f"{symbol}", f"{fixed}"])
728+
729+
# Create an HTML table
730+
table_html = "<table>"
731+
table_html += "<tr><th>Index</th><th>Element</th><th>Fixed</th></tr>"
732+
for row in table_data:
733+
table_html += "<tr>"
734+
for cell in row:
735+
table_html += f"<td>{cell}</td>"
736+
table_html += "</tr>"
737+
table_html += "</table>"
738+
739+
# Set layout to a fix size
740+
self.fixed_display.layout = {
741+
"overflow": "auto",
742+
"height": "100px",
743+
"width": "150px",
744+
}
745+
with self.fixed_display:
746+
clear_output()
747+
display(HTML(table_html))
748+
self.scroll_note.layout = {"visibility": "visible"}
749+
else:
750+
self.fixed_display.layout = {}
751+
with self.fixed_display:
752+
clear_output()
753+
self.scroll_note.layout = {"visibility": "hidden"}
754+
755+
def _from_selection(self, _=None):
756+
"""Set the atom selection from the current selection."""
757+
self.atom_selection.value = list_to_string_range(self.selection)
758+
759+
def _add_fixed(self, _=None):
760+
"""Add fixed coordinates to the selected atoms."""
761+
if not self.atom_selection.value:
762+
self._status_message.message = """
763+
<div class="alert alert-info">
764+
<strong>Please select atoms first.</strong>
765+
</div>
766+
"""
767+
else:
768+
selection = string_range_to_list(self.atom_selection.value)[0]
769+
new_structure = deepcopy(self.structure)
770+
if not new_structure.get_fixed().any():
771+
new_fixed = np.zeros(len(new_structure))
772+
else:
773+
new_fixed = new_structure.get_fixed()
774+
new_fixed[selection] = self.fixed.value
775+
new_structure.set_fixed(new_fixed)
776+
self.structure = None
777+
self.structure = deepcopy(new_structure)
778+
self.input_selection = None
779+
self.input_selection = deepcopy(self.selection)
780+
781+
def _reset_fixed(self, _=None):
782+
"""Clear fixed coordinates from selected atoms."""
783+
if not self.atom_selection.value:
784+
self._status_message.message = """
785+
<div class="alert alert-info">
786+
<strong>Please select atoms first.</strong>
787+
</div>
788+
"""
789+
else:
790+
selection = string_range_to_list(self.atom_selection.value)[0]
791+
new_structure = deepcopy(self.structure)
792+
new_fixed = new_structure.get_fixed()
793+
new_fixed[selection] = 0
794+
new_structure.set_fixed(new_fixed)
795+
self.structure = None
796+
self.structure = deepcopy(new_structure)
797+
self.input_selection = None
798+
self.input_selection = deepcopy(self.selection)
799+
800+
def _reset_all_fixed(self, _=None):
801+
"""Clear all fixed coordinates."""
802+
new_structure = deepcopy(self.structure)
803+
new_fixed = np.zeros(len(new_structure))
804+
new_structure.set_fixed(new_fixed)
805+
self.structure = None
806+
self.structure = deepcopy(new_structure)
807+
self.input_selection = None
808+
self.input_selection = deepcopy(self.selection)
809+
628810

629811
class PeriodicityEditor(ipw.VBox):
630812
"""Editor for changing periodicity of structures."""

0 commit comments

Comments
 (0)