Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions prody/dynamics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,9 +296,9 @@
from .anm import *
__all__.extend(anm.__all__)

from . import constrained_anm
from .constrained_anm import *
__all__.extend(constrained_anm.__all__)
from . import generalized_anm
from .generalized_anm import *
__all__.extend(generalized_anm.__all__)

from . import rtb
from .rtb import *
Expand Down
34 changes: 16 additions & 18 deletions prody/dynamics/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import os
from os.path import abspath, join, isfile, isdir, split, splitext
#from constrained_anm import cANM
#from generalized_anm import genANM

import numpy as np

Expand Down Expand Up @@ -494,25 +494,24 @@ class that supports selection or a :class:`~numpy.ndarray`.
exanm.buildHessian(atoms, gamma=gamma, **kwargs)
enm = exanm
MaskedModel = MaskedExANM
elif model in ('canm', 'constrained', 'constrained_anm'):
# Your Constrained ANM model
elif model.lower() in ('genanm', 'generalized', 'generalized_anm'):
# Generalized ANM model
try:
from constrained_anm import cANM # your local file
from generalized_anm import genANM # your local file
except ImportError:
# fallback if you later put constrained_anm inside the ProDy dynamics package
from .constrained_anm import cANM
canm = cANM(title)

# Extract constrained-ANM parameters, with defaults if missing
cutoff = kwargs.pop('cutoff', 15.0) # your example value
k_theta = kwargs.pop('k_theta', 10.0) # example
k_phi = kwargs.pop('k_phi', 1.0) # example
kappa = kwargs.pop('kappa', 20.0) # example
from .generalized_anm import genANM
genanm = genANM(title)

# Extract generalized-ANM parameters, with defaults if missing
cutoff = kwargs.pop('cutoff', 15.0)
k_theta = kwargs.pop('k_theta', 10.0)
k_phi = kwargs.pop('k_phi', 1.0)
kappa = kwargs.pop('kappa', 20.0)
include_sequential = kwargs.pop('include_sequential', True)
symmetrize = kwargs.pop('symmetrize', True)

