import _modeller from modeller.util import modutil __docformat__ = "epytext en" class feature(object): """Base class for all features defined on model atoms""" _builtin_index = None _user_index = None numatoms = 0 def __init__(self, *atoms): if '_builtin_index' not in self.__class__.__dict__ \ and self._user_index is None: self.__class__._user_index = \ _modeller.mod_user_feature_new(self.eval, self.deriv, self.is_angle) (self.__atoms, self.__mdl) = self.__get_list_atom_indices(atoms) self._check_atoms(self.__atoms) def get_value(self, deriv=False): """Get the current feature value, and also derivatives (with respect to atomic coordinates) if C{deriv} is True.""" (inds, mdl) = self.get_atom_indices() val = self.eval(mdl, inds) if deriv: return (val, self.deriv(mdl, inds, val)) else: return val def is_angle(self): """Return True if the feature is an angle (i.e. measures in radians, and has a 2pi periodicity).""" if self._builtin_index is not None: return _modeller.mod_feature_isangle(self._builtin_index) else: raise NotImplementedError("is_angle not defined for feature " + str(self)) def get_atom_indices(self): """Returns the atom indices this feature is defined on""" return (self.__atoms, self.__mdl) def __get_list_atom_indices(self, atoms): inds = [] mdl = None for obj in atoms: if modutil.non_string_iterable(obj) \ and not hasattr(obj, 'get_atom_indices'): (objinds, objmdl) = self.__get_list_atom_indices(obj) else: (objinds, objmdl) = obj.get_atom_indices() if mdl is None: mdl = objmdl elif mdl != objmdl: raise ValueError("All atoms must be from the same model") inds.extend(objinds) return (inds, mdl) def _check_atoms(self, atoms): if len(atoms) != self.numatoms: raise ValueError("This feature is defined on %d atoms - got %d" % (self.numatoms, len(atoms))) def get_type(cls): """Get the numeric type of this feature""" if cls._user_index is not None: return cls._user_index elif cls._builtin_index is not None: return cls._builtin_index else: raise ValueError("Cannot get type - no objects created yet") get_type = classmethod(get_type) def eval(self, mdl, atom_indices): if self._builtin_index is not None: return _modeller.mod_feature_eval(mdl.modpt, atom_indices, self._builtin_index) else: raise NotImplementedError("Value not defined for feature " + str(self)) def deriv(self, mdl, atom_indices, feat): if self._builtin_index is not None: return _modeller.mod_feature_deriv(mdl.modpt, atom_indices, self._builtin_index, feat) else: raise NotImplementedError("Derivatives not defined for feature " + str(self)) def indices_to_atoms(self, mdl, atom_indices): """Converts Modeller-style atom indices into atom objects""" return [mdl.atoms[x-1] for x in atom_indices] class distance(feature): """Cartesian distance between two atoms""" _builtin_index = 1 numatoms = 2 class angle(feature): """Angle (in radians) between three atoms""" _builtin_index = 2 numatoms = 3 class dihedral(feature): """Dihedral angle (in radians) between four atoms""" _builtin_index = 3 numatoms = 4 class minimal_distance(feature): """Distance between the nearest pair of atoms""" _builtin_index = 6 def _check_atoms(self, atoms): if len(atoms) % 2 != 0 or len(atoms) == 0: raise ValueError("This feature is defined on a non-zero even " + "number of atoms - got %d" % len(atoms)) class solvent_access(feature): """Atomic area exposed to solvent""" _builtin_index = 7 numatoms = 1 class density(feature): """Atomic density (number of atoms within contact_shell)""" _builtin_index = 8 numatoms = 1 class x_coordinate(feature): """x coordinate of an atom""" _builtin_index = 9 numatoms = 1 class y_coordinate(feature): """y coordinate of an atom""" _builtin_index = 10 numatoms = 1 class z_coordinate(feature): """z coordinate of an atom""" _builtin_index = 11 numatoms = 1 class dihedral_diff(feature): """Difference (in radians) between two dihedrals""" _builtin_index = 12 numatoms = 8