From d822e9d3deb843a7a54a47829e6237b9a8cc0f66 Mon Sep 17 00:00:00 2001 From: Carlo Pignedoli Date: Tue, 25 Aug 2020 09:32:16 +0200 Subject: [PATCH 1/4] Correctly modify element in the base structure editor (#105) --- aiidalab_widgets_base/structures.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 9f02342d9..53621bc88 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -803,7 +803,13 @@ def mod_element(self, _=None): if self.ligand.value == 0: for idx in self.selection: - atoms[idx].symbol = self.element.value + new = Atom(self.element.value) + atoms[idx].mass = new.mass + atoms[idx].magmom = new.magmom + atoms[idx].momentum = new.momentum + atoms[idx].symbol = new.symbol + atoms[idx].tag = new.tag + atoms[idx].charge = new.charge else: initial_ligand = self.ligand.rotate(align_to=self.action_vector, remove_anchor=True) for idx in self.selection: From 4bfb4443f3e516921ac856bbaa5b46b42d7b0873 Mon Sep 17 00:00:00 2001 From: Kristjan Eimre Date: Thu, 27 Aug 2020 00:16:15 +0200 Subject: [PATCH 2/4] Set default cell to uploaded structures without a cell (#116) Co-authored-by: Aliaksandr Yakutovich --- aiidalab_widgets_base/structures.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 53621bc88..c8efc067b 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -47,7 +47,7 @@ def __init__(self, importers, viewer=None, editors=None, storable=True, node_cla """ Arguments: importers(list): list of tuples each containing the displayed name of importer and the - importer object. Each object should containt 'structure' trait pointing to the imported + importer object. Each object should contain 'structure' trait pointing to the imported structure. The trait will be linked to 'structure' trait of this class. storable(bool): Whether to provide Store button (together with Store format) @@ -314,6 +314,22 @@ def __init__(self, title='', description="Upload Structure"): self.file_upload.observe(self._on_file_upload, names='value') super().__init__(children=[self.file_upload, supported_formats]) + def _validate_and_fix_ase_cell(self, ase_structure, vacuum_ang=10.0): + """ + Checks if the ase Atoms object has a cell set, + otherwise sets it to bounding box plus specified "vacuum" space + """ + cell = ase_structure.cell + + if (np.linalg.norm(cell[0]) < 0.1 or np.linalg.norm(cell[1]) < 0.1 or np.linalg.norm(cell[2]) < 0.1): + # if any of the cell vectors is too short, consider it faulty + # set cell as bounding box + vacuum_ang + bbox = np.ptp(ase_structure.positions, axis=0) + new_structure = ase_structure.copy() + new_structure.cell = bbox + vacuum_ang + return new_structure + return ase_structure + def _on_file_upload(self, change=None): """When file upload button is pressed.""" for fname, item in change['new'].items(): @@ -321,7 +337,8 @@ def _on_file_upload(self, change=None): if frmt == 'cif': self.structure = CifData(file=io.BytesIO(item['content'])) else: - self.structure = get_ase_from_file(io.StringIO(item['content'].decode()), format=frmt) + self.structure = self._validate_and_fix_ase_cell( + get_ase_from_file(io.StringIO(item['content'].decode()), format=frmt)) self.file_upload.value.clear() break From 84e90dddeb458d809a2c1f1dc2ed66f7651cd928 Mon Sep 17 00:00:00 2001 From: Carlo Pignedoli Date: Mon, 31 Aug 2020 09:54:54 +0200 Subject: [PATCH 3/4] Improv stability of smiles renderer (#117) Co-authored-by: Aliaksandr Yakutovich --- aiidalab_widgets_base/structures.py | 32 +++++++++++++++-------------- setup.json | 3 ++- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index c8efc067b..d736dbb0f 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -8,6 +8,8 @@ import ipywidgets as ipw from traitlets import Instance, Int, List, Unicode, Union, dlink, link, default, observe +from sklearn.decomposition import PCA + # ASE imports import ase from ase import Atom, Atoms @@ -538,13 +540,13 @@ def __init__(self, title=''): def pymol_2_ase(pymol): """Convert pymol object into ASE Atoms.""" - asemol = Atoms() - for atm in pymol.atoms: - asemol.append(Atom(chemical_symbols[atm.atomicnum], atm.coords)) - asemol.cell = np.amax(asemol.positions, axis=0) - np.amin(asemol.positions, axis=0) + [10] * 3 - asemol.pbc = True - asemol.center() - return asemol + species = [chemical_symbols[atm.atomicnum] for atm in pymol.atoms] + pos = np.asarray([atm.coords for atm in pymol.atoms]) + pca = PCA(n_components=3) + posnew = pca.fit_transform(pos) + atoms = Atoms(species, positions=posnew, pbc=True, cell=np.ptp(posnew, axis=0) + 10) + atoms.center() + return atoms def _optimize_mol(self, mol): """Optimize a molecule using force field (needed for complex SMILES).""" @@ -555,17 +557,16 @@ def _optimize_mol(self, mol): self.output.value = "Screening possible conformers {}".format(self.SPINNER) #font-size:20em; - f_f = pybel._forcefields["mmff94"] # pylint: disable=protected-access + f_f = pybel._forcefields["uff"] # pylint: disable=protected-access if not f_f.Setup(mol.OBMol): - f_f = pybel._forcefields["uff"] # pylint: disable=protected-access + f_f = pybel._forcefields["mmff94"] # pylint: disable=protected-access if not f_f.Setup(mol.OBMol): self.output.value = "Cannot set up forcefield" return - # initial cleanup before the weighted search - f_f.SteepestDescent(5500, 1.0e-9) - f_f.WeightedRotorSearch(15000, 500) - f_f.ConjugateGradients(6500, 1.0e-10) + # Initial cleanup before the weighted search. + f_f.Setup(mol.OBMol) + f_f.SteepestDescent(5000, 1.0e-9) f_f.GetCoordinates(mol.OBMol) self.output.value = "" @@ -580,12 +581,13 @@ def _on_button_pressed(self, change): # pylint: disable=unused-argument if not self.smiles.value: return - mol = pybel.readstring("smi", self.smiles.value) + mol = pybel.readstring("smiles", self.smiles.value) self.output.value = """SMILES to 3D conversion {}""".format(self.SPINNER) mol.make3D() + mol.addh() pybel._builder.Build(mol.OBMol) # pylint: disable=protected-access - mol.addh() + self._optimize_mol(mol) self.structure = self.pymol_2_ase(mol) diff --git a/setup.json b/setup.json index bc729d8b2..ec427b716 100644 --- a/setup.json +++ b/setup.json @@ -22,7 +22,8 @@ ], "extras_require": { "testing": [ - "aiida-core[testing]~=1.0" + "aiida-core[testing]~=1.0", + "scikit-learn==0.23.2" ], "pre-commit": [ "pre-commit==1.17.0", From 186a086c12f8e8d6a81115ac0a96c8084f3fdd81 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 31 Aug 2020 21:00:16 +0200 Subject: [PATCH 4/4] Prepare release 1.0.0b10 --- aiidalab_widgets_base/__init__.py | 2 +- setup.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aiidalab_widgets_base/__init__.py b/aiidalab_widgets_base/__init__.py index 4fb48675b..404838a02 100644 --- a/aiidalab_widgets_base/__init__.py +++ b/aiidalab_widgets_base/__init__.py @@ -18,4 +18,4 @@ from .structures_multi import MultiStructureUploadWidget from .viewers import viewer -__version__ = "1.0.0b9" +__version__ = "1.0.0b10" diff --git a/setup.json b/setup.json index ec427b716..8a1e94a11 100644 --- a/setup.json +++ b/setup.json @@ -1,5 +1,5 @@ { - "version": "1.0.0b9", + "version": "1.0.0b10", "name": "aiidalab-widgets-base", "author_email": "aiidalab@materialscloud.org", "url": "https://github.com/aiidalab/aiidalab-widgets-base",