Source code for schrodinger.project.surface

"""
Pythonic wrappings for mmsurf surfaces retrieved from a project
"""

from collections.abc import MutableMapping

import schrodinger
from schrodinger.infra import mm
from schrodinger.infra import mmproj
from schrodinger.infra import mmsurf
from schrodinger.surface import MolSurfType
from schrodinger.surface import Surface
from schrodinger.surface import _requires_update

# This module is first imported while Maestro is loading, so
# schrodinger.get_maestro() will return a _DummyMaestroModule if we run it now.
# Instead, we import Maestro when the first Surface object is instantiated.
maestro = DELAYED_MAESTRO_LOAD = object()


[docs]class ProjectSurface(Surface): """ A Pythonic wrapping for mmsurf surfaces retrieved from a project. ProjectSurface objects are typically accessed via the `schrodinger.project.ProjectRow.surface` `SurfaceDict` object or created via `schrodinger.project.ProjectRow.newMolecularSurface`. """
[docs] def __init__(self, proj_handle, eid, name, proj_row): """ :param proj_handle: The project handle :type proj_handle: int :param eid: The entry ID of the entry containing the surface :type eid: str :param name: The name of the surface :type name: str :note: This method does not confirm that the specified surface exists, as this check is done in `SurfaceDict.__getitem__`. Attempting to get or set values on a non-existent surface will lead to a RuntimeError. """ self._proj_handle = proj_handle self._eid = eid self._name = name self._proj_row = proj_row self._force_update = False # For _requires_update self._initializeMmlibs() # If this is the first ProjectSurface object to be instantiated, try # importing maestro global maestro if maestro is DELAYED_MAESTRO_LOAD: maestro = schrodinger.get_maestro()
def __del__(self): self._terminateMmlibs() @property def _handle(self): """ The mmsurf handle for this surface. Note that mmproj may change the mmsurf handle whenever the entry leaves the workspace, so we fetch the mmsurf handle using the surface name. This ensures that we're not using a stale handle. :type: int :raise RuntimeError: If the underlying surface has been deleted or cannot be found. """ if self._name is None: raise RuntimeError("Surface has been deleted") try: return mmproj.mmproj_entry_get_surface(self._proj_handle, self._eid, self._name) except mm.MmException: err = ("Cannot retrieve mmsurf surface. Surface may have been " "deleted or may have changed names.") raise RuntimeError(err) def _checkHandle(self): """ Make sure that our mmsurf handle is still valid. Raise a RuntimeError if it isn't. """ # Calculating the handle number will raise a RuntimeError if the # underlying surface can't be found _ = self._handle
[docs] @_requires_update def delete(self): """ Delete this surface. After the surface is deleted, any further attempt to interact with this object will result in a RuntimeError. """ self._checkHandle() mmproj.mmproj_entry_delete_surface(self._proj_handle, self._eid, self._name) self._name = None
[docs] @_requires_update def setColoring(self, coloring): """ Set the surface coloring. Must be one of: - A `ColorBy` value other than `ColorBy.SourceColor` to color based on the nearest atom - A `Color` value for constant coloring - A list or numpy array containing a color for each vertex """ super().setColoring(coloring) # Set surface to update surface data in project(MAE-43506) mmproj.mmproj_entry_set_surface(self._proj_handle, self._eid, self._handle)
@property def name(self): """ The name of the surface. Note that all surfaces for a given project entry have unique names. :type: str """ self._checkHandle() return self._name @name.setter def name(self, value): err = ("Cannot set name of ProjectSurface via attribute assignment. " "Use rename() instead. Note that two surfaces for the same " "entry may not have the same name.") raise RuntimeError(err)
[docs] def rename(self, new_name, overwrite=True): """ Rename this surface :param new_name: The new surface name :type new_name: str :param overwrite: What to do if the new name is the same as an existing surface for this project row. If True, the existing surface will be overwritten. In False, a ValueError will be raised. :type overwrite: bool """ self._checkHandle() try: mmproj.mmproj_entry_get_surface(self._proj_handle, self._eid, new_name) except mm.MmException: # No surface exists with the desired name, so we don't need to worry # about a name clash pass else: if overwrite: mmproj.mmproj_entry_delete_surface(self._proj_handle, self._eid, new_name) else: err = "Surface of name %s already exists." % new_name raise ValueError(err) mmproj.mmproj_entry_rename_surface(self._proj_handle, self._eid, self._name, new_name) self._name = new_name
def _updateMaestro(self): """ Tell Maestro to update the workspace surface representation. """ if maestro: maestro.update_surface_display()
[docs] @classmethod def newMolecularSurface(cls, proj, row, name, asl=None, atoms=None, resolution=0.5, probe_radius=None, vdw_scaling=1.0, mol_surf_type=MolSurfType.molecular, overwrite=True): """ Create a new molecular surface for the specified project row :param proj: The project that this surface will be part of :type proj: `schrodinger.project.Project` :param row: The project row that this surface will belong to. This is the structure that the surface will be created around. :type row: `schrodinger.project.ProjectRow` :param name: The name of the surface. Note that project rows require all surfaces to be named uniquely. See `overwrite`. :type name: str :param asl: If given, the surface will only be created for atoms in the structure that match the provided ASL. Note that only one of `asl` and `atoms` may be given. If neither are given, then the surface will be created for all atoms in the structure. :type asl: str or NoneType :param atoms: An optional list of atom numbers. If given, the surface will only be created for the specified atoms. Note that only one of `asl` and `atoms` may be given. If neither are given, then the surface will be created for all atoms in the structure. :type atoms: list or NoneType :param resolution: The resolution of the surface, generally between 0 and 1. Smaller numbers lead to a more highly detailed surface. :type resolution: float :param probe_radius: The radius of the rolling sphere used to calculate the surface. Defaults to 1.4 if `mol_surf_type` is `MolSurfType.Molecular` or `MolSurfType.Extended`. May not be given if `mol_surf_type` is `MolSurfType.vdw`. :type probe_radius: float :param vdw_scaling: If given, all atomic radii will be scaled by the provided value before the surface is calculated. :type vdw_scaling: float :param mol_surf_type: The type of surface to create. :type mol_surf_type: `MolSurfType` :param overwrite: What to do if the new surface has the same name as an existing surface for this project row. If True, the existing surface will be overwritten. In False, a ValueError will be raised. :type overwrite: bool :return: The new surface :rtype: `ProjectSurface` """ struc = row.getStructure() if not overwrite: cls._checkName(proj, row, name) mmsurf.mmsurf_initialize(mm.MMERR_DEFAULT_HANDLER) handle = cls._createMolecularSurface(struc, name, asl, atoms, resolution, probe_radius, vdw_scaling, mol_surf_type) surf = cls._addHandleToProject(handle, proj, row, name) mmsurf.mmsurf_terminate() return surf
@classmethod def _addHandleToProject(cls, handle, proj, row, name): """ Add the specified mmsurf handle to a project. :param handle: The mmsurf handle to add. :type handle: int :param proj: The project to add the surface to. :type proj: `schrodinger.project.Project` :param row: The project row to add the surface to. :type row: `schrodinger.project.ProjectRow` :param name: The name of the surface :type name: str """ mmproj.mmproj_entry_set_surface(proj.handle, row.entry_id, handle) surf = cls(proj.handle, row.entry_id, name, row) surf._updateMaestro() return surf
[docs] @classmethod def addSurfaceToProject(cls, surf, proj, row, overwrite=True, copy=False): """ Add an existing `Surface` object to a project. Note that, by default, this method will invalidate the input `Surface` object, as the mmsurf handle will be managed by the project. :param surf: The surface to add. :type surf: `Surface` :param proj: The project to add the surface to. :type proj: `schrodinger.project.Project` :param row: The project row to add the surface to. :type row: `schrodinger.project.ProjectRow` :param overwrite: What to do if the new surface has the same name as an existing surface for this project row. If True, the existing surface will be overwritten. In False, a ValueError will be raised. :type overwrite: bool :param copy: If True, a copy of the surface will be added to the project and the input surface will not be invalidated. :type copy: bool """ if not overwrite: cls._checkName(proj, row, surf.name) if copy: surf = surf.copy() proj_surf = cls._addHandleToProject(surf._handle, proj, row, surf.name) # The input mmsurf handle is no longer valid since the surface now # belongs to the project, so invalidate the Surface object surf._manage = False surf._handle = -1 return proj_surf
@staticmethod def _checkName(proj, row, name): """ Make sure that no surface of the specified name exists. Raise a ValueError if one does. :param proj: The project to check :type proj: `schrodinger.project.Project` :param row: The project row to check :type row: `schrodinger.project.ProjectRow` :param name: The name to check :type name: str """ try: mmproj.mmproj_entry_get_surface(proj.handle, row.entry_id, name) except mm.MmException: pass else: raise ValueError("Surface of name %s already exists." % name)
[docs] @classmethod def read(cls, filename): err = ("Cannot read a file directly into a ProjectSurface object. Use " "project_row.surface.addFromFile() instead.") raise RuntimeError(err)
[docs]class SurfaceDict(MutableMapping): """ A dictionary of {surface name: `ProjectSurface` object} for the specified project row. Note that surfaces may not be created by assignment to a dictionary value. Use `add`, {addFromFile}, or `schrodinger.project.ProjectRow.newMolecularSurface` instead. `SurfaceDict` objects are typically accessed via `schrodinger.project.ProjectRow.surface`. """
[docs] def __init__(self, proj, row): """ :param proj: The project :type proj: `schrodinger.project.Project` :param row: The project row :type row: L{schrodinger.project.ProjectRow """ self._proj = proj self._proj_handle = proj.handle self._row = row self._eid = row.entry_id
[docs] def __len__(self): return sum(1 for _ in self)
def __getitem__(self, name): try: mmproj.mmproj_entry_get_surface(self._proj_handle, self._eid, name) except mm.MmException: raise KeyError("No surface %s found for row %s." % (name, self._eid)) else: return ProjectSurface(self._proj_handle, self._eid, name, self._row) def __setitem__(self, key, value): raise RuntimeError("Use add() to add surfaces to a SurfaceDict") def __delitem__(self, name): surf = self[name] surf.delete() def __iter__(self): try: name = mmproj.mmproj_entry_get_first_surface_name( self._proj_handle, self._eid) except mm.MmException: return else: yield name while True: try: name = mmproj.mmproj_entry_get_next_surface_name( self._proj_handle, self._eid, name) except mm.MmException: return else: yield name
[docs] def add(self, surf, overwrite=True, copy=False): """ Add an existing `Surface` object to the project row. Note that if copy is False (the default), this method will invalidate the input `Surface` object, as the mmsurf handle will be managed by the project. :param surf: The surface to add. :type surf: `Surface` :param overwrite: What to do if the new surface has the same name as an existing surface for this project row. If True, the existing surface will be overwritten. In False, a ValueError will be raised. :type overwrite: bool :param copy: If True, a copy of the surface will be added to the project and the input surface will not be invalidated. :type copy: bool :return: A `ProjectSurface` object for the added surface. :rtype: `ProjectSurface` """ return ProjectSurface.addSurfaceToProject(surf, self._proj, self._row, overwrite, copy)
[docs] def addFromFile(self, filename, name=None, overwrite=True): """ Read a surface from a file and add it to the project row. :param filename: The file to read :type filename: str :param name: If given, the surface will be renamed to this before being loaded into the project. :type name: str :param overwrite: What to do if the new surface has the same name as an existing surface for this project row. If True, the existing surface will be overwritten. In False, a ValueError will be raised. :type overwrite: bool :return: A `ProjectSurface` object for the added surface. :rtype: `ProjectSurface` """ surf = Surface.read(filename) if name is not None: surf.name = name return self.add(surf, overwrite)