Source code for fast_forward.constraint_coupling
from . import DATA_PATH
import numpy as np
from vermouth.data import COMMON_CITATIONS
from vermouth.citation_parser import citation_formatter, read_bib
from collections import ChainMap
[docs]
def compute_coupling(u, constraints):
'''
Function to calculate the constraint coupling matrix A used in the LINCS algorithm.
Adopted from https://github.com/bio-phys/constraint-coupling-analysis
Parameters
----------
u: :class:`~MDAnalysis.core.universe.Universe`
constraints: list
List of constraints in the system
Returns
-------
A: :class:`~numpy.ndarray`
Constraint coupling matrix
'''
# sort the constraint list
for c in constraints:
if c.atoms[0] > c.atoms[1]:
c.atoms[0], c.atoms[1] = c.atoms[1], c.atoms[0]
constraints = sorted(constraints, key=lambda x: (x.atoms[0], x.atoms[1]))
n_con = len(constraints)
Sdiag = [np.sqrt(1/u.atoms[c.atoms[0]].mass +1/u.atoms[c.atoms[1]].mass) for c in constraints]
Sdiag_inv = [1/elem for elem in Sdiag]
# normalized constraint direction vectors
B = np.array([u.atoms[c.atoms[0]].position - u.atoms[c.atoms[1]].position for c in constraints])
B = np.array([v/np.linalg.norm(v) for v in B])
# CONSTRAINT COUPLING MATRIX
A = np.zeros((n_con,n_con))
mass_factor = np.zeros((n_con,n_con))
# Iterate through pairs of constraints
for i,c1 in enumerate(constraints):
for j,c2 in enumerate(constraints):
# find the common atom between the two constraints
common = set(tuple(c1.atoms)) & set(tuple(c2.atoms))
if len(common) != 1: # coupling is zero if no identical atoms of if constraints are identical
continue
common = common.pop() # convert set to int
# signs
if (c1.atoms[0] == c2.atoms[0]) or (c1.atoms[1] == c2.atoms[1]):
sign = -1
else:
sign = +1
# mass factor
invmass = 1/u.atoms[common].mass
mass_factor[i,j] = sign * (invmass*Sdiag_inv[i]*Sdiag_inv[j])
# constraint couplings
A[i,j] = mass_factor[i,j] * np.dot(B[i],B[j])
return A
[docs]
def report_constraint_coupling(u, block, estimate_lincs=False):
'''
Function to check the constraint coupling of a molecule.
Will print a warning if the constraint coupling is high.
Parameters
----------
block: :class:`~vermouth.molecule.Block`
Block containing the molecule of interest.
estimate_lincs: bool
optional.
Whether to report the estimated LINCS order required for the constraints, defaults to False
'''
constraints = block.interactions['constraints']
if len(constraints) <= 0:
if estimate_lincs:
print(f"[ Constraint Coupling Report for {block.name} ]\nNo constraints coupling detected.\n")
return
A = compute_coupling(u, constraints)
# check if A contains inf or nan
if np.isnan(A).any() or np.isinf(A).any():
print('WARNING: unable to calculate constraint coupling matrix, contains NaN or Inf values.')
print(' Check the masses of the atoms in the itp.')
return
# eigenvalues
w, _ = np.linalg.eig(A)
eig_max = np.abs(w).max() # lambda_max
if eig_max == 0:
if estimate_lincs:
print(f"[ Constraint Coupling Report for {block.name} ]\nNo constraints coupling detected.\n")
return
# ESTIMATE LINCS ORDER:
lincs_order = int(np.log(0.4**4)/np.log(eig_max))
threshold = 0.8
if estimate_lincs or eig_max > threshold:
with open(DATA_PATH/'citations.bib') as citation_file:
ff_citations = read_bib(citation_file)
buff = '\n'
buff += f"[ Constraint Coupling Report for {{molname}} ]\n"
if eig_max >= threshold:
buff += "WARNING: High constraint coupling detected, consider changing your bonded network!\n\n"
buff += f"Largest eigenvalue: {{eig_max:.2f}}"
buff += f"\nEstimated LINCS order: {{lincs_order}}\n\n"
buff += "For more information see:\n"
citations = {'cholesterol_constraints'}
citation_map = ChainMap(ff_citations, COMMON_CITATIONS)
for citation in citations:
cite_string = citation_formatter(
citation_map[citation]
)
buff += (cite_string) + "\n"
printable = buff.format(molname=block.name,
eig_max=eig_max,
lincs_order=lincs_order)
print(printable)