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.
"""

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: 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: 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(structure.Structure) :param structs: The structures to find the species in :rtype: list(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. :param cms.Cms model: The model you want to count the species for :param bool sanitize: Whether RDKit sanitization should be performed when identifying unique species. 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. Setting to `False` can speed this up substantially. :rtype: dict :return: Dictionary whose keys are the display formulae 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 = {} 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 formatted chemical formulae for each species from a Desmond system. They are formatted for printing in a GUI. :param cms.Cms model: The model you want to get the formulae for :param bool sanitize: Whether RDKit sanitization should be performed when identifying unique species to get chemical formulae for. 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 analyze. Setting to `False` can speed this up substantially. :rtype: dict(str=clusterstruct.SpeciesData) :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())] display_names = _get_display_names_from_species( all_species, is_cg=pattern.is_coarse_grain) return display_names
def _get_display_names_from_species(species, *, is_cg=False): """ Turns a list of `SpeciesData` objects into a list of human-readable chemical formulae meant for displaying in a GUI. :param list(clusterstruct.SpeciesData) species: list of species you want to get display names for :param bool is_cg: whether all the species coarse-grained or not :rtype: dict(str=clusterstruct.SpeciesData) :return: Keys are the names of each species found in the system, and values are their corresponding `SpeciesData` objects. """ display_names = clusterstruct.get_species_display_names(species) subscripted_names = _subscript_species_display_names(display_names, is_cg=is_cg) # We've been bitten by de-synchronized species and names before. Check if # it happens again. See MATSCI-12378 n_species = len(species) n_names = len(subscripted_names) if n_names != n_species: msg = (f'Tried to find display names for {n_species} species, but was ' f'only able to make {n_names} names') raise RuntimeError(msg) return subscripted_names def _subscript_species_display_names(display_names, *, is_cg=False): """ Turns a list of `SpeciesData` objects into a list of human-readable chemical formulae meant for displaying in a GUI. :param list(clusterstruct.SpeciesData) species: list of species you want to get display names for :param bool is_cg: whether all the species coarse-grained or not :rtype: dict(str=clusterstruct.SpeciesData) :return: Keys are the names of each species found in the system, and values are their corresponding `SpeciesData` objects. """ # CG formulae look like (PEO)10 while AA formulae look like C6H6. We need # to handle subscripting them differently. if is_cg: subscripting_function = gutils.subscript_cg_formula else: subscripting_function = gutils.subscript_chemical_formula subscripted_display_names = {} for display_name, species in display_names.items(): # 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(' ') formula = split_name[0] suffix = ' '.join(split_name[1:]) subscripted_formula = subscripting_function(formula) new_display_name = f'{subscripted_formula} {suffix}' subscripted_display_names[new_display_name] = species return subscripted_display_names