# Build constrained Hessian
canm.buildHessian(atoms,
# Build generalized Hessian
genanm.buildHessian(atoms,
cutoff=cutoff,
gamma=gamma,
k_theta=k_theta,
Expand All @@ -521,9 +520,8 @@ class that supports selection or a :class:`~numpy.ndarray`.
include_sequential=include_sequential,
symmetrize=symmetrize)

enm = canm

# Reuse the same masked model as ANM because cANM is a subclass of ANM
enm = genanm
# Reuse the same masked model as ANM because genANM is a subclass of ANM
MaskedModel = MaskedANM
else:
raise TypeError('model should be either ANM or GNM instead of {0}'.format(model))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# -*- coding: utf-8 -*-
"""Constrained ANM (cANM)
A ProDy-compatible ANM subclass that builds a constrained Hessian with:
"""Generalized ANM (genANM)
A ProDy-compatible ANM subclass that builds a generalized Hessian with:
- pairwise distance springs (ANM-style)
- 3-body bond-angle terms
- 4-body torsional/dihedral terms
- backbone curvature (discrete second-difference) term

This module provides:
- class cANM(ANM)
- calcCANM(...) convenience function, analogous to calcANM in anm.py
- class genANM(ANM)
- calcGenANM(...) convenience function, analogous to calcANM in anm.py

Notes
-----
Expand All @@ -30,10 +30,10 @@
from prody.dynamics.anm import ANM
from prody.dynamics.gnm import checkENMParameters

__all__ = ['cANM', 'calcCANM']
__all__ = ['genANM', 'calcGenANM']

# ---------------------------------------------------------------------------
# Internal helpers to assemble constrained Hessian
# Internal helpers to assemble generalized Hessian
# ---------------------------------------------------------------------------

def _pairwise_blocks(coords: np.ndarray, cutoff: float, k_pair: float, include_sequential: bool) -> np.ndarray:
Expand Down Expand Up @@ -138,7 +138,7 @@ def _backbone_curvature_blocks(coords: np.ndarray, kappa: float) -> np.ndarray:
return H


def build_constrained_hessian(
def build_generalized_hessian(
coords: np.ndarray,
cutoff: float = 10.0,
gamma: float = 1.0,
Expand All @@ -148,7 +148,7 @@ def build_constrained_hessian(
include_sequential: bool = True,
symmetrize: bool = True,
) -> np.ndarray:
"""Assemble the constrained Hessian from an (N,3) coordinate array."""
"""Assemble the generalized Hessian from an (N,3) coordinate array."""
H = (
_pairwise_blocks(coords, cutoff=cutoff, k_pair=gamma, include_sequential=include_sequential)
+ _angle_blocks(coords, k_theta=k_theta)
Expand All @@ -164,14 +164,14 @@ def build_constrained_hessian(
# ProDy-compatible subclass
# ---------------------------------------------------------------------------

class cANM(ANM):
"""Constrained ANM that behaves like a ProDy ANM/NMA object."""
class genANM(ANM):
"""Generalized ANM that behaves like a ProDy ANM/NMA object."""

def __init__(self, name: str = 'cANM') -> None:
def __init__(self, name: str = 'genANM') -> None:
super().__init__(name)

def buildHessian(self, coords, cutoff=15., gamma=1., **kwargs):
"""Build constrained Hessian from coordinates or an object exposing getCoords().
"""Build generalized Hessian from coordinates or an object exposing getCoords().

Additional keyword arguments (defaults shown):
k_theta=0.5
Expand All @@ -190,10 +190,9 @@ def buildHessian(self, coords, cutoff=15., gamma=1., **kwargs):
except TypeError:
raise TypeError('coords must be a Numpy array or an object with `getCoords` method')

#cutoff, g, gamma_checked = checkENMParameters(cutoff, gamma)
cutoff, g, gamma_func = checkENMParameters(cutoff, gamma)

# constrained-specific parameters
# generalized-specific parameters
k_theta = float(kwargs.pop('k_theta', 0.5))
k_phi = float(kwargs.pop('k_phi', 0.2))
kappa = float(kwargs.pop('kappa', 0.8))
Expand All @@ -202,7 +201,7 @@ def buildHessian(self, coords, cutoff=15., gamma=1., **kwargs):

# Do not support sparse construction in this reference implementation
if kwargs.pop('sparse', False):
raise NotImplementedError('sparse=True is not supported by constrained ANM (dense NumPy used).')
raise NotImplementedError('sparse=True is not supported by generalized ANM (dense NumPy used).')

coords = np.asarray(coords, dtype=float)
if coords.ndim != 2 or coords.shape[1] != 3:
Expand All @@ -212,21 +211,11 @@ def buildHessian(self, coords, cutoff=15., gamma=1., **kwargs):
self._cutoff = cutoff
self._gamma = g
n_atoms = coords.shape[0]
#H = build_constrained_hessian(
# coords,
# cutoff=cutoff,
# gamma=gamma_checked,
# k_theta=k_theta,
# k_phi=k_phi,
# kappa=kappa,
# include_sequential=include_sequential,
# symmetrize=symmetrize,
#)

H = build_constrained_hessian(

H = build_generalized_hessian(
coords,
cutoff=cutoff,
gamma=g, # <--- Use 'g' (the value) instead of the function
gamma=g,
k_theta=k_theta,
k_phi=k_phi,
kappa=kappa,
Expand All @@ -245,9 +234,9 @@ def buildHessian(self, coords, cutoff=15., gamma=1., **kwargs):
# Convenience functions
# ---------------------------------------------------------------------------

def calcCANM(pdb, selstr='calpha', cutoff=15., gamma=1., n_modes=20,
zeros=False, title=None, **kwargs):
"""Perform cANM calculation and return (cANM, selection) similar to calcANM.
def calcGenANM(pdb, selstr='calpha', cutoff=15., gamma=1., n_modes=20,
zeros=False, title=None, **kwargs):
"""Perform genANM calculation and return (genANM, selection) similar to calcANM.

Accepts the same kinds of inputs as calcANM:
- numpy.ndarray (Hessian or coords) => if array is Hessian, accepted directly
Expand All @@ -261,7 +250,7 @@ def calcCANM(pdb, selstr='calpha', cutoff=15., gamma=1., n_modes=20,
H = pdb
if title is None:
title = 'Unknown'
model = cANM(title)
model = genANM(title)
model.setHessian(H)
model.calcModes(n_modes, zeros)
return model
Expand All @@ -281,7 +270,7 @@ def calcCANM(pdb, selstr='calpha', cutoff=15., gamma=1., n_modes=20,
else:
raise TypeError('pdb must be an atomic class, not {0}'.format(type(pdb)))

model = cANM(title)
model = genANM(title)
sel = ag.select(selstr)
model.buildHessian(sel, cutoff, gamma, **kwargs)
model.calcModes(n_modes, zeros)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import numpy as np
import pytest

from prody.dynamics.constrained_anm import cANM, calcCANM
from prody.dynamics.generalized_anm import genANM, calcGenANM

def _sample_coords():
return np.array([
Expand All @@ -13,21 +13,21 @@ def _sample_coords():

def test_build_hessian_symmetry_and_shape():
coords = _sample_coords()
model = cANM('test')
model = genANM('test')
H = model.buildHessian(coords, cutoff=5.0, gamma=1.0)
assert H.shape == (12, 12)
assert np.allclose(H, H.T, atol=1e-8)

def test_calc_modes_and_calcCANM_with_array():
def test_calc_modes_and_calcGenANM_with_array():
coords = _sample_coords()
model = cANM('test2')
model = genANM('test2')
H = model.buildHessian(coords, cutoff=5.0)
model.calcModes(n_modes=6, zeros=False)
eigvals = model.getEigvals()
assert eigvals is not None and eigvals.size > 0

# calcCANM accepting Hessian array should return an ANM-like model
m2 = calcCANM(H, n_modes=6, zeros=False)
# calcGenANM accepting Hessian array should return an ANM-like model
m2 = calcGenANM(H, n_modes=6, zeros=False)
assert hasattr(m2, 'getHessian')
# calcCANM sets a symmetric copy via setHessian in our implementation
# calcGenANM sets a symmetric copy via setHessian in our implementation
assert np.allclose(m2.getHessian(), 0.5*(H + H.T), atol=1e-8)
Loading