From e3ce568640da29c664cc65d63c2f44784c4fa386 Mon Sep 17 00:00:00 2001 From: Carlo Date: Fri, 14 Oct 2022 15:44:13 +0000 Subject: [PATCH 01/87] draft --- aiidalab_widgets_base/viewers.py | 210 +++++++++++++++++++++++++++---- 1 file changed, 185 insertions(+), 25 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 77897bdf..29252bf6 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -190,6 +190,7 @@ def __init__( self._viewer.camera = default_camera self._viewer.observe(self._on_atom_click, names="picked") self._viewer.stage.set_parameters(mouse_preset="pymol") + self.vis_dict={} view_box = ipw.VBox([self._viewer]) @@ -335,8 +336,78 @@ def change_camera(change): center_button = ipw.Button(description="Center molecule") center_button.on_click(lambda c: self._viewer.center()) + # "b": { + # "ids": "set_ids", + # "aspectRatio": 4, + # "highlight_aspectRatio": 4.1, + # "highlight_color": "green", + # "highlight_opacity": 0.6, + # "name": "bulk", + # "type": "ball+stick", + # }, + + # representations + def apply_representations(change): + DEFAULT_REPRESENTATIONS={ "molecule": { + "ids": "1..2", + "aspectRatio": 3.5, + "highlight_aspectRatio": 3.6, + "highlight_color": "red", + "highlight_opacity": 0.6, + "name": "molecule", + "type": "ball+stick", + }, + "surface": { + "ids": "1..2", + "aspectRatio": 5, + "highlight_aspectRatio": 5, + "highlight_color": "green", + "highlight_opacity": 0.6, + "name": "surface", + "type": "ball+stick", + },} + #iterate on number of representations + self.vis_dict={} + current_rep=0 + for sel,rep in [(self._selected_atoms_r1.value , self.rep1.value),(self._selected_atoms_r2.value,self.rep2.value)]: + if sel: + self.vis_dict[current_rep] = DEFAULT_REPRESENTATIONS[rep] + self.vis_dict[current_rep]["ids"] = sel + self.vis_dict[current_rep]["name"] = "rep"+str(current_rep) + current_rep+=1 + self.update_viewer() + + apply_rep = ipw.Button(description="Apply rep") + apply_rep.on_click(apply_representations) + + self.rep1 = ipw.Dropdown( + #options=["ball+stick", "licorice", "spacefill", "surface"], + options=["molecule","surface"], + value="molecule", + description="Rep1", + disabled=False, + ) + self.rep2 = ipw.Dropdown( + #options=["ball+stick", "licorice", "spacefill", "surface"], + options=["molecule","surface"], + value="molecule", + description="Rep2", + disabled=False, + ) + self._selected_atoms_r1 = ipw.Text( + description="atoms rep 1:", + value="", + style={"description_width": "initial"}, + ) + self._selected_atoms_r2 = ipw.Text( + description="atoms rep 2:", + value="", + style={"description_width": "initial"}, + ) + return ipw.VBox( - [supercell_selector, background_color, camera_type, center_button] + [supercell_selector, background_color, camera_type, + ipw.HBox([self._selected_atoms_r1,self.rep1]), ipw.HBox([self._selected_atoms_r2,self.rep2]), apply_rep, center_button] ) @observe("cell") @@ -623,6 +694,32 @@ def _render_structure(self, change=None): self._download(payload=payload, filename=fname) self.render_btn.disabled = False + def _gen_translation_indexes(self): + """Transfromation of indexes in case of multiple representations + dictionaries for back and forth transformations.""" + + self._translate_i_glob_loc = {} + self._translate_i_loc_glob = {} + for component in range(len(self.vis_dict.keys())): + comp_i = 0 + ids = list( + string_range_to_list(self.vis_dict[component]["ids"], shift=0)[0] + ) + for i_g in ids: + self._translate_i_glob_loc[i_g] = (component, comp_i) + self._translate_i_loc_glob[(component, comp_i)] = i_g + comp_i += 1 + + def _translate_glob_loc(self, indexes): + """From global index to indexes of different components.""" + all_comp = [list() for i in range(len(self.vis_dict.keys()))] + print("all_comp ",all_comp) + for i_g in indexes: + i_c, i_a = self._translate_i_glob_loc[i_g] + all_comp[i_c].append(i_a) + + return all_comp + def _on_atom_click(self, _=None): """Update selection when clicked on atom.""" if "atom1" not in self._viewer.picked.keys(): @@ -638,7 +735,8 @@ def _on_atom_click(self, _=None): else: selection = [index] - self.selection = selection + self.selection = selection + def highlight_atoms( self, @@ -648,18 +746,90 @@ def highlight_atoms( opacity=DEFAULT_SELECTION_OPACITY, ): """Highlighting atoms according to the provided list.""" + print(vis_list) if not hasattr(self._viewer, "component_0"): return - self._viewer._remove_representations_by_name( - repr_name="selected_atoms" - ) # pylint:disable=protected-access - self._viewer.add_ball_and_stick( # pylint:disable=no-member - name="selected_atoms", - selection=list() if vis_list is None else vis_list, - color=color, - aspectRatio=size, - opacity=opacity, - ) + + if self.vis_dict is None: + self._viewer._remove_representations_by_name( + repr_name="selected_atoms" + ) # pylint:disable=protected-access + self._viewer.add_ball_and_stick( # pylint:disable=no-member + name="selected_atoms", + selection=list() if vis_list is None else vis_list, + color=color, + aspectRatio=size, + opacity=opacity, + ) + else: + + ncomponents = len(self.vis_dict.keys()) + for component in range(ncomponents): + name = "highlight_" + self.vis_dict[component]["name"] + self._viewer._remove_representations_by_name( + repr_name=name, component=component + ) + color = self.vis_dict[component]["highlight_color"] + aspectRatio = self.vis_dict[component]["highlight_aspectRatio"] + opacity = self.vis_dict[component]["highlight_opacity"] + if vis_list is None: + self._viewer.add_ball_and_stick( + name=name, + selection=list(), + color=color, + aspectRatio=aspectRatio, + opacity=opacity, + component=component, + ) + else: + all_comp = self._translate_glob_loc(vis_list) + selection = all_comp[component] + self._viewer.add_ball_and_stick( + name=name, + selection=selection, + color=color, + aspectRatio=aspectRatio, + opacity=opacity, + component=component, + ) + def update_viewer(self, c=None): + with self.hold_trait_notifications(): + + while hasattr(self._viewer, "component_0"): + self._viewer.component_0.clear_representations() + cid = self._viewer.component_0.id + self._viewer.remove_component(cid) + + # self.vis_dict = vis_dict + for component in range(len(self.vis_dict)): + + rep_indexes = list( + string_range_to_list(self.vis_dict[component]["ids"], shift=-1)[ + 0 + ] + ) + if rep_indexes: + mol = self.displayed_structure[rep_indexes] + + self._viewer.add_component( + nglview.ASEStructure(mol), default_representation=False + ) + + if self.vis_dict[component]["type"] == "ball+stick": + print("ball ",component) + aspectRatio = self.vis_dict[component]["aspectRatio"] + self._viewer.add_ball_and_stick( + aspectRatio=aspectRatio, + opacity=1.0, + component=component, + ) + elif self.vis_dict[component]["type"] == "licorice": + self._viewer.add_licorice(opacity=1.0, component=component) + elif self.vis_dict[component]["type"] == "hyperball": + self._viewer.add_hyperball(opacity=1.0, component=component) + self._gen_translation_indexes() + self._viewer.add_unitcell() + self._viewer.center() @default("supercell") def _default_supercell(self): @@ -802,19 +972,9 @@ def _observe_structure(self, change): @observe("displayed_structure") def _update_structure_viewer(self, change): """Update the view if displayed_structure trait was modified.""" - with self.hold_trait_notifications(): - for ( - comp_id - ) in self._viewer._ngl_component_ids: # pylint: disable=protected-access - self._viewer.remove_component(comp_id) - self.selection = list() - if change["new"] is not None: - self._viewer.add_component(nglview.ASEStructure(change["new"])) - self._viewer.clear() - self._viewer.add_ball_and_stick( - aspectRatio=4 - ) # pylint: disable=no-member - self._viewer.add_unitcell() # pylint: disable=no-member + self.update_viewer() + + def d_from(self, operand): point = np.array([float(i) for i in operand[1:-1].split(",")]) From 6768950316ba90b181b82cdf065f66700b410b32 Mon Sep 17 00:00:00 2001 From: Carlo Pignedoli Date: Sat, 15 Oct 2022 08:13:49 +0000 Subject: [PATCH 02/87] work in progress --- aiidalab_widgets_base/viewers.py | 98 ++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 29252bf6..3828d885 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -190,7 +190,25 @@ def __init__( self._viewer.camera = default_camera self._viewer.observe(self._on_atom_click, names="picked") self._viewer.stage.set_parameters(mouse_preset="pymol") - self.vis_dict={} + self.rep_dict={} + self.default_representations={ "molecule": { + "ids": "1..2", + "aspectRatio": 3.5, + "highlight_aspectRatio": 3.6, + "highlight_color": "red", + "highlight_opacity": 0.6, + "name": "molecule", + "type": "ball+stick", + }, + "surface": { + "ids": "1..2", + "aspectRatio": 5, + "highlight_aspectRatio": 5, + "highlight_color": "green", + "highlight_opacity": 0.6, + "name": "surface", + "type": "ball+stick", + },} view_box = ipw.VBox([self._viewer]) @@ -348,32 +366,14 @@ def change_camera(change): # representations def apply_representations(change): - DEFAULT_REPRESENTATIONS={ "molecule": { - "ids": "1..2", - "aspectRatio": 3.5, - "highlight_aspectRatio": 3.6, - "highlight_color": "red", - "highlight_opacity": 0.6, - "name": "molecule", - "type": "ball+stick", - }, - "surface": { - "ids": "1..2", - "aspectRatio": 5, - "highlight_aspectRatio": 5, - "highlight_color": "green", - "highlight_opacity": 0.6, - "name": "surface", - "type": "ball+stick", - },} #iterate on number of representations - self.vis_dict={} + self.rep_dict={} current_rep=0 for sel,rep in [(self._selected_atoms_r1.value , self.rep1.value),(self._selected_atoms_r2.value,self.rep2.value)]: if sel: - self.vis_dict[current_rep] = DEFAULT_REPRESENTATIONS[rep] - self.vis_dict[current_rep]["ids"] = sel - self.vis_dict[current_rep]["name"] = "rep"+str(current_rep) + self.rep_dict[current_rep] = self.default_representations[rep] + self.rep_dict[current_rep]["ids"] = sel + self.rep_dict[current_rep]["name"] = "rep"+str(current_rep) current_rep+=1 self.update_viewer() @@ -700,10 +700,10 @@ def _gen_translation_indexes(self): self._translate_i_glob_loc = {} self._translate_i_loc_glob = {} - for component in range(len(self.vis_dict.keys())): + for component in range(len(self.rep_dict.keys())): comp_i = 0 ids = list( - string_range_to_list(self.vis_dict[component]["ids"], shift=0)[0] + string_range_to_list(self.rep_dict[component]["ids"], shift=0)[0] ) for i_g in ids: self._translate_i_glob_loc[i_g] = (component, comp_i) @@ -712,7 +712,7 @@ def _gen_translation_indexes(self): def _translate_glob_loc(self, indexes): """From global index to indexes of different components.""" - all_comp = [list() for i in range(len(self.vis_dict.keys()))] + all_comp = [list() for i in range(len(self.rep_dict.keys()))] print("all_comp ",all_comp) for i_g in indexes: i_c, i_a = self._translate_i_glob_loc[i_g] @@ -725,6 +725,13 @@ def _on_atom_click(self, _=None): if "atom1" not in self._viewer.picked.keys(): return # did not click on atom index = self._viewer.picked["atom1"]["index"] + print("index ",index) + selection = self.selection.copy() + + if self.rep_dict: + component = self._viewer.picked["component"] + index = self._translate_i_loc_glob[(component, index)] + selection = self.selection.copy() if selection: @@ -734,8 +741,13 @@ def _on_atom_click(self, _=None): selection.remove(index) else: selection = [index] + print(selection,"selection") + self.selection = selection + + return + + - self.selection = selection def highlight_atoms( @@ -746,11 +758,11 @@ def highlight_atoms( opacity=DEFAULT_SELECTION_OPACITY, ): """Highlighting atoms according to the provided list.""" - print(vis_list) + print("vis_list",vis_list) if not hasattr(self._viewer, "component_0"): return - if self.vis_dict is None: + if self.rep_dict is None: self._viewer._remove_representations_by_name( repr_name="selected_atoms" ) # pylint:disable=protected-access @@ -763,15 +775,15 @@ def highlight_atoms( ) else: - ncomponents = len(self.vis_dict.keys()) + ncomponents = len(self.rep_dict.keys()) for component in range(ncomponents): - name = "highlight_" + self.vis_dict[component]["name"] + name = "highlight_" + self.rep_dict[component]["name"] self._viewer._remove_representations_by_name( repr_name=name, component=component ) - color = self.vis_dict[component]["highlight_color"] - aspectRatio = self.vis_dict[component]["highlight_aspectRatio"] - opacity = self.vis_dict[component]["highlight_opacity"] + color = self.rep_dict[component]["highlight_color"] + aspectRatio = self.rep_dict[component]["highlight_aspectRatio"] + opacity = self.rep_dict[component]["highlight_opacity"] if vis_list is None: self._viewer.add_ball_and_stick( name=name, @@ -800,11 +812,11 @@ def update_viewer(self, c=None): cid = self._viewer.component_0.id self._viewer.remove_component(cid) - # self.vis_dict = vis_dict - for component in range(len(self.vis_dict)): + + for component in range(len(self.rep_dict)): rep_indexes = list( - string_range_to_list(self.vis_dict[component]["ids"], shift=-1)[ + string_range_to_list(self.rep_dict[component]["ids"], shift=-1)[ 0 ] ) @@ -815,17 +827,17 @@ def update_viewer(self, c=None): nglview.ASEStructure(mol), default_representation=False ) - if self.vis_dict[component]["type"] == "ball+stick": + if self.rep_dict[component]["type"] == "ball+stick": print("ball ",component) - aspectRatio = self.vis_dict[component]["aspectRatio"] + aspectRatio = self.rep_dict[component]["aspectRatio"] self._viewer.add_ball_and_stick( aspectRatio=aspectRatio, opacity=1.0, component=component, ) - elif self.vis_dict[component]["type"] == "licorice": + elif self.rep_dict[component]["type"] == "licorice": self._viewer.add_licorice(opacity=1.0, component=component) - elif self.vis_dict[component]["type"] == "hyperball": + elif self.rep_dict[component]["type"] == "hyperball": self._viewer.add_hyperball(opacity=1.0, component=component) self._gen_translation_indexes() self._viewer.add_unitcell() @@ -846,7 +858,7 @@ def _validate_selection(self, provided): @observe("selection") def _observe_selection(self, _=None): self.highlight_atoms(self.selection) - self._selected_atoms.value = list_to_string_range(self.selection, shift=1) + self._selected_atoms.value = list_to_string_range(self.selection, shift=0) # if atom is selected from nglview, shift to selection tab if self._selected_atoms.value: @@ -963,6 +975,8 @@ def _observe_structure(self, change): """Update displayed_structure trait after the structure trait has been modified.""" # Remove the current structure(s) from the viewer. if change["new"] is not None: + self.rep_dict ={0: self.default_representations['surface']} + self.rep_dict[0]['ids'] = '1..'+str(len(change["new"])) self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) self.set_trait("cell", change["new"].cell) else: From 445a3aa2221eb8f63e85c2a0ac4d3c2e3844f04a Mon Sep 17 00:00:00 2001 From: Carlo Pignedoli Date: Sat, 15 Oct 2022 12:41:38 +0000 Subject: [PATCH 03/87] first working version, to-do: add/remove atoms in edit structure --- aiidalab_widgets_base/viewers.py | 55 +++++++++++++++++++------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 3828d885..77eceeef 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -191,6 +191,7 @@ def __init__( self._viewer.observe(self._on_atom_click, names="picked") self._viewer.stage.set_parameters(mouse_preset="pymol") self.rep_dict={} + self.rep_dict_unit={} self.default_representations={ "molecule": { "ids": "1..2", "aspectRatio": 3.5, @@ -367,14 +368,14 @@ def change_camera(change): # representations def apply_representations(change): #iterate on number of representations - self.rep_dict={} + self.rep_dict_unit={} current_rep=0 for sel,rep in [(self._selected_atoms_r1.value , self.rep1.value),(self._selected_atoms_r2.value,self.rep2.value)]: if sel: - self.rep_dict[current_rep] = self.default_representations[rep] - self.rep_dict[current_rep]["ids"] = sel - self.rep_dict[current_rep]["name"] = "rep"+str(current_rep) - current_rep+=1 + self.rep_dict_unit[current_rep] = deepcopy(self.default_representations[rep]) + self.rep_dict_unit[current_rep]["ids"] = list_to_string_range(string_range_to_list(sel,shift=-1)[0],shift=0) + self.rep_dict_unit[current_rep]["name"] = "rep"+str(current_rep) + current_rep+=1 self.update_viewer() apply_rep = ipw.Button(description="Apply rep") @@ -699,21 +700,21 @@ def _gen_translation_indexes(self): dictionaries for back and forth transformations.""" self._translate_i_glob_loc = {} - self._translate_i_loc_glob = {} + self._translate_i_loc_glob = {} for component in range(len(self.rep_dict.keys())): comp_i = 0 ids = list( - string_range_to_list(self.rep_dict[component]["ids"], shift=0)[0] - ) + string_range_to_list(self.rep_dict[component]["ids"], shift=0)[0]) for i_g in ids: self._translate_i_glob_loc[i_g] = (component, comp_i) self._translate_i_loc_glob[(component, comp_i)] = i_g comp_i += 1 + def _translate_glob_loc(self, indexes): """From global index to indexes of different components.""" all_comp = [list() for i in range(len(self.rep_dict.keys()))] - print("all_comp ",all_comp) + for i_g in indexes: i_c, i_a = self._translate_i_glob_loc[i_g] all_comp[i_c].append(i_a) @@ -725,15 +726,14 @@ def _on_atom_click(self, _=None): if "atom1" not in self._viewer.picked.keys(): return # did not click on atom index = self._viewer.picked["atom1"]["index"] - print("index ",index) - selection = self.selection.copy() if self.rep_dict: component = self._viewer.picked["component"] - index = self._translate_i_loc_glob[(component, index)] + index = self._translate_i_loc_glob[(component, index)]%self.natoms + selection = self.selection.copy() - + if selection: if index not in selection: selection.append(index) @@ -741,8 +741,9 @@ def _on_atom_click(self, _=None): selection.remove(index) else: selection = [index] - print(selection,"selection") + self.selection = selection + return @@ -758,7 +759,6 @@ def highlight_atoms( opacity=DEFAULT_SELECTION_OPACITY, ): """Highlighting atoms according to the provided list.""" - print("vis_list",vis_list) if not hasattr(self._viewer, "component_0"): return @@ -812,14 +812,21 @@ def update_viewer(self, c=None): cid = self._viewer.component_0.id self._viewer.remove_component(cid) - + #copy representations of structure into rep of display structure + nrep = np.prod(self.supercell) + self.rep_dict = deepcopy(self.rep_dict_unit) + if nrep > 1: + for component in range(len(self.rep_dict_unit.keys())): + ids = string_range_to_list(self.rep_dict_unit[component]["ids"] , shift=0)[0] + new_ids = [i+rep*self.natoms for rep in range(nrep) for i in ids] + self.rep_dict[component]["ids"] = list_to_string_range(new_ids,shift=0) + for component in range(len(self.rep_dict)): rep_indexes = list( - string_range_to_list(self.rep_dict[component]["ids"], shift=-1)[ - 0 - ] + string_range_to_list(self.rep_dict[component]["ids"], shift=0)[0] ) + if rep_indexes: mol = self.displayed_structure[rep_indexes] @@ -828,7 +835,6 @@ def update_viewer(self, c=None): ) if self.rep_dict[component]["type"] == "ball+stick": - print("ball ",component) aspectRatio = self.rep_dict[component]["aspectRatio"] self._viewer.add_ball_and_stick( aspectRatio=aspectRatio, @@ -858,7 +864,7 @@ def _validate_selection(self, provided): @observe("selection") def _observe_selection(self, _=None): self.highlight_atoms(self.selection) - self._selected_atoms.value = list_to_string_range(self.selection, shift=0) + self._selected_atoms.value = list_to_string_range(self.selection, shift=1) # if atom is selected from nglview, shift to selection tab if self._selected_atoms.value: @@ -974,9 +980,11 @@ def _valid_structure(self, change): # pylint: disable=no-self-use def _observe_structure(self, change): """Update displayed_structure trait after the structure trait has been modified.""" # Remove the current structure(s) from the viewer. + + self.natoms = len(change["new"]) if change["new"] is not None: - self.rep_dict ={0: self.default_representations['surface']} - self.rep_dict[0]['ids'] = '1..'+str(len(change["new"])) + self.rep_dict_unit ={0: deepcopy(self.default_representations['surface'])} + self.rep_dict_unit[0]['ids'] = '0..'+str(self.natoms - 1) self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) self.set_trait("cell", change["new"].cell) else: @@ -986,6 +994,7 @@ def _observe_structure(self, change): @observe("displayed_structure") def _update_structure_viewer(self, change): """Update the view if displayed_structure trait was modified.""" + # Create visualization for repeat atoms. self.update_viewer() From 06a6f1228f304f2cf300be93f46eaf7b426d21d0 Mon Sep 17 00:00:00 2001 From: Carlo Pignedoli Date: Sat, 15 Oct 2022 12:50:30 +0000 Subject: [PATCH 04/87] comments --- aiidalab_widgets_base/viewers.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 77eceeef..d1619928 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -697,7 +697,18 @@ def _render_structure(self, change=None): def _gen_translation_indexes(self): """Transfromation of indexes in case of multiple representations - dictionaries for back and forth transformations.""" + dictionaries for back and forth transformations. + suppose we have 3 representations: + component = 0,1,2 + and a structure with 10 atoms. + If we assign the first 3 atoms to the first representation + atom 4 to the second and the rest to the third + teh two dictionaries will look like: + {0:(0,0),1:(0,1),2:(0,2),3:(1,0),4:(2,0),5:(2,1),6:(2,2),7:(2,3),8:(2,4),9:(2,5)} + and + {(0,0):0,(0,1):1,(0,2):2,(1,0):3,(2,0):4,(2,1):5,(2,2):6,(2,3):7,(2,4):8,(2,5):9} + """ + self._translate_i_glob_loc = {} self._translate_i_loc_glob = {} From 9cea5186fb5f8b4486301baeb6af972879a9bffc Mon Sep 17 00:00:00 2001 From: Carlo Pignedoli Date: Sun, 16 Oct 2022 08:29:59 +0000 Subject: [PATCH 05/87] toward general formalism --- aiidalab_widgets_base/viewers.py | 64 +++++++++++++++++++------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index d1619928..095e8d83 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -370,10 +370,10 @@ def apply_representations(change): #iterate on number of representations self.rep_dict_unit={} current_rep=0 - for sel,rep in [(self._selected_atoms_r1.value , self.rep1.value),(self._selected_atoms_r2.value,self.rep2.value)]: - if sel: - self.rep_dict_unit[current_rep] = deepcopy(self.default_representations[rep]) - self.rep_dict_unit[current_rep]["ids"] = list_to_string_range(string_range_to_list(sel,shift=-1)[0],shift=0) + for sel,rep,show in self.representations: + if sel.value: + self.rep_dict_unit[current_rep] = deepcopy(self.default_representations[rep.value]) + self.rep_dict_unit[current_rep]["ids"] = list_to_string_range(string_range_to_list(sel.value,shift=-1)[0],shift=0) self.rep_dict_unit[current_rep]["name"] = "rep"+str(current_rep) current_rep+=1 self.update_viewer() @@ -381,34 +381,47 @@ def apply_representations(change): apply_rep = ipw.Button(description="Apply rep") apply_rep.on_click(apply_representations) - self.rep1 = ipw.Dropdown( - #options=["ball+stick", "licorice", "spacefill", "surface"], + + + # TO DO make this general + # options=["ball+stick", "licorice", "spacefill", "surface"], + self.representations = [ + ( + ipw.Text( + description="atoms:", + value="", + style={"description_width": "initial"} ), + ipw.Dropdown( options=["molecule","surface"], value="molecule", - description="Rep1", + description="mode", + disabled=False,), + ipw.Checkbox( + description="show", + value=True, disabled=False, - ) - self.rep2 = ipw.Dropdown( - #options=["ball+stick", "licorice", "spacefill", "surface"], + indent=False) + ), + ( + ipw.Text( + description="atoms:", + value="", + style={"description_width": "initial"} ), + ipw.Dropdown( options=["molecule","surface"], value="molecule", - description="Rep2", + description="mode", + disabled=False,), + ipw.Checkbox( + description="show", + value=True, disabled=False, - ) - self._selected_atoms_r1 = ipw.Text( - description="atoms rep 1:", - value="", - style={"description_width": "initial"}, - ) - self._selected_atoms_r2 = ipw.Text( - description="atoms rep 2:", - value="", - style={"description_width": "initial"}, - ) - + indent=False) + ) + ] return ipw.VBox( [supercell_selector, background_color, camera_type, - ipw.HBox([self._selected_atoms_r1,self.rep1]), ipw.HBox([self._selected_atoms_r2,self.rep2]), apply_rep, center_button] + ipw.VBox([ipw.HBox([iwidget for iwidget in irep]) for irep in self.representations]), apply_rep, center_button] ) @observe("cell") @@ -992,8 +1005,9 @@ def _observe_structure(self, change): """Update displayed_structure trait after the structure trait has been modified.""" # Remove the current structure(s) from the viewer. - self.natoms = len(change["new"]) + if change["new"] is not None: + self.natoms = len(change["new"]) self.rep_dict_unit ={0: deepcopy(self.default_representations['surface'])} self.rep_dict_unit[0]['ids'] = '0..'+str(self.natoms - 1) self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) From 1ea70672d7b7a989bd023414514c0b7f51ce6a63 Mon Sep 17 00:00:00 2001 From: Carlo Pignedoli Date: Sun, 16 Oct 2022 11:02:39 +0000 Subject: [PATCH 06/87] starting modifiying addition and rmeova of atoms --- aiidalab_widgets_base/structures.py | 25 ++++++++++++++++++---- aiidalab_widgets_base/viewers.py | 32 +++++++++++++++++++---------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 3d42e2b5..943ff34b 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -11,6 +11,7 @@ import ase import ipywidgets as ipw import numpy as np +from copy import deepcopy # spglib for cell converting import spglib @@ -29,7 +30,7 @@ from ase import Atom, Atoms from ase.data import chemical_symbols, covalent_radii from sklearn.decomposition import PCA -from traitlets import Instance, Int, List, Unicode, Union, default, dlink, link, observe +from traitlets import Dict, Instance, Int, List, Unicode, Union, default, dlink, link, observe # Local imports from .data import LigandSelectorWidget @@ -176,6 +177,8 @@ def _structure_editors(self, editors): link((editors[0], "structure"), (self, "structure")) if editors[0].has_trait("selection"): link((editors[0], "selection"), (self.viewer, "selection")) + if editors[0].has_trait("list_of_representations"): + link((editors[0], "list_of_representations"), (self.viewer, "list_of_representations")) if editors[0].has_trait("camera_orientation"): dlink( (self.viewer._viewer, "_camera_orientation"), @@ -949,6 +952,7 @@ class BasicStructureEditor(ipw.VBox): # pylint: disable=too-many-instance-attri position of periodic structure in cell) editing.""" structure = Instance(Atoms, allow_none=True) + list_of_representations = List() selection = List(Int) camera_orientation = List() @@ -1182,6 +1186,13 @@ def disable_element(_=None): ] ) + + def find_index(self,list_of_lists, element): + for i, x in enumerate(list_of_lists): + if element in x: + return i + return -1 + def str2vec(self, string): return np.array(list(map(float, string.split()))) @@ -1402,7 +1413,8 @@ def copy_sel(self, _=None, atoms=None, selection=None): def add(self, _=None, atoms=None, selection=None): """Add atoms.""" last_atom = atoms.get_global_number_of_atoms() - + end_atom=last_atom + new_list_of_representations = deepcopy(self.list_of_representations) if self.ligand.value == 0: initial_ligand = Atoms([Atom(self.element.value, [0, 0, 0])]) rad = SYMBOL_RADIUS[self.element.value] @@ -1425,11 +1437,14 @@ def add(self, _=None, atoms=None, selection=None): lgnd.translate(position + self.action_vector * self.bond_length.value) atoms += lgnd + rep_of_idx = self.find_index(new_list_of_representations, idx) + new_list_of_representations[rep_of_idx]+=[i for i in range(end_atom, end_atom + len(lgnd))] + end_atom += len(lgnd) new_selection = [ - i for i in range(last_atom, last_atom + len(selection) * len(lgnd)) + i for i in range(last_atom, end_atom) ] - + self.list_of_representations = new_list_of_representations self.structure, self.selection = atoms, new_selection @_register_structure @@ -1437,5 +1452,7 @@ def add(self, _=None, atoms=None, selection=None): def remove(self, _, atoms=None, selection=None): """Remove selected atoms.""" del [atoms[selection]] + new_list = [[ele for ele in sub if ele not in selection] for sub in self.list_of_representations] + self.list_of_representations = new_list self.structure, self.selection = atoms, list() diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 095e8d83..1fbffa28 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -20,6 +20,7 @@ from matplotlib.colors import to_rgb from numpy.linalg import norm from traitlets import ( + Dict, Instance, Int, List, @@ -171,6 +172,7 @@ class _StructureDataBaseViewer(ipw.VBox): selection = List(Int) selection_adv = Unicode() supercell = List(Int) + list_of_representations = List() cell = Instance(Cell, allow_none=True) DEFAULT_SELECTION_OPACITY = 0.2 DEFAULT_SELECTION_RADIUS = 6 @@ -355,27 +357,26 @@ def change_camera(change): center_button = ipw.Button(description="Center molecule") center_button.on_click(lambda c: self._viewer.center()) - # "b": { - # "ids": "set_ids", - # "aspectRatio": 4, - # "highlight_aspectRatio": 4.1, - # "highlight_color": "green", - # "highlight_opacity": 0.6, - # "name": "bulk", - # "type": "ball+stick", - # }, # representations def apply_representations(change): #iterate on number of representations self.rep_dict_unit={} current_rep=0 + list_of_representations=list() for sel,rep,show in self.representations: - if sel.value: + # in representation dictionary indexes start from 0 + idsl = string_range_to_list(sel.value,shift=-1)[0] + ids = list_to_string_range(idsl,shift=0) + if ids: self.rep_dict_unit[current_rep] = deepcopy(self.default_representations[rep.value]) - self.rep_dict_unit[current_rep]["ids"] = list_to_string_range(string_range_to_list(sel.value,shift=-1)[0],shift=0) + self.rep_dict_unit[current_rep]["ids"] = ids self.rep_dict_unit[current_rep]["name"] = "rep"+str(current_rep) + list_of_representations.append(idsl) + else: + list_of_representations.append([]) current_rep+=1 + self.list_of_representations = list_of_representations self.update_viewer() apply_rep = ipw.Button(description="Apply rep") @@ -424,6 +425,14 @@ def apply_representations(change): ipw.VBox([ipw.HBox([iwidget for iwidget in irep]) for irep in self.representations]), apply_rep, center_button] ) + @observe("list_of_representations") + def _observe_list_of_representations(self, _=None): + """Update the value of representation selection widgets when the list of representations changes.""" + for i,list in enumerate(self.list_of_representations): + self.representations[i][0].value = list_to_string_range(list,shift=1) + #apply_representations() + + @observe("cell") def _observe_cell(self, _=None): # only update cell info when it is a 3D structure. @@ -852,6 +861,7 @@ def update_viewer(self, c=None): ) if rep_indexes: + print("rep_indexes",rep_indexes) mol = self.displayed_structure[rep_indexes] self._viewer.add_component( From 5a12592ec9941d97706d9691ea58692549b0b15f Mon Sep 17 00:00:00 2001 From: Carlo Date: Mon, 17 Oct 2022 12:43:23 +0000 Subject: [PATCH 07/87] intermediate mess --- aiidalab_widgets_base/representations.py | 92 +++++++++++++++++++++++ aiidalab_widgets_base/structures.py | 36 +++++++-- aiidalab_widgets_base/viewers.py | 94 ++++++++++++------------ 3 files changed, 167 insertions(+), 55 deletions(-) create mode 100644 aiidalab_widgets_base/representations.py diff --git a/aiidalab_widgets_base/representations.py b/aiidalab_widgets_base/representations.py new file mode 100644 index 00000000..241eb999 --- /dev/null +++ b/aiidalab_widgets_base/representations.py @@ -0,0 +1,92 @@ +import traitlets +import ipywidgets as ipw +from IPython.display import clear_output +import aiidalab_widgets_base as awb + +STYLE = {'description_width': '100px'} +BOX_LAYOUT = ipw.Layout(display='flex-wrap', flex_flow='row wrap', justify_content='space-between') + +class Representation(ipw.HBox): + master_class = None + def __init__(self, indices="1..2", name="no-name"): + self.label = ipw.HTML("Rep") + self.selection = ipw.Text(description="atoms:",value="",style={"description_width": "initial"} ) + self.style = ipw.Dropdown(options=["molecule","surface"],value="molecule",description="mode",disabled=False) + #self.show = ipw.Checkbox(description="show",value=True,disabled=False,indent=False) + #self.name = ipw.Text(description="Name", value=name, style=STYLE) + + #ipw.dlink((self.name, "value"), (self.label, "value"), transform=lambda x: f"Fragment: {x}") + + #self.output = ipw.Output() + + # Delete button. + self.delete_button = ipw.Button(description="Delete", button_style="danger") + self.delete_button.on_click(self.delete_myself) + + + super().__init__( + children=[ + self.label, + ipw.HTML("
"), + self.selection, + self.style, + #self.show, + self.delete_button, + ] + ) + + #@traitlets.observe("uks") + #def _observe_uks(self, change): + # with self.output: + # clear_output() + # if change['new']: + # display(ipw.VBox([self.multiplicity])) + + def delete_myself(self, _): + self.master_class.delete_representation(self) + + +class RepresentationList(ipw.VBox): + representations = traitlets.List() + #selection_string = traitlets.Unicode() + + def __init__(self): + # Fragment selection. + self.new_representation_name = ipw.Text(value='', description='Rep name', style={"description_width": "initial"}) + self.add_new_rep_button = ipw.Button(description="Add rep", button_style="info") + self.add_new_rep_button.on_click(self.add_representation) + + # Outputs. + #self.fragment_add_message = awb.utils.StatusHTML() + self.representation_output = ipw.Box(layout=BOX_LAYOUT) + super().__init__(children=[ipw.HBox([self.new_representation_name, self.add_new_rep_button])])#, self.fragment_add_message, self.fragment_output]) + + + self.representation_output.children = self.representations + + def add_representation(self, _): + """Add a representation to the list of representations.""" + + self.representations = self.representations + [Representation()] + self.new_representation_name.value = '' + + def delete_representation(self, representation): + try: + index = self.representations.index(representation) + except ValueError: + self.representation_add_message.message = f"""Error: Fragment {representation} not found.""" + return + + self.representation_add_message.message = f"""Info: Removing {representation.name.value} ({representation.indices.value}) from the fragment list.""" + self.representations = self.representations[:index] + self.representations[index+1:] + del representation + + @traitlets.observe("representations") + def _observe_representations(self, change): + """Update the list of representations.""" + if change['new']: + self.representation_output.children = change["new"] + self.representations[-1].master_class = self + else: + self.representation_output.children = [] + diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 943ff34b..c28a729b 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -57,6 +57,7 @@ class StructureManagerWidget(ipw.VBox): structure = Union([Instance(Atoms), Instance(Data)], allow_none=True) structure_node = Instance(Data, allow_none=True, read_only=True) node_class = Unicode() + list_of_representations = List() SUPPORTED_DATA_FORMATS = {"CifData": "cif", "StructureData": "structure"} @@ -179,6 +180,7 @@ def _structure_editors(self, editors): link((editors[0], "selection"), (self.viewer, "selection")) if editors[0].has_trait("list_of_representations"): link((editors[0], "list_of_representations"), (self.viewer, "list_of_representations")) + link((editors[0], "list_of_representations"), (self, "list_of_representations")) if editors[0].has_trait("camera_orientation"): dlink( (self.viewer._viewer, "_camera_orientation"), @@ -238,7 +240,8 @@ def undo(self, _): if self.history: self.history = self.history[:-1] if self.history: - self.structure = self.history[-1] + self.structure = self.history[-1][0] + self.list_of_representations = self.history[-1][1] else: self.input_structure = None self.structure_set_by_undo = False @@ -336,6 +339,11 @@ def _observe_input_structure(self, change): else: self.structure = None + @observe("list_of_representations") + def _observe_list_of_representations(self, change=None): + """Update list of representations in teh history list.""" + self.history[-1]=(self.history[-1][0],deepcopy(self.list_of_representations)) + @observe("structure") def _structure_changed(self, change=None): """Perform some operations that depend on the value of `structure` trait. @@ -344,7 +352,7 @@ def _structure_changed(self, change=None): Also, the function sets `structure_node` trait to the selected node type. """ if not self.structure_set_by_undo: - self.history.append(change["new"]) + self.history.append((change["new"],deepcopy(self.list_of_representations))) # If structure trait was set to None, structure_node should become None as well. if self.structure is None: @@ -1382,31 +1390,44 @@ def mod_element(self, _=None, atoms=None, selection=None): initial_ligand = self.ligand.rotate( align_to=self.action_vector, remove_anchor=True ) + end_atom=last_atom + new_list_of_representations = deepcopy(self.list_of_representations) for idx in self.selection: position = self.structure.positions[idx].copy() lgnd = initial_ligand.copy() lgnd.translate(position) atoms += lgnd + rep_of_idx = self.find_index(new_list_of_representations, idx) + new_list_of_representations[rep_of_idx]+=[i for i in range(end_atom, end_atom + len(lgnd))] + end_atom += len(lgnd) new_selection = [ i for i in range(last_atom, last_atom + len(selection) * len(lgnd)) ] - + self.structure, self.selection = atoms, new_selection + self.list_of_representations = new_list_of_representations @_register_structure @_register_selection def copy_sel(self, _=None, atoms=None, selection=None): """Copy selected atoms and shift by 1.0 A along X-axis.""" last_atom = atoms.get_global_number_of_atoms() + new_list_of_representations = deepcopy(self.list_of_representations) # The action add_atoms = atoms[self.selection].copy() add_atoms.translate([1.0, 0, 0]) atoms += add_atoms + for i,id in enumerate(selection): + rep_of_idx = self.find_index(new_list_of_representations, id) + new_list_of_representations[rep_of_idx]+=[last_atom+i] + new_selection = [i for i in range(last_atom, last_atom + len(selection))] + self.structure, self.selection = atoms, new_selection + self.list_of_representations = new_list_of_representations @_register_structure @_register_selection @@ -1444,15 +1465,18 @@ def add(self, _=None, atoms=None, selection=None): new_selection = [ i for i in range(last_atom, end_atom) ] - self.list_of_representations = new_list_of_representations + + self.structure, self.selection = atoms, new_selection + self.list_of_representations = new_list_of_representations @_register_structure @_register_selection def remove(self, _, atoms=None, selection=None): """Remove selected atoms.""" del [atoms[selection]] - new_list = [[ele for ele in sub if ele not in selection] for sub in self.list_of_representations] + new_list_of_representations = [[ele for ele in sub if ele not in selection] for sub in self.list_of_representations] - self.list_of_representations = new_list + self.structure, self.selection = atoms, list() + self.list_of_representations = new_list_of_representations diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 1fbffa28..5be8655e 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -194,6 +194,8 @@ def __init__( self._viewer.stage.set_parameters(mouse_preset="pymol") self.rep_dict={} self.rep_dict_unit={} + self.representations = list() + self.representations_box = ipw.VBox() self.default_representations={ "molecule": { "ids": "1..2", "aspectRatio": 3.5, @@ -358,51 +360,26 @@ def change_camera(change): center_button.on_click(lambda c: self._viewer.center()) - # representations - def apply_representations(change): - #iterate on number of representations - self.rep_dict_unit={} - current_rep=0 - list_of_representations=list() - for sel,rep,show in self.representations: - # in representation dictionary indexes start from 0 - idsl = string_range_to_list(sel.value,shift=-1)[0] - ids = list_to_string_range(idsl,shift=0) - if ids: - self.rep_dict_unit[current_rep] = deepcopy(self.default_representations[rep.value]) - self.rep_dict_unit[current_rep]["ids"] = ids - self.rep_dict_unit[current_rep]["name"] = "rep"+str(current_rep) - list_of_representations.append(idsl) - else: - list_of_representations.append([]) - current_rep+=1 - self.list_of_representations = list_of_representations - self.update_viewer() - + # 5. representations buttons + add_rep = ipw.Button(description="Add rep") + add_rep.on_click(self.add_representation) + #remove_rep = ipw.Button(description="Remove rep") + #remove_rep.on_click(self.remove_representation) apply_rep = ipw.Button(description="Apply rep") - apply_rep.on_click(apply_representations) + apply_rep.on_click(self.apply_representations) - # TO DO make this general - # options=["ball+stick", "licorice", "spacefill", "surface"], - self.representations = [ - ( - ipw.Text( - description="atoms:", - value="", - style={"description_width": "initial"} ), - ipw.Dropdown( - options=["molecule","surface"], - value="molecule", - description="mode", - disabled=False,), - ipw.Checkbox( - description="show", - value=True, - disabled=False, - indent=False) - ), + + return ipw.VBox( + [supercell_selector, background_color, camera_type, self.representations_box,add_rep, + apply_rep, center_button] + ) + + + def add_representation(self,_=None): + """Add representation""" + self.representations.append( ( ipw.Text( description="atoms:", @@ -419,18 +396,35 @@ def apply_representations(change): disabled=False, indent=False) ) - ] - return ipw.VBox( - [supercell_selector, background_color, camera_type, - ipw.VBox([ipw.HBox([iwidget for iwidget in irep]) for irep in self.representations]), apply_rep, center_button] - ) + ) + self.representations_box.children = [ipw.VBox([ipw.HBox([iwidget for iwidget in irep]) for irep in self.representations])] + def apply_representations(self,change=None): + #iterate on number of representations + self.rep_dict_unit={} + current_rep=0 + list_of_representations=list() + for sel,rep,show in self.representations: + # in representation dictionary indexes start from 0 + idsl = string_range_to_list(sel.value,shift=-1)[0] + ids = list_to_string_range(idsl,shift=0) + if ids: + self.rep_dict_unit[current_rep] = deepcopy(self.default_representations[rep.value]) + self.rep_dict_unit[current_rep]["ids"] = ids + self.rep_dict_unit[current_rep]["name"] = "rep"+str(current_rep) + list_of_representations.append(idsl) + else: + list_of_representations.append([]) + current_rep+=1 + self.list_of_representations = list_of_representations + self.update_viewer() + @observe("list_of_representations") def _observe_list_of_representations(self, _=None): """Update the value of representation selection widgets when the list of representations changes.""" for i,list in enumerate(self.list_of_representations): self.representations[i][0].value = list_to_string_range(list,shift=1) - #apply_representations() + self.apply_representations() @observe("cell") @@ -861,7 +855,6 @@ def update_viewer(self, c=None): ) if rep_indexes: - print("rep_indexes",rep_indexes) mol = self.displayed_structure[rep_indexes] self._viewer.add_component( @@ -1015,7 +1008,10 @@ def _observe_structure(self, change): """Update displayed_structure trait after the structure trait has been modified.""" # Remove the current structure(s) from the viewer. - + # to do: distingush the case a brand new structure in uploaded/ creted + # and the case a structure is changed, in teh first case we create a brand new representation dictionary + # in the second case we just update the existing one this would avoid a double representation of the structure + # not easy: the order in wich structure and list_of_rep traits are updated can create conflicts if change["new"] is not None: self.natoms = len(change["new"]) self.rep_dict_unit ={0: deepcopy(self.default_representations['surface'])} From f6523967992164f6001f467ee3aeb84c88ad881a Mon Sep 17 00:00:00 2001 From: Carlo Date: Mon, 17 Oct 2022 13:27:22 +0000 Subject: [PATCH 08/87] used same concept of fragments for representations --- aiidalab_widgets_base/representations.py | 18 ++++++----- aiidalab_widgets_base/viewers.py | 41 ++++++------------------ 2 files changed, 19 insertions(+), 40 deletions(-) diff --git a/aiidalab_widgets_base/representations.py b/aiidalab_widgets_base/representations.py index 241eb999..e86786d1 100644 --- a/aiidalab_widgets_base/representations.py +++ b/aiidalab_widgets_base/representations.py @@ -13,9 +13,9 @@ def __init__(self, indices="1..2", name="no-name"): self.selection = ipw.Text(description="atoms:",value="",style={"description_width": "initial"} ) self.style = ipw.Dropdown(options=["molecule","surface"],value="molecule",description="mode",disabled=False) #self.show = ipw.Checkbox(description="show",value=True,disabled=False,indent=False) - #self.name = ipw.Text(description="Name", value=name, style=STYLE) + self.name = ipw.Text(description="Name", value=name, style=STYLE) - #ipw.dlink((self.name, "value"), (self.label, "value"), transform=lambda x: f"Fragment: {x}") + ipw.dlink((self.name, "value"), (self.label, "value"), transform=lambda x: f"Fragment: {x}") #self.output = ipw.Output() @@ -57,27 +57,29 @@ def __init__(self): self.add_new_rep_button.on_click(self.add_representation) # Outputs. - #self.fragment_add_message = awb.utils.StatusHTML() + self.representation_add_message = awb.utils.StatusHTML() self.representation_output = ipw.Box(layout=BOX_LAYOUT) - super().__init__(children=[ipw.HBox([self.new_representation_name, self.add_new_rep_button])])#, self.fragment_add_message, self.fragment_output]) + super().__init__(children=[ipw.HBox([self.new_representation_name, self.add_new_rep_button,self.representation_add_message,self.representation_output])]) self.representation_output.children = self.representations def add_representation(self, _): """Add a representation to the list of representations.""" - - self.representations = self.representations + [Representation()] + if not self.new_representation_name.value: + self.representation_add_message.message = """Error: Please enter a name for the Rep.""" + return + self.representations = self.representations + [Representation(name=self.new_representation_name.value)] self.new_representation_name.value = '' def delete_representation(self, representation): try: index = self.representations.index(representation) except ValueError: - self.representation_add_message.message = f"""Error: Fragment {representation} not found.""" + self.representation_add_message.message = f"""Error: Rep. {representation} not found.""" return - self.representation_add_message.message = f"""Info: Removing {representation.name.value} ({representation.indices.value}) from the fragment list.""" + self.representation_add_message.message = f"""Info: Removing {representation.name.value} ({representation.selection.value}) """ self.representations = self.representations[:index] + self.representations[index+1:] del representation diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 5be8655e..25a21ae1 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -14,6 +14,7 @@ from aiida.cmdline.utils.common import get_workchain_report from aiida.cmdline.utils.query import formatting from aiida.orm import Node +from aiidalab_widgets_base.representations import Representation, RepresentationList from ase import Atoms, neighborlist from ase.cell import Cell from IPython.display import clear_output, display @@ -194,8 +195,7 @@ def __init__( self._viewer.stage.set_parameters(mouse_preset="pymol") self.rep_dict={} self.rep_dict_unit={} - self.representations = list() - self.representations_box = ipw.VBox() + self.representations = RepresentationList() self.default_representations={ "molecule": { "ids": "1..2", "aspectRatio": 3.5, @@ -361,10 +361,7 @@ def change_camera(change): # 5. representations buttons - add_rep = ipw.Button(description="Add rep") - add_rep.on_click(self.add_representation) - #remove_rep = ipw.Button(description="Remove rep") - #remove_rep.on_click(self.remove_representation) + apply_rep = ipw.Button(description="Apply rep") apply_rep.on_click(self.apply_representations) @@ -372,43 +369,23 @@ def change_camera(change): return ipw.VBox( - [supercell_selector, background_color, camera_type, self.representations_box,add_rep, + [supercell_selector, background_color, camera_type, self.representations, apply_rep, center_button] ) - def add_representation(self,_=None): - """Add representation""" - self.representations.append( - ( - ipw.Text( - description="atoms:", - value="", - style={"description_width": "initial"} ), - ipw.Dropdown( - options=["molecule","surface"], - value="molecule", - description="mode", - disabled=False,), - ipw.Checkbox( - description="show", - value=True, - disabled=False, - indent=False) - ) - ) - self.representations_box.children = [ipw.VBox([ipw.HBox([iwidget for iwidget in irep]) for irep in self.representations])] + def apply_representations(self,change=None): #iterate on number of representations self.rep_dict_unit={} current_rep=0 list_of_representations=list() - for sel,rep,show in self.representations: + for rep in self.representations.representations: # in representation dictionary indexes start from 0 - idsl = string_range_to_list(sel.value,shift=-1)[0] + idsl = string_range_to_list(rep.selection.value,shift=-1)[0] ids = list_to_string_range(idsl,shift=0) if ids: - self.rep_dict_unit[current_rep] = deepcopy(self.default_representations[rep.value]) + self.rep_dict_unit[current_rep] = deepcopy(self.default_representations[rep.style.value]) self.rep_dict_unit[current_rep]["ids"] = ids self.rep_dict_unit[current_rep]["name"] = "rep"+str(current_rep) list_of_representations.append(idsl) @@ -423,7 +400,7 @@ def apply_representations(self,change=None): def _observe_list_of_representations(self, _=None): """Update the value of representation selection widgets when the list of representations changes.""" for i,list in enumerate(self.list_of_representations): - self.representations[i][0].value = list_to_string_range(list,shift=1) + self.representations.representations[i].selection.value = list_to_string_range(list,shift=1) self.apply_representations() From 469ef535d425b4da575ff3e08d1132f4dba3cdb0 Mon Sep 17 00:00:00 2001 From: Carlo Date: Mon, 17 Oct 2022 14:02:01 +0000 Subject: [PATCH 09/87] reordering representation class --- aiidalab_widgets_base/representations.py | 94 ------------------------ aiidalab_widgets_base/viewers.py | 65 +++++++++++++--- 2 files changed, 54 insertions(+), 105 deletions(-) delete mode 100644 aiidalab_widgets_base/representations.py diff --git a/aiidalab_widgets_base/representations.py b/aiidalab_widgets_base/representations.py deleted file mode 100644 index e86786d1..00000000 --- a/aiidalab_widgets_base/representations.py +++ /dev/null @@ -1,94 +0,0 @@ -import traitlets -import ipywidgets as ipw -from IPython.display import clear_output -import aiidalab_widgets_base as awb - -STYLE = {'description_width': '100px'} -BOX_LAYOUT = ipw.Layout(display='flex-wrap', flex_flow='row wrap', justify_content='space-between') - -class Representation(ipw.HBox): - master_class = None - def __init__(self, indices="1..2", name="no-name"): - self.label = ipw.HTML("Rep") - self.selection = ipw.Text(description="atoms:",value="",style={"description_width": "initial"} ) - self.style = ipw.Dropdown(options=["molecule","surface"],value="molecule",description="mode",disabled=False) - #self.show = ipw.Checkbox(description="show",value=True,disabled=False,indent=False) - self.name = ipw.Text(description="Name", value=name, style=STYLE) - - ipw.dlink((self.name, "value"), (self.label, "value"), transform=lambda x: f"Fragment: {x}") - - #self.output = ipw.Output() - - # Delete button. - self.delete_button = ipw.Button(description="Delete", button_style="danger") - self.delete_button.on_click(self.delete_myself) - - - super().__init__( - children=[ - self.label, - ipw.HTML("
"), - self.selection, - self.style, - #self.show, - self.delete_button, - ] - ) - - #@traitlets.observe("uks") - #def _observe_uks(self, change): - # with self.output: - # clear_output() - # if change['new']: - # display(ipw.VBox([self.multiplicity])) - - def delete_myself(self, _): - self.master_class.delete_representation(self) - - -class RepresentationList(ipw.VBox): - representations = traitlets.List() - #selection_string = traitlets.Unicode() - - def __init__(self): - # Fragment selection. - self.new_representation_name = ipw.Text(value='', description='Rep name', style={"description_width": "initial"}) - self.add_new_rep_button = ipw.Button(description="Add rep", button_style="info") - self.add_new_rep_button.on_click(self.add_representation) - - # Outputs. - self.representation_add_message = awb.utils.StatusHTML() - self.representation_output = ipw.Box(layout=BOX_LAYOUT) - super().__init__(children=[ipw.HBox([self.new_representation_name, self.add_new_rep_button,self.representation_add_message,self.representation_output])]) - - - self.representation_output.children = self.representations - - def add_representation(self, _): - """Add a representation to the list of representations.""" - if not self.new_representation_name.value: - self.representation_add_message.message = """Error: Please enter a name for the Rep.""" - return - self.representations = self.representations + [Representation(name=self.new_representation_name.value)] - self.new_representation_name.value = '' - - def delete_representation(self, representation): - try: - index = self.representations.index(representation) - except ValueError: - self.representation_add_message.message = f"""Error: Rep. {representation} not found.""" - return - - self.representation_add_message.message = f"""Info: Removing {representation.name.value} ({representation.selection.value}) """ - self.representations = self.representations[:index] + self.representations[index+1:] - del representation - - @traitlets.observe("representations") - def _observe_representations(self, change): - """Update the list of representations.""" - if change['new']: - self.representation_output.children = change["new"] - self.representations[-1].master_class = self - else: - self.representation_output.children = [] - diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 25a21ae1..d7c063b5 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -14,7 +14,6 @@ from aiida.cmdline.utils.common import get_workchain_report from aiida.cmdline.utils.query import formatting from aiida.orm import Node -from aiidalab_widgets_base.representations import Representation, RepresentationList from ase import Atoms, neighborlist from ase.cell import Cell from IPython.display import clear_output, display @@ -49,6 +48,7 @@ from .utils import ase2spglib, list_to_string_range, string_range_to_list AIIDA_VIEWER_MAPPING = dict() +BOX_LAYOUT = ipw.Layout(display='flex-wrap', flex_flow='row wrap', justify_content='space-between') def register_viewer_widget(key): @@ -158,6 +158,29 @@ def __init__(self, parameter, downloadable=True, **kwargs): super().__init__([self.widget], **kwargs) +class Representation(ipw.HBox): + master_class = None + def __init__(self, indices="1..2"): + self.selection = ipw.Text(description="atoms:",value="",style={"description_width": "initial"} ) + self.style = ipw.Dropdown(options=["molecule","surface"],value="molecule",description="mode",disabled=False) + + # Delete button. + self.delete_button = ipw.Button(description="Delete", button_style="danger") + self.delete_button.on_click(self.delete_myself) + + + super().__init__( + children=[ + self.selection, + self.style, + self.delete_button, + ] + ) + + + def delete_myself(self, _): + self.master_class.delete_representation(self) + class _StructureDataBaseViewer(ipw.VBox): """Base viewer class for AiiDA structure or trajectory objects. @@ -169,6 +192,7 @@ class _StructureDataBaseViewer(ipw.VBox): :type default_camera: string """ + representations = traitlets.List() selection = List(Int) selection_adv = Unicode() @@ -195,7 +219,6 @@ def __init__( self._viewer.stage.set_parameters(mouse_preset="pymol") self.rep_dict={} self.rep_dict_unit={} - self.representations = RepresentationList() self.default_representations={ "molecule": { "ids": "1..2", "aspectRatio": 3.5, @@ -350,7 +373,6 @@ def change_supercell(_=None): ) def change_camera(change): - self._viewer.camera = change["new"] camera_type.observe(change_camera, names="value") @@ -361,28 +383,49 @@ def change_camera(change): # 5. representations buttons + self.add_new_rep_button = ipw.Button(description="Add rep", button_style="info") + self.add_new_rep_button.on_click(self.add_representation) apply_rep = ipw.Button(description="Apply rep") apply_rep.on_click(self.apply_representations) - - - + self.representation_output = ipw.Box(layout=BOX_LAYOUT) return ipw.VBox( - [supercell_selector, background_color, camera_type, self.representations, - apply_rep, center_button] + [supercell_selector, background_color, camera_type, + self.add_new_rep_button, self.representation_output, apply_rep, center_button] ) + def add_representation(self, _): + """Add a representation to the list of representations.""" + self.representations = self.representations + [Representation()] + def delete_representation(self, representation): + try: + index = self.representations.index(representation) + except ValueError: + self.representation_add_message.message = f"""Error: Rep. {representation} not found.""" + return + + self.representations = self.representations[:index] + self.representations[index+1:] + del representation + + @observe("representations") + def _observe_representations(self, change): + """Update the list of representations.""" + if change['new']: + self.representation_output.children = change["new"] + self.representations[-1].master_class = self + else: + self.representation_output.children = [] def apply_representations(self,change=None): #iterate on number of representations self.rep_dict_unit={} current_rep=0 list_of_representations=list() - for rep in self.representations.representations: + for rep in self.representations: # in representation dictionary indexes start from 0 - idsl = string_range_to_list(rep.selection.value,shift=-1)[0] + idsl = string_range_to_list(rep.selection.value, shift=-1)[0] ids = list_to_string_range(idsl,shift=0) if ids: self.rep_dict_unit[current_rep] = deepcopy(self.default_representations[rep.style.value]) @@ -400,7 +443,7 @@ def apply_representations(self,change=None): def _observe_list_of_representations(self, _=None): """Update the value of representation selection widgets when the list of representations changes.""" for i,list in enumerate(self.list_of_representations): - self.representations.representations[i].selection.value = list_to_string_range(list,shift=1) + self.representations[i].selection.value = list_to_string_range(list,shift=1) self.apply_representations() From 9749526a00752a348b9665cff009661a209e16ec Mon Sep 17 00:00:00 2001 From: Carlo Date: Mon, 17 Oct 2022 15:13:15 +0000 Subject: [PATCH 10/87] first draft working --- aiidalab_widgets_base/viewers.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index d7c063b5..d70e4cd8 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -6,6 +6,7 @@ import warnings from copy import deepcopy +import itertools import ipywidgets as ipw import nglview import numpy as np @@ -193,7 +194,7 @@ class _StructureDataBaseViewer(ipw.VBox): """ representations = traitlets.List() - + natoms = Int() selection = List(Int) selection_adv = Unicode() supercell = List(Int) @@ -217,6 +218,7 @@ def __init__( self._viewer.camera = default_camera self._viewer.observe(self._on_atom_click, names="picked") self._viewer.stage.set_parameters(mouse_preset="pymol") + self.natoms=0 self.rep_dict={} self.rep_dict_unit={} self.default_representations={ "molecule": { @@ -383,6 +385,7 @@ def change_camera(change): # 5. representations buttons + self.atoms_not_represented=ipw.Output() self.add_new_rep_button = ipw.Button(description="Add rep", button_style="info") self.add_new_rep_button.on_click(self.add_representation) @@ -392,7 +395,7 @@ def change_camera(change): return ipw.VBox( [supercell_selector, background_color, camera_type, - self.add_new_rep_button, self.representation_output, apply_rep, center_button] + self.add_new_rep_button, self.representation_output,self.atoms_not_represented, apply_rep, center_button] ) def add_representation(self, _): @@ -408,6 +411,7 @@ def delete_representation(self, representation): self.representations = self.representations[:index] + self.representations[index+1:] del representation + self.apply_representations() @observe("representations") def _observe_representations(self, change): @@ -436,6 +440,14 @@ def apply_representations(self,change=None): list_of_representations.append([]) current_rep+=1 self.list_of_representations = list_of_representations + missing_atoms = set(range(self.natoms)).difference(set(list(itertools.chain.from_iterable(list_of_representations)))) + if missing_atoms: + self.atoms_not_represented.clear_output() + with self.atoms_not_represented: + print("Atoms not represented: ",list_to_string_range(list(missing_atoms),shift=1)) + else: + self.atoms_not_represented.clear_output() + self.update_viewer() From 46709349150955de81bd5160f9f517cf32319b8f Mon Sep 17 00:00:00 2001 From: Carlo Date: Tue, 18 Oct 2022 15:16:46 +0000 Subject: [PATCH 11/87] redesigned, debug prints present --- aiidalab_widgets_base/structures.py | 64 +++++++++-- aiidalab_widgets_base/viewers.py | 166 ++++++++++++++++++++-------- 2 files changed, 174 insertions(+), 56 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index c28a729b..d74af5ee 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -30,7 +30,7 @@ from ase import Atom, Atoms from ase.data import chemical_symbols, covalent_radii from sklearn.decomposition import PCA -from traitlets import Dict, Instance, Int, List, Unicode, Union, default, dlink, link, observe +from traitlets import Bool,Dict, Instance, Int, List, Unicode, Union, default, dlink, link, observe # Local imports from .data import LigandSelectorWidget @@ -58,6 +58,7 @@ class StructureManagerWidget(ipw.VBox): structure_node = Instance(Data, allow_none=True, read_only=True) node_class = Unicode() list_of_representations = List() + brand_new_structure = Bool() SUPPORTED_DATA_FORMATS = {"CifData": "cif", "StructureData": "structure"} @@ -161,7 +162,12 @@ def _structure_importers(self, importers): if len(importers) == 1: # Assigning a function which will be called when importer provides a structure. dlink((importers[0], "structure"), (self, "input_structure")) + if importers[0].has_trait("brand_new_structure"): + link((importers[0], "brand_new_structure"), (self.viewer, "brand_new_structure")) + link((importers[0], "brand_new_structure"), (self, "brand_new_structure")) + return importers[0] + # Otherwise making one tab per importer. importers_tab = ipw.Tab() @@ -170,6 +176,9 @@ def _structure_importers(self, importers): # Labeling tabs. importers_tab.set_title(i, importer.title) dlink((importer, "structure"), (self, "input_structure")) + if importer.has_trait("brand_new_structure"): + link((importer, "brand_new_structure"), (self.viewer, "brand_new_structure")) + link((importer, "brand_new_structure"), (self, "brand_new_structure")) return importers_tab def _structure_editors(self, editors): @@ -197,6 +206,9 @@ def _structure_editors(self, editors): link((editor, "structure"), (self, "structure")) if editor.has_trait("selection"): link((editor, "selection"), (self.viewer, "selection")) + if editor.has_trait("list_of_representations"): + link((editors, "list_of_representations"), (self.viewer, "list_of_representations")) + link((editors, "list_of_representations"), (self, "list_of_representations")) if editor.has_trait("camera_orientation"): dlink( (self.viewer._viewer, "_camera_orientation"), @@ -240,7 +252,9 @@ def undo(self, _): if self.history: self.history = self.history[:-1] if self.history: + self.brand_new_structure = False self.structure = self.history[-1][0] + print("in undo",self.history[-1][1]) self.list_of_representations = self.history[-1][1] else: self.input_structure = None @@ -341,8 +355,10 @@ def _observe_input_structure(self, change): @observe("list_of_representations") def _observe_list_of_representations(self, change=None): - """Update list of representations in teh history list.""" + """Update list of representations in the history list.""" + print("observe list_rep of history") self.history[-1]=(self.history[-1][0],deepcopy(self.list_of_representations)) + print(self.history) @observe("structure") def _structure_changed(self, change=None): @@ -353,6 +369,7 @@ def _structure_changed(self, change=None): """ if not self.structure_set_by_undo: self.history.append((change["new"],deepcopy(self.list_of_representations))) + print("history: ",self.history) # If structure trait was set to None, structure_node should become None as well. if self.structure is None: @@ -691,6 +708,7 @@ class SmilesWidget(ipw.VBox): """Convert SMILES into 3D structure.""" structure = Instance(Atoms, allow_none=True) + brand_new_structure = Bool() SPINNER = """""" @@ -816,6 +834,8 @@ def _on_button_pressed(self, change): # pylint: disable=unused-argument return spinner = f"Screening possible conformers {self.SPINNER}" # font-size:20em; self.output.value = spinner + self.brand_new_structure = True + print("created in smiles") self.structure = self._mol_from_smiles(self.smiles.value) # Don't overwrite possible error/warning messages if self.output.value == spinner: @@ -1277,6 +1297,7 @@ def translate_dr(self, _=None, atoms=None, selection=None): self.action_vector * self.displacement.value ) + self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1287,6 +1308,8 @@ def translate_dxdydz(self, _=None, atoms=None, selection=None): # The action. atoms.positions[self.selection] += np.array(self.str2vec(self.dxyz.value)) + self.brand_new_structure = False + self.structure, self.selection = atoms, selection @_register_structure @@ -1297,6 +1320,7 @@ def translate_to_xyz(self, _=None, atoms=None, selection=None): geo_center = np.average(self.structure[self.selection].get_positions(), axis=0) atoms.positions[self.selection] += self.str2vec(self.dxyz.value) - geo_center + self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1311,6 +1335,7 @@ def rotate(self, _=None, atoms=None, selection=None): rotated_subset.rotate(self.phi.value, v=vec, center=center, rotate_cell=False) atoms.positions[list(self.selection)] = rotated_subset.positions + self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1344,6 +1369,7 @@ def mirror(self, _=None, norm=None, point=None, atoms=None, selection=None): # Mirror atoms. atoms.positions[selection] -= 2 * projections + self.brand_new_structure = False self.structure, self.selection = atoms, selection def mirror_3p(self, _=None): @@ -1368,6 +1394,7 @@ def align(self, _=None, atoms=None, selection=None): subset.rotate(self.action_vector, self.str2vec(self.dxyz.value), center=center) atoms.positions[selection] = subset.positions + self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1376,6 +1403,7 @@ def mod_element(self, _=None, atoms=None, selection=None): """Modify selected atoms into the given element.""" last_atom = atoms.get_global_number_of_atoms() + new_list_of_representations = [] if self.ligand.value == 0: for idx in self.selection: new = Atom(self.element.value) @@ -1404,8 +1432,14 @@ def mod_element(self, _=None, atoms=None, selection=None): i for i in range(last_atom, last_atom + len(selection) * len(lgnd)) ] - self.structure, self.selection = atoms, new_selection - self.list_of_representations = new_list_of_representations + self.brand_new_structure = False + + # the order of the traitlets below is important + self.selection = [] + self.structure = atoms + if new_list_of_representations: + self.list_of_representations = new_list_of_representations + self.selection = new_selection @_register_structure @_register_selection @@ -1425,9 +1459,13 @@ def copy_sel(self, _=None, atoms=None, selection=None): new_selection = [i for i in range(last_atom, last_atom + len(selection))] - - self.structure, self.selection = atoms, new_selection + self.brand_new_structure = False + + # the order of the traitlets below is important + self.selection = [] + self.structure = atoms self.list_of_representations = new_list_of_representations + self.selection = new_selection @_register_structure @_register_selection @@ -1466,9 +1504,13 @@ def add(self, _=None, atoms=None, selection=None): i for i in range(last_atom, end_atom) ] - - self.structure, self.selection = atoms, new_selection + self.brand_new_structure = False + + # the order of the traitlets below is important + self.selection = [] + self.structure = atoms self.list_of_representations = new_list_of_representations + self.selection = new_selection @_register_structure @_register_selection @@ -1478,5 +1520,9 @@ def remove(self, _, atoms=None, selection=None): new_list_of_representations = [[ele for ele in sub if ele not in selection] for sub in self.list_of_representations] - self.structure, self.selection = atoms, list() + self.brand_new_structure = False + + # the order of the traitlets below is important + self.selection = [] + self.structure = atoms self.list_of_representations = new_list_of_representations diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index d70e4cd8..a3afa21e 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -21,6 +21,7 @@ from matplotlib.colors import to_rgb from numpy.linalg import norm from traitlets import ( + Bool, Dict, Instance, Int, @@ -195,6 +196,7 @@ class _StructureDataBaseViewer(ipw.VBox): """ representations = traitlets.List() natoms = Int() + brand_new_structure = Bool(True) selection = List(Int) selection_adv = Unicode() supercell = List(Int) @@ -218,6 +220,7 @@ def __init__( self._viewer.camera = default_camera self._viewer.observe(self._on_atom_click, names="picked") self._viewer.stage.set_parameters(mouse_preset="pymol") + self.first_update_of_viewer = True self.natoms=0 self.rep_dict={} self.rep_dict_unit={} @@ -390,7 +393,7 @@ def change_camera(change): self.add_new_rep_button.on_click(self.add_representation) apply_rep = ipw.Button(description="Apply rep") - apply_rep.on_click(self.apply_representations) + apply_rep.on_click(self.on_click_apply_representations) self.representation_output = ipw.Box(layout=BOX_LAYOUT) return ipw.VBox( @@ -422,41 +425,76 @@ def _observe_representations(self, change): else: self.representation_output.children = [] + def update_representations(self, change=None): + """Update the representations using the list of representations""" + print("update representations") + if not self.representations and self.list_of_representations: + self.representations = [Representation()] + for i,list in enumerate(self.list_of_representations): + self.representations[i].selection.value = list_to_string_range(list,shift=1) + self.apply_representations() + + def replicate_representations(self, change=None): + #copy representations of structure into rep of display structure + nrep = np.prod(self.supercell) + self.rep_dict = deepcopy(self.rep_dict_unit) + if nrep > 1: + for component in range(len(self.rep_dict_unit.keys())): + ids = string_range_to_list(self.rep_dict_unit[component]["ids"] , shift=0)[0] + new_ids = [i+rep*self.natoms for rep in range(nrep) for i in ids] + self.rep_dict[component]["ids"] = list_to_string_range(new_ids,shift=0) + + self._gen_translation_indexes() + + def on_click_apply_representations(self,change=None): + list_of_representations = [] + for rep in self.representations: + list_of_representations.append(string_range_to_list(rep.selection.value,shift=-1)[0]) + self.list_of_representations = list_of_representations + + def apply_representations(self,change=None): #iterate on number of representations self.rep_dict_unit={} current_rep=0 - list_of_representations=list() + + print("apply representations") + self.brand_new_structure=False for rep in self.representations: # in representation dictionary indexes start from 0 idsl = string_range_to_list(rep.selection.value, shift=-1)[0] ids = list_to_string_range(idsl,shift=0) - if ids: - self.rep_dict_unit[current_rep] = deepcopy(self.default_representations[rep.style.value]) - self.rep_dict_unit[current_rep]["ids"] = ids - self.rep_dict_unit[current_rep]["name"] = "rep"+str(current_rep) - list_of_representations.append(idsl) - else: - list_of_representations.append([]) + + self.rep_dict_unit[current_rep] = deepcopy(self.default_representations[rep.style.value]) + self.rep_dict_unit[current_rep]["ids"] = ids + self.rep_dict_unit[current_rep]["name"] = "rep"+str(current_rep) + current_rep+=1 - self.list_of_representations = list_of_representations - missing_atoms = set(range(self.natoms)).difference(set(list(itertools.chain.from_iterable(list_of_representations)))) + missing_atoms = set(range(self.natoms)).difference(set(list(itertools.chain.from_iterable(self.list_of_representations)))) if missing_atoms: self.atoms_not_represented.clear_output() with self.atoms_not_represented: print("Atoms not represented: ",list_to_string_range(list(missing_atoms),shift=1)) else: self.atoms_not_represented.clear_output() - + print("before calling replicate the rep dict is",self.rep_dict_unit) + self.replicate_representations() self.update_viewer() + if self.first_update_of_viewer: + self.first_update_of_viewer = self.orient_z_up() @observe("list_of_representations") def _observe_list_of_representations(self, _=None): """Update the value of representation selection widgets when the list of representations changes.""" + print("obsereve list of representations") + if not self.representations and self.list_of_representations: + self.representations = [Representation()] + self.representations[0].style.value = "molecule" for i,list in enumerate(self.list_of_representations): self.representations[i].selection.value = list_to_string_range(list,shift=1) - self.apply_representations() + print("in observe list the list is ",self.list_of_representations) + self.update_representations() @observe("cell") @@ -871,42 +909,72 @@ def update_viewer(self, c=None): cid = self._viewer.component_0.id self._viewer.remove_component(cid) - #copy representations of structure into rep of display structure - nrep = np.prod(self.supercell) - self.rep_dict = deepcopy(self.rep_dict_unit) - if nrep > 1: - for component in range(len(self.rep_dict_unit.keys())): - ids = string_range_to_list(self.rep_dict_unit[component]["ids"] , shift=0)[0] - new_ids = [i+rep*self.natoms for rep in range(nrep) for i in ids] - self.rep_dict[component]["ids"] = list_to_string_range(new_ids,shift=0) - - for component in range(len(self.rep_dict)): + #copy representations of structure into rep of display structure + print("quasay display") - rep_indexes = list( - string_range_to_list(self.rep_dict[component]["ids"], shift=0)[0] - ) - - if rep_indexes: - mol = self.displayed_structure[rep_indexes] + if self.displayed_structure: + print("inside displaying") + for component in range(len(self.rep_dict)): - self._viewer.add_component( - nglview.ASEStructure(mol), default_representation=False + rep_indexes = list( + string_range_to_list(self.rep_dict[component]["ids"], shift=0)[0] ) + + if rep_indexes: + mol = self.displayed_structure[rep_indexes] - if self.rep_dict[component]["type"] == "ball+stick": - aspectRatio = self.rep_dict[component]["aspectRatio"] - self._viewer.add_ball_and_stick( - aspectRatio=aspectRatio, - opacity=1.0, - component=component, + self._viewer.add_component( + nglview.ASEStructure(mol), default_representation=False ) - elif self.rep_dict[component]["type"] == "licorice": - self._viewer.add_licorice(opacity=1.0, component=component) - elif self.rep_dict[component]["type"] == "hyperball": - self._viewer.add_hyperball(opacity=1.0, component=component) - self._gen_translation_indexes() - self._viewer.add_unitcell() - self._viewer.center() + + if self.rep_dict[component]["type"] == "ball+stick": + aspectRatio = self.rep_dict[component]["aspectRatio"] + self._viewer.add_ball_and_stick( + aspectRatio=aspectRatio, + opacity=1.0, + component=component, + ) + elif self.rep_dict[component]["type"] == "licorice": + self._viewer.add_licorice(opacity=1.0, component=component) + elif self.rep_dict[component]["type"] == "hyperball": + self._viewer.add_hyperball(opacity=1.0, component=component) + #self._gen_translation_indexes() + self._viewer.add_unitcell() + self._viewer.center() + + def orient_z_up(self, _=None): + try: + print("inside orient_z_up") + if self.structure is not None: + print("orienting") + cell_z = self.structure.cell[2, 2] + com = self.structure.get_center_of_mass() + def_orientation = self._viewer._camera_orientation + top_z_orientation = [ + 1.0, + 0.0, + 0.0, + 0, + 0.0, + 1.0, + 0.0, + 0, + 0.0, + 0.0, + -np.max([cell_z, 30.0]), + 0, + -com[0], + -com[1], + -com[2], + 1, + ] + self._viewer._set_camera_orientation(top_z_orientation) + return False + else: + return True + except AttributeError: + print("AttributeError") + return True @default("supercell") def _default_supercell(self): @@ -1015,6 +1083,7 @@ def __init__(self, structure=None, **kwargs): def repeat(self, _=None): if self.structure is not None: self.set_trait("displayed_structure", self.structure.repeat(self.supercell)) + self.apply_representations() @validate("structure") def _valid_structure(self, change): # pylint: disable=no-self-use @@ -1044,11 +1113,12 @@ def _observe_structure(self, change): # and the case a structure is changed, in teh first case we create a brand new representation dictionary # in the second case we just update the existing one this would avoid a double representation of the structure # not easy: the order in wich structure and list_of_rep traits are updated can create conflicts + print("brand new structure?",self.brand_new_structure) if change["new"] is not None: self.natoms = len(change["new"]) - self.rep_dict_unit ={0: deepcopy(self.default_representations['surface'])} - self.rep_dict_unit[0]['ids'] = '0..'+str(self.natoms - 1) self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) + if self.brand_new_structure: + self.list_of_representations = [list(range(self.natoms))] self.set_trait("cell", change["new"].cell) else: self.set_trait("displayed_structure", None) @@ -1058,7 +1128,9 @@ def _observe_structure(self, change): def _update_structure_viewer(self, change): """Update the view if displayed_structure trait was modified.""" # Create visualization for repeat atoms. - self.update_viewer() + #if not self.brand_new_structure: + # print("observe displayed_structure calls update_structure_viewer") + # self.update_representations() From 83cbc8115024255c01575a9d91431bd13a11f8aa Mon Sep 17 00:00:00 2001 From: Carlo Date: Tue, 18 Oct 2022 15:34:22 +0000 Subject: [PATCH 12/87] refinements --- aiidalab_widgets_base/structures.py | 2 +- aiidalab_widgets_base/viewers.py | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index d74af5ee..9e89316a 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -980,7 +980,7 @@ class BasicStructureEditor(ipw.VBox): # pylint: disable=too-many-instance-attri position of periodic structure in cell) editing.""" structure = Instance(Atoms, allow_none=True) - list_of_representations = List() + list_of_representations = List([[]]) selection = List(Int) camera_orientation = List() diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index a3afa21e..5e7ebbc8 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -2,6 +2,7 @@ # pylint: disable=no-self-use import base64 +from hashlib import new import re import warnings from copy import deepcopy @@ -200,7 +201,7 @@ class _StructureDataBaseViewer(ipw.VBox): selection = List(Int) selection_adv = Unicode() supercell = List(Int) - list_of_representations = List() + list_of_representations = List([[]]) cell = Instance(Cell, allow_none=True) DEFAULT_SELECTION_OPACITY = 0.2 DEFAULT_SELECTION_RADIUS = 6 @@ -969,6 +970,7 @@ def orient_z_up(self, _=None): 1, ] self._viewer._set_camera_orientation(top_z_orientation) + self._viewer.center() return False else: return True @@ -1107,18 +1109,20 @@ def _valid_structure(self, change): # pylint: disable=no-self-use @observe("structure") def _observe_structure(self, change): """Update displayed_structure trait after the structure trait has been modified.""" - # Remove the current structure(s) from the viewer. - - # to do: distingush the case a brand new structure in uploaded/ creted - # and the case a structure is changed, in teh first case we create a brand new representation dictionary - # in the second case we just update the existing one this would avoid a double representation of the structure - # not easy: the order in wich structure and list_of_rep traits are updated can create conflicts + # if a structure originates from an input widget previous represnetations are kept + # but all atoms are put in the first one + print("brand new structure?",self.brand_new_structure) if change["new"] is not None: self.natoms = len(change["new"]) self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) if self.brand_new_structure: - self.list_of_representations = [list(range(self.natoms))] + new_list_of_representations = deepcopy(self.list_of_representations) + print(new_list_of_representations) + for rep in new_list_of_representations: + rep=[] + new_list_of_representations[0] = list(range(self.natoms)) + self.list_of_representations = new_list_of_representations self.set_trait("cell", change["new"].cell) else: self.set_trait("displayed_structure", None) From c8006b68187dceba5464f16f9bec5da634eef942 Mon Sep 17 00:00:00 2001 From: Carlo Date: Wed, 19 Oct 2022 11:31:23 +0000 Subject: [PATCH 13/87] use dtratlet input_structure --- aiidalab_widgets_base/structures.py | 13 ++++++----- aiidalab_widgets_base/viewers.py | 36 ++++++++++++++++++----------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 9e89316a..5a7bfc24 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -101,6 +101,7 @@ def __init__( else: self.viewer = StructureDataViewer(**kwargs) dlink((self, "structure_node"), (self.viewer, "structure")) + dlink((self, "input_structure"), (self.viewer, "input_structure")) # Store button. self.btn_store = ipw.Button(description="Store in AiiDA", disabled=True) @@ -162,9 +163,9 @@ def _structure_importers(self, importers): if len(importers) == 1: # Assigning a function which will be called when importer provides a structure. dlink((importers[0], "structure"), (self, "input_structure")) - if importers[0].has_trait("brand_new_structure"): - link((importers[0], "brand_new_structure"), (self.viewer, "brand_new_structure")) - link((importers[0], "brand_new_structure"), (self, "brand_new_structure")) + #if importers[0].has_trait("brand_new_structure"): + # link((importers[0], "brand_new_structure"), (self.viewer, "brand_new_structure")) + # link((importers[0], "brand_new_structure"), (self, "brand_new_structure")) return importers[0] @@ -176,9 +177,9 @@ def _structure_importers(self, importers): # Labeling tabs. importers_tab.set_title(i, importer.title) dlink((importer, "structure"), (self, "input_structure")) - if importer.has_trait("brand_new_structure"): - link((importer, "brand_new_structure"), (self.viewer, "brand_new_structure")) - link((importer, "brand_new_structure"), (self, "brand_new_structure")) + #if importer.has_trait("brand_new_structure"): + # link((importer, "brand_new_structure"), (self.viewer, "brand_new_structure")) + # link((importer, "brand_new_structure"), (self, "brand_new_structure")) return importers_tab def _structure_editors(self, editors): diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 5e7ebbc8..0169cbc3 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -15,7 +15,7 @@ import traitlets from aiida.cmdline.utils.common import get_workchain_report from aiida.cmdline.utils.query import formatting -from aiida.orm import Node +from aiida.orm import Node, Data from ase import Atoms, neighborlist from ase.cell import Cell from IPython.display import clear_output, display @@ -1073,6 +1073,7 @@ class StructureDataViewer(_StructureDataBaseViewer): """ structure = Union([Instance(Atoms), Instance(Node)], allow_none=True) + input_structure = Union([Instance(Atoms), Instance(Data)], allow_none=True) displayed_structure = Instance(Atoms, allow_none=True, read_only=True) pk = Int(allow_none=True) @@ -1106,23 +1107,31 @@ def _valid_structure(self, change): # pylint: disable=no-self-use "ASE Atoms object, AiiDA CifData or StructureData." ) + @observe("input_structure") + def _observe_input_structure(self, change): + """Update displayed structure.""" + self.brand_new_structure = True + self.natoms = 0 + if change["new"] is not None: + self.natoms = len(change["new"]) + new_list_of_representations = deepcopy(self.list_of_representations) + print("in observe structure previous list", new_list_of_representations) + for rep in range(len(new_list_of_representations)): + new_list_of_representations[rep]=[] + new_list_of_representations[0] = list(range(self.natoms)) + print("in observe structure updated list", new_list_of_representations) + self.list_of_representations = new_list_of_representations + @observe("structure") def _observe_structure(self, change): """Update displayed_structure trait after the structure trait has been modified.""" - # if a structure originates from an input widget previous represnetations are kept + # if a structure originates from an importer widget previous represnetations are kept # but all atoms are put in the first one print("brand new structure?",self.brand_new_structure) if change["new"] is not None: self.natoms = len(change["new"]) - self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) - if self.brand_new_structure: - new_list_of_representations = deepcopy(self.list_of_representations) - print(new_list_of_representations) - for rep in new_list_of_representations: - rep=[] - new_list_of_representations[0] = list(range(self.natoms)) - self.list_of_representations = new_list_of_representations + self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) self.set_trait("cell", change["new"].cell) else: self.set_trait("displayed_structure", None) @@ -1131,10 +1140,9 @@ def _observe_structure(self, change): @observe("displayed_structure") def _update_structure_viewer(self, change): """Update the view if displayed_structure trait was modified.""" - # Create visualization for repeat atoms. - #if not self.brand_new_structure: - # print("observe displayed_structure calls update_structure_viewer") - # self.update_representations() + # not needed for the moment, actions are defined in the editors functions + # to avoid unnecessary updates + # reactivation would require some care From 3d642d16cb291c308d0614d1f302d7885b4cf723 Mon Sep 17 00:00:00 2001 From: Carlo Date: Wed, 19 Oct 2022 14:18:00 +0000 Subject: [PATCH 14/87] version for first evaluation --- aiidalab_widgets_base/structures.py | 41 +++++++++-------- aiidalab_widgets_base/viewers.py | 71 +++++++++++++++++++---------- 2 files changed, 69 insertions(+), 43 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 5a7bfc24..de32bad7 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -58,7 +58,7 @@ class StructureManagerWidget(ipw.VBox): structure_node = Instance(Data, allow_none=True, read_only=True) node_class = Unicode() list_of_representations = List() - brand_new_structure = Bool() + #brand_new_structure = Bool() SUPPORTED_DATA_FORMATS = {"CifData": "cif", "StructureData": "structure"} @@ -253,9 +253,9 @@ def undo(self, _): if self.history: self.history = self.history[:-1] if self.history: - self.brand_new_structure = False + #self.brand_new_structure = False self.structure = self.history[-1][0] - print("in undo",self.history[-1][1]) + #print("in undo",self.history[-1][1]) self.list_of_representations = self.history[-1][1] else: self.input_structure = None @@ -357,9 +357,9 @@ def _observe_input_structure(self, change): @observe("list_of_representations") def _observe_list_of_representations(self, change=None): """Update list of representations in the history list.""" - print("observe list_rep of history") + #print("observe list_rep of history") self.history[-1]=(self.history[-1][0],deepcopy(self.list_of_representations)) - print(self.history) + #print(self.history) @observe("structure") def _structure_changed(self, change=None): @@ -370,7 +370,7 @@ def _structure_changed(self, change=None): """ if not self.structure_set_by_undo: self.history.append((change["new"],deepcopy(self.list_of_representations))) - print("history: ",self.history) + #print("history: ",self.history) # If structure trait was set to None, structure_node should become None as well. if self.structure is None: @@ -709,7 +709,7 @@ class SmilesWidget(ipw.VBox): """Convert SMILES into 3D structure.""" structure = Instance(Atoms, allow_none=True) - brand_new_structure = Bool() + #brand_new_structure = Bool() SPINNER = """""" @@ -835,8 +835,6 @@ def _on_button_pressed(self, change): # pylint: disable=unused-argument return spinner = f"Screening possible conformers {self.SPINNER}" # font-size:20em; self.output.value = spinner - self.brand_new_structure = True - print("created in smiles") self.structure = self._mol_from_smiles(self.smiles.value) # Don't overwrite possible error/warning messages if self.output.value == spinner: @@ -1298,7 +1296,7 @@ def translate_dr(self, _=None, atoms=None, selection=None): self.action_vector * self.displacement.value ) - self.brand_new_structure = False + #self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1309,7 +1307,7 @@ def translate_dxdydz(self, _=None, atoms=None, selection=None): # The action. atoms.positions[self.selection] += np.array(self.str2vec(self.dxyz.value)) - self.brand_new_structure = False + #self.brand_new_structure = False self.structure, self.selection = atoms, selection @@ -1321,7 +1319,7 @@ def translate_to_xyz(self, _=None, atoms=None, selection=None): geo_center = np.average(self.structure[self.selection].get_positions(), axis=0) atoms.positions[self.selection] += self.str2vec(self.dxyz.value) - geo_center - self.brand_new_structure = False + #self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1336,7 +1334,7 @@ def rotate(self, _=None, atoms=None, selection=None): rotated_subset.rotate(self.phi.value, v=vec, center=center, rotate_cell=False) atoms.positions[list(self.selection)] = rotated_subset.positions - self.brand_new_structure = False + #self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1370,7 +1368,7 @@ def mirror(self, _=None, norm=None, point=None, atoms=None, selection=None): # Mirror atoms. atoms.positions[selection] -= 2 * projections - self.brand_new_structure = False + #self.brand_new_structure = False self.structure, self.selection = atoms, selection def mirror_3p(self, _=None): @@ -1395,7 +1393,7 @@ def align(self, _=None, atoms=None, selection=None): subset.rotate(self.action_vector, self.str2vec(self.dxyz.value), center=center) atoms.positions[selection] = subset.positions - self.brand_new_structure = False + #self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1460,7 +1458,7 @@ def copy_sel(self, _=None, atoms=None, selection=None): new_selection = [i for i in range(last_atom, last_atom + len(selection))] - self.brand_new_structure = False + #self.brand_new_structure = False # the order of the traitlets below is important self.selection = [] @@ -1505,7 +1503,7 @@ def add(self, _=None, atoms=None, selection=None): i for i in range(last_atom, end_atom) ] - self.brand_new_structure = False + #self.brand_new_structure = False # the order of the traitlets below is important self.selection = [] @@ -1519,9 +1517,14 @@ def remove(self, _, atoms=None, selection=None): """Remove selected atoms.""" del [atoms[selection]] new_list_of_representations = [[ele for ele in sub if ele not in selection] for sub in self.list_of_representations] - + # shifts atoms ids to account for preceding atoms removed + for sel_atom in selection: + for idrep in range(len(new_list_of_representations)): + for idatom in range(len(new_list_of_representations[idrep])): + if sel_atom < new_list_of_representations[idrep][idatom]: + new_list_of_representations[idrep][idatom]=new_list_of_representations[idrep][idatom]-1 - self.brand_new_structure = False + #self.brand_new_structure = False # the order of the traitlets below is important self.selection = [] diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 0169cbc3..573122e9 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -162,6 +162,21 @@ def __init__(self, parameter, downloadable=True, **kwargs): class Representation(ipw.HBox): + """Representation for StructureData in nglviewer + if a structure is imported the traitlet import_structure will trigger + initialization of the list 'list_of_representations' e.g. [[0,1,2,3,4,5]] + the traitlet 'list_of_representations' will trigger creation of self.representations as list of Representations() + default style set in self.representations[0].style is 'molecule' + self.representations[0].selection is populated with all atoms + self.update_representations() which copyes selections in the representations widgets into the list_of_representations + ad calls self.apply_representations() + + apply_representations: + creates the representation dictionary for the main structure 'rep_dict_unit', replicates teh reprsentattions in case + of supercell and calls self.update_viewer() that creates teh nglview representattions + + Editors of a structure update the traitlet self.list_of_representations. + """ master_class = None def __init__(self, indices="1..2"): self.selection = ipw.Text(description="atoms:",value="",style={"description_width": "initial"} ) @@ -197,7 +212,7 @@ class _StructureDataBaseViewer(ipw.VBox): """ representations = traitlets.List() natoms = Int() - brand_new_structure = Bool(True) + #brand_new_structure = Bool(True) selection = List(Int) selection_adv = Unicode() supercell = List(Int) @@ -221,7 +236,7 @@ def __init__( self._viewer.camera = default_camera self._viewer.observe(self._on_atom_click, names="picked") self._viewer.stage.set_parameters(mouse_preset="pymol") - self.first_update_of_viewer = True + #self.first_update_of_viewer = True self.natoms=0 self.rep_dict={} self.rep_dict_unit={} @@ -428,11 +443,11 @@ def _observe_representations(self, change): def update_representations(self, change=None): """Update the representations using the list of representations""" - print("update representations") + #print("update representations") if not self.representations and self.list_of_representations: self.representations = [Representation()] - for i,list in enumerate(self.list_of_representations): - self.representations[i].selection.value = list_to_string_range(list,shift=1) + for i,selection in enumerate(self.list_of_representations): + self.representations[i].selection.value = list_to_string_range(selection,shift=1) self.apply_representations() def replicate_representations(self, change=None): @@ -451,7 +466,12 @@ def on_click_apply_representations(self,change=None): list_of_representations = [] for rep in self.representations: list_of_representations.append(string_range_to_list(rep.selection.value,shift=-1)[0]) - self.list_of_representations = list_of_representations + if self.list_of_representations == list_of_representations: + # needed in case I just change the style of a representation without changing the selection + self.apply_representations() + else: + self.list_of_representations = list_of_representations + def apply_representations(self,change=None): @@ -459,8 +479,8 @@ def apply_representations(self,change=None): self.rep_dict_unit={} current_rep=0 - print("apply representations") - self.brand_new_structure=False + #print("apply representations") + #self.brand_new_structure=False for rep in self.representations: # in representation dictionary indexes start from 0 idsl = string_range_to_list(rep.selection.value, shift=-1)[0] @@ -478,23 +498,24 @@ def apply_representations(self,change=None): print("Atoms not represented: ",list_to_string_range(list(missing_atoms),shift=1)) else: self.atoms_not_represented.clear_output() - print("before calling replicate the rep dict is",self.rep_dict_unit) + #print("before calling replicate the rep dict is",self.rep_dict_unit) self.replicate_representations() self.update_viewer() - if self.first_update_of_viewer: - self.first_update_of_viewer = self.orient_z_up() + #if self.first_update_of_viewer: + #self.first_update_of_viewer = self.orient_z_up() + self.orient_z_up() @observe("list_of_representations") def _observe_list_of_representations(self, _=None): """Update the value of representation selection widgets when the list of representations changes.""" - print("obsereve list of representations") + #print("obsereve list of representations") if not self.representations and self.list_of_representations: self.representations = [Representation()] self.representations[0].style.value = "molecule" for i,list in enumerate(self.list_of_representations): self.representations[i].selection.value = list_to_string_range(list,shift=1) - print("in observe list the list is ",self.list_of_representations) + #print("in observe list the list is ",self.list_of_representations) self.update_representations() @@ -911,12 +932,11 @@ def update_viewer(self, c=None): self._viewer.remove_component(cid) #copy representations of structure into rep of display structure - print("quasay display") + print("inside update viewer") if self.displayed_structure: - print("inside displaying") + #print("inside displaying") for component in range(len(self.rep_dict)): - rep_indexes = list( string_range_to_list(self.rep_dict[component]["ids"], shift=0)[0] ) @@ -945,9 +965,9 @@ def update_viewer(self, c=None): def orient_z_up(self, _=None): try: - print("inside orient_z_up") + #print("inside orient_z_up") if self.structure is not None: - print("orienting") + #print("orienting") cell_z = self.structure.cell[2, 2] com = self.structure.get_center_of_mass() def_orientation = self._viewer._camera_orientation @@ -975,7 +995,6 @@ def orient_z_up(self, _=None): else: return True except AttributeError: - print("AttributeError") return True @default("supercell") @@ -1110,16 +1129,19 @@ def _valid_structure(self, change): # pylint: disable=no-self-use @observe("input_structure") def _observe_input_structure(self, change): """Update displayed structure.""" - self.brand_new_structure = True + #self.brand_new_structure = True self.natoms = 0 if change["new"] is not None: - self.natoms = len(change["new"]) + if isinstance(change["new"], Atoms): + self.natoms = len(change["new"]) + else: + self.natoms = len(change["new"].get_ase()) new_list_of_representations = deepcopy(self.list_of_representations) - print("in observe structure previous list", new_list_of_representations) + #print("in observe structure previous list", new_list_of_representations) for rep in range(len(new_list_of_representations)): new_list_of_representations[rep]=[] new_list_of_representations[0] = list(range(self.natoms)) - print("in observe structure updated list", new_list_of_representations) + #print("in observe structure updated list", new_list_of_representations) self.list_of_representations = new_list_of_representations @observe("structure") @@ -1128,7 +1150,7 @@ def _observe_structure(self, change): # if a structure originates from an importer widget previous represnetations are kept # but all atoms are put in the first one - print("brand new structure?",self.brand_new_structure) + #print("brand new structure?",self.brand_new_structure) if change["new"] is not None: self.natoms = len(change["new"]) self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) @@ -1140,6 +1162,7 @@ def _observe_structure(self, change): @observe("displayed_structure") def _update_structure_viewer(self, change): """Update the view if displayed_structure trait was modified.""" + self.update_viewer() # not needed for the moment, actions are defined in the editors functions # to avoid unnecessary updates # reactivation would require some care From 090b94af0fdceaada19d8da853d8ffe01c604f2e Mon Sep 17 00:00:00 2001 From: Carlo Date: Fri, 21 Oct 2022 12:25:29 +0000 Subject: [PATCH 15/87] problem of syncronization of arrays in structure traitlet --- aiidalab_widgets_base/structures.py | 73 +++-------- aiidalab_widgets_base/viewers.py | 190 +++++++++++++--------------- 2 files changed, 104 insertions(+), 159 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index de32bad7..48af5185 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -57,7 +57,6 @@ class StructureManagerWidget(ipw.VBox): structure = Union([Instance(Atoms), Instance(Data)], allow_none=True) structure_node = Instance(Data, allow_none=True, read_only=True) node_class = Unicode() - list_of_representations = List() #brand_new_structure = Bool() SUPPORTED_DATA_FORMATS = {"CifData": "cif", "StructureData": "structure"} @@ -100,8 +99,7 @@ def __init__( self.viewer = viewer else: self.viewer = StructureDataViewer(**kwargs) - dlink((self, "structure_node"), (self.viewer, "structure")) - dlink((self, "input_structure"), (self.viewer, "input_structure")) + link((self, "structure"), (self.viewer, "structure")) # Store button. self.btn_store = ipw.Button(description="Store in AiiDA", disabled=True) @@ -188,9 +186,6 @@ def _structure_editors(self, editors): link((editors[0], "structure"), (self, "structure")) if editors[0].has_trait("selection"): link((editors[0], "selection"), (self.viewer, "selection")) - if editors[0].has_trait("list_of_representations"): - link((editors[0], "list_of_representations"), (self.viewer, "list_of_representations")) - link((editors[0], "list_of_representations"), (self, "list_of_representations")) if editors[0].has_trait("camera_orientation"): dlink( (self.viewer._viewer, "_camera_orientation"), @@ -206,10 +201,7 @@ def _structure_editors(self, editors): editors_tab.set_title(i, editor.title) link((editor, "structure"), (self, "structure")) if editor.has_trait("selection"): - link((editor, "selection"), (self.viewer, "selection")) - if editor.has_trait("list_of_representations"): - link((editors, "list_of_representations"), (self.viewer, "list_of_representations")) - link((editors, "list_of_representations"), (self, "list_of_representations")) + link((editor, "selection"), (self.viewer, "selection")) if editor.has_trait("camera_orientation"): dlink( (self.viewer._viewer, "_camera_orientation"), @@ -253,10 +245,7 @@ def undo(self, _): if self.history: self.history = self.history[:-1] if self.history: - #self.brand_new_structure = False - self.structure = self.history[-1][0] - #print("in undo",self.history[-1][1]) - self.list_of_representations = self.history[-1][1] + self.structure = self.history[-1] else: self.input_structure = None self.structure_set_by_undo = False @@ -354,12 +343,6 @@ def _observe_input_structure(self, change): else: self.structure = None - @observe("list_of_representations") - def _observe_list_of_representations(self, change=None): - """Update list of representations in the history list.""" - #print("observe list_rep of history") - self.history[-1]=(self.history[-1][0],deepcopy(self.list_of_representations)) - #print(self.history) @observe("structure") def _structure_changed(self, change=None): @@ -368,9 +351,9 @@ def _structure_changed(self, change=None): This function enables/disables `btn_store` widget if structure is provided/set to None. Also, the function sets `structure_node` trait to the selected node type. """ + if not self.structure_set_by_undo: - self.history.append((change["new"],deepcopy(self.list_of_representations))) - #print("history: ",self.history) + self.history.append(change["new"]) # If structure trait was set to None, structure_node should become None as well. if self.structure is None: @@ -979,7 +962,6 @@ class BasicStructureEditor(ipw.VBox): # pylint: disable=too-many-instance-attri position of periodic structure in cell) editing.""" structure = Instance(Atoms, allow_none=True) - list_of_representations = List([[]]) selection = List(Int) camera_orientation = List() @@ -1402,7 +1384,7 @@ def mod_element(self, _=None, atoms=None, selection=None): """Modify selected atoms into the given element.""" last_atom = atoms.get_global_number_of_atoms() - new_list_of_representations = [] + if self.ligand.value == 0: for idx in self.selection: new = Atom(self.element.value) @@ -1417,53 +1399,44 @@ def mod_element(self, _=None, atoms=None, selection=None): initial_ligand = self.ligand.rotate( align_to=self.action_vector, remove_anchor=True ) - end_atom=last_atom - new_list_of_representations = deepcopy(self.list_of_representations) + for idx in self.selection: position = self.structure.positions[idx].copy() lgnd = initial_ligand.copy() lgnd.translate(position) - atoms += lgnd - rep_of_idx = self.find_index(new_list_of_representations, idx) - new_list_of_representations[rep_of_idx]+=[i for i in range(end_atom, end_atom + len(lgnd))] - end_atom += len(lgnd) + atoms += lgnd new_selection = [ i for i in range(last_atom, last_atom + len(selection) * len(lgnd)) ] - self.brand_new_structure = False + # the order of the traitlets below is important self.selection = [] self.structure = atoms - if new_list_of_representations: - self.list_of_representations = new_list_of_representations self.selection = new_selection @_register_structure @_register_selection def copy_sel(self, _=None, atoms=None, selection=None): """Copy selected atoms and shift by 1.0 A along X-axis.""" + print("in copy representations", self.structure.arrays["representations"]) last_atom = atoms.get_global_number_of_atoms() - new_list_of_representations = deepcopy(self.list_of_representations) # The action add_atoms = atoms[self.selection].copy() add_atoms.translate([1.0, 0, 0]) atoms += add_atoms - for i,id in enumerate(selection): - rep_of_idx = self.find_index(new_list_of_representations, id) - new_list_of_representations[rep_of_idx]+=[last_atom+i] + new_selection = [i for i in range(last_atom, last_atom + len(selection))] - #self.brand_new_structure = False + # the order of the traitlets below is important self.selection = [] self.structure = atoms - self.list_of_representations = new_list_of_representations self.selection = new_selection @_register_structure @@ -1471,8 +1444,6 @@ def copy_sel(self, _=None, atoms=None, selection=None): def add(self, _=None, atoms=None, selection=None): """Add atoms.""" last_atom = atoms.get_global_number_of_atoms() - end_atom=last_atom - new_list_of_representations = deepcopy(self.list_of_representations) if self.ligand.value == 0: initial_ligand = Atoms([Atom(self.element.value, [0, 0, 0])]) rad = SYMBOL_RADIUS[self.element.value] @@ -1495,12 +1466,9 @@ def add(self, _=None, atoms=None, selection=None): lgnd.translate(position + self.action_vector * self.bond_length.value) atoms += lgnd - rep_of_idx = self.find_index(new_list_of_representations, idx) - new_list_of_representations[rep_of_idx]+=[i for i in range(end_atom, end_atom + len(lgnd))] - end_atom += len(lgnd) new_selection = [ - i for i in range(last_atom, end_atom) + i for i in range(last_atom, last_atom + len(selection) * len(lgnd)) ] #self.brand_new_structure = False @@ -1508,7 +1476,6 @@ def add(self, _=None, atoms=None, selection=None): # the order of the traitlets below is important self.selection = [] self.structure = atoms - self.list_of_representations = new_list_of_representations self.selection = new_selection @_register_structure @@ -1516,17 +1483,7 @@ def add(self, _=None, atoms=None, selection=None): def remove(self, _, atoms=None, selection=None): """Remove selected atoms.""" del [atoms[selection]] - new_list_of_representations = [[ele for ele in sub if ele not in selection] for sub in self.list_of_representations] - # shifts atoms ids to account for preceding atoms removed - for sel_atom in selection: - for idrep in range(len(new_list_of_representations)): - for idatom in range(len(new_list_of_representations[idrep])): - if sel_atom < new_list_of_representations[idrep][idatom]: - new_list_of_representations[idrep][idatom]=new_list_of_representations[idrep][idatom]-1 - - #self.brand_new_structure = False - # the order of the traitlets below is important + self.selection = [] - self.structure = atoms - self.list_of_representations = new_list_of_representations + self.structure = atoms \ No newline at end of file diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 573122e9..48ce7f45 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -181,6 +181,7 @@ class Representation(ipw.HBox): def __init__(self, indices="1..2"): self.selection = ipw.Text(description="atoms:",value="",style={"description_width": "initial"} ) self.style = ipw.Dropdown(options=["molecule","surface"],value="molecule",description="mode",disabled=False) + self.show = ipw.Checkbox(value=True,description="show",disabled=False) # Delete button. self.delete_button = ipw.Button(description="Delete", button_style="danger") @@ -191,6 +192,7 @@ def __init__(self, indices="1..2"): children=[ self.selection, self.style, + self.show, self.delete_button, ] ) @@ -216,7 +218,6 @@ class _StructureDataBaseViewer(ipw.VBox): selection = List(Int) selection_adv = Unicode() supercell = List(Int) - list_of_representations = List([[]]) cell = Instance(Cell, allow_none=True) DEFAULT_SELECTION_OPACITY = 0.2 DEFAULT_SELECTION_RADIUS = 6 @@ -251,8 +252,8 @@ def __init__( }, "surface": { "ids": "1..2", - "aspectRatio": 5, - "highlight_aspectRatio": 5, + "aspectRatio": 10, + "highlight_aspectRatio": 10.1, "highlight_color": "green", "highlight_opacity": 0.6, "name": "surface", @@ -324,13 +325,7 @@ def _selection_tab(self): # 4. Button to clear selection. clear_selection = ipw.Button(description="Clear selection") # clear_selection.on_click(lambda _: self.set_trait('selection', list())) # lambda cannot contain assignments - clear_selection.on_click( - lambda _: ( - self.set_trait("selection", list()), - self.set_trait("selection_adv", ""), - # self.wrong_syntax.layout.visibility = 'hidden' - ) - ) + clear_selection.on_click(self.clear_selection) # CLEAR self.wrong_syntax.layout.visibility = 'visible' # 5. Button to apply selection @@ -443,12 +438,21 @@ def _observe_representations(self, change): def update_representations(self, change=None): """Update the representations using the list of representations""" - #print("update representations") - if not self.representations and self.list_of_representations: - self.representations = [Representation()] - for i,selection in enumerate(self.list_of_representations): - self.representations[i].selection.value = list_to_string_range(selection,shift=1) - self.apply_representations() + number_of_representation_widgets = len(self.representations) + if self.displayed_structure: + if number_of_representation_widgets == 0: + self.representations = [Representation()] + # shoudl not be needed teh check of more reps in array['representations'] than actually defined reps + + representations = self.structure.arrays['representations'] + for rep in set(representations): + if rep >=0: # negative values are used for atoms not represented (different from the case of hidden representations) + self.representations[int(rep)].selection.value = list_to_string_range([int(i) for i in np.where(representations == rep )[0]],shift=1) + # empty selection field for unused representations + for rep in range(number_of_representation_widgets): + if rep not in set([int(i) for i in representations]): + self.representations[rep].selection.value = "" + self.apply_representations() def replicate_representations(self, change=None): #copy representations of structure into rep of display structure @@ -459,18 +463,19 @@ def replicate_representations(self, change=None): ids = string_range_to_list(self.rep_dict_unit[component]["ids"] , shift=0)[0] new_ids = [i+rep*self.natoms for rep in range(nrep) for i in ids] self.rep_dict[component]["ids"] = list_to_string_range(new_ids,shift=0) - self._gen_translation_indexes() def on_click_apply_representations(self,change=None): - list_of_representations = [] - for rep in self.representations: - list_of_representations.append(string_range_to_list(rep.selection.value,shift=-1)[0]) - if self.list_of_representations == list_of_representations: - # needed in case I just change the style of a representation without changing the selection - self.apply_representations() - else: - self.list_of_representations = list_of_representations + """Updates self.displayed_structure.arrays['representations'] according to user defined representations""" + + for irep, rep in enumerate(self.representations): + selection = string_range_to_list(rep.selection.value, shift=-1)[0] + for index in selection: + self.structure.arrays['representations'][index] = irep + print("in on click apply rep", self.structure.arrays['representations']) + self.apply_representations() + + @@ -479,10 +484,10 @@ def apply_representations(self,change=None): self.rep_dict_unit={} current_rep=0 - #print("apply representations") + #self.brand_new_structure=False for rep in self.representations: - # in representation dictionary indexes start from 0 + # in representation dictionary indexes start from 0 so we transform '1..4' in '0..3' idsl = string_range_to_list(rep.selection.value, shift=-1)[0] ids = list_to_string_range(idsl,shift=0) @@ -491,7 +496,7 @@ def apply_representations(self,change=None): self.rep_dict_unit[current_rep]["name"] = "rep"+str(current_rep) current_rep+=1 - missing_atoms = set(range(self.natoms)).difference(set(list(itertools.chain.from_iterable(self.list_of_representations)))) + missing_atoms = set([int(i) for i in np.where(self.displayed_structure.arrays['representations']<0)[0]]) if missing_atoms: self.atoms_not_represented.clear_output() with self.atoms_not_represented: @@ -506,18 +511,6 @@ def apply_representations(self,change=None): self.orient_z_up() - @observe("list_of_representations") - def _observe_list_of_representations(self, _=None): - """Update the value of representation selection widgets when the list of representations changes.""" - #print("obsereve list of representations") - if not self.representations and self.list_of_representations: - self.representations = [Representation()] - self.representations[0].style.value = "molecule" - for i,list in enumerate(self.list_of_representations): - self.representations[i].selection.value = list_to_string_range(list,shift=1) - #print("in observe list the list is ",self.list_of_representations) - self.update_representations() - @observe("cell") def _observe_cell(self, _=None): @@ -848,22 +841,24 @@ def _on_atom_click(self, _=None): if self.rep_dict: component = self._viewer.picked["component"] - index = self._translate_i_loc_glob[(component, index)]%self.natoms - - + + index = self._translate_i_loc_glob[(component, index)] + selection = self.selection.copy() - + if selection: - if index not in selection: + if index not in selection : selection.append(index) else: selection.remove(index) else: selection = [index] - self.selection = selection - + #selection_unit = [i for i in selection if i < self.natoms] + self.selection = selection + #self.selection = selection_unit + return @@ -931,8 +926,7 @@ def update_viewer(self, c=None): cid = self._viewer.component_0.id self._viewer.remove_component(cid) - #copy representations of structure into rep of display structure - print("inside update viewer") + #copy representations of structure into rep of display structure if self.displayed_structure: #print("inside displaying") @@ -1018,6 +1012,10 @@ def _observe_selection(self, _=None): if self._selected_atoms.value: self.configuration_box.selected_index = self.selection_tab_idx + def clear_selection(self, _=None): + self.set_trait("selection", list()), + self.set_trait("selection_adv", ""), + def apply_selection(self, _=None): """Apply selection specified in the text field.""" selection_string = self._selected_atoms.value @@ -1028,6 +1026,7 @@ def apply_selection(self, _=None): if syntax_ok: self.wrong_syntax.layout.visibility = "hidden" self.selection = expanded_selection + self.selection = expanded_selection self._selected_atoms.value = ( selection_string # Keep the old string for further editing. ) @@ -1092,7 +1091,6 @@ class StructureDataViewer(_StructureDataBaseViewer): """ structure = Union([Instance(Atoms), Instance(Node)], allow_none=True) - input_structure = Union([Instance(Atoms), Instance(Data)], allow_none=True) displayed_structure = Instance(Atoms, allow_none=True, read_only=True) pk = Int(allow_none=True) @@ -1107,62 +1105,49 @@ def repeat(self, _=None): self.set_trait("displayed_structure", self.structure.repeat(self.supercell)) self.apply_representations() - @validate("structure") - def _valid_structure(self, change): # pylint: disable=no-self-use - """Update structure.""" - structure = change["value"] - - if structure is None: - return None # if no structure provided, the rest of the code can be skipped - - if isinstance(structure, Atoms): - self.pk = None - return structure - if isinstance(structure, Node): - self.pk = structure.pk - return structure.get_ase() - raise ValueError( - "Unsupported type {}, structure must be one of the following types: " - "ASE Atoms object, AiiDA CifData or StructureData." - ) - - @observe("input_structure") - def _observe_input_structure(self, change): - """Update displayed structure.""" - #self.brand_new_structure = True - self.natoms = 0 - if change["new"] is not None: - if isinstance(change["new"], Atoms): - self.natoms = len(change["new"]) - else: - self.natoms = len(change["new"].get_ase()) - new_list_of_representations = deepcopy(self.list_of_representations) - #print("in observe structure previous list", new_list_of_representations) - for rep in range(len(new_list_of_representations)): - new_list_of_representations[rep]=[] - new_list_of_representations[0] = list(range(self.natoms)) - #print("in observe structure updated list", new_list_of_representations) - self.list_of_representations = new_list_of_representations + # @validate("structure") + # def _valid_structure(self, change): # pylint: disable=no-self-use + # """Update structure.""" + # structure = change["value"] + + # if structure is None: + # return None # if no structure provided, the rest of the code can be skipped + + # if isinstance(structure, Atoms): + # self.pk = None + # return structure + # if isinstance(structure, Node): + # self.pk = structure.pk + # return structure.get_ase() + # raise ValueError( + # "Unsupported type {}, structure must be one of the following types: " + # "ASE Atoms object, AiiDA CifData or StructureData." + # ) + @observe("structure") def _observe_structure(self, change): """Update displayed_structure trait after the structure trait has been modified.""" - # if a structure originates from an importer widget previous represnetations are kept - # but all atoms are put in the first one - #print("brand new structure?",self.brand_new_structure) if change["new"] is not None: self.natoms = len(change["new"]) + if 'representations' not in change["new"].arrays: + print("Resetting representations") + change["new"].set_array("representations", np.zeros(self.natoms)) + self.structure = change["new"].copy() + + self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) self.set_trait("cell", change["new"].cell) + self.structure = change["new"].copy() else: self.set_trait("displayed_structure", None) self.set_trait("cell", None) @observe("displayed_structure") - def _update_structure_viewer(self, change): + def _observe_displayed_structure(self, change): """Update the view if displayed_structure trait was modified.""" - self.update_viewer() + self.update_representations() # not needed for the moment, actions are defined in the editors functions # to avoid unnecessary updates # reactivation would require some care @@ -1338,7 +1323,6 @@ def union(opa, opb): def create_selection_info(self): """Create information to be displayed with selected atoms""" - if not self.selection: return "" @@ -1346,24 +1330,27 @@ def print_pos(pos): return " ".join([str(i) for i in pos.round(2)]) def add_info(indx, atom): - return f"Id: {indx + 1}; Symbol: {atom.symbol}; Coordinates: ({print_pos(atom.position)})
" + id_string="Id:" + if indx >=self.natoms: + id_string = "Id-x"+str(int(indx/self.natoms)) + return f"{id_string} {indx + 1}; Symbol: {atom.symbol}; Coordinates: ({print_pos(atom.position)})
" # Find geometric center. geom_center = print_pos( - np.average(self.structure[self.selection].get_positions(), axis=0) + np.average(self.displayed_structure[self.selection].get_positions(), axis=0) ) # Report coordinates. if len(self.selection) == 1: - return add_info(self.selection[0], self.structure[self.selection[0]]) + return add_info(self.selection[0], self.displayed_structure[self.selection[0]]) # Report coordinates, distance and center. if len(self.selection) == 2: info = "" - info += add_info(self.selection[0], self.structure[self.selection[0]]) - info += add_info(self.selection[1], self.structure[self.selection[1]]) - dist = self.structure.get_distance(*self.selection) - distv = self.structure.get_distance(*self.selection, vector=True) + info += add_info(self.selection[0], self.displayed_structure[self.selection[0]]) + info += add_info(self.selection[1], self.displayed_structure[self.selection[1]]) + dist = self.displayed_structure.get_distance(*self.selection) + distv = self.displayed_structure.get_distance(*self.selection, vector=True) info += f"Distance: {dist:.2f} ({print_pos(distv)})
Geometric center: ({geom_center})" return info @@ -1373,7 +1360,7 @@ def add_info(indx, atom): # Report angle geometric center and normal. if len(self.selection) == 3: - angle = self.structure.get_angle(*self.selection).round(2) + angle = self.displayed_structure.get_angle(*self.selection).round(2) normal = np.cross( *self.structure.get_distances( self.selection[1], @@ -1388,7 +1375,7 @@ def add_info(indx, atom): # Report dihedral angle and geometric center. if len(self.selection) == 4: try: - dihedral = self.structure.get_dihedral(self.selection) * 180 / np.pi + dihedral = self.displayed_structure.get_dihedral(self.selection) * 180 / np.pi dihedral_str = f"{dihedral:.2f}" except ZeroDivisionError: dihedral_str = "nan" @@ -1401,6 +1388,7 @@ def _observe_selection_adv(self, _=None): """Apply the advanced boolean atom selection""" try: sel = self.parse_advanced_sel(condition=self.selection_adv) + self.selection = sel self._selected_atoms.value = list_to_string_range(sel, shift=1) self.wrong_syntax.layout.visibility = "hidden" self.apply_selection() From 8467ce66ac5d6f2860301ed3c4404827e790f491 Mon Sep 17 00:00:00 2001 From: Carlo Date: Fri, 21 Oct 2022 12:39:48 +0000 Subject: [PATCH 16/87] updated validate structure --- aiidalab_widgets_base/viewers.py | 49 +++++++++++++++----------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 48ce7f45..ed6a9722 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -1105,38 +1105,35 @@ def repeat(self, _=None): self.set_trait("displayed_structure", self.structure.repeat(self.supercell)) self.apply_representations() - # @validate("structure") - # def _valid_structure(self, change): # pylint: disable=no-self-use - # """Update structure.""" - # structure = change["value"] - - # if structure is None: - # return None # if no structure provided, the rest of the code can be skipped - - # if isinstance(structure, Atoms): - # self.pk = None - # return structure - # if isinstance(structure, Node): - # self.pk = structure.pk - # return structure.get_ase() - # raise ValueError( - # "Unsupported type {}, structure must be one of the following types: " - # "ASE Atoms object, AiiDA CifData or StructureData." - # ) + @validate("structure") + def _valid_structure(self, change): # pylint: disable=no-self-use + """Update structure.""" + structure = change["value"] + if structure is None: + return None # if no structure provided, the rest of the code can be skipped + if isinstance(structure, Atoms): + self.pk = None + elif isinstance(structure, Node): + self.pk = structure.pk + structure = structure.get_ase() + else: + raise ValueError( + "Unsupported type {}, structure must be one of the following types: " + "ASE Atoms object, AiiDA CifData or StructureData." + ) + self.natoms= len(structure) + if 'representations' not in structure.arrays: + print("Resetting representations") + structure.set_array("representations", np.zeros(self.natoms)) + + return structure @observe("structure") def _observe_structure(self, change): """Update displayed_structure trait after the structure trait has been modified.""" - if change["new"] is not None: - self.natoms = len(change["new"]) - if 'representations' not in change["new"].arrays: - print("Resetting representations") - change["new"].set_array("representations", np.zeros(self.natoms)) - self.structure = change["new"].copy() - - + if change["new"] is not None: self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) self.set_trait("cell", change["new"].cell) self.structure = change["new"].copy() From 745e6cbc59898d1af4d589d959511f98aae9c6cf Mon Sep 17 00:00:00 2001 From: Carlo Date: Fri, 21 Oct 2022 13:31:43 +0000 Subject: [PATCH 17/87] Fixed bug of representrations not passed --- aiidalab_widgets_base/viewers.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index ed6a9722..26da7f95 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -444,7 +444,7 @@ def update_representations(self, change=None): self.representations = [Representation()] # shoudl not be needed teh check of more reps in array['representations'] than actually defined reps - representations = self.structure.arrays['representations'] + representations = self.structure.arrays["representations"] for rep in set(representations): if rep >=0: # negative values are used for atoms not represented (different from the case of hidden representations) self.representations[int(rep)].selection.value = list_to_string_range([int(i) for i in np.where(representations == rep )[0]],shift=1) @@ -467,12 +467,16 @@ def replicate_representations(self, change=None): def on_click_apply_representations(self,change=None): """Updates self.displayed_structure.arrays['representations'] according to user defined representations""" - + + arrayrepresentations=np.zeros(self.natoms) for irep, rep in enumerate(self.representations): selection = string_range_to_list(rep.selection.value, shift=-1)[0] for index in selection: - self.structure.arrays['representations'][index] = irep - print("in on click apply rep", self.structure.arrays['representations']) + arrayrepresentations[index] = irep + + self.structure.set_array("representations", arrayrepresentations) + + self.apply_representations() @@ -496,11 +500,11 @@ def apply_representations(self,change=None): self.rep_dict_unit[current_rep]["name"] = "rep"+str(current_rep) current_rep+=1 - missing_atoms = set([int(i) for i in np.where(self.displayed_structure.arrays['representations']<0)[0]]) + missing_atoms = set([int(i) for i in np.where(self.structure.arrays["representations"]<0)[0]]) if missing_atoms: self.atoms_not_represented.clear_output() with self.atoms_not_represented: - print("Atoms not represented: ",list_to_string_range(list(missing_atoms),shift=1)) + print("Atoms excluded from representations: ",list_to_string_range(list(missing_atoms),shift=1)) else: self.atoms_not_represented.clear_output() #print("before calling replicate the rep dict is",self.rep_dict_unit) @@ -1122,7 +1126,7 @@ def _valid_structure(self, change): # pylint: disable=no-self-use "ASE Atoms object, AiiDA CifData or StructureData." ) self.natoms= len(structure) - if 'representations' not in structure.arrays: + if "representations" not in structure.arrays: print("Resetting representations") structure.set_array("representations", np.zeros(self.natoms)) @@ -1136,7 +1140,7 @@ def _observe_structure(self, change): if change["new"] is not None: self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) self.set_trait("cell", change["new"].cell) - self.structure = change["new"].copy() + self.structure = change["new"] else: self.set_trait("displayed_structure", None) self.set_trait("cell", None) From e0f5318a316e352fb97dcc34350d3ed538600d26 Mon Sep 17 00:00:00 2001 From: Carlo Date: Fri, 21 Oct 2022 13:48:36 +0000 Subject: [PATCH 18/87] default -1 for arrays['representations'] for atoms not included in a rep --- aiidalab_widgets_base/structures.py | 1 - aiidalab_widgets_base/viewers.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 48af5185..eb1f99ff 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -1420,7 +1420,6 @@ def mod_element(self, _=None, atoms=None, selection=None): @_register_selection def copy_sel(self, _=None, atoms=None, selection=None): """Copy selected atoms and shift by 1.0 A along X-axis.""" - print("in copy representations", self.structure.arrays["representations"]) last_atom = atoms.get_global_number_of_atoms() # The action diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 26da7f95..406cd0f8 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -468,7 +468,8 @@ def replicate_representations(self, change=None): def on_click_apply_representations(self,change=None): """Updates self.displayed_structure.arrays['representations'] according to user defined representations""" - arrayrepresentations=np.zeros(self.natoms) + # negative value means an atom is not assigned to a representation + arrayrepresentations=-1*np.ones(self.natoms) for irep, rep in enumerate(self.representations): selection = string_range_to_list(rep.selection.value, shift=-1)[0] for index in selection: @@ -1127,7 +1128,6 @@ def _valid_structure(self, change): # pylint: disable=no-self-use ) self.natoms= len(structure) if "representations" not in structure.arrays: - print("Resetting representations") structure.set_array("representations", np.zeros(self.natoms)) return structure From a04e32cdcbf44dfae415135d42cd20bfca731bb9 Mon Sep 17 00:00:00 2001 From: Carlo Date: Fri, 21 Oct 2022 15:08:18 +0000 Subject: [PATCH 19/87] added show of representations --- aiidalab_widgets_base/viewers.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 406cd0f8..ebc3637e 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -180,7 +180,7 @@ class Representation(ipw.HBox): master_class = None def __init__(self, indices="1..2"): self.selection = ipw.Text(description="atoms:",value="",style={"description_width": "initial"} ) - self.style = ipw.Dropdown(options=["molecule","surface"],value="molecule",description="mode",disabled=False) + self.style = ipw.Dropdown(options=["molecule","surface","bulk"],value="molecule",description="mode",disabled=False) self.show = ipw.Checkbox(value=True,description="show",disabled=False) # Delete button. @@ -258,7 +258,17 @@ def __init__( "highlight_opacity": 0.6, "name": "surface", "type": "ball+stick", - },} + }, + "bulk": { + "ids": "1..2", + "aspectRatio": 5, + "highlight_aspectRatio": 5.1, + "highlight_color": "green", + "highlight_opacity": 0.6, + "name": "surface", + "type": "ball+stick", + }, + } view_box = ipw.VBox([self._viewer]) @@ -470,13 +480,17 @@ def on_click_apply_representations(self,change=None): # negative value means an atom is not assigned to a representation arrayrepresentations=-1*np.ones(self.natoms) + arrayrepresentationsshow=np.zeros(self.natoms) for irep, rep in enumerate(self.representations): selection = string_range_to_list(rep.selection.value, shift=-1)[0] for index in selection: arrayrepresentations[index] = irep + if rep.show.value: + arrayrepresentationsshow[index] = 1 self.structure.set_array("representations", arrayrepresentations) - + self.structure.set_array("representationsshow", arrayrepresentationsshow) + print("show",self.structure.arrays["representationsshow"]) self.apply_representations() @@ -493,8 +507,8 @@ def apply_representations(self,change=None): #self.brand_new_structure=False for rep in self.representations: # in representation dictionary indexes start from 0 so we transform '1..4' in '0..3' - idsl = string_range_to_list(rep.selection.value, shift=-1)[0] - ids = list_to_string_range(idsl,shift=0) + idsl = string_range_to_list(rep.selection.value, shift=-1)[0] + ids = list_to_string_range([i for i in idsl if self.structure.arrays['representationsshow'][i]],shift=0) self.rep_dict_unit[current_rep] = deepcopy(self.default_representations[rep.style.value]) self.rep_dict_unit[current_rep]["ids"] = ids @@ -509,6 +523,7 @@ def apply_representations(self,change=None): else: self.atoms_not_represented.clear_output() #print("before calling replicate the rep dict is",self.rep_dict_unit) + print(self.rep_dict_unit) self.replicate_representations() self.update_viewer() #if self.first_update_of_viewer: @@ -1129,6 +1144,8 @@ def _valid_structure(self, change): # pylint: disable=no-self-use self.natoms= len(structure) if "representations" not in structure.arrays: structure.set_array("representations", np.zeros(self.natoms)) + if "representationsshow" not in structure.arrays: + structure.set_array("representationsshow", np.ones(self.natoms)) return structure From 755a6a4d3bfde42dfd8fb52437732e7af49da561 Mon Sep 17 00:00:00 2001 From: Carlo Date: Fri, 21 Oct 2022 15:35:39 +0000 Subject: [PATCH 20/87] modification --- aiidalab_widgets_base/viewers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index ebc3637e..b408b27c 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -490,7 +490,6 @@ def on_click_apply_representations(self,change=None): self.structure.set_array("representations", arrayrepresentations) self.structure.set_array("representationsshow", arrayrepresentationsshow) - print("show",self.structure.arrays["representationsshow"]) self.apply_representations() @@ -523,7 +522,6 @@ def apply_representations(self,change=None): else: self.atoms_not_represented.clear_output() #print("before calling replicate the rep dict is",self.rep_dict_unit) - print(self.rep_dict_unit) self.replicate_representations() self.update_viewer() #if self.first_update_of_viewer: @@ -949,7 +947,7 @@ def update_viewer(self, c=None): #copy representations of structure into rep of display structure if self.displayed_structure: - #print("inside displaying") + print("inside displaying ", self.rep_dict,self.displayed_structure) for component in range(len(self.rep_dict)): rep_indexes = list( string_range_to_list(self.rep_dict[component]["ids"], shift=0)[0] From 3ae05c362272c3fe62100efdc478a6978f63d285 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 21 Oct 2022 15:36:15 +0000 Subject: [PATCH 21/87] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- aiidalab_widgets_base/structures.py | 61 +++--- aiidalab_widgets_base/viewers.py | 324 +++++++++++++++------------- 2 files changed, 210 insertions(+), 175 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index eb1f99ff..53780904 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -7,11 +7,11 @@ import pathlib import tempfile from collections import OrderedDict +from copy import deepcopy import ase import ipywidgets as ipw import numpy as np -from copy import deepcopy # spglib for cell converting import spglib @@ -30,7 +30,19 @@ from ase import Atom, Atoms from ase.data import chemical_symbols, covalent_radii from sklearn.decomposition import PCA -from traitlets import Bool,Dict, Instance, Int, List, Unicode, Union, default, dlink, link, observe +from traitlets import ( + Bool, + Dict, + Instance, + Int, + List, + Unicode, + Union, + default, + dlink, + link, + observe, +) # Local imports from .data import LigandSelectorWidget @@ -57,7 +69,7 @@ class StructureManagerWidget(ipw.VBox): structure = Union([Instance(Atoms), Instance(Data)], allow_none=True) structure_node = Instance(Data, allow_none=True, read_only=True) node_class = Unicode() - #brand_new_structure = Bool() + # brand_new_structure = Bool() SUPPORTED_DATA_FORMATS = {"CifData": "cif", "StructureData": "structure"} @@ -161,12 +173,11 @@ def _structure_importers(self, importers): if len(importers) == 1: # Assigning a function which will be called when importer provides a structure. dlink((importers[0], "structure"), (self, "input_structure")) - #if importers[0].has_trait("brand_new_structure"): + # if importers[0].has_trait("brand_new_structure"): # link((importers[0], "brand_new_structure"), (self.viewer, "brand_new_structure")) # link((importers[0], "brand_new_structure"), (self, "brand_new_structure")) return importers[0] - # Otherwise making one tab per importer. importers_tab = ipw.Tab() @@ -175,7 +186,7 @@ def _structure_importers(self, importers): # Labeling tabs. importers_tab.set_title(i, importer.title) dlink((importer, "structure"), (self, "input_structure")) - #if importer.has_trait("brand_new_structure"): + # if importer.has_trait("brand_new_structure"): # link((importer, "brand_new_structure"), (self.viewer, "brand_new_structure")) # link((importer, "brand_new_structure"), (self, "brand_new_structure")) return importers_tab @@ -201,7 +212,7 @@ def _structure_editors(self, editors): editors_tab.set_title(i, editor.title) link((editor, "structure"), (self, "structure")) if editor.has_trait("selection"): - link((editor, "selection"), (self.viewer, "selection")) + link((editor, "selection"), (self.viewer, "selection")) if editor.has_trait("camera_orientation"): dlink( (self.viewer._viewer, "_camera_orientation"), @@ -343,7 +354,6 @@ def _observe_input_structure(self, change): else: self.structure = None - @observe("structure") def _structure_changed(self, change=None): """Perform some operations that depend on the value of `structure` trait. @@ -351,7 +361,7 @@ def _structure_changed(self, change=None): This function enables/disables `btn_store` widget if structure is provided/set to None. Also, the function sets `structure_node` trait to the selected node type. """ - + if not self.structure_set_by_undo: self.history.append(change["new"]) @@ -692,7 +702,7 @@ class SmilesWidget(ipw.VBox): """Convert SMILES into 3D structure.""" structure = Instance(Atoms, allow_none=True) - #brand_new_structure = Bool() + # brand_new_structure = Bool() SPINNER = """""" @@ -1195,8 +1205,7 @@ def disable_element(_=None): ] ) - - def find_index(self,list_of_lists, element): + def find_index(self, list_of_lists, element): for i, x in enumerate(list_of_lists): if element in x: return i @@ -1278,7 +1287,7 @@ def translate_dr(self, _=None, atoms=None, selection=None): self.action_vector * self.displacement.value ) - #self.brand_new_structure = False + # self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1289,7 +1298,7 @@ def translate_dxdydz(self, _=None, atoms=None, selection=None): # The action. atoms.positions[self.selection] += np.array(self.str2vec(self.dxyz.value)) - #self.brand_new_structure = False + # self.brand_new_structure = False self.structure, self.selection = atoms, selection @@ -1301,7 +1310,7 @@ def translate_to_xyz(self, _=None, atoms=None, selection=None): geo_center = np.average(self.structure[self.selection].get_positions(), axis=0) atoms.positions[self.selection] += self.str2vec(self.dxyz.value) - geo_center - #self.brand_new_structure = False + # self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1316,7 +1325,7 @@ def rotate(self, _=None, atoms=None, selection=None): rotated_subset.rotate(self.phi.value, v=vec, center=center, rotate_cell=False) atoms.positions[list(self.selection)] = rotated_subset.positions - #self.brand_new_structure = False + # self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1350,7 +1359,7 @@ def mirror(self, _=None, norm=None, point=None, atoms=None, selection=None): # Mirror atoms. atoms.positions[selection] -= 2 * projections - #self.brand_new_structure = False + # self.brand_new_structure = False self.structure, self.selection = atoms, selection def mirror_3p(self, _=None): @@ -1375,7 +1384,7 @@ def align(self, _=None, atoms=None, selection=None): subset.rotate(self.action_vector, self.str2vec(self.dxyz.value), center=center) atoms.positions[selection] = subset.positions - #self.brand_new_structure = False + # self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1384,7 +1393,6 @@ def mod_element(self, _=None, atoms=None, selection=None): """Modify selected atoms into the given element.""" last_atom = atoms.get_global_number_of_atoms() - if self.ligand.value == 0: for idx in self.selection: new = Atom(self.element.value) @@ -1399,17 +1407,15 @@ def mod_element(self, _=None, atoms=None, selection=None): initial_ligand = self.ligand.rotate( align_to=self.action_vector, remove_anchor=True ) - + for idx in self.selection: position = self.structure.positions[idx].copy() lgnd = initial_ligand.copy() lgnd.translate(position) - atoms += lgnd + atoms += lgnd new_selection = [ i for i in range(last_atom, last_atom + len(selection) * len(lgnd)) ] - - # the order of the traitlets below is important self.selection = [] @@ -1427,12 +1433,8 @@ def copy_sel(self, _=None, atoms=None, selection=None): add_atoms.translate([1.0, 0, 0]) atoms += add_atoms - - new_selection = [i for i in range(last_atom, last_atom + len(selection))] - - # the order of the traitlets below is important self.selection = [] self.structure = atoms @@ -1470,7 +1472,7 @@ def add(self, _=None, atoms=None, selection=None): i for i in range(last_atom, last_atom + len(selection) * len(lgnd)) ] - #self.brand_new_structure = False + # self.brand_new_structure = False # the order of the traitlets below is important self.selection = [] @@ -1483,6 +1485,5 @@ def remove(self, _, atoms=None, selection=None): """Remove selected atoms.""" del [atoms[selection]] - self.selection = [] - self.structure = atoms \ No newline at end of file + self.structure = atoms diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index b408b27c..a1495204 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -2,12 +2,12 @@ # pylint: disable=no-self-use import base64 -from hashlib import new +import itertools import re import warnings from copy import deepcopy +from hashlib import new -import itertools import ipywidgets as ipw import nglview import numpy as np @@ -15,7 +15,7 @@ import traitlets from aiida.cmdline.utils.common import get_workchain_report from aiida.cmdline.utils.query import formatting -from aiida.orm import Node, Data +from aiida.orm import Data, Node from ase import Atoms, neighborlist from ase.cell import Cell from IPython.display import clear_output, display @@ -51,7 +51,9 @@ from .utils import ase2spglib, list_to_string_range, string_range_to_list AIIDA_VIEWER_MAPPING = dict() -BOX_LAYOUT = ipw.Layout(display='flex-wrap', flex_flow='row wrap', justify_content='space-between') +BOX_LAYOUT = ipw.Layout( + display="flex-wrap", flex_flow="row wrap", justify_content="space-between" +) def register_viewer_widget(key): @@ -163,31 +165,39 @@ def __init__(self, parameter, downloadable=True, **kwargs): class Representation(ipw.HBox): """Representation for StructureData in nglviewer - if a structure is imported the traitlet import_structure will trigger - initialization of the list 'list_of_representations' e.g. [[0,1,2,3,4,5]] - the traitlet 'list_of_representations' will trigger creation of self.representations as list of Representations() - default style set in self.representations[0].style is 'molecule' - self.representations[0].selection is populated with all atoms - self.update_representations() which copyes selections in the representations widgets into the list_of_representations - ad calls self.apply_representations() - - apply_representations: - creates the representation dictionary for the main structure 'rep_dict_unit', replicates teh reprsentattions in case - of supercell and calls self.update_viewer() that creates teh nglview representattions - - Editors of a structure update the traitlet self.list_of_representations. + if a structure is imported the traitlet import_structure will trigger + initialization of the list 'list_of_representations' e.g. [[0,1,2,3,4,5]] + the traitlet 'list_of_representations' will trigger creation of self.representations as list of Representations() + default style set in self.representations[0].style is 'molecule' + self.representations[0].selection is populated with all atoms + self.update_representations() which copyes selections in the representations widgets into the list_of_representations + ad calls self.apply_representations() + + apply_representations: + creates the representation dictionary for the main structure 'rep_dict_unit', replicates teh reprsentattions in case + of supercell and calls self.update_viewer() that creates teh nglview representattions + + Editors of a structure update the traitlet self.list_of_representations. """ + master_class = None + def __init__(self, indices="1..2"): - self.selection = ipw.Text(description="atoms:",value="",style={"description_width": "initial"} ) - self.style = ipw.Dropdown(options=["molecule","surface","bulk"],value="molecule",description="mode",disabled=False) - self.show = ipw.Checkbox(value=True,description="show",disabled=False) + self.selection = ipw.Text( + description="atoms:", value="", style={"description_width": "initial"} + ) + self.style = ipw.Dropdown( + options=["molecule", "surface", "bulk"], + value="molecule", + description="mode", + disabled=False, + ) + self.show = ipw.Checkbox(value=True, description="show", disabled=False) # Delete button. self.delete_button = ipw.Button(description="Delete", button_style="danger") self.delete_button.on_click(self.delete_myself) - super().__init__( children=[ self.selection, @@ -195,12 +205,12 @@ def __init__(self, indices="1..2"): self.show, self.delete_button, ] - ) + ) - def delete_myself(self, _): self.master_class.delete_representation(self) + class _StructureDataBaseViewer(ipw.VBox): """Base viewer class for AiiDA structure or trajectory objects. @@ -212,9 +222,10 @@ class _StructureDataBaseViewer(ipw.VBox): :type default_camera: string """ + representations = traitlets.List() natoms = Int() - #brand_new_structure = Bool(True) + # brand_new_structure = Bool(True) selection = List(Int) selection_adv = Unicode() supercell = List(Int) @@ -237,37 +248,38 @@ def __init__( self._viewer.camera = default_camera self._viewer.observe(self._on_atom_click, names="picked") self._viewer.stage.set_parameters(mouse_preset="pymol") - #self.first_update_of_viewer = True - self.natoms=0 - self.rep_dict={} - self.rep_dict_unit={} - self.default_representations={ "molecule": { - "ids": "1..2", - "aspectRatio": 3.5, - "highlight_aspectRatio": 3.6, - "highlight_color": "red", - "highlight_opacity": 0.6, - "name": "molecule", - "type": "ball+stick", - }, - "surface": { - "ids": "1..2", - "aspectRatio": 10, - "highlight_aspectRatio": 10.1, - "highlight_color": "green", - "highlight_opacity": 0.6, - "name": "surface", - "type": "ball+stick", - }, - "bulk": { - "ids": "1..2", - "aspectRatio": 5, - "highlight_aspectRatio": 5.1, - "highlight_color": "green", - "highlight_opacity": 0.6, - "name": "surface", - "type": "ball+stick", - }, + # self.first_update_of_viewer = True + self.natoms = 0 + self.rep_dict = {} + self.rep_dict_unit = {} + self.default_representations = { + "molecule": { + "ids": "1..2", + "aspectRatio": 3.5, + "highlight_aspectRatio": 3.6, + "highlight_color": "red", + "highlight_opacity": 0.6, + "name": "molecule", + "type": "ball+stick", + }, + "surface": { + "ids": "1..2", + "aspectRatio": 10, + "highlight_aspectRatio": 10.1, + "highlight_color": "green", + "highlight_opacity": 0.6, + "name": "surface", + "type": "ball+stick", + }, + "bulk": { + "ids": "1..2", + "aspectRatio": 5, + "highlight_aspectRatio": 5.1, + "highlight_color": "green", + "highlight_opacity": 0.6, + "name": "surface", + "type": "ball+stick", + }, } view_box = ipw.VBox([self._viewer]) @@ -407,19 +419,26 @@ def change_camera(change): center_button = ipw.Button(description="Center molecule") center_button.on_click(lambda c: self._viewer.center()) - # 5. representations buttons - self.atoms_not_represented=ipw.Output() + self.atoms_not_represented = ipw.Output() self.add_new_rep_button = ipw.Button(description="Add rep", button_style="info") self.add_new_rep_button.on_click(self.add_representation) apply_rep = ipw.Button(description="Apply rep") - apply_rep.on_click(self.on_click_apply_representations) + apply_rep.on_click(self.on_click_apply_representations) self.representation_output = ipw.Box(layout=BOX_LAYOUT) return ipw.VBox( - [supercell_selector, background_color, camera_type, - self.add_new_rep_button, self.representation_output,self.atoms_not_represented, apply_rep, center_button] + [ + supercell_selector, + background_color, + camera_type, + self.add_new_rep_button, + self.representation_output, + self.atoms_not_represented, + apply_rep, + center_button, + ] ) def add_representation(self, _): @@ -433,14 +452,16 @@ def delete_representation(self, representation): self.representation_add_message.message = f"""Error: Rep. {representation} not found.""" return - self.representations = self.representations[:index] + self.representations[index+1:] + self.representations = ( + self.representations[:index] + self.representations[index + 1 :] + ) del representation self.apply_representations() @observe("representations") def _observe_representations(self, change): """Update the list of representations.""" - if change['new']: + if change["new"]: self.representation_output.children = change["new"] self.representations[-1].master_class = self else: @@ -453,34 +474,42 @@ def update_representations(self, change=None): if number_of_representation_widgets == 0: self.representations = [Representation()] # shoudl not be needed teh check of more reps in array['representations'] than actually defined reps - - representations = self.structure.arrays["representations"] + + representations = self.structure.arrays["representations"] for rep in set(representations): - if rep >=0: # negative values are used for atoms not represented (different from the case of hidden representations) - self.representations[int(rep)].selection.value = list_to_string_range([int(i) for i in np.where(representations == rep )[0]],shift=1) + if ( + rep >= 0 + ): # negative values are used for atoms not represented (different from the case of hidden representations) + self.representations[ + int(rep) + ].selection.value = list_to_string_range( + [int(i) for i in np.where(representations == rep)[0]], shift=1 + ) # empty selection field for unused representations for rep in range(number_of_representation_widgets): - if rep not in set([int(i) for i in representations]): - self.representations[rep].selection.value = "" + if rep not in {int(i) for i in representations}: + self.representations[rep].selection.value = "" self.apply_representations() def replicate_representations(self, change=None): - #copy representations of structure into rep of display structure + # copy representations of structure into rep of display structure nrep = np.prod(self.supercell) self.rep_dict = deepcopy(self.rep_dict_unit) - if nrep > 1: + if nrep > 1: for component in range(len(self.rep_dict_unit.keys())): - ids = string_range_to_list(self.rep_dict_unit[component]["ids"] , shift=0)[0] - new_ids = [i+rep*self.natoms for rep in range(nrep) for i in ids] - self.rep_dict[component]["ids"] = list_to_string_range(new_ids,shift=0) - self._gen_translation_indexes() - - def on_click_apply_representations(self,change=None): + ids = string_range_to_list( + self.rep_dict_unit[component]["ids"], shift=0 + )[0] + new_ids = [i + rep * self.natoms for rep in range(nrep) for i in ids] + self.rep_dict[component]["ids"] = list_to_string_range(new_ids, shift=0) + self._gen_translation_indexes() + + def on_click_apply_representations(self, change=None): """Updates self.displayed_structure.arrays['representations'] according to user defined representations""" # negative value means an atom is not assigned to a representation - arrayrepresentations=-1*np.ones(self.natoms) - arrayrepresentationsshow=np.zeros(self.natoms) + arrayrepresentations = -1 * np.ones(self.natoms) + arrayrepresentationsshow = np.zeros(self.natoms) for irep, rep in enumerate(self.representations): selection = string_range_to_list(rep.selection.value, shift=-1)[0] for index in selection: @@ -493,43 +522,46 @@ def on_click_apply_representations(self,change=None): self.apply_representations() - - + def apply_representations(self, change=None): + # iterate on number of representations + self.rep_dict_unit = {} + current_rep = 0 - - def apply_representations(self,change=None): - #iterate on number of representations - self.rep_dict_unit={} - current_rep=0 - - - #self.brand_new_structure=False + # self.brand_new_structure=False for rep in self.representations: # in representation dictionary indexes start from 0 so we transform '1..4' in '0..3' - idsl = string_range_to_list(rep.selection.value, shift=-1)[0] - ids = list_to_string_range([i for i in idsl if self.structure.arrays['representationsshow'][i]],shift=0) + idsl = string_range_to_list(rep.selection.value, shift=-1)[0] + ids = list_to_string_range( + [i for i in idsl if self.structure.arrays["representationsshow"][i]], + shift=0, + ) - self.rep_dict_unit[current_rep] = deepcopy(self.default_representations[rep.style.value]) + self.rep_dict_unit[current_rep] = deepcopy( + self.default_representations[rep.style.value] + ) self.rep_dict_unit[current_rep]["ids"] = ids - self.rep_dict_unit[current_rep]["name"] = "rep"+str(current_rep) + self.rep_dict_unit[current_rep]["name"] = "rep" + str(current_rep) - current_rep+=1 - missing_atoms = set([int(i) for i in np.where(self.structure.arrays["representations"]<0)[0]]) + current_rep += 1 + missing_atoms = { + int(i) for i in np.where(self.structure.arrays["representations"] < 0)[0] + } if missing_atoms: self.atoms_not_represented.clear_output() with self.atoms_not_represented: - print("Atoms excluded from representations: ",list_to_string_range(list(missing_atoms),shift=1)) + print( + "Atoms excluded from representations: ", + list_to_string_range(list(missing_atoms), shift=1), + ) else: self.atoms_not_represented.clear_output() - #print("before calling replicate the rep dict is",self.rep_dict_unit) + # print("before calling replicate the rep dict is",self.rep_dict_unit) self.replicate_representations() self.update_viewer() - #if self.first_update_of_viewer: - #self.first_update_of_viewer = self.orient_z_up() + # if self.first_update_of_viewer: + # self.first_update_of_viewer = self.orient_z_up() self.orient_z_up() - - @observe("cell") def _observe_cell(self, _=None): # only update cell info when it is a 3D structure. @@ -827,20 +859,19 @@ def _gen_translation_indexes(self): and {(0,0):0,(0,1):1,(0,2):2,(1,0):3,(2,0):4,(2,1):5,(2,2):6,(2,3):7,(2,4):8,(2,5):9} """ - self._translate_i_glob_loc = {} - self._translate_i_loc_glob = {} + self._translate_i_loc_glob = {} for component in range(len(self.rep_dict.keys())): comp_i = 0 ids = list( - string_range_to_list(self.rep_dict[component]["ids"], shift=0)[0]) + string_range_to_list(self.rep_dict[component]["ids"], shift=0)[0] + ) for i_g in ids: self._translate_i_glob_loc[i_g] = (component, comp_i) self._translate_i_loc_glob[(component, comp_i)] = i_g comp_i += 1 - def _translate_glob_loc(self, indexes): """From global index to indexes of different components.""" all_comp = [list() for i in range(len(self.rep_dict.keys()))] @@ -859,29 +890,24 @@ def _on_atom_click(self, _=None): if self.rep_dict: component = self._viewer.picked["component"] - - index = self._translate_i_loc_glob[(component, index)] - + + index = self._translate_i_loc_glob[(component, index)] + selection = self.selection.copy() - + if selection: - if index not in selection : + if index not in selection: selection.append(index) else: selection.remove(index) else: selection = [index] - - #selection_unit = [i for i in selection if i < self.natoms] - self.selection = selection - #self.selection = selection_unit - - - return - - + # selection_unit = [i for i in selection if i < self.natoms] + self.selection = selection + # self.selection = selection_unit + return def highlight_atoms( self, @@ -936,23 +962,26 @@ def highlight_atoms( opacity=opacity, component=component, ) + def update_viewer(self, c=None): with self.hold_trait_notifications(): - + while hasattr(self._viewer, "component_0"): self._viewer.component_0.clear_representations() cid = self._viewer.component_0.id self._viewer.remove_component(cid) - #copy representations of structure into rep of display structure + # copy representations of structure into rep of display structure if self.displayed_structure: - print("inside displaying ", self.rep_dict,self.displayed_structure) + print("inside displaying ", self.rep_dict, self.displayed_structure) for component in range(len(self.rep_dict)): rep_indexes = list( - string_range_to_list(self.rep_dict[component]["ids"], shift=0)[0] + string_range_to_list(self.rep_dict[component]["ids"], shift=0)[ + 0 + ] ) - + if rep_indexes: mol = self.displayed_structure[rep_indexes] @@ -971,15 +1000,15 @@ def update_viewer(self, c=None): self._viewer.add_licorice(opacity=1.0, component=component) elif self.rep_dict[component]["type"] == "hyperball": self._viewer.add_hyperball(opacity=1.0, component=component) - #self._gen_translation_indexes() + # self._gen_translation_indexes() self._viewer.add_unitcell() self._viewer.center() def orient_z_up(self, _=None): try: - #print("inside orient_z_up") + # print("inside orient_z_up") if self.structure is not None: - #print("orienting") + # print("orienting") cell_z = self.structure.cell[2, 2] com = self.structure.get_center_of_mass() def_orientation = self._viewer._camera_orientation @@ -1006,7 +1035,7 @@ def orient_z_up(self, _=None): return False else: return True - except AttributeError: + except AttributeError: return True @default("supercell") @@ -1031,8 +1060,8 @@ def _observe_selection(self, _=None): self.configuration_box.selected_index = self.selection_tab_idx def clear_selection(self, _=None): - self.set_trait("selection", list()), - self.set_trait("selection_adv", ""), + self.set_trait("selection", list()), + self.set_trait("selection_adv", ""), def apply_selection(self, _=None): """Apply selection specified in the text field.""" @@ -1128,32 +1157,31 @@ def _valid_structure(self, change): # pylint: disable=no-self-use """Update structure.""" structure = change["value"] if structure is None: - return None # if no structure provided, the rest of the code can be skipped + return None # if no structure provided, the rest of the code can be skipped if isinstance(structure, Atoms): self.pk = None elif isinstance(structure, Node): self.pk = structure.pk - structure = structure.get_ase() + structure = structure.get_ase() else: raise ValueError( "Unsupported type {}, structure must be one of the following types: " "ASE Atoms object, AiiDA CifData or StructureData." ) - self.natoms= len(structure) + self.natoms = len(structure) if "representations" not in structure.arrays: structure.set_array("representations", np.zeros(self.natoms)) if "representationsshow" not in structure.arrays: - structure.set_array("representationsshow", np.ones(self.natoms)) + structure.set_array("representationsshow", np.ones(self.natoms)) return structure - @observe("structure") def _observe_structure(self, change): """Update displayed_structure trait after the structure trait has been modified.""" - if change["new"] is not None: - self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) + if change["new"] is not None: + self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) self.set_trait("cell", change["new"].cell) self.structure = change["new"] else: @@ -1168,8 +1196,6 @@ def _observe_displayed_structure(self, change): # to avoid unnecessary updates # reactivation would require some care - - def d_from(self, operand): point = np.array([float(i) for i in operand[1:-1].split(",")]) return np.linalg.norm(self.structure.positions - point, axis=1) @@ -1346,9 +1372,9 @@ def print_pos(pos): return " ".join([str(i) for i in pos.round(2)]) def add_info(indx, atom): - id_string="Id:" - if indx >=self.natoms: - id_string = "Id-x"+str(int(indx/self.natoms)) + id_string = "Id:" + if indx >= self.natoms: + id_string = "Id-x" + str(int(indx / self.natoms)) return f"{id_string} {indx + 1}; Symbol: {atom.symbol}; Coordinates: ({print_pos(atom.position)})
" # Find geometric center. @@ -1358,13 +1384,19 @@ def add_info(indx, atom): # Report coordinates. if len(self.selection) == 1: - return add_info(self.selection[0], self.displayed_structure[self.selection[0]]) + return add_info( + self.selection[0], self.displayed_structure[self.selection[0]] + ) # Report coordinates, distance and center. if len(self.selection) == 2: info = "" - info += add_info(self.selection[0], self.displayed_structure[self.selection[0]]) - info += add_info(self.selection[1], self.displayed_structure[self.selection[1]]) + info += add_info( + self.selection[0], self.displayed_structure[self.selection[0]] + ) + info += add_info( + self.selection[1], self.displayed_structure[self.selection[1]] + ) dist = self.displayed_structure.get_distance(*self.selection) distv = self.displayed_structure.get_distance(*self.selection, vector=True) info += f"Distance: {dist:.2f} ({print_pos(distv)})
Geometric center: ({geom_center})" @@ -1391,7 +1423,9 @@ def add_info(indx, atom): # Report dihedral angle and geometric center. if len(self.selection) == 4: try: - dihedral = self.displayed_structure.get_dihedral(self.selection) * 180 / np.pi + dihedral = ( + self.displayed_structure.get_dihedral(self.selection) * 180 / np.pi + ) dihedral_str = f"{dihedral:.2f}" except ZeroDivisionError: dihedral_str = "nan" From 2957cef8bc46d50f5285f5e494a8f2ebd86f608c Mon Sep 17 00:00:00 2001 From: Carlo Pignedoli Date: Sat, 22 Oct 2022 16:20:21 +0000 Subject: [PATCH 22/87] simplifying to single component --- aiidalab_widgets_base/viewers.py | 137 ++++++++++++------------------- 1 file changed, 54 insertions(+), 83 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index a1495204..72c89cf2 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -163,7 +163,7 @@ def __init__(self, parameter, downloadable=True, **kwargs): super().__init__([self.widget], **kwargs) -class Representation(ipw.HBox): +class Representation(ipw.VBox): """Representation for StructureData in nglviewer if a structure is imported the traitlet import_structure will trigger initialization of the list 'list_of_representations' e.g. [[0,1,2,3,4,5]] @@ -186,10 +186,19 @@ def __init__(self, indices="1..2"): self.selection = ipw.Text( description="atoms:", value="", style={"description_width": "initial"} ) - self.style = ipw.Dropdown( - options=["molecule", "surface", "bulk"], - value="molecule", - description="mode", + self.repr_type = ipw.Dropdown( + options=["ball+stick"], + value="ball+stick", + description="type", + disabled=False, + ) + self.size = ipw.FloatText( + value=3, description="size", style={"description_width": "initial"} + ) + self.color = ipw.Dropdown( + options=["element", "red", "green", "blue", "yellow", "orange", "purple"], + value="element", + description="color", disabled=False, ) self.show = ipw.Checkbox(value=True, description="show", disabled=False) @@ -200,12 +209,12 @@ def __init__(self, indices="1..2"): super().__init__( children=[ - self.selection, - self.style, - self.show, - self.delete_button, - ] - ) + ipw.HBox([self.selection,self.show,self.delete_button,]), + ipw.HBox([self.repr_type, self.size, self.color])]) + + + + def delete_myself(self, _): self.master_class.delete_representation(self) @@ -223,7 +232,7 @@ class _StructureDataBaseViewer(ipw.VBox): """ - representations = traitlets.List() + all_representations = traitlets.List() natoms = Int() # brand_new_structure = Bool(True) selection = List(Int) @@ -443,36 +452,36 @@ def change_camera(change): def add_representation(self, _): """Add a representation to the list of representations.""" - self.representations = self.representations + [Representation()] + self.all_representations = self.all_representations + [Representation()] def delete_representation(self, representation): try: - index = self.representations.index(representation) + index = self.all_representations.index(representation) except ValueError: self.representation_add_message.message = f"""Error: Rep. {representation} not found.""" return - self.representations = ( - self.representations[:index] + self.representations[index + 1 :] + self.all_representations = ( + self.all_representations[:index] + self.all_representations[index + 1 :] ) del representation self.apply_representations() - @observe("representations") + @observe("all_representations") def _observe_representations(self, change): """Update the list of representations.""" if change["new"]: self.representation_output.children = change["new"] - self.representations[-1].master_class = self + self.all_representations[-1].master_class = self else: - self.representation_output.children = [] + self.all_representation_output.children = [] def update_representations(self, change=None): """Update the representations using the list of representations""" - number_of_representation_widgets = len(self.representations) + number_of_representation_widgets = len(self.all_representations) if self.displayed_structure: if number_of_representation_widgets == 0: - self.representations = [Representation()] + self.all_representations = [Representation()] # shoudl not be needed teh check of more reps in array['representations'] than actually defined reps representations = self.structure.arrays["representations"] @@ -480,7 +489,7 @@ def update_representations(self, change=None): if ( rep >= 0 ): # negative values are used for atoms not represented (different from the case of hidden representations) - self.representations[ + self.all_representations[ int(rep) ].selection.value = list_to_string_range( [int(i) for i in np.where(representations == rep)[0]], shift=1 @@ -488,21 +497,9 @@ def update_representations(self, change=None): # empty selection field for unused representations for rep in range(number_of_representation_widgets): if rep not in {int(i) for i in representations}: - self.representations[rep].selection.value = "" + self.all_representations[rep].selection.value = "" self.apply_representations() - def replicate_representations(self, change=None): - # copy representations of structure into rep of display structure - nrep = np.prod(self.supercell) - self.rep_dict = deepcopy(self.rep_dict_unit) - if nrep > 1: - for component in range(len(self.rep_dict_unit.keys())): - ids = string_range_to_list( - self.rep_dict_unit[component]["ids"], shift=0 - )[0] - new_ids = [i + rep * self.natoms for rep in range(nrep) for i in ids] - self.rep_dict[component]["ids"] = list_to_string_range(new_ids, shift=0) - self._gen_translation_indexes() def on_click_apply_representations(self, change=None): """Updates self.displayed_structure.arrays['representations'] according to user defined representations""" @@ -510,7 +507,7 @@ def on_click_apply_representations(self, change=None): # negative value means an atom is not assigned to a representation arrayrepresentations = -1 * np.ones(self.natoms) arrayrepresentationsshow = np.zeros(self.natoms) - for irep, rep in enumerate(self.representations): + for irep, rep in enumerate(self.all_representations): selection = string_range_to_list(rep.selection.value, shift=-1)[0] for index in selection: arrayrepresentations[index] = irep @@ -524,23 +521,22 @@ def on_click_apply_representations(self, change=None): def apply_representations(self, change=None): # iterate on number of representations - self.rep_dict_unit = {} - current_rep = 0 - + self.repr_params = [] # self.brand_new_structure=False - for rep in self.representations: + nrep = np.prod(self.supercell) + current_rep = 0 + for rep in self.all_representations: # in representation dictionary indexes start from 0 so we transform '1..4' in '0..3' idsl = string_range_to_list(rep.selection.value, shift=-1)[0] - ids = list_to_string_range( - [i for i in idsl if self.structure.arrays["representationsshow"][i]], - shift=0, - ) - - self.rep_dict_unit[current_rep] = deepcopy( - self.default_representations[rep.style.value] - ) - self.rep_dict_unit[current_rep]["ids"] = ids - self.rep_dict_unit[current_rep]["name"] = "rep" + str(current_rep) + idsl_rep = [i + rep * self.natoms for rep in range(nrep) for i in idsl if self.structure.arrays["representationsshow"][i]] + ids = "@" + ",".join(str(s) for s in idsl_rep) + self.repr_params.append({'type': rep.repr_type.value, + 'params': {'sele':ids, + 'opacity': 1, + 'name':'rep_'+str(current_rep), + 'aspectRatio': rep.size.value, + 'color':rep.color.value, + }}) current_rep += 1 missing_atoms = { @@ -555,8 +551,6 @@ def apply_representations(self, change=None): ) else: self.atoms_not_represented.clear_output() - # print("before calling replicate the rep dict is",self.rep_dict_unit) - self.replicate_representations() self.update_viewer() # if self.first_update_of_viewer: # self.first_update_of_viewer = self.orient_z_up() @@ -963,44 +957,19 @@ def highlight_atoms( component=component, ) - def update_viewer(self, c=None): + def remove_viewer_components(self, c=None): with self.hold_trait_notifications(): - while hasattr(self._viewer, "component_0"): self._viewer.component_0.clear_representations() cid = self._viewer.component_0.id self._viewer.remove_component(cid) - # copy representations of structure into rep of display structure + def update_viewer(self, c=None): if self.displayed_structure: - print("inside displaying ", self.rep_dict, self.displayed_structure) - for component in range(len(self.rep_dict)): - rep_indexes = list( - string_range_to_list(self.rep_dict[component]["ids"], shift=0)[ - 0 - ] - ) - - if rep_indexes: - mol = self.displayed_structure[rep_indexes] - - self._viewer.add_component( - nglview.ASEStructure(mol), default_representation=False - ) - - if self.rep_dict[component]["type"] == "ball+stick": - aspectRatio = self.rep_dict[component]["aspectRatio"] - self._viewer.add_ball_and_stick( - aspectRatio=aspectRatio, - opacity=1.0, - component=component, - ) - elif self.rep_dict[component]["type"] == "licorice": - self._viewer.add_licorice(opacity=1.0, component=component) - elif self.rep_dict[component]["type"] == "hyperball": - self._viewer.add_hyperball(opacity=1.0, component=component) - # self._gen_translation_indexes() + print("in update viewer", self.repr_params) + print(hasattr(self._viewer, "component_0")) + self._viewer.set_representations(self.repr_params,component=0) self._viewer.add_unitcell() self._viewer.center() @@ -1191,7 +1160,9 @@ def _observe_structure(self, change): @observe("displayed_structure") def _observe_displayed_structure(self, change): """Update the view if displayed_structure trait was modified.""" - self.update_representations() + if change["new"] is not None: + self._viewer.add_component(nglview.ASEStructure(self.displayed_structure), default_representation=False,name='Structure') + self.update_representations() # not needed for the moment, actions are defined in the editors functions # to avoid unnecessary updates # reactivation would require some care From f3d6310ab67322ceed16174fadb14a43277884c2 Mon Sep 17 00:00:00 2001 From: Carlo Pignedoli Date: Sat, 22 Oct 2022 18:59:29 +0000 Subject: [PATCH 23/87] bug fix --- aiidalab_widgets_base/viewers.py | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 72c89cf2..a91ccd30 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -165,19 +165,6 @@ def __init__(self, parameter, downloadable=True, **kwargs): class Representation(ipw.VBox): """Representation for StructureData in nglviewer - if a structure is imported the traitlet import_structure will trigger - initialization of the list 'list_of_representations' e.g. [[0,1,2,3,4,5]] - the traitlet 'list_of_representations' will trigger creation of self.representations as list of Representations() - default style set in self.representations[0].style is 'molecule' - self.representations[0].selection is populated with all atoms - self.update_representations() which copyes selections in the representations widgets into the list_of_representations - ad calls self.apply_representations() - - apply_representations: - creates the representation dictionary for the main structure 'rep_dict_unit', replicates teh reprsentattions in case - of supercell and calls self.update_viewer() that creates teh nglview representattions - - Editors of a structure update the traitlet self.list_of_representations. """ master_class = None @@ -456,7 +443,7 @@ def add_representation(self, _): def delete_representation(self, representation): try: - index = self.all_representations.index(representation) + index = self.all_representations.index(representation) except ValueError: self.representation_add_message.message = f"""Error: Rep. {representation} not found.""" return @@ -958,6 +945,7 @@ def highlight_atoms( ) def remove_viewer_components(self, c=None): + print("remove components") with self.hold_trait_notifications(): while hasattr(self._viewer, "component_0"): self._viewer.component_0.clear_representations() @@ -967,17 +955,13 @@ def remove_viewer_components(self, c=None): def update_viewer(self, c=None): if self.displayed_structure: - print("in update viewer", self.repr_params) - print(hasattr(self._viewer, "component_0")) self._viewer.set_representations(self.repr_params,component=0) self._viewer.add_unitcell() self._viewer.center() def orient_z_up(self, _=None): try: - # print("inside orient_z_up") if self.structure is not None: - # print("orienting") cell_z = self.structure.cell[2, 2] com = self.structure.get_center_of_mass() def_orientation = self._viewer._camera_orientation @@ -1124,6 +1108,7 @@ def repeat(self, _=None): @validate("structure") def _valid_structure(self, change): # pylint: disable=no-self-use """Update structure.""" + self.remove_viewer_components() structure = change["value"] if structure is None: return None # if no structure provided, the rest of the code can be skipped @@ -1161,6 +1146,7 @@ def _observe_structure(self, change): def _observe_displayed_structure(self, change): """Update the view if displayed_structure trait was modified.""" if change["new"] is not None: + print("create components") self._viewer.add_component(nglview.ASEStructure(self.displayed_structure), default_representation=False,name='Structure') self.update_representations() # not needed for the moment, actions are defined in the editors functions From 861c45dba2e74a53b5f0423ef4d086e3383ddf03 Mon Sep 17 00:00:00 2001 From: Carlo Pignedoli Date: Sat, 22 Oct 2022 19:02:53 +0000 Subject: [PATCH 24/87] bug fix --- aiidalab_widgets_base/viewers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index a91ccd30..db7c2609 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -1137,7 +1137,6 @@ def _observe_structure(self, change): if change["new"] is not None: self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) self.set_trait("cell", change["new"].cell) - self.structure = change["new"] else: self.set_trait("displayed_structure", None) self.set_trait("cell", None) From 1a1ce8545d5804996e429fd58ca2b79747fa9541 Mon Sep 17 00:00:00 2001 From: Carlo Pignedoli Date: Sun, 23 Oct 2022 18:04:04 +0000 Subject: [PATCH 25/87] strange ngl problme --- aiidalab_widgets_base/structures.py | 6 +- aiidalab_widgets_base/viewers.py | 268 +++++++++++----------------- 2 files changed, 112 insertions(+), 162 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 53780904..9a0db139 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -1418,7 +1418,7 @@ def mod_element(self, _=None, atoms=None, selection=None): ] # the order of the traitlets below is important - self.selection = [] + #self.selection = [] self.structure = atoms self.selection = new_selection @@ -1436,7 +1436,7 @@ def copy_sel(self, _=None, atoms=None, selection=None): new_selection = [i for i in range(last_atom, last_atom + len(selection))] # the order of the traitlets below is important - self.selection = [] + #self.selection = [] self.structure = atoms self.selection = new_selection @@ -1475,7 +1475,7 @@ def add(self, _=None, atoms=None, selection=None): # self.brand_new_structure = False # the order of the traitlets below is important - self.selection = [] + #self.selection = [] self.structure = atoms self.selection = new_selection diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index db7c2609..93c7952d 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -171,27 +171,27 @@ class Representation(ipw.VBox): def __init__(self, indices="1..2"): self.selection = ipw.Text( - description="atoms:", value="", style={"description_width": "initial"} + description="atoms:", value="", layout=ipw.Layout(width='35%', height='30px') ) self.repr_type = ipw.Dropdown( - options=["ball+stick"], + options=["ball+stick","spacefill"], value="ball+stick", description="type", - disabled=False, + disabled=False,layout=ipw.Layout(width='35%', height='30px') ) self.size = ipw.FloatText( - value=3, description="size", style={"description_width": "initial"} + value=3, description="size", layout=ipw.Layout(width='25%', height='30px') ) self.color = ipw.Dropdown( options=["element", "red", "green", "blue", "yellow", "orange", "purple"], value="element", description="color", - disabled=False, + disabled=False,layout=ipw.Layout(width='35%', height='30px') ) self.show = ipw.Checkbox(value=True, description="show", disabled=False) # Delete button. - self.delete_button = ipw.Button(description="Delete", button_style="danger") + self.delete_button = ipw.Button(description="Delete", button_style="danger",layout=ipw.Layout(width='15%', height='30px')) self.delete_button.on_click(self.delete_myself) super().__init__( @@ -246,37 +246,8 @@ def __init__( self._viewer.stage.set_parameters(mouse_preset="pymol") # self.first_update_of_viewer = True self.natoms = 0 - self.rep_dict = {} - self.rep_dict_unit = {} - self.default_representations = { - "molecule": { - "ids": "1..2", - "aspectRatio": 3.5, - "highlight_aspectRatio": 3.6, - "highlight_color": "red", - "highlight_opacity": 0.6, - "name": "molecule", - "type": "ball+stick", - }, - "surface": { - "ids": "1..2", - "aspectRatio": 10, - "highlight_aspectRatio": 10.1, - "highlight_color": "green", - "highlight_opacity": 0.6, - "name": "surface", - "type": "ball+stick", - }, - "bulk": { - "ids": "1..2", - "aspectRatio": 5, - "highlight_aspectRatio": 5.1, - "highlight_color": "green", - "highlight_opacity": 0.6, - "name": "surface", - "type": "ball+stick", - }, - } + self.n_all_representations = 0 + view_box = ipw.VBox([self._viewer]) @@ -421,7 +392,7 @@ def change_camera(change): self.add_new_rep_button.on_click(self.add_representation) apply_rep = ipw.Button(description="Apply rep") - apply_rep.on_click(self.on_click_apply_representations) + apply_rep.on_click(self.apply_representations) self.representation_output = ipw.Box(layout=BOX_LAYOUT) return ipw.VBox( @@ -440,6 +411,7 @@ def change_camera(change): def add_representation(self, _): """Add a representation to the list of representations.""" self.all_representations = self.all_representations + [Representation()] + self.n_all_representations += 1 def delete_representation(self, representation): try: @@ -452,6 +424,7 @@ def delete_representation(self, representation): self.all_representations[:index] + self.all_representations[index + 1 :] ) del representation + self.n_all_representations -= 1 self.apply_representations() @observe("all_representations") @@ -468,8 +441,9 @@ def update_representations(self, change=None): number_of_representation_widgets = len(self.all_representations) if self.displayed_structure: if number_of_representation_widgets == 0: - self.all_representations = [Representation()] - # shoudl not be needed teh check of more reps in array['representations'] than actually defined reps + self.n_all_representations = 0 + #self.all_representations = [Representation()] + self.add_representation(None) representations = self.structure.arrays["representations"] for rep in set(representations): @@ -487,11 +461,28 @@ def update_representations(self, change=None): self.all_representations[rep].selection.value = "" self.apply_representations() + def representation_parameters(self, representation): + """Return the parameters dictionary of a representation.""" + idsl = string_range_to_list(representation.selection.value, shift=-1)[0] + idsl_rep = [i + rep * self.natoms for rep in range(np.prod(self.supercell)) for i in idsl if self.structure.arrays["representationsshow"][i]] + ids = self.list_to_nglview(idsl_rep) + params = {'type': representation.repr_type.value, + 'params': {'sele':ids, + 'opacity': 1, + 'color':representation.color.value, + }} + if representation.repr_type.value == "ball+stick": + params["params"]["aspectRatio"] = representation.size.value + else: + params["params"]["radiusScale"] = 0.1 * representation.size.value + + return params + - def on_click_apply_representations(self, change=None): - """Updates self.displayed_structure.arrays['representations'] according to user defined representations""" - + def apply_representations(self, change=None): + """Apply the representations to the displayed structure.""" # negative value means an atom is not assigned to a representation + self._viewer.clear_representations(component=0) arrayrepresentations = -1 * np.ones(self.natoms) arrayrepresentationsshow = np.zeros(self.natoms) for irep, rep in enumerate(self.all_representations): @@ -502,30 +493,18 @@ def on_click_apply_representations(self, change=None): arrayrepresentationsshow[index] = 1 self.structure.set_array("representations", arrayrepresentations) - self.structure.set_array("representationsshow", arrayrepresentationsshow) - - self.apply_representations() - - def apply_representations(self, change=None): + self.structure.set_array("representationsshow", arrayrepresentationsshow) + #self.displayed_structure.set_array("representations", arrayrepresentations) + #self.displayed_structure.set_array("representationsshow", arrayrepresentationsshow) # iterate on number of representations self.repr_params = [] # self.brand_new_structure=False - nrep = np.prod(self.supercell) current_rep = 0 for rep in self.all_representations: # in representation dictionary indexes start from 0 so we transform '1..4' in '0..3' - idsl = string_range_to_list(rep.selection.value, shift=-1)[0] - idsl_rep = [i + rep * self.natoms for rep in range(nrep) for i in idsl if self.structure.arrays["representationsshow"][i]] - ids = "@" + ",".join(str(s) for s in idsl_rep) - self.repr_params.append({'type': rep.repr_type.value, - 'params': {'sele':ids, - 'opacity': 1, - 'name':'rep_'+str(current_rep), - 'aspectRatio': rep.size.value, - 'color':rep.color.value, - }}) - + self.repr_params.append(self.representation_parameters(rep)) current_rep += 1 + missing_atoms = { int(i) for i in np.where(self.structure.arrays["representations"] < 0)[0] } @@ -827,125 +806,96 @@ def _render_structure(self, change=None): self._download(payload=payload, filename=fname) self.render_btn.disabled = False - def _gen_translation_indexes(self): - """Transfromation of indexes in case of multiple representations - dictionaries for back and forth transformations. - suppose we have 3 representations: - component = 0,1,2 - and a structure with 10 atoms. - If we assign the first 3 atoms to the first representation - atom 4 to the second and the rest to the third - teh two dictionaries will look like: - {0:(0,0),1:(0,1),2:(0,2),3:(1,0),4:(2,0),5:(2,1),6:(2,2),7:(2,3),8:(2,4),9:(2,5)} - and - {(0,0):0,(0,1):1,(0,2):2,(1,0):3,(2,0):4,(2,1):5,(2,2):6,(2,3):7,(2,4):8,(2,5):9} - """ - - self._translate_i_glob_loc = {} - self._translate_i_loc_glob = {} - for component in range(len(self.rep_dict.keys())): - comp_i = 0 - ids = list( - string_range_to_list(self.rep_dict[component]["ids"], shift=0)[0] - ) - for i_g in ids: - self._translate_i_glob_loc[i_g] = (component, comp_i) - self._translate_i_loc_glob[(component, comp_i)] = i_g - comp_i += 1 - - def _translate_glob_loc(self, indexes): - """From global index to indexes of different components.""" - all_comp = [list() for i in range(len(self.rep_dict.keys()))] - - for i_g in indexes: - i_c, i_a = self._translate_i_glob_loc[i_g] - all_comp[i_c].append(i_a) - return all_comp def _on_atom_click(self, _=None): """Update selection when clicked on atom.""" - if "atom1" not in self._viewer.picked.keys(): - return # did not click on atom - index = self._viewer.picked["atom1"]["index"] + if hasattr(self._viewer, "component_0"): + if "atom1" not in self._viewer.picked.keys(): + return # did not click on atom + index = self._viewer.picked["atom1"]["index"] + #component = self._viewer.picked["component"] - if self.rep_dict: - component = self._viewer.picked["component"] + selection = self.selection.copy() - index = self._translate_i_loc_glob[(component, index)] - - selection = self.selection.copy() - - if selection: - if index not in selection: - selection.append(index) + if selection: + if index not in selection: + selection.append(index) + else: + selection.remove(index) else: - selection.remove(index) - else: - selection = [index] + selection = [index] - # selection_unit = [i for i in selection if i < self.natoms] - self.selection = selection - # self.selection = selection_unit + # selection_unit = [i for i in selection if i < self.natoms] + self.selection = selection + # self.selection = selection_unit return + def list_to_nglview(self, list): + """Converts a list of structures to a nglview widget""" + sele = 'none' + if list: + sele = "@" + ",".join(str(s) for s in list) + return sele def highlight_atoms( self, vis_list, - color=DEFAULT_SELECTION_COLOR, - size=DEFAULT_SELECTION_RADIUS, - opacity=DEFAULT_SELECTION_OPACITY, ): """Highlighting atoms according to the provided list.""" if not hasattr(self._viewer, "component_0"): return + print(self.repr_params) + print("prima",len(self._viewer._ngl_repr_dict['0'])) + print("prima",self._viewer._ngl_repr_dict['0']) + ids=[[] for rep in range(self.n_all_representations)] + # Map vis_list and self.displayed_structure.arrays["representations"] to a list of strings + # that goes to the highlight_reps + # there are N representations defined by the user and N automatically added for highlighting + print("vis_list",vis_list) + print("self.structure.arrays['representations']",self.structure.arrays["representations"]) + for i in vis_list: + ids[int(self.structure.arrays["representations"][i])].append(i) + print("ids ",ids) + # _update_representations does not allow to change the selection of a representation + #for i,selection in enumerate(ids): + # viewer._update_representations_by_name('highlight_rep'+str(i), component=0, + # sele=self.list_to_nglview(selection)) + + # So we have to remove and add the representations + # also setting visibility of a representation seems not to work while if works for components + #while(len(self._viewer._ngl_repr_dict['0']) > self.n_all_representations ): + #we always remove the same index: after teh representation 'unitcell' + #self._viewer._remove_representation(component=0, repr_index=self.n_all_representations ) + print(self.n_all_representations) + self._viewer._remove_representation(component=0,repr_index=2) + print("adesso: ",len(self._viewer._ngl_repr_dict['0'])) + print(self._viewer._ngl_repr_dict['0']) + + # to make sure that the unitcell representation is always in position self.n_all_representations + self._viewer.add_unitcell() + + for i,selection in enumerate(ids): + + + params=self.representation_parameters(self.all_representations[i]) + params['params']['sele']=self.list_to_nglview(selection) + params['params']['color']='red' + params['params']['opacity']=0.6 + params['params']['component_index']=0 + if 'radiusScale' in params['params']: + params['params']['radiusScale'] += 0.1 + else: + params['params']['aspectRatio'] += 0.1 - if self.rep_dict is None: - self._viewer._remove_representations_by_name( - repr_name="selected_atoms" - ) # pylint:disable=protected-access - self._viewer.add_ball_and_stick( # pylint:disable=no-member - name="selected_atoms", - selection=list() if vis_list is None else vis_list, - color=color, - aspectRatio=size, - opacity=opacity, - ) - else: - - ncomponents = len(self.rep_dict.keys()) - for component in range(ncomponents): - name = "highlight_" + self.rep_dict[component]["name"] - self._viewer._remove_representations_by_name( - repr_name=name, component=component - ) - color = self.rep_dict[component]["highlight_color"] - aspectRatio = self.rep_dict[component]["highlight_aspectRatio"] - opacity = self.rep_dict[component]["highlight_opacity"] - if vis_list is None: - self._viewer.add_ball_and_stick( - name=name, - selection=list(), - color=color, - aspectRatio=aspectRatio, - opacity=opacity, - component=component, - ) - else: - all_comp = self._translate_glob_loc(vis_list) - selection = all_comp[component] - self._viewer.add_ball_and_stick( - name=name, - selection=selection, - color=color, - aspectRatio=aspectRatio, - opacity=opacity, - component=component, - ) + self._viewer._remote_call('addRepresentation', + target='compList', + args=[params['type']], + kwargs=params['params']) + print("dopo",len(self._viewer._ngl_repr_dict[0])) + print("dopo",self._viewer._ngl_repr_dict[0]) def remove_viewer_components(self, c=None): - print("remove components") with self.hold_trait_notifications(): while hasattr(self._viewer, "component_0"): self._viewer.component_0.clear_representations() @@ -1109,6 +1059,7 @@ def repeat(self, _=None): def _valid_structure(self, change): # pylint: disable=no-self-use """Update structure.""" self.remove_viewer_components() + self.clear_selection() structure = change["value"] if structure is None: return None # if no structure provided, the rest of the code can be skipped @@ -1145,7 +1096,6 @@ def _observe_structure(self, change): def _observe_displayed_structure(self, change): """Update the view if displayed_structure trait was modified.""" if change["new"] is not None: - print("create components") self._viewer.add_component(nglview.ASEStructure(self.displayed_structure), default_representation=False,name='Structure') self.update_representations() # not needed for the moment, actions are defined in the editors functions From bf627bb115fdb81862bed2cf2ba2f269a7b68123 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 23 Oct 2022 18:04:29 +0000 Subject: [PATCH 26/87] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- aiidalab_widgets_base/structures.py | 6 +- aiidalab_widgets_base/viewers.py | 164 ++++++++++++++++------------ 2 files changed, 97 insertions(+), 73 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 9a0db139..31d79931 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -1418,7 +1418,7 @@ def mod_element(self, _=None, atoms=None, selection=None): ] # the order of the traitlets below is important - #self.selection = [] + # self.selection = [] self.structure = atoms self.selection = new_selection @@ -1436,7 +1436,7 @@ def copy_sel(self, _=None, atoms=None, selection=None): new_selection = [i for i in range(last_atom, last_atom + len(selection))] # the order of the traitlets below is important - #self.selection = [] + # self.selection = [] self.structure = atoms self.selection = new_selection @@ -1475,7 +1475,7 @@ def add(self, _=None, atoms=None, selection=None): # self.brand_new_structure = False # the order of the traitlets below is important - #self.selection = [] + # self.selection = [] self.structure = atoms self.selection = new_selection diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 93c7952d..73a87c0f 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -164,44 +164,55 @@ def __init__(self, parameter, downloadable=True, **kwargs): class Representation(ipw.VBox): - """Representation for StructureData in nglviewer - """ + """Representation for StructureData in nglviewer""" master_class = None def __init__(self, indices="1..2"): self.selection = ipw.Text( - description="atoms:", value="", layout=ipw.Layout(width='35%', height='30px') + description="atoms:", + value="", + layout=ipw.Layout(width="35%", height="30px"), ) self.repr_type = ipw.Dropdown( - options=["ball+stick","spacefill"], + options=["ball+stick", "spacefill"], value="ball+stick", description="type", - disabled=False,layout=ipw.Layout(width='35%', height='30px') + disabled=False, + layout=ipw.Layout(width="35%", height="30px"), ) self.size = ipw.FloatText( - value=3, description="size", layout=ipw.Layout(width='25%', height='30px') + value=3, description="size", layout=ipw.Layout(width="25%", height="30px") ) self.color = ipw.Dropdown( options=["element", "red", "green", "blue", "yellow", "orange", "purple"], value="element", description="color", - disabled=False,layout=ipw.Layout(width='35%', height='30px') + disabled=False, + layout=ipw.Layout(width="35%", height="30px"), ) self.show = ipw.Checkbox(value=True, description="show", disabled=False) # Delete button. - self.delete_button = ipw.Button(description="Delete", button_style="danger",layout=ipw.Layout(width='15%', height='30px')) + self.delete_button = ipw.Button( + description="Delete", + button_style="danger", + layout=ipw.Layout(width="15%", height="30px"), + ) self.delete_button.on_click(self.delete_myself) super().__init__( children=[ - ipw.HBox([self.selection,self.show,self.delete_button,]), - ipw.HBox([self.repr_type, self.size, self.color])]) - - - - + ipw.HBox( + [ + self.selection, + self.show, + self.delete_button, + ] + ), + ipw.HBox([self.repr_type, self.size, self.color]), + ] + ) def delete_myself(self, _): self.master_class.delete_representation(self) @@ -248,7 +259,6 @@ def __init__( self.natoms = 0 self.n_all_representations = 0 - view_box = ipw.VBox([self._viewer]) configuration_tabs_map = { @@ -415,7 +425,7 @@ def add_representation(self, _): def delete_representation(self, representation): try: - index = self.all_representations.index(representation) + index = self.all_representations.index(representation) except ValueError: self.representation_add_message.message = f"""Error: Rep. {representation} not found.""" return @@ -442,7 +452,7 @@ def update_representations(self, change=None): if self.displayed_structure: if number_of_representation_widgets == 0: self.n_all_representations = 0 - #self.all_representations = [Representation()] + # self.all_representations = [Representation()] self.add_representation(None) representations = self.structure.arrays["representations"] @@ -464,20 +474,27 @@ def update_representations(self, change=None): def representation_parameters(self, representation): """Return the parameters dictionary of a representation.""" idsl = string_range_to_list(representation.selection.value, shift=-1)[0] - idsl_rep = [i + rep * self.natoms for rep in range(np.prod(self.supercell)) for i in idsl if self.structure.arrays["representationsshow"][i]] - ids = self.list_to_nglview(idsl_rep) - params = {'type': representation.repr_type.value, - 'params': {'sele':ids, - 'opacity': 1, - 'color':representation.color.value, - }} + idsl_rep = [ + i + rep * self.natoms + for rep in range(np.prod(self.supercell)) + for i in idsl + if self.structure.arrays["representationsshow"][i] + ] + ids = self.list_to_nglview(idsl_rep) + params = { + "type": representation.repr_type.value, + "params": { + "sele": ids, + "opacity": 1, + "color": representation.color.value, + }, + } if representation.repr_type.value == "ball+stick": - params["params"]["aspectRatio"] = representation.size.value - else: - params["params"]["radiusScale"] = 0.1 * representation.size.value + params["params"]["aspectRatio"] = representation.size.value + else: + params["params"]["radiusScale"] = 0.1 * representation.size.value return params - def apply_representations(self, change=None): """Apply the representations to the displayed structure.""" @@ -493,9 +510,9 @@ def apply_representations(self, change=None): arrayrepresentationsshow[index] = 1 self.structure.set_array("representations", arrayrepresentations) - self.structure.set_array("representationsshow", arrayrepresentationsshow) - #self.displayed_structure.set_array("representations", arrayrepresentations) - #self.displayed_structure.set_array("representationsshow", arrayrepresentationsshow) + self.structure.set_array("representationsshow", arrayrepresentationsshow) + # self.displayed_structure.set_array("representations", arrayrepresentations) + # self.displayed_structure.set_array("representationsshow", arrayrepresentationsshow) # iterate on number of representations self.repr_params = [] # self.brand_new_structure=False @@ -806,15 +823,13 @@ def _render_structure(self, change=None): self._download(payload=payload, filename=fname) self.render_btn.disabled = False - - def _on_atom_click(self, _=None): """Update selection when clicked on atom.""" if hasattr(self._viewer, "component_0"): if "atom1" not in self._viewer.picked.keys(): return # did not click on atom index = self._viewer.picked["atom1"]["index"] - #component = self._viewer.picked["component"] + # component = self._viewer.picked["component"] selection = self.selection.copy() @@ -831,9 +846,10 @@ def _on_atom_click(self, _=None): # self.selection = selection_unit return + def list_to_nglview(self, list): """Converts a list of structures to a nglview widget""" - sele = 'none' + sele = "none" if list: sele = "@" + ",".join(str(s) for s in list) return sele @@ -846,54 +862,58 @@ def highlight_atoms( if not hasattr(self._viewer, "component_0"): return print(self.repr_params) - print("prima",len(self._viewer._ngl_repr_dict['0'])) - print("prima",self._viewer._ngl_repr_dict['0']) - ids=[[] for rep in range(self.n_all_representations)] + print("prima", len(self._viewer._ngl_repr_dict["0"])) + print("prima", self._viewer._ngl_repr_dict["0"]) + ids = [[] for rep in range(self.n_all_representations)] # Map vis_list and self.displayed_structure.arrays["representations"] to a list of strings # that goes to the highlight_reps # there are N representations defined by the user and N automatically added for highlighting - print("vis_list",vis_list) - print("self.structure.arrays['representations']",self.structure.arrays["representations"]) + print("vis_list", vis_list) + print( + "self.structure.arrays['representations']", + self.structure.arrays["representations"], + ) for i in vis_list: ids[int(self.structure.arrays["representations"][i])].append(i) - print("ids ",ids) + print("ids ", ids) # _update_representations does not allow to change the selection of a representation - #for i,selection in enumerate(ids): + # for i,selection in enumerate(ids): # viewer._update_representations_by_name('highlight_rep'+str(i), component=0, # sele=self.list_to_nglview(selection)) # So we have to remove and add the representations # also setting visibility of a representation seems not to work while if works for components - #while(len(self._viewer._ngl_repr_dict['0']) > self.n_all_representations ): - #we always remove the same index: after teh representation 'unitcell' - #self._viewer._remove_representation(component=0, repr_index=self.n_all_representations ) + # while(len(self._viewer._ngl_repr_dict['0']) > self.n_all_representations ): + # we always remove the same index: after teh representation 'unitcell' + # self._viewer._remove_representation(component=0, repr_index=self.n_all_representations ) print(self.n_all_representations) - self._viewer._remove_representation(component=0,repr_index=2) - print("adesso: ",len(self._viewer._ngl_repr_dict['0'])) - print(self._viewer._ngl_repr_dict['0']) + self._viewer._remove_representation(component=0, repr_index=2) + print("adesso: ", len(self._viewer._ngl_repr_dict["0"])) + print(self._viewer._ngl_repr_dict["0"]) - # to make sure that the unitcell representation is always in position self.n_all_representations + # to make sure that the unitcell representation is always in position self.n_all_representations self._viewer.add_unitcell() - for i,selection in enumerate(ids): - + for i, selection in enumerate(ids): - params=self.representation_parameters(self.all_representations[i]) - params['params']['sele']=self.list_to_nglview(selection) - params['params']['color']='red' - params['params']['opacity']=0.6 - params['params']['component_index']=0 - if 'radiusScale' in params['params']: - params['params']['radiusScale'] += 0.1 + params = self.representation_parameters(self.all_representations[i]) + params["params"]["sele"] = self.list_to_nglview(selection) + params["params"]["color"] = "red" + params["params"]["opacity"] = 0.6 + params["params"]["component_index"] = 0 + if "radiusScale" in params["params"]: + params["params"]["radiusScale"] += 0.1 else: - params['params']['aspectRatio'] += 0.1 + params["params"]["aspectRatio"] += 0.1 - self._viewer._remote_call('addRepresentation', - target='compList', - args=[params['type']], - kwargs=params['params']) - print("dopo",len(self._viewer._ngl_repr_dict[0])) - print("dopo",self._viewer._ngl_repr_dict[0]) + self._viewer._remote_call( + "addRepresentation", + target="compList", + args=[params["type"]], + kwargs=params["params"], + ) + print("dopo", len(self._viewer._ngl_repr_dict[0])) + print("dopo", self._viewer._ngl_repr_dict[0]) def remove_viewer_components(self, c=None): with self.hold_trait_notifications(): @@ -904,10 +924,10 @@ def remove_viewer_components(self, c=None): def update_viewer(self, c=None): - if self.displayed_structure: - self._viewer.set_representations(self.repr_params,component=0) - self._viewer.add_unitcell() - self._viewer.center() + if self.displayed_structure: + self._viewer.set_representations(self.repr_params, component=0) + self._viewer.add_unitcell() + self._viewer.center() def orient_z_up(self, _=None): try: @@ -1096,7 +1116,11 @@ def _observe_structure(self, change): def _observe_displayed_structure(self, change): """Update the view if displayed_structure trait was modified.""" if change["new"] is not None: - self._viewer.add_component(nglview.ASEStructure(self.displayed_structure), default_representation=False,name='Structure') + self._viewer.add_component( + nglview.ASEStructure(self.displayed_structure), + default_representation=False, + name="Structure", + ) self.update_representations() # not needed for the moment, actions are defined in the editors functions # to avoid unnecessary updates From c73fc68a0ff5be3cf8f46feddf4d4c653e342604 Mon Sep 17 00:00:00 2001 From: Carlo Date: Mon, 24 Oct 2022 09:00:23 +0000 Subject: [PATCH 27/87] first working version --- aiidalab_widgets_base/viewers.py | 73 ++++++++++++-------------------- 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 73a87c0f..5ef97abb 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -861,59 +861,42 @@ def highlight_atoms( """Highlighting atoms according to the provided list.""" if not hasattr(self._viewer, "component_0"): return - print(self.repr_params) - print("prima", len(self._viewer._ngl_repr_dict["0"])) - print("prima", self._viewer._ngl_repr_dict["0"]) - ids = [[] for rep in range(self.n_all_representations)] + + # Map vis_list and self.displayed_structure.arrays["representations"] to a list of strings # that goes to the highlight_reps # there are N representations defined by the user and N automatically added for highlighting - print("vis_list", vis_list) - print( - "self.structure.arrays['representations']", - self.structure.arrays["representations"], - ) + ids = [[] for rep in range(self.n_all_representations)] + for i in vis_list: ids[int(self.structure.arrays["representations"][i])].append(i) - print("ids ", ids) - # _update_representations does not allow to change the selection of a representation - # for i,selection in enumerate(ids): - # viewer._update_representations_by_name('highlight_rep'+str(i), component=0, - # sele=self.list_to_nglview(selection)) - - # So we have to remove and add the representations - # also setting visibility of a representation seems not to work while if works for components - # while(len(self._viewer._ngl_repr_dict['0']) > self.n_all_representations ): - # we always remove the same index: after teh representation 'unitcell' - # self._viewer._remove_representation(component=0, repr_index=self.n_all_representations ) - print(self.n_all_representations) - self._viewer._remove_representation(component=0, repr_index=2) - print("adesso: ", len(self._viewer._ngl_repr_dict["0"])) - print(self._viewer._ngl_repr_dict["0"]) - - # to make sure that the unitcell representation is always in position self.n_all_representations - self._viewer.add_unitcell() - for i, selection in enumerate(ids): + # remove previous highlight_rep representations + for i in range(self.n_all_representations): + self._viewer._remove_representations_by_name(repr_name="highlight_rep" + str(i), component=0) - params = self.representation_parameters(self.all_representations[i]) - params["params"]["sele"] = self.list_to_nglview(selection) - params["params"]["color"] = "red" - params["params"]["opacity"] = 0.6 - params["params"]["component_index"] = 0 - if "radiusScale" in params["params"]: - params["params"]["radiusScale"] += 0.1 - else: - params["params"]["aspectRatio"] += 0.1 + # create the dictionaries for highlight_reps + for i, selection in enumerate(ids): + if selection: + params = self.representation_parameters(self.all_representations[i]) + params["params"]["sele"] = self.list_to_nglview(selection) + params["params"]["name"] = "highlight_rep" + str(i) + params["params"]["color"] = "red" + params["params"]["opacity"] = 0.6 + params["params"]["component_index"] = 0 + if "radiusScale" in params["params"]: + params["params"]["radiusScale"] += 0.1 + else: + params["params"]["aspectRatio"] += 0.1 + + # and use directly teh remote call for more flexibility + self._viewer._remote_call( + "addRepresentation", + target="compList", + args=[params["type"]], + kwargs=params["params"], + ) - self._viewer._remote_call( - "addRepresentation", - target="compList", - args=[params["type"]], - kwargs=params["params"], - ) - print("dopo", len(self._viewer._ngl_repr_dict[0])) - print("dopo", self._viewer._ngl_repr_dict[0]) def remove_viewer_components(self, c=None): with self.hold_trait_notifications(): From 8e86ecb9bc2dcf4ec510144f2909e3d6e2b6c6f5 Mon Sep 17 00:00:00 2001 From: Carlo Date: Mon, 24 Oct 2022 09:26:16 +0000 Subject: [PATCH 28/87] exclude from adavnced selection atoms not shown --- aiidalab_widgets_base/viewers.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 5ef97abb..cc2d65c7 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -500,8 +500,13 @@ def apply_representations(self, change=None): """Apply the representations to the displayed structure.""" # negative value means an atom is not assigned to a representation self._viewer.clear_representations(component=0) + + # initially not atoms are assigned to a representation arrayrepresentations = -1 * np.ones(self.natoms) + + # the atom is not shown arrayrepresentationsshow = np.zeros(self.natoms) + for irep, rep in enumerate(self.all_representations): selection = string_range_to_list(rep.selection.value, shift=-1)[0] for index in selection: @@ -511,6 +516,8 @@ def apply_representations(self, change=None): self.structure.set_array("representations", arrayrepresentations) self.structure.set_array("representationsshow", arrayrepresentationsshow) + + # when supercell bugs will be fixed, decide on how to handle supercell selections # self.displayed_structure.set_array("representations", arrayrepresentations) # self.displayed_structure.set_array("representationsshow", arrayrepresentationsshow) # iterate on number of representations @@ -1350,7 +1357,7 @@ def add_info(indx, atom): def _observe_selection_adv(self, _=None): """Apply the advanced boolean atom selection""" try: - sel = self.parse_advanced_sel(condition=self.selection_adv) + sel = [int(i) for i in self.parse_advanced_sel(condition=self.selection_adv) if self.structure.arrays['representationsshow'][i]] self.selection = sel self._selected_atoms.value = list_to_string_range(sel, shift=1) self.wrong_syntax.layout.visibility = "hidden" From fec6d9a7dc07a7524f09dda5d6963c184d8fdc7b Mon Sep 17 00:00:00 2001 From: Carlo Date: Mon, 24 Oct 2022 14:56:51 +0000 Subject: [PATCH 29/87] cleaning --- aiidalab_widgets_base/structures.py | 16 +--------------- aiidalab_widgets_base/viewers.py | 20 +++++++++----------- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 31d79931..fa1bee4a 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -7,7 +7,6 @@ import pathlib import tempfile from collections import OrderedDict -from copy import deepcopy import ase import ipywidgets as ipw @@ -30,19 +29,7 @@ from ase import Atom, Atoms from ase.data import chemical_symbols, covalent_radii from sklearn.decomposition import PCA -from traitlets import ( - Bool, - Dict, - Instance, - Int, - List, - Unicode, - Union, - default, - dlink, - link, - observe, -) +from traitlets import Instance, Int, List, Unicode, Union, default, dlink, link, observe # Local imports from .data import LigandSelectorWidget @@ -69,7 +56,6 @@ class StructureManagerWidget(ipw.VBox): structure = Union([Instance(Atoms), Instance(Data)], allow_none=True) structure_node = Instance(Data, allow_none=True, read_only=True) node_class = Unicode() - # brand_new_structure = Bool() SUPPORTED_DATA_FORMATS = {"CifData": "cif", "StructureData": "structure"} diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index cc2d65c7..5674aacc 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -2,11 +2,9 @@ # pylint: disable=no-self-use import base64 -import itertools import re import warnings from copy import deepcopy -from hashlib import new import ipywidgets as ipw import nglview @@ -15,15 +13,13 @@ import traitlets from aiida.cmdline.utils.common import get_workchain_report from aiida.cmdline.utils.query import formatting -from aiida.orm import Data, Node +from aiida.orm import Node from ase import Atoms, neighborlist from ase.cell import Cell from IPython.display import clear_output, display from matplotlib.colors import to_rgb from numpy.linalg import norm from traitlets import ( - Bool, - Dict, Instance, Int, List, @@ -232,7 +228,6 @@ class _StructureDataBaseViewer(ipw.VBox): all_representations = traitlets.List() natoms = Int() - # brand_new_structure = Bool(True) selection = List(Int) selection_adv = Unicode() supercell = List(Int) @@ -869,7 +864,6 @@ def highlight_atoms( if not hasattr(self._viewer, "component_0"): return - # Map vis_list and self.displayed_structure.arrays["representations"] to a list of strings # that goes to the highlight_reps # there are N representations defined by the user and N automatically added for highlighting @@ -880,7 +874,9 @@ def highlight_atoms( # remove previous highlight_rep representations for i in range(self.n_all_representations): - self._viewer._remove_representations_by_name(repr_name="highlight_rep" + str(i), component=0) + self._viewer._remove_representations_by_name( + repr_name="highlight_rep" + str(i), component=0 + ) # create the dictionaries for highlight_reps for i, selection in enumerate(ids): @@ -904,7 +900,6 @@ def highlight_atoms( kwargs=params["params"], ) - def remove_viewer_components(self, c=None): with self.hold_trait_notifications(): while hasattr(self._viewer, "component_0"): @@ -924,7 +919,6 @@ def orient_z_up(self, _=None): if self.structure is not None: cell_z = self.structure.cell[2, 2] com = self.structure.get_center_of_mass() - def_orientation = self._viewer._camera_orientation top_z_orientation = [ 1.0, 0.0, @@ -1357,7 +1351,11 @@ def add_info(indx, atom): def _observe_selection_adv(self, _=None): """Apply the advanced boolean atom selection""" try: - sel = [int(i) for i in self.parse_advanced_sel(condition=self.selection_adv) if self.structure.arrays['representationsshow'][i]] + sel = [ + int(i) + for i in self.parse_advanced_sel(condition=self.selection_adv) + if self.structure.arrays["representationsshow"][i] + ] self.selection = sel self._selected_atoms.value = list_to_string_range(sel, shift=1) self.wrong_syntax.layout.visibility = "hidden" From 9c19ecb3f7923d25ee3285117516d60260639c86 Mon Sep 17 00:00:00 2001 From: Carlo Date: Mon, 24 Oct 2022 15:15:53 +0000 Subject: [PATCH 30/87] cleaning, removed orient_z_up to be put in separate PR --- aiidalab_widgets_base/structures.py | 26 +++--------- aiidalab_widgets_base/viewers.py | 65 +++++------------------------ 2 files changed, 16 insertions(+), 75 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index fa1bee4a..a5c4446d 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -97,7 +97,7 @@ def __init__( self.viewer = viewer else: self.viewer = StructureDataViewer(**kwargs) - link((self, "structure"), (self.viewer, "structure")) + dlink((self, "structure"), (self.viewer, "structure")) # Store button. self.btn_store = ipw.Button(description="Store in AiiDA", disabled=True) @@ -159,9 +159,6 @@ def _structure_importers(self, importers): if len(importers) == 1: # Assigning a function which will be called when importer provides a structure. dlink((importers[0], "structure"), (self, "input_structure")) - # if importers[0].has_trait("brand_new_structure"): - # link((importers[0], "brand_new_structure"), (self.viewer, "brand_new_structure")) - # link((importers[0], "brand_new_structure"), (self, "brand_new_structure")) return importers[0] @@ -172,9 +169,6 @@ def _structure_importers(self, importers): # Labeling tabs. importers_tab.set_title(i, importer.title) dlink((importer, "structure"), (self, "input_structure")) - # if importer.has_trait("brand_new_structure"): - # link((importer, "brand_new_structure"), (self.viewer, "brand_new_structure")) - # link((importer, "brand_new_structure"), (self, "brand_new_structure")) return importers_tab def _structure_editors(self, editors): @@ -688,7 +682,6 @@ class SmilesWidget(ipw.VBox): """Convert SMILES into 3D structure.""" structure = Instance(Atoms, allow_none=True) - # brand_new_structure = Bool() SPINNER = """""" @@ -1273,7 +1266,6 @@ def translate_dr(self, _=None, atoms=None, selection=None): self.action_vector * self.displacement.value ) - # self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1284,8 +1276,6 @@ def translate_dxdydz(self, _=None, atoms=None, selection=None): # The action. atoms.positions[self.selection] += np.array(self.str2vec(self.dxyz.value)) - # self.brand_new_structure = False - self.structure, self.selection = atoms, selection @_register_structure @@ -1296,7 +1286,6 @@ def translate_to_xyz(self, _=None, atoms=None, selection=None): geo_center = np.average(self.structure[self.selection].get_positions(), axis=0) atoms.positions[self.selection] += self.str2vec(self.dxyz.value) - geo_center - # self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1311,7 +1300,6 @@ def rotate(self, _=None, atoms=None, selection=None): rotated_subset.rotate(self.phi.value, v=vec, center=center, rotate_cell=False) atoms.positions[list(self.selection)] = rotated_subset.positions - # self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1345,7 +1333,6 @@ def mirror(self, _=None, norm=None, point=None, atoms=None, selection=None): # Mirror atoms. atoms.positions[selection] -= 2 * projections - # self.brand_new_structure = False self.structure, self.selection = atoms, selection def mirror_3p(self, _=None): @@ -1370,7 +1357,6 @@ def align(self, _=None, atoms=None, selection=None): subset.rotate(self.action_vector, self.str2vec(self.dxyz.value), center=center) atoms.positions[selection] = subset.positions - # self.brand_new_structure = False self.structure, self.selection = atoms, selection @_register_structure @@ -1404,7 +1390,7 @@ def mod_element(self, _=None, atoms=None, selection=None): ] # the order of the traitlets below is important - # self.selection = [] + # we must be sure trait atoms is set BEFORE trait selection self.structure = atoms self.selection = new_selection @@ -1422,7 +1408,7 @@ def copy_sel(self, _=None, atoms=None, selection=None): new_selection = [i for i in range(last_atom, last_atom + len(selection))] # the order of the traitlets below is important - # self.selection = [] + # we must be sure trait atoms is set BEFORE trait selection self.structure = atoms self.selection = new_selection @@ -1458,10 +1444,8 @@ def add(self, _=None, atoms=None, selection=None): i for i in range(last_atom, last_atom + len(selection) * len(lgnd)) ] - # self.brand_new_structure = False - # the order of the traitlets below is important - # self.selection = [] + # we must be sure trait atoms is set BEFORE trait selection self.structure = atoms self.selection = new_selection @@ -1471,5 +1455,7 @@ def remove(self, _, atoms=None, selection=None): """Remove selected atoms.""" del [atoms[selection]] + # the order of the traitlets below is important + # we must be sure trait atoms is set BEFORE trait selection self.selection = [] self.structure = atoms diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 5674aacc..08e51cf1 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -159,7 +159,7 @@ def __init__(self, parameter, downloadable=True, **kwargs): super().__init__([self.widget], **kwargs) -class Representation(ipw.VBox): +class NlgViewrRepresentation(ipw.VBox): """Representation for StructureData in nglviewer""" master_class = None @@ -250,7 +250,6 @@ def __init__( self._viewer.camera = default_camera self._viewer.observe(self._on_atom_click, names="picked") self._viewer.stage.set_parameters(mouse_preset="pymol") - # self.first_update_of_viewer = True self.natoms = 0 self.n_all_representations = 0 @@ -318,7 +317,6 @@ def _selection_tab(self): # 4. Button to clear selection. clear_selection = ipw.Button(description="Clear selection") - # clear_selection.on_click(lambda _: self.set_trait('selection', list())) # lambda cannot contain assignments clear_selection.on_click(self.clear_selection) # CLEAR self.wrong_syntax.layout.visibility = 'visible' @@ -392,7 +390,7 @@ def change_camera(change): center_button.on_click(lambda c: self._viewer.center()) # 5. representations buttons - self.atoms_not_represented = ipw.Output() + self.atoms_not_represented = ipw.HTML() self.add_new_rep_button = ipw.Button(description="Add rep", button_style="info") self.add_new_rep_button.on_click(self.add_representation) @@ -415,7 +413,7 @@ def change_camera(change): def add_representation(self, _): """Add a representation to the list of representations.""" - self.all_representations = self.all_representations + [Representation()] + self.all_representations = self.all_representations + [NlgViewrRepresentation()] self.n_all_representations += 1 def delete_representation(self, representation): @@ -433,7 +431,7 @@ def delete_representation(self, representation): self.apply_representations() @observe("all_representations") - def _observe_representations(self, change): + def _observe_all_representations(self, change): """Update the list of representations.""" if change["new"]: self.representation_output.children = change["new"] @@ -447,7 +445,6 @@ def update_representations(self, change=None): if self.displayed_structure: if number_of_representation_widgets == 0: self.n_all_representations = 0 - # self.all_representations = [Representation()] self.add_representation(None) representations = self.structure.arrays["representations"] @@ -517,7 +514,6 @@ def apply_representations(self, change=None): # self.displayed_structure.set_array("representationsshow", arrayrepresentationsshow) # iterate on number of representations self.repr_params = [] - # self.brand_new_structure=False current_rep = 0 for rep in self.all_representations: # in representation dictionary indexes start from 0 so we transform '1..4' in '0..3' @@ -528,18 +524,13 @@ def apply_representations(self, change=None): int(i) for i in np.where(self.structure.arrays["representations"] < 0)[0] } if missing_atoms: - self.atoms_not_represented.clear_output() - with self.atoms_not_represented: - print( - "Atoms excluded from representations: ", - list_to_string_range(list(missing_atoms), shift=1), - ) + self.atoms_not_represented.value = ( + "Atoms excluded from representations: " + + list_to_string_range(list(missing_atoms), shift=1) + ) else: - self.atoms_not_represented.clear_output() + self.atoms_not_represented.value = "" self.update_viewer() - # if self.first_update_of_viewer: - # self.first_update_of_viewer = self.orient_z_up() - self.orient_z_up() @observe("cell") def _observe_cell(self, _=None): @@ -831,7 +822,7 @@ def _on_atom_click(self, _=None): if "atom1" not in self._viewer.picked.keys(): return # did not click on atom index = self._viewer.picked["atom1"]["index"] - # component = self._viewer.picked["component"] + # component = self._viewer.picked["component"] to be used in case of multiple components will be implemented selection = self.selection.copy() @@ -843,9 +834,7 @@ def _on_atom_click(self, _=None): else: selection = [index] - # selection_unit = [i for i in selection if i < self.natoms] self.selection = selection - # self.selection = selection_unit return @@ -914,37 +903,6 @@ def update_viewer(self, c=None): self._viewer.add_unitcell() self._viewer.center() - def orient_z_up(self, _=None): - try: - if self.structure is not None: - cell_z = self.structure.cell[2, 2] - com = self.structure.get_center_of_mass() - top_z_orientation = [ - 1.0, - 0.0, - 0.0, - 0, - 0.0, - 1.0, - 0.0, - 0, - 0.0, - 0.0, - -np.max([cell_z, 30.0]), - 0, - -com[0], - -com[1], - -com[2], - 1, - ] - self._viewer._set_camera_orientation(top_z_orientation) - self._viewer.center() - return False - else: - return True - except AttributeError: - return True - @default("supercell") def _default_supercell(self): return [1, 1, 1] @@ -1106,9 +1064,6 @@ def _observe_displayed_structure(self, change): name="Structure", ) self.update_representations() - # not needed for the moment, actions are defined in the editors functions - # to avoid unnecessary updates - # reactivation would require some care def d_from(self, operand): point = np.array([float(i) for i in operand[1:-1].split(",")]) From 61575a3c4f14bf4189e6bd632be739799f49cf5b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 12 Jan 2023 10:58:29 +0000 Subject: [PATCH 31/87] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- aiidalab_widgets_base/structures.py | 8 +++--- aiidalab_widgets_base/viewers.py | 38 ++++++++++++++--------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 93bf28d4..7822db50 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -1392,7 +1392,7 @@ def mod_element(self, _=None, atoms=None, selection=None): range(last_atom, last_atom + len(selection) * len(lgnd)) ) - # The order of the traitlets below is important - + # The order of the traitlets below is important - # we must be sure trait atoms is set before trait selection self.structure, self.input_selection = atoms, new_selection @@ -1408,7 +1408,7 @@ def copy_sel(self, _=None, atoms=None, selection=None): atoms += add_atoms new_selection = [i for i in range(last_atom, last_atom + len(selection))] - # The order of the traitlets below is important - + # The order of the traitlets below is important - # we must be sure trait atoms is set before trait selection new_selection = list(range(last_atom, last_atom + len(selection))) self.structure, self.input_selection = atoms, new_selection @@ -1443,7 +1443,7 @@ def add(self, _=None, atoms=None, selection=None): new_selection = list(range(last_atom, last_atom + len(selection) * len(lgnd))) - # The order of the traitlets below is important - + # The order of the traitlets below is important - # we must be sure trait atoms is set before trait selection self.structure, self.input_selection = atoms, new_selection @@ -1453,7 +1453,7 @@ def remove(self, _, atoms=None, selection=None): """Remove selected atoms.""" del [atoms[selection]] - # The order of the traitlets below is important - + # The order of the traitlets below is important - # we must be sure trait atoms is set before trait selection self.structure = atoms self.input_selection = None diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 68e46c4f..78a11225 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -52,7 +52,6 @@ ) - def register_viewer_widget(key): """Register widget as a viewer for the given key.""" @@ -228,7 +227,6 @@ class _StructureDataBaseViewer(ipw.VBox): """ - all_representations = traitlets.List() natoms = Int() input_selection = List(Int, allow_none=True) @@ -319,8 +317,9 @@ def _selection_tab(self): # 4. Button to clear selection. clear_selection = ipw.Button(description="Clear selection") - clear_selection.on_click(lambda _: self.set_trait('displayed_selection', [])) # lambda cannot contain assignments - + clear_selection.on_click( + lambda _: self.set_trait("displayed_selection", []) + ) # lambda cannot contain assignments # 5. Button to apply selection apply_displayed_selection = ipw.Button(description="Apply selection") @@ -839,7 +838,6 @@ def _on_atom_click(self, _=None): displayed_selection = [index] self.displayed_selection = displayed_selection - def list_to_nglview(self, list): """Converts a list of structures to a nglview widget""" sele = "none" @@ -905,7 +903,6 @@ def update_viewer(self, c=None): self._viewer.add_unitcell() self._viewer.center() - @default("supercell") def _default_supercell(self): return [1, 1, 1] @@ -933,7 +930,6 @@ def _observe_displayed_selection(self, _=None): self.selection = [x for x in seq if not (x in seen or seen.add(x))] self.highlight_atoms(self.displayed_selection) - def apply_displayed_selection(self, _=None): """Apply selection specified in the text field.""" expanded_selection, syntax_ok = string_range_to_list( @@ -1035,22 +1031,22 @@ def _valid_structure(self, change): # pylint: disable=no-self-use self.clear_selection() structure = change["value"] if structure is None: - return None # If no structure is provided, the rest of the code can be skipped + return ( + None # If no structure is provided, the rest of the code can be skipped + ) if isinstance(structure, Atoms): self.pk = None elif isinstance(structure, Node): self.pk = structure.pk structure = structure.get_ase() - + raise TypeError( f"Unsupported type {type(structure)}, structure must be one of the following types: " "ASE Atoms object, AiiDA CifData or StructureData." ) - return structure - @observe("structure") def _observe_structure(self, change): """Update displayed_structure trait after the structure trait has been modified.""" @@ -1071,13 +1067,13 @@ def _observe_structure(self, change): def _observe_displayed_structure(self, change): """Update the view if displayed_structure trait was modified.""" with self.hold_trait_notifications(): - if change["new"] is not None: - self._viewer.add_component( - nglview.ASEStructure(self.displayed_structure), - default_representation=False, - name="Structure", - ) - self.update_representations() + if change["new"] is not None: + self._viewer.add_component( + nglview.ASEStructure(self.displayed_structure), + default_representation=False, + name="Structure", + ) + self.update_representations() self.displayed_selection = [] def d_from(self, operand): @@ -1323,8 +1319,10 @@ def add_info(indx, atom): dihedral_str = f"{dihedral:.2f}" except ZeroDivisionError: dihedral_str = "nan" - info += f"

{info_natoms_geo_center}

Dihedral angle: {dihedral_str}

" - + info += ( + f"

{info_natoms_geo_center}

Dihedral angle: {dihedral_str}

" + ) + return info + info_natoms_geo_center @observe("displayed_selection") From 0e19bccb87952c9a3b5576a54488d49500d98cc4 Mon Sep 17 00:00:00 2001 From: Carlo Pignedoli Date: Mon, 16 Jan 2023 16:18:27 +0000 Subject: [PATCH 32/87] some flake fixes --- aiidalab_widgets_base/structures.py | 2 +- aiidalab_widgets_base/viewers.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 7822db50..b1df582e 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -1406,7 +1406,7 @@ def copy_sel(self, _=None, atoms=None, selection=None): add_atoms = atoms[self.selection].copy() add_atoms.translate([1.0, 0, 0]) atoms += add_atoms - new_selection = [i for i in range(last_atom, last_atom + len(selection))] + new_selection = list(range(last_atom, last_atom + len(selection))) # The order of the traitlets below is important - # we must be sure trait atoms is set before trait selection diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 78a11225..7a192cf5 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -838,11 +838,11 @@ def _on_atom_click(self, _=None): displayed_selection = [index] self.displayed_selection = displayed_selection - def list_to_nglview(self, list): + def list_to_nglview(self, the_list): """Converts a list of structures to a nglview widget""" sele = "none" - if list: - sele = "@" + ",".join(str(s) for s in list) + if the_list: + sele = "@" + ",".join(str(s) for s in the_list) return sele def highlight_atoms( @@ -1052,10 +1052,10 @@ def _observe_structure(self, change): """Update displayed_structure trait after the structure trait has been modified.""" # Remove the current structure(s) from the viewer. self.natoms = len(self.structure) if self.structure is not None else 0 - if "representations" not in structure.arrays: - structure.set_array("representations", np.zeros(self.natoms)) - if "representationsshow" not in structure.arrays: - structure.set_array("representationsshow", np.ones(self.natoms)) + if "representations" not in self.structure.arrays: + self.structure.set_array("representations", np.zeros(self.natoms)) + if "representationsshow" not in self.structure.arrays: + self.structure.set_array("representationsshow", np.ones(self.natoms)) if change["new"] is not None: self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) self.set_trait("cell", change["new"].cell) From a32d8b5bf70c151b4b294125df6fa1ad857126d0 Mon Sep 17 00:00:00 2001 From: Carlo Pignedoli Date: Tue, 17 Jan 2023 14:24:04 +0000 Subject: [PATCH 33/87] falke fix --- aiidalab_widgets_base/computational_resources.py | 2 +- aiidalab_widgets_base/elns.py | 6 +++--- aiidalab_widgets_base/nodes.py | 2 +- aiidalab_widgets_base/process.py | 2 +- aiidalab_widgets_base/utils/__init__.py | 2 +- aiidalab_widgets_base/utils/exceptions.py | 2 +- aiidalab_widgets_base/wizard.py | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/aiidalab_widgets_base/computational_resources.py b/aiidalab_widgets_base/computational_resources.py index fe52076b..6676e2a4 100644 --- a/aiidalab_widgets_base/computational_resources.py +++ b/aiidalab_widgets_base/computational_resources.py @@ -188,7 +188,7 @@ def refresh(self, _=None): with self.hold_trait_notifications(): self.code_select_dropdown.options = self._get_codes() if not self.code_select_dropdown.options: - self.output.value = f"No codes found for default calcjob plugin '{self.default_calc_job_plugin}'." + self.output.value = f"No codes found for default calcjob plugin {self.default_calc_job_plugin!r}." self.code_select_dropdown.disabled = True else: self.code_select_dropdown.disabled = False diff --git a/aiidalab_widgets_base/elns.py b/aiidalab_widgets_base/elns.py index 6565f6be..4d8bcc9f 100644 --- a/aiidalab_widgets_base/elns.py +++ b/aiidalab_widgets_base/elns.py @@ -24,7 +24,7 @@ def connect_to_eln(eln_instance=None, **kwargs): except (FileNotFoundError, json.JSONDecodeError, KeyError): return ( None, - f"Can't open '{ELN_CONFIG}' (ELN configuration file). Instance: {eln_instance}", + f"Can't open {ELN_CONFIG!r} (ELN configuration file). Instance: {eln_instance}", ) # If no ELN instance was specified, trying the default one. @@ -36,7 +36,7 @@ def connect_to_eln(eln_instance=None, **kwargs): eln_config = config[eln_instance] eln_type = eln_config.pop("eln_type", None) else: # The selected instance is not present in the config. - return None, f"Didn't find configuration for the '{eln_instance}' instance." + return None, f"Didn't find configuration for the {eln_instance!r} instance." # If the ELN type cannot be identified - aborting. if not eln_type: @@ -74,7 +74,7 @@ def __init__(self, path_to_root="../", **kwargs): if eln is None: url = f"{path_to_root}aiidalab-widgets-base/notebooks/eln_configure.ipynb" - error_message.value = f"""Warning! The access to ELN is not configured. Please follow the link to configure it.
More details: {msg}""" + error_message.value = f"""Warning! The access to ELN is not configured. Please follow the link to configure it.
More details: {msg!r}""" return traitlets.dlink((eln, "node"), (self, "node")) diff --git a/aiidalab_widgets_base/nodes.py b/aiidalab_widgets_base/nodes.py index 0416195b..6ddd1b54 100644 --- a/aiidalab_widgets_base/nodes.py +++ b/aiidalab_widgets_base/nodes.py @@ -315,7 +315,7 @@ def to_html_string(self): return f""" - +

{self.description}

diff --git a/aiidalab_widgets_base/process.py b/aiidalab_widgets_base/process.py index a90799e2..d21fcd56 100644 --- a/aiidalab_widgets_base/process.py +++ b/aiidalab_widgets_base/process.py @@ -749,7 +749,7 @@ def _run(funcs): func() except Exception as error: warnings.warn( - f"WARNING: Callback function '{func}' disabled due to error: {error}" + f"WARNING: Callback function {func!r} disabled due to error: {error!r}" ) disabled_funcs.add(func) diff --git a/aiidalab_widgets_base/utils/__init__.py b/aiidalab_widgets_base/utils/__init__.py index 69d51898..776b23e2 100644 --- a/aiidalab_widgets_base/utils/__init__.py +++ b/aiidalab_widgets_base/utils/__init__.py @@ -33,7 +33,7 @@ def predefine_settings(obj, **kwargs): if hasattr(obj, key): setattr(obj, key, value) else: - raise AttributeError(f"'{obj}' object has no attribute '{key}'") + raise AttributeError(f"{obj!r} object has no attribute {key!r}") def get_ase_from_file(fname, file_format=None): # pylint: disable=redefined-builtin diff --git a/aiidalab_widgets_base/utils/exceptions.py b/aiidalab_widgets_base/utils/exceptions.py index 452419c7..14892867 100644 --- a/aiidalab_widgets_base/utils/exceptions.py +++ b/aiidalab_widgets_base/utils/exceptions.py @@ -3,5 +3,5 @@ class ListOrTuppleError(TypeError): def __init__(self, value): super().__init__( - f"The provided value '{value}' is not a list or a tupple, but a {type(value)}." + f"The provided value {value!r} is not a list or a tupple, but a {type(value)}." ) diff --git a/aiidalab_widgets_base/wizard.py b/aiidalab_widgets_base/wizard.py index 7202fe68..1e3e7498 100644 --- a/aiidalab_widgets_base/wizard.py +++ b/aiidalab_widgets_base/wizard.py @@ -127,7 +127,7 @@ def spinner_thread(): for widget in widgets: if not widget.has_trait("state"): raise TypeError( - f"The provided '{widget}' as wizard app step has no `state` trait. " + f"The provided {widget!r} as wizard app step has no `state` trait. " "It is expected that step classes are derived from the WizardAppWidgetStep class." ) widget.observe(self._update_step_state, names=["state"]) From 09aff815e0abc045b882a142a3c9da1afc2086dc Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 23 Jan 2023 16:31:56 +0000 Subject: [PATCH 34/87] Small fixes, more compact representations styling. --- aiidalab_widgets_base/viewers.py | 83 ++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 7a192cf5..c1e1dc30 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -160,54 +160,63 @@ def __init__(self, parameter, downloadable=True, **kwargs): super().__init__([self.widget], **kwargs) -class NlgViewrRepresentation(ipw.VBox): +class NglViewrRepresentation(ipw.HBox): """Representation for StructureData in nglviewer""" master_class = None def __init__(self, indices="1..2"): + + self.show = ipw.Checkbox( + value=True, + layout={"width": "40px"}, + style={"description_width": "0px"}, + disabled=False, + ) + self.selection = ipw.Text( - description="atoms:", value="", - layout=ipw.Layout(width="35%", height="30px"), + layout={"width": "80px"}, + style={"description_width": "0px"}, ) self.repr_type = ipw.Dropdown( options=["ball+stick", "spacefill"], value="ball+stick", - description="type", disabled=False, - layout=ipw.Layout(width="35%", height="30px"), + layout={"width": "100px"}, + style={"description_width": "0px"}, ) self.size = ipw.FloatText( - value=3, description="size", layout=ipw.Layout(width="25%", height="30px") + value=3, + layout={"width": "40px"}, + style={"description_width": "0px"}, ) self.color = ipw.Dropdown( options=["element", "red", "green", "blue", "yellow", "orange", "purple"], value="element", - description="color", disabled=False, - layout=ipw.Layout(width="35%", height="30px"), + layout={"width": "80px"}, + style={"description_width": "initial"}, ) - self.show = ipw.Checkbox(value=True, description="show", disabled=False) # Delete button. self.delete_button = ipw.Button( - description="Delete", + description="", + icon="trash", button_style="danger", - layout=ipw.Layout(width="15%", height="30px"), + layout={"width": "50px"}, + style={"description_width": "initial"}, ) self.delete_button.on_click(self.delete_myself) super().__init__( children=[ - ipw.HBox( - [ - self.selection, - self.show, - self.delete_button, - ] - ), - ipw.HBox([self.repr_type, self.size, self.color]), + self.show, + self.selection, + self.repr_type, + self.size, + self.color, + self.delete_button, ] ) @@ -393,6 +402,34 @@ def change_camera(change): center_button.on_click(lambda c: self._viewer.center()) # 5. representations buttons + self.representations_header = ipw.HBox( + [ + ipw.HTML( + """

Show

""", + layout={"width": "40px"}, + ), + ipw.HTML( + """

Atoms

""", + layout={"width": "80px"}, + ), + ipw.HTML( + """

Type

""", + layout={"width": "100px"}, + ), + ipw.HTML( + """

Size

""", + layout={"width": "40px"}, + ), + ipw.HTML( + """

Color

""", + layout={"width": "80px"}, + ), + ipw.HTML( + """

Delete

""", + layout={"width": "50px"}, + ), + ] + ) self.atoms_not_represented = ipw.HTML() self.add_new_rep_button = ipw.Button(description="Add rep", button_style="info") self.add_new_rep_button.on_click(self.add_representation) @@ -407,6 +444,7 @@ def change_camera(change): background_color, camera_type, self.add_new_rep_button, + self.representations_header, self.representation_output, self.atoms_not_represented, apply_rep, @@ -416,7 +454,7 @@ def change_camera(change): def add_representation(self, _): """Add a representation to the list of representations.""" - self.all_representations = self.all_representations + [NlgViewrRepresentation()] + self.all_representations = self.all_representations + [NglViewrRepresentation()] self.n_all_representations += 1 def delete_representation(self, representation): @@ -1028,7 +1066,6 @@ def repeat(self, _=None): def _valid_structure(self, change): # pylint: disable=no-self-use """Update structure.""" self.remove_viewer_components() - self.clear_selection() structure = change["value"] if structure is None: return ( @@ -1052,6 +1089,10 @@ def _observe_structure(self, change): """Update displayed_structure trait after the structure trait has been modified.""" # Remove the current structure(s) from the viewer. self.natoms = len(self.structure) if self.structure is not None else 0 + + if not self.structure: # If the structure is not set - do nothing. + return + if "representations" not in self.structure.arrays: self.structure.set_array("representations", np.zeros(self.natoms)) if "representationsshow" not in self.structure.arrays: From d3a7cb5d216f3f6da286edcb2d6d500cc000ce89 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 24 Jan 2023 16:01:00 +0000 Subject: [PATCH 35/87] Simplify the code. --- aiidalab_widgets_base/structures.py | 16 ++----- aiidalab_widgets_base/viewers.py | 67 +++++++++++++---------------- 2 files changed, 34 insertions(+), 49 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index b1df582e..ed735e03 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -1184,12 +1184,6 @@ def disable_element(_=None): ] ) - def find_index(self, list_of_lists, element): - for i, x in enumerate(list_of_lists): - if element in x: - return i - return -1 - def str2vec(self, string): return np.array(list(map(float, string.split()))) @@ -1392,8 +1386,6 @@ def mod_element(self, _=None, atoms=None, selection=None): range(last_atom, last_atom + len(selection) * len(lgnd)) ) - # The order of the traitlets below is important - - # we must be sure trait atoms is set before trait selection self.structure, self.input_selection = atoms, new_selection @_register_structure @@ -1406,12 +1398,10 @@ def copy_sel(self, _=None, atoms=None, selection=None): add_atoms = atoms[self.selection].copy() add_atoms.translate([1.0, 0, 0]) atoms += add_atoms - new_selection = list(range(last_atom, last_atom + len(selection))) - # The order of the traitlets below is important - - # we must be sure trait atoms is set before trait selection - new_selection = list(range(last_atom, last_atom + len(selection))) - self.structure, self.input_selection = atoms, new_selection + self.structure, self.input_selection = atoms, list( + range(last_atom, last_atom + len(selection)) + ) @_register_structure @_register_selection diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index c1e1dc30..74e0de66 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -554,11 +554,11 @@ def apply_representations(self, change=None): # self.displayed_structure.set_array("representations", arrayrepresentations) # self.displayed_structure.set_array("representationsshow", arrayrepresentationsshow) # iterate on number of representations - self.repr_params = [] + representation_parameters = [] current_rep = 0 for rep in self.all_representations: # in representation dictionary indexes start from 0 so we transform '1..4' in '0..3' - self.repr_params.append(self.representation_parameters(rep)) + representation_parameters.append(self.representation_parameters(rep)) current_rep += 1 missing_atoms = { @@ -571,7 +571,11 @@ def apply_representations(self, change=None): ) else: self.atoms_not_represented.value = "" - self.update_viewer() + + if self.displayed_structure: + self._viewer.set_representations(representation_parameters, component=0) + self._viewer.add_unitcell() + self._viewer.center() @observe("cell") def _observe_cell(self, _=None): @@ -928,18 +932,10 @@ def highlight_atoms( ) def remove_viewer_components(self, c=None): - with self.hold_trait_notifications(): - while hasattr(self._viewer, "component_0"): - self._viewer.component_0.clear_representations() - cid = self._viewer.component_0.id - self._viewer.remove_component(cid) - - def update_viewer(self, c=None): - - if self.displayed_structure: - self._viewer.set_representations(self.repr_params, component=0) - self._viewer.add_unitcell() - self._viewer.center() + if hasattr(self._viewer, "component_0"): + self._viewer.component_0.clear_representations() + cid = self._viewer.component_0.id + self._viewer.remove_component(cid) @default("supercell") def _default_supercell(self): @@ -1051,10 +1047,9 @@ class StructureDataViewer(_StructureDataBaseViewer): displayed_structure = Instance(Atoms, allow_none=True, read_only=True) pk = Int(allow_none=True) - def __init__(self, structure=None, **kwargs): + def __init__(self, **kwargs): super().__init__(**kwargs) - self.structure = structure - self.natoms = len(self.structure) if self.structure is not None else 0 + self.natoms = len(self.structure) if self.structure else 0 @observe("supercell") def repeat(self, _=None): @@ -1088,21 +1083,20 @@ def _valid_structure(self, change): # pylint: disable=no-self-use def _observe_structure(self, change): """Update displayed_structure trait after the structure trait has been modified.""" # Remove the current structure(s) from the viewer. - self.natoms = len(self.structure) if self.structure is not None else 0 - - if not self.structure: # If the structure is not set - do nothing. - return - - if "representations" not in self.structure.arrays: - self.structure.set_array("representations", np.zeros(self.natoms)) - if "representationsshow" not in self.structure.arrays: - self.structure.set_array("representationsshow", np.ones(self.natoms)) - if change["new"] is not None: - self.set_trait("displayed_structure", change["new"].repeat(self.supercell)) - self.set_trait("cell", change["new"].cell) + structure = change["new"] + + if structure: + self.natoms = len(structure) + if "representations" not in structure.arrays: + structure.set_array("representations", np.zeros(self.natoms)) + if "representationsshow" not in structure.arrays: + structure.set_array("representationsshow", np.ones(self.natoms)) + self.set_trait("displayed_structure", structure.repeat(self.supercell)) + self.set_trait("cell", structure.cell) else: self.set_trait("displayed_structure", None) self.set_trait("cell", None) + self.natoms = 0 @observe("displayed_structure") def _observe_displayed_structure(self, change): @@ -1310,7 +1304,6 @@ def add_info(indx, atom): axis=0, ) ) - info_natoms_geo_center = f"

{len(self.displayed_selection)} atoms selected

Geometric center: ({geom_center})

" # Report coordinates. if len(self.displayed_selection) == 1: @@ -1349,7 +1342,7 @@ def add_info(indx, atom): ) ) normal = normal / np.linalg.norm(normal) - info += f"

{info_natoms_geo_center}

Angle: {angle}

Normal: ({print_pos(normal)})

" + info += f"

Angle: {angle}, Normal: ({print_pos(normal)})

" # Report dihedral angle and geometric center. elif len(self.displayed_selection) == 4: @@ -1360,11 +1353,13 @@ def add_info(indx, atom): dihedral_str = f"{dihedral:.2f}" except ZeroDivisionError: dihedral_str = "nan" - info += ( - f"

{info_natoms_geo_center}

Dihedral angle: {dihedral_str}

" - ) + info += f"

Dihedral angle: {dihedral_str}

" - return info + info_natoms_geo_center + return ( + info + + f"

Geometric center: ({geom_center})

" + + f"

{len(self.displayed_selection)} atoms selected

" + ) @observe("displayed_selection") def _observe_displayed_selection_2(self, _=None): From 2e26732e838765cedfd0a57d8d4eb15090723c7b Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 24 Jan 2023 22:30:56 +0000 Subject: [PATCH 36/87] Continue with changes. --- aiidalab_widgets_base/viewers.py | 48 +++++++++++++------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 74e0de66..674a58b9 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -47,9 +47,6 @@ from .utils import ase2spglib, list_to_string_range, string_range_to_list AIIDA_VIEWER_MAPPING = {} -BOX_LAYOUT = ipw.Layout( - display="flex-wrap", flex_flow="row wrap", justify_content="space-between" -) def register_viewer_widget(key): @@ -262,7 +259,6 @@ def __init__( self._viewer.observe(self._on_atom_click, names="picked") self._viewer.stage.set_parameters(mouse_preset="pymol") self.natoms = 0 - self.n_all_representations = 0 view_box = ipw.VBox([self._viewer]) @@ -431,23 +427,24 @@ def change_camera(change): ] ) self.atoms_not_represented = ipw.HTML() - self.add_new_rep_button = ipw.Button(description="Add rep", button_style="info") - self.add_new_rep_button.on_click(self.add_representation) + add_new_representation_button = ipw.Button( + description="Add representation", button_style="info" + ) + add_new_representation_button.on_click(self.add_representation) - apply_rep = ipw.Button(description="Apply rep") + apply_rep = ipw.Button(description="Apply representations") apply_rep.on_click(self.apply_representations) - self.representation_output = ipw.Box(layout=BOX_LAYOUT) + self.representation_output = ipw.VBox() return ipw.VBox( [ supercell_selector, background_color, camera_type, - self.add_new_rep_button, self.representations_header, self.representation_output, self.atoms_not_represented, - apply_rep, + ipw.HBox([apply_rep, add_new_representation_button]), center_button, ] ) @@ -455,7 +452,6 @@ def change_camera(change): def add_representation(self, _): """Add a representation to the list of representations.""" self.all_representations = self.all_representations + [NglViewrRepresentation()] - self.n_all_representations += 1 def delete_representation(self, representation): try: @@ -468,25 +464,20 @@ def delete_representation(self, representation): self.all_representations[:index] + self.all_representations[index + 1 :] ) del representation - self.n_all_representations -= 1 self.apply_representations() @observe("all_representations") def _observe_all_representations(self, change): """Update the list of representations.""" + self.representation_output.children = change["new"] if change["new"]: - self.representation_output.children = change["new"] self.all_representations[-1].master_class = self - else: - self.all_representation_output.children = [] def update_representations(self, change=None): """Update the representations using the list of representations""" - number_of_representation_widgets = len(self.all_representations) if self.displayed_structure: - if number_of_representation_widgets == 0: - self.n_all_representations = 0 - self.add_representation(None) + if not self.all_representations: + self.add_representation(None) # Sasha: what is this? representations = self.structure.arrays["representations"] for rep in set(representations): @@ -499,7 +490,7 @@ def update_representations(self, change=None): [int(i) for i in np.where(representations == rep)[0]], shift=1 ) # empty selection field for unused representations - for rep in range(number_of_representation_widgets): + for rep in range(len(self.all_representations)): if rep not in {int(i) for i in representations}: self.all_representations[rep].selection.value = "" self.apply_representations() @@ -531,13 +522,14 @@ def representation_parameters(self, representation): def apply_representations(self, change=None): """Apply the representations to the displayed structure.""" - # negative value means an atom is not assigned to a representation + + # Negative value means an atom is not assigned to a representation. self._viewer.clear_representations(component=0) - # initially not atoms are assigned to a representation + # Initially no atoms are assigned to a representation. arrayrepresentations = -1 * np.ones(self.natoms) - # the atom is not shown + # The atom is not shown arrayrepresentationsshow = np.zeros(self.natoms) for irep, rep in enumerate(self.all_representations): @@ -864,13 +856,13 @@ def _render_structure(self, change=None): def _on_atom_click(self, _=None): """Update selection when clicked on atom.""" if hasattr(self._viewer, "component_0"): + # Did not click on atom: if "atom1" not in self._viewer.picked.keys(): - return # did not click on atom + return + index = self._viewer.picked["atom1"]["index"] - # component = self._viewer.picked["component"] to be used in case of multiple components will be implemented displayed_selection = self.displayed_selection.copy() - if displayed_selection: if index not in displayed_selection: displayed_selection.append(index) @@ -898,13 +890,13 @@ def highlight_atoms( # Map vis_list and self.displayed_structure.arrays["representations"] to a list of strings # that goes to the highlight_reps # there are N representations defined by the user and N automatically added for highlighting - ids = [[] for rep in range(self.n_all_representations)] + ids = [[] for _rep in self.all_representations] for i in vis_list: ids[int(self.structure.arrays["representations"][i])].append(i) # Remove previous highlight_rep representations. - for i in range(self.n_all_representations): + for i in range(len(self.all_representations)): self._viewer._remove_representations_by_name( repr_name="highlight_rep" + str(i), component=0 ) From e7bef5e84e2158b8d5b50ec2e705b7095debb984 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 25 Jan 2023 10:06:12 +0000 Subject: [PATCH 37/87] Fix supercell appearance (old components were not removed). --- aiidalab_widgets_base/viewers.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 674a58b9..c6d53fce 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -475,9 +475,9 @@ def _observe_all_representations(self, change): def update_representations(self, change=None): """Update the representations using the list of representations""" - if self.displayed_structure: + if self.structure: if not self.all_representations: - self.add_representation(None) # Sasha: what is this? + self.add_representation(None) representations = self.structure.arrays["representations"] for rep in set(representations): @@ -1089,18 +1089,20 @@ def _observe_structure(self, change): self.set_trait("displayed_structure", None) self.set_trait("cell", None) self.natoms = 0 + self.update_representations() @observe("displayed_structure") def _observe_displayed_structure(self, change): """Update the view if displayed_structure trait was modified.""" with self.hold_trait_notifications(): + for component_index in self._viewer._ngl_component_ids: + self._viewer.remove_component(component_index) if change["new"] is not None: self._viewer.add_component( nglview.ASEStructure(self.displayed_structure), default_representation=False, name="Structure", ) - self.update_representations() self.displayed_selection = [] def d_from(self, operand): From fc4985d5b3a7cb1e54818f6fea1e3256fe7c9acd Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 25 Jan 2023 16:29:50 +0000 Subject: [PATCH 38/87] Further simplify and improve the code. --- aiidalab_widgets_base/viewers.py | 186 +++++++++++++------------------ 1 file changed, 77 insertions(+), 109 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index c6d53fce..908550f0 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -2,9 +2,9 @@ # pylint: disable=no-self-use import base64 +import copy import re import warnings -from copy import deepcopy import ipywidgets as ipw import nglview @@ -157,12 +157,12 @@ def __init__(self, parameter, downloadable=True, **kwargs): super().__init__([self.widget], **kwargs) -class NglViewrRepresentation(ipw.HBox): +class NglViewerRepresentation(ipw.HBox): """Representation for StructureData in nglviewer""" master_class = None - def __init__(self, indices="1..2"): + def __init__(self, indices=None): self.show = ipw.Checkbox( value=True, @@ -172,11 +172,11 @@ def __init__(self, indices="1..2"): ) self.selection = ipw.Text( - value="", + value=indices, layout={"width": "80px"}, style={"description_width": "0px"}, ) - self.repr_type = ipw.Dropdown( + self.type = ipw.Dropdown( options=["ball+stick", "spacefill"], value="ball+stick", disabled=False, @@ -210,7 +210,7 @@ def __init__(self, indices="1..2"): children=[ self.show, self.selection, - self.repr_type, + self.type, self.size, self.color, self.delete_button, @@ -430,10 +430,10 @@ def change_camera(change): add_new_representation_button = ipw.Button( description="Add representation", button_style="info" ) - add_new_representation_button.on_click(self.add_representation) + add_new_representation_button.on_click(self._add_representation) - apply_rep = ipw.Button(description="Apply representations") - apply_rep.on_click(self.apply_representations) + apply_representations = ipw.Button(description="Apply representations") + apply_representations.on_click(self._apply_representations) self.representation_output = ipw.VBox() return ipw.VBox( @@ -444,14 +444,21 @@ def change_camera(change): self.representations_header, self.representation_output, self.atoms_not_represented, - ipw.HBox([apply_rep, add_new_representation_button]), + ipw.HBox([apply_representations, add_new_representation_button]), center_button, ] ) - def add_representation(self, _): + def _add_representation(self, _=None): """Add a representation to the list of representations.""" - self.all_representations = self.all_representations + [NglViewrRepresentation()] + if self.all_representations: + self.all_representations = self.all_representations + [ + NglViewerRepresentation() + ] + else: + self.all_representations = [ + NglViewerRepresentation(indices=f"1..{len(self.structure)}") + ] def delete_representation(self, representation): try: @@ -464,7 +471,6 @@ def delete_representation(self, representation): self.all_representations[:index] + self.all_representations[index + 1 :] ) del representation - self.apply_representations() @observe("all_representations") def _observe_all_representations(self, change): @@ -473,101 +479,38 @@ def _observe_all_representations(self, change): if change["new"]: self.all_representations[-1].master_class = self - def update_representations(self, change=None): - """Update the representations using the list of representations""" - if self.structure: - if not self.all_representations: - self.add_representation(None) - - representations = self.structure.arrays["representations"] - for rep in set(representations): - if ( - rep >= 0 - ): # negative values are used for atoms not represented (different from the case of hidden representations) - self.all_representations[ - int(rep) - ].selection.value = list_to_string_range( - [int(i) for i in np.where(representations == rep)[0]], shift=1 - ) - # empty selection field for unused representations - for rep in range(len(self.all_representations)): - if rep not in {int(i) for i in representations}: - self.all_representations[rep].selection.value = "" - self.apply_representations() - - def representation_parameters(self, representation): + def _get_representation_parameters(self, indices, representation): """Return the parameters dictionary of a representation.""" - idsl = string_range_to_list(representation.selection.value, shift=-1)[0] - idsl_rep = [ - i + rep * self.natoms - for rep in range(np.prod(self.supercell)) - for i in idsl - if self.structure.arrays["representationsshow"][i] - ] - ids = self.list_to_nglview(idsl_rep) params = { - "type": representation.repr_type.value, + "type": representation.type.value, "params": { - "sele": ids, + "sele": self.list_to_nglview(indices), "opacity": 1, "color": representation.color.value, }, } - if representation.repr_type.value == "ball+stick": + if representation.type.value == "ball+stick": params["params"]["aspectRatio"] = representation.size.value else: params["params"]["radiusScale"] = 0.1 * representation.size.value return params - def apply_representations(self, change=None): + def _apply_representations(self, change=None): """Apply the representations to the displayed structure.""" - # Negative value means an atom is not assigned to a representation. - self._viewer.clear_representations(component=0) + self._observe_structure({"new": self.structure}) - # Initially no atoms are assigned to a representation. - arrayrepresentations = -1 * np.ones(self.natoms) - - # The atom is not shown - arrayrepresentationsshow = np.zeros(self.natoms) - - for irep, rep in enumerate(self.all_representations): - selection = string_range_to_list(rep.selection.value, shift=-1)[0] - for index in selection: - arrayrepresentations[index] = irep - if rep.show.value: - arrayrepresentationsshow[index] = 1 - - self.structure.set_array("representations", arrayrepresentations) - self.structure.set_array("representationsshow", arrayrepresentationsshow) - - # when supercell bugs will be fixed, decide on how to handle supercell selections - # self.displayed_structure.set_array("representations", arrayrepresentations) - # self.displayed_structure.set_array("representationsshow", arrayrepresentationsshow) - # iterate on number of representations - representation_parameters = [] - current_rep = 0 - for rep in self.all_representations: - # in representation dictionary indexes start from 0 so we transform '1..4' in '0..3' - representation_parameters.append(self.representation_parameters(rep)) - current_rep += 1 - - missing_atoms = { - int(i) for i in np.where(self.structure.arrays["representations"] < 0)[0] - } - if missing_atoms: - self.atoms_not_represented.value = ( - "Atoms excluded from representations: " - + list_to_string_range(list(missing_atoms), shift=1) - ) - else: - self.atoms_not_represented.value = "" - - if self.displayed_structure: - self._viewer.set_representations(representation_parameters, component=0) - self._viewer.add_unitcell() - self._viewer.center() + # missing_atoms = { + # int(i) for i in np.where(self.structure.arrays["representations"] < 0)[0] + # } + # if missing_atoms: + # self.atoms_not_represented.value = ( + # "Atoms excluded from representations: " + # + list_to_string_range(list(missing_atoms), shift=1) + # ) + # else: + # self.atoms_not_represented.value = "" @observe("cell") def _observe_cell(self, _=None): @@ -730,7 +673,7 @@ def _render_structure(self, change=None): zfactor = norm(omat[0, 0:3]) omat[0:3, 0:3] = omat[0:3, 0:3] / zfactor - bb = deepcopy(self.displayed_structure) + bb = copy.deepcopy(self.displayed_structure) bb.pbc = (False, False, False) for i in bb: @@ -875,8 +818,8 @@ def _on_atom_click(self, _=None): def list_to_nglview(self, the_list): """Converts a list of structures to a nglview widget""" sele = "none" - if the_list: - sele = "@" + ",".join(str(s) for s in the_list) + if len(the_list): + sele = "@" + ",".join(map(str, the_list)) return sele def highlight_atoms( @@ -1044,15 +987,16 @@ def __init__(self, **kwargs): self.natoms = len(self.structure) if self.structure else 0 @observe("supercell") - def repeat(self, _=None): + def _observe_supercell(self, _=None): if self.structure is not None: + self.set_trait( + "displayed_structure", None + ) # To make sure the structure is always updated. self.set_trait("displayed_structure", self.structure.repeat(self.supercell)) - self.apply_representations() @validate("structure") def _valid_structure(self, change): # pylint: disable=no-self-use """Update structure.""" - self.remove_viewer_components() structure = change["value"] if structure is None: return ( @@ -1068,41 +1012,65 @@ def _valid_structure(self, change): # pylint: disable=no-self-use f"Unsupported type {type(structure)}, structure must be one of the following types: " "ASE Atoms object, AiiDA CifData or StructureData." ) - return structure @observe("structure") def _observe_structure(self, change): """Update displayed_structure trait after the structure trait has been modified.""" - # Remove the current structure(s) from the viewer. structure = change["new"] + self._viewer.clear_representations(component=0) + + if not self.all_representations: + self._add_representation() + if structure: self.natoms = len(structure) - if "representations" not in structure.arrays: - structure.set_array("representations", np.zeros(self.natoms)) - if "representationsshow" not in structure.arrays: - structure.set_array("representationsshow", np.ones(self.natoms)) - self.set_trait("displayed_structure", structure.repeat(self.supercell)) + for index, representation in enumerate(self.all_representations): + array_representation = np.zeros(self.natoms) + selection = string_range_to_list( + representation.selection.value, shift=-1 + )[0] + if representation.show: + array_representation[selection] = 1 + self.structure.set_array( + f"_aiidalab_viewer_representation_{index}", array_representation + ) + + self._observe_supercell() # To trigger an update of the displayed structure self.set_trait("cell", structure.cell) else: self.set_trait("displayed_structure", None) self.set_trait("cell", None) self.natoms = 0 - self.update_representations() @observe("displayed_structure") def _observe_displayed_structure(self, change): """Update the view if displayed_structure trait was modified.""" with self.hold_trait_notifications(): - for component_index in self._viewer._ngl_component_ids: - self._viewer.remove_component(component_index) - if change["new"] is not None: + self.remove_viewer_components() + if change["new"]: self._viewer.add_component( nglview.ASEStructure(self.displayed_structure), default_representation=False, name="Structure", ) + representation_parameters = [] + for rep_name in [ + r + for r in self.displayed_structure.arrays + if r.startswith("_aiidalab_viewer_representation_") + ]: + indices = np.where(self.displayed_structure.arrays[rep_name] > 0)[0] + representation = self.all_representations[ + int(rep_name[32:]) + ] # Cut of the _aiidalab_viewer_representation_ prefix + representation_parameters.append( + self._get_representation_parameters(indices, representation) + ) + self._viewer.set_representations(representation_parameters, component=0) + self._viewer.add_unitcell() + self._viewer.center() self.displayed_selection = [] def d_from(self, operand): From ae7d8ad59a4b00441fe00e2026620f7e2be115df Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Thu, 26 Jan 2023 22:35:35 +0000 Subject: [PATCH 39/87] Improve hightlight_atoms(), simplify the handling of representations. --- aiidalab_widgets_base/viewers.py | 117 ++++++++++++++++--------------- 1 file changed, 61 insertions(+), 56 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 908550f0..5fd70d3c 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -9,6 +9,7 @@ import ipywidgets as ipw import nglview import numpy as np +import shortuuid import spglib import traitlets from aiida.cmdline.utils.common import get_workchain_report @@ -164,6 +165,8 @@ class NglViewerRepresentation(ipw.HBox): def __init__(self, indices=None): + self.uuid = f"_aiidalab_viewer_representation_{shortuuid.uuid()}" + self.show = ipw.Checkbox( value=True, layout={"width": "40px"}, @@ -220,6 +223,17 @@ def __init__(self, indices=None): def delete_myself(self, _): self.master_class.delete_representation(self) + def add_myself_to_atoms_object(self, structure): + """Add representation array to the structure object.""" + if structure: + array_representation = np.zeros(len(structure)) + selection = string_range_to_list(self.selection.value, shift=-1)[0] + if self.show.value: + array_representation[selection] = 1 + structure.set_array(self.uuid, array_representation) + return True + return False + class _StructureDataBaseViewer(ipw.VBox): """Base viewer class for AiiDA structure or trajectory objects. @@ -459,6 +473,7 @@ def _add_representation(self, _=None): self.all_representations = [ NglViewerRepresentation(indices=f"1..{len(self.structure)}") ] + self._apply_representations() def delete_representation(self, representation): try: @@ -470,6 +485,9 @@ def delete_representation(self, representation): self.all_representations = ( self.all_representations[:index] + self.all_representations[index + 1 :] ) + + if representation.uuid in self.structure.arrays: + del self.structure.arrays[representation.uuid] del representation @observe("all_representations") @@ -501,16 +519,17 @@ def _apply_representations(self, change=None): self._observe_structure({"new": self.structure}) - # missing_atoms = { - # int(i) for i in np.where(self.structure.arrays["representations"] < 0)[0] - # } - # if missing_atoms: - # self.atoms_not_represented.value = ( - # "Atoms excluded from representations: " - # + list_to_string_range(list(missing_atoms), shift=1) - # ) - # else: - # self.atoms_not_represented.value = "" + missing_atoms = np.zeros(self.natoms) + for representation in self.all_representations: + missing_atoms += self.structure.arrays[representation.uuid] + missing_atoms = np.where(missing_atoms == 0)[0] + if len(missing_atoms) > 0: + self.atoms_not_represented.value = ( + "Atoms excluded from representations: " + + list_to_string_range(list(missing_atoms), shift=1) + ) + else: + self.atoms_not_represented.value = "" @observe("cell") def _observe_cell(self, _=None): @@ -817,10 +836,10 @@ def _on_atom_click(self, _=None): def list_to_nglview(self, the_list): """Converts a list of structures to a nglview widget""" - sele = "none" + selection = "none" if len(the_list): - sele = "@" + ",".join(map(str, the_list)) - return sele + selection = "@" + ",".join(map(str, the_list)) + return selection def highlight_atoms( self, @@ -830,35 +849,33 @@ def highlight_atoms( if not hasattr(self._viewer, "component_0"): return - # Map vis_list and self.displayed_structure.arrays["representations"] to a list of strings - # that goes to the highlight_reps - # there are N representations defined by the user and N automatically added for highlighting - ids = [[] for _rep in self.all_representations] - - for i in vis_list: - ids[int(self.structure.arrays["representations"][i])].append(i) + # Get the list of representation arrays stored in the ase.Atoms object. + representations = [ + self.displayed_structure.arrays[r.uuid] for r in self.all_representations + ] - # Remove previous highlight_rep representations. - for i in range(len(self.all_representations)): + # Create the dictionaries for highlight_representations. + for i, selection in enumerate(representations): + # First remove the previous highlight_representation. self._viewer._remove_representations_by_name( - repr_name="highlight_rep" + str(i), component=0 + repr_name=f"highlight_representation_{i}", component=0 ) - - # Create the dictionaries for highlight_reps. - for i, selection in enumerate(ids): - if selection: - params = self.representation_parameters(self.all_representations[i]) - params["params"]["sele"] = self.list_to_nglview(selection) - params["params"]["name"] = "highlight_rep" + str(i) - params["params"]["color"] = "red" - params["params"]["opacity"] = 0.6 + # Then add the new one if needed. + indices = np.intersect1d(vis_list, np.where(selection > 0)) + if len(indices) > 0: + params = self._get_representation_parameters( + indices, self.all_representations[i] + ) + params["params"]["name"] = f"highlight_representation_{i}" + params["params"]["color"] = "black" + params["params"]["opacity"] = 0.3 params["params"]["component_index"] = 0 if "radiusScale" in params["params"]: - params["params"]["radiusScale"] += 0.1 + params["params"]["radiusScale"] *= 1.4 else: - params["params"]["aspectRatio"] += 0.1 + params["params"]["aspectRatio"] *= 1.4 - # and use directly teh remote call for more flexibility + # Use directly the remote call for more flexibility. self._viewer._remote_call( "addRepresentation", target="compList", @@ -1015,7 +1032,7 @@ def _valid_structure(self, change): # pylint: disable=no-self-use return structure @observe("structure") - def _observe_structure(self, change): + def _observe_structure(self, change=None): """Update displayed_structure trait after the structure trait has been modified.""" structure = change["new"] @@ -1026,17 +1043,9 @@ def _observe_structure(self, change): if structure: self.natoms = len(structure) - for index, representation in enumerate(self.all_representations): - array_representation = np.zeros(self.natoms) - selection = string_range_to_list( - representation.selection.value, shift=-1 - )[0] - if representation.show: - array_representation[selection] = 1 - self.structure.set_array( - f"_aiidalab_viewer_representation_{index}", array_representation - ) - + # Make sure that the structure has all the representation arrays. + for representation in self.all_representations: + representation.add_myself_to_atoms_object(structure) self._observe_supercell() # To trigger an update of the displayed structure self.set_trait("cell", structure.cell) else: @@ -1056,18 +1065,14 @@ def _observe_displayed_structure(self, change): name="Structure", ) representation_parameters = [] - for rep_name in [ - r - for r in self.displayed_structure.arrays - if r.startswith("_aiidalab_viewer_representation_") - ]: - indices = np.where(self.displayed_structure.arrays[rep_name] > 0)[0] - representation = self.all_representations[ - int(rep_name[32:]) - ] # Cut of the _aiidalab_viewer_representation_ prefix + for representation in self.all_representations: + indices = np.where( + self.displayed_structure.arrays[representation.uuid] > 0 + )[0] representation_parameters.append( self._get_representation_parameters(indices, representation) ) + self._viewer.set_representations(representation_parameters, component=0) self._viewer.add_unitcell() self._viewer.center() From fbc5a42e5216c9ba51353b92bf56d5accf18e607 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Thu, 26 Jan 2023 22:48:00 +0000 Subject: [PATCH 40/87] small, non-important changes. --- aiidalab_widgets_base/viewers.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 5fd70d3c..030b1247 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -502,7 +502,7 @@ def _get_representation_parameters(self, indices, representation): params = { "type": representation.type.value, "params": { - "sele": self.list_to_nglview(indices), + "sele": self._list_to_nglview(indices), "opacity": 1, "color": representation.color.value, }, @@ -834,7 +834,7 @@ def _on_atom_click(self, _=None): displayed_selection = [index] self.displayed_selection = displayed_selection - def list_to_nglview(self, the_list): + def _list_to_nglview(self, the_list): """Converts a list of structures to a nglview widget""" selection = "none" if len(the_list): @@ -867,7 +867,6 @@ def highlight_atoms( indices, self.all_representations[i] ) params["params"]["name"] = f"highlight_representation_{i}" - params["params"]["color"] = "black" params["params"]["opacity"] = 0.3 params["params"]["component_index"] = 0 if "radiusScale" in params["params"]: @@ -1015,10 +1014,9 @@ def _observe_supercell(self, _=None): def _valid_structure(self, change): # pylint: disable=no-self-use """Update structure.""" structure = change["value"] + # If no structure is provided, the rest of the code can be skipped. if structure is None: - return ( - None # If no structure is provided, the rest of the code can be skipped - ) + return None if isinstance(structure, Atoms): self.pk = None elif isinstance(structure, Node): From 79ac26f505a74268b48e40b8a858ca15c6444cf5 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 30 Jan 2023 16:16:33 +0000 Subject: [PATCH 41/87] Keep representations after editing the structure --- aiidalab_widgets_base/viewers.py | 70 ++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 030b1247..25b215d9 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -163,9 +163,9 @@ class NglViewerRepresentation(ipw.HBox): master_class = None - def __init__(self, indices=None): + def __init__(self, uuid=None, indices=None): - self.uuid = f"_aiidalab_viewer_representation_{shortuuid.uuid()}" + self.uuid = uuid or f"_aiidalab_viewer_representation_{shortuuid.uuid()}" self.show = ipw.Checkbox( value=True, @@ -175,7 +175,7 @@ def __init__(self, indices=None): ) self.selection = ipw.Text( - value=indices, + value=list_to_string_range(indices, shift=1) if indices else "", layout={"width": "80px"}, style={"description_width": "0px"}, ) @@ -223,8 +223,18 @@ def __init__(self, indices=None): def delete_myself(self, _): self.master_class.delete_representation(self) + def sync_myself_to_array_from_atoms_object(self, structure): + """Update representation from the structure object.""" + if structure: + if self.uuid in structure.arrays: + self.selection.value = list_to_string_range( + np.where(structure.arrays[self.uuid] > 0)[0], shift=1 + ) + return True + return False + def add_myself_to_atoms_object(self, structure): - """Add representation array to the structure object.""" + """Add representation array to the structure object. If the array already exists, update it.""" if structure: array_representation = np.zeros(len(structure)) selection = string_range_to_list(self.selection.value, shift=-1)[0] @@ -463,15 +473,17 @@ def change_camera(change): ] ) - def _add_representation(self, _=None): + def _add_representation(self, _=None, uuid=None, indices=None): """Add a representation to the list of representations.""" if self.all_representations: self.all_representations = self.all_representations + [ - NglViewerRepresentation() + NglViewerRepresentation(uuid=uuid, indices=indices) ] else: self.all_representations = [ - NglViewerRepresentation(indices=f"1..{len(self.structure)}") + NglViewerRepresentation( + uuid=uuid, indices=indices or list(range(self.natoms)) + ) ] self._apply_representations() @@ -516,12 +528,26 @@ def _get_representation_parameters(self, indices, representation): def _apply_representations(self, change=None): """Apply the representations to the displayed structure.""" - + rep_uuids = [] + # Add existing representations to the structure. + for representation in self.all_representations: + representation.add_myself_to_atoms_object(self.structure) + rep_uuids.append(representation.uuid) + + # Remove missing representations from the structure. + for array in self.structure.arrays: + if ( + array.startswith("_aiidalab_viewer_representation_") + and array not in rep_uuids + ): + del self.structure.arrays[array] self._observe_structure({"new": self.structure}) + self._check_missing_atoms_in_representations() + def _check_missing_atoms_in_representations(self): missing_atoms = np.zeros(self.natoms) - for representation in self.all_representations: - missing_atoms += self.structure.arrays[representation.uuid] + for rep in self.all_representations: + missing_atoms += self.structure.arrays[rep.uuid] missing_atoms = np.where(missing_atoms == 0)[0] if len(missing_atoms) > 0: self.atoms_not_represented.value = ( @@ -1036,14 +1062,26 @@ def _observe_structure(self, change=None): self._viewer.clear_representations(component=0) - if not self.all_representations: - self._add_representation() - if structure: self.natoms = len(structure) - # Make sure that the structure has all the representation arrays. - for representation in self.all_representations: - representation.add_myself_to_atoms_object(structure) + # Make sure that the representation arrays from structure are present in the viewer. + uuids = [ + uuid + for uuid in structure.arrays + if uuid.startswith("_aiidalab_viewer_representation_") + ] + rep_uuids = [rep.uuid for rep in self.all_representations] + for uuid in uuids: + try: + index = rep_uuids.index(uuid) + self.all_representations[ + index + ].sync_myself_to_array_from_atoms_object(structure) + except ValueError: + self._add_representation(uuid=uuid) + # No representations found, adding default one. + if not self.all_representations: + self._add_representation() self._observe_supercell() # To trigger an update of the displayed structure self.set_trait("cell", structure.cell) else: From 227d09b2a5a89ba05809aa5606b555b4a6fe4b03 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 1 Feb 2023 08:51:56 +0000 Subject: [PATCH 42/87] Apply suggestions from the code review. - Create a default representation that can't be deleted and remains in the very top. - If new atoms are added, put the into the default representation. - Check that only existing atoms can be put in the representation. --- aiidalab_widgets_base/viewers.py | 128 ++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 44 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 25b215d9..29a5b215 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -163,8 +163,20 @@ class NglViewerRepresentation(ipw.HBox): master_class = None - def __init__(self, uuid=None, indices=None): + def __init__(self, uuid=None, indices=None, deletable=True, array_threshold=1.0): + """Initialize the representation. + + uuid: str + Unique identifier for the representation. + indices: list + List of indices to be displayed. + deletable: bool + If True, add a button to delete the representation. + array_threshold: float + Threshold for displaying atoms based on the values of _aiidalab_viewer_representation_* arrays. + """ + self.array_threshold = array_threshold self.uuid = uuid or f"_aiidalab_viewer_representation_{shortuuid.uuid()}" self.show = ipw.Checkbox( @@ -175,7 +187,7 @@ def __init__(self, uuid=None, indices=None): ) self.selection = ipw.Text( - value=list_to_string_range(indices, shift=1) if indices else "", + value=list_to_string_range(indices, shift=1) if indices is not None else "", layout={"width": "80px"}, style={"description_width": "0px"}, ) @@ -204,7 +216,10 @@ def __init__(self, uuid=None, indices=None): description="", icon="trash", button_style="danger", - layout={"width": "50px"}, + layout={ + "width": "50px", + "visibility": "visible" if deletable else "hidden", + }, style={"description_width": "initial"}, ) self.delete_button.on_click(self.delete_myself) @@ -228,7 +243,7 @@ def sync_myself_to_array_from_atoms_object(self, structure): if structure: if self.uuid in structure.arrays: self.selection.value = list_to_string_range( - np.where(structure.arrays[self.uuid] > 0)[0], shift=1 + np.where(self.atoms_in_representaion(structure))[0], shift=1 ) return True return False @@ -236,14 +251,23 @@ def sync_myself_to_array_from_atoms_object(self, structure): def add_myself_to_atoms_object(self, structure): """Add representation array to the structure object. If the array already exists, update it.""" if structure: - array_representation = np.zeros(len(structure)) - selection = string_range_to_list(self.selection.value, shift=-1)[0] - if self.show.value: - array_representation[selection] = 1 + array_representation = np.full(len(structure), -1) + selection = np.array( + string_range_to_list(self.selection.value, shift=-1)[0] + ) + # Only attempt to display the existing atoms. + array_representation[selection[selection < len(structure)]] = 1 structure.set_array(self.uuid, array_representation) return True return False + def atoms_in_representaion(self, structure=None): + """Return an array of booleans indicating which atoms are present in the representation.""" + if structure: + if self.uuid in structure.arrays: + return structure.arrays[self.uuid] >= self.array_threshold + return False + class _StructureDataBaseViewer(ipw.VBox): """Base viewer class for AiiDA structure or trajectory objects. @@ -257,7 +281,7 @@ class _StructureDataBaseViewer(ipw.VBox): """ - all_representations = traitlets.List() + _all_representations = traitlets.List() natoms = Int() input_selection = List(Int, allow_none=True) selection = List(Int) @@ -460,6 +484,15 @@ def change_camera(change): apply_representations.on_click(self._apply_representations) self.representation_output = ipw.VBox() + # The default representation is always present and cannot be deleted. + # Moreover, it always shows new atoms due to the array_threshold=0. + self._all_representations = [ + NglViewerRepresentation( + uuid="_aiidalab_viewer_representation_default", + deletable=False, + array_threshold=0, + ) + ] return ipw.VBox( [ supercell_selector, @@ -475,39 +508,33 @@ def change_camera(change): def _add_representation(self, _=None, uuid=None, indices=None): """Add a representation to the list of representations.""" - if self.all_representations: - self.all_representations = self.all_representations + [ - NglViewerRepresentation(uuid=uuid, indices=indices) - ] - else: - self.all_representations = [ - NglViewerRepresentation( - uuid=uuid, indices=indices or list(range(self.natoms)) - ) - ] + self._all_representations = self._all_representations + [ + NglViewerRepresentation(uuid=uuid, indices=indices) + ] self._apply_representations() def delete_representation(self, representation): try: - index = self.all_representations.index(representation) + index = self._all_representations.index(representation) except ValueError: self.representation_add_message.message = f"""Error: Rep. {representation} not found.""" return - self.all_representations = ( - self.all_representations[:index] + self.all_representations[index + 1 :] + self._all_representations = ( + self._all_representations[:index] + self._all_representations[index + 1 :] ) if representation.uuid in self.structure.arrays: del self.structure.arrays[representation.uuid] del representation + self._apply_representations() - @observe("all_representations") + @observe("_all_representations") def _observe_all_representations(self, change): """Update the list of representations.""" self.representation_output.children = change["new"] if change["new"]: - self.all_representations[-1].master_class = self + self._all_representations[-1].master_class = self def _get_representation_parameters(self, indices, representation): """Return the parameters dictionary of a representation.""" @@ -529,8 +556,13 @@ def _get_representation_parameters(self, indices, representation): def _apply_representations(self, change=None): """Apply the representations to the displayed structure.""" rep_uuids = [] + + # Representation can only be applied if a structure is present. + if self.structure is None: + return + # Add existing representations to the structure. - for representation in self.all_representations: + for representation in self._all_representations: representation.add_myself_to_atoms_object(self.structure) rep_uuids.append(representation.uuid) @@ -546,8 +578,8 @@ def _apply_representations(self, change=None): def _check_missing_atoms_in_representations(self): missing_atoms = np.zeros(self.natoms) - for rep in self.all_representations: - missing_atoms += self.structure.arrays[rep.uuid] + for rep in self._all_representations: + missing_atoms += rep.atoms_in_representaion(self.structure) missing_atoms = np.where(missing_atoms == 0)[0] if len(missing_atoms) > 0: self.atoms_not_represented.value = ( @@ -875,22 +907,24 @@ def highlight_atoms( if not hasattr(self._viewer, "component_0"): return - # Get the list of representation arrays stored in the ase.Atoms object. - representations = [ - self.displayed_structure.arrays[r.uuid] for r in self.all_representations - ] - # Create the dictionaries for highlight_representations. - for i, selection in enumerate(representations): + for i, representation in enumerate(self._all_representations): + # First remove the previous highlight_representation. self._viewer._remove_representations_by_name( repr_name=f"highlight_representation_{i}", component=0 ) + # Then add the new one if needed. - indices = np.intersect1d(vis_list, np.where(selection > 0)) + indices = np.intersect1d( + vis_list, + np.where( + representation.atoms_in_representaion(self.displayed_structure) + )[0], + ) if len(indices) > 0: params = self._get_representation_parameters( - indices, self.all_representations[i] + indices, self._all_representations[i] ) params["params"]["name"] = f"highlight_representation_{i}" params["params"]["opacity"] = 0.3 @@ -1037,7 +1071,7 @@ def _observe_supercell(self, _=None): self.set_trait("displayed_structure", self.structure.repeat(self.supercell)) @validate("structure") - def _valid_structure(self, change): # pylint: disable=no-self-use + def _valid_structure(self, change): """Update structure.""" structure = change["value"] # If no structure is provided, the rest of the code can be skipped. @@ -1053,6 +1087,10 @@ def _valid_structure(self, change): # pylint: disable=no-self-use f"Unsupported type {type(structure)}, structure must be one of the following types: " "ASE Atoms object, AiiDA CifData or StructureData." ) + if "_aiidalab_viewer_representation_default" not in structure.arrays: + structure.set_array( + "_aiidalab_viewer_representation_default", np.zeros(len(structure)) + ) return structure @observe("structure") @@ -1070,18 +1108,18 @@ def _observe_structure(self, change=None): for uuid in structure.arrays if uuid.startswith("_aiidalab_viewer_representation_") ] - rep_uuids = [rep.uuid for rep in self.all_representations] + rep_uuids = [rep.uuid for rep in self._all_representations] for uuid in uuids: try: index = rep_uuids.index(uuid) - self.all_representations[ + self._all_representations[ index ].sync_myself_to_array_from_atoms_object(structure) except ValueError: - self._add_representation(uuid=uuid) - # No representations found, adding default one. - if not self.all_representations: - self._add_representation() + self._add_representation( + uuid=uuid, + indices=np.where(structure.arrays[self.uuid] >= 1.0)[0], + ) self._observe_supercell() # To trigger an update of the displayed structure self.set_trait("cell", structure.cell) else: @@ -1101,9 +1139,11 @@ def _observe_displayed_structure(self, change): name="Structure", ) representation_parameters = [] - for representation in self.all_representations: + for representation in self._all_representations: + if not representation.show.value: + continue indices = np.where( - self.displayed_structure.arrays[representation.uuid] > 0 + representation.atoms_in_representaion(self.displayed_structure) )[0] representation_parameters.append( self._get_representation_parameters(indices, representation) From bdda60da3558423d8cc48e2d0e3a1cf677951c9b Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 1 Feb 2023 09:21:58 +0000 Subject: [PATCH 43/87] Fix numpy array type problem. --- aiidalab_widgets_base/viewers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 29a5b215..26f4384b 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -253,7 +253,7 @@ def add_myself_to_atoms_object(self, structure): if structure: array_representation = np.full(len(structure), -1) selection = np.array( - string_range_to_list(self.selection.value, shift=-1)[0] + string_range_to_list(self.selection.value, shift=-1)[0], dtype=int ) # Only attempt to display the existing atoms. array_representation[selection[selection < len(structure)]] = 1 From 85dcd8384619aa55af5d781241f7a0609ae22207 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 1 Feb 2023 09:54:53 +0000 Subject: [PATCH 44/87] Remove atoms selection from non-default representations when new structure is imported. --- aiidalab_widgets_base/viewers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 26f4384b..39f07e02 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -1120,6 +1120,11 @@ def _observe_structure(self, change=None): uuid=uuid, indices=np.where(structure.arrays[self.uuid] >= 1.0)[0], ) + # Empty representations that are not present in the structure. + for i, uuid in enumerate(rep_uuids): + if uuid not in uuids: + self._all_representations[i].selection.value = "" + self._observe_supercell() # To trigger an update of the displayed structure self.set_trait("cell", structure.cell) else: From 3b0fdd1afc5befe0933c86514b2ceaad83ae12c3 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 1 Feb 2023 10:00:09 +0000 Subject: [PATCH 45/87] Small change to a comment. --- aiidalab_widgets_base/viewers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 39f07e02..2be55773 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -1120,7 +1120,8 @@ def _observe_structure(self, change=None): uuid=uuid, indices=np.where(structure.arrays[self.uuid] >= 1.0)[0], ) - # Empty representations that are not present in the structure. + # Empty atoms selection for the representations that are not present in the structure. + # Typically this happens when a new structure is imported. for i, uuid in enumerate(rep_uuids): if uuid not in uuids: self._all_representations[i].selection.value = "" From 76bc8e2b8dd35d0a913a07bc13ece1c00e96f97c Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 27 Sep 2023 16:08:53 +0000 Subject: [PATCH 46/87] Fix bugs. --- aiidalab_widgets_base/viewers.py | 93 ++++++++++++++++---------------- 1 file changed, 45 insertions(+), 48 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 602f9263..83f8c458 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -22,6 +22,15 @@ from .misc import CopyToClipboardButton, ReversePolishNotation from .utils import ase2spglib, list_to_string_range, string_range_to_list + +def list_to_nglview(the_list): + """Converts a list of structures to a nglview widget""" + selection = "none" + if len(the_list): + selection = "@" + ",".join(map(str, the_list)) + return selection + + AIIDA_VIEWER_MAPPING = {} @@ -244,6 +253,23 @@ def atoms_in_representaion(self, structure=None): return structure.arrays[self.uuid] >= self.array_threshold return False + def nglview_parameters(self, indices): + """Return the parameters dictionary of a representation.""" + params = { + "type": self.type.value, + "params": { + "sele": list_to_nglview(indices), + "opacity": 1, + "color": self.color.value, + }, + } + if self.type.value == "ball+stick": + params["params"]["aspectRatio"] = self.size.value + else: + params["params"]["radiusScale"] = 0.1 * self.size.value + + return params + class _StructureDataBaseViewer(ipw.VBox): """Base viewer class for AiiDA structure or trajectory objects. @@ -513,23 +539,6 @@ def _observe_all_representations(self, change): if change["new"]: self._all_representations[-1].master_class = self - def _get_representation_parameters(self, indices, representation): - """Return the parameters dictionary of a representation.""" - params = { - "type": representation.type.value, - "params": { - "sele": self._list_to_nglview(indices), - "opacity": 1, - "color": representation.color.value, - }, - } - if representation.type.value == "ball+stick": - params["params"]["aspectRatio"] = representation.size.value - else: - params["params"]["radiusScale"] = 0.1 * representation.size.value - - return params - def _apply_representations(self, change=None): """Apply the representations to the displayed structure.""" rep_uuids = [] @@ -899,13 +908,6 @@ def _on_atom_click(self, _=None): displayed_selection = [index] self.displayed_selection = displayed_selection - def _list_to_nglview(self, the_list): - """Converts a list of structures to a nglview widget""" - selection = "none" - if len(the_list): - selection = "@" + ",".join(map(str, the_list)) - return selection - def highlight_atoms( self, vis_list, @@ -929,9 +931,7 @@ def highlight_atoms( )[0], ) if len(indices) > 0: - params = self._get_representation_parameters( - indices, self._all_representations[i] - ) + params = representation.nglview_parameters(indices) params["params"]["name"] = f"highlight_representation_{i}" params["params"]["opacity"] = 0.3 params["params"]["component_index"] = 0 @@ -1086,26 +1086,24 @@ def _observe_supercell(self, _=None): def _valid_structure(self, change): """Update structure.""" structure = change["value"] - # If no structure is provided, the rest of the code can be skipped. - if structure is None: - return None # if no structure provided, the rest of the code can be skipped - if isinstance(structure, ase.Atoms): self.pk = None - return structure elif isinstance(structure, orm.Node): self.pk = structure.pk structure = structure.get_ase() - + elif structure: raise TypeError( f"Unsupported type {type(structure)}, structure must be one of the following types: " "ASE Atoms object, AiiDA CifData or StructureData." ) + + # Add default representation array if it is not present. + # This will make sure that the new structure is displayed at the beginning. if "_aiidalab_viewer_representation_default" not in structure.arrays: structure.set_array( "_aiidalab_viewer_representation_default", np.zeros(len(structure)) ) - return structure + return structure # This also includes the case when the structure is None. @tl.observe("structure") def _observe_structure(self, change=None): @@ -1117,13 +1115,14 @@ def _observe_structure(self, change=None): if structure: self.natoms = len(structure) # Make sure that the representation arrays from structure are present in the viewer. - uuids = [ + structure_uuids = [ uuid for uuid in structure.arrays if uuid.startswith("_aiidalab_viewer_representation_") ] rep_uuids = [rep.uuid for rep in self._all_representations] - for uuid in uuids: + + for uuid in structure_uuids: try: index = rep_uuids.index(uuid) self._all_representations[ @@ -1137,7 +1136,7 @@ def _observe_structure(self, change=None): # Empty atoms selection for the representations that are not present in the structure. # Typically this happens when a new structure is imported. for i, uuid in enumerate(rep_uuids): - if uuid not in uuids: + if uuid not in structure_uuids: self._all_representations[i].selection.value = "" self._observe_supercell() # To trigger an update of the displayed structure @@ -1158,18 +1157,16 @@ def _observe_displayed_structure(self, change): default_representation=False, name="Structure", ) - representation_parameters = [] - for representation in self._all_representations: - if not representation.show.value: - continue - indices = np.where( - representation.atoms_in_representaion(self.displayed_structure) - )[0] - representation_parameters.append( - self._get_representation_parameters(indices, representation) + nglview_params = [ + rep.nglview_parameters( + np.where(rep.atoms_in_representaion(self.displayed_structure))[ + 0 + ] ) - - self._viewer.set_representations(representation_parameters, component=0) + for rep in self._all_representations + if rep.show.value + ] + self._viewer.set_representations(nglview_params, component=0) self._viewer.add_unitcell() self._viewer.center() self.displayed_selection = [] From 0ab456d6854c15a7a191e16db20991c40e80bc82 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Thu, 28 Sep 2023 13:36:28 +0000 Subject: [PATCH 47/87] Fix bug for the editor. --- aiidalab_widgets_base/structures.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index f8b21c80..6647df79 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -1,5 +1,4 @@ """Module to provide functionality to import structures.""" - import datetime import functools import io @@ -182,6 +181,10 @@ def _structure_editors(self, editors): for i, editor in enumerate(editors): editors_tab.set_title(i, editor.title) tl.link((editor, "structure"), (self, "structure")) + if editor.has_trait("input_selection"): + tl.dlink( + (editor, "input_selection"), (self.viewer, "input_selection") + ) if editor.has_trait("selection"): tl.link((editor, "selection"), (self.viewer, "selection")) if editor.has_trait("camera_orientation"): @@ -1288,6 +1291,8 @@ def translate_dr(self, _=None, atoms=None, selection=None): self.action_vector * self.displacement.value ) + self.input_selection = None # Clear selection. + self.structure, self.input_selection = atoms, selection @_register_structure @@ -1297,7 +1302,7 @@ def translate_dxdydz(self, _=None, atoms=None, selection=None): # The action. atoms.positions[self.selection] += np.array(self.str2vec(self.dxyz.value)) - + self.input_selection = None # Clear selection. self.structure, self.input_selection = atoms, selection @_register_structure @@ -1307,7 +1312,7 @@ def translate_to_xyz(self, _=None, atoms=None, selection=None): # The action. geo_center = np.average(self.structure[self.selection].get_positions(), axis=0) atoms.positions[self.selection] += self.str2vec(self.dxyz.value) - geo_center - + self.input_selection = None # Clear selection. self.structure, self.input_selection = atoms, selection @_register_structure @@ -1321,6 +1326,7 @@ def rotate(self, _=None, atoms=None, selection=None): center = self.str2vec(self.point.value) rotated_subset.rotate(self.phi.value, v=vec, center=center, rotate_cell=False) atoms.positions[self.selection] = rotated_subset.positions + self.input_selection = None # Clear selection. self.structure, self.input_selection = atoms, selection @@ -1355,6 +1361,8 @@ def mirror(self, _=None, norm=None, point=None, atoms=None, selection=None): # Mirror atoms. atoms.positions[selection] -= 2 * projections + self.input_selection = None # Clear selection. + self.structure, self.input_selection = atoms, selection def mirror_3p(self, _=None): @@ -1417,6 +1425,7 @@ def mod_element(self, _=None, atoms=None, selection=None): @_register_selection def copy_sel(self, _=None, atoms=None, selection=None): """Copy selected atoms and shift by 1.0 A along X-axis.""" + last_atom = atoms.get_global_number_of_atoms() # The action From bb3a53636619c30bcc4dbde7ff381e8ad7ee58f6 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Sun, 8 Oct 2023 20:51:59 +0000 Subject: [PATCH 48/87] Fix tests. --- aiidalab_widgets_base/viewers.py | 3 ++- notebooks/aiida_datatypes_viewers.ipynb | 16 +++++++++------- tests/test_viewers.py | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 83f8c458..77172689 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -1070,8 +1070,9 @@ class StructureDataViewer(_StructureDataBaseViewer): displayed_structure = tl.Instance(ase.Atoms, allow_none=True, read_only=True) pk = tl.Int(allow_none=True) - def __init__(self, **kwargs): + def __init__(self, structure=None, **kwargs): super().__init__(**kwargs) + self.structure = structure self.natoms = len(self.structure) if self.structure else 0 @tl.observe("supercell") diff --git a/notebooks/aiida_datatypes_viewers.ipynb b/notebooks/aiida_datatypes_viewers.ipynb index deb647a1..199f3353 100644 --- a/notebooks/aiida_datatypes_viewers.ipynb +++ b/notebooks/aiida_datatypes_viewers.ipynb @@ -1,24 +1,26 @@ { "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Displaying AiiDA data types" - ] - }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ + "from aiida import load_profile\n", + "load_profile()\n", "import io\n", "from os import path\n", "from aiida.plugins import DataFactory\n", "from aiidalab_widgets_base import viewer" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Displaying AiiDA data types" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/tests/test_viewers.py b/tests/test_viewers.py index b5c9287d..86c80e97 100644 --- a/tests/test_viewers.py +++ b/tests/test_viewers.py @@ -89,7 +89,7 @@ def test_structure_data_viwer(structure_data_object): format_cases = [ ( "Extended xyz", - """MgpMYXR0aWNlPSIzLjg0NzM3IDAuMCAwLjAgMS45MjM2ODUgMy4zMzE5MiAwLjAgMS45MjM2ODUgMS4xMTA2NCAzLjE0MTM2NCIgUHJvcGVydGllcz1zcGVjaWVzOlM6MTpwb3M6UjozOm1hc3NlczpSOjEgcGJjPSJUIFQgVCIKU2kgICAgICAgMC4wMDAwMDAwMCAgICAgICAwLjAwMDAwMDAwICAgICAgIDAuMDAwMDAwMDAgICAgICAyOC4wODU1MDAwMApTaSAgICAgICAxLjkyMzY4NTAwICAgICAgIDEuMTEwNjQwMDAgICAgICAgMC43ODUzNDEwMCAgICAgIDI4LjA4NTUwMDAwCg==""", + """MgpMYXR0aWNlPSIzLjg0NzM3IDAuMCAwLjAgMS45MjM2ODUgMy4zMzE5MiAwLjAgMS45MjM2ODUgMS4xMTA2NCAzLjE0MTM2NCIgUHJvcGVydGllcz1zcGVjaWVzOlM6MTpwb3M6UjozOm1hc3NlczpSOjE6X2FpaWRhbGFiX3ZpZXdlcl9yZXByZXNlbnRhdGlvbl9kZWZhdWx0OlI6MSBwYmM9IlQgVCBUIgpTaSAgICAgICAwLjAwMDAwMDAwICAgICAgIDAuMDAwMDAwMDAgICAgICAgMC4wMDAwMDAwMCAgICAgIDI4LjA4NTUwMDAwICAgICAgIDAuMDAwMDAwMDAKU2kgICAgICAgMS45MjM2ODUwMCAgICAgICAxLjExMDY0MDAwICAgICAgIDAuNzg1MzQxMDAgICAgICAyOC4wODU1MDAwMCAgICAgICAwLjAwMDAwMDAwCg==""", ), ( "xsf", From 5f190ec2f5e09fd7d4115f7fd74ffb17a203e703 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 9 Oct 2023 08:30:28 +0000 Subject: [PATCH 49/87] Add tests for the viewer. --- tests/test_viewers.py | 73 ++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/tests/test_viewers.py b/tests/test_viewers.py index 86c80e97..81199b89 100644 --- a/tests/test_viewers.py +++ b/tests/test_viewers.py @@ -63,17 +63,39 @@ def test_structure_data_viwer(structure_data_object): v = viewers.viewer(structure_data_object) assert isinstance(v, viewers.StructureDataViewer) + # Check the `_prepare_payload` function used for downloading. + format_cases = [ + ( + "Extended xyz", + """MgpMYXR0aWNlPSIzLjg0NzM3IDAuMCAwLjAgMS45MjM2ODUgMy4zMzE5MiAwLjAgMS45MjM2ODUgMS4xMTA2NCAzLjE0MTM2NCIgUHJvcGVydGllcz1zcGVjaWVzOlM6MTpwb3M6UjozOm1hc3NlczpSOjE6X2FpaWRhbGFiX3ZpZXdlcl9yZXByZXNlbnRhdGlvbl9kZWZhdWx0OlI6MSBwYmM9IlQgVCBUIgpTaSAgICAgICAwLjAwMDAwMDAwICAgICAgIDAuMDAwMDAwMDAgICAgICAgMC4wMDAwMDAwMCAgICAgIDI4LjA4NTUwMDAwICAgICAgIDAuMDAwMDAwMDAKU2kgICAgICAgMS45MjM2ODUwMCAgICAgICAxLjExMDY0MDAwICAgICAgIDAuNzg1MzQxMDAgICAgICAyOC4wODU1MDAwMCAgICAgICAwLjAwMDAwMDAwCg==""", + ), + ( + "xsf", + """Q1JZU1RBTApQUklNVkVDCiAzLjg0NzM3MDAwMDAwMDAwIDAuMDAwMDAwMDAwMDAwMDAgMC4wMDAwMDAwMDAwMDAwMAogMS45MjM2ODUwMDAwMDAwMCAzLjMzMTkyMDAwMDAwMDAwIDAuMDAwMDAwMDAwMDAwMDAKIDEuOTIzNjg1MDAwMDAwMDAgMS4xMTA2NDAwMDAwMDAwMCAzLjE0MTM2NDAwMDAwMDAwClBSSU1DT09SRAogMiAxCiAxNCAgICAgMC4wMDAwMDAwMDAwMDAwMCAgICAgMC4wMDAwMDAwMDAwMDAwMCAgICAgMC4wMDAwMDAwMDAwMDAwMAogMTQgICAgIDEuOTIzNjg1MDAwMDAwMDAgICAgIDEuMTEwNjQwMDAwMDAwMDAgICAgIDAuNzg1MzQxMDAwMDAwMDAK""", + ), + ( + "cif", + """ZGF0YV9pbWFnZTAKX2NoZW1pY2FsX2Zvcm11bGFfc3RydWN0dXJhbCAgICAgICBTaTIKX2NoZW1pY2FsX2Zvcm11bGFfc3VtICAgICAgICAgICAgICAiU2kyIgpfY2VsbF9sZW5ndGhfYSAgICAgICAzLjg0NzM3Cl9jZWxsX2xlbmd0aF9iICAgICAgIDMuODQ3MzcKX2NlbGxfbGVuZ3RoX2MgICAgICAgMy44NDczNwpfY2VsbF9hbmdsZV9hbHBoYSAgICA2MApfY2VsbF9hbmdsZV9iZXRhICAgICA2MApfY2VsbF9hbmdsZV9nYW1tYSAgICA2MAoKX3NwYWNlX2dyb3VwX25hbWVfSC1NX2FsdCAgICAiUCAxIgpfc3BhY2VfZ3JvdXBfSVRfbnVtYmVyICAgICAgIDEKCmxvb3BfCiAgX3NwYWNlX2dyb3VwX3N5bW9wX29wZXJhdGlvbl94eXoKICAneCwgeSwgeicKCmxvb3BfCiAgX2F0b21fc2l0ZV90eXBlX3N5bWJvbAogIF9hdG9tX3NpdGVfbGFiZWwKICBfYXRvbV9zaXRlX3N5bW1ldHJ5X211bHRpcGxpY2l0eQogIF9hdG9tX3NpdGVfZnJhY3RfeAogIF9hdG9tX3NpdGVfZnJhY3RfeQogIF9hdG9tX3NpdGVfZnJhY3RfegogIF9hdG9tX3NpdGVfb2NjdXBhbmN5CiAgU2kgIFNpMSAgICAgICAxLjAgIDAuMDAwMDAgIDAuMDAwMDAgIDAuMDAwMDAgIDEuMDAwMAogIFNpICBTaTIgICAgICAgMS4wICAwLjI1MDAwICAwLjI1MDAwICAwLjI1MDAwICAxLjAwMDAK""", + ), + ] + + for fmt, out in format_cases: + v.file_format.label = fmt + assert v._prepare_payload() == out + # Selection of atoms. # Direct selection. v._selected_atoms.value = "1..2" v.apply_displayed_selection() assert v.selection == [0, 1] + assert v.displayed_selection == [0, 1] # The x coordinate lower than 0.5. v._selected_atoms.value = "x<0.5" v.apply_displayed_selection() assert v.selection == [0] + assert v.displayed_selection == [0] # The id of the second atom v._selected_atoms.value = "id > 1" @@ -85,22 +107,37 @@ def test_structure_data_viwer(structure_data_object): v.apply_displayed_selection() assert v.selection == [0, 1] - # Check the `_prepare_payload` function used for downloading. - format_cases = [ - ( - "Extended xyz", - """MgpMYXR0aWNlPSIzLjg0NzM3IDAuMCAwLjAgMS45MjM2ODUgMy4zMzE5MiAwLjAgMS45MjM2ODUgMS4xMTA2NCAzLjE0MTM2NCIgUHJvcGVydGllcz1zcGVjaWVzOlM6MTpwb3M6UjozOm1hc3NlczpSOjE6X2FpaWRhbGFiX3ZpZXdlcl9yZXByZXNlbnRhdGlvbl9kZWZhdWx0OlI6MSBwYmM9IlQgVCBUIgpTaSAgICAgICAwLjAwMDAwMDAwICAgICAgIDAuMDAwMDAwMDAgICAgICAgMC4wMDAwMDAwMCAgICAgIDI4LjA4NTUwMDAwICAgICAgIDAuMDAwMDAwMDAKU2kgICAgICAgMS45MjM2ODUwMCAgICAgICAxLjExMDY0MDAwICAgICAgIDAuNzg1MzQxMDAgICAgICAyOC4wODU1MDAwMCAgICAgICAwLjAwMDAwMDAwCg==""", - ), - ( - "xsf", - """Q1JZU1RBTApQUklNVkVDCiAzLjg0NzM3MDAwMDAwMDAwIDAuMDAwMDAwMDAwMDAwMDAgMC4wMDAwMDAwMDAwMDAwMAogMS45MjM2ODUwMDAwMDAwMCAzLjMzMTkyMDAwMDAwMDAwIDAuMDAwMDAwMDAwMDAwMDAKIDEuOTIzNjg1MDAwMDAwMDAgMS4xMTA2NDAwMDAwMDAwMCAzLjE0MTM2NDAwMDAwMDAwClBSSU1DT09SRAogMiAxCiAxNCAgICAgMC4wMDAwMDAwMDAwMDAwMCAgICAgMC4wMDAwMDAwMDAwMDAwMCAgICAgMC4wMDAwMDAwMDAwMDAwMAogMTQgICAgIDEuOTIzNjg1MDAwMDAwMDAgICAgIDEuMTEwNjQwMDAwMDAwMDAgICAgIDAuNzg1MzQxMDAwMDAwMDAK""", - ), - ( - "cif", - """ZGF0YV9pbWFnZTAKX2NoZW1pY2FsX2Zvcm11bGFfc3RydWN0dXJhbCAgICAgICBTaTIKX2NoZW1pY2FsX2Zvcm11bGFfc3VtICAgICAgICAgICAgICAiU2kyIgpfY2VsbF9sZW5ndGhfYSAgICAgICAzLjg0NzM3Cl9jZWxsX2xlbmd0aF9iICAgICAgIDMuODQ3MzcKX2NlbGxfbGVuZ3RoX2MgICAgICAgMy44NDczNwpfY2VsbF9hbmdsZV9hbHBoYSAgICA2MApfY2VsbF9hbmdsZV9iZXRhICAgICA2MApfY2VsbF9hbmdsZV9nYW1tYSAgICA2MAoKX3NwYWNlX2dyb3VwX25hbWVfSC1NX2FsdCAgICAiUCAxIgpfc3BhY2VfZ3JvdXBfSVRfbnVtYmVyICAgICAgIDEKCmxvb3BfCiAgX3NwYWNlX2dyb3VwX3N5bW9wX29wZXJhdGlvbl94eXoKICAneCwgeSwgeicKCmxvb3BfCiAgX2F0b21fc2l0ZV90eXBlX3N5bWJvbAogIF9hdG9tX3NpdGVfbGFiZWwKICBfYXRvbV9zaXRlX3N5bW1ldHJ5X211bHRpcGxpY2l0eQogIF9hdG9tX3NpdGVfZnJhY3RfeAogIF9hdG9tX3NpdGVfZnJhY3RfeQogIF9hdG9tX3NpdGVfZnJhY3RfegogIF9hdG9tX3NpdGVfb2NjdXBhbmN5CiAgU2kgIFNpMSAgICAgICAxLjAgIDAuMDAwMDAgIDAuMDAwMDAgIDAuMDAwMDAgIDEuMDAwMAogIFNpICBTaTIgICAgICAgMS4wICAwLjI1MDAwICAwLjI1MDAwICAwLjI1MDAwICAxLjAwMDAK""", - ), - ] + # Display 2*2*2 supercell + v.supercell = [2, 2, 2] + assert len(v.structure) == 2 + assert len(v.displayed_structure) == 16 - for fmt, out in format_cases: - v.file_format.label = fmt - assert v._prepare_payload() == out + # Test intersection of the selection with the supercell. + v._selected_atoms.value = "z>0 and z<2.5" + assert v.selection == [2] + assert v.displayed_selection == [2, 6, 10, 14] + + v._selected_atoms.value = "z>=0 and z<2.5" + assert v.selection == [0, 1] + assert v.displayed_selection == [0, 1, 4, 5, 8, 9, 12, 13] + + # Convert to boron nitride. + new_structure = v.structure.copy() + new_structure.symbos = ["B", "N"] + v.structure = None + v.structure = new_structure + + # Use "name" operator. + v._selected_atoms.value = "z<2 and name B" + assert v.selection == [1] + assert v.displayed_selection == [1, 5, 9, 13] + + # Use "id" operator. + v._selected_atoms.value = "id == 1 or id == 8" + assert v.selection == [1, 2] + assert v.displayed_selection == [1, 8] + + # Use the d_from operator. + v._selected_atoms.value = "d_from[0,0,0] < 4" + assert v.selection == [1, 2] + assert v.displayed_selection == [4, 8, 0, 1, 2] From 5ac5e79396f03ffbd1cf602b8a7e170266190ad7 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 9 Oct 2023 09:06:47 +0000 Subject: [PATCH 50/87] Fix tests --- tests/test_viewers.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/test_viewers.py b/tests/test_viewers.py index 81199b89..fddc35e4 100644 --- a/tests/test_viewers.py +++ b/tests/test_viewers.py @@ -114,30 +114,35 @@ def test_structure_data_viwer(structure_data_object): # Test intersection of the selection with the supercell. v._selected_atoms.value = "z>0 and z<2.5" - assert v.selection == [2] - assert v.displayed_selection == [2, 6, 10, 14] + v.apply_displayed_selection() + assert v.selection == [1] + assert v.displayed_selection == [1, 5, 9, 13] v._selected_atoms.value = "z>=0 and z<2.5" + v.apply_displayed_selection() assert v.selection == [0, 1] assert v.displayed_selection == [0, 1, 4, 5, 8, 9, 12, 13] # Convert to boron nitride. new_structure = v.structure.copy() - new_structure.symbos = ["B", "N"] + new_structure.symbols = ["B", "N"] v.structure = None v.structure = new_structure # Use "name" operator. v._selected_atoms.value = "z<2 and name B" - assert v.selection == [1] - assert v.displayed_selection == [1, 5, 9, 13] + v.apply_displayed_selection() + assert v.selection == [0] + assert v.displayed_selection == [0, 4, 8, 12] # Use "id" operator. v._selected_atoms.value = "id == 1 or id == 8" - assert v.selection == [1, 2] - assert v.displayed_selection == [1, 8] + v.apply_displayed_selection() + assert v.selection == [0, 1] + assert v.displayed_selection == [0, 7] # Use the d_from operator. v._selected_atoms.value = "d_from[0,0,0] < 4" - assert v.selection == [1, 2] + v.apply_displayed_selection() + assert v.selection == [0, 1] assert v.displayed_selection == [4, 8, 0, 1, 2] From 2d2efee26e762ab4ea0d37ab2847bd255f3fed1c Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 9 Oct 2023 09:25:22 +0000 Subject: [PATCH 51/87] Test 3 atoms selection. --- tests/test_viewers.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_viewers.py b/tests/test_viewers.py index fddc35e4..26d6e4fa 100644 --- a/tests/test_viewers.py +++ b/tests/test_viewers.py @@ -90,12 +90,15 @@ def test_structure_data_viwer(structure_data_object): v.apply_displayed_selection() assert v.selection == [0, 1] assert v.displayed_selection == [0, 1] + assert "Distance" in v.selection_info.value + assert "2 atoms selected" in v.selection_info.value # The x coordinate lower than 0.5. v._selected_atoms.value = "x<0.5" v.apply_displayed_selection() assert v.selection == [0] assert v.displayed_selection == [0] + assert "1 atoms selected" in v.selection_info.value # The id of the second atom v._selected_atoms.value = "id > 1" @@ -118,10 +121,12 @@ def test_structure_data_viwer(structure_data_object): assert v.selection == [1] assert v.displayed_selection == [1, 5, 9, 13] - v._selected_atoms.value = "z>=0 and z<2.5" + v._selected_atoms.value = "x<=2.0 and z<3" v.apply_displayed_selection() assert v.selection == [0, 1] - assert v.displayed_selection == [0, 1, 4, 5, 8, 9, 12, 13] + assert v.displayed_selection == [4, 0, 1] + assert "Angle" in v.selection_info.value + assert "3 atoms selected" in v.selection_info.value # Convert to boron nitride. new_structure = v.structure.copy() From 263d9668a7c7b152462d0565c036a47504dbc651 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 9 Oct 2023 09:38:55 +0000 Subject: [PATCH 52/87] More tests. --- tests/test_viewers.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_viewers.py b/tests/test_viewers.py index 26d6e4fa..a456eb6e 100644 --- a/tests/test_viewers.py +++ b/tests/test_viewers.py @@ -151,3 +151,15 @@ def test_structure_data_viwer(structure_data_object): v.apply_displayed_selection() assert v.selection == [0, 1] assert v.displayed_selection == [4, 8, 0, 1, 2] + + # Use the != operator. + v._selected_atoms.value = "id != 5" + v.apply_displayed_selection() + assert v.selection == [0, 1] + assert v.displayed_selection == [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + + # Use ^ and - operators. + v._selected_atoms.value = "(x-4)^2 + (y-2)^2 < 4" + v.apply_displayed_selection() + assert v.selection == [1, 0] + assert v.displayed_selection == [3, 9, 10] From 8f0559d200ed0742e6a23138404eba7252bf6bd7 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 9 Oct 2023 10:04:33 +0000 Subject: [PATCH 53/87] More tests. --- tests/test_viewers.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/test_viewers.py b/tests/test_viewers.py index a456eb6e..8361bcb7 100644 --- a/tests/test_viewers.py +++ b/tests/test_viewers.py @@ -106,7 +106,7 @@ def test_structure_data_viwer(structure_data_object): assert v.selection == [1] # or of the two selections. - v._selected_atoms.value = "x>0.5 or x<0.5" + v._selected_atoms.value = "x>=0.5 or x<0.5" v.apply_displayed_selection() assert v.selection == [0, 1] @@ -134,12 +134,22 @@ def test_structure_data_viwer(structure_data_object): v.structure = None v.structure = new_structure - # Use "name" operator. + # Use "name" and "not" operators. v._selected_atoms.value = "z<2 and name B" v.apply_displayed_selection() assert v.selection == [0] assert v.displayed_selection == [0, 4, 8, 12] + v._selected_atoms.value = "z<2 and name not B" + v.apply_displayed_selection() + assert v.selection == [1] + assert v.displayed_selection == [1, 5, 9, 13] + + v._selected_atoms.value = "z<2 and name not [B, O]" + v.apply_displayed_selection() + assert v.selection == [1] + assert v.displayed_selection == [1, 5, 9, 13] + # Use "id" operator. v._selected_atoms.value = "id == 1 or id == 8" v.apply_displayed_selection() From 87469391a8e27ef7f83ef73f4d53a1292764c583 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 9 Oct 2023 11:17:01 +0000 Subject: [PATCH 54/87] More tests. --- aiidalab_widgets_base/viewers.py | 14 +++++++------- tests/test_viewers.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 77172689..5cc01abe 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -1065,7 +1065,12 @@ class StructureDataViewer(_StructureDataBaseViewer): """ structure = tl.Union( - [tl.Instance(ase.Atoms), tl.Instance(orm.Node)], allow_none=True + [ + tl.Instance(ase.Atoms), + tl.Instance(orm.StructureData), + tl.Instance(orm.CifData), + ], + allow_none=True, ) displayed_structure = tl.Instance(ase.Atoms, allow_none=True, read_only=True) pk = tl.Int(allow_none=True) @@ -1089,14 +1094,9 @@ def _valid_structure(self, change): structure = change["value"] if isinstance(structure, ase.Atoms): self.pk = None - elif isinstance(structure, orm.Node): + elif isinstance(structure, (orm.StructureData, orm.CifData)): self.pk = structure.pk structure = structure.get_ase() - elif structure: - raise TypeError( - f"Unsupported type {type(structure)}, structure must be one of the following types: " - "ASE Atoms object, AiiDA CifData or StructureData." - ) # Add default representation array if it is not present. # This will make sure that the new structure is displayed at the beginning. diff --git a/tests/test_viewers.py b/tests/test_viewers.py index 8361bcb7..fb90aeb6 100644 --- a/tests/test_viewers.py +++ b/tests/test_viewers.py @@ -1,4 +1,5 @@ import pytest +import traitlets as tl from aiida import orm from aiidalab_widgets_base import viewers @@ -173,3 +174,21 @@ def test_structure_data_viwer(structure_data_object): v.apply_displayed_selection() assert v.selection == [1, 0] assert v.displayed_selection == [3, 9, 10] + + # Division and multiplication. + v._selected_atoms.value = "x/2 < 1" + v.apply_displayed_selection() + assert v.selection == [0, 1] + assert v.displayed_selection == [4, 0, 1, 2] + + v._selected_atoms.value = "x*1.5 < y + z" + v.apply_displayed_selection() + assert v.selection == [0, 1] + assert v.displayed_selection == [2, 3, 4, 6, 7] + + # Try to provide different object type than the viewer accepts. + with pytest.raises(tl.TraitError): + v.structure = 2 + + with pytest.raises(tl.TraitError): + v.structure = orm.Int(1) From 43a6776b886bc6b418f368112431b6e0e84c04bc Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 9 Oct 2023 11:49:07 +0000 Subject: [PATCH 55/87] Add tests for the representations. --- tests/test_viewers.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_viewers.py b/tests/test_viewers.py index fb90aeb6..db0272ce 100644 --- a/tests/test_viewers.py +++ b/tests/test_viewers.py @@ -186,6 +186,38 @@ def test_structure_data_viwer(structure_data_object): assert v.selection == [0, 1] assert v.displayed_selection == [2, 3, 4, 6, 7] + # Test wrong syntax. + assert v.wrong_syntax.layout.visibility == "hidden" + v._selected_atoms.value = "x--x" + v.apply_displayed_selection() + assert v.wrong_syntax.layout.visibility == "visible" + + # Test representations. + + # By default, there should be one "default" representation. + assert len(v._all_representations) == 1 + assert v._all_representations[0].uuid == "_aiidalab_viewer_representation_default" + assert v._all_representations[0].selection.value == "1..2" + + # Display only one atom. + v._all_representations[0].selection.value = "1" + v._apply_representations() + assert "2" in v.atoms_not_represented.value + + # Add a new representation. + v._add_representation() + assert "2" in v.atoms_not_represented.value + v._all_representations[1].selection.value = "2" + v._apply_representations() + assert v.atoms_not_represented.value == "" + + # Delete the second representation. + assert v._all_representations[0].delete_button.layout.visibility == "hidden" + assert v._all_representations[1].delete_button.layout.visibility == "visible" + v._all_representations[1].delete_button.click() + assert len(v._all_representations) == 1 + assert "2" in v.atoms_not_represented.value + # Try to provide different object type than the viewer accepts. with pytest.raises(tl.TraitError): v.structure = 2 From ec9b55319c9a92a12366f35dbbe507b89d7949ac Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 9 Oct 2023 12:11:34 +0000 Subject: [PATCH 56/87] Slightly improve datatypes viewers notebook. --- notebooks/aiida_datatypes_viewers.ipynb | 40 ++++++++----------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/notebooks/aiida_datatypes_viewers.ipynb b/notebooks/aiida_datatypes_viewers.ipynb index 199f3353..178aa557 100644 --- a/notebooks/aiida_datatypes_viewers.ipynb +++ b/notebooks/aiida_datatypes_viewers.ipynb @@ -10,7 +10,8 @@ "load_profile()\n", "import io\n", "from os import path\n", - "from aiida.plugins import DataFactory\n", + "from ase import build\n", + "from aiida import engine, orm, plugins\n", "from aiidalab_widgets_base import viewer" ] }, @@ -34,7 +35,7 @@ "metadata": {}, "outputs": [], "source": [ - "Dict = DataFactory('core.dict')\n", + "Dict = plugins.DataFactory('core.dict')\n", "p = Dict(dict={\n", " 'Parameter' :'super long string '*4,\n", " 'parameter 2' :'value 2',\n", @@ -52,12 +53,11 @@ "outputs": [], "source": [ "# create molecule\n", - "from ase.build import molecule, bulk\n", - "m = molecule('H2O')\n", + "m = build.molecule('H2O')\n", "m.center(vacuum=2.0)\n", "#\n", "# create bulk Pt\n", - "pt = bulk('Pt', cubic = True)" + "pt = build.bulk('Pt', cubic = True)" ] }, { @@ -73,7 +73,7 @@ "metadata": {}, "outputs": [], "source": [ - "CifData = DataFactory('core.cif')\n", + "CifData = plugins.DataFactory('core.cif')\n", "s = CifData(ase=pt)\n", "vwr = viewer(s.store(), configuration_tabs=['Selection', 'Appearance', 'Cell', 'Download'])\n", "display(vwr)" @@ -92,7 +92,7 @@ "metadata": {}, "outputs": [], "source": [ - "StructureData = DataFactory('core.structure')\n", + "StructureData = plugins.DataFactory('core.structure')\n", "s = StructureData(ase=m)\n", "vwr = viewer(s.store())\n", "display(vwr)" @@ -112,7 +112,7 @@ "outputs": [], "source": [ "import numpy as np\n", - "BandsData = DataFactory('core.array.bands')\n", + "BandsData = plugins.DataFactory('core.array.bands')\n", "bs = BandsData()\n", "kpoints = np.array([[0. , 0. , 0. ], # array shape is 12 * 3\n", " [0.1 , 0. , 0.1 ],\n", @@ -164,7 +164,7 @@ "metadata": {}, "outputs": [], "source": [ - "FolderData = DataFactory('core.folder')\n", + "FolderData = plugins.DataFactory('core.folder')\n", "fd = FolderData()\n", "with io.StringIO('content of test1 filelike') as fobj:\n", " fd.put_object_from_filelike(fobj, path='test1.txt')\n", @@ -189,9 +189,7 @@ "metadata": {}, "outputs": [], "source": [ - "from aiida.workflows.arithmetic.add_multiply import add, add_multiply\n", - "from aiida.engine import run_get_node\n", - "from aiida.orm import Int" + "from aiida.workflows.arithmetic.add_multiply import add, add_multiply" ] }, { @@ -200,7 +198,7 @@ "metadata": {}, "outputs": [], "source": [ - "result, workfunction = run_get_node(add_multiply, Int(3), Int(4), Int(5))\n", + "result, workfunction = engine.run_get_node(add_multiply, orm.Int(3), orm.Int(4), orm.Int(5))\n", "display(viewer(workfunction))" ] }, @@ -210,23 +208,9 @@ "metadata": {}, "outputs": [], "source": [ - "result, calcfunction = run_get_node(add, Int(3), Int(4))\n", + "result, calcfunction = engine.run_get_node(add, Int(3), Int(4))\n", "display(viewer(calcfunction))" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From 38d19d3d67ef10450a3ad90c537b767bba01ccde Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 10 Oct 2023 07:57:51 +0000 Subject: [PATCH 57/87] More tests. --- tests/test_structures.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/test_structures.py b/tests/test_structures.py index 4dba080b..5465258f 100644 --- a/tests/test_structures.py +++ b/tests/test_structures.py @@ -17,8 +17,8 @@ def test_structure_manager_widget(structure_data_object): editors=[ awb.BasicStructureEditor(title="Basic Editor"), ], + input_structure=structure_data_object, ) - structure_manager_widget.input_structure = structure_data_object assert structure_manager_widget.structure is not None assert isinstance(structure_manager_widget.structure, ase.Atoms) @@ -31,6 +31,10 @@ def test_structure_manager_widget(structure_data_object): assert structure_manager_widget.structure_node.is_stored assert structure_manager_widget.structure_node.pk is not None + # Try to store the stored structure. + structure_manager_widget.btn_store.click() + assert "Already stored" in structure_manager_widget.output.value + # Simulate the structure modification. new_structure = structure_manager_widget.structure.copy() new_structure[0].position += [0, 0, 1] @@ -47,11 +51,18 @@ def test_structure_manager_widget(structure_data_object): structure_manager_widget.structure[0].position != new_structure[0].position ) - # test the widget can be instantiated with empty inputs + # Test the widget with multiple importers, editors. Specify the viewer and the node class structure_manager_widget = awb.StructureManagerWidget( importers=[ awb.StructureUploadWidget(title="From computer"), + awb.StructureBrowserWidget(title="AiiDA database"), + ], + editors=[ + awb.BasicStructureEditor(title="Basic Editor"), + awb.BasicCellEditor(title="Cell Editor"), ], + viewer=awb.viewers.StructureDataViewer(), + node_class="StructureData", ) assert structure_manager_widget.structure is None From f0335822699f462c5eb68033fd3d9cd78cbabbba Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 10 Oct 2023 08:44:25 +0000 Subject: [PATCH 58/87] More tests. --- tests/test_structures.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_structures.py b/tests/test_structures.py index 5465258f..f485a472 100644 --- a/tests/test_structures.py +++ b/tests/test_structures.py @@ -27,7 +27,7 @@ def test_structure_manager_widget(structure_data_object): assert structure_manager_widget.viewer.periodicity.value == "Periodicity: xyz" # Store structure and check that it is stored. - structure_manager_widget.store_structure() + structure_manager_widget.btn_store.click() assert structure_manager_widget.structure_node.is_stored assert structure_manager_widget.structure_node.pk is not None @@ -45,11 +45,17 @@ def test_structure_manager_widget(structure_data_object): structure_manager_widget.structure[0].position == new_structure[0].position ) + # Store the modified structure. + structure_manager_widget.btn_store.click() + assert structure_manager_widget.structure_node.is_stored + # Undo the structure modification. structure_manager_widget.undo() assert np.any( structure_manager_widget.structure[0].position != new_structure[0].position ) + structure_manager_widget.undo() # Undo the structure creation. + assert structure_manager_widget.structure is None # Test the widget with multiple importers, editors. Specify the viewer and the node class structure_manager_widget = awb.StructureManagerWidget( From c08712ed23ea0d243fb2460a32f925465fa30f8e Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 10 Oct 2023 09:41:15 +0000 Subject: [PATCH 59/87] More tests. --- tests/test_structures.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_structures.py b/tests/test_structures.py index f485a472..e4a769f1 100644 --- a/tests/test_structures.py +++ b/tests/test_structures.py @@ -48,6 +48,7 @@ def test_structure_manager_widget(structure_data_object): # Store the modified structure. structure_manager_widget.btn_store.click() assert structure_manager_widget.structure_node.is_stored + stored_structure = structure_manager_widget.structure_node # Undo the structure modification. structure_manager_widget.undo() @@ -72,6 +73,7 @@ def test_structure_manager_widget(structure_data_object): ) assert structure_manager_widget.structure is None + structure_manager_widget.input_structure = stored_structure @pytest.mark.usefixtures("aiida_profile_clean") From 37639fbc24dac118ec4ff30e71aefdc6fa306d30 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 10 Oct 2023 10:07:03 +0000 Subject: [PATCH 60/87] More tests. --- aiidalab_widgets_base/structures.py | 4 ++-- tests/test_structures.py | 21 ++++++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index f8b4bb6f..50f3f519 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -14,7 +14,7 @@ from aiida import engine, orm, plugins # Local imports -from .data import LigandSelectorWidget +from .data import FunctionalGroupSelectorWidget from .utils import StatusHTML, exceptions, get_ase_from_file, get_formula from .viewers import StructureDataViewer @@ -1055,7 +1055,7 @@ def disable_element(_=None): self.element.disabled = True # Ligand selection. - self.ligand = LigandSelectorWidget() + self.ligand = FunctionalGroupSelectorWidget() self.ligand.observe(disable_element, names="value") # Add atom. diff --git a/tests/test_structures.py b/tests/test_structures.py index e4a769f1..a48f78aa 100644 --- a/tests/test_structures.py +++ b/tests/test_structures.py @@ -59,13 +59,14 @@ def test_structure_manager_widget(structure_data_object): assert structure_manager_widget.structure is None # Test the widget with multiple importers, editors. Specify the viewer and the node class + base_editor = awb.BasicStructureEditor(title="Basic Editor") structure_manager_widget = awb.StructureManagerWidget( importers=[ awb.StructureUploadWidget(title="From computer"), awb.StructureBrowserWidget(title="AiiDA database"), ], editors=[ - awb.BasicStructureEditor(title="Basic Editor"), + base_editor, awb.BasicCellEditor(title="Cell Editor"), ], viewer=awb.viewers.StructureDataViewer(), @@ -75,6 +76,9 @@ def test_structure_manager_widget(structure_data_object): assert structure_manager_widget.structure is None structure_manager_widget.input_structure = stored_structure + # Set action vector perpendicular to screen. + base_editor.def_perpendicular_to_screen() + @pytest.mark.usefixtures("aiida_profile_clean") def test_structure_browser_widget(structure_data_object): @@ -241,12 +245,16 @@ def test_basic_structure_editor(structure_data_object): # Set the structure. widget.structure = structure_data_object.get_ase() - # Set first action point vector to the first atom. + # Set first vector point vector to the first atom. widget.selection = [0] widget.def_axis_p1() assert widget.axis_p1.value == "0.0 0.0 0.0" - # Set second action point vector to the second atom. + # Set action point to the first atom. + widget.def_point() + assert widget.point.value == "0.0 0.0 0.0" + + # Set second vector point vector to the second atom. widget.selection = [1] widget.def_axis_p2() assert widget.axis_p2.value == "1.92 1.11 0.79" @@ -298,3 +306,10 @@ def test_basic_structure_editor(structure_data_object): widget.element.value = "O" widget.mod_element() assert widget.structure.get_chemical_formula() == "COSi" + + # Add a ligand. + widget.ligand.label = "Methyl -CH3" + widget.selection = [2] + widget.add() + assert len(widget.structure) == 7 + assert widget.structure.get_chemical_formula() == "C2H3OSi" From 1c1bc6707b67a325ec0a69eb6c123429c1e34cad Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 10 Oct 2023 11:22:48 +0000 Subject: [PATCH 61/87] Review. --- aiidalab_widgets_base/viewers.py | 38 ++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 5cc01abe..d888219d 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -421,11 +421,20 @@ def change_supercell(_=None): for elem in _supercell: elem.observe(change_supercell, names="value") supercell_selector = ipw.HBox( - [ipw.HTML(description="Super cell:")] + _supercell + [ + ipw.HTML( + description="Super cell:", style={"description_width": "initial"} + ) + ] + + _supercell ) # 2. Choose background color. - background_color = ipw.ColorPicker(description="Background") + background_color = ipw.ColorPicker( + description="Background", + style={"description_width": "initial"}, + layout={"width": "200px"}, + ) tl.link((background_color, "value"), (self._viewer, "background")) background_color.value = "white" @@ -435,7 +444,7 @@ def change_supercell(_=None): description="Camera type:", value=self._viewer.camera, layout={"align_self": "flex-start"}, - style={"button_width": "115.5px"}, + style={"button_width": "115.5px", "description_width": "initial"}, orientation="vertical", ) @@ -496,16 +505,31 @@ def change_camera(change): array_threshold=0, ) ] + + representation_accordion = ipw.Accordion( + children=[ + ipw.VBox( + [ + self.representations_header, + self.representation_output, + self.atoms_not_represented, + ipw.HBox( + [apply_representations, add_new_representation_button] + ), + ] + ) + ], + ) + representation_accordion.set_title(0, "Representations") + representation_accordion.selected_index = None + return ipw.VBox( [ supercell_selector, background_color, camera_type, - self.representations_header, - self.representation_output, - self.atoms_not_represented, - ipw.HBox([apply_representations, add_new_representation_button]), center_button, + representation_accordion, ] ) From 81ae8a9064bc7093493d8e96ebec79e78bf4853b Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 10 Oct 2023 11:38:24 +0000 Subject: [PATCH 62/87] Remove unnecessary warning check. --- tests_notebooks/test_notebooks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests_notebooks/test_notebooks.py b/tests_notebooks/test_notebooks.py index f7579fd1..3bd005cf 100644 --- a/tests_notebooks/test_notebooks.py +++ b/tests_notebooks/test_notebooks.py @@ -21,7 +21,6 @@ def test_aiida_datatypes_viewers(selenium_driver, final_screenshot): driver.set_window_size(1000, 2000) driver.find_element(By.CLASS_NAME, "widget-label") driver.find_element(By.XPATH, '//button[text()="Clear selection"]') - driver.find_element(By.XPATH, '//p[text()="Warning:"]') time.sleep(5) From 13f9254db7087e105e7a0b0794e8742df3817c52 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Fri, 20 Oct 2023 12:52:31 +0000 Subject: [PATCH 63/87] review --- aiidalab_widgets_base/viewers.py | 1 - tests/test_viewers.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 83e6393d..e4e6972a 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -195,7 +195,6 @@ def __init__(self, uuid=None, indices=None, deletable=True, array_threshold=1.0) "width": "50px", "visibility": "visible" if deletable else "hidden", }, - style={"description_width": "initial"}, ) self.delete_button.on_click(self.delete_myself) diff --git a/tests/test_viewers.py b/tests/test_viewers.py index 1094f2a3..7454d015 100644 --- a/tests/test_viewers.py +++ b/tests/test_viewers.py @@ -206,6 +206,8 @@ def test_structure_data_viewer(structure_data_object): v._add_representation() assert "2" in v.atoms_not_represented.value v._all_representations[1].selection.value = "2" + v._all_representations[0].type.value = "ball+stick" + v._all_representations[1].type.value = "spacefill" v._apply_representations() assert v.atoms_not_represented.value == "" From 42b1d642e85084b8eeddeb1de99af1bbdcae6205 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Fri, 20 Oct 2023 15:01:15 +0000 Subject: [PATCH 64/87] Improve selection color. --- aiidalab_widgets_base/viewers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index e4e6972a..1bc43995 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -945,12 +945,13 @@ def highlight_atoms( if len(indices) > 0: params = representation.nglview_parameters(indices) params["params"]["name"] = f"highlight_representation_{i}" - params["params"]["opacity"] = 0.3 + params["params"]["opacity"] = 0.8 + params["params"]["color"] = "darkgreen" params["params"]["component_index"] = 0 if "radiusScale" in params["params"]: - params["params"]["radiusScale"] *= 1.4 + params["params"]["radiusScale"] *= 1.2 else: - params["params"]["aspectRatio"] *= 1.4 + params["params"]["aspectRatio"] *= 1.2 # Use directly the remote call for more flexibility. self._viewer._remote_call( From 2be1f3095b2c496f950348673108a5344a169ef1 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 23 Oct 2023 14:13:09 +0000 Subject: [PATCH 65/87] Representation arrays: convert them to bool type. --- aiidalab_widgets_base/viewers.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 1bc43995..afd64c1b 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -138,7 +138,7 @@ class NglViewerRepresentation(ipw.HBox): master_class = None - def __init__(self, uuid=None, indices=None, deletable=True, array_threshold=1.0): + def __init__(self, uuid=None, indices=None, deletable=True): """Initialize the representation. uuid: str @@ -147,11 +147,8 @@ def __init__(self, uuid=None, indices=None, deletable=True, array_threshold=1.0) List of indices to be displayed. deletable: bool If True, add a button to delete the representation. - array_threshold: float - Threshold for displaying atoms based on the values of _aiidalab_viewer_representation_* arrays. """ - self.array_threshold = array_threshold self.uuid = uuid or f"_aiidalab_viewer_representation_{shortuuid.uuid()}" self.show = ipw.Checkbox( @@ -225,12 +222,12 @@ def sync_myself_to_array_from_atoms_object(self, structure): def add_myself_to_atoms_object(self, structure): """Add representation array to the structure object. If the array already exists, update it.""" if structure: - array_representation = np.full(len(structure), -1) + array_representation = np.full(len(structure), False, dtype=bool) selection = np.array( string_range_to_list(self.selection.value, shift=-1)[0], dtype=int ) # Only attempt to display the existing atoms. - array_representation[selection[selection < len(structure)]] = 1 + array_representation[selection[selection < len(structure)]] = True structure.set_array(self.uuid, array_representation) return True return False @@ -239,8 +236,8 @@ def atoms_in_representaion(self, structure=None): """Return an array of booleans indicating which atoms are present in the representation.""" if structure: if self.uuid in structure.arrays: - return structure.arrays[self.uuid] >= self.array_threshold - return False + return structure.arrays[self.uuid] + return None def nglview_parameters(self, indices): """Return the parameters dictionary of a representation.""" @@ -485,12 +482,10 @@ def change_camera(change): self.representation_output = ipw.VBox() # The default representation is always present and cannot be deleted. - # Moreover, it always shows new atoms due to the array_threshold=0. self._all_representations = [ NglViewerRepresentation( uuid="_aiidalab_viewer_representation_default", deletable=False, - array_threshold=0, ) ] @@ -1115,7 +1110,8 @@ def _valid_structure(self, change): # This will make sure that the new structure is displayed at the beginning. if "_aiidalab_viewer_representation_default" not in structure.arrays: structure.set_array( - "_aiidalab_viewer_representation_default", np.zeros(len(structure)) + "_aiidalab_viewer_representation_default", + np.full(len(structure), True, dtype=bool), ) return structure # This also includes the case when the structure is None. @@ -1145,7 +1141,7 @@ def _observe_structure(self, change=None): except ValueError: self._add_representation( uuid=uuid, - indices=np.where(structure.arrays[self.uuid] >= 1.0)[0], + indices=np.where(structure.arrays[uuid])[0], ) # Empty atoms selection for the representations that are not present in the structure. # Typically this happens when a new structure is imported. From 374128a517af85c52e310807f8a69710cf5e4d67 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 23 Oct 2023 14:55:43 +0000 Subject: [PATCH 66/87] Fix pytests --- aiidalab_widgets_base/structures.py | 2 +- aiidalab_widgets_base/viewers.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 42fb02b9..fc7c8c08 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -83,7 +83,7 @@ def __init__( if viewer: self.viewer = viewer else: - self.viewer = StructureDataViewer(**kwargs) + self.viewer = StructureDataViewer() tl.dlink((self, "structure"), (self.viewer, "structure")) # Store button. diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index afd64c1b..56cb74ae 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -917,7 +917,7 @@ def _on_atom_click(self, _=None): def highlight_atoms( self, - vis_list, + list_of_atoms, ): """Highlighting atoms according to the provided list.""" if not hasattr(self._viewer, "component_0"): @@ -932,7 +932,7 @@ def highlight_atoms( # Then add the new one if needed. indices = np.intersect1d( - vis_list, + list_of_atoms, np.where( representation.atoms_in_representaion(self.displayed_structure) )[0], From 5c82e8a4745bfedb5934e53aede5add109f74a87 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 23 Oct 2023 15:20:09 +0000 Subject: [PATCH 67/87] Fix tests. --- aiidalab_widgets_base/structures.py | 4 +--- tests/test_viewers.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index fc7c8c08..346a58ea 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -38,9 +38,7 @@ class StructureManagerWidget(ipw.VBox): input_structure = tl.Union( [tl.Instance(ase.Atoms), tl.Instance(orm.Data)], allow_none=True ) - structure = tl.Union( - [tl.Instance(ase.Atoms), tl.Instance(orm.Data)], allow_none=True - ) + structure = tl.Instance(ase.Atoms, allow_none=True) structure_node = tl.Instance(orm.Data, allow_none=True, read_only=True) node_class = tl.Unicode() diff --git a/tests/test_viewers.py b/tests/test_viewers.py index 7454d015..2db49719 100644 --- a/tests/test_viewers.py +++ b/tests/test_viewers.py @@ -66,7 +66,7 @@ def test_structure_data_viewer(structure_data_object): format_cases = [ ( "Extended xyz", - """MgpMYXR0aWNlPSIzLjg0NzM3IDAuMCAwLjAgMS45MjM2ODUgMy4zMzE5MiAwLjAgMS45MjM2ODUgMS4xMTA2NCAzLjE0MTM2NCIgUHJvcGVydGllcz1zcGVjaWVzOlM6MTpwb3M6UjozOm1hc3NlczpSOjE6X2FpaWRhbGFiX3ZpZXdlcl9yZXByZXNlbnRhdGlvbl9kZWZhdWx0OlI6MSBwYmM9IlQgVCBUIgpTaSAgICAgICAwLjAwMDAwMDAwICAgICAgIDAuMDAwMDAwMDAgICAgICAgMC4wMDAwMDAwMCAgICAgIDI4LjA4NTUwMDAwICAgICAgIDAuMDAwMDAwMDAKU2kgICAgICAgMS45MjM2ODUwMCAgICAgICAxLjExMDY0MDAwICAgICAgIDAuNzg1MzQxMDAgICAgICAyOC4wODU1MDAwMCAgICAgICAwLjAwMDAwMDAwCg==""", + """MgpMYXR0aWNlPSIzLjg0NzM3IDAuMCAwLjAgMS45MjM2ODUgMy4zMzE5MiAwLjAgMS45MjM2ODUgMS4xMTA2NCAzLjE0MTM2NCIgUHJvcGVydGllcz1zcGVjaWVzOlM6MTpwb3M6UjozOm1hc3NlczpSOjE6X2FpaWRhbGFiX3ZpZXdlcl9yZXByZXNlbnRhdGlvbl9kZWZhdWx0Okw6MSBwYmM9IlQgVCBUIgpTaSAgICAgICAwLjAwMDAwMDAwICAgICAgIDAuMDAwMDAwMDAgICAgICAgMC4wMDAwMDAwMCAgICAgIDI4LjA4NTUwMDAwICBUClNpICAgICAgIDEuOTIzNjg1MDAgICAgICAgMS4xMTA2NDAwMCAgICAgICAwLjc4NTM0MTAwICAgICAgMjguMDg1NTAwMDAgIFQK""", ), ( "xsf", From 9592cb3384431babe52718fac3d4672f52a9c584 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 24 Oct 2023 17:15:25 +0000 Subject: [PATCH 68/87] Remove list_to_nglview function as it is used only in one place. --- aiidalab_widgets_base/viewers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 56cb74ae..aef10617 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -244,7 +244,7 @@ def nglview_parameters(self, indices): params = { "type": self.type.value, "params": { - "sele": list_to_nglview(indices), + "sele": "@" + ",".join(map(str, indices)) if indices else "none", "opacity": 1, "color": self.color.value, }, From 3b4843bed91d5d68c869bb6df0f96f9ac6eecdd6 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 24 Oct 2023 17:24:44 +0000 Subject: [PATCH 69/87] NglViewerRepresentation - improve class docstring, add comment for the master_class. --- aiidalab_widgets_base/viewers.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index aef10617..93d117ec 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -22,15 +22,6 @@ from .misc import CopyToClipboardButton, ReversePolishNotation from .utils import ase2spglib, list_to_string_range, string_range_to_list - -def list_to_nglview(the_list): - """Converts a list of structures to a nglview widget""" - selection = "none" - if len(the_list): - selection = "@" + ",".join(map(str, the_list)) - return selection - - AIIDA_VIEWER_MAPPING = {} @@ -134,9 +125,13 @@ def __init__(self, parameter, downloadable=True, **kwargs): class NglViewerRepresentation(ipw.HBox): - """Representation for StructureData in nglviewer""" + """This class represents the parameters for displaying a structure in NGLViewer. + + It is utilized in the structure viewer, where multiple representations can be defined, + each specifying how to visually represent a particular subset of atoms. + """ - master_class = None + master_class = None # The structure viewer class that contains this representation. def __init__(self, uuid=None, indices=None, deletable=True): """Initialize the representation. From 9424bb7449000aa83bfac0d92a6154d9d1f6199c Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 24 Oct 2023 17:32:38 +0000 Subject: [PATCH 70/87] fix --- aiidalab_widgets_base/viewers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 93d117ec..bfe67cf7 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -239,7 +239,9 @@ def nglview_parameters(self, indices): params = { "type": self.type.value, "params": { - "sele": "@" + ",".join(map(str, indices)) if indices else "none", + "sele": "@" + ",".join(map(str, indices)) + if len(indices) > 0 + else "none", "opacity": 1, "color": self.color.value, }, From 114959a42f3d72db019435830f59a46ab54d6e53 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 24 Oct 2023 17:37:43 +0000 Subject: [PATCH 71/87] update sync_myself_to_array_from_atoms_object --- aiidalab_widgets_base/viewers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index bfe67cf7..79ef0276 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -211,8 +211,6 @@ def sync_myself_to_array_from_atoms_object(self, structure): self.selection.value = list_to_string_range( np.where(self.atoms_in_representaion(structure))[0], shift=1 ) - return True - return False def add_myself_to_atoms_object(self, structure): """Add representation array to the structure object. If the array already exists, update it.""" From 781684f5a0837b34f27d548ff2090b2f5f03504a Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 24 Oct 2023 17:52:24 +0000 Subject: [PATCH 72/87] Add type annotations. --- aiidalab_widgets_base/viewers.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 79ef0276..c3897ba3 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -1,3 +1,5 @@ +from __future__ import annotations + """Jupyter viewers for AiiDA data objects.""" # pylint: disable=no-self-use @@ -204,7 +206,7 @@ def __init__(self, uuid=None, indices=None, deletable=True): def delete_myself(self, _): self.master_class.delete_representation(self) - def sync_myself_to_array_from_atoms_object(self, structure): + def sync_myself_to_array_from_atoms_object(self, structure: ase.Atoms | None): """Update representation from the structure object.""" if structure: if self.uuid in structure.arrays: @@ -212,7 +214,7 @@ def sync_myself_to_array_from_atoms_object(self, structure): np.where(self.atoms_in_representaion(structure))[0], shift=1 ) - def add_myself_to_atoms_object(self, structure): + def add_myself_to_atoms_object(self, structure: ase.Atoms | None): """Add representation array to the structure object. If the array already exists, update it.""" if structure: array_representation = np.full(len(structure), False, dtype=bool) @@ -225,7 +227,7 @@ def add_myself_to_atoms_object(self, structure): return True return False - def atoms_in_representaion(self, structure=None): + def atoms_in_representaion(self, structure: ase.Atoms | None = None): """Return an array of booleans indicating which atoms are present in the representation.""" if structure: if self.uuid in structure.arrays: From 58f6a37df7b11cc111425cf3b7a13dcecaa5ed12 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 24 Oct 2023 17:54:15 +0000 Subject: [PATCH 73/87] Better name for the nglview parameters dictionary. --- aiidalab_widgets_base/viewers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index c3897ba3..e3412938 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -236,7 +236,7 @@ def atoms_in_representaion(self, structure: ase.Atoms | None = None): def nglview_parameters(self, indices): """Return the parameters dictionary of a representation.""" - params = { + nglview_parameters_dict = { "type": self.type.value, "params": { "sele": "@" + ",".join(map(str, indices)) @@ -247,11 +247,11 @@ def nglview_parameters(self, indices): }, } if self.type.value == "ball+stick": - params["params"]["aspectRatio"] = self.size.value + nglview_parameters_dict["params"]["aspectRatio"] = self.size.value else: - params["params"]["radiusScale"] = 0.1 * self.size.value + nglview_parameters_dict["params"]["radiusScale"] = 0.1 * self.size.value - return params + return nglview_parameters_dict class _StructureDataBaseViewer(ipw.VBox): From e75402f95380fcef234b1f1b8bf80d4806068812 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 24 Oct 2023 18:16:38 +0000 Subject: [PATCH 74/87] natoms - make it a class property. --- aiidalab_widgets_base/viewers.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index e3412938..ee04820b 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -267,7 +267,6 @@ class _StructureDataBaseViewer(ipw.VBox): """ _all_representations = tl.List() - natoms = tl.Int() input_selection = tl.List(tl.Int(), allow_none=True) selection = tl.List(tl.Int()) displayed_selection = tl.List(tl.Int()) @@ -291,7 +290,6 @@ def __init__( self._viewer.camera = default_camera self._viewer.observe(self._on_atom_click, names="picked") self._viewer.stage.set_parameters(mouse_preset="pymol") - self.natoms = 0 view_box = ipw.VBox([self._viewer]) @@ -1053,6 +1051,11 @@ def _prepare_payload(self, file_format=None): def thumbnail(self): return self._prepare_payload(file_format="png") + @property + def natoms(self): + """Number of atoms in the structure.""" + return 0 if self.structure is None else len(self.structure) + @register_viewer_widget("data.core.cif.CifData.") @register_viewer_widget("data.core.structure.StructureData.") @@ -1083,7 +1086,6 @@ class StructureDataViewer(_StructureDataBaseViewer): def __init__(self, structure=None, **kwargs): super().__init__(**kwargs) self.structure = structure - self.natoms = len(self.structure) if self.structure else 0 @tl.observe("supercell") def _observe_supercell(self, _=None): @@ -1120,7 +1122,6 @@ def _observe_structure(self, change=None): self._viewer.clear_representations(component=0) if structure: - self.natoms = len(structure) # Make sure that the representation arrays from structure are present in the viewer. structure_uuids = [ uuid @@ -1151,7 +1152,6 @@ def _observe_structure(self, change=None): else: self.set_trait("displayed_structure", None) self.set_trait("cell", None) - self.natoms = 0 @tl.observe("displayed_structure") def _observe_displayed_structure(self, change): From 133167ff2d6833d2aa50bfffffbc49693ac162a0 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 24 Oct 2023 18:23:00 +0000 Subject: [PATCH 75/87] Put representation name into a constant --- aiidalab_widgets_base/viewers.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index ee04820b..0cc5b1b1 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -275,6 +275,8 @@ class _StructureDataBaseViewer(ipw.VBox): DEFAULT_SELECTION_OPACITY = 0.2 DEFAULT_SELECTION_RADIUS = 6 DEFAULT_SELECTION_COLOR = "green" + REPRESENTATION_PREFIX = "_aiidalab_viewer_representation_" + DEFAULT_REPRESENTATION = "_aiidalab_viewer_representation_default" def __init__( self, @@ -479,7 +481,7 @@ def change_camera(change): # The default representation is always present and cannot be deleted. self._all_representations = [ NglViewerRepresentation( - uuid="_aiidalab_viewer_representation_default", + uuid=self.DEFAULT_REPRESENTATION, deletable=False, ) ] @@ -1107,9 +1109,9 @@ def _valid_structure(self, change): # Add default representation array if it is not present. # This will make sure that the new structure is displayed at the beginning. - if "_aiidalab_viewer_representation_default" not in structure.arrays: + if self.DEFAULT_REPRESENTATION not in structure.arrays: structure.set_array( - "_aiidalab_viewer_representation_default", + self.DEFAULT_REPRESENTATION, np.full(len(structure), True, dtype=bool), ) return structure # This also includes the case when the structure is None. From b6c84e1dcab9811f4c546b2b4deeb4218bc9aed7 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 25 Oct 2023 11:25:07 +0000 Subject: [PATCH 76/87] _StructureDataBaseViewer work on docstrings. --- aiidalab_widgets_base/viewers.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 0cc5b1b1..55ba27d2 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -257,13 +257,13 @@ def nglview_parameters(self, indices): class _StructureDataBaseViewer(ipw.VBox): """Base viewer class for AiiDA structure or trajectory objects. - :param configure_view: If True, add configuration tabs (deprecated) - :type configure_view: bool - :param configuration_tabs: List of configuration tabs (default: ["Selection", "Appearance", "Cell", "Download"]) - :type configure_view: list - :param default_camera: default camera (orthographic|perspective), can be changed in the Appearance tab - :type default_camera: string - + Traits: + _all_representations: list, containing all the representations of the structure. + input_selection: list used mostly by external tools to populate the selection field. + selection: list of currently selected atoms. + displayed_selection: list of currently displayed atoms in the displayed structure, which also includes super-cell. + supercell: list of supercell dimensions. + cell: ase.cell.Cell object. """ _all_representations = tl.List() @@ -285,6 +285,12 @@ def __init__( default_camera="orthographic", **kwargs, ): + """Initialize the viewer. + + :param configure_view: If True, add configuration tabs (deprecated). + :param configuration_tabs: List of configuration tabs (default: ["Selection", "Appearance", "Cell", "Download"]). + :param default_camera: default camera (orthographic|perspective), can be changed in the Appearance tab. + """ # Defining viewer box. # Nglviwer From 255564efeafc99b07fc98c754045772ec0efe553 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 25 Oct 2023 11:29:45 +0000 Subject: [PATCH 77/87] delete_representation: add type hint. --- aiidalab_widgets_base/viewers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 55ba27d2..01ac769a 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -526,7 +526,7 @@ def _add_representation(self, _=None, uuid=None, indices=None): ] self._apply_representations() - def delete_representation(self, representation): + def delete_representation(self, representation: NglViewerRepresentation): try: index = self._all_representations.index(representation) except ValueError: From 694cf47fdfba475b39a9c45d69df4a689c1a9312 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 25 Oct 2023 11:45:45 +0000 Subject: [PATCH 78/87] Slightly improve _observe_structure method. --- aiidalab_widgets_base/viewers.py | 59 ++++++++++++++++---------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 01ac769a..f771fa9b 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -1129,37 +1129,38 @@ def _observe_structure(self, change=None): self._viewer.clear_representations(component=0) - if structure: - # Make sure that the representation arrays from structure are present in the viewer. - structure_uuids = [ - uuid - for uuid in structure.arrays - if uuid.startswith("_aiidalab_viewer_representation_") - ] - rep_uuids = [rep.uuid for rep in self._all_representations] - - for uuid in structure_uuids: - try: - index = rep_uuids.index(uuid) - self._all_representations[ - index - ].sync_myself_to_array_from_atoms_object(structure) - except ValueError: - self._add_representation( - uuid=uuid, - indices=np.where(structure.arrays[uuid])[0], - ) - # Empty atoms selection for the representations that are not present in the structure. - # Typically this happens when a new structure is imported. - for i, uuid in enumerate(rep_uuids): - if uuid not in structure_uuids: - self._all_representations[i].selection.value = "" - - self._observe_supercell() # To trigger an update of the displayed structure - self.set_trait("cell", structure.cell) - else: + if not structure: self.set_trait("displayed_structure", None) self.set_trait("cell", None) + return + + # Make sure that the representation arrays from structure are present in the viewer. + structure_uuids = [ + uuid + for uuid in structure.arrays + if uuid.startswith("_aiidalab_viewer_representation_") + ] + rep_uuids = [rep.uuid for rep in self._all_representations] + + for uuid in structure_uuids: + try: + index = rep_uuids.index(uuid) + self._all_representations[index].sync_myself_to_array_from_atoms_object( + structure + ) + except ValueError: + self._add_representation( + uuid=uuid, + indices=np.where(structure.arrays[uuid])[0], + ) + # Empty atoms selection for the representations that are not present in the structure. + # Typically this happens when a new structure is imported. + for i, uuid in enumerate(rep_uuids): + if uuid not in structure_uuids: + self._all_representations[i].selection.value = "" + + self._observe_supercell() # To trigger an update of the displayed structure + self.set_trait("cell", structure.cell) @tl.observe("displayed_structure") def _observe_displayed_structure(self, change): From 523e20775d114727bc6856acc4953a6605b56b60 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 25 Oct 2023 12:44:40 +0000 Subject: [PATCH 79/87] try to fix docs build. --- docs/source/conf.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 66cf0928..46706aaa 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -94,6 +94,11 @@ # of the sidebar. html_logo = "_static/aiidalab_logo.png" + +html_theme_options = { + "navigation_with_keys": True, +} + # -- Modifications for Readthedocs ---------------------------------------- From f5337874c38388e5cc8aa3a74634ad3db43e2c97 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 30 Oct 2023 16:32:44 +0000 Subject: [PATCH 80/87] Representation arrays: move back to int to properly handle new atoms. The issues with the bool array is that only shows whether the atoms was in the representation or not. This is not enough, because one also needs to consider the newly added atoms. --- aiidalab_widgets_base/viewers.py | 16 ++++++++++------ tests/test_viewers.py | 16 ++++++++++++---- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index f771fa9b..fe639d91 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -135,7 +135,7 @@ class NglViewerRepresentation(ipw.HBox): master_class = None # The structure viewer class that contains this representation. - def __init__(self, uuid=None, indices=None, deletable=True): + def __init__(self, uuid=None, indices=None, deletable=True, atom_show_threshold=1): """Initialize the representation. uuid: str @@ -144,8 +144,11 @@ def __init__(self, uuid=None, indices=None, deletable=True): List of indices to be displayed. deletable: bool If True, add a button to delete the representation. + atom_show_threshold: int + only show the atom if the corresponding value in the representation array is larger or equal than this threshold. """ + self.atom_show_threshold = atom_show_threshold self.uuid = uuid or f"_aiidalab_viewer_representation_{shortuuid.uuid()}" self.show = ipw.Checkbox( @@ -217,12 +220,12 @@ def sync_myself_to_array_from_atoms_object(self, structure: ase.Atoms | None): def add_myself_to_atoms_object(self, structure: ase.Atoms | None): """Add representation array to the structure object. If the array already exists, update it.""" if structure: - array_representation = np.full(len(structure), False, dtype=bool) + array_representation = np.full(len(structure), -1, dtype=int) selection = np.array( string_range_to_list(self.selection.value, shift=-1)[0], dtype=int ) # Only attempt to display the existing atoms. - array_representation[selection[selection < len(structure)]] = True + array_representation[selection[selection < len(structure)]] = 1 structure.set_array(self.uuid, array_representation) return True return False @@ -231,7 +234,7 @@ def atoms_in_representaion(self, structure: ase.Atoms | None = None): """Return an array of booleans indicating which atoms are present in the representation.""" if structure: if self.uuid in structure.arrays: - return structure.arrays[self.uuid] + return structure.arrays[self.uuid] >= self.atom_show_threshold return None def nglview_parameters(self, indices): @@ -489,6 +492,7 @@ def change_camera(change): NglViewerRepresentation( uuid=self.DEFAULT_REPRESENTATION, deletable=False, + atom_show_threshold=0, ) ] @@ -1118,7 +1122,7 @@ def _valid_structure(self, change): if self.DEFAULT_REPRESENTATION not in structure.arrays: structure.set_array( self.DEFAULT_REPRESENTATION, - np.full(len(structure), True, dtype=bool), + np.zeros(len(structure), dtype=int), ) return structure # This also includes the case when the structure is None. @@ -1151,7 +1155,7 @@ def _observe_structure(self, change=None): except ValueError: self._add_representation( uuid=uuid, - indices=np.where(structure.arrays[uuid])[0], + indices=np.where(structure.arrays[self.uuid] >= 1)[0], ) # Empty atoms selection for the representations that are not present in the structure. # Typically this happens when a new structure is imported. diff --git a/tests/test_viewers.py b/tests/test_viewers.py index 2db49719..9c024371 100644 --- a/tests/test_viewers.py +++ b/tests/test_viewers.py @@ -1,3 +1,4 @@ +import ase import pytest import traitlets as tl from aiida import orm @@ -8,9 +9,6 @@ @pytest.mark.usefixtures("aiida_profile_clean") def test_pbc_structure_data_viewer(structure_data_object): """Test the periodicity of the structure viewer widget.""" - - import ase - # Prepare a structure with periodicity xy ase_input = ase.Atoms( symbols="Li2", @@ -66,7 +64,7 @@ def test_structure_data_viewer(structure_data_object): format_cases = [ ( "Extended xyz", - """MgpMYXR0aWNlPSIzLjg0NzM3IDAuMCAwLjAgMS45MjM2ODUgMy4zMzE5MiAwLjAgMS45MjM2ODUgMS4xMTA2NCAzLjE0MTM2NCIgUHJvcGVydGllcz1zcGVjaWVzOlM6MTpwb3M6UjozOm1hc3NlczpSOjE6X2FpaWRhbGFiX3ZpZXdlcl9yZXByZXNlbnRhdGlvbl9kZWZhdWx0Okw6MSBwYmM9IlQgVCBUIgpTaSAgICAgICAwLjAwMDAwMDAwICAgICAgIDAuMDAwMDAwMDAgICAgICAgMC4wMDAwMDAwMCAgICAgIDI4LjA4NTUwMDAwICBUClNpICAgICAgIDEuOTIzNjg1MDAgICAgICAgMS4xMTA2NDAwMCAgICAgICAwLjc4NTM0MTAwICAgICAgMjguMDg1NTAwMDAgIFQK""", + """MgpMYXR0aWNlPSIzLjg0NzM3IDAuMCAwLjAgMS45MjM2ODUgMy4zMzE5MiAwLjAgMS45MjM2ODUgMS4xMTA2NCAzLjE0MTM2NCIgUHJvcGVydGllcz1zcGVjaWVzOlM6MTpwb3M6UjozOm1hc3NlczpSOjE6X2FpaWRhbGFiX3ZpZXdlcl9yZXByZXNlbnRhdGlvbl9kZWZhdWx0Okk6MSBwYmM9IlQgVCBUIgpTaSAgICAgICAwLjAwMDAwMDAwICAgICAgIDAuMDAwMDAwMDAgICAgICAgMC4wMDAwMDAwMCAgICAgIDI4LjA4NTUwMDAwICAgICAgICAwClNpICAgICAgIDEuOTIzNjg1MDAgICAgICAgMS4xMTA2NDAwMCAgICAgICAwLjc4NTM0MTAwICAgICAgMjguMDg1NTAwMDAgICAgICAgIDAK""", ), ( "xsf", @@ -211,6 +209,16 @@ def test_structure_data_viewer(structure_data_object): v._apply_representations() assert v.atoms_not_represented.value == "" + # Add an atom to the structure. + new_structure = v.structure.copy() + new_structure.append(ase.Atom("C", (0.5, 0.5, 0.5))) + v.structure = None + v.structure = new_structure + + # The new atom should appear in the default representation. + assert v._all_representations[0].selection.value == "1 3" + assert "3" not in v.atoms_not_represented.value + # Delete the second representation. assert v._all_representations[0].delete_button.layout.visibility == "hidden" assert v._all_representations[1].delete_button.layout.visibility == "visible" From 23f9a2f0912f8cbf0ca36fc6b84dd563fe3d18b8 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 31 Oct 2023 10:23:33 +0000 Subject: [PATCH 81/87] Remove return statements from the 'add_myself_to_atoms_object' method. --- aiidalab_widgets_base/viewers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index fe639d91..3e796d19 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -227,8 +227,6 @@ def add_myself_to_atoms_object(self, structure: ase.Atoms | None): # Only attempt to display the existing atoms. array_representation[selection[selection < len(structure)]] = 1 structure.set_array(self.uuid, array_representation) - return True - return False def atoms_in_representaion(self, structure: ase.Atoms | None = None): """Return an array of booleans indicating which atoms are present in the representation.""" From 66ccfbdf2e689b91e927ec2c8b25af3d602f9262 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 31 Oct 2023 12:30:18 +0000 Subject: [PATCH 82/87] Replace "_aiidalab_viewer_representation_" with REPRESENTATION_PREFIX constant. --- aiidalab_widgets_base/viewers.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 3e796d19..5358510b 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -149,7 +149,7 @@ def __init__(self, uuid=None, indices=None, deletable=True, atom_show_threshold= """ self.atom_show_threshold = atom_show_threshold - self.uuid = uuid or f"_aiidalab_viewer_representation_{shortuuid.uuid()}" + self.uuid = uuid or f"{self.REPRESENTATION_PREFIX}{shortuuid.uuid()}" self.show = ipw.Checkbox( value=True, @@ -566,10 +566,7 @@ def _apply_representations(self, change=None): # Remove missing representations from the structure. for array in self.structure.arrays: - if ( - array.startswith("_aiidalab_viewer_representation_") - and array not in rep_uuids - ): + if array.startswith(self.REPRESENTATION_PREFIX) and array not in rep_uuids: del self.structure.arrays[array] self._observe_structure({"new": self.structure}) self._check_missing_atoms_in_representations() @@ -1140,7 +1137,7 @@ def _observe_structure(self, change=None): structure_uuids = [ uuid for uuid in structure.arrays - if uuid.startswith("_aiidalab_viewer_representation_") + if uuid.startswith(self.REPRESENTATION_PREFIX) ] rep_uuids = [rep.uuid for rep in self._all_representations] From 6be5d98593db838ac934e98d6cca1e80736384bd Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Thu, 2 Nov 2023 08:32:02 +0000 Subject: [PATCH 83/87] Fix issue with representation's name constant. --- aiidalab_widgets_base/viewers.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 5358510b..6991c698 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -135,7 +135,7 @@ class NglViewerRepresentation(ipw.HBox): master_class = None # The structure viewer class that contains this representation. - def __init__(self, uuid=None, indices=None, deletable=True, atom_show_threshold=1): + def __init__(self, uuid, indices=None, deletable=True, atom_show_threshold=1): """Initialize the representation. uuid: str @@ -149,7 +149,7 @@ def __init__(self, uuid=None, indices=None, deletable=True, atom_show_threshold= """ self.atom_show_threshold = atom_show_threshold - self.uuid = uuid or f"{self.REPRESENTATION_PREFIX}{shortuuid.uuid()}" + self.uuid = uuid self.show = ipw.Checkbox( value=True, @@ -524,7 +524,10 @@ def change_camera(change): def _add_representation(self, _=None, uuid=None, indices=None): """Add a representation to the list of representations.""" self._all_representations = self._all_representations + [ - NglViewerRepresentation(uuid=uuid, indices=indices) + NglViewerRepresentation( + uuid=uuid or f"{self.REPRESENTATION_PREFIX}{shortuuid.uuid()}", + indices=indices, + ) ] self._apply_representations() From cb6e6bb83df0f4ebe7086e8e9b69a1e4254691fc Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Thu, 2 Nov 2023 08:42:50 +0000 Subject: [PATCH 84/87] Split tests for better code maintainability. --- tests/test_structures.py | 6 ++++-- tests/test_viewers.py | 12 +++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/test_structures.py b/tests/test_structures.py index a48f78aa..823d7615 100644 --- a/tests/test_structures.py +++ b/tests/test_structures.py @@ -48,7 +48,6 @@ def test_structure_manager_widget(structure_data_object): # Store the modified structure. structure_manager_widget.btn_store.click() assert structure_manager_widget.structure_node.is_stored - stored_structure = structure_manager_widget.structure_node # Undo the structure modification. structure_manager_widget.undo() @@ -58,6 +57,9 @@ def test_structure_manager_widget(structure_data_object): structure_manager_widget.undo() # Undo the structure creation. assert structure_manager_widget.structure is None + +@pytest.mark.usefixtures("aiida_profile_clean") +def test_structure_manager_widget_multiple_importers_editors(structure_data_object): # Test the widget with multiple importers, editors. Specify the viewer and the node class base_editor = awb.BasicStructureEditor(title="Basic Editor") structure_manager_widget = awb.StructureManagerWidget( @@ -74,7 +76,7 @@ def test_structure_manager_widget(structure_data_object): ) assert structure_manager_widget.structure is None - structure_manager_widget.input_structure = stored_structure + structure_manager_widget.input_structure = structure_data_object # Set action vector perpendicular to screen. base_editor.def_perpendicular_to_screen() diff --git a/tests/test_viewers.py b/tests/test_viewers.py index 9c024371..ff4cd369 100644 --- a/tests/test_viewers.py +++ b/tests/test_viewers.py @@ -56,7 +56,7 @@ def test_several_data_viewers( @pytest.mark.usefixtures("aiida_profile_clean") -def test_structure_data_viewer(structure_data_object): +def test_structure_data_viewer_storage(structure_data_object): v = viewers.viewer(structure_data_object) assert isinstance(v, viewers.StructureDataViewer) @@ -80,7 +80,10 @@ def test_structure_data_viewer(structure_data_object): v.file_format.label = fmt assert v._prepare_payload() == out - # Selection of atoms. + +@pytest.mark.usefixtures("aiida_profile_clean") +def test_structure_data_viewer_selection(structure_data_object): + v = viewers.viewer(structure_data_object) # Direct selection. v._selected_atoms.value = "1..2" @@ -188,7 +191,10 @@ def test_structure_data_viewer(structure_data_object): v.apply_displayed_selection() assert v.wrong_syntax.layout.visibility == "visible" - # Test representations. + +@pytest.mark.usefixtures("aiida_profile_clean") +def test_structure_data_viewer_representation(structure_data_object): + v = viewers.viewer(structure_data_object) # By default, there should be one "default" representation. assert len(v._all_representations) == 1 From f6ef4d1a67c3f92e4ce35d3e08db04e9ea0892f8 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Thu, 2 Nov 2023 09:06:18 +0000 Subject: [PATCH 85/87] review. --- aiidalab_widgets_base/structures.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 346a58ea..df19c1fd 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -1454,4 +1454,3 @@ def remove(self, _=None, atoms=None, selection=None): # we must be sure trait atoms is set before trait selection self.structure = atoms self.input_selection = None - self.input_selection = [] From 5abbf5d601879bd50f033f8900caf7d221d9a080 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Thu, 2 Nov 2023 09:38:20 +0000 Subject: [PATCH 86/87] Rename master_class with viewer_class --- aiidalab_widgets_base/viewers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 6991c698..88dd987b 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -133,7 +133,7 @@ class NglViewerRepresentation(ipw.HBox): each specifying how to visually represent a particular subset of atoms. """ - master_class = None # The structure viewer class that contains this representation. + viewer_class = None # The structure viewer class that contains this representation. def __init__(self, uuid, indices=None, deletable=True, atom_show_threshold=1): """Initialize the representation. @@ -207,7 +207,7 @@ def __init__(self, uuid, indices=None, deletable=True, atom_show_threshold=1): ) def delete_myself(self, _): - self.master_class.delete_representation(self) + self.viewer_class.delete_representation(self) def sync_myself_to_array_from_atoms_object(self, structure: ase.Atoms | None): """Update representation from the structure object.""" @@ -552,7 +552,7 @@ def _observe_all_representations(self, change): """Update the list of representations.""" self.representation_output.children = change["new"] if change["new"]: - self._all_representations[-1].master_class = self + self._all_representations[-1].viewer_class = self def _apply_representations(self, change=None): """Apply the representations to the displayed structure.""" From 05430677d4fc1b6851516c05bd836173ed952590 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 6 Nov 2023 14:42:21 +0000 Subject: [PATCH 87/87] Remove aiida_datatypes_viewers.ipynb --- notebooks/aiida_datatypes_viewers.ipynb | 237 ------------------------ 1 file changed, 237 deletions(-) delete mode 100644 notebooks/aiida_datatypes_viewers.ipynb diff --git a/notebooks/aiida_datatypes_viewers.ipynb b/notebooks/aiida_datatypes_viewers.ipynb deleted file mode 100644 index 178aa557..00000000 --- a/notebooks/aiida_datatypes_viewers.ipynb +++ /dev/null @@ -1,237 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from aiida import load_profile\n", - "load_profile()\n", - "import io\n", - "from os import path\n", - "from ase import build\n", - "from aiida import engine, orm, plugins\n", - "from aiidalab_widgets_base import viewer" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Displaying AiiDA data types" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Dict" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Dict = plugins.DataFactory('core.dict')\n", - "p = Dict(dict={\n", - " 'Parameter' :'super long string '*4,\n", - " 'parameter 2' :'value 2',\n", - " 'parameter 3' : 1,\n", - " 'parameter 4' : 2,\n", - "})\n", - "vwr = viewer(p.store(), downloadable=True)\n", - "display(vwr)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# create molecule\n", - "m = build.molecule('H2O')\n", - "m.center(vacuum=2.0)\n", - "#\n", - "# create bulk Pt\n", - "pt = build.bulk('Pt', cubic = True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## CifData" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "CifData = plugins.DataFactory('core.cif')\n", - "s = CifData(ase=pt)\n", - "vwr = viewer(s.store(), configuration_tabs=['Selection', 'Appearance', 'Cell', 'Download'])\n", - "display(vwr)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## StructureData" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "StructureData = plugins.DataFactory('core.structure')\n", - "s = StructureData(ase=m)\n", - "vwr = viewer(s.store())\n", - "display(vwr)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## BandsData" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "BandsData = plugins.DataFactory('core.array.bands')\n", - "bs = BandsData()\n", - "kpoints = np.array([[0. , 0. , 0. ], # array shape is 12 * 3\n", - " [0.1 , 0. , 0.1 ],\n", - " [0.2 , 0. , 0.2 ],\n", - " [0.3 , 0. , 0.3 ],\n", - " [0.4 , 0. , 0.4 ],\n", - " [0.5 , 0. , 0.5 ],\n", - " [0.5 , 0. , 0.5 ],\n", - " [0.525 , 0.05 , 0.525 ],\n", - " [0.55 , 0.1 , 0.55 ],\n", - " [0.575 , 0.15 , 0.575 ],\n", - " [0.6 , 0.2 , 0.6 ],\n", - " [0.625 , 0.25 , 0.625 ]])\n", - "\n", - "bands = np.array([\n", - " [-5.64024889, 6.66929678, 6.66929678, 6.66929678, 8.91047649], # array shape is 12 * 5, where 12 is the size of the kpoints mesh\n", - " [-5.46976726, 5.76113772, 5.97844699, 5.97844699, 8.48186734], # and 5 is the number of states\n", - " [-4.93870761, 4.06179965, 4.97235487, 4.97235488, 7.68276008],\n", - " [-4.05318686, 2.21579935, 4.18048674, 4.18048675, 7.04145185],\n", - " [-2.83974972, 0.37738276, 3.69024464, 3.69024465, 6.75053465],\n", - " [-1.34041116, -1.34041115, 3.52500177, 3.52500178, 6.92381041],\n", - " [-1.34041116, -1.34041115, 3.52500177, 3.52500178, 6.92381041],\n", - " [-1.34599146, -1.31663872, 3.34867603, 3.54390139, 6.93928289],\n", - " [-1.36769345, -1.24523403, 2.94149041, 3.6004033 , 6.98809593],\n", - " [-1.42050683, -1.12604118, 2.48497007, 3.69389815, 7.07537154],\n", - " [-1.52788845, -0.95900776, 2.09104321, 3.82330632, 7.20537566],\n", - " [-1.71354964, -0.74425095, 1.82242466, 3.98697455, 7.37979746]])\n", - "bs.set_kpoints(kpoints)\n", - "bs.set_bands(bands)\n", - "labels = [(0, u'GAMMA'),\n", - " (5, u'X'),\n", - " (6, u'Z'),\n", - " (11, u'U')]\n", - "bs.labels = labels\n", - "vwr = viewer(bs.store(), downloadable=True)\n", - "display(vwr)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## FolderData" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "FolderData = plugins.DataFactory('core.folder')\n", - "fd = FolderData()\n", - "with io.StringIO('content of test1 filelike') as fobj:\n", - " fd.put_object_from_filelike(fobj, path='test1.txt')\n", - "with io.StringIO('content of test2 filelike') as fobj:\n", - " fd.put_object_from_filelike(fobj, path='test2.txt')\n", - "with io.StringIO(u'content of test_long file'*1000) as fobj:\n", - " fd.put_object_from_filelike(fobj, path='test_long.txt')\n", - "vwr = viewer(fd.store(), downloadable=True)\n", - "display(vwr)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Workfunctions and Calcfunctions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from aiida.workflows.arithmetic.add_multiply import add, add_multiply" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result, workfunction = engine.run_get_node(add_multiply, orm.Int(3), orm.Int(4), orm.Int(5))\n", - "display(viewer(workfunction))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result, calcfunction = engine.run_get_node(add, Int(3), Int(4))\n", - "display(viewer(calcfunction))" - ] - } - ], - "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.9.13" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -}