Source code for schrodinger.application.matsci.clusterstructgui

"""
GUI elements to aid in pulling dimers or clusters of nearest
neighbors from a larger structure.

Copyright Schrodinger, LLC. All rights reserved.
"""

import collections

from schrodinger.application.matsci import clusterstruct
from schrodinger.application.matsci import gutils
from schrodinger.application.matsci import rdpattern
from schrodinger.Qt import QtCore
from schrodinger.ui.qt import swidgets

CRITERIA = [
    clusterstruct.NO_MOLS, clusterstruct.ONE_ONLY_MOL,
    clusterstruct.AT_LEAST_ONE_MOL, clusterstruct.TWO_MOLS
]


[docs]class SpeciesCombo(swidgets.SLabeledComboBox): """ Combobox for showing species """
[docs] def __init__(self, label=None, **kwargs): """ Create a SpeciesCombo instance :param str label: The label for the combobox """ super().__init__(label, **kwargs) if not label: self.label.hide() self._fixTypeComboSizing()
def _fixTypeComboSizing(self): """ Make sure the combo expands if necessary when new species are added """ self.setSizeAdjustPolicy(self.AdjustToContents) policy = self.sizePolicy() policy.setHorizontalPolicy(policy.Minimum) self.setSizePolicy(policy)
[docs] def setCurrentSpecies(self, species): """ Fill the combo with the list of current species :type species: dict :param species: Keys are unique SMILES strings, values are SpeciesData objects for the species with that SMILES string. """ self.clear() itemdict = clusterstruct.get_species_display_names(species.values()) self.addItemsFromDict(itemdict)
[docs] def currentSpecies(self): """ Return the currently selected species :rtype: `schrodinger.application.matsci.clusterstruct.SpeciesData` :return: The currently selected species """ return self.currentData()
[docs] def getSampleMolNumber(self): """ Get a sample molecule number for the current species :rtype: int :return: a sample molecule number for the current species """ return self.currentSpecies().getSampleMolNumber()
[docs]class MonomerSpeciesSelector(swidgets.SCheckBoxWithSubWidget): """ Checkbox and combo that allows the user to select a species. The enabled state of the combobox is controlled by the checkbox. """ species_changed = QtCore.pyqtSignal()
[docs] def __init__(self, label='Limit active molecules to those of type:', command=None, find_species_fn=None, **kwargs): """ Create a Monomer SpeciesSelector instance :type label: str :param label: The text between the checkbox and combobox :type command: callable :param command: The slot to connect to the species_changed signal :type find_species_fn: function :param find_species_fn: Function that is used to create species All other keyword args are passed to the SCheckBoxWithSubWidget class """ self.species = {} self.find_species_fn = (find_species_fn if find_species_fn else clusterstruct.find_species) self.subframe = swidgets.SFrame(layout_type=swidgets.HORIZONTAL) self.createSubWidgets() swidgets.SCheckBoxWithSubWidget.__init__(self, label, self.subframe, checked=False, **kwargs) if command: self.species_changed.connect(command)
[docs] def createSubWidgets(self): """ Create the subwidgets the checkbox controls For this class, it is only the species type combobox """ sflayout = self.subframe.mylayout self.type_combo = SpeciesCombo(nocall=True, layout=sflayout, command=self.speciesChanged)
[docs] def currentSpecies(self): """ Return the currently selected species :rtype: `schrodinger.application.matsci.clusterstruct.SpeciesData` :return: The currently selected species """ return self.type_combo.currentSpecies()
[docs] def speciesChanged(self): """ React to a new species being selected """ self.species_changed.emit()
[docs] def hasSpecies(self): """ Check if any species have been loaded :rtype: bool :return: True of any species has been loaded, False if not """ return bool(self.type_combo.count())
[docs] def clear(self): """ Clear out the species combo """ self.type_combo.clear()
[docs] def loadSpecies(self, structs): """ Find all the species in the given structures and load them into the species type combobox :type structs: list of `schrodinger.structure.Structure` :param structs: The structures to find the species in :rtype: list of `schrodinger.application.matsci.clusterstruct.SpeciesData` :return: Each item of the list is the data for a species found """ species = self.find_species_fn(structs) self.setCurrentSpecies(species) return species
[docs] def setCurrentSpecies(self, species): """ Fill the species combo with the list of current species :type species: dict :param species: Keys are unique SMILES strings, values are SpeciesData objects for the species with that SMILES string. """ self.species = species self.type_combo.setCurrentSpecies(species)
[docs] def reset(self): """ Reset the widget, including clearing the species type combo """ swidgets.SCheckBoxWithSubWidget.reset(self) self.clear()
[docs] def getNumberOfType(self): """ Get the number of members of the current species :rtype: int :return: The number of members of the current species """ data = self.type_combo.currentData() if data: return len(data.members) else: return 0
[docs]class DimerSpeciesSelector(MonomerSpeciesSelector): """ Checkbox and combo that allows the user to select a species for the purpose of selecting dimers with or without the given species. The enabled state of the combobox is controlled by the checkbox. """ criterion_changed = QtCore.pyqtSignal(str)
[docs] def __init__(self, label='Limit dimers to only those that contain:', command=None, **kwargs): """ Create a DimerSpeciesSelector instance :type label: str :param label: The text between the checkbox and combobox :type command: callable :param command: The slot to connect to the species_changed and criterion_changed signals See parent class for additional documentation """ MonomerSpeciesSelector.__init__(self, label=label, command=command, **kwargs) if command: self.criterion_changed.connect(command)
[docs] def createSubWidgets(self): """ Create the subwidgets the checkbox controls For this class, it is an amount criterion combobox and the species type combobox """ sflayout = self.subframe.mylayout self.crit_combo = swidgets.SComboBox(items=CRITERIA, command=self.criterionChanged, nocall=True, layout=sflayout) self.type_combo = SpeciesCombo(label='molecules of type:', command=self.speciesChanged, nocall=True, layout=sflayout)
[docs] def currentCriterion(self): """ Return the current amount criterion :rtype: str :return: The desired amount of the species. Will be one of the items from the CRITERIA list """ return self.crit_combo.currentText()
[docs] def criterionChanged(self): """ React to a change in the amount criterion """ self.criterion_changed.emit(self.currentCriterion())
[docs] def reset(self): """ Reset the widgets """ MonomerSpeciesSelector.reset(self) self.crit_combo.reset()
[docs] def applyToDimers(self, dimers): """ Apply the current settings to a list of dimers, marking them as meeting the criteria or not. If the controlling checkbox is unchecked, all dimers will be marked as meeting the criteria. :type dimers: list :param dimers: A list of Dimer objects. Each dimer will have its meets_species_criterion property set based on the results of the Dimer.evaluateSpeciesCriterion method. """ if self.isChecked(): crit_species = self.currentSpecies() crit = self.currentCriterion() species = self.species else: crit_species = crit = species = None for dimer in dimers: dimer.evaluateSpeciesCriterion(crit_species, crit, species)
[docs]def count_species_in_cms(model, sanitize=True, include_stereo=True): """ Enumerates and counts the species in a Desmond system. For all-atom systems, this means counting the the number of molecules in each `model.comp_ct`. For coarse-grained systems, this means finding all unique molecules and counting the number of them. :param schrodinger.application.desmond.cms.Cms model: The model you want to get the species names for :param bool sanitize: Whether RDKit sanitization should be performed when identifying unique species to display. This option is not applicable for coarsegrained structures. :param bool include_stereo: Whether the stereochemistry of the structure should be considered when identifying unique species to display. Setting to `False` can speed this up substantially. :rtype: collections.OrderedDict :return: Dictionary whose keys are the display names of the species and whose values are the number of molecules of that species present in the system. """ display_names = get_species_display_names_from_cms( model, sanitize=sanitize, include_stereo=include_stereo) counts = collections.OrderedDict() for name, species in display_names.items(): counts[name] = len(species) return counts
[docs]def get_species_display_names_from_cms(model, sanitize=True, include_stereo=True): """ Gets prettier display names of each component's species from a Desmond system. They are formatted for printing in a GUI. :param schrodinger.application.desmond.cms.Cms model: The model you want to get the species names for :param bool sanitize: Whether RDKit sanitization should be performed when identifying unique species to display. This option is not applicable for coarsegrained structures. :param bool include_stereo: Whether the stereochemistry of the structure should be considered when identifying unique species to display. Setting to `False` can speed this up substantially. :rtype: collections.OrderedDict :return: Keys are the names of each species found in the system, and values are their corresponding `SpeciesData` objects. """ # Find each species, and then sort it to get consistent ordering from this # function. To speed this step up, you can set sanitize=True and # stereo=False pattern = rdpattern.Pattern(model.fsys_ct, sanitize=sanitize, include_stereo=include_stereo) species_dict = clusterstruct.find_mol_species_from_rdmol([pattern]) all_species = [species for _, species in sorted(species_dict.items())] # CG formulae look like (PEO)10 while AA formulae look like C6H6. We need # to handle subscripting them differently. if pattern.is_coarse_grain: subscripting_function = gutils.subscript_cg_formula else: subscripting_function = gutils.subscript_chemical_formula # Since we simplify chemical formulae, we end up pooling isomeric molecules # onto identical display names. To un-pool them, we keep track of repeats # via this counter dictionary. See MATSCI-12378. isomer_counts = collections.defaultdict(int) display_names = collections.OrderedDict() for species in all_species: names = clusterstruct.get_species_display_names([species]) display_name = list(names.keys())[0] # Currently, display names are printed like "formula foo:bar baz". We # only want to fix the formatting of the "formula" part. split_name = display_name.split(' ') name = split_name[0] suffix = ' '.join(split_name[1:]) formula = subscripting_function(name) display_formula = f'{formula} {suffix}' # If we find a 2nd isomer, turn the original isomer label from "name # suffix" to "name_1 suffix" isomer_counts[display_formula] += 1 isomer_count = isomer_counts[display_formula] if isomer_count == 2: og_species = display_names.pop(display_formula) updated_display_formula = f'{formula}_1 {suffix}' display_names[updated_display_formula] = og_species # All subsequent isomer names get their suffixes incremented by 1 each # time if isomer_count > 1: display_formula = f'{formula}_{isomer_count} {suffix}' display_names[display_formula] = species n_species = len(all_species) n_names = len(display_names) if n_names != n_species: msg = (f'Found {n_species} species in {model.title}, but only ' f'{n_names} species names to display.') raise RuntimeError(msg) return display_names