diff --git a/.github/workflows/ut.yml b/.github/workflows/ut.yml index e219fdcad..caa0c77c4 100644 --- a/.github/workflows/ut.yml +++ b/.github/workflows/ut.yml @@ -24,6 +24,7 @@ jobs: conda update pip conda install numpy openmm pytest -c conda-forge pip install jax jax_md + pip install mdtraj==1.9.7 pymbar==4.0.1 - name: Install DMFF run: | source $CONDA/bin/activate diff --git a/.gitignore b/.gitignore index 8b6f1c54d..74c7e80e1 100644 --- a/.gitignore +++ b/.gitignore @@ -781,4 +781,7 @@ FodyWeavers.xsd *.acpype/ */_date.py -*/_version.py \ No newline at end of file +*/_version.py + +# hmtff cache +*.hmtff/ \ No newline at end of file diff --git a/dmff/__init__.py b/dmff/__init__.py index c27289a1d..1a443c204 100644 --- a/dmff/__init__.py +++ b/dmff/__init__.py @@ -1,3 +1,4 @@ from .settings import * from .common.nblist import NeighborList, NeighborListFreud -from .api import Hamiltonian \ No newline at end of file +from .api import Hamiltonian +from .generators import * \ No newline at end of file diff --git a/dmff/api.py b/dmff/api.py index 56a386c53..d2af46683 100644 --- a/dmff/api.py +++ b/dmff/api.py @@ -1,54 +1,13 @@ -import sys import linecache -import itertools -from collections import defaultdict -from typing import Dict -import xml.etree.ElementTree as ET -from copy import deepcopy -import warnings import numpy as np import jax.numpy as jnp import openmm as mm import openmm.app as app -import openmm.app.element as elem import openmm.unit as unit - -from dmff.admp.disp_pme import ADMPDispPmeForce -from dmff.admp.multipole import convert_cart2harm, convert_harm2cart -from dmff.admp.pairwise import (TT_damping_qq_c6_kernel, - generate_pairwise_interaction, - slater_disp_damping_kernel, slater_sr_kernel, - TT_damping_qq_kernel) -from dmff.admp.pme import ADMPPmeForce, setup_ewald_parameters -from dmff.classical.intra import ( - HarmonicBondJaxForce, - HarmonicAngleJaxForce, - PeriodicTorsionJaxForce, -) -from dmff.classical.inter import ( - LennardJonesForce, - LennardJonesLongRangeForce, - CoulombPMEForce, - CoulNoCutoffForce, - CoulombPMEForce, - CoulReactionFieldForce, - LennardJonesForce, -) -from .classical.intra import ( - HarmonicAngleJaxForce, - HarmonicBondJaxForce, - PeriodicTorsionJaxForce, -) -from dmff.classical.fep import (LennardJonesFreeEnergyForce, - LennardJonesLongRangeFreeEnergyForce, - CoulombPMEFreeEnergyForce) -from dmff.utils import jit_condition, isinstance_jnp, DMFFException, findItemInList -from dmff.fftree import ForcefieldTree, XMLParser, TypeMatcher -from collections import defaultdict - -jaxGenerators = {} +from dmff.utils import DMFFException +from dmff.fftree import ForcefieldTree, XMLParser def get_line_context(file_path, line_number): @@ -86,1025 +45,8 @@ def findAtomTypeTexts(attribs, num): return typetxt -class ADMPDispGenerator: - def __init__(self, ff): - - self.name = "ADMPDispForce" - self.ff = ff - self.fftree = ff.fftree - self.paramtree = ff.paramtree - - # default params - self._jaxPotential = None - self.types = [] - self.ethresh = 5e-04 - self.pmax = 10 - - def extract(self): - - mScales = [self.fftree.get_attribs(f'{self.name}', f'mScale1{i}')[0] for i in range(2, 7)] - mScales.append(1.0) - self.paramtree[self.name] = {} - self.paramtree[self.name]['mScales'] = jnp.array(mScales) - - ABQC = self.fftree.get_attribs(f'{self.name}/Atom', ['A', 'B', 'Q', 'C6', 'C8', 'C10']) - - ABQC = np.array(ABQC) - A = ABQC[:, 0] - B = ABQC[:, 1] - Q = ABQC[:, 2] - C6 = ABQC[:, 3] - C8 = ABQC[:, 4] - C10 = ABQC[:, 5] - - self.paramtree[self.name]['A'] = jnp.array(A) - self.paramtree[self.name]['B'] = jnp.array(B) - self.paramtree[self.name]['Q'] = jnp.array(Q) - self.paramtree[self.name]['C6'] = jnp.array(C6) - self.paramtree[self.name]['C8'] = jnp.array(C8) - self.paramtree[self.name]['C10'] = jnp.array(C10) - - atomTypes = self.fftree.get_attribs(f'{self.name}/Atom', f'type') - if type(atomTypes[0]) != str: - self.atomTypes = np.array(atomTypes, dtype=int).astype(str) - else: - self.atomTypes = np.array(atomTypes) - - - def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, - args): - - methodMap = { - app.CutoffPeriodic: "CutoffPeriodic", - app.NoCutoff: "NoCutoff", - app.PME: "PME", - } - if nonbondedMethod not in methodMap: - raise ValueError("Illegal nonbonded method for ADMPDispForce") - if nonbondedMethod is app.CutoffPeriodic: - self.lpme = False - else: - self.lpme = True - - n_atoms = len(data.atoms) - # build index map - map_atomtype = np.zeros(n_atoms, dtype=int) - for i in range(n_atoms): - atype = data.atomType[data.atoms[i]] - map_atomtype[i] = np.where(self.atomTypes == atype)[0][0] - self.map_atomtype = map_atomtype - # build covalent map - self.covalent_map = build_covalent_map(data, 6) - # here box is only used to setup ewald parameters, no need to be differentiable - a, b, c = system.getDefaultPeriodicBoxVectors() - box = jnp.array([a._value, b._value, c._value]) * 10 - # get the admp calculator - rc = nonbondedCutoff.value_in_unit(unit.angstrom) - - # get calculator - if "ethresh" in args: - self.ethresh = args["ethresh"] - - Force_DispPME = ADMPDispPmeForce(box, - rc, - self.ethresh, - self.pmax, - lpme=self.lpme) - self.disp_pme_force = Force_DispPME - pot_fn_lr = Force_DispPME.get_energy - pot_fn_sr = generate_pairwise_interaction(TT_damping_qq_c6_kernel, - static_args={}) - - def potential_fn(positions, box, pairs, params): - params = params[self.name] - mScales = params["mScales"] - a_list = (params["A"][map_atomtype] / 2625.5 - ) # kj/mol to au, as expected by TT_damping kernel - b_list = params["B"][map_atomtype] * 0.0529177249 # nm^-1 to au - q_list = params["Q"][map_atomtype] - c6_list = jnp.sqrt(params["C6"][map_atomtype] * 1e6) - c8_list = jnp.sqrt(params["C8"][map_atomtype] * 1e8) - c10_list = jnp.sqrt(params["C10"][map_atomtype] * 1e10) - c_list = jnp.vstack((c6_list, c8_list, c10_list)) - - E_sr = pot_fn_sr(positions, box, pairs, mScales, a_list, b_list, - q_list, c_list[0]) - E_lr = pot_fn_lr(positions, box, pairs, c_list.T, mScales) - return E_sr - E_lr - - self._jaxPotential = potential_fn - # self._top_data = data - - def overwrite(self): - - self.fftree.set_attrib(f'{self.name}', 'mScale12', [self.paramtree[self.name]['mScales'][0]]) - self.fftree.set_attrib(f'{self.name}', 'mScale13', [self.paramtree[self.name]['mScales'][1]]) - self.fftree.set_attrib(f'{self.name}', 'mScale14', [self.paramtree[self.name]['mScales'][2]]) - self.fftree.set_attrib(f'{self.name}', 'mScale15', [self.paramtree[self.name]['mScales'][3]]) - self.fftree.set_attrib(f'{self.name}', 'mScale16', [self.paramtree[self.name]['mScales'][4]]) - - self.fftree.set_attrib(f'{self.name}/Atom', 'A', [self.paramtree[self.name]['A']]) - self.fftree.set_attrib(f'{self.name}/Atom', 'B', [self.paramtree[self.name]['B']]) - self.fftree.set_attrib(f'{self.name}/Atom', 'Q', [self.paramtree[self.name]['Q']]) - self.fftree.set_attrib(f'{self.name}/Atom', 'C6', [self.paramtree[self.name]['C6']]) - self.fftree.set_attrib(f'{self.name}/Atom', 'C8', [self.paramtree[self.name]['C8']]) - self.fftree.set_attrib(f'{self.name}/Atom', 'C10', [self.paramtree[self.name]['C10']]) - - - def getJaxPotential(self): - return self._jaxPotential - -jaxGenerators['ADMPDispForce'] = ADMPDispGenerator - - -class ADMPDispPmeGenerator: - r""" - This one computes the undamped C6/C8/C10 interactions - u = \sum_{ij} c6/r^6 + c8/r^8 + c10/r^10 - """ - def __init__(self, ff): - self.ff = ff - self.fftree = ff.fftree - self.paramtree = ff.paramtree - - self.params = {"C6": [], "C8": [], "C10": []} - self._jaxPotential = None - self.atomTypes = None - self.ethresh = 5e-04 - self.pmax = 10 - self.name = "ADMPDispPmeForce" - - def extract(self): - - mScales = [self.fftree.get_attribs(f'{self.name}', f'mScale1{i}')[0] for i in range(2, 7)] - mScales.append(1.0) - - self.paramtree[self.name] = {} - self.paramtree[self.name]['mScales'] = jnp.array(mScales) - - C6 = self.fftree.get_attribs(f'{self.name}/Atom', f'C6') - C8 = self.fftree.get_attribs(f'{self.name}/Atom', f'C8') - C10 = self.fftree.get_attribs(f'{self.name}/Atom', f'C10') - - self.paramtree[self.name]['C6'] = jnp.array(C6) - self.paramtree[self.name]['C8'] = jnp.array(C8) - self.paramtree[self.name]['C10'] = jnp.array(C10) - - atomTypes = self.fftree.get_attribs(f'{self.name}/Atom', f'type') - if type(atomTypes[0]) != str: - self.atomTypes = np.array(atomTypes, dtype=int).astype(str) - else: - self.atomTypes = np.array(atomTypes) - - def overwrite(self): - - self.fftree.set_attrib(f'{self.name}', 'mScale12', [self.paramtree[self.name]['mScales'][0]]) - self.fftree.set_attrib(f'{self.name}', 'mScale13', [self.paramtree[self.name]['mScales'][1]]) - self.fftree.set_attrib(f'{self.name}', 'mScale14', [self.paramtree[self.name]['mScales'][2]]) - self.fftree.set_attrib(f'{self.name}', 'mScale15', [self.paramtree[self.name]['mScales'][3]]) - self.fftree.set_attrib(f'{self.name}', 'mScale16', [self.paramtree[self.name]['mScales'][4]]) - - self.fftree.set_attrib(f'{self.name}/Atom', 'C6', self.paramtree[self.name]['C6']) - self.fftree.set_attrib(f'{self.name}/Atom', 'C8', self.paramtree[self.name]['C8']) - self.fftree.set_attrib(f'{self.name}/Atom', 'C10', self.paramtree[self.name]['C10']) - - - def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, - args): - methodMap = { - app.CutoffPeriodic: "CutoffPeriodic", - app.NoCutoff: "NoCutoff", - app.PME: "PME", - } - if nonbondedMethod not in methodMap: - raise ValueError("Illegal nonbonded method for ADMPDispPmeForce") - if nonbondedMethod is app.CutoffPeriodic: - self.lpme = False - else: - self.lpme = True - - n_atoms = len(data.atoms) - # build index map - map_atomtype = np.zeros(n_atoms, dtype=int) - for i in range(n_atoms): - atype = data.atomType[data.atoms[i]] - map_atomtype[i] = np.where(self.atomTypes == atype)[0][0] - self.map_atomtype = map_atomtype - - # build covalent map - self.covalent_map = build_covalent_map(data, 6) - - # here box is only used to setup ewald parameters, no need to be differentiable - a, b, c = system.getDefaultPeriodicBoxVectors() - box = jnp.array([a._value, b._value, c._value]) * 10 - # get the admp calculator - rc = nonbondedCutoff.value_in_unit(unit.angstrom) - - # get calculator - if "ethresh" in args: - self.ethresh = args["ethresh"] - - disp_force = ADMPDispPmeForce(box, rc, self.ethresh, - self.pmax, self.lpme) - self.disp_force = disp_force - pot_fn_lr = disp_force.get_energy - - def potential_fn(positions, box, pairs, params): - params = params[self.name] - mScales = params["mScales"] - C6_list = params["C6"][map_atomtype] * 1e6 # to kj/mol * A**6 - C8_list = params["C8"][map_atomtype] * 1e8 - C10_list = params["C10"][map_atomtype] * 1e10 - c6_list = jnp.sqrt(C6_list) - c8_list = jnp.sqrt(C8_list) - c10_list = jnp.sqrt(C10_list) - c_list = jnp.vstack((c6_list, c8_list, c10_list)) - E_lr = pot_fn_lr(positions, box, pairs, c_list.T, mScales) - return -E_lr - - self._jaxPotential = potential_fn - # self._top_data = data - - def getJaxPotential(self): - return self._jaxPotential - -jaxGenerators['ADMPDispPmeForce'] = ADMPDispPmeGenerator - - -class QqTtDampingGenerator: - r""" - This one calculates the tang-tonnies damping of charge-charge interaction - E = \sum_ij exp(-B*r)*(1+B*r)*q_i*q_j/r - """ - def __init__(self, ff): - self.ff = ff - self.fftree = ff.fftree - self._jaxPotential = None - self.paramtree = ff.paramtree - self._jaxPotnetial = None - self.name = "QqTtDampingForce" - - def extract(self): - # get mscales - mScales = [self.fftree.get_attribs(f'{self.name}', f'mScale1{i}')[0] for i in range(2, 7)] - mScales.append(1.0) - self.paramtree[self.name] = {} - self.paramtree[self.name]['mScales'] = jnp.array(mScales) - # get atomtypes - atomTypes = self.fftree.get_attribs(f'{self.name}/Atom', f'type') - if type(atomTypes[0]) != str: - self.atomTypes = np.array(atomTypes, dtype=int).astype(str) - else: - self.atomTypes = np.array(atomTypes) - # get atomic parameters - B = self.fftree.get_attribs(f'{self.name}/Atom', f'B') - Q = self.fftree.get_attribs(f'{self.name}/Atom', f'Q') - self.paramtree[self.name]['B'] = jnp.array(B) - self.paramtree[self.name]['Q'] = jnp.array(Q) - - - def overwrite(self): - - self.fftree.set_attrib(f'{self.name}', 'mScale12', [self.paramtree[self.name]['mScales'][0]]) - self.fftree.set_attrib(f'{self.name}', 'mScale13', [self.paramtree[self.name]['mScales'][1]]) - self.fftree.set_attrib(f'{self.name}', 'mScale14', [self.paramtree[self.name]['mScales'][2]]) - self.fftree.set_attrib(f'{self.name}', 'mScale15', [self.paramtree[self.name]['mScales'][3]]) - self.fftree.set_attrib(f'{self.name}', 'mScale16', [self.paramtree[self.name]['mScales'][4]]) - - self.fftree.set_attrib(f'{self.name}/Atom', 'B', self.paramtree[self.name]['B']) - self.fftree.set_attrib(f'{self.name}/Atom', 'Q', self.paramtree[self.name]['Q']) - - # on working - def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, - args): - - n_atoms = len(data.atoms) - # build index map - map_atomtype = np.zeros(n_atoms, dtype=int) - for i in range(n_atoms): - atype = data.atomType[data.atoms[i]] - map_atomtype[i] = np.where(self.atomTypes == atype)[0][0] - self.map_atomtype = map_atomtype - - # build covalent map - self.covalent_map = build_covalent_map(data, 6) - - pot_fn_sr = generate_pairwise_interaction(TT_damping_qq_kernel, - static_args={}) - - def potential_fn(positions, box, pairs, params): - params = params[self.name] - mScales = params["mScales"] - b_list = params["B"][map_atomtype] / 10 # convert to A^-1 - q_list = params["Q"][map_atomtype] - - E_sr = pot_fn_sr(positions, box, pairs, mScales, b_list, q_list) - return E_sr - - self._jaxPotential = potential_fn - - def getJaxPotential(self): - return self._jaxPotential - -# register all parsers -jaxGenerators['QqTtDampingForce'] = QqTtDampingGenerator - - -class SlaterDampingGenerator: - r""" - This one computes the slater-type damping function for c6/c8/c10 dispersion - E = \sum_ij (f6-1)*c6/r6 + (f8-1)*c8/r8 + (f10-1)*c10/r10 - fn = f_tt(x, n) - x = br - (2*br2 + 3*br) / (br2 + 3*br + 3) - """ - def __init__(self, ff): - self.name = "SlaterDampingForce" - self.ff = ff - self.fftree = ff.fftree - self.paramtree = ff.paramtree - self._jaxPotential = None - - def extract(self): - # get mscales - mScales = [self.fftree.get_attribs(f'{self.name}', f'mScale1{i}')[0] for i in range(2, 7)] - mScales.append(1.0) - self.paramtree[self.name] = {} - self.paramtree[self.name]['mScales'] = jnp.array(mScales) - # get atomtypes - atomTypes = self.fftree.get_attribs(f'{self.name}/Atom', f'type') - if type(atomTypes[0]) != str: - self.atomTypes = np.array(atomTypes, dtype=int).astype(str) - else: - self.atomTypes = np.array(atomTypes) - # get atomic parameters - B = self.fftree.get_attribs(f'{self.name}/Atom', f'B') - C6 = self.fftree.get_attribs(f'{self.name}/Atom', f'C6') - C8 = self.fftree.get_attribs(f'{self.name}/Atom', f'C8') - C10 = self.fftree.get_attribs(f'{self.name}/Atom', f'C10') - self.paramtree[self.name]['B'] = jnp.array(B) - self.paramtree[self.name]['C6'] = jnp.array(C6) - self.paramtree[self.name]['C8'] = jnp.array(C8) - self.paramtree[self.name]['C10'] = jnp.array(C10) - - - def overwrite(self): - - self.fftree.set_attrib(f'{self.name}', 'mScale12', [self.paramtree[self.name]['mScales'][0]]) - self.fftree.set_attrib(f'{self.name}', 'mScale13', [self.paramtree[self.name]['mScales'][1]]) - self.fftree.set_attrib(f'{self.name}', 'mScale14', [self.paramtree[self.name]['mScales'][2]]) - self.fftree.set_attrib(f'{self.name}', 'mScale15', [self.paramtree[self.name]['mScales'][3]]) - self.fftree.set_attrib(f'{self.name}', 'mScale16', [self.paramtree[self.name]['mScales'][4]]) - - self.fftree.set_attrib(f'{self.name}/Atom', 'B', [self.paramtree[self.name]['B']]) - self.fftree.set_attrib(f'{self.name}/Atom', 'C6', [self.paramtree[self.name]['C6']]) - self.fftree.set_attrib(f'{self.name}/Atom', 'C8', [self.paramtree[self.name]['C8']]) - self.fftree.set_attrib(f'{self.name}/Atom', 'C10', [self.paramtree[self.name]['C10']]) - - - def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, - args): - - n_atoms = len(data.atoms) - # build index map - map_atomtype = np.zeros(n_atoms, dtype=int) - for i in range(n_atoms): - atype = data.atomType[data.atoms[i]] - map_atomtype[i] = np.where(self.atomTypes == atype)[0][0] - self.map_atomtype = map_atomtype - # build covalent map - self.covalent_map = build_covalent_map(data, 6) - - # WORKING - pot_fn_sr = generate_pairwise_interaction(slater_disp_damping_kernel, - static_args={}) - - def potential_fn(positions, box, pairs, params): - params = params[self.name] - mScales = params["mScales"] - b_list = params["B"][map_atomtype] / 10 # convert to A^-1 - c6_list = jnp.sqrt(params["C6"][map_atomtype] * - 1e6) # to kj/mol * A**6 - c8_list = jnp.sqrt(params["C8"][map_atomtype] * 1e8) - c10_list = jnp.sqrt(params["C10"][map_atomtype] * 1e10) - E_sr = pot_fn_sr(positions, box, pairs, mScales, b_list, c6_list, - c8_list, c10_list) - return E_sr - - self._jaxPotential = potential_fn - # self._top_data = data - - def getJaxPotential(self): - return self._jaxPotential - -jaxGenerators['SlaterDampingForce'] = SlaterDampingGenerator - - - -class SlaterExGenerator: - r""" - This one computes the Slater-ISA type exchange interaction - u = \sum_ij A * (1/3*(Br)^2 + Br + 1) * exp(-B * r) - """ - def __init__(self, ff): - self.name = "SlaterExForce" - self.ff = ff - self.fftree = ff.fftree - self.paramtree = ff.paramtree - self._jaxPotential = None - - - def extract(self): - # get mscales - mScales = [self.fftree.get_attribs(f'{self.name}', f'mScale1{i}')[0] for i in range(2, 7)] - mScales.append(1.0) - self.paramtree[self.name] = {} - self.paramtree[self.name]['mScales'] = jnp.array(mScales) - # get atomtypes - atomTypes = self.fftree.get_attribs(f'{self.name}/Atom', f'type') - if type(atomTypes[0]) != str: - self.atomTypes = np.array(atomTypes, dtype=int).astype(str) - else: - self.atomTypes = np.array(atomTypes) - # get atomic parameters - A = self.fftree.get_attribs(f'{self.name}/Atom', f'A') - B = self.fftree.get_attribs(f'{self.name}/Atom', f'B') - self.paramtree[self.name]['A'] = jnp.array(A) - self.paramtree[self.name]['B'] = jnp.array(B) - - - def overwrite(self): - - self.fftree.set_attrib(f'{self.name}', 'mScale12', [self.paramtree[self.name]['mScales'][0]]) - self.fftree.set_attrib(f'{self.name}', 'mScale13', [self.paramtree[self.name]['mScales'][1]]) - self.fftree.set_attrib(f'{self.name}', 'mScale14', [self.paramtree[self.name]['mScales'][2]]) - self.fftree.set_attrib(f'{self.name}', 'mScale15', [self.paramtree[self.name]['mScales'][3]]) - self.fftree.set_attrib(f'{self.name}', 'mScale16', [self.paramtree[self.name]['mScales'][4]]) - - self.fftree.set_attrib(f'{self.name}/Atom', 'A', [self.paramtree[self.name]['A']]) - self.fftree.set_attrib(f'{self.name}/Atom', 'B', [self.paramtree[self.name]['B']]) - - - def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, - args): - - n_atoms = len(data.atoms) - # build index map - map_atomtype = np.zeros(n_atoms, dtype=int) - for i in range(n_atoms): - atype = data.atomType[data.atoms[i]] - map_atomtype[i] = np.where(self.atomTypes == atype)[0][0] - self.map_atomtype = map_atomtype - # build covalent map - self.covalent_map = build_covalent_map(data, 6) - - pot_fn_sr = generate_pairwise_interaction(slater_sr_kernel, - static_args={}) - - def potential_fn(positions, box, pairs, params): - params = params[self.name] - mScales = params["mScales"] - a_list = params["A"][map_atomtype] - b_list = params["B"][map_atomtype] / 10 # nm^-1 to A^-1 - - return pot_fn_sr(positions, box, pairs, mScales, a_list, b_list) - - self._jaxPotential = potential_fn - # self._top_data = data - - def getJaxPotential(self): - return self._jaxPotential - - -jaxGenerators["SlaterExForce"] = SlaterExGenerator - - -# Here are all the short range "charge penetration" terms -# They all have the exchange form -class SlaterSrEsGenerator(SlaterExGenerator): - def __init__(self, ff): - super().__init__(ff) - self.name = "SlaterSrEsForce" - - def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, - args): - - n_atoms = len(data.atoms) - # build index map - map_atomtype = np.zeros(n_atoms, dtype=int) - for i in range(n_atoms): - atype = data.atomType[data.atoms[i]] - map_atomtype[i] = np.where(self.atomTypes == atype)[0][0] - self.map_atomtype = map_atomtype - # build covalent map - covalent_map = build_covalent_map(data, 6) - - pot_fn_sr = generate_pairwise_interaction(slater_sr_kernel, - static_args={}) - def potential_fn(positions, box, pairs, params): - params = params[self.name] - mScales = params["mScales"] - a_list = params["A"][map_atomtype] - b_list = params["B"][map_atomtype] / 10 # nm^-1 to A^-1 - - # add minus sign - return - pot_fn_sr(positions, box, pairs, mScales, a_list, b_list) - self._jaxPotential = potential_fn - - -class SlaterSrPolGenerator(SlaterSrEsGenerator): - def __init__(self, ff): - super().__init__(ff) - self.name = "SlaterSrPolForce" - - -class SlaterSrDispGenerator(SlaterSrEsGenerator): - def __init__(self, ff): - super().__init__(ff) - self.name = "SlaterSrDispForce" - - -class SlaterDhfGenerator(SlaterSrEsGenerator): - def __init__(self, ff): - super().__init__(ff) - self.name = "SlaterDhfForce" - - -# register all parsers -jaxGenerators["SlaterSrEsForce"] = SlaterSrEsGenerator -jaxGenerators["SlaterSrPolForce"] = SlaterSrPolGenerator -jaxGenerators["SlaterSrDispForce"] = SlaterSrDispGenerator -jaxGenerators["SlaterDhfForce"] = SlaterDhfGenerator - - -class ADMPPmeGenerator: - - def __init__(self, ff): - - self.name = 'ADMPPmeForce' - self.ff = ff - self.fftree = ff.fftree - self.paramtree = ff.paramtree - - # default params - self._jaxPotential = None - self.types = [] - self.ethresh = 5e-04 - self.step_pol = None - self.lpol = False - self.ref_dip = "" - - def extract(self): - - self.lmax = self.fftree.get_attribs(f'{self.name}', 'lmax')[0] # return [lmax] - - mScales = [self.fftree.get_attribs(f'{self.name}', f'mScale1{i}')[0] for i in range(2, 7)] - pScales = [self.fftree.get_attribs(f'{self.name}', f'pScale1{i}')[0] for i in range(2, 7)] - dScales = [self.fftree.get_attribs(f'{self.name}', f'dScale1{i}')[0] for i in range(2, 7)] - - # make sure the last digit is 1.0 - mScales.append(1.0) - pScales.append(1.0) - dScales.append(1.0) - - self.paramtree[self.name] = {} - self.paramtree[self.name]['mScales'] = jnp.array(mScales) - self.paramtree[self.name]['pScales'] = jnp.array(pScales) - self.paramtree[self.name]['dScales'] = jnp.array(dScales) - - # check if polarize - polarize = self.fftree.get_nodes(f'{self.name}/Polarize') - if polarize: - self.lpol = True - else: - self.lpol = False - - atomTypes = self.fftree.get_attribs(f'{self.name}/Atom', 'type') - if type(atomTypes[0]) != str: - self.atomTypes = np.array(atomTypes, dtype=int).astype(str) - else: - self.atomTypes = np.array(atomTypes) - kx = self.fftree.get_attribs(f'{self.name}/Atom', 'kx') - ky = self.fftree.get_attribs(f'{self.name}/Atom', 'ky') - kz = self.fftree.get_attribs(f'{self.name}/Atom', 'kz') - - kx = [ 0 if kx_ is None else int(kx_) for kx_ in kx ] - ky = [ 0 if ky_ is None else int(ky_) for ky_ in ky ] - kz = [ 0 if kz_ is None else int(kz_) for kz_ in kz ] - - # invoke by `self.kStrings["kz"][itype]` - self.kStrings = {} - self.kStrings['kx'] = kx - self.kStrings['ky'] = ky - self.kStrings['kz'] = kz - - c0 = self.fftree.get_attribs(f'{self.name}/Atom', 'c0') - dX = self.fftree.get_attribs(f'{self.name}/Atom', 'dX') - dY = self.fftree.get_attribs(f'{self.name}/Atom', 'dY') - dZ = self.fftree.get_attribs(f'{self.name}/Atom', 'dZ') - qXX = self.fftree.get_attribs(f'{self.name}/Atom', 'qXX') - qYY = self.fftree.get_attribs(f'{self.name}/Atom', 'qYY') - qZZ = self.fftree.get_attribs(f'{self.name}/Atom', 'qZZ') - qXY = self.fftree.get_attribs(f'{self.name}/Atom', 'qXY') - qXZ = self.fftree.get_attribs(f'{self.name}/Atom', 'qXZ') - qYZ = self.fftree.get_attribs(f'{self.name}/Atom', 'qYZ') - - # assume that polarize tag match the per atom type - # pol_XX = self.fftree.get_attribs(f'{self.name}/Polarize', 'polarizabilityXX') - # pol_YY = self.fftree.get_attribs(f'{self.name}/Polarize', 'polarizabilityYY') - # pol_ZZ = self.fftree.get_attribs(f'{self.name}/Polarize', 'polarizabilityZZ') - # thole_0 = self.fftree.get_attribs(f'{self.name}/Polarize', 'thole') - if self.lpol: - polarizabilityXX = self.fftree.get_attribs(f'{self.name}/Polarize', 'polarizabilityXX') - polarizabilityYY = self.fftree.get_attribs(f'{self.name}/Polarize', 'polarizabilityYY') - polarizabilityZZ = self.fftree.get_attribs(f'{self.name}/Polarize', 'polarizabilityZZ') - thole = self.fftree.get_attribs(f'{self.name}/Polarize', 'thole') - polarize_types = self.fftree.get_attribs(f'{self.name}/Polarize', 'type') - if type(polarize_types[0]) != str: - polarize_types = np.array(polarize_types, dtype=int).astype(str) - else: - polarize_types = np.array(polarize_types) - self.polarize_types = polarize_types - - n_atoms = len(atomTypes) - - - # assert n_atoms == len(polarizabilityXX), "Number of polarizabilityXX does not match number of atoms!" - - # map atom multipole moments - if self.lmax == 0: - n_mtps = 1 - elif self.lmax == 1: - n_mtps = 4 - elif self.lmax == 2: - n_mtps = 10 - Q = np.zeros((n_atoms, n_mtps)) - - # TDDO: unit conversion - Q[:, 0] = c0 - if self.lmax >= 1: - Q[:, 1] = dX - Q[:, 2] = dY - Q[:, 3] = dZ - Q[:, 1:4] *= 10 - if self.lmax >= 2: - Q[:, 4] = qXX - Q[:, 5] = qYY - Q[:, 6] = qZZ - Q[:, 7] = qXY - Q[:, 8] = qXZ - Q[:, 9] = qYZ - Q[:, 4:10] *= 300 - - # add all differentiable params to self.params - Q_local = convert_cart2harm(Q, self.lmax) - self.paramtree[self.name]["Q_local"] = Q_local - - if self.lpol: - pol = jnp.vstack(( - polarizabilityXX, - polarizabilityYY, - polarizabilityZZ, - )).T - pol = 1000 * jnp.mean(pol, axis=1) - tholes = jnp.array(thole) - self.paramtree[self.name]["pol"] = pol - self.paramtree[self.name]["tholes"] = tholes - else: - pol = None - tholes = None - - - def overwrite(self): - - self.fftree.set_attrib(f'{self.name}', 'mScale12', [self.paramtree[self.name]['mScales'][0]]) - self.fftree.set_attrib(f'{self.name}', 'mScale13', [self.paramtree[self.name]['mScales'][1]]) - self.fftree.set_attrib(f'{self.name}', 'mScale14', [self.paramtree[self.name]['mScales'][2]]) - self.fftree.set_attrib(f'{self.name}', 'mScale15', [self.paramtree[self.name]['mScales'][3]]) - self.fftree.set_attrib(f'{self.name}', 'mScale16', [self.paramtree[self.name]['mScales'][4]]) - - self.fftree.set_attrib(f'{self.name}', 'pScale12', [self.paramtree[self.name]['pScales'][0]]) - self.fftree.set_attrib(f'{self.name}', 'pScale13', [self.paramtree[self.name]['pScales'][1]]) - self.fftree.set_attrib(f'{self.name}', 'pScale14', [self.paramtree[self.name]['pScales'][2]]) - self.fftree.set_attrib(f'{self.name}', 'pScale15', [self.paramtree[self.name]['pScales'][3]]) - self.fftree.set_attrib(f'{self.name}', 'pScale16', [self.paramtree[self.name]['pScales'][4]]) - - self.fftree.set_attrib(f'{self.name}', 'dScale12', [self.paramtree[self.name]['dScales'][0]]) - self.fftree.set_attrib(f'{self.name}', 'dScale13', [self.paramtree[self.name]['dScales'][1]]) - self.fftree.set_attrib(f'{self.name}', 'dScale14', [self.paramtree[self.name]['dScales'][2]]) - self.fftree.set_attrib(f'{self.name}', 'dScale15', [self.paramtree[self.name]['dScales'][3]]) - self.fftree.set_attrib(f'{self.name}', 'dScale16', [self.paramtree[self.name]['dScales'][4]]) - - Q_global = convert_harm2cart(self.paramtree[self.name]['Q_local'], self.lmax) - - - self.fftree.set_attrib(f'{self.name}/Atom', 'c0', Q_global[:, 0]) - self.fftree.set_attrib(f'{self.name}/Atom', 'dX', Q_global[:, 1]) - self.fftree.set_attrib(f'{self.name}/Atom', 'dY', Q_global[:, 2]) - self.fftree.set_attrib(f'{self.name}/Atom', 'dZ', Q_global[:, 3]) - self.fftree.set_attrib(f'{self.name}/Atom', 'qXX', Q_global[:, 4]) - self.fftree.set_attrib(f'{self.name}/Atom', 'qYY', Q_global[:, 5]) - self.fftree.set_attrib(f'{self.name}/Atom', 'qZZ', Q_global[:, 6]) - self.fftree.set_attrib(f'{self.name}/Atom', 'qXY', Q_global[:, 7]) - self.fftree.set_attrib(f'{self.name}/Atom', 'qXZ', Q_global[:, 8]) - self.fftree.set_attrib(f'{self.name}/Atom', 'qYZ', Q_global[:, 9]) - - if self.lpol: - # self.paramtree[self.name]['pol']: every element is the mean value of XX YY ZZ - # get the number of polarize element - n_pol = len(self.paramtree[self.name]['pol']) - self.fftree.set_attrib(f'{self.name}/Polarize', 'polarizabilityXX', [self.paramtree[self.name]['pol'][0]] * n_pol) - self.fftree.set_attrib(f'{self.name}/Polarize', 'polarizabilityYY', [self.paramtree[self.name]['pol'][1]] * n_pol) - self.fftree.set_attrib(f'{self.name}/Polarize', 'polarizabilityZZ', [self.paramtree[self.name]['pol'][2]] * n_pol) - self.fftree.set_attrib(f'{self.name}/Polarize', 'thole', self.paramtree[self.name]['tholes']) - - - def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, - args): - - methodMap = { - app.CutoffPeriodic: "CutoffPeriodic", - app.NoCutoff: "NoCutoff", - app.PME: "PME", - } - if nonbondedMethod not in methodMap: - raise ValueError("Illegal nonbonded method for ADMPPmeForce") - if nonbondedMethod is app.CutoffPeriodic: - self.lpme = False - else: - self.lpme = True - - n_atoms = len(data.atoms) - map_atomtype = np.zeros(n_atoms, dtype=int) - map_poltype = np.zeros(n_atoms, dtype=int) - - for i in range(n_atoms): - atype = data.atomType[data.atoms[i]] # convert str to int to match atomTypes - map_atomtype[i] = np.where(self.atomTypes == atype)[0][0] - if self.lpol: - map_poltype[i] = np.where(self.polarize_types == atype)[0][0] - self.map_atomtype = map_atomtype - if self.lpol: - self.map_poltype = map_poltype - - # here box is only used to setup ewald parameters, no need to be differentiable - a, b, c = system.getDefaultPeriodicBoxVectors() - box = jnp.array([a._value, b._value, c._value]) * 10 - - # get the admp calculator - rc = nonbondedCutoff.value_in_unit(unit.angstrom) - - # build covalent map - self.covalent_map = covalent_map = build_covalent_map(data, 6) - - # build intra-molecule axis - # the following code is the direct transplant of forcefield.py in openmm 7.4.0 - - if self.lmax > 0: - - # setting up axis_indices and axis_type - ZThenX = 0 - Bisector = 1 - ZBisect = 2 - ThreeFold = 3 - ZOnly = 4 # typo fix - NoAxisType = 5 - LastAxisTypeIndex = 6 - - self.axis_types = [] - self.axis_indices = [] - for i_atom in range(n_atoms): - atom = data.atoms[i_atom] - t = data.atomType[atom] - # if t is in type list? - if t in self.atomTypes: - itypes = np.where(self.atomTypes == t)[0] - hit = 0 - # try to assign multipole parameters via only 1-2 connected atoms - for itype in itypes: - if hit != 0: - break - kz = int(self.kStrings["kz"][itype]) - kx = int(self.kStrings["kx"][itype]) - ky = int(self.kStrings["ky"][itype]) - neighbors = np.where(covalent_map[i_atom] == 1)[0] - zaxis = -1 - xaxis = -1 - yaxis = -1 - for z_index in neighbors: - if hit != 0: - break - z_type = int(data.atomType[data.atoms[z_index]]) - if z_type == abs( - kz - ): # find the z atom, start searching for x - for x_index in neighbors: - if x_index == z_index or hit != 0: - continue - x_type = int( - data.atomType[data.atoms[x_index]]) - if x_type == abs( - kx - ): # find the x atom, start searching for y - if ky == 0: - zaxis = z_index - xaxis = x_index - # cannot ditinguish x and z? use the smaller index for z, and the larger index for x - if x_type == z_type and xaxis < zaxis: - swap = z_axis - z_axis = x_axis - x_axis = swap - # otherwise, try to see if we can find an even smaller index for x? - else: - for x_index in neighbors: - x_type1 = int( - data.atomType[ - data. - atoms[x_index]]) - if (x_type1 == abs(kx) and - x_index != z_index - and - x_index < xaxis): - xaxis = x_index - hit = 1 # hit, finish matching - matched_itype = itype - else: - for y_index in neighbors: - if (y_index == z_index - or y_index == x_index - or hit != 0): - continue - y_type = int(data.atomType[ - data.atoms[y_index]]) - if y_type == abs(ky): - zaxis = z_index - xaxis = x_index - yaxis = y_index - hit = 2 - matched_itype = itype - # assign multipole parameters via 1-2 and 1-3 connected atoms - for itype in itypes: - if hit != 0: - break - kz = int(self.kStrings["kz"][itype]) - kx = int(self.kStrings["kx"][itype]) - ky = int(self.kStrings["ky"][itype]) - neighbors_1st = np.where(covalent_map[i_atom] == 1)[0] - neighbors_2nd = np.where(covalent_map[i_atom] == 2)[0] - zaxis = -1 - xaxis = -1 - yaxis = -1 - for z_index in neighbors_1st: - if hit != 0: - break - z_type = int(data.atomType[data.atoms[z_index]]) - if z_type == abs(kz): - for x_index in neighbors_2nd: - if x_index == z_index or hit != 0: - continue - x_type = int( - data.atomType[data.atoms[x_index]]) - # we ask x to be in 2'nd neighbor, and x is z's neighbor - if (x_type == abs(kx) - and covalent_map[z_index, - x_index] == 1): - if ky == 0: - zaxis = z_index - xaxis = x_index - # select smallest x index - for x_index in neighbors_2nd: - x_type1 = int(data.atomType[ - data.atoms[x_index]]) - if (x_type1 == abs(kx) - and x_index != z_index - and - covalent_map[x_index, - z_index] - == 1 - and x_index < xaxis): - xaxis = x_index - hit = 3 - matched_itype = itype - else: - for y_index in neighbors_2nd: - if (y_index == z_index - or y_index == x_index - or hit != 0): - continue - y_type = int(data.atomType[ - data.atoms[y_index]]) - if (y_type == abs(ky) and - covalent_map[y_index, - z_index] - == 1): - zaxis = z_index - xaxis = x_index - yaxis = y_index - hit = 4 - matched_itype = itype - # assign multipole parameters via only a z-defining atom - for itype in itypes: - if hit != 0: - break - kz = int(self.kStrings["kz"][itype]) - kx = int(self.kStrings["kx"][itype]) - zaxis = -1 - xaxis = -1 - yaxis = -1 - neighbors = np.where(covalent_map[i_atom] == 1)[0] - for z_index in neighbors: - if hit != 0: - break - z_type = int(data.atomType[data.atoms[z_index]]) - if kx == 0 and z_type == abs(kz): - zaxis = z_index - hit = 5 - matched_itype = itype - # assign multipole parameters via no connected atoms - for itype in itypes: - if hit != 0: - break - kz = int(self.kStrings["kz"][itype]) - zaxis = -1 - xaxis = -1 - yaxis = -1 - if kz == 0: - hit = 6 - matched_itype = itype - # add particle if there was a hit - if hit != 0: - map_atomtype[i_atom] = matched_itype - self.axis_indices.append([zaxis, xaxis, yaxis]) - - kz = int(self.kStrings["kz"][matched_itype]) - kx = int(self.kStrings["kx"][matched_itype]) - ky = int(self.kStrings["ky"][matched_itype]) - axisType = ZThenX - if kz == 0: - axisType = NoAxisType - if kz != 0 and kx == 0: - axisType = ZOnly - if kz < 0 or kx < 0: - axisType = Bisector - if kx < 0 and ky < 0: - axisType = ZBisect - if kz < 0 and kx < 0 and ky < 0: - axisType = ThreeFold - self.axis_types.append(axisType) - - else: - sys.exit("Atom %d not matched in forcefield!" % i_atom) - - else: - sys.exit("Atom %d not matched in forcefield!" % i_atom) - self.axis_indices = np.array(self.axis_indices) - self.axis_types = np.array(self.axis_types) - else: - self.axis_types = jnp.zeros(n_atoms) - self.axis_indices = None - - if "ethresh" in args: - self.ethresh = args["ethresh"] - if "step_pol" in args: - self.step_pol = args["step_pol"] - - pme_force = ADMPPmeForce(box, self.axis_types, self.axis_indices, - rc, self.ethresh, self.lmax, - self.lpol, self.lpme, self.step_pol) - self.pme_force = pme_force - - def potential_fn(positions, box, pairs, params): - params = params['ADMPPmeForce'] - mScales = params["mScales"] - Q_local = params["Q_local"][map_atomtype] - if self.lpol: - pScales = params["pScales"] - dScales = params["dScales"] - pol = params["pol"][map_poltype] - tholes = params["tholes"][map_poltype] - - return pme_force.get_energy( - positions, - box, - pairs, - Q_local, - pol, - tholes, - mScales, - pScales, - dScales, - pme_force.U_ind, - ) - else: - return pme_force.get_energy(positions, box, pairs, Q_local, - mScales) - - self._jaxPotential = potential_fn - - def getJaxPotential(self): - return self._jaxPotential +jaxGenerators = {} -# app.forcefield.parsers["ADMPPmeForce"] = ADMPPmeGenerator.parseElement -jaxGenerators["ADMPPmeForce"] = ADMPPmeGenerator class Potential: def __init__(self): @@ -1130,7 +72,8 @@ def getPotentialFunc(self, names=[]): def totalPE(positions, box, pairs, params): totale_list = [ self.dmff_potentials[k](positions, box, pairs, params) - for k in self.dmff_potentials.keys() if (len(names) == 0 or k in names) + for k in self.dmff_potentials.keys() + if (len(names) == 0 or k in names) ] totale = jnp.sum(jnp.array(totale_list)) return totale @@ -1141,6 +84,7 @@ def totalPE(positions, box, pairs, params): class Hamiltonian(app.forcefield.ForceField): def __init__(self, *xmlnames): super().__init__(*xmlnames) + self._pseudo_ff = app.ForceField(*xmlnames) # parse XML forcefields self.fftree = ForcefieldTree('ForcefieldTree') self.xmlparser = XMLParser(self.fftree) @@ -1185,6 +129,32 @@ def createPotential(self, **args): # load_constraints_from_system_if_needed # create potentials + """ + Create differentiable jax potential for given openmm.app.Topology object + + Parameters + ---------- + topology: openmm.app.Topology + Input openmm topology + nonbondedMethod: object=NoCutoff + The method to use for nonbonded interactions. Allowed values are + NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, PME, or LJPME. + nonbondedCutoff : distance=1*nanometer + The cutoff distance to use for nonbonded interactions + jaxForces: list of str + Specified forces to create. If set to [], will create all existing types of forces. + args + Arbitrary parameters in openmm.app.ForceField.createSystem function + + Return + ------ + potObj: dmff.api.Potential + Differentiable jax potential energy function + """ + pseudo_data = app.ForceField._SystemData(topology) + residueTemplates = {} + templateForResidue = self._pseudo_ff._matchAllResiduesToTemplates(pseudo_data, topology, residueTemplates, False) + self.templateNameForResidue = [i.name for i in templateForResidue] system = self.createSystem( topology, @@ -1195,7 +165,9 @@ def createPotential(self, removeIdx = [] jaxGens = [i.name for i in self._jaxGenerators] for nf, force in enumerate(system.getForces()): - if (len(jaxForces) > 0 and force.getName() in jaxForces) or (force.getName() in jaxGens): + if (len(jaxForces) > 0 + and force.getName() in jaxForces) or (force.getName() + in jaxGens): removeIdx.append(nf) for nf in removeIdx[::-1]: system.removeForce(nf) @@ -1229,719 +201,4 @@ def update_iter(node, ref): else: node[key] = ref[key] - update_iter(self.paramtree, paramtree) - - -class HarmonicBondJaxGenerator: - def __init__(self, ff:Hamiltonian): - self.name = "HarmonicBondForce" - self.ff:Hamiltonian = ff - self.fftree:ForcefieldTree = ff.fftree - self.paramtree:Dict = ff.paramtree - - def extract(self): - """ - extract forcefield paramters from ForcefieldTree. - """ - lengths = self.fftree.get_attribs(f"{self.name}/Bond", "length") - # get_attribs will return a list of list. - ks = self.fftree.get_attribs(f"{self.name}/Bond", "k") - self.paramtree[self.name] = {} - self.paramtree[self.name]["length"] = jnp.array(lengths) - self.paramtree[self.name]["k"] = jnp.array(ks) - - def overwrite(self): - """ - update parameters in the fftree by using paramtree of this generator. - """ - self.fftree.set_attrib(f"{self.name}/Bond", "length", - self.paramtree[self.name]["length"]) - self.fftree.set_attrib(f"{self.name}/Bond", "k", - self.paramtree[self.name]["k"]) - - def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args): - """ - This method will create a potential calculation kernel. It usually should do the following: - - 1. Match the corresponding bond parameters according to the atomic types at both ends of each bond. - - 2. Create a potential calculation kernel, and pass those mapped parameters to the kernel. - - 3. assign the jax potential to the _jaxPotential. - - Args: - Those args are the same as those in createSystem. - """ - - # initialize typemap - matcher = TypeMatcher(self.fftree, "HarmonicBondForce/Bond") - - map_atom1, map_atom2, map_param = [], [], [] - n_bonds = len(data.bonds) - # build map - for i in range(n_bonds): - idx1 = data.bonds[i].atom1 - idx2 = data.bonds[i].atom2 - type1 = data.atomType[data.atoms[idx1]] - type2 = data.atomType[data.atoms[idx2]] - ifFound, ifForward, nfunc = matcher.matchGeneral([type1, type2]) - if not ifFound: - raise BaseException( - f"No parameter for bond ({idx1},{type1}) - ({idx2},{type2})" - ) - map_atom1.append(idx1) - map_atom2.append(idx2) - map_param.append(nfunc) - map_atom1 = np.array(map_atom1, dtype=int) - map_atom2 = np.array(map_atom2, dtype=int) - map_param = np.array(map_param, dtype=int) - - bforce = HarmonicBondJaxForce(map_atom1, map_atom2, map_param) - - def potential_fn(positions, box, pairs, params): - return bforce.get_energy(positions, box, pairs, - params[self.name]["k"], - params[self.name]["length"]) - - self._jaxPotential = potential_fn - # self._top_data = data - - def getJaxPotential(self): - return self._jaxPotential - - -jaxGenerators["HarmonicBondForce"] = HarmonicBondJaxGenerator - - -class HarmonicAngleJaxGenerator: - def __init__(self, ff): - self.name = "HarmonicAngleForce" - self.ff = ff - self.fftree = ff.fftree - self.paramtree = ff.paramtree - - def extract(self): - angles = self.fftree.get_attribs(f"{self.name}/Angle", "angle") - ks = self.fftree.get_attribs(f"{self.name}/Angle", "k") - self.paramtree[self.name] = {} - self.paramtree[self.name]["angle"] = jnp.array(angles) - self.paramtree[self.name]["k"] = jnp.array(ks) - - def overwrite(self): - self.fftree.set_attrib(f"{self.name}/Angle", "angle", - self.paramtree[self.name]["angle"]) - self.fftree.set_attrib(f"{self.name}/Angle", "k", - self.paramtree[self.name]["k"]) - - def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args): - matcher = TypeMatcher(self.fftree, "HarmonicAngleForce/Angle") - - map_atom1, map_atom2, map_atom3, map_param = [], [], [], [] - n_angles = len(data.angles) - for nangle in range(n_angles): - idx1 = data.angles[nangle][0] - idx2 = data.angles[nangle][1] - idx3 = data.angles[nangle][2] - type1 = data.atomType[data.atoms[idx1]] - type2 = data.atomType[data.atoms[idx2]] - type3 = data.atomType[data.atoms[idx3]] - ifFound, ifForward, nfunc = matcher.matchGeneral( - [type1, type2, type3]) - if not ifFound: - print( - f"No parameter for angle ({idx1},{type1}) - ({idx2},{type2}) - ({idx3},{type3})" - ) - else: - map_atom1.append(idx1) - map_atom2.append(idx2) - map_atom3.append(idx3) - map_param.append(nfunc) - map_atom1 = np.array(map_atom1, dtype=int) - map_atom2 = np.array(map_atom2, dtype=int) - map_atom3 = np.array(map_atom3, dtype=int) - map_param = np.array(map_param, dtype=int) - - aforce = HarmonicAngleJaxForce(map_atom1, map_atom2, map_atom3, - map_param) - - def potential_fn(positions, box, pairs, params): - return aforce.get_energy(positions, box, pairs, - params[self.name]["k"], - params[self.name]["angle"]) - - self._jaxPotential = potential_fn - # self._top_data = data - - def getJaxPotential(self): - return self._jaxPotential - - -jaxGenerators["HarmonicAngleForce"] = HarmonicAngleJaxGenerator - - -class PeriodicTorsionJaxGenerator: - def __init__(self, ff): - self.name = "PeriodicTorsionForce" - self.ff = ff - self.fftree = ff.fftree - self.paramtree = ff.paramtree - self.meta = {} - - self.meta["prop_order"] = defaultdict(list) - self.meta["prop_nodeidx"] = defaultdict(list) - - self.meta["impr_order"] = defaultdict(list) - self.meta["impr_nodeidx"] = defaultdict(list) - - self.max_pred_prop = 0 - self.max_pred_impr = 0 - - def extract(self): - propers = self.fftree.get_nodes("PeriodicTorsionForce/Proper") - impropers = self.fftree.get_nodes("PeriodicTorsionForce/Improper") - self.paramtree[self.name] = {} - # propers - prop_phase = defaultdict(list) - prop_k = defaultdict(list) - for nnode, node in enumerate(propers): - for key in node.attrs: - if "periodicity" in key: - order = int(key[-1]) - phase = float(node.attrs[f"phase{order}"]) - k = float(node.attrs[f"k{order}"]) - periodicity = int(node.attrs[f"periodicity{order}"]) - if self.max_pred_prop < periodicity: - self.max_pred_prop = periodicity - prop_phase[f"{periodicity}"].append(phase) - prop_k[f"{periodicity}"].append(k) - self.meta[f"prop_order"][f"{periodicity}"].append(order) - self.meta[f"prop_nodeidx"][f"{periodicity}"].append(nnode) - - self.paramtree[self.name]["prop_phase"] = {} - self.paramtree[self.name]["prop_k"] = {} - for npred in range(1, self.max_pred_prop + 1): - self.paramtree[self.name]["prop_phase"][f"{npred}"] = jnp.array( - prop_phase[f"{npred}"]) - self.paramtree[self.name]["prop_k"][f"{npred}"] = jnp.array( - prop_k[f"{npred}"]) - if self.max_pred_prop == 0: - del self.paramtree[self.name]["prop_phase"] - del self.paramtree[self.name]["prop_k"] - - # impropers - impr_phase = defaultdict(list) - impr_k = defaultdict(list) - for nnode, node in enumerate(impropers): - for key in node.attrs: - if "periodicity" in key: - order = int(key[-1]) - phase = float(node.attrs[f"phase{order}"]) - k = float(node.attrs[f"k{order}"]) - periodicity = int(node.attrs[f"periodicity{order}"]) - if self.max_pred_impr < periodicity: - self.max_pred_impr = periodicity - impr_phase[f"{periodicity}"].append(phase) - impr_k[f"{periodicity}"].append(k) - self.meta[f"impr_order"][f"{periodicity}"].append(order) - self.meta[f"impr_nodeidx"][f"{periodicity}"].append(nnode) - - self.paramtree[self.name]["impr_phase"] = {} - self.paramtree[self.name]["impr_k"] = {} - for npred in range(1, self.max_pred_impr + 1): - self.paramtree[self.name]["impr_phase"][f"{npred}"] = jnp.array( - impr_phase[f"{npred}"]) - self.paramtree[self.name]["impr_k"][f"{npred}"] = jnp.array( - impr_k[f"{npred}"]) - if self.max_pred_impr == 0: - del self.paramtree[self.name]["impr_phase"] - del self.paramtree[self.name]["impr_k"] - - def overwrite(self): - propers = self.fftree.get_nodes("PeriodicTorsionForce/Proper") - impropers = self.fftree.get_nodes("PeriodicTorsionForce/Improper") - prop_data = [{} for _ in propers] - impr_data = [{} for _ in impropers] - # make propers - for periodicity in range(1, self.max_pred_prop+1): - nterms = len( - self.paramtree[self.name][f"prop_phase"][f"{periodicity}"]) - for nitem in range(nterms): - phase = self.paramtree[ - self.name][f"prop_phase"][f"{periodicity}"][nitem] - k = self.paramtree[ - self.name][f"prop_k"][f"{periodicity}"][nitem] - nodeidx = self.meta[f"prop_nodeidx"][f"{periodicity}"][nitem] - order = self.meta[f"prop_order"][f"{periodicity}"][nitem] - prop_data[nodeidx][f"phase{order}"] = phase - prop_data[nodeidx][f"k{order}"] = k - if "prop_phase" in self.paramtree[self.name]: - self.fftree.set_node("PeriodicTorsionForce/Proper", prop_data) - - # make impropers - for periodicity in range(1, self.max_pred_impr+1): - nterms = len( - self.paramtree[self.name][f"impr_phase"][f"{periodicity}"]) - for nitem in range(nterms): - phase = self.paramtree[ - self.name][f"impr_phase"][f"{periodicity}"][nitem] - k = self.paramtree[ - self.name][f"impr_k"][f"{periodicity}"][nitem] - nodeidx = self.meta[f"impr_nodeidx"][f"{periodicity}"][nitem] - order = self.meta[f"impr_order"][f"{periodicity}"][nitem] - impr_data[nodeidx][f"phase{order}"] = phase - impr_data[nodeidx][f"k{order}"] = k - if "impr_phase" in self.paramtree[self.name]: - self.fftree.set_node("PeriodicTorsionForce/Improper", impr_data) - - def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args): - proper_matcher = TypeMatcher(self.fftree, - "PeriodicTorsionForce/Proper") - map_prop_atom1 = {i: [] for i in range(1, self.max_pred_prop + 1)} - map_prop_atom2 = {i: [] for i in range(1, self.max_pred_prop + 1)} - map_prop_atom3 = {i: [] for i in range(1, self.max_pred_prop + 1)} - map_prop_atom4 = {i: [] for i in range(1, self.max_pred_prop + 1)} - map_prop_param = {i: [] for i in range(1, self.max_pred_prop + 1)} - n_matched_props = 0 - for torsion in data.propers: - types = [data.atomType[data.atoms[torsion[i]]] for i in range(4)] - ifFound, ifForward, nnode = proper_matcher.matchGeneral(types) - if not ifFound: - continue - # find terms for node - for periodicity in range(1, self.max_pred_prop + 1): - idx = findItemInList( - nnode, self.meta[f"prop_nodeidx"][f"{periodicity}"]) - if idx < 0: - continue - n_matched_props += 1 - map_prop_atom1[periodicity].append(torsion[0]) - map_prop_atom2[periodicity].append(torsion[1]) - map_prop_atom3[periodicity].append(torsion[2]) - map_prop_atom4[periodicity].append(torsion[3]) - map_prop_param[periodicity].append(idx) - - impr_matcher = TypeMatcher(self.fftree, - "PeriodicTorsionForce/Improper") - try: - ordering = self.fftree.get_attribs("PeriodicTorsionForce", - "ordering")[0] - except KeyError as e: - ordering = "default" - map_impr_atom1 = {i: [] for i in range(1, self.max_pred_impr + 1)} - map_impr_atom2 = {i: [] for i in range(1, self.max_pred_impr + 1)} - map_impr_atom3 = {i: [] for i in range(1, self.max_pred_impr + 1)} - map_impr_atom4 = {i: [] for i in range(1, self.max_pred_impr + 1)} - map_impr_param = {i: [] for i in range(1, self.max_pred_impr + 1)} - n_matched_imprs = 0 - for impr in data.impropers: - match = impr_matcher.matchImproper(impr, data, ordering=ordering) - if match is not None: - (a1, a2, a3, a4, nnode) = match - n_matched_imprs += 1 - # find terms for node - for periodicity in range(1, self.max_pred_impr + 1): - idx = findItemInList( - nnode, self.meta[f"impr_nodeidx"][f"{periodicity}"]) - if idx < 0: - continue - if ordering == 'smirnoff': - # Add all torsions in trefoil - map_impr_atom1[periodicity].append(a1) - map_impr_atom2[periodicity].append(a2) - map_impr_atom3[periodicity].append(a3) - map_impr_atom4[periodicity].append(a4) - map_impr_param[periodicity].append(idx) - map_impr_atom1[periodicity].append(a1) - map_impr_atom2[periodicity].append(a3) - map_impr_atom3[periodicity].append(a4) - map_impr_atom4[periodicity].append(a2) - map_impr_param[periodicity].append(idx) - map_impr_atom1[periodicity].append(a1) - map_impr_atom2[periodicity].append(a4) - map_impr_atom3[periodicity].append(a2) - map_impr_atom4[periodicity].append(a3) - map_impr_param[periodicity].append(idx) - else: - map_impr_atom1[periodicity].append(a1) - map_impr_atom2[periodicity].append(a2) - map_impr_atom3[periodicity].append(a3) - map_impr_atom4[periodicity].append(a4) - map_impr_param[periodicity].append(idx) - - props = [ - PeriodicTorsionJaxForce(jnp.array(map_prop_atom1[p], dtype=int), - jnp.array(map_prop_atom2[p], dtype=int), - jnp.array(map_prop_atom3[p], dtype=int), - jnp.array(map_prop_atom4[p], dtype=int), - jnp.array(map_prop_param[p], dtype=int), p) - for p in range(1, self.max_pred_prop + 1) - ] - imprs = [ - PeriodicTorsionJaxForce(jnp.array(map_impr_atom1[p], dtype=int), - jnp.array(map_impr_atom2[p], dtype=int), - jnp.array(map_impr_atom3[p], dtype=int), - jnp.array(map_impr_atom4[p], dtype=int), - jnp.array(map_impr_param[p], dtype=int), p) - for p in range(1, self.max_pred_impr + 1) - ] - - def potential_fn(positions, box, pairs, params): - prop_sum = sum([ - props[i].get_energy( - positions, box, pairs, - params["PeriodicTorsionForce"]["prop_k"][f"{i+1}"], - params["PeriodicTorsionForce"]["prop_phase"][f"{i+1}"]) - for i in range(self.max_pred_prop) - ]) - impr_sum = sum([ - imprs[i].get_energy( - positions, box, pairs, - params["PeriodicTorsionForce"]["impr_k"][f"{i+1}"], - params["PeriodicTorsionForce"]["impr_phase"][f"{i+1}"]) - for i in range(self.max_pred_impr) - ]) - - return prop_sum + impr_sum - - self._jaxPotential = potential_fn - - def getJaxPotential(self): - return self._jaxPotential - - -jaxGenerators["PeriodicTorsionForce"] = PeriodicTorsionJaxGenerator - - -class NonbondedJaxGenerator: - def __init__(self, ff): - self.name = "NonbondedForce" - self.ff = ff - self.fftree = ff.fftree - self.paramtree = ff.paramtree - self.paramtree[self.name] = {} - self.paramtree[self.name]["sigfix"] = jnp.array([]) - self.paramtree[self.name]["epsfix"] = jnp.array([]) - - self.from_force = [] - self.from_residue = [] - self.ra2idx = {} - self.idx2rai = {} - - def extract(self): - self.from_residue = self.fftree.get_attribs( - "NonbondedForce/UseAttributeFromResidue", "name") - self.from_force = [ - i for i in ["charge", "sigma", "epsilon"] - if i not in self.from_residue - ] - # Build per-atom array for from_force - for prm in self.from_force: - vals = self.fftree.get_attribs("NonbondedForce/Atom", prm) - self.paramtree[self.name][prm] = jnp.array(vals) - # Build per-atom array for from_residue - residues = self.fftree.get_nodes("Residues/Residue") - resvals = {k: [] for k in self.from_residue} - for resnode in residues: - resname = resnode.attrs["name"] - resvals[resname] = [] - atomname = resnode.get_attribs("Atom", "name") - shift = len(self.ra2idx) - for natom, aname in enumerate(atomname): - self.ra2idx[(resname, aname)] = shift + natom - self.idx2rai[shift + natom] = (resname, atomname, natom) - for prm in self.from_residue: - atomval = resnode.get_attribs("Atom", prm) - resvals[prm].extend(atomval) - for prm in self.from_residue: - self.paramtree[self.name][prm] = jnp.array(resvals[prm]) - # Build coulomb14scale and lj14scale - coulomb14scale, lj14scale = self.fftree.get_attribs("NonbondedForce", - ["coulomb14scale", "lj14scale"])[0] - self.paramtree[self.name]["coulomb14scale"] = jnp.array([coulomb14scale]) - self.paramtree[self.name]["lj14scale"] = jnp.array([lj14scale]) - - def overwrite(self): - # write coulomb14scale - self.fftree.set_attrib("NonbondedForce", "coulomb14scale", - self.paramtree[self.name]["coulomb14scale"]) - # write lj14scale - self.fftree.set_attrib("NonbondedForce", "lj14scale", - self.paramtree[self.name]["lj14scale"]) - # write prm from force - for prm in self.from_force: - self.fftree.set_attrib("NonbondedForce/Atom", prm, - self.paramtree[self.name][prm]) - # write prm from residue - residues = self.fftree.get_nodes("Residues/Residue") - for prm in self.from_residue: - vals = self.paramtree[self.name][prm] - data = [] - for idx in range(vals.shape[0]): - rname, atomname, aidx = self.idx2rai[idx] - data.append((rname, aidx, vals[idx])) - - for resnode in residues: - tmp = sorted( - [d for d in data if d[0] == resnode.attrs["name"]], - key=lambda x: x[1]) - resnode.set_attrib("Atom", prm, [t[2] for t in tmp]) - - def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, - args): - methodMap = { - app.NoCutoff: "NoCutoff", - app.CutoffPeriodic: "CutoffPeriodic", - app.CutoffNonPeriodic: "CutoffNonPeriodic", - app.PME: "PME", - } - if nonbondedMethod not in methodMap: - raise DMFFException("Illegal nonbonded method for NonbondedForce") - isNoCut = False - if nonbondedMethod is app.NoCutoff: - isNoCut = True - - mscales_coul = jnp.array([0.0, 0.0, 0.0, 1.0, 1.0, - 1.0]) # mscale for PME - mscales_coul = mscales_coul.at[2].set( - self.paramtree[self.name]["coulomb14scale"][0]) - mscales_lj = jnp.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]) # mscale for LJ - mscales_lj = mscales_lj.at[2].set( - self.paramtree[self.name]["lj14scale"][0]) - - # Coulomb: only support PME for now - # set PBC - if nonbondedMethod not in [app.NoCutoff, app.CutoffNonPeriodic]: - ifPBC = True - else: - ifPBC = False - - nbmatcher = TypeMatcher(self.fftree, "NonbondedForce/Atom") - # load LJ from types - maps = {} - for prm in self.from_force: - maps[prm] = [] - for atom in data.atoms: - atype = data.atomType[atom] - ifFound, _, nnode = nbmatcher.matchGeneral([atype]) - if not ifFound: - raise DMFFException( - "AtomType of %s mismatched in NonbondedForce" % - (str(atom))) - maps[prm].append(nnode) - maps[prm] = jnp.array(maps[prm], dtype=int) - - for prm in self.from_residue: - maps[prm] = [] - for atom in data.atoms: - resname, aname = atom.residue.name, atom.name - maps[prm].append(self.ra2idx[(resname, aname)]) - # TODO: implement NBFIX - map_nbfix = [] - map_nbfix = np.array(map_nbfix, dtype=int).reshape((-1, 2)) - - self.covalent_map = build_covalent_map(data, 6) - - if unit.is_quantity(nonbondedCutoff): - r_cut = nonbondedCutoff.value_in_unit(unit.nanometer) - else: - r_cut = nonbondedCutoff - if "switchDistance" in args and args["switchDistance"] is not None: - r_switch = args["switchDistance"] - r_switch = (r_switch if not unit.is_quantity(r_switch) else - r_switch.value_in_unit(unit.nanometer)) - ifSwitch = True - else: - r_switch = r_cut - ifSwitch = False - - # PME Settings - if nonbondedMethod is app.PME: - a, b, c = system.getDefaultPeriodicBoxVectors() - box = jnp.array([a._value, b._value, c._value]) - self.ethresh = args.get("ethresh", 1e-6) - self.coeff_method = args.get("PmeCoeffMethod", "openmm") - self.fourier_spacing = args.get("PmeSpacing", 0.1) - kappa, K1, K2, K3 = setup_ewald_parameters(r_cut, self.ethresh, - box, - self.fourier_spacing, - self.coeff_method) - - map_lj = jnp.array(maps["sigma"]) - map_nbfix = jnp.array(map_nbfix) - map_charge = jnp.array(maps["charge"]) - - # Free Energy Settings # - isFreeEnergy = args.get("isFreeEnergy", False) - if isFreeEnergy: - vdwLambda = args.get("vdwLambda", 0.0) - coulLambda = args.get("coulLambda", 0.0) - ifStateA = args.get("ifStateA", True) - - # soft-cores - vdwSoftCore = args.get("vdwSoftCore", False) - coulSoftCore = args.get("coulSoftCore", False) - scAlpha = args.get("scAlpha", 0.0) - scSigma = args.get("scSigma", 0.0) - - # couple - coupleIndex = args.get("coupleIndex", []) - if len(coupleIndex) > 0: - coupleMask = [False for _ in range(len(data.atoms))] - for atomIndex in coupleIndex: - coupleMask[atomIndex] = True - coupleMask = jnp.array(coupleMask, dtype=bool) - else: - coupleMask = None - - if not isFreeEnergy: - ljforce = LennardJonesForce(r_switch, - r_cut, - map_lj, - map_nbfix, - isSwitch=ifSwitch, - isPBC=ifPBC, - isNoCut=isNoCut) - else: - ljforce = LennardJonesFreeEnergyForce(r_switch, - r_cut, - map_lj, - map_nbfix, - isSwitch=ifSwitch, - isPBC=ifPBC, - isNoCut=isNoCut, - feLambda=vdwLambda, - coupleMask=coupleMask, - useSoftCore=vdwSoftCore, - ifStateA=ifStateA, - sc_alpha=scAlpha, - sc_sigma=scSigma) - - ljenergy = ljforce.generate_get_energy() - - # dispersion correction - useDispersionCorrection = args.get("useDispersionCorrection", False) - if useDispersionCorrection: - numTypes = self.paramtree[self.name]["sigma"].shape[0] - countVec = np.zeros(numTypes, dtype=int) - countMat = np.zeros((numTypes, numTypes), dtype=int) - types, count = np.unique(map_lj, return_counts=True) - - for typ, cnt in zip(types, count): - countVec[typ] += cnt - for i in range(numTypes): - for j in range(i, numTypes): - if i != j: - countMat[i, j] = countVec[i] * countVec[j] - else: - countMat[i, i] = countVec[i] * (countVec[i] - 1) // 2 - assert np.sum(countMat) == len(map_lj) * (len(map_lj) - 1) // 2 - - colv_pairs = np.argwhere( - np.logical_and(self.covalent_map > 0, self.covalent_map <= 3)) - for pair in colv_pairs: - if pair[0] <= pair[1]: - tmp = (map_lj[pair[0]], map_lj[pair[1]]) - t1, t2 = min(tmp), max(tmp) - countMat[t1, t2] -= 1 - - if not isFreeEnergy: - ljDispCorrForce = LennardJonesLongRangeForce( - r_cut, map_lj, map_nbfix, countMat) - else: - ljDispCorrForce = LennardJonesLongRangeFreeEnergyForce( - r_cut, map_lj, map_nbfix, countMat, vdwLambda, ifStateA, - coupleMask) - ljDispEnergyFn = ljDispCorrForce.generate_get_energy() - - if not isFreeEnergy: - if nonbondedMethod is not app.PME: - # do not use PME - if nonbondedMethod in [ - app.CutoffPeriodic, app.CutoffNonPeriodic - ]: - # use Reaction Field - coulforce = CoulReactionFieldForce(r_cut, - map_charge, - isPBC=ifPBC) - if nonbondedMethod is app.NoCutoff: - # use NoCutoff - coulforce = CoulNoCutoffForce(map_charge) - else: - coulforce = CoulombPMEForce(r_cut, map_charge, kappa, - (K1, K2, K3)) - else: - assert nonbondedMethod is app.PME, "Only PME is supported in free energy calculations" - coulforce = CoulombPMEFreeEnergyForce(r_cut, - map_charge, - kappa, (K1, K2, K3), - coulLambda, - ifStateA=ifStateA, - coupleMask=coupleMask, - useSoftCore=coulSoftCore, - sc_alpha=scAlpha, - sc_sigma=scSigma) - - coulenergy = coulforce.generate_get_energy() - - if not isFreeEnergy: - - def potential_fn(positions, box, pairs, params): - - # check whether args passed into potential_fn are jnp.array and differentiable - # note this check will be optimized away by jit - # it is jit-compatiable - isinstance_jnp(positions, box, params) - - ljE = ljenergy(positions, box, pairs, - params[self.name]["epsilon"], - params[self.name]["sigma"], - params[self.name]["epsfix"], - params[self.name]["sigfix"], mscales_lj) - coulE = coulenergy(positions, box, pairs, - params[self.name]["charge"], mscales_coul) - - if useDispersionCorrection: - ljDispEnergy = ljDispEnergyFn(box, - params[self.name]['epsilon'], - params[self.name]['sigma'], - params[self.name]['epsfix'], - params[self.name]['sigfix']) - - return ljE + coulE + ljDispEnergy - else: - return ljE + coulE - - self._jaxPotential = potential_fn - else: - # Free Energy - @jit_condition() - def potential_fn(positions, box, pairs, params, vdwLambda, - coulLambda): - ljE = ljenergy(positions, box, pairs, - params[self.name]["epsilon"], - params[self.name]["sigma"], - params[self.name]["epsfix"], - params[self.name]["sigfix"], mscales_lj, - vdwLambda) - coulE = coulenergy(positions, box, pairs, - params[self.name]["charge"], mscales_coul, - coulLambda) - - if useDispersionCorrection: - ljDispEnergy = ljDispEnergyFn(box, - params[self.name]['epsilon'], - params[self.name]['sigma'], - params[self.name]['epsfix'], - params[self.name]['sigfix'], - vdwLambda) - return ljE + coulE + ljDispEnergy - else: - return ljE + coulE - - self._jaxPotential = potential_fn - - def getJaxPotential(self): - return self._jaxPotential - - -jaxGenerators["NonbondedForce"] = NonbondedJaxGenerator + update_iter(self.paramtree, paramtree) \ No newline at end of file diff --git a/dmff/common/nblist.py b/dmff/common/nblist.py index cd5e69c9a..c57b481be 100644 --- a/dmff/common/nblist.py +++ b/dmff/common/nblist.py @@ -1,11 +1,12 @@ -from typing import Optional +from typing import Optional, Literal + import numpy as np import jax.numpy as jnp from jax_md import space, partition -from dmff.utils import regularize_pairs -from typing import Literal import freud -from dmff.admp.pairwise import distribute_v3 + +from dmff.utils import regularize_pairs + class NeighborList: def __init__(self, box, r_cutoff, covalent_map, dr_threshold=0, capacity_multiplier=1.25, format=Literal['dense', 'sparse', ]) -> None: diff --git a/dmff/generators/__init__.py b/dmff/generators/__init__.py new file mode 100644 index 000000000..7bf876ff3 --- /dev/null +++ b/dmff/generators/__init__.py @@ -0,0 +1,2 @@ +from .admp import * +from .classical import * diff --git a/dmff/generators/admp.py b/dmff/generators/admp.py new file mode 100644 index 000000000..c388cde7e --- /dev/null +++ b/dmff/generators/admp.py @@ -0,0 +1,1102 @@ +import sys + +import numpy as np +import jax.numpy as jnp + +import openmm.app as app +import openmm.unit as unit + +import dmff +from dmff.api import build_covalent_map +from dmff.admp.disp_pme import ADMPDispPmeForce +from dmff.admp.multipole import convert_cart2harm, convert_harm2cart +from dmff.admp.pairwise import ( + TT_damping_qq_c6_kernel, + generate_pairwise_interaction, + slater_disp_damping_kernel, + slater_sr_kernel, + TT_damping_qq_kernel +) +from dmff.admp.pme import ADMPPmeForce + + +class ADMPDispGenerator: + def __init__(self, ff): + + self.name = "ADMPDispForce" + self.ff = ff + self.fftree = ff.fftree + self.paramtree = ff.paramtree + + # default params + self._jaxPotential = None + self.types = [] + self.ethresh = 5e-4 + self.pmax = 10 + + def extract(self): + + mScales = [ + self.fftree.get_attribs(f'{self.name}', f'mScale1{i}')[0] + for i in range(2, 7) + ] + mScales.append(1.0) + self.paramtree[self.name] = {} + self.paramtree[self.name]['mScales'] = jnp.array(mScales) + + ABQC = self.fftree.get_attribs(f'{self.name}/Atom', + ['A', 'B', 'Q', 'C6', 'C8', 'C10']) + + ABQC = np.array(ABQC) + A = ABQC[:, 0] + B = ABQC[:, 1] + Q = ABQC[:, 2] + C6 = ABQC[:, 3] + C8 = ABQC[:, 4] + C10 = ABQC[:, 5] + + self.paramtree[self.name]['A'] = jnp.array(A) + self.paramtree[self.name]['B'] = jnp.array(B) + self.paramtree[self.name]['Q'] = jnp.array(Q) + self.paramtree[self.name]['C6'] = jnp.array(C6) + self.paramtree[self.name]['C8'] = jnp.array(C8) + self.paramtree[self.name]['C10'] = jnp.array(C10) + + atomTypes = self.fftree.get_attribs(f'{self.name}/Atom', f'type') + if type(atomTypes[0]) != str: + self.atomTypes = np.array(atomTypes, dtype=int).astype(str) + else: + self.atomTypes = np.array(atomTypes) + + def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, + args): + + methodMap = { + app.CutoffPeriodic: "CutoffPeriodic", + app.NoCutoff: "NoCutoff", + app.PME: "PME", + } + if nonbondedMethod not in methodMap: + raise ValueError("Illegal nonbonded method for ADMPDispForce") + if nonbondedMethod is app.CutoffPeriodic: + self.lpme = False + else: + self.lpme = True + + n_atoms = len(data.atoms) + # build index map + map_atomtype = np.zeros(n_atoms, dtype=int) + for i in range(n_atoms): + atype = data.atomType[data.atoms[i]] + map_atomtype[i] = np.where(self.atomTypes == atype)[0][0] + self.map_atomtype = map_atomtype + # build covalent map + self.covalent_map = build_covalent_map(data, 6) + # here box is only used to setup ewald parameters, no need to be differentiable + a, b, c = system.getDefaultPeriodicBoxVectors() + box = jnp.array([a._value, b._value, c._value]) * 10 + # get the admp calculator + rc = nonbondedCutoff.value_in_unit(unit.angstrom) + + # get calculator + if "ethresh" in args: + self.ethresh = args["ethresh"] + + Force_DispPME = ADMPDispPmeForce(box, + rc, + self.ethresh, + self.pmax, + lpme=self.lpme) + self.disp_pme_force = Force_DispPME + pot_fn_lr = Force_DispPME.get_energy + pot_fn_sr = generate_pairwise_interaction(TT_damping_qq_c6_kernel, + static_args={}) + + def potential_fn(positions, box, pairs, params): + params = params[self.name] + mScales = params["mScales"] + a_list = (params["A"][map_atomtype] / 2625.5 + ) # kj/mol to au, as expected by TT_damping kernel + b_list = params["B"][map_atomtype] * 0.0529177249 # nm^-1 to au + q_list = params["Q"][map_atomtype] + c6_list = jnp.sqrt(params["C6"][map_atomtype] * 1e6) + c8_list = jnp.sqrt(params["C8"][map_atomtype] * 1e8) + c10_list = jnp.sqrt(params["C10"][map_atomtype] * 1e10) + c_list = jnp.vstack((c6_list, c8_list, c10_list)) + + E_sr = pot_fn_sr(positions, box, pairs, mScales, a_list, b_list, + q_list, c_list[0]) + E_lr = pot_fn_lr(positions, box, pairs, c_list.T, mScales) + return E_sr - E_lr + + self._jaxPotential = potential_fn + # self._top_data = data + + def overwrite(self): + + self.fftree.set_attrib(f'{self.name}', 'mScale12', + [self.paramtree[self.name]['mScales'][0]]) + self.fftree.set_attrib(f'{self.name}', 'mScale13', + [self.paramtree[self.name]['mScales'][1]]) + self.fftree.set_attrib(f'{self.name}', 'mScale14', + [self.paramtree[self.name]['mScales'][2]]) + self.fftree.set_attrib(f'{self.name}', 'mScale15', + [self.paramtree[self.name]['mScales'][3]]) + self.fftree.set_attrib(f'{self.name}', 'mScale16', + [self.paramtree[self.name]['mScales'][4]]) + + self.fftree.set_attrib(f'{self.name}/Atom', 'A', + [self.paramtree[self.name]['A']]) + self.fftree.set_attrib(f'{self.name}/Atom', 'B', + [self.paramtree[self.name]['B']]) + self.fftree.set_attrib(f'{self.name}/Atom', 'Q', + [self.paramtree[self.name]['Q']]) + self.fftree.set_attrib(f'{self.name}/Atom', 'C6', + [self.paramtree[self.name]['C6']]) + self.fftree.set_attrib(f'{self.name}/Atom', 'C8', + [self.paramtree[self.name]['C8']]) + self.fftree.set_attrib(f'{self.name}/Atom', 'C10', + [self.paramtree[self.name]['C10']]) + + def getJaxPotential(self): + return self._jaxPotential + + +dmff.api.jaxGenerators['ADMPDispForce'] = ADMPDispGenerator + + +class ADMPDispPmeGenerator: + r""" + This one computes the undamped C6/C8/C10 interactions + u = \sum_{ij} c6/r^6 + c8/r^8 + c10/r^10 + """ + def __init__(self, ff): + self.ff = ff + self.fftree = ff.fftree + self.paramtree = ff.paramtree + + self.params = {"C6": [], "C8": [], "C10": []} + self._jaxPotential = None + self.atomTypes = None + self.ethresh = 5e-4 + self.pmax = 10 + self.name = "ADMPDispPmeForce" + + def extract(self): + + mScales = [ + self.fftree.get_attribs(f'{self.name}', f'mScale1{i}')[0] + for i in range(2, 7) + ] + mScales.append(1.0) + + self.paramtree[self.name] = {} + self.paramtree[self.name]['mScales'] = jnp.array(mScales) + + C6 = self.fftree.get_attribs(f'{self.name}/Atom', f'C6') + C8 = self.fftree.get_attribs(f'{self.name}/Atom', f'C8') + C10 = self.fftree.get_attribs(f'{self.name}/Atom', f'C10') + + self.paramtree[self.name]['C6'] = jnp.array(C6) + self.paramtree[self.name]['C8'] = jnp.array(C8) + self.paramtree[self.name]['C10'] = jnp.array(C10) + + atomTypes = self.fftree.get_attribs(f'{self.name}/Atom', f'type') + if type(atomTypes[0]) != str: + self.atomTypes = np.array(atomTypes, dtype=int).astype(str) + else: + self.atomTypes = np.array(atomTypes) + + def overwrite(self): + + self.fftree.set_attrib(f'{self.name}', 'mScale12', + [self.paramtree[self.name]['mScales'][0]]) + self.fftree.set_attrib(f'{self.name}', 'mScale13', + [self.paramtree[self.name]['mScales'][1]]) + self.fftree.set_attrib(f'{self.name}', 'mScale14', + [self.paramtree[self.name]['mScales'][2]]) + self.fftree.set_attrib(f'{self.name}', 'mScale15', + [self.paramtree[self.name]['mScales'][3]]) + self.fftree.set_attrib(f'{self.name}', 'mScale16', + [self.paramtree[self.name]['mScales'][4]]) + + self.fftree.set_attrib(f'{self.name}/Atom', 'C6', + self.paramtree[self.name]['C6']) + self.fftree.set_attrib(f'{self.name}/Atom', 'C8', + self.paramtree[self.name]['C8']) + self.fftree.set_attrib(f'{self.name}/Atom', 'C10', + self.paramtree[self.name]['C10']) + + def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, + args): + methodMap = { + app.CutoffPeriodic: "CutoffPeriodic", + app.NoCutoff: "NoCutoff", + app.PME: "PME", + } + if nonbondedMethod not in methodMap: + raise ValueError("Illegal nonbonded method for ADMPDispPmeForce") + if nonbondedMethod is app.CutoffPeriodic: + self.lpme = False + else: + self.lpme = True + + n_atoms = len(data.atoms) + # build index map + map_atomtype = np.zeros(n_atoms, dtype=int) + for i in range(n_atoms): + atype = data.atomType[data.atoms[i]] + map_atomtype[i] = np.where(self.atomTypes == atype)[0][0] + self.map_atomtype = map_atomtype + + # build covalent map + self.covalent_map = build_covalent_map(data, 6) + + # here box is only used to setup ewald parameters, no need to be differentiable + a, b, c = system.getDefaultPeriodicBoxVectors() + box = jnp.array([a._value, b._value, c._value]) * 10 + # get the admp calculator + rc = nonbondedCutoff.value_in_unit(unit.angstrom) + + # get calculator + if "ethresh" in args: + self.ethresh = args["ethresh"] + + disp_force = ADMPDispPmeForce(box, rc, self.ethresh, self.pmax, + self.lpme) + self.disp_force = disp_force + pot_fn_lr = disp_force.get_energy + + def potential_fn(positions, box, pairs, params): + params = params[self.name] + mScales = params["mScales"] + C6_list = params["C6"][map_atomtype] * 1e6 # to kj/mol * A**6 + C8_list = params["C8"][map_atomtype] * 1e8 + C10_list = params["C10"][map_atomtype] * 1e10 + c6_list = jnp.sqrt(C6_list) + c8_list = jnp.sqrt(C8_list) + c10_list = jnp.sqrt(C10_list) + c_list = jnp.vstack((c6_list, c8_list, c10_list)) + E_lr = pot_fn_lr(positions, box, pairs, c_list.T, mScales) + return -E_lr + + self._jaxPotential = potential_fn + # self._top_data = data + + def getJaxPotential(self): + return self._jaxPotential + + +dmff.api.jaxGenerators['ADMPDispPmeForce'] = ADMPDispPmeGenerator + + +class QqTtDampingGenerator: + r""" + This one calculates the tang-tonnies damping of charge-charge interaction + E = \sum_ij exp(-B*r)*(1+B*r)*q_i*q_j/r + """ + def __init__(self, ff): + self.ff = ff + self.fftree = ff.fftree + self._jaxPotential = None + self.paramtree = ff.paramtree + self._jaxPotnetial = None + self.name = "QqTtDampingForce" + + def extract(self): + # get mscales + mScales = [ + self.fftree.get_attribs(f'{self.name}', f'mScale1{i}')[0] + for i in range(2, 7) + ] + mScales.append(1.0) + self.paramtree[self.name] = {} + self.paramtree[self.name]['mScales'] = jnp.array(mScales) + # get atomtypes + atomTypes = self.fftree.get_attribs(f'{self.name}/Atom', f'type') + if type(atomTypes[0]) != str: + self.atomTypes = np.array(atomTypes, dtype=int).astype(str) + else: + self.atomTypes = np.array(atomTypes) + # get atomic parameters + B = self.fftree.get_attribs(f'{self.name}/Atom', f'B') + Q = self.fftree.get_attribs(f'{self.name}/Atom', f'Q') + self.paramtree[self.name]['B'] = jnp.array(B) + self.paramtree[self.name]['Q'] = jnp.array(Q) + + def overwrite(self): + + self.fftree.set_attrib(f'{self.name}', 'mScale12', + [self.paramtree[self.name]['mScales'][0]]) + self.fftree.set_attrib(f'{self.name}', 'mScale13', + [self.paramtree[self.name]['mScales'][1]]) + self.fftree.set_attrib(f'{self.name}', 'mScale14', + [self.paramtree[self.name]['mScales'][2]]) + self.fftree.set_attrib(f'{self.name}', 'mScale15', + [self.paramtree[self.name]['mScales'][3]]) + self.fftree.set_attrib(f'{self.name}', 'mScale16', + [self.paramtree[self.name]['mScales'][4]]) + + self.fftree.set_attrib(f'{self.name}/Atom', 'B', + self.paramtree[self.name]['B']) + self.fftree.set_attrib(f'{self.name}/Atom', 'Q', + self.paramtree[self.name]['Q']) + + # on working + def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, + args): + + n_atoms = len(data.atoms) + # build index map + map_atomtype = np.zeros(n_atoms, dtype=int) + for i in range(n_atoms): + atype = data.atomType[data.atoms[i]] + map_atomtype[i] = np.where(self.atomTypes == atype)[0][0] + self.map_atomtype = map_atomtype + + # build covalent map + self.covalent_map = build_covalent_map(data, 6) + + pot_fn_sr = generate_pairwise_interaction(TT_damping_qq_kernel, + static_args={}) + + def potential_fn(positions, box, pairs, params): + params = params[self.name] + mScales = params["mScales"] + b_list = params["B"][map_atomtype] / 10 # convert to A^-1 + q_list = params["Q"][map_atomtype] + + E_sr = pot_fn_sr(positions, box, pairs, mScales, b_list, q_list) + return E_sr + + self._jaxPotential = potential_fn + + def getJaxPotential(self): + return self._jaxPotential + + +# register all parsers +dmff.api.jaxGenerators['QqTtDampingForce'] = QqTtDampingGenerator + + +class SlaterDampingGenerator: + r""" + This one computes the slater-type damping function for c6/c8/c10 dispersion + E = \sum_ij (f6-1)*c6/r6 + (f8-1)*c8/r8 + (f10-1)*c10/r10 + fn = f_tt(x, n) + x = br - (2*br2 + 3*br) / (br2 + 3*br + 3) + """ + def __init__(self, ff): + self.name = "SlaterDampingForce" + self.ff = ff + self.fftree = ff.fftree + self.paramtree = ff.paramtree + self._jaxPotential = None + + def extract(self): + # get mscales + mScales = [ + self.fftree.get_attribs(f'{self.name}', f'mScale1{i}')[0] + for i in range(2, 7) + ] + mScales.append(1.0) + self.paramtree[self.name] = {} + self.paramtree[self.name]['mScales'] = jnp.array(mScales) + # get atomtypes + atomTypes = self.fftree.get_attribs(f'{self.name}/Atom', f'type') + if type(atomTypes[0]) != str: + self.atomTypes = np.array(atomTypes, dtype=int).astype(str) + else: + self.atomTypes = np.array(atomTypes) + # get atomic parameters + B = self.fftree.get_attribs(f'{self.name}/Atom', f'B') + C6 = self.fftree.get_attribs(f'{self.name}/Atom', f'C6') + C8 = self.fftree.get_attribs(f'{self.name}/Atom', f'C8') + C10 = self.fftree.get_attribs(f'{self.name}/Atom', f'C10') + self.paramtree[self.name]['B'] = jnp.array(B) + self.paramtree[self.name]['C6'] = jnp.array(C6) + self.paramtree[self.name]['C8'] = jnp.array(C8) + self.paramtree[self.name]['C10'] = jnp.array(C10) + + def overwrite(self): + + self.fftree.set_attrib(f'{self.name}', 'mScale12', + [self.paramtree[self.name]['mScales'][0]]) + self.fftree.set_attrib(f'{self.name}', 'mScale13', + [self.paramtree[self.name]['mScales'][1]]) + self.fftree.set_attrib(f'{self.name}', 'mScale14', + [self.paramtree[self.name]['mScales'][2]]) + self.fftree.set_attrib(f'{self.name}', 'mScale15', + [self.paramtree[self.name]['mScales'][3]]) + self.fftree.set_attrib(f'{self.name}', 'mScale16', + [self.paramtree[self.name]['mScales'][4]]) + + self.fftree.set_attrib(f'{self.name}/Atom', 'B', + [self.paramtree[self.name]['B']]) + self.fftree.set_attrib(f'{self.name}/Atom', 'C6', + [self.paramtree[self.name]['C6']]) + self.fftree.set_attrib(f'{self.name}/Atom', 'C8', + [self.paramtree[self.name]['C8']]) + self.fftree.set_attrib(f'{self.name}/Atom', 'C10', + [self.paramtree[self.name]['C10']]) + + def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, + args): + + n_atoms = len(data.atoms) + # build index map + map_atomtype = np.zeros(n_atoms, dtype=int) + for i in range(n_atoms): + atype = data.atomType[data.atoms[i]] + map_atomtype[i] = np.where(self.atomTypes == atype)[0][0] + self.map_atomtype = map_atomtype + # build covalent map + self.covalent_map = build_covalent_map(data, 6) + + # WORKING + pot_fn_sr = generate_pairwise_interaction(slater_disp_damping_kernel, + static_args={}) + + def potential_fn(positions, box, pairs, params): + params = params[self.name] + mScales = params["mScales"] + b_list = params["B"][map_atomtype] / 10 # convert to A^-1 + c6_list = jnp.sqrt(params["C6"][map_atomtype] * + 1e6) # to kj/mol * A**6 + c8_list = jnp.sqrt(params["C8"][map_atomtype] * 1e8) + c10_list = jnp.sqrt(params["C10"][map_atomtype] * 1e10) + E_sr = pot_fn_sr(positions, box, pairs, mScales, b_list, c6_list, + c8_list, c10_list) + return E_sr + + self._jaxPotential = potential_fn + # self._top_data = data + + def getJaxPotential(self): + return self._jaxPotential + + +dmff.api.jaxGenerators['SlaterDampingForce'] = SlaterDampingGenerator + + +class SlaterExGenerator: + r""" + This one computes the Slater-ISA type exchange interaction + u = \sum_ij A * (1/3*(Br)^2 + Br + 1) + """ + def __init__(self, ff): + self.name = "SlaterExForce" + self.ff = ff + self.fftree = ff.fftree + self.paramtree = ff.paramtree + self._jaxPotential = None + + def extract(self): + # get mscales + mScales = [ + self.fftree.get_attribs(f'{self.name}', f'mScale1{i}')[0] + for i in range(2, 7) + ] + mScales.append(1.0) + self.paramtree[self.name] = {} + self.paramtree[self.name]['mScales'] = jnp.array(mScales) + # get atomtypes + atomTypes = self.fftree.get_attribs(f'{self.name}/Atom', f'type') + if type(atomTypes[0]) != str: + self.atomTypes = np.array(atomTypes, dtype=int).astype(str) + else: + self.atomTypes = np.array(atomTypes) + # get atomic parameters + A = self.fftree.get_attribs(f'{self.name}/Atom', f'A') + B = self.fftree.get_attribs(f'{self.name}/Atom', f'B') + self.paramtree[self.name]['A'] = jnp.array(A) + self.paramtree[self.name]['B'] = jnp.array(B) + + def overwrite(self): + + self.fftree.set_attrib(f'{self.name}', 'mScale12', + [self.paramtree[self.name]['mScales'][0]]) + self.fftree.set_attrib(f'{self.name}', 'mScale13', + [self.paramtree[self.name]['mScales'][1]]) + self.fftree.set_attrib(f'{self.name}', 'mScale14', + [self.paramtree[self.name]['mScales'][2]]) + self.fftree.set_attrib(f'{self.name}', 'mScale15', + [self.paramtree[self.name]['mScales'][3]]) + self.fftree.set_attrib(f'{self.name}', 'mScale16', + [self.paramtree[self.name]['mScales'][4]]) + + self.fftree.set_attrib(f'{self.name}/Atom', 'A', + [self.paramtree[self.name]['A']]) + self.fftree.set_attrib(f'{self.name}/Atom', 'B', + [self.paramtree[self.name]['B']]) + + def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, + args): + + n_atoms = len(data.atoms) + # build index map + map_atomtype = np.zeros(n_atoms, dtype=int) + for i in range(n_atoms): + atype = data.atomType[data.atoms[i]] + map_atomtype[i] = np.where(self.atomTypes == atype)[0][0] + self.map_atomtype = map_atomtype + # build covalent map + self.covalent_map = build_covalent_map(data, 6) + + pot_fn_sr = generate_pairwise_interaction(slater_sr_kernel, + static_args={}) + + def potential_fn(positions, box, pairs, params): + params = params[self.name] + mScales = params["mScales"] + a_list = params["A"][map_atomtype] + b_list = params["B"][map_atomtype] / 10 # nm^-1 to A^-1 + + return pot_fn_sr(positions, box, pairs, mScales, a_list, b_list) + + self._jaxPotential = potential_fn + # self._top_data = data + + def getJaxPotential(self): + return self._jaxPotential + + +dmff.api.jaxGenerators["SlaterExForce"] = SlaterExGenerator + + +# Here are all the short range "charge penetration" terms +# They all have the exchange form +class SlaterSrEsGenerator(SlaterExGenerator): + def __init__(self, ff): + super().__init__(ff) + self.name = "SlaterSrEsForce" + + +class SlaterSrPolGenerator(SlaterExGenerator): + def __init__(self, ff): + super().__init__(ff) + self.name = "SlaterSrPolForce" + + +class SlaterSrDispGenerator(SlaterExGenerator): + def __init__(self, ff): + super().__init__(ff) + self.name = "SlaterSrDispForce" + + +class SlaterDhfGenerator(SlaterExGenerator): + def __init__(self, ff): + super().__init__(ff) + self.name = "SlaterDhfForce" + + +# register all parsers +dmff.api.jaxGenerators["SlaterSrEsForce"] = SlaterSrEsGenerator +dmff.api.jaxGenerators["SlaterSrPolForce"] = SlaterSrPolGenerator +dmff.api.jaxGenerators["SlaterSrDispForce"] = SlaterSrDispGenerator +dmff.api.jaxGenerators["SlaterDhfForce"] = SlaterDhfGenerator + + +class ADMPPmeGenerator: + def __init__(self, ff): + + self.name = 'ADMPPmeForce' + self.ff = ff + self.fftree = ff.fftree + self.paramtree = ff.paramtree + + # default params + self._jaxPotential = None + self.types = [] + self.ethresh = 5e-4 + self.step_pol = None + self.lpol = False + self.ref_dip = "" + + def extract(self): + + self.lmax = self.fftree.get_attribs(f'{self.name}', + 'lmax')[0] # return [lmax] + + mScales = [ + self.fftree.get_attribs(f'{self.name}', f'mScale1{i}')[0] + for i in range(2, 7) + ] + pScales = [ + self.fftree.get_attribs(f'{self.name}', f'pScale1{i}')[0] + for i in range(2, 7) + ] + dScales = [ + self.fftree.get_attribs(f'{self.name}', f'dScale1{i}')[0] + for i in range(2, 7) + ] + + # make sure the last digit is 1.0 + mScales.append(1.0) + pScales.append(1.0) + dScales.append(1.0) + + self.paramtree[self.name] = {} + self.paramtree[self.name]['mScales'] = jnp.array(mScales) + self.paramtree[self.name]['pScales'] = jnp.array(pScales) + self.paramtree[self.name]['dScales'] = jnp.array(dScales) + + # check if polarize + polarize = self.fftree.get_nodes(f'{self.name}/Polarize') + if polarize: + self.lpol = True + else: + self.lpol = False + + atomTypes = self.fftree.get_attribs(f'{self.name}/Atom', 'type') + if type(atomTypes[0]) != str: + self.atomTypes = np.array(atomTypes, dtype=int).astype(str) + else: + self.atomTypes = np.array(atomTypes) + kx = self.fftree.get_attribs(f'{self.name}/Atom', 'kx') + ky = self.fftree.get_attribs(f'{self.name}/Atom', 'ky') + kz = self.fftree.get_attribs(f'{self.name}/Atom', 'kz') + + kx = [0 if kx_ is None else int(kx_) for kx_ in kx] + ky = [0 if ky_ is None else int(ky_) for ky_ in ky] + kz = [0 if kz_ is None else int(kz_) for kz_ in kz] + + # invoke by `self.kStrings["kz"][itype]` + self.kStrings = {} + self.kStrings['kx'] = kx + self.kStrings['ky'] = ky + self.kStrings['kz'] = kz + + c0 = self.fftree.get_attribs(f'{self.name}/Atom', 'c0') + dX = self.fftree.get_attribs(f'{self.name}/Atom', 'dX') + dY = self.fftree.get_attribs(f'{self.name}/Atom', 'dY') + dZ = self.fftree.get_attribs(f'{self.name}/Atom', 'dZ') + qXX = self.fftree.get_attribs(f'{self.name}/Atom', 'qXX') + qYY = self.fftree.get_attribs(f'{self.name}/Atom', 'qYY') + qZZ = self.fftree.get_attribs(f'{self.name}/Atom', 'qZZ') + qXY = self.fftree.get_attribs(f'{self.name}/Atom', 'qXY') + qXZ = self.fftree.get_attribs(f'{self.name}/Atom', 'qXZ') + qYZ = self.fftree.get_attribs(f'{self.name}/Atom', 'qYZ') + + # assume that polarize tag match the per atom type + # pol_XX = self.fftree.get_attribs(f'{self.name}/Polarize', 'polarizabilityXX') + # pol_YY = self.fftree.get_attribs(f'{self.name}/Polarize', 'polarizabilityYY') + # pol_ZZ = self.fftree.get_attribs(f'{self.name}/Polarize', 'polarizabilityZZ') + # thole_0 = self.fftree.get_attribs(f'{self.name}/Polarize', 'thole') + if self.lpol: + polarizabilityXX = self.fftree.get_attribs(f'{self.name}/Polarize', + 'polarizabilityXX') + polarizabilityYY = self.fftree.get_attribs(f'{self.name}/Polarize', + 'polarizabilityYY') + polarizabilityZZ = self.fftree.get_attribs(f'{self.name}/Polarize', + 'polarizabilityZZ') + thole = self.fftree.get_attribs(f'{self.name}/Polarize', 'thole') + polarize_types = self.fftree.get_attribs(f'{self.name}/Polarize', + 'type') + if type(polarize_types[0]) != str: + polarize_types = np.array(polarize_types, + dtype=int).astype(str) + else: + polarize_types = np.array(polarize_types) + self.polarize_types = polarize_types + + n_atoms = len(atomTypes) + + # assert n_atoms == len(polarizabilityXX), "Number of polarizabilityXX does not match number of atoms!" + + # map atom multipole moments + if self.lmax == 0: + n_mtps = 1 + elif self.lmax == 1: + n_mtps = 4 + elif self.lmax == 2: + n_mtps = 10 + Q = np.zeros((n_atoms, n_mtps)) + + # TDDO: unit conversion + Q[:, 0] = c0 + if self.lmax >= 1: + Q[:, 1] = dX + Q[:, 2] = dY + Q[:, 3] = dZ + Q[:, 1:4] *= 10 + if self.lmax >= 2: + Q[:, 4] = qXX + Q[:, 5] = qYY + Q[:, 6] = qZZ + Q[:, 7] = qXY + Q[:, 8] = qXZ + Q[:, 9] = qYZ + Q[:, 4:10] *= 300 + + # add all differentiable params to self.params + Q_local = convert_cart2harm(Q, self.lmax) + self.paramtree[self.name]["Q_local"] = Q_local + + if self.lpol: + pol = jnp.vstack(( + polarizabilityXX, + polarizabilityYY, + polarizabilityZZ, + )).T + pol = 1000 * jnp.mean(pol, axis=1) + tholes = jnp.array(thole) + self.paramtree[self.name]["pol"] = pol + self.paramtree[self.name]["tholes"] = tholes + else: + pol = None + tholes = None + + def overwrite(self): + + self.fftree.set_attrib(f'{self.name}', 'mScale12', + [self.paramtree[self.name]['mScales'][0]]) + self.fftree.set_attrib(f'{self.name}', 'mScale13', + [self.paramtree[self.name]['mScales'][1]]) + self.fftree.set_attrib(f'{self.name}', 'mScale14', + [self.paramtree[self.name]['mScales'][2]]) + self.fftree.set_attrib(f'{self.name}', 'mScale15', + [self.paramtree[self.name]['mScales'][3]]) + self.fftree.set_attrib(f'{self.name}', 'mScale16', + [self.paramtree[self.name]['mScales'][4]]) + + self.fftree.set_attrib(f'{self.name}', 'pScale12', + [self.paramtree[self.name]['pScales'][0]]) + self.fftree.set_attrib(f'{self.name}', 'pScale13', + [self.paramtree[self.name]['pScales'][1]]) + self.fftree.set_attrib(f'{self.name}', 'pScale14', + [self.paramtree[self.name]['pScales'][2]]) + self.fftree.set_attrib(f'{self.name}', 'pScale15', + [self.paramtree[self.name]['pScales'][3]]) + self.fftree.set_attrib(f'{self.name}', 'pScale16', + [self.paramtree[self.name]['pScales'][4]]) + + self.fftree.set_attrib(f'{self.name}', 'dScale12', + [self.paramtree[self.name]['dScales'][0]]) + self.fftree.set_attrib(f'{self.name}', 'dScale13', + [self.paramtree[self.name]['dScales'][1]]) + self.fftree.set_attrib(f'{self.name}', 'dScale14', + [self.paramtree[self.name]['dScales'][2]]) + self.fftree.set_attrib(f'{self.name}', 'dScale15', + [self.paramtree[self.name]['dScales'][3]]) + self.fftree.set_attrib(f'{self.name}', 'dScale16', + [self.paramtree[self.name]['dScales'][4]]) + + Q_global = convert_harm2cart(self.paramtree[self.name]['Q_local'], + self.lmax) + + self.fftree.set_attrib(f'{self.name}/Atom', 'c0', Q_global[:, 0]) + self.fftree.set_attrib(f'{self.name}/Atom', 'dX', Q_global[:, 1]) + self.fftree.set_attrib(f'{self.name}/Atom', 'dY', Q_global[:, 2]) + self.fftree.set_attrib(f'{self.name}/Atom', 'dZ', Q_global[:, 3]) + self.fftree.set_attrib(f'{self.name}/Atom', 'qXX', Q_global[:, 4]) + self.fftree.set_attrib(f'{self.name}/Atom', 'qYY', Q_global[:, 5]) + self.fftree.set_attrib(f'{self.name}/Atom', 'qZZ', Q_global[:, 6]) + self.fftree.set_attrib(f'{self.name}/Atom', 'qXY', Q_global[:, 7]) + self.fftree.set_attrib(f'{self.name}/Atom', 'qXZ', Q_global[:, 8]) + self.fftree.set_attrib(f'{self.name}/Atom', 'qYZ', Q_global[:, 9]) + + if self.lpol: + # self.paramtree[self.name]['pol']: every element is the mean value of XX YY ZZ + # get the number of polarize element + n_pol = len(self.paramtree[self.name]['pol']) + self.fftree.set_attrib(f'{self.name}/Polarize', 'polarizabilityXX', + [self.paramtree[self.name]['pol'][0]] * + n_pol) + self.fftree.set_attrib(f'{self.name}/Polarize', 'polarizabilityYY', + [self.paramtree[self.name]['pol'][1]] * + n_pol) + self.fftree.set_attrib(f'{self.name}/Polarize', 'polarizabilityZZ', + [self.paramtree[self.name]['pol'][2]] * + n_pol) + self.fftree.set_attrib(f'{self.name}/Polarize', 'thole', + self.paramtree[self.name]['tholes']) + + def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, + args): + + methodMap = { + app.CutoffPeriodic: "CutoffPeriodic", + app.NoCutoff: "NoCutoff", + app.PME: "PME", + } + if nonbondedMethod not in methodMap: + raise ValueError("Illegal nonbonded method for ADMPPmeForce") + if nonbondedMethod is app.CutoffPeriodic: + self.lpme = False + else: + self.lpme = True + + n_atoms = len(data.atoms) + map_atomtype = np.zeros(n_atoms, dtype=int) + map_poltype = np.zeros(n_atoms, dtype=int) + + for i in range(n_atoms): + atype = data.atomType[ + data.atoms[i]] # convert str to int to match atomTypes + map_atomtype[i] = np.where(self.atomTypes == atype)[0][0] + if self.lpol: + map_poltype[i] = np.where(self.polarize_types == atype)[0][0] + self.map_atomtype = map_atomtype + if self.lpol: + self.map_poltype = map_poltype + + # here box is only used to setup ewald parameters, no need to be differentiable + a, b, c = system.getDefaultPeriodicBoxVectors() + box = jnp.array([a._value, b._value, c._value]) * 10 + + # get the admp calculator + rc = nonbondedCutoff.value_in_unit(unit.angstrom) + + # build covalent map + self.covalent_map = covalent_map = build_covalent_map(data, 6) + + # build intra-molecule axis + # the following code is the direct transplant of forcefield.py in openmm 7.4.0 + + if self.lmax > 0: + + # setting up axis_indices and axis_type + ZThenX = 0 + Bisector = 1 + ZBisect = 2 + ThreeFold = 3 + ZOnly = 4 # typo fix + NoAxisType = 5 + LastAxisTypeIndex = 6 + + self.axis_types = [] + self.axis_indices = [] + for i_atom in range(n_atoms): + atom = data.atoms[i_atom] + t = data.atomType[atom] + # if t is in type list? + if t in self.atomTypes: + itypes = np.where(self.atomTypes == t)[0] + hit = 0 + # try to assign multipole parameters via only 1-2 connected atoms + for itype in itypes: + if hit != 0: + break + kz = int(self.kStrings["kz"][itype]) + kx = int(self.kStrings["kx"][itype]) + ky = int(self.kStrings["ky"][itype]) + neighbors = np.where(covalent_map[i_atom] == 1)[0] + zaxis = -1 + xaxis = -1 + yaxis = -1 + for z_index in neighbors: + if hit != 0: + break + z_type = int(data.atomType[data.atoms[z_index]]) + if z_type == abs( + kz + ): # find the z atom, start searching for x + for x_index in neighbors: + if x_index == z_index or hit != 0: + continue + x_type = int( + data.atomType[data.atoms[x_index]]) + if x_type == abs( + kx + ): # find the x atom, start searching for y + if ky == 0: + zaxis = z_index + xaxis = x_index + # cannot ditinguish x and z? use the smaller index for z, and the larger index for x + if x_type == z_type and xaxis < zaxis: + swap = z_axis + z_axis = x_axis + x_axis = swap + # otherwise, try to see if we can find an even smaller index for x? + else: + for x_index in neighbors: + x_type1 = int( + data.atomType[ + data. + atoms[x_index]]) + if (x_type1 == abs(kx) and + x_index != z_index + and + x_index < xaxis): + xaxis = x_index + hit = 1 # hit, finish matching + matched_itype = itype + else: + for y_index in neighbors: + if (y_index == z_index + or y_index == x_index + or hit != 0): + continue + y_type = int(data.atomType[ + data.atoms[y_index]]) + if y_type == abs(ky): + zaxis = z_index + xaxis = x_index + yaxis = y_index + hit = 2 + matched_itype = itype + # assign multipole parameters via 1-2 and 1-3 connected atoms + for itype in itypes: + if hit != 0: + break + kz = int(self.kStrings["kz"][itype]) + kx = int(self.kStrings["kx"][itype]) + ky = int(self.kStrings["ky"][itype]) + neighbors_1st = np.where(covalent_map[i_atom] == 1)[0] + neighbors_2nd = np.where(covalent_map[i_atom] == 2)[0] + zaxis = -1 + xaxis = -1 + yaxis = -1 + for z_index in neighbors_1st: + if hit != 0: + break + z_type = int(data.atomType[data.atoms[z_index]]) + if z_type == abs(kz): + for x_index in neighbors_2nd: + if x_index == z_index or hit != 0: + continue + x_type = int( + data.atomType[data.atoms[x_index]]) + # we ask x to be in 2'nd neighbor, and x is z's neighbor + if (x_type == abs(kx) + and covalent_map[z_index, + x_index] == 1): + if ky == 0: + zaxis = z_index + xaxis = x_index + # select smallest x index + for x_index in neighbors_2nd: + x_type1 = int(data.atomType[ + data.atoms[x_index]]) + if (x_type1 == abs(kx) + and x_index != z_index + and + covalent_map[x_index, + z_index] + == 1 + and x_index < xaxis): + xaxis = x_index + hit = 3 + matched_itype = itype + else: + for y_index in neighbors_2nd: + if (y_index == z_index + or y_index == x_index + or hit != 0): + continue + y_type = int(data.atomType[ + data.atoms[y_index]]) + if (y_type == abs(ky) and + covalent_map[y_index, + z_index] + == 1): + zaxis = z_index + xaxis = x_index + yaxis = y_index + hit = 4 + matched_itype = itype + # assign multipole parameters via only a z-defining atom + for itype in itypes: + if hit != 0: + break + kz = int(self.kStrings["kz"][itype]) + kx = int(self.kStrings["kx"][itype]) + zaxis = -1 + xaxis = -1 + yaxis = -1 + neighbors = np.where(covalent_map[i_atom] == 1)[0] + for z_index in neighbors: + if hit != 0: + break + z_type = int(data.atomType[data.atoms[z_index]]) + if kx == 0 and z_type == abs(kz): + zaxis = z_index + hit = 5 + matched_itype = itype + # assign multipole parameters via no connected atoms + for itype in itypes: + if hit != 0: + break + kz = int(self.kStrings["kz"][itype]) + zaxis = -1 + xaxis = -1 + yaxis = -1 + if kz == 0: + hit = 6 + matched_itype = itype + # add particle if there was a hit + if hit != 0: + map_atomtype[i_atom] = matched_itype + self.axis_indices.append([zaxis, xaxis, yaxis]) + + kz = int(self.kStrings["kz"][matched_itype]) + kx = int(self.kStrings["kx"][matched_itype]) + ky = int(self.kStrings["ky"][matched_itype]) + axisType = ZThenX + if kz == 0: + axisType = NoAxisType + if kz != 0 and kx == 0: + axisType = ZOnly + if kz < 0 or kx < 0: + axisType = Bisector + if kx < 0 and ky < 0: + axisType = ZBisect + if kz < 0 and kx < 0 and ky < 0: + axisType = ThreeFold + self.axis_types.append(axisType) + + else: + sys.exit("Atom %d not matched in forcefield!" % i_atom) + + else: + sys.exit("Atom %d not matched in forcefield!" % i_atom) + self.axis_indices = np.array(self.axis_indices) + self.axis_types = np.array(self.axis_types) + else: + self.axis_types = None + self.axis_indices = None + + if "ethresh" in args: + self.ethresh = args["ethresh"] + if "step_pol" in args: + self.step_pol = args["step_pol"] + + pme_force = ADMPPmeForce(box, self.axis_types, self.axis_indices, rc, + self.ethresh, self.lmax, self.lpol, self.lpme, + self.step_pol) + self.pme_force = pme_force + + def potential_fn(positions, box, pairs, params): + params = params['ADMPPmeForce'] + mScales = params["mScales"] + Q_local = params["Q_local"][map_atomtype] + if self.lpol: + pScales = params["pScales"] + dScales = params["dScales"] + pol = params["pol"][map_poltype] + tholes = params["tholes"][map_poltype] + + return pme_force.get_energy( + positions, + box, + pairs, + Q_local, + pol, + tholes, + mScales, + pScales, + dScales, + pme_force.U_ind, + ) + else: + return pme_force.get_energy(positions, box, pairs, Q_local, + mScales) + + self._jaxPotential = potential_fn + + def getJaxPotential(self): + return self._jaxPotential + + +dmff.api.jaxGenerators["ADMPPmeForce"] = ADMPPmeGenerator \ No newline at end of file diff --git a/dmff/generators/classical.py b/dmff/generators/classical.py new file mode 100644 index 000000000..b4584ba9a --- /dev/null +++ b/dmff/generators/classical.py @@ -0,0 +1,942 @@ +from collections import defaultdict +from typing import Dict + +import numpy as np +import jax.numpy as jnp +import openmm.app as app +import openmm.unit as unit + +import dmff +from dmff.classical.intra import ( + HarmonicBondJaxForce, + HarmonicAngleJaxForce, + PeriodicTorsionJaxForce, +) +from dmff.classical.inter import ( + LennardJonesForce, + LennardJonesLongRangeForce, + CoulombPMEForce, + CoulNoCutoffForce, + CoulombPMEForce, + CoulReactionFieldForce, + LennardJonesForce, +) +from dmff.classical.fep import ( + LennardJonesFreeEnergyForce, + LennardJonesLongRangeFreeEnergyForce, + CoulombPMEFreeEnergyForce +) +from dmff.admp.pme import setup_ewald_parameters +from dmff.utils import jit_condition, isinstance_jnp, DMFFException, findItemInList +from dmff.fftree import ForcefieldTree, TypeMatcher +from dmff.api import Hamiltonian, build_covalent_map + + +class HarmonicBondJaxGenerator: + def __init__(self, ff: Hamiltonian): + self.name = "HarmonicBondForce" + self.ff: Hamiltonian = ff + self.fftree: ForcefieldTree = ff.fftree + self.paramtree: Dict = ff.paramtree + + def extract(self): + """ + extract forcefield paramters from ForcefieldTree. + """ + lengths = self.fftree.get_attribs(f"{self.name}/Bond", "length") + # get_attribs will return a list of list. + ks = self.fftree.get_attribs(f"{self.name}/Bond", "k") + self.paramtree[self.name] = {} + self.paramtree[self.name]["length"] = jnp.array(lengths) + self.paramtree[self.name]["k"] = jnp.array(ks) + + def overwrite(self): + """ + update parameters in the fftree by using paramtree of this generator. + """ + self.fftree.set_attrib(f"{self.name}/Bond", "length", + self.paramtree[self.name]["length"]) + self.fftree.set_attrib(f"{self.name}/Bond", "k", + self.paramtree[self.name]["k"]) + + def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args): + """ + This method will create a potential calculation kernel. It usually should do the following: + + 1. Match the corresponding bond parameters according to the atomic types at both ends of each bond. + + 2. Create a potential calculation kernel, and pass those mapped parameters to the kernel. + + 3. assign the jax potential to the _jaxPotential. + + Args: + Those args are the same as those in createSystem. + """ + + # initialize typemap + matcher = TypeMatcher(self.fftree, "HarmonicBondForce/Bond") + + map_atom1, map_atom2, map_param = [], [], [] + n_bonds = len(data.bonds) + # build map + for i in range(n_bonds): + idx1 = data.bonds[i].atom1 + idx2 = data.bonds[i].atom2 + type1 = data.atomType[data.atoms[idx1]] + type2 = data.atomType[data.atoms[idx2]] + ifFound, ifForward, nfunc = matcher.matchGeneral([type1, type2]) + if not ifFound: + raise BaseException( + f"No parameter for bond ({idx1},{type1}) - ({idx2},{type2})" + ) + map_atom1.append(idx1) + map_atom2.append(idx2) + map_param.append(nfunc) + map_atom1 = np.array(map_atom1, dtype=int) + map_atom2 = np.array(map_atom2, dtype=int) + map_param = np.array(map_param, dtype=int) + + bforce = HarmonicBondJaxForce(map_atom1, map_atom2, map_param) + + def potential_fn(positions, box, pairs, params): + return bforce.get_energy(positions, box, pairs, + params[self.name]["k"], + params[self.name]["length"]) + + self._jaxPotential = potential_fn + # self._top_data = data + + def getJaxPotential(self): + return self._jaxPotential + + +dmff.api.jaxGenerators["HarmonicBondForce"] = HarmonicBondJaxGenerator + + +class HarmonicAngleJaxGenerator: + def __init__(self, ff): + self.name = "HarmonicAngleForce" + self.ff = ff + self.fftree = ff.fftree + self.paramtree = ff.paramtree + + def extract(self): + angles = self.fftree.get_attribs(f"{self.name}/Angle", "angle") + ks = self.fftree.get_attribs(f"{self.name}/Angle", "k") + self.paramtree[self.name] = {} + self.paramtree[self.name]["angle"] = jnp.array(angles) + self.paramtree[self.name]["k"] = jnp.array(ks) + + def overwrite(self): + self.fftree.set_attrib(f"{self.name}/Angle", "angle", + self.paramtree[self.name]["angle"]) + self.fftree.set_attrib(f"{self.name}/Angle", "k", + self.paramtree[self.name]["k"]) + + def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args): + matcher = TypeMatcher(self.fftree, "HarmonicAngleForce/Angle") + + map_atom1, map_atom2, map_atom3, map_param = [], [], [], [] + n_angles = len(data.angles) + for nangle in range(n_angles): + idx1 = data.angles[nangle][0] + idx2 = data.angles[nangle][1] + idx3 = data.angles[nangle][2] + type1 = data.atomType[data.atoms[idx1]] + type2 = data.atomType[data.atoms[idx2]] + type3 = data.atomType[data.atoms[idx3]] + ifFound, ifForward, nfunc = matcher.matchGeneral( + [type1, type2, type3]) + if not ifFound: + print( + f"No parameter for angle ({idx1},{type1}) - ({idx2},{type2}) - ({idx3},{type3})" + ) + else: + map_atom1.append(idx1) + map_atom2.append(idx2) + map_atom3.append(idx3) + map_param.append(nfunc) + map_atom1 = np.array(map_atom1, dtype=int) + map_atom2 = np.array(map_atom2, dtype=int) + map_atom3 = np.array(map_atom3, dtype=int) + map_param = np.array(map_param, dtype=int) + + aforce = HarmonicAngleJaxForce(map_atom1, map_atom2, map_atom3, + map_param) + + def potential_fn(positions, box, pairs, params): + return aforce.get_energy(positions, box, pairs, + params[self.name]["k"], + params[self.name]["angle"]) + + self._jaxPotential = potential_fn + # self._top_data = data + + def getJaxPotential(self): + return self._jaxPotential + + +dmff.api.jaxGenerators["HarmonicAngleForce"] = HarmonicAngleJaxGenerator + + +class PeriodicTorsionJaxGenerator: + def __init__(self, ff): + self.name = "PeriodicTorsionForce" + self.ff = ff + self.fftree = ff.fftree + self.paramtree = ff.paramtree + self.meta = {} + + self.meta["prop_order"] = defaultdict(list) + self.meta["prop_nodeidx"] = defaultdict(list) + + self.meta["impr_order"] = defaultdict(list) + self.meta["impr_nodeidx"] = defaultdict(list) + + self.max_pred_prop = 0 + self.max_pred_impr = 0 + + def extract(self): + propers = self.fftree.get_nodes("PeriodicTorsionForce/Proper") + impropers = self.fftree.get_nodes("PeriodicTorsionForce/Improper") + self.paramtree[self.name] = {} + # propers + prop_phase = defaultdict(list) + prop_k = defaultdict(list) + for nnode, node in enumerate(propers): + for key in node.attrs: + if "periodicity" in key: + order = int(key[-1]) + phase = float(node.attrs[f"phase{order}"]) + k = float(node.attrs[f"k{order}"]) + periodicity = int(node.attrs[f"periodicity{order}"]) + if self.max_pred_prop < periodicity: + self.max_pred_prop = periodicity + prop_phase[f"{periodicity}"].append(phase) + prop_k[f"{periodicity}"].append(k) + self.meta[f"prop_order"][f"{periodicity}"].append(order) + self.meta[f"prop_nodeidx"][f"{periodicity}"].append(nnode) + + self.paramtree[self.name]["prop_phase"] = {} + self.paramtree[self.name]["prop_k"] = {} + for npred in range(1, self.max_pred_prop + 1): + self.paramtree[self.name]["prop_phase"][f"{npred}"] = jnp.array( + prop_phase[f"{npred}"]) + self.paramtree[self.name]["prop_k"][f"{npred}"] = jnp.array( + prop_k[f"{npred}"]) + if self.max_pred_prop == 0: + del self.paramtree[self.name]["prop_phase"] + del self.paramtree[self.name]["prop_k"] + + # impropers + impr_phase = defaultdict(list) + impr_k = defaultdict(list) + for nnode, node in enumerate(impropers): + for key in node.attrs: + if "periodicity" in key: + order = int(key[-1]) + phase = float(node.attrs[f"phase{order}"]) + k = float(node.attrs[f"k{order}"]) + periodicity = int(node.attrs[f"periodicity{order}"]) + if self.max_pred_impr < periodicity: + self.max_pred_impr = periodicity + impr_phase[f"{periodicity}"].append(phase) + impr_k[f"{periodicity}"].append(k) + self.meta[f"impr_order"][f"{periodicity}"].append(order) + self.meta[f"impr_nodeidx"][f"{periodicity}"].append(nnode) + + self.paramtree[self.name]["impr_phase"] = {} + self.paramtree[self.name]["impr_k"] = {} + for npred in range(1, self.max_pred_impr + 1): + self.paramtree[self.name]["impr_phase"][f"{npred}"] = jnp.array( + impr_phase[f"{npred}"]) + self.paramtree[self.name]["impr_k"][f"{npred}"] = jnp.array( + impr_k[f"{npred}"]) + if self.max_pred_impr == 0: + del self.paramtree[self.name]["impr_phase"] + del self.paramtree[self.name]["impr_k"] + + def overwrite(self): + propers = self.fftree.get_nodes("PeriodicTorsionForce/Proper") + impropers = self.fftree.get_nodes("PeriodicTorsionForce/Improper") + prop_data = [{} for _ in propers] + impr_data = [{} for _ in impropers] + # make propers + for periodicity in range(1, self.max_pred_prop + 1): + nterms = len( + self.paramtree[self.name][f"prop_phase"][f"{periodicity}"]) + for nitem in range(nterms): + phase = self.paramtree[ + self.name][f"prop_phase"][f"{periodicity}"][nitem] + k = self.paramtree[ + self.name][f"prop_k"][f"{periodicity}"][nitem] + nodeidx = self.meta[f"prop_nodeidx"][f"{periodicity}"][nitem] + order = self.meta[f"prop_order"][f"{periodicity}"][nitem] + prop_data[nodeidx][f"phase{order}"] = phase + prop_data[nodeidx][f"k{order}"] = k + if "prop_phase" in self.paramtree[self.name]: + self.fftree.set_node("PeriodicTorsionForce/Proper", prop_data) + + # make impropers + for periodicity in range(1, self.max_pred_impr + 1): + nterms = len( + self.paramtree[self.name][f"impr_phase"][f"{periodicity}"]) + for nitem in range(nterms): + phase = self.paramtree[ + self.name][f"impr_phase"][f"{periodicity}"][nitem] + k = self.paramtree[ + self.name][f"impr_k"][f"{periodicity}"][nitem] + nodeidx = self.meta[f"impr_nodeidx"][f"{periodicity}"][nitem] + order = self.meta[f"impr_order"][f"{periodicity}"][nitem] + impr_data[nodeidx][f"phase{order}"] = phase + impr_data[nodeidx][f"k{order}"] = k + if "impr_phase" in self.paramtree[self.name]: + self.fftree.set_node("PeriodicTorsionForce/Improper", impr_data) + + def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args): + proper_matcher = TypeMatcher(self.fftree, + "PeriodicTorsionForce/Proper") + map_prop_atom1 = {i: [] for i in range(1, self.max_pred_prop + 1)} + map_prop_atom2 = {i: [] for i in range(1, self.max_pred_prop + 1)} + map_prop_atom3 = {i: [] for i in range(1, self.max_pred_prop + 1)} + map_prop_atom4 = {i: [] for i in range(1, self.max_pred_prop + 1)} + map_prop_param = {i: [] for i in range(1, self.max_pred_prop + 1)} + n_matched_props = 0 + for torsion in data.propers: + types = [data.atomType[data.atoms[torsion[i]]] for i in range(4)] + ifFound, ifForward, nnode = proper_matcher.matchGeneral(types) + if not ifFound: + continue + # find terms for node + for periodicity in range(1, self.max_pred_prop + 1): + idx = findItemInList( + nnode, self.meta[f"prop_nodeidx"][f"{periodicity}"]) + if idx < 0: + continue + n_matched_props += 1 + map_prop_atom1[periodicity].append(torsion[0]) + map_prop_atom2[periodicity].append(torsion[1]) + map_prop_atom3[periodicity].append(torsion[2]) + map_prop_atom4[periodicity].append(torsion[3]) + map_prop_param[periodicity].append(idx) + + impr_matcher = TypeMatcher(self.fftree, + "PeriodicTorsionForce/Improper") + try: + ordering = self.fftree.get_attribs("PeriodicTorsionForce", + "ordering")[0] + except KeyError as e: + ordering = "default" + map_impr_atom1 = {i: [] for i in range(1, self.max_pred_impr + 1)} + map_impr_atom2 = {i: [] for i in range(1, self.max_pred_impr + 1)} + map_impr_atom3 = {i: [] for i in range(1, self.max_pred_impr + 1)} + map_impr_atom4 = {i: [] for i in range(1, self.max_pred_impr + 1)} + map_impr_param = {i: [] for i in range(1, self.max_pred_impr + 1)} + n_matched_imprs = 0 + for impr in data.impropers: + match = impr_matcher.matchImproper(impr, data, ordering=ordering) + if match is not None: + (a1, a2, a3, a4, nnode) = match + n_matched_imprs += 1 + # find terms for node + for periodicity in range(1, self.max_pred_impr + 1): + idx = findItemInList( + nnode, self.meta[f"impr_nodeidx"][f"{periodicity}"]) + if idx < 0: + continue + if ordering == 'smirnoff': + # Add all torsions in trefoil + map_impr_atom1[periodicity].append(a1) + map_impr_atom2[periodicity].append(a2) + map_impr_atom3[periodicity].append(a3) + map_impr_atom4[periodicity].append(a4) + map_impr_param[periodicity].append(idx) + map_impr_atom1[periodicity].append(a1) + map_impr_atom2[periodicity].append(a3) + map_impr_atom3[periodicity].append(a4) + map_impr_atom4[periodicity].append(a2) + map_impr_param[periodicity].append(idx) + map_impr_atom1[periodicity].append(a1) + map_impr_atom2[periodicity].append(a4) + map_impr_atom3[periodicity].append(a2) + map_impr_atom4[periodicity].append(a3) + map_impr_param[periodicity].append(idx) + else: + map_impr_atom1[periodicity].append(a1) + map_impr_atom2[periodicity].append(a2) + map_impr_atom3[periodicity].append(a3) + map_impr_atom4[periodicity].append(a4) + map_impr_param[periodicity].append(idx) + + props = [ + PeriodicTorsionJaxForce(jnp.array(map_prop_atom1[p], dtype=int), + jnp.array(map_prop_atom2[p], dtype=int), + jnp.array(map_prop_atom3[p], dtype=int), + jnp.array(map_prop_atom4[p], dtype=int), + jnp.array(map_prop_param[p], dtype=int), p) + for p in range(1, self.max_pred_prop + 1) + ] + imprs = [ + PeriodicTorsionJaxForce(jnp.array(map_impr_atom1[p], dtype=int), + jnp.array(map_impr_atom2[p], dtype=int), + jnp.array(map_impr_atom3[p], dtype=int), + jnp.array(map_impr_atom4[p], dtype=int), + jnp.array(map_impr_param[p], dtype=int), p) + for p in range(1, self.max_pred_impr + 1) + ] + + def potential_fn(positions, box, pairs, params): + prop_sum = sum([ + props[i].get_energy( + positions, box, pairs, + params["PeriodicTorsionForce"]["prop_k"][f"{i+1}"], + params["PeriodicTorsionForce"]["prop_phase"][f"{i+1}"]) + for i in range(self.max_pred_prop) + ]) + impr_sum = sum([ + imprs[i].get_energy( + positions, box, pairs, + params["PeriodicTorsionForce"]["impr_k"][f"{i+1}"], + params["PeriodicTorsionForce"]["impr_phase"][f"{i+1}"]) + for i in range(self.max_pred_impr) + ]) + + return prop_sum + impr_sum + + self._jaxPotential = potential_fn + + def getJaxPotential(self): + return self._jaxPotential + + +dmff.api.jaxGenerators["PeriodicTorsionForce"] = PeriodicTorsionJaxGenerator + + +class NonbondedJaxGenerator: + def __init__(self, ff): + self.name = "NonbondedForce" + self.ff = ff + self.fftree = ff.fftree + self.paramtree = ff.paramtree + self.paramtree[self.name] = {} + self.paramtree[self.name]["sigfix"] = jnp.array([]) + self.paramtree[self.name]["epsfix"] = jnp.array([]) + + self.from_force = [] + self.from_residue = [] + self.ra2idx = {} + self.idx2rai = {} + + def extract(self): + self.from_residue = self.fftree.get_attribs( + "NonbondedForce/UseAttributeFromResidue", "name") + self.from_force = [ + i for i in ["charge", "sigma", "epsilon"] + if i not in self.from_residue + ] + # Build per-atom array for from_force + for prm in self.from_force: + vals = self.fftree.get_attribs("NonbondedForce/Atom", prm) + self.paramtree[self.name][prm] = jnp.array(vals) + # Build per-atom array for from_residue + residues = self.fftree.get_nodes("Residues/Residue") + resvals = {k: [] for k in self.from_residue} + for resnode in residues: + resname = resnode.attrs["name"] + resvals[resname] = [] + atomname = resnode.get_attribs("Atom", "name") + shift = len(self.ra2idx) + for natom, aname in enumerate(atomname): + self.ra2idx[(resname, natom)] = shift + natom + self.idx2rai[shift + natom] = (resname, atomname, natom) + for prm in self.from_residue: + atomval = resnode.get_attribs("Atom", prm) + resvals[prm].extend(atomval) + for prm in self.from_residue: + self.paramtree[self.name][prm] = jnp.array(resvals[prm]) + # Build coulomb14scale and lj14scale + coulomb14scale, lj14scale = self.fftree.get_attribs( + "NonbondedForce", ["coulomb14scale", "lj14scale"])[0] + self.paramtree[self.name]["coulomb14scale"] = jnp.array( + [coulomb14scale]) + self.paramtree[self.name]["lj14scale"] = jnp.array([lj14scale]) + + def overwrite(self): + # write coulomb14scale + self.fftree.set_attrib("NonbondedForce", "coulomb14scale", + self.paramtree[self.name]["coulomb14scale"]) + # write lj14scale + self.fftree.set_attrib("NonbondedForce", "lj14scale", + self.paramtree[self.name]["lj14scale"]) + # write prm from force + for prm in self.from_force: + self.fftree.set_attrib("NonbondedForce/Atom", prm, + self.paramtree[self.name][prm]) + # write prm from residue + residues = self.fftree.get_nodes("Residues/Residue") + for prm in self.from_residue: + vals = self.paramtree[self.name][prm] + data = [] + for idx in range(vals.shape[0]): + rname, atomname, aidx = self.idx2rai[idx] + data.append((rname, aidx, vals[idx])) + + for resnode in residues: + tmp = sorted( + [d for d in data if d[0] == resnode.attrs["name"]], + key=lambda x: x[1]) + resnode.set_attrib("Atom", prm, [t[2] for t in tmp]) + + def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, + args): + methodMap = { + app.NoCutoff: "NoCutoff", + app.CutoffPeriodic: "CutoffPeriodic", + app.CutoffNonPeriodic: "CutoffNonPeriodic", + app.PME: "PME", + } + if nonbondedMethod not in methodMap: + raise DMFFException("Illegal nonbonded method for NonbondedForce") + isNoCut = False + if nonbondedMethod is app.NoCutoff: + isNoCut = True + + mscales_coul = jnp.array([0.0, 0.0, 0.0, 1.0, 1.0, + 1.0]) # mscale for PME + mscales_coul = mscales_coul.at[2].set( + self.paramtree[self.name]["coulomb14scale"][0]) + mscales_lj = jnp.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]) # mscale for LJ + mscales_lj = mscales_lj.at[2].set( + self.paramtree[self.name]["lj14scale"][0]) + + # Coulomb: only support PME for now + # set PBC + if nonbondedMethod not in [app.NoCutoff, app.CutoffNonPeriodic]: + ifPBC = True + else: + ifPBC = False + + nbmatcher = TypeMatcher(self.fftree, "NonbondedForce/Atom") + # load LJ from types + maps = {} + for prm in self.from_force: + maps[prm] = [] + for atom in data.atoms: + atype = data.atomType[atom] + ifFound, _, nnode = nbmatcher.matchGeneral([atype]) + if not ifFound: + raise DMFFException( + "AtomType of %s mismatched in NonbondedForce" % + (str(atom))) + maps[prm].append(nnode) + maps[prm] = jnp.array(maps[prm], dtype=int) + + for prm in self.from_residue: + maps[prm] = [] + for atom in data.atoms: + templateName = self.ff.templateNameForResidue[atom.residue.index] + aidx = data.atomTemplateIndexes[atom] + resname, aname = templateName, atom.name + maps[prm].append(self.ra2idx[(resname, aidx)]) + map_nbfix = [] + map_nbfix = np.array(map_nbfix, dtype=int).reshape((-1, 2)) + + self.covalent_map = build_covalent_map(data, 6) + + if unit.is_quantity(nonbondedCutoff): + r_cut = nonbondedCutoff.value_in_unit(unit.nanometer) + else: + r_cut = nonbondedCutoff + if "switchDistance" in args and args["switchDistance"] is not None: + r_switch = args["switchDistance"] + r_switch = (r_switch if not unit.is_quantity(r_switch) else + r_switch.value_in_unit(unit.nanometer)) + ifSwitch = True + else: + r_switch = r_cut + ifSwitch = False + + # PME Settings + if nonbondedMethod is app.PME: + a, b, c = system.getDefaultPeriodicBoxVectors() + box = jnp.array([a._value, b._value, c._value]) + self.ethresh = args.get("ethresh", 1e-6) + self.coeff_method = args.get("PmeCoeffMethod", "openmm") + self.fourier_spacing = args.get("PmeSpacing", 0.1) + kappa, K1, K2, K3 = setup_ewald_parameters(r_cut, self.ethresh, + box, + self.fourier_spacing, + self.coeff_method) + + map_lj = jnp.array(maps["sigma"]) + map_nbfix = jnp.array(map_nbfix) + map_charge = jnp.array(maps["charge"]) + + # Free Energy Settings # + isFreeEnergy = args.get("isFreeEnergy", False) + if isFreeEnergy: + vdwLambda = args.get("vdwLambda", 0.0) + coulLambda = args.get("coulLambda", 0.0) + ifStateA = args.get("ifStateA", True) + + # soft-cores + vdwSoftCore = args.get("vdwSoftCore", False) + coulSoftCore = args.get("coulSoftCore", False) + scAlpha = args.get("scAlpha", 0.0) + scSigma = args.get("scSigma", 0.0) + + # couple + coupleIndex = args.get("coupleIndex", []) + if len(coupleIndex) > 0: + coupleMask = [False for _ in range(len(data.atoms))] + for atomIndex in coupleIndex: + coupleMask[atomIndex] = True + coupleMask = jnp.array(coupleMask, dtype=bool) + else: + coupleMask = None + + if not isFreeEnergy: + ljforce = LennardJonesForce(r_switch, + r_cut, + map_lj, + map_nbfix, + isSwitch=ifSwitch, + isPBC=ifPBC, + isNoCut=isNoCut) + else: + ljforce = LennardJonesFreeEnergyForce(r_switch, + r_cut, + map_lj, + map_nbfix, + isSwitch=ifSwitch, + isPBC=ifPBC, + isNoCut=isNoCut, + feLambda=vdwLambda, + coupleMask=coupleMask, + useSoftCore=vdwSoftCore, + ifStateA=ifStateA, + sc_alpha=scAlpha, + sc_sigma=scSigma) + + ljenergy = ljforce.generate_get_energy() + + # dispersion correction + useDispersionCorrection = args.get("useDispersionCorrection", False) + if useDispersionCorrection: + numTypes = self.paramtree[self.name]["sigma"].shape[0] + countVec = np.zeros(numTypes, dtype=int) + countMat = np.zeros((numTypes, numTypes), dtype=int) + types, count = np.unique(map_lj, return_counts=True) + + for typ, cnt in zip(types, count): + countVec[typ] += cnt + for i in range(numTypes): + for j in range(i, numTypes): + if i != j: + countMat[i, j] = countVec[i] * countVec[j] + else: + countMat[i, i] = countVec[i] * (countVec[i] - 1) // 2 + assert np.sum(countMat) == len(map_lj) * (len(map_lj) - 1) // 2 + + colv_pairs = np.argwhere( + np.logical_and(self.covalent_map > 0, self.covalent_map <= 3)) + for pair in colv_pairs: + if pair[0] <= pair[1]: + tmp = (map_lj[pair[0]], map_lj[pair[1]]) + t1, t2 = min(tmp), max(tmp) + countMat[t1, t2] -= 1 + + if not isFreeEnergy: + ljDispCorrForce = LennardJonesLongRangeForce( + r_cut, map_lj, map_nbfix, countMat) + else: + ljDispCorrForce = LennardJonesLongRangeFreeEnergyForce( + r_cut, map_lj, map_nbfix, countMat, vdwLambda, ifStateA, + coupleMask) + ljDispEnergyFn = ljDispCorrForce.generate_get_energy() + + if not isFreeEnergy: + if nonbondedMethod is not app.PME: + # do not use PME + if nonbondedMethod in [ + app.CutoffPeriodic, app.CutoffNonPeriodic + ]: + # use Reaction Field + coulforce = CoulReactionFieldForce(r_cut, + map_charge, + isPBC=ifPBC) + if nonbondedMethod is app.NoCutoff: + # use NoCutoff + coulforce = CoulNoCutoffForce(map_charge) + else: + coulforce = CoulombPMEForce(r_cut, map_charge, kappa, + (K1, K2, K3)) + else: + assert nonbondedMethod is app.PME, "Only PME is supported in free energy calculations" + coulforce = CoulombPMEFreeEnergyForce(r_cut, + map_charge, + kappa, (K1, K2, K3), + coulLambda, + ifStateA=ifStateA, + coupleMask=coupleMask, + useSoftCore=coulSoftCore, + sc_alpha=scAlpha, + sc_sigma=scSigma) + + coulenergy = coulforce.generate_get_energy() + + if not isFreeEnergy: + + def potential_fn(positions, box, pairs, params): + + # check whether args passed into potential_fn are jnp.array and differentiable + # note this check will be optimized away by jit + # it is jit-compatiable + isinstance_jnp(positions, box, params) + + ljE = ljenergy(positions, box, pairs, + params[self.name]["epsilon"], + params[self.name]["sigma"], + params[self.name]["epsfix"], + params[self.name]["sigfix"], mscales_lj) + coulE = coulenergy(positions, box, pairs, + params[self.name]["charge"], mscales_coul) + + if useDispersionCorrection: + ljDispEnergy = ljDispEnergyFn(box, + params[self.name]['epsilon'], + params[self.name]['sigma'], + params[self.name]['epsfix'], + params[self.name]['sigfix']) + + return ljE + coulE + ljDispEnergy + else: + return ljE + coulE + + self._jaxPotential = potential_fn + else: + # Free Energy + @jit_condition() + def potential_fn(positions, box, pairs, params, vdwLambda, + coulLambda): + ljE = ljenergy(positions, box, pairs, + params[self.name]["epsilon"], + params[self.name]["sigma"], + params[self.name]["epsfix"], + params[self.name]["sigfix"], mscales_lj, + vdwLambda) + coulE = coulenergy(positions, box, pairs, + params[self.name]["charge"], mscales_coul, + coulLambda) + + if useDispersionCorrection: + ljDispEnergy = ljDispEnergyFn(box, + params[self.name]['epsilon'], + params[self.name]['sigma'], + params[self.name]['epsfix'], + params[self.name]['sigfix'], + vdwLambda) + return ljE + coulE + ljDispEnergy + else: + return ljE + coulE + + self._jaxPotential = potential_fn + + def getJaxPotential(self): + return self._jaxPotential + + +dmff.api.jaxGenerators["NonbondedForce"] = NonbondedJaxGenerator + + +class LennardJonesGenerator: + def __init__(self, ff): + self.name = "LennardJonesForce" + self.ff = ff + self.fftree = ff.fftree + self.paramtree = ff.paramtree + self.paramtree[self.name] = {} + self.paramtree[self.name] + self.paramtree[self.name] + + def extract(self): + for prm in ["sigma", "epsilon"]: + vals = self.fftree.get_attribs("LennardJonesForce/Atom", prm) + self.paramtree[self.name][prm] = jnp.array(vals) + valfix = self.fftree.get_attribs("LennardJonesForce/NBFixPair", + prm) + self.paramtree[self.name][f"{prm}_nbfix"] = jnp.array(valfix) + + lj14scale = self.fftree.get_attribs("LennardJonesForce", + "lj14scale")[0] + self.paramtree[self.name]["lj14scale"] = jnp.array([lj14scale]) + + def overwrite(self): + self.fftree.set_attrib("LennardJonesForce", "lj14scale", + self.paramtree[self.name]["lj14scale"]) + for prm in ["sigma", "epsilon"]: + self.fftree.set_attrib("LennardJonesForce/Atom", prm, + self.paramtree[self.name][prm]) + if len(self.paramtree[self.name][f"{prm}_nbfix"]) > 0: + self.fftree.set_attrib( + "LennardJonesForce/NBFixPair", prm, + self.paramtree[self.name][f"{prm}_nbfix"]) + + def createForce(self, system, data, nonbondedMethod, nonbondedCutoff, + args): + methodMap = { + app.NoCutoff: "NoCutoff", + app.PME: "CutoffPeriodic", + app.CutoffPeriodic: "CutoffPeriodic", + app.CutoffNonPeriodic: "CutoffNonPeriodic" + } + if nonbondedMethod not in methodMap: + raise DMFFException("Illegal nonbonded method for NonbondedForce") + isNoCut = False + if nonbondedMethod is app.NoCutoff: + isNoCut = True + + mscales_lj = jnp.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]) # mscale for LJ + mscales_lj = mscales_lj.at[2].set( + self.paramtree[self.name]["lj14scale"][0]) + + if nonbondedMethod not in [app.NoCutoff, app.CutoffNonPeriodic]: + ifPBC = True + else: + ifPBC = False + + nbmatcher = TypeMatcher(self.fftree, "LennardJonesForce/Atom") + + maps = {} + for prm in ["sigma", "epsilon"]: + maps[prm] = [] + for atom in data.atoms: + atype = data.atomType[atom] + ifFound, _, nnode = nbmatcher.matchGeneral([atype]) + if not ifFound: + raise DMFFException( + "AtomType of %s mismatched in NonbondedForce" % + (str(atom))) + maps[prm].append(nnode) + maps[prm] = jnp.array(maps[prm], dtype=int) + + map_lj = jnp.array(maps["sigma"]) + + ifType = len(self.fftree.get_attribs("LennardJonesForce/Atom", + "type")) != 0 + if ifType: + atom_labels = self.fftree.get_attribs("LennardJonesForce/Atom", + "type") + fix_label1 = self.fftree.get_attribs("LennardJonesForce/NBFixPair", + "type1") + fix_label2 = self.fftree.get_attribs("LennardJonesForce/NBFixPair", + "type2") + else: + atom_labels = self.fftree.get_attribs("LennardJonesForce/Atom", + "class") + fix_label1 = self.fftree.get_attribs("LennardJonesForce/NBFixPair", + "class1") + fix_label2 = self.fftree.get_attribs("LennardJonesForce/NBFixPair", + "class2") + + map_nbfix = [] + + def findIdx(labels, label): + for ni in range(len(labels)): + if labels[ni] == label: + return ni + raise DMFFException( + "AtomType of %s mismatched in LennardJonesForce" % (label)) + + for nfix in range(len(fix_label1)): + l1, l2 = fix_label1[nfix], fix_label2[nfix] + i1 = findIdx(atom_labels, l1) + i2 = findIdx(atom_labels, l2) + map_nbfix.append([i1, i2]) + map_nbfix = np.array(map_nbfix, dtype=int).reshape((-1, 2)) + map_nbfix = jnp.array(map_nbfix) + + colv_map = build_covalent_map(data, 6) + + if unit.is_quantity(nonbondedCutoff): + r_cut = nonbondedCutoff.value_in_unit(unit.nanometer) + else: + r_cut = nonbondedCutoff + if "switchDistance" in args and args["switchDistance"] is not None: + r_switch = args["switchDistance"] + r_switch = (r_switch if not unit.is_quantity(r_switch) else + r_switch.value_in_unit(unit.nanometer)) + ifSwitch = True + else: + r_switch = r_cut + ifSwitch = False + + ljforce = LennardJonesForce(r_switch, + r_cut, + map_lj, + map_nbfix, + isSwitch=ifSwitch, + isPBC=ifPBC, + isNoCut=isNoCut) + ljenergy = ljforce.generate_get_energy() + + useDispersionCorrection = self.fftree.get_attribs( + "LennardJonesForce", "useDispersionCorrection")[0] == "True" + if useDispersionCorrection: + numTypes = self.paramtree[self.name]["sigma"].shape[0] + countVec = np.zeros(numTypes, dtype=int) + countMat = np.zeros((numTypes, numTypes), dtype=int) + types, count = np.unique(map_lj, return_counts=True) + + for typ, cnt in zip(types, count): + countVec[typ] += cnt + for i in range(numTypes): + for j in range(i, numTypes): + if i != j: + countMat[i, j] = countVec[i] * countVec[j] + else: + countMat[i, i] = countVec[i] * (countVec[i] - 1) // 2 + assert np.sum(countMat) == len(map_lj) * (len(map_lj) - 1) // 2 + + colv_pairs = np.argwhere( + np.logical_and(colv_map > 0, colv_map <= 3)) + for pair in colv_pairs: + if pair[0] <= pair[1]: + tmp = (map_lj[pair[0]], map_lj[pair[1]]) + t1, t2 = min(tmp), max(tmp) + countMat[t1, t2] -= 1 + + ljDispCorrForce = LennardJonesLongRangeForce( + r_cut, map_lj, map_nbfix, countMat) + + ljDispEnergyFn = ljDispCorrForce.generate_get_energy() + + def potential_fn(positions, box, pairs, params): + + # check whether args passed into potential_fn are jnp.array and differentiable + # note this check will be optimized away by jit + # it is jit-compatiable + isinstance_jnp(positions, box, params) + ljE = ljenergy(positions, box, pairs, params[self.name]["epsilon"], + params[self.name]["sigma"], + params[self.name]["epsilon_nbfix"], + params[self.name]["sigma_nbfix"], mscales_lj) + + if useDispersionCorrection: + ljDispEnergy = ljDispEnergyFn( + box, params[self.name]['epsilon'], + params[self.name]['sigma'], + params[self.name]['epsilon_nbfix'], + params[self.name]['sigma_nbfix']) + + return ljE + ljDispEnergy + else: + return ljE + + self._jaxPotential = potential_fn + + def getJaxPotential(self): + return self._jaxPotential + + +dmff.api.jaxGenerators["LennardJonesForce"] = LennardJonesGenerator \ No newline at end of file diff --git a/dmff/mbar.py b/dmff/mbar.py new file mode 100644 index 000000000..a92486ab6 --- /dev/null +++ b/dmff/mbar.py @@ -0,0 +1,250 @@ +import numpy as np +import mdtraj as md +from pymbar import MBAR +import dmff +dmff.update_jax_precision(dmff.PRECISION) +import jax +import jax.numpy as jnp +from jax import grad +from tqdm import tqdm, trange +import openmm as mm +import openmm.app as app +import openmm.unit as unit + + +class TargetState: + def __init__(self, temperature, energy_function): + self._temperature = temperature + self._efunc = energy_function + + def calc_energy(self, trajectory, parameters): + beta = 1. / self._temperature / 8.314 * 1000. + eners = self._efunc(trajectory, parameters) + ulist = jnp.concatenate([beta * e.reshape((1, )) for e in eners]) + return ulist + + +class SampleState: + def __init__(self, temperature, name): + self._temperature = temperature + self.name = name + + def calc_energy_frame(self, frame): + return 0.0 + + def calc_energy(self, trajectory): + # return beta * u + beta = 1. / self._temperature / 8.314 * 1000. + eners = [] + for frame in tqdm(trajectory): + e = self.calc_energy_frame(frame) + eners.append(e * beta) + return jnp.array(eners) + + +class OpenMMSampleState(SampleState): + def __init__(self, + name, + parameter, + topology, + temperature=300.0, + pressure=0.0): + super(OpenMMSampleState, self).__init__(temperature, name) + self._pressure = pressure + # create a context + pdb = app.PDBFile(topology) + ff = app.ForceField(parameter) + system = ff.createSystem(pdb.topology, + nonbondedMethod=app.PME, + nonbondedCutoff=0.9 * unit.nanometer, + constraints=None, + rigidWater=False) + + for force in system.getForces(): + if isinstance(force, mm.NonbondedForce): + force.setUseDispersionCorrection(False) + force.setUseSwitchingFunction(False) + + integ = mm.LangevinIntegrator(0 * unit.kelvin, 5 / unit.picosecond, + 1.0 * unit.femtosecond) + self.ctx = mm.Context(system, integ) + + def calc_energy_frame(self, frame): + self.ctx.setPositions(frame.openmm_positions(0)) + self.ctx.setPeriodicBoxVectors(*frame.openmm_boxes(0)) + state = self.ctx.getState(getEnergy=True) + vol = frame.unitcell_volumes[0] # in nm^3 + ener = state.getPotentialEnergy().value_in_unit( + unit.kilojoule_per_mole) + 0.06023 * vol * self._pressure + return ener + + +class Sample: + def __init__(self, trajectory, from_state): + self.trajectory = trajectory + self.from_state = from_state + self.energy_data = {} + + def generate_energy(self, state_list): + for state in state_list: + if state.name not in self.energy_data: + self.energy_data[state.name] = np.array( + [state.calc_energy(self.trajectory)]) + + +class MBAREstimator: + def __init__(self): + self.samples = [] + self.states = [] + self._mbar = None + self._umat = None + self._nk = None + self._full_samples = None + + def add_sample(self, sample): + self.samples.append(sample) + + def add_state(self, state): + self.states.append(state) + + def remove_sample(self, name): + init_num = len(self.samples) + self.samples = [s for s in self.samples if s.from_state != name] + final_num = len(self.samples) + assert init_num > final_num + + def remove_state(self, name): + init_num = len(self.states) + self.states = [s for s in self.states if s.name != name] + final_num = len(self.states) + assert init_num > final_num + self.remove_sample(name) + + def compute_energy_matrix(self): + for sample in self.samples: + sample.generate_energy(self.states) + + def _build_umat(self): + nk_states = {state.name: 0 for state in self.states} + for sample in self.samples: + nk_states[sample.from_state] += sample.trajectory.n_frames + nk_names = [k.name for k in self.states] + nk = np.array([nk_states[k] for k in nk_states.keys()]) + umat = np.zeros((nk.shape[0], nk.sum())) + istart = 0 + traj_merge = [] + for nk_name in nk_names: + for sample in [s for s in self.samples if nk_name == s.from_state]: + traj_merge.append(sample.trajectory) + sample_frames = sample.trajectory.n_frames + iend = istart + sample_frames + for nnk, nk_name2 in enumerate(nk_names): + umat[nnk, istart:iend] = sample.energy_data[nk_name2] + istart = iend + return umat, nk, md.join(traj_merge) + + def optimize_mbar(self, initialize="BAR"): + self.compute_energy_matrix() + umat, nk, samples = self._build_umat() + self._umat = umat + self._nk = nk + self._full_samples = samples + + self._mbar = MBAR(self._umat, self._nk, initialize=initialize) + self._umat_jax = jax.numpy.array(self._umat) + self._free_energy_jax = jax.numpy.array(self._mbar.f_k) + self._nk_jax = jax.numpy.array(nk) + + def estimate_weight(self, + state, + parameters=None, + decompose=True, + return_energy=True): + if isinstance(state, TargetState): + unew = state.calc_energy(self._full_samples, parameters) + else: + unew = state.calc_energy(self._full_samples) + unew_max = unew.max() + du_1 = self._free_energy_jax.reshape((-1, 1)) - self._umat_jax + delta_u = du_1 + unew.reshape((1, -1)) - unew_max - du_1.min() + cm = 1. / (jax.numpy.exp(delta_u) * jax.numpy.array(self._nk).reshape( + (-1, 1))).sum(axis=0) + weight = cm / cm.sum() + if return_energy: + return weight, unew + return weight + + def _estimate_weight_numpy(self, unew_npy, return_cn=False): + unew_mean = unew_npy.mean() + du_1 = self._mbar.f_k.reshape((-1, 1)) - self._umat + delta_u = du_1 + unew_npy.reshape((1, -1)) - unew_mean - du_1.mean() + cn = 1. / (np.exp(delta_u) * self._nk.reshape((-1, 1))).sum(axis=0) + weight = cn / cn.sum() + if return_cn: + return weight, cn + else: + return weight + + def _computeCovar(self, W, N_k): + K, N = W.shape + Ndiag = np.diag(N_k) + I = np.identity(K, dtype=np.float64) + + S2, V = np.linalg.eigh(W @ W.T) + S2[np.where(S2 < 0.0)] = 0.0 + Sigma = np.diag(np.sqrt(S2)) + + # Compute covariance + Theta = (V @ Sigma @ np.linalg.pinv( + I - Sigma @ V.T @ Ndiag @ V @ Sigma, rcond=1e-10) @ Sigma @ V.T) + return Theta + + def estimate_effective_sample(self, unew, decompose=False): + wnew, cn = self._estimate_weight_numpy(unew, return_cn=True) + eff_samples = 1. / (wnew**2).sum() + if decompose: + state_effect = {} + argsort = np.argsort(wnew)[::-1][:int(eff_samples)] + for nstate in range(len(self.states)): + istart = self._nk[:nstate].sum() + iend = istart + self._nk[nstate] + state_effect[self.states[nstate].name] = ( + (argsort > istart) & (argsort < iend)).sum() + state_effect["Total"] = eff_samples + return state_effect + return eff_samples + + def _estimate_free_energy(self, unew): + a = self._free_energy_jax - self._umat_jax.T + # log(sum(n_k*exp(a))) + a_max = a.max(axis=1, keepdims=True) + log_denominator_n = jnp.log((self._nk_jax.reshape( + (1, -1)) * jnp.exp(a - a_max)).sum(axis=1)) + a_max.reshape((-1, )) + a2 = -unew - log_denominator_n + # log(sum(exp(a2))) + a2_max = a2.max() + f_new = -jnp.log(jnp.sum(jnp.exp(a2 - a2_max))) - a2_max + return f_new + + def estimate_free_energy_difference(self, + target_state, + ref_state, + target_parameters=None, + ref_parameters=None, + decompose=True, + return_energy=False): + # compute F_target - F_ref + if isinstance(ref_state, TargetState): + u_ref = ref_state.calc_energy(self._full_samples, ref_parameters) + else: + u_ref = ref_state.calc_energy(self._full_samples) + if isinstance(target_state, TargetState): + u_target = target_state.calc_energy(self._full_samples, + target_parameters) + else: + u_target = target_state.calc_energy(self._full_samples) + f_ref = self._estimate_free_energy(u_ref) + f_target = self._estimate_free_energy(u_target) + if return_energy: + return f_target - f_ref, u_target, u_ref + return f_target - f_ref \ No newline at end of file diff --git a/dmff/optimize.py b/dmff/optimize.py new file mode 100644 index 000000000..3bf29aa83 --- /dev/null +++ b/dmff/optimize.py @@ -0,0 +1,123 @@ +from jax import grad +from typing import Optional +import optax + +PeriodicParamsState = optax._src.base.EmptyState + + +def periodic_move(pmin, pmax): + def init_fn(params): + del params + return PeriodicParamsState() + + def update_fn(updates, state, params): + if params is None: + raise ValueError(optax.base.NO_PARAMS_MSG) + + updates = jax.tree_map( + lambda p, u: jnp.where((p + u) < pmin, u + pmax - pmin, u), params, + updates) + updates = jax.tree_map( + lambda p, u: jnp.where((p + u) > pmax, u - pmax + pmin, u), params, + updates) + return updates, state + + return optax._src.base.GradientTransformation(init_fn, update_fn) + + +def genOptimizer(optimizer="adam", + learning_rate=1.0, + nonzero=True, + clip=10.0, + periodic=None, + transition_steps=1000, + decay_rate=0.99, + options: dict={}): + options["learning_rate"] = learning_rate + # Exponential decay of the learning rate. + scheduler = optax.exponential_decay(init_value=learning_rate, + transition_steps=transition_steps, + decay_rate=decay_rate) + + # Combining gradient transforms using `optax.chain`. + if optimizer == "sgd": + chain = [optax.sgd(**options), optax.clip(clip)] + elif optimizer == "adam": + chain = [optax.adam(**options), optax.clip(clip)] + elif optimizer == "adagrad": + chain = [optax.adagrad(**options), optax.clip(clip)] + elif optimizer == "adamw": + chain = [optax.adamw(**options), optax.clip(clip)] + elif optimizer == "rmsprop": + chain = [optax.rmsprop(**options), optax.clip(clip)] + else: + print(f"Unknown optimizer {optimizer}.") + + if periodic is not None: + chain.append(periodic_move(periodic[0], periodic[1])) + elif nonzero: + chain.append(optax.keep_params_nonnegative()) + gradient_transform = optax.chain(*chain) + return gradient_transform + + +def label_iter(parent, ltree, label): + for key in parent: + if label: + newl = f"{label}/{key}" + else: + newl = key + if isinstance(parent[key], dict): + label_iter(parent[key], ltree, newl) + else: + child = ltree + for k2 in label.split("/"): + if k2 not in child: + child[k2] = {} + child = child[k2] + child[key] = newl + + +def mark_iter(parent, mtree): + for key in parent: + if isinstance(parent[key], dict): + mtree[key] = {} + mark_iter(parent[key], mtree[key]) + else: + mtree[key] = False + + +def label2trans_iter(parent, mtree, ttree): + for key in parent: + if isinstance(parent[key], dict): + label2trans_iter(parent[key], mtree[key], ttree) + else: + label = parent[key] + if label in ttree: + mtree[key] = True + # print(label, True) + else: + mtree[key] = False + # print(label, False) + ttree[label] = optax.set_to_zero() + + +class MultiTransform: + def __init__(self, param_tree): + self.transforms = {} + self.labels = {} + self.mask = {} + label_iter(param_tree, self.labels, "") + mark_iter(self.labels, self.mask) + + def __getitem__(self, key): + return self.transforms[key] + + def __setitem__(self, key, val): + self.transforms[key] = val + + def __delitem__(self, key): + del self.transforms[key] + + def finalize(self): + label2trans_iter(self.labels, self.mask, self.transforms) \ No newline at end of file diff --git a/dmff/settings.py b/dmff/settings.py index aa89677f5..a02175d26 100644 --- a/dmff/settings.py +++ b/dmff/settings.py @@ -6,7 +6,14 @@ DEBUG = False -if PRECISION == 'double': - config.update("jax_enable_x64", True) - -__all__ = ['PRECISION', 'DO_JIT', 'DEBUG'] + +def update_jax_precision(precision): + if precision == 'double': + config.update("jax_enable_x64", True) + else: + config.update("jax_enable_x64", False) + + +update_jax_precision(PRECISION) + +__all__ = ['PRECISION', 'DO_JIT', 'DEBUG', "update_jax_precision"] \ No newline at end of file diff --git a/docs/user_guide/installation.md b/docs/user_guide/installation.md index 79f6ea000..d4b3c4687 100644 --- a/docs/user_guide/installation.md +++ b/docs/user_guide/installation.md @@ -1,16 +1,21 @@ # 2. Installation ## 2.1 Install Dependencies ++ Create conda environment: +``` +conda create -n dmff python=3.9 --yes +``` + Install [jax](https://github.com/google/jax) (select the correct cuda version, see more details in the Jax installation guide): ```bash -pip install jax[cuda11_cudnn82] -f https://storage.googleapis.com/jax-releases/jax_releases.html +pip install "jaxlib[cuda11_cudnn805]==0.3.15" -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html +pip install jax==0.3.17 ``` + Install [jax-md](https://github.com/google/jax-md): ```bash -pip install jax-md +pip install jax-md==0.2.0 ``` + Install [OpenMM](https://openmm.org/): ```bash -conda install -c conda-forge openmm +conda install -c conda-forge openmm==7.7.0 ``` ## 2.2 Install DMFF from Source Code One can download the DMFF source code from github: @@ -19,7 +24,7 @@ git clone https://github.com/deepmodeling/DMFF.git ``` Then you may install DMFF using `pip`: ```bash -cd dmff +cd DMFF pip install . --user ``` diff --git a/examples/mbar/ben-prm.xml b/examples/mbar/ben-prm.xml new file mode 100644 index 000000000..bac99356d --- /dev/null +++ b/examples/mbar/ben-prm.xml @@ -0,0 +1,29 @@ + + + 2022-09-18 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/mbar/ben-top.xml b/examples/mbar/ben-top.xml new file mode 100644 index 000000000..9ef62d2c0 --- /dev/null +++ b/examples/mbar/ben-top.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/examples/mbar/ben.pdb b/examples/mbar/ben.pdb new file mode 100644 index 000000000..5af843280 --- /dev/null +++ b/examples/mbar/ben.pdb @@ -0,0 +1,4 @@ +REMARK 1 CREATED WITH OPENMM 7.7, 2022-09-18 +HETATM 1 C1 BEN A 1 3.131 2.883 -0.435 1.00 0.00 C +HETATM 3 C3 BEN A 1 3.131 1.046 1.132 1.00 0.00 C +HETATM 5 C5 BEN A 1 4.939 2.647 1.147 1.00 0.00 C \ No newline at end of file diff --git a/examples/mbar/benz.txt b/examples/mbar/benz.txt new file mode 100644 index 000000000..2eeb0748b --- /dev/null +++ b/examples/mbar/benz.txt @@ -0,0 +1,106 @@ +3.50000e+0 1.36691e-2 +3.60000e+0 2.33244e-2 +3.70000e+0 3.88271e-2 +3.80000e+0 5.73159e-2 +3.90000e+0 8.12927e-2 +4.00000e+0 1.10869e-1 +4.10000e+0 1.47153e-1 +4.20000e+0 1.97505e-1 +4.30000e+0 2.51627e-1 +4.40000e+0 3.33850e-1 +4.50000e+0 4.46947e-1 +4.60000e+0 6.06233e-1 +4.70000e+0 8.30737e-1 +4.80000e+0 1.02379e+0 +4.90000e+0 1.28467e+0 +5.00000e+0 1.48432e+0 +5.10000e+0 1.60134e+0 +5.20000e+0 1.66708e+0 +5.30000e+0 1.70344e+0 +5.40000e+0 1.73484e+0 +5.50000e+0 1.77387e+0 +5.60000e+0 1.81304e+0 +5.70000e+0 1.85190e+0 +5.80000e+0 1.87969e+0 +5.90000e+0 1.88458e+0 +6.00000e+0 1.86235e+0 +6.10000e+0 1.80173e+0 +6.20000e+0 1.71302e+0 +6.30000e+0 1.57397e+0 +6.40000e+0 1.44336e+0 +6.50000e+0 1.31439e+0 +6.60000e+0 1.19057e+0 +6.70000e+0 1.06929e+0 +6.80000e+0 9.66103e-1 +6.90000e+0 8.82179e-1 +7.00000e+0 8.16834e-1 +7.10000e+0 7.64724e-1 +7.20000e+0 7.26786e-1 +7.30000e+0 6.98183e-1 +7.40000e+0 6.78272e-1 +7.50000e+0 6.68151e-1 +7.60000e+0 6.58833e-1 +7.70000e+0 6.54363e-1 +7.80000e+0 6.55446e-1 +7.90000e+0 6.60430e-1 +8.00000e+0 6.67523e-1 +8.10000e+0 6.81308e-1 +8.20000e+0 6.96787e-1 +8.30000e+0 7.15460e-1 +8.40000e+0 7.40184e-1 +8.50000e+0 7.81555e-1 +8.60000e+0 8.11797e-1 +8.70000e+0 8.41624e-1 +8.80000e+0 8.73386e-1 +8.90000e+0 9.11023e-1 +9.00000e+0 9.48117e-1 +9.10000e+0 9.79008e-1 +9.20000e+0 1.01636e+0 +9.30000e+0 1.05518e+0 +9.40000e+0 1.08475e+0 +9.50000e+0 1.11409e+0 +9.60000e+0 1.14053e+0 +9.70000e+0 1.16202e+0 +9.80000e+0 1.18017e+0 +9.90000e+0 1.19236e+0 +1.00000e+1 1.19962e+0 +1.01000e+1 1.20169e+0 +1.02000e+1 1.19914e+0 +1.03000e+1 1.19294e+0 +1.04000e+1 1.18467e+0 +1.05000e+1 1.17460e+0 +1.06000e+1 1.16124e+0 +1.07000e+1 1.15031e+0 +1.08000e+1 1.13482e+0 +1.09000e+1 1.11122e+0 +1.10000e+1 1.09903e+0 +1.11000e+1 1.08409e+0 +1.12000e+1 1.06913e+0 +1.13000e+1 1.05144e+0 +1.14000e+1 1.03694e+0 +1.15000e+1 1.02233e+0 +1.16000e+1 1.00696e+0 +1.17000e+1 9.92727e-1 +1.18000e+1 9.79214e-1 +1.19000e+1 9.68538e-1 +1.20000e+1 9.60193e-1 +1.21000e+1 9.49812e-1 +1.22000e+1 9.41204e-1 +1.23000e+1 9.33168e-1 +1.24000e+1 9.29037e-1 +1.25000e+1 9.24243e-1 +1.26000e+1 9.23099e-1 +1.27000e+1 9.23096e-1 +1.28000e+1 9.24229e-1 +1.29000e+1 9.26455e-1 +1.30000e+1 9.31653e-1 +1.31000e+1 9.35617e-1 +1.32000e+1 9.41629e-1 +1.33000e+1 9.53214e-1 +1.34000e+1 9.67656e-1 +1.35000e+1 9.75437e-1 +1.36000e+1 9.86745e-1 +1.37000e+1 9.98225e-1 +1.38000e+1 1.00970e+0 +1.39000e+1 1.01895e+0 +1.40000e+1 1.02877e+0 \ No newline at end of file diff --git a/examples/mbar/box_relaxed.pdb b/examples/mbar/box_relaxed.pdb new file mode 100644 index 000000000..5122b01fc --- /dev/null +++ b/examples/mbar/box_relaxed.pdb @@ -0,0 +1,607 @@ +HEADER +TITLE Built with Packmol +REMARK Packmol generated pdb file +REMARK Home-Page: http://m3g.iqm.unicamp.br/packmol +REMARK +CRYST1 30.05 30.05 30.05 90.00 90.00 90.00 P 1 1 +HETATM 1 C1 BEN A 1 24.148 20.445 23.381 1.00 0.00 C +HETATM 2 C3 BEN A 1 25.088 21.452 25.364 1.00 0.00 C +HETATM 3 C5 BEN A 1 22.693 21.264 25.125 1.00 0.00 C +HETATM 4 C1 BEN A 2 4.869 26.635 27.351 1.00 0.00 C +HETATM 5 C3 BEN A 2 2.513 26.374 26.890 1.00 0.00 C +HETATM 6 C5 BEN A 2 3.255 28.017 28.498 1.00 0.00 C +HETATM 7 C1 BEN A 3 18.723 9.938 9.174 1.00 0.00 C +HETATM 8 C3 BEN A 3 19.013 8.044 7.705 1.00 0.00 C +HETATM 9 C5 BEN A 3 16.797 8.883 8.172 1.00 0.00 C +HETATM 10 C1 BEN A 4 17.984 7.600 2.626 1.00 0.00 C +HETATM 11 C3 BEN A 4 16.822 5.636 3.414 1.00 0.00 C +HETATM 12 C5 BEN A 4 16.533 6.377 1.134 1.00 0.00 C +HETATM 13 C1 BEN A 5 17.570 5.323 30.003 1.00 0.00 C +HETATM 14 C3 BEN A 5 19.932 4.820 30.001 1.00 0.00 C +HETATM 15 C5 BEN A 5 18.339 3.146 29.299 1.00 0.00 C +HETATM 16 C1 BEN A 6 11.251 3.589 11.059 1.00 0.00 C +HETATM 17 C3 BEN A 6 9.721 5.428 11.388 1.00 0.00 C +HETATM 18 C5 BEN A 6 12.073 5.715 11.855 1.00 0.00 C +HETATM 19 C1 BEN A 7 9.659 22.692 21.411 1.00 0.00 C +HETATM 20 C3 BEN A 7 8.485 20.584 21.496 1.00 0.00 C +HETATM 21 C5 BEN A 7 10.889 20.620 21.265 1.00 0.00 C +HETATM 22 C1 BEN A 8 27.278 13.927 19.581 1.00 0.00 C +HETATM 23 C3 BEN A 8 29.530 13.477 20.328 1.00 0.00 C +HETATM 24 C5 BEN A 8 28.672 15.734 20.367 1.00 0.00 C +HETATM 25 C1 BEN A 9 22.837 10.589 27.153 1.00 0.00 C +HETATM 26 C3 BEN A 9 24.122 11.194 29.105 1.00 0.00 C +HETATM 27 C5 BEN A 9 24.464 9.049 28.051 1.00 0.00 C +HETATM 28 C1 BEN A 10 8.528 29.627 17.543 1.00 0.00 C +HETATM 29 C3 BEN A 10 8.172 28.020 15.777 1.00 0.00 C +HETATM 30 C5 BEN A 10 7.039 27.759 17.894 1.00 0.00 C +HETATM 31 C1 BEN A 11 19.875 6.973 13.839 1.00 0.00 C +HETATM 32 C3 BEN A 11 19.751 8.784 12.247 1.00 0.00 C +HETATM 33 C5 BEN A 11 21.658 7.303 12.246 1.00 0.00 C +HETATM 34 C1 BEN A 12 7.958 29.226 20.865 1.00 0.00 C +HETATM 35 C3 BEN A 12 9.940 29.179 22.243 1.00 0.00 C +HETATM 36 C5 BEN A 12 8.911 27.112 21.534 1.00 0.00 C +HETATM 37 C1 BEN A 13 16.240 27.827 18.591 1.00 0.00 C +HETATM 38 C3 BEN A 13 18.157 28.743 19.739 1.00 0.00 C +HETATM 39 C5 BEN A 13 17.612 26.402 19.975 1.00 0.00 C +HETATM 40 C1 BEN A 14 2.948 7.869 20.826 1.00 0.00 C +HETATM 41 C3 BEN A 14 3.522 9.947 21.913 1.00 0.00 C +HETATM 42 C5 BEN A 14 1.204 9.354 21.586 1.00 0.00 C +HETATM 43 C1 BEN A 15 25.704 24.134 22.005 1.00 0.00 C +HETATM 44 C3 BEN A 15 25.067 26.029 23.360 1.00 0.00 C +HETATM 45 C5 BEN A 15 23.874 23.939 23.567 1.00 0.00 C +HETATM 46 C1 BEN A 16 0.812 18.252 10.232 1.00 0.00 C +HETATM 47 C3 BEN A 16 0.582 16.870 8.265 1.00 0.00 C +HETATM 48 C5 BEN A 16 1.414 15.916 10.322 1.00 0.00 C +HETATM 49 C1 BEN A 17 26.389 13.045 4.179 1.00 0.00 C +HETATM 50 C3 BEN A 17 24.814 11.859 5.573 1.00 0.00 C +HETATM 51 C5 BEN A 17 26.779 10.730 4.740 1.00 0.00 C +HETATM 52 C1 BEN A 18 7.136 6.278 4.342 1.00 0.00 C +HETATM 53 C3 BEN A 18 9.399 5.581 4.812 1.00 0.00 C +HETATM 54 C5 BEN A 18 7.862 6.001 6.627 1.00 0.00 C +HETATM 55 C1 BEN A 19 28.274 28.266 22.125 1.00 0.00 C +HETATM 56 C3 BEN A 19 29.999 28.703 23.757 1.00 0.00 C +HETATM 57 C5 BEN A 19 29.919 29.983 21.711 1.00 0.00 C +HETATM 58 C1 BEN A 20 17.598 6.239 16.695 1.00 0.00 C +HETATM 59 C3 BEN A 20 15.672 6.646 15.298 1.00 0.00 C +HETATM 60 C5 BEN A 20 17.353 8.359 15.566 1.00 0.00 C +HETATM 61 C1 BEN A 21 21.879 26.236 25.198 1.00 0.00 C +HETATM 62 C3 BEN A 21 22.109 24.469 26.827 1.00 0.00 C +HETATM 63 C5 BEN A 21 23.563 26.394 26.920 1.00 0.00 C +HETATM 64 C1 BEN A 22 11.509 21.789 18.241 1.00 0.00 C +HETATM 65 C3 BEN A 22 9.148 21.287 18.169 1.00 0.00 C +HETATM 66 C5 BEN A 22 9.894 23.583 18.218 1.00 0.00 C +HETATM 67 C1 BEN A 23 20.524 0.600 10.775 1.00 0.00 C +HETATM 68 C3 BEN A 23 22.015 2.364 10.071 1.00 0.00 C +HETATM 69 C5 BEN A 23 21.420 0.600 8.533 1.00 0.00 C +HETATM 70 C1 BEN A 24 23.381 28.881 11.610 1.00 0.00 C +HETATM 71 C3 BEN A 24 22.052 28.191 9.717 1.00 0.00 C +HETATM 72 C5 BEN A 24 21.144 28.041 11.949 1.00 0.00 C +HETATM 73 C1 BEN A 25 12.290 5.318 21.654 1.00 0.00 C +HETATM 74 C3 BEN A 25 10.097 5.045 20.681 1.00 0.00 C +HETATM 75 C5 BEN A 25 11.422 6.992 20.147 1.00 0.00 C +HETATM 76 C1 BEN A 26 30.000 14.399 4.497 1.00 0.00 C +HETATM 77 C3 BEN A 26 28.758 16.469 4.454 1.00 0.00 C +HETATM 78 C5 BEN A 26 29.488 15.455 2.387 1.00 0.00 C +HETATM 79 C1 BEN A 27 29.600 16.851 24.027 1.00 0.00 C +HETATM 80 C3 BEN A 27 27.473 16.656 25.153 1.00 0.00 C +HETATM 81 C5 BEN A 27 28.732 14.672 24.596 1.00 0.00 C +HETATM 82 C1 BEN A 28 8.330 10.530 5.762 1.00 0.00 C +HETATM 83 C3 BEN A 28 6.772 12.297 5.234 1.00 0.00 C +HETATM 84 C5 BEN A 28 8.622 11.834 3.752 1.00 0.00 C +HETATM 85 C1 BEN A 29 4.849 12.492 18.746 1.00 0.00 C +HETATM 86 C3 BEN A 29 2.852 13.170 19.921 1.00 0.00 C +HETATM 87 C5 BEN A 29 2.678 11.866 17.896 1.00 0.00 C +HETATM 88 C1 BEN A 30 8.511 28.110 25.809 1.00 0.00 C +HETATM 89 C3 BEN A 30 8.678 28.565 28.175 1.00 0.00 C +HETATM 90 C5 BEN A 30 7.348 30.000 26.759 1.00 0.00 C +HETATM 91 C1 BEN A 31 26.832 21.092 7.099 1.00 0.00 C +HETATM 92 C3 BEN A 31 28.739 20.252 5.880 1.00 0.00 C +HETATM 93 C5 BEN A 31 26.991 21.446 4.717 1.00 0.00 C +HETATM 94 C1 BEN A 32 25.050 5.984 24.165 1.00 0.00 C +HETATM 95 C3 BEN A 32 26.064 7.614 22.700 1.00 0.00 C +HETATM 96 C5 BEN A 32 24.801 8.340 24.626 1.00 0.00 C +HETATM 97 C1 BEN A 33 8.547 28.952 8.815 1.00 0.00 C +HETATM 98 C3 BEN A 33 7.997 30.011 6.716 1.00 0.00 C +HETATM 99 C5 BEN A 33 10.277 30.018 7.511 1.00 0.00 C +HETATM 100 C1 BEN A 34 1.919 27.170 6.277 1.00 0.00 C +HETATM 101 C3 BEN A 34 3.502 28.447 7.578 1.00 0.00 C +HETATM 102 C5 BEN A 34 2.195 26.710 8.631 1.00 0.00 C +HETATM 103 C1 BEN A 35 6.862 11.711 29.247 1.00 0.00 C +HETATM 104 C3 BEN A 35 5.642 12.942 27.566 1.00 0.00 C +HETATM 105 C5 BEN A 35 7.598 11.666 26.948 1.00 0.00 C +HETATM 106 C1 BEN A 36 0.600 4.929 4.123 1.00 0.00 C +HETATM 107 C3 BEN A 36 0.780 6.531 2.325 1.00 0.00 C +HETATM 108 C5 BEN A 36 2.592 4.995 2.760 1.00 0.00 C +HETATM 109 C1 BEN A 37 14.754 28.711 26.368 1.00 0.00 C +HETATM 110 C3 BEN A 37 15.860 30.000 24.652 1.00 0.00 C +HETATM 111 C5 BEN A 37 15.416 27.652 24.302 1.00 0.00 C +HETATM 112 C1 BEN A 38 29.592 11.484 26.286 1.00 0.00 C +HETATM 113 C3 BEN A 38 29.520 13.120 28.061 1.00 0.00 C +HETATM 114 C5 BEN A 38 27.512 12.580 26.833 1.00 0.00 C +HETATM 115 C1 BEN A 39 1.247 5.969 13.377 1.00 0.00 C +HETATM 116 C3 BEN A 39 1.688 8.113 12.357 1.00 0.00 C +HETATM 117 C5 BEN A 39 3.512 6.605 12.836 1.00 0.00 C +HETATM 118 C1 BEN A 40 15.508 26.266 9.683 1.00 0.00 C +HETATM 119 C3 BEN A 40 13.668 26.171 11.244 1.00 0.00 C +HETATM 120 C5 BEN A 40 15.357 24.445 11.261 1.00 0.00 C +HETATM 121 C1 BEN A 41 17.331 21.393 19.947 1.00 0.00 C +HETATM 122 C3 BEN A 41 19.187 21.359 18.404 1.00 0.00 C +HETATM 123 C5 BEN A 41 17.982 19.323 18.890 1.00 0.00 C +HETATM 124 C1 BEN A 42 3.361 5.282 17.050 1.00 0.00 C +HETATM 125 C3 BEN A 42 4.768 4.410 15.292 1.00 0.00 C +HETATM 126 C5 BEN A 42 2.625 3.392 15.742 1.00 0.00 C +HETATM 127 C1 BEN A 43 8.010 5.057 15.838 1.00 0.00 C +HETATM 128 C3 BEN A 43 9.103 3.580 17.404 1.00 0.00 C +HETATM 129 C5 BEN A 43 9.585 5.947 17.436 1.00 0.00 C +HETATM 130 C1 BEN A 44 21.415 22.505 20.595 1.00 0.00 C +HETATM 131 C3 BEN A 44 21.873 24.611 19.506 1.00 0.00 C +HETATM 132 C5 BEN A 44 20.890 24.579 21.712 1.00 0.00 C +HETATM 133 C1 BEN A 45 21.978 25.813 15.341 1.00 0.00 C +HETATM 134 C3 BEN A 45 20.510 27.723 15.172 1.00 0.00 C +HETATM 135 C5 BEN A 45 20.153 26.074 16.900 1.00 0.00 C +HETATM 136 C1 BEN A 46 17.990 21.529 28.654 1.00 0.00 C +HETATM 137 C3 BEN A 46 19.626 23.273 28.992 1.00 0.00 C +HETATM 138 C5 BEN A 46 19.037 22.586 26.753 1.00 0.00 C +HETATM 139 C1 BEN A 47 17.014 2.643 7.846 1.00 0.00 C +HETATM 140 C3 BEN A 47 17.887 0.600 8.790 1.00 0.00 C +HETATM 141 C5 BEN A 47 15.896 1.604 9.716 1.00 0.00 C +HETATM 142 C1 BEN A 48 29.279 9.080 12.126 1.00 0.00 C +HETATM 143 C3 BEN A 48 29.170 10.110 14.307 1.00 0.00 C +HETATM 144 C5 BEN A 48 30.000 11.366 12.419 1.00 0.00 C +HETATM 145 C1 BEN A 49 2.724 19.194 7.318 1.00 0.00 C +HETATM 146 C3 BEN A 49 3.574 18.224 5.277 1.00 0.00 C +HETATM 147 C5 BEN A 49 3.750 17.009 7.357 1.00 0.00 C +HETATM 148 C1 BEN A 50 5.034 21.105 5.945 1.00 0.00 C +HETATM 149 C3 BEN A 50 3.267 21.367 4.320 1.00 0.00 C +HETATM 150 C5 BEN A 50 4.978 23.058 4.527 1.00 0.00 C +HETATM 151 C1 BEN A 51 11.172 16.114 11.197 1.00 0.00 C +HETATM 152 C3 BEN A 51 12.506 16.105 9.184 1.00 0.00 C +HETATM 153 C5 BEN A 51 12.853 17.813 10.855 1.00 0.00 C +HETATM 154 C1 BEN A 52 8.471 0.948 13.454 1.00 0.00 C +HETATM 155 C3 BEN A 52 8.964 0.672 15.801 1.00 0.00 C +HETATM 156 C5 BEN A 52 6.822 1.548 15.111 1.00 0.00 C +HETATM 157 C1 BEN A 53 10.411 21.049 6.167 1.00 0.00 C +HETATM 158 C3 BEN A 53 10.578 19.969 4.015 1.00 0.00 C +HETATM 159 C5 BEN A 53 10.478 18.640 6.029 1.00 0.00 C +HETATM 160 C1 BEN A 54 12.667 28.038 19.670 1.00 0.00 C +HETATM 161 C3 BEN A 54 13.988 29.246 21.290 1.00 0.00 C +HETATM 162 C5 BEN A 54 14.043 26.832 21.245 1.00 0.00 C +HETATM 163 C1 BEN A 55 12.353 2.849 14.706 1.00 0.00 C +HETATM 164 C3 BEN A 55 11.768 1.049 13.207 1.00 0.00 C +HETATM 165 C5 BEN A 55 13.091 0.600 15.177 1.00 0.00 C +HETATM 166 C1 BEN A 56 21.291 17.031 25.755 1.00 0.00 C +HETATM 167 C3 BEN A 56 22.400 15.823 27.527 1.00 0.00 C +HETATM 168 C5 BEN A 56 23.297 17.928 26.754 1.00 0.00 C +HETATM 169 C1 BEN A 57 19.011 30.000 10.366 1.00 0.00 C +HETATM 170 C3 BEN A 57 17.928 29.991 12.524 1.00 0.00 C +HETATM 171 C5 BEN A 57 16.601 30.000 10.506 1.00 0.00 C +HETATM 172 C1 BEN A 58 12.697 10.687 18.061 1.00 0.00 C +HETATM 173 C3 BEN A 58 14.169 11.362 16.270 1.00 0.00 C +HETATM 174 C5 BEN A 58 13.931 9.023 16.821 1.00 0.00 C +HETATM 175 C1 BEN A 59 16.131 23.467 23.003 1.00 0.00 C +HETATM 176 C3 BEN A 59 13.897 22.599 22.709 1.00 0.00 C +HETATM 177 C5 BEN A 59 15.006 22.386 24.844 1.00 0.00 C +HETATM 178 C1 BEN A 60 21.137 18.937 16.418 1.00 0.00 C +HETATM 179 C3 BEN A 60 21.249 18.607 18.807 1.00 0.00 C +HETATM 180 C5 BEN A 60 21.659 16.756 17.311 1.00 0.00 C +HETATM 181 C1 BEN A 61 28.576 19.874 12.985 1.00 0.00 C +HETATM 182 C3 BEN A 61 26.869 20.448 11.377 1.00 0.00 C +HETATM 183 C5 BEN A 61 28.995 21.591 11.340 1.00 0.00 C +HETATM 184 C1 BEN A 62 30.000 19.399 9.087 1.00 0.00 C +HETATM 185 C3 BEN A 62 30.000 17.515 10.598 1.00 0.00 C +HETATM 186 C5 BEN A 62 28.188 17.804 9.027 1.00 0.00 C +HETATM 187 C1 BEN A 63 9.588 26.733 6.606 1.00 0.00 C +HETATM 188 C3 BEN A 63 11.455 27.733 5.447 1.00 0.00 C +HETATM 189 C5 BEN A 63 9.307 27.563 4.357 1.00 0.00 C +HETATM 190 C1 BEN A 64 2.584 4.572 21.893 1.00 0.00 C +HETATM 191 C3 BEN A 64 0.600 3.713 22.968 1.00 0.00 C +HETATM 192 C5 BEN A 64 2.678 3.842 24.192 1.00 0.00 C +HETATM 193 C1 BEN A 65 4.824 30.000 16.412 1.00 0.00 C +HETATM 194 C3 BEN A 65 3.046 29.788 18.032 1.00 0.00 C +HETATM 195 C5 BEN A 65 3.709 27.880 16.708 1.00 0.00 C +HETATM 196 C1 BEN A 66 21.712 20.958 4.222 1.00 0.00 C +HETATM 197 C3 BEN A 66 23.551 20.962 5.788 1.00 0.00 C +HETATM 198 C5 BEN A 66 21.947 22.767 5.803 1.00 0.00 C +HETATM 199 C1 BEN A 67 5.751 19.330 13.093 1.00 0.00 C +HETATM 200 C3 BEN A 67 7.638 18.221 14.114 1.00 0.00 C +HETATM 201 C5 BEN A 67 7.457 20.628 14.203 1.00 0.00 C +HETATM 202 C1 BEN A 68 22.385 5.738 15.730 1.00 0.00 C +HETATM 203 C3 BEN A 68 23.137 7.381 17.332 1.00 0.00 C +HETATM 204 C5 BEN A 68 24.209 5.219 17.224 1.00 0.00 C +HETATM 205 C1 BEN A 69 13.689 4.612 3.242 1.00 0.00 C +HETATM 206 C3 BEN A 69 14.409 4.558 5.546 1.00 0.00 C +HETATM 207 C5 BEN A 69 12.875 6.267 4.799 1.00 0.00 C +HETATM 208 C1 BEN A 70 17.229 18.173 29.377 1.00 0.00 C +HETATM 209 C3 BEN A 70 19.261 16.887 29.595 1.00 0.00 C +HETATM 210 C5 BEN A 70 18.927 18.301 27.666 1.00 0.00 C +HETATM 211 C1 BEN A 71 7.180 16.087 22.607 1.00 0.00 C +HETATM 212 C3 BEN A 71 7.562 13.746 22.154 1.00 0.00 C +HETATM 213 C5 BEN A 71 7.520 15.338 20.338 1.00 0.00 C +HETATM 214 C1 BEN A 72 20.961 5.856 24.996 1.00 0.00 C +HETATM 215 C3 BEN A 72 22.493 6.853 26.574 1.00 0.00 C +HETATM 216 C5 BEN A 72 20.123 7.278 26.758 1.00 0.00 C +HETATM 217 C1 BEN A 73 5.231 22.622 30.002 1.00 0.00 C +HETATM 218 C3 BEN A 73 2.879 22.074 30.002 1.00 0.00 C +HETATM 219 C5 BEN A 73 3.728 23.758 28.492 1.00 0.00 C +HETATM 220 C1 BEN A 74 15.407 14.189 26.926 1.00 0.00 C +HETATM 221 C3 BEN A 74 14.613 15.723 25.239 1.00 0.00 C +HETATM 222 C5 BEN A 74 16.882 15.889 26.051 1.00 0.00 C +HETATM 223 C1 BEN A 75 21.574 11.782 16.293 1.00 0.00 C +HETATM 224 C3 BEN A 75 21.545 10.882 14.053 1.00 0.00 C +HETATM 225 C5 BEN A 75 20.329 9.769 15.818 1.00 0.00 C +HETATM 226 C1 BEN A 76 18.888 4.249 5.581 1.00 0.00 C +HETATM 227 C3 BEN A 76 20.526 5.176 7.092 1.00 0.00 C +HETATM 228 C5 BEN A 76 20.191 6.169 4.916 1.00 0.00 C +HETATM 229 C1 BEN A 77 17.223 8.529 21.107 1.00 0.00 C +HETATM 230 C3 BEN A 77 16.280 10.107 19.541 1.00 0.00 C +HETATM 231 C5 BEN A 77 16.006 7.735 19.179 1.00 0.00 C +HETATM 232 C1 BEN A 78 4.302 14.912 4.839 1.00 0.00 C +HETATM 233 C3 BEN A 78 2.925 12.962 5.205 1.00 0.00 C +HETATM 234 C5 BEN A 78 1.896 15.114 4.829 1.00 0.00 C +HETATM 235 C1 BEN A 79 10.079 8.039 13.374 1.00 0.00 C +HETATM 236 C3 BEN A 79 8.948 9.358 15.051 1.00 0.00 C +HETATM 237 C5 BEN A 79 11.339 9.552 14.770 1.00 0.00 C +HETATM 238 C1 BEN A 80 6.652 9.138 12.120 1.00 0.00 C +HETATM 239 C3 BEN A 80 6.390 11.004 13.630 1.00 0.00 C +HETATM 240 C5 BEN A 80 4.443 9.911 12.710 1.00 0.00 C +HETATM 241 C1 BEN A 81 12.483 21.846 28.850 1.00 0.00 C +HETATM 242 C3 BEN A 81 13.935 19.974 29.316 1.00 0.00 C +HETATM 243 C5 BEN A 81 14.826 22.218 29.292 1.00 0.00 C +HETATM 244 C1 BEN A 82 29.695 5.717 16.509 1.00 0.00 C +HETATM 245 C3 BEN A 82 27.533 4.920 17.229 1.00 0.00 C +HETATM 246 C5 BEN A 82 29.531 4.202 18.381 1.00 0.00 C +HETATM 247 C1 BEN A 83 10.805 12.360 26.313 1.00 0.00 C +HETATM 248 C3 BEN A 83 9.445 13.999 25.176 1.00 0.00 C +HETATM 249 C5 BEN A 83 11.751 13.685 24.530 1.00 0.00 C +HETATM 250 C1 BEN A 84 14.131 10.923 26.703 1.00 0.00 C +HETATM 251 C3 BEN A 84 16.246 9.780 26.480 1.00 0.00 C +HETATM 252 C5 BEN A 84 15.659 11.548 24.942 1.00 0.00 C +HETATM 253 C1 BEN A 85 24.305 10.196 12.381 1.00 0.00 C +HETATM 254 C3 BEN A 85 24.875 11.135 14.531 1.00 0.00 C +HETATM 255 C5 BEN A 85 25.630 12.210 12.505 1.00 0.00 C +HETATM 256 C1 BEN A 86 3.668 16.512 1.416 1.00 0.00 C +HETATM 257 C3 BEN A 86 3.178 14.183 1.823 1.00 0.00 C +HETATM 258 C5 BEN A 86 1.609 15.552 0.600 1.00 0.00 C +HETATM 259 C1 BEN A 87 18.605 23.358 15.843 1.00 0.00 C +HETATM 260 C3 BEN A 87 17.084 24.864 16.960 1.00 0.00 C +HETATM 261 C5 BEN A 87 16.226 22.958 15.750 1.00 0.00 C +HETATM 262 C1 BEN A 88 16.063 15.153 30.000 1.00 0.00 C +HETATM 263 C3 BEN A 88 15.924 12.743 30.001 1.00 0.00 C +HETATM 264 C5 BEN A 88 18.081 13.829 29.972 1.00 0.00 C +HETATM 265 C1 BEN A 89 10.294 12.611 13.843 1.00 0.00 C +HETATM 266 C3 BEN A 89 10.825 12.847 11.499 1.00 0.00 C +HETATM 267 C5 BEN A 89 9.381 11.057 12.237 1.00 0.00 C +HETATM 268 C1 BEN A 90 4.733 25.507 6.722 1.00 0.00 C +HETATM 269 C3 BEN A 90 4.535 26.323 4.458 1.00 0.00 C +HETATM 270 C5 BEN A 90 6.704 25.747 5.350 1.00 0.00 C +HETATM 271 C1 BEN A 91 17.229 3.703 19.954 1.00 0.00 C +HETATM 272 C3 BEN A 91 16.142 1.668 20.666 1.00 0.00 C +HETATM 273 C5 BEN A 91 18.525 1.929 20.955 1.00 0.00 C +HETATM 274 C1 BEN A 92 6.334 13.251 1.580 1.00 0.00 C +HETATM 275 C3 BEN A 92 7.360 14.905 3.008 1.00 0.00 C +HETATM 276 C5 BEN A 92 7.360 15.211 0.613 1.00 0.00 C +HETATM 277 C1 BEN A 93 12.524 20.222 24.625 1.00 0.00 C +HETATM 278 C3 BEN A 93 14.491 19.049 23.860 1.00 0.00 C +HETATM 279 C5 BEN A 93 13.727 18.773 26.135 1.00 0.00 C +HETATM 280 C1 BEN A 94 0.636 14.357 17.094 1.00 0.00 C +HETATM 281 C3 BEN A 94 0.847 16.003 15.340 1.00 0.00 C +HETATM 282 C5 BEN A 94 2.493 14.249 15.554 1.00 0.00 C +HETATM 283 C1 BEN A 95 29.278 10.454 29.989 1.00 0.00 C +HETATM 284 C3 BEN A 95 27.137 9.339 29.964 1.00 0.00 C +HETATM 285 C5 BEN A 95 27.243 11.752 30.013 1.00 0.00 C +HETATM 286 C1 BEN A 96 12.647 11.937 6.470 1.00 0.00 C +HETATM 287 C3 BEN A 96 13.557 10.706 8.336 1.00 0.00 C +HETATM 288 C5 BEN A 96 13.999 13.070 8.117 1.00 0.00 C +HETATM 289 C1 BEN A 97 18.548 13.492 18.376 1.00 0.00 C +HETATM 290 C3 BEN A 97 17.447 12.944 16.298 1.00 0.00 C +HETATM 291 C5 BEN A 97 19.444 14.302 16.286 1.00 0.00 C +HETATM 292 C1 BEN A 98 9.420 1.713 2.666 1.00 0.00 C +HETATM 293 C3 BEN A 98 7.278 0.600 2.606 1.00 0.00 C +HETATM 294 C5 BEN A 98 7.610 2.504 4.054 1.00 0.00 C +HETATM 295 C1 BEN A 99 19.626 24.343 12.378 1.00 0.00 C +HETATM 296 C3 BEN A 99 18.861 25.962 10.757 1.00 0.00 C +HETATM 297 C5 BEN A 99 18.260 26.204 13.084 1.00 0.00 C +HETATM 298 C1 BEN A 100 15.141 3.322 26.190 1.00 0.00 C +HETATM 299 C3 BEN A 100 16.367 5.265 26.932 1.00 0.00 C +HETATM 300 C5 BEN A 100 14.924 4.087 28.469 1.00 0.00 C +HETATM 301 C1 BEN A 101 8.127 9.398 9.067 1.00 0.00 C +HETATM 302 C3 BEN A 101 10.128 8.515 10.089 1.00 0.00 C +HETATM 303 C5 BEN A 101 10.288 10.268 8.436 1.00 0.00 C +HETATM 304 C1 BEN A 102 7.684 20.274 7.855 1.00 0.00 C +HETATM 305 C3 BEN A 102 5.660 19.371 8.814 1.00 0.00 C +HETATM 306 C5 BEN A 102 6.835 18.145 7.097 1.00 0.00 C +HETATM 307 C1 BEN A 103 6.184 21.307 26.038 1.00 0.00 C +HETATM 308 C3 BEN A 103 4.479 20.533 27.562 1.00 0.00 C +HETATM 309 C5 BEN A 103 6.809 20.373 28.175 1.00 0.00 C +HETATM 310 C1 BEN A 104 4.985 19.935 23.020 1.00 0.00 C +HETATM 311 C3 BEN A 104 4.947 22.245 22.319 1.00 0.00 C +HETATM 312 C5 BEN A 104 4.888 20.481 20.671 1.00 0.00 C +HETATM 313 C1 BEN A 105 13.046 25.672 7.465 1.00 0.00 C +HETATM 314 C3 BEN A 105 12.182 27.583 8.661 1.00 0.00 C +HETATM 315 C5 BEN A 105 11.283 25.383 9.088 1.00 0.00 C +HETATM 316 C1 BEN A 106 14.853 22.372 0.599 1.00 0.00 C +HETATM 317 C3 BEN A 106 14.818 23.878 2.486 1.00 0.00 C +HETATM 318 C5 BEN A 106 13.360 24.269 0.601 1.00 0.00 C +HETATM 319 C1 BEN A 107 20.270 6.229 18.869 1.00 0.00 C +HETATM 320 C3 BEN A 107 20.097 5.235 21.062 1.00 0.00 C +HETATM 321 C5 BEN A 107 21.811 6.875 20.610 1.00 0.00 C +HETATM 322 C1 BEN A 108 26.741 24.542 17.937 1.00 0.00 C +HETATM 323 C3 BEN A 108 26.625 25.797 15.877 1.00 0.00 C +HETATM 324 C5 BEN A 108 25.404 23.725 16.100 1.00 0.00 C +HETATM 325 C1 BEN A 109 6.482 26.595 9.845 1.00 0.00 C +HETATM 326 C3 BEN A 109 7.735 27.536 11.682 1.00 0.00 C +HETATM 327 C5 BEN A 109 5.878 28.756 10.736 1.00 0.00 C +HETATM 328 C1 BEN A 110 28.593 12.991 7.129 1.00 0.00 C +HETATM 329 C3 BEN A 110 29.999 12.893 9.089 1.00 0.00 C +HETATM 330 C5 BEN A 110 29.601 15.007 7.992 1.00 0.00 C +HETATM 331 C1 BEN A 111 24.276 28.997 24.563 1.00 0.00 C +HETATM 332 C3 BEN A 111 26.454 29.997 24.266 1.00 0.00 C +HETATM 333 C5 BEN A 111 24.865 29.998 22.447 1.00 0.00 C +HETATM 334 C1 BEN A 112 16.307 13.918 5.640 1.00 0.00 C +HETATM 335 C3 BEN A 112 17.707 12.708 7.191 1.00 0.00 C +HETATM 336 C5 BEN A 112 17.671 15.122 7.226 1.00 0.00 C +HETATM 337 C1 BEN A 113 15.953 22.892 7.466 1.00 0.00 C +HETATM 338 C3 BEN A 113 14.357 23.225 5.686 1.00 0.00 C +HETATM 339 C5 BEN A 113 15.988 24.936 6.182 1.00 0.00 C +HETATM 340 C1 BEN A 114 8.042 14.821 14.824 1.00 0.00 C +HETATM 341 C3 BEN A 114 5.698 14.255 14.711 1.00 0.00 C +HETATM 342 C5 BEN A 114 6.745 14.640 16.852 1.00 0.00 C +HETATM 343 C1 BEN A 115 20.102 27.794 1.773 1.00 0.00 C +HETATM 344 C3 BEN A 115 20.246 27.663 4.179 1.00 0.00 C +HETATM 345 C5 BEN A 115 21.466 26.093 2.809 1.00 0.00 C +HETATM 346 C1 BEN A 116 26.648 24.394 30.000 1.00 0.00 C +HETATM 347 C3 BEN A 116 26.936 22.488 28.545 1.00 0.00 C +HETATM 348 C5 BEN A 116 24.906 23.792 28.442 1.00 0.00 C +HETATM 349 C1 BEN A 117 20.804 1.888 3.320 1.00 0.00 C +HETATM 350 C3 BEN A 117 22.180 0.600 4.830 1.00 0.00 C +HETATM 351 C5 BEN A 117 21.998 2.992 5.104 1.00 0.00 C +HETATM 352 C1 BEN A 118 22.726 1.439 27.980 1.00 0.00 C +HETATM 353 C3 BEN A 118 23.182 3.767 28.427 1.00 0.00 C +HETATM 354 C5 BEN A 118 21.179 3.136 27.235 1.00 0.00 C +HETATM 355 C1 BEN A 119 17.569 1.376 4.624 1.00 0.00 C +HETATM 356 C3 BEN A 119 15.741 0.953 3.104 1.00 0.00 C +HETATM 357 C5 BEN A 119 15.444 0.611 5.476 1.00 0.00 C +HETATM 358 C1 BEN A 120 25.680 17.300 6.895 1.00 0.00 C +HETATM 359 C3 BEN A 120 24.665 15.371 5.857 1.00 0.00 C +HETATM 360 C5 BEN A 120 24.662 17.499 4.716 1.00 0.00 C +HETATM 361 C1 BEN A 121 27.272 7.959 9.260 1.00 0.00 C +HETATM 362 C3 BEN A 121 27.348 10.221 8.420 1.00 0.00 C +HETATM 363 C5 BEN A 121 28.317 8.421 7.134 1.00 0.00 C +HETATM 364 C1 BEN A 122 20.146 11.194 21.235 1.00 0.00 C +HETATM 365 C3 BEN A 122 21.812 11.039 19.494 1.00 0.00 C +HETATM 366 C5 BEN A 122 20.006 9.438 19.583 1.00 0.00 C +HETATM 367 C1 BEN A 123 3.551 8.728 0.809 1.00 0.00 C +HETATM 368 C3 BEN A 123 5.686 8.375 1.880 1.00 0.00 C +HETATM 369 C5 BEN A 123 4.093 9.916 2.839 1.00 0.00 C +HETATM 370 C1 BEN A 124 6.935 27.734 2.025 1.00 0.00 C +HETATM 371 C3 BEN A 124 6.800 29.806 3.257 1.00 0.00 C +HETATM 372 C5 BEN A 124 4.802 28.824 2.321 1.00 0.00 C +HETATM 373 C1 BEN A 125 28.523 23.399 14.060 1.00 0.00 C +HETATM 374 C3 BEN A 125 28.734 25.629 13.159 1.00 0.00 C +HETATM 375 C5 BEN A 125 26.690 24.384 12.836 1.00 0.00 C +HETATM 376 C1 BEN A 126 10.955 16.873 20.721 1.00 0.00 C +HETATM 377 C3 BEN A 126 12.695 15.504 21.686 1.00 0.00 C +HETATM 378 C5 BEN A 126 11.527 17.116 23.054 1.00 0.00 C +HETATM 379 C1 BEN A 127 18.567 26.962 26.235 1.00 0.00 C +HETATM 380 C3 BEN A 127 18.472 25.916 24.061 1.00 0.00 C +HETATM 381 C5 BEN A 127 18.754 28.308 24.239 1.00 0.00 C +HETATM 382 C1 BEN A 128 23.760 30.000 17.496 1.00 0.00 C +HETATM 383 C3 BEN A 128 22.795 27.814 17.839 1.00 0.00 C +HETATM 384 C5 BEN A 128 21.364 29.748 17.634 1.00 0.00 C +HETATM 385 C1 BEN A 129 16.957 3.806 14.157 1.00 0.00 C +HETATM 386 C3 BEN A 129 19.010 3.228 13.025 1.00 0.00 C +HETATM 387 C5 BEN A 129 17.291 1.560 13.337 1.00 0.00 C +HETATM 388 C1 BEN A 130 10.309 19.879 9.844 1.00 0.00 C +HETATM 389 C3 BEN A 130 8.327 19.184 11.035 1.00 0.00 C +HETATM 390 C5 BEN A 130 8.775 21.533 10.702 1.00 0.00 C +HETATM 391 C1 BEN A 131 27.783 24.369 24.760 1.00 0.00 C +HETATM 392 C3 BEN A 131 29.303 25.422 26.312 1.00 0.00 C +HETATM 393 C5 BEN A 131 26.962 26.013 26.325 1.00 0.00 C +HETATM 394 C1 BEN A 132 3.766 28.350 24.564 1.00 0.00 C +HETATM 395 C3 BEN A 132 4.741 29.857 22.948 1.00 0.00 C +HETATM 396 C5 BEN A 132 5.746 27.697 23.346 1.00 0.00 C +HETATM 397 C1 BEN A 133 1.488 26.285 21.661 1.00 0.00 C +HETATM 398 C3 BEN A 133 1.839 28.598 21.063 1.00 0.00 C +HETATM 399 C5 BEN A 133 2.782 26.842 19.701 1.00 0.00 C +HETATM 400 C1 BEN A 134 21.531 1.073 22.467 1.00 0.00 C +HETATM 401 C3 BEN A 134 20.044 1.265 24.360 1.00 0.00 C +HETATM 402 C5 BEN A 134 22.436 1.253 24.698 1.00 0.00 C +HETATM 403 C1 BEN A 135 2.437 3.379 11.663 1.00 0.00 C +HETATM 404 C3 BEN A 135 2.460 5.153 10.026 1.00 0.00 C +HETATM 405 C5 BEN A 135 4.263 3.548 10.094 1.00 0.00 C +HETATM 406 C1 BEN A 136 10.419 17.837 1.485 1.00 0.00 C +HETATM 407 C3 BEN A 136 12.190 16.227 1.803 1.00 0.00 C +HETATM 408 C5 BEN A 136 10.433 16.432 3.447 1.00 0.00 C +HETATM 409 C1 BEN A 137 13.942 9.115 21.653 1.00 0.00 C +HETATM 410 C3 BEN A 137 14.635 8.479 23.877 1.00 0.00 C +HETATM 411 C5 BEN A 137 12.306 8.927 23.419 1.00 0.00 C +HETATM 412 C1 BEN A 138 22.272 20.852 28.372 1.00 0.00 C +HETATM 413 C3 BEN A 138 20.881 19.737 30.000 1.00 0.00 C +HETATM 414 C5 BEN A 138 23.245 19.256 29.899 1.00 0.00 C +HETATM 415 C1 BEN A 139 8.030 8.109 20.978 1.00 0.00 C +HETATM 416 C3 BEN A 139 6.813 10.073 21.679 1.00 0.00 C +HETATM 417 C5 BEN A 139 9.205 9.961 21.986 1.00 0.00 C +HETATM 418 C1 BEN A 140 13.350 25.360 25.611 1.00 0.00 C +HETATM 419 C3 BEN A 140 11.339 25.327 24.275 1.00 0.00 C +HETATM 420 C5 BEN A 140 11.228 24.843 26.639 1.00 0.00 C +HETATM 421 C1 BEN A 141 9.769 11.694 19.202 1.00 0.00 C +HETATM 422 C3 BEN A 141 8.049 11.392 17.535 1.00 0.00 C +HETATM 423 C5 BEN A 141 9.888 12.900 17.114 1.00 0.00 C +HETATM 424 C1 BEN A 142 24.379 17.110 23.422 1.00 0.00 C +HETATM 425 C3 BEN A 142 23.330 16.873 21.261 1.00 0.00 C +HETATM 426 C5 BEN A 142 25.209 15.475 21.851 1.00 0.00 C +HETATM 427 C1 BEN A 143 15.474 4.649 23.007 1.00 0.00 C +HETATM 428 C3 BEN A 143 17.702 3.944 23.617 1.00 0.00 C +HETATM 429 C5 BEN A 143 17.121 6.288 23.662 1.00 0.00 C +HETATM 430 C1 BEN A 144 13.754 14.931 16.942 1.00 0.00 C +HETATM 431 C3 BEN A 144 12.605 13.904 18.801 1.00 0.00 C +HETATM 432 C5 BEN A 144 14.821 14.816 19.104 1.00 0.00 C +HETATM 433 C1 BEN A 145 2.873 16.109 23.966 1.00 0.00 C +HETATM 434 C3 BEN A 145 4.825 17.052 25.029 1.00 0.00 C +HETATM 435 C5 BEN A 145 2.684 18.146 25.247 1.00 0.00 C +HETATM 436 C1 BEN A 146 5.535 24.064 11.968 1.00 0.00 C +HETATM 437 C3 BEN A 146 4.562 21.955 11.306 1.00 0.00 C +HETATM 438 C5 BEN A 146 5.423 23.467 9.631 1.00 0.00 C +HETATM 439 C1 BEN A 147 26.559 13.591 9.656 1.00 0.00 C +HETATM 440 C3 BEN A 147 25.712 15.832 9.956 1.00 0.00 C +HETATM 441 C5 BEN A 147 27.331 14.934 11.507 1.00 0.00 C +HETATM 442 C1 BEN A 148 12.767 30.043 3.494 1.00 0.00 C +HETATM 443 C3 BEN A 148 12.265 28.114 2.132 1.00 0.00 C +HETATM 444 C5 BEN A 148 10.791 30.026 2.107 1.00 0.00 C +HETATM 445 C1 BEN A 149 6.228 5.185 19.150 1.00 0.00 C +HETATM 446 C3 BEN A 149 6.290 3.137 20.426 1.00 0.00 C +HETATM 447 C5 BEN A 149 6.225 5.267 21.562 1.00 0.00 C +HETATM 448 C1 BEN A 150 27.888 1.583 10.933 1.00 0.00 C +HETATM 449 C3 BEN A 150 27.470 3.855 10.230 1.00 0.00 C +HETATM 450 C5 BEN A 150 29.525 2.753 9.600 1.00 0.00 C +HETATM 451 C1 BEN A 151 14.809 18.693 7.931 1.00 0.00 C +HETATM 452 C3 BEN A 151 16.787 18.675 6.546 1.00 0.00 C +HETATM 453 C5 BEN A 151 14.945 17.197 6.041 1.00 0.00 C +HETATM 454 C1 BEN A 152 13.800 6.594 9.166 1.00 0.00 C +HETATM 455 C3 BEN A 152 12.071 5.341 8.039 1.00 0.00 C +HETATM 456 C5 BEN A 152 12.255 7.724 7.694 1.00 0.00 C +HETATM 457 C1 BEN A 153 14.036 20.344 3.067 1.00 0.00 C +HETATM 458 C3 BEN A 153 16.150 21.007 4.028 1.00 0.00 C +HETATM 459 C5 BEN A 153 16.076 19.270 2.351 1.00 0.00 C +HETATM 460 C1 BEN A 154 5.488 21.951 0.594 1.00 0.00 C +HETATM 461 C3 BEN A 154 5.750 20.364 2.395 1.00 0.00 C +HETATM 462 C5 BEN A 154 7.222 22.273 2.241 1.00 0.00 C +HETATM 463 C1 BEN A 155 0.929 12.558 26.795 1.00 0.00 C +HETATM 464 C3 BEN A 155 0.916 12.041 24.437 1.00 0.00 C +HETATM 465 C5 BEN A 155 2.996 12.559 25.548 1.00 0.00 C +HETATM 466 C1 BEN A 156 16.614 18.751 14.571 1.00 0.00 C +HETATM 467 C3 BEN A 156 16.271 16.361 14.576 1.00 0.00 C +HETATM 468 C5 BEN A 156 18.345 17.286 15.398 1.00 0.00 C +HETATM 469 C1 BEN A 157 28.754 7.231 19.919 1.00 0.00 C +HETATM 470 C3 BEN A 157 26.796 7.817 18.634 1.00 0.00 C +HETATM 471 C5 BEN A 157 28.728 9.266 18.621 1.00 0.00 C +HETATM 472 C1 BEN A 158 11.810 1.813 27.939 1.00 0.00 C +HETATM 473 C3 BEN A 158 12.542 1.356 25.684 1.00 0.00 C +HETATM 474 C5 BEN A 158 10.500 2.588 26.065 1.00 0.00 C +HETATM 475 C1 BEN A 159 11.451 29.998 11.317 1.00 0.00 C +HETATM 476 C3 BEN A 159 11.151 27.910 12.492 1.00 0.00 C +HETATM 477 C5 BEN A 159 12.913 29.426 13.150 1.00 0.00 C +HETATM 478 C1 BEN A 160 22.890 8.234 5.892 1.00 0.00 C +HETATM 479 C3 BEN A 160 23.963 6.663 7.378 1.00 0.00 C +HETATM 480 C5 BEN A 160 24.377 9.030 7.619 1.00 0.00 C +HETATM 481 C1 BEN A 161 25.845 6.129 2.136 1.00 0.00 C +HETATM 482 C3 BEN A 161 23.972 7.315 1.180 1.00 0.00 C +HETATM 483 C5 BEN A 161 26.122 8.399 1.363 1.00 0.00 C +HETATM 484 C1 BEN A 162 19.145 5.051 10.277 1.00 0.00 C +HETATM 485 C3 BEN A 162 17.569 6.353 11.561 1.00 0.00 C +HETATM 486 C5 BEN A 162 17.036 5.699 9.298 1.00 0.00 C +HETATM 487 C1 BEN A 163 23.316 24.346 12.194 1.00 0.00 C +HETATM 488 C3 BEN A 163 23.608 22.051 11.503 1.00 0.00 C +HETATM 489 C5 BEN A 163 22.218 22.572 13.408 1.00 0.00 C +HETATM 490 C1 BEN A 164 4.825 7.738 26.139 1.00 0.00 C +HETATM 491 C3 BEN A 164 5.253 5.849 24.697 1.00 0.00 C +HETATM 492 C5 BEN A 164 3.191 7.089 24.486 1.00 0.00 C +HETATM 493 C1 BEN A 165 3.620 29.249 13.074 1.00 0.00 C +HETATM 494 C3 BEN A 165 2.705 27.159 12.285 1.00 0.00 C +HETATM 495 C5 BEN A 165 4.825 27.190 13.441 1.00 0.00 C +HETATM 496 C1 BEN A 166 20.369 13.120 4.944 1.00 0.00 C +HETATM 497 C3 BEN A 166 21.080 13.915 2.778 1.00 0.00 C +HETATM 498 C5 BEN A 166 18.781 13.266 3.132 1.00 0.00 C +HETATM 499 C1 BEN A 167 18.805 20.714 9.072 1.00 0.00 C +HETATM 500 C3 BEN A 167 19.005 21.770 6.910 1.00 0.00 C +HETATM 501 C5 BEN A 167 18.912 23.120 8.910 1.00 0.00 C +HETATM 502 C1 BEN A 168 24.138 21.035 19.448 1.00 0.00 C +HETATM 503 C3 BEN A 168 23.082 21.779 17.408 1.00 0.00 C +HETATM 504 C5 BEN A 168 24.989 20.300 17.312 1.00 0.00 C +HETATM 505 C1 BEN A 169 14.446 19.723 19.911 1.00 0.00 C +HETATM 506 C3 BEN A 169 13.082 18.752 18.172 1.00 0.00 C +HETATM 507 C5 BEN A 169 14.758 20.400 17.615 1.00 0.00 C +HETATM 508 C1 BEN A 170 5.523 8.724 18.255 1.00 0.00 C +HETATM 509 C3 BEN A 170 5.740 7.420 16.235 1.00 0.00 C +HETATM 510 C5 BEN A 170 4.079 9.167 16.372 1.00 0.00 C +HETATM 511 C1 BEN A 171 7.001 2.400 26.082 1.00 0.00 C +HETATM 512 C3 BEN A 171 6.248 0.596 24.665 1.00 0.00 C +HETATM 513 C5 BEN A 171 6.317 2.854 23.812 1.00 0.00 C +HETATM 514 C1 BEN A 172 10.161 23.650 1.703 1.00 0.00 C +HETATM 515 C3 BEN A 172 11.446 25.094 3.150 1.00 0.00 C +HETATM 516 C5 BEN A 172 9.342 24.183 3.910 1.00 0.00 C +HETATM 517 C1 BEN A 173 21.295 17.710 2.848 1.00 0.00 C +HETATM 518 C3 BEN A 173 19.167 17.155 1.851 1.00 0.00 C +HETATM 519 C5 BEN A 173 19.384 17.264 4.254 1.00 0.00 C +HETATM 520 C1 BEN A 174 26.923 28.911 2.652 1.00 0.00 C +HETATM 521 C3 BEN A 174 26.874 27.634 0.603 1.00 0.00 C +HETATM 522 C5 BEN A 174 24.874 27.852 1.940 1.00 0.00 C +HETATM 523 C1 BEN A 175 27.977 7.855 25.378 1.00 0.00 C +HETATM 524 C3 BEN A 175 28.904 9.156 23.568 1.00 0.00 C +HETATM 525 C5 BEN A 175 30.000 7.126 24.280 1.00 0.00 C +HETATM 526 C1 BEN A 176 23.712 14.276 16.589 1.00 0.00 C +HETATM 527 C3 BEN A 176 23.347 13.908 18.947 1.00 0.00 C +HETATM 528 C5 BEN A 176 24.682 15.781 18.209 1.00 0.00 C +HETATM 529 C1 BEN A 177 17.782 25.079 1.635 1.00 0.00 C +HETATM 530 C3 BEN A 177 16.919 27.068 2.697 1.00 0.00 C +HETATM 531 C5 BEN A 177 15.988 26.318 0.599 1.00 0.00 C +HETATM 532 C1 BEN A 178 7.235 14.111 9.571 1.00 0.00 C +HETATM 533 C3 BEN A 178 6.834 15.734 11.313 1.00 0.00 C +HETATM 534 C5 BEN A 178 8.248 16.298 9.437 1.00 0.00 C +HETATM 535 C1 BEN A 179 14.064 10.333 11.575 1.00 0.00 C +HETATM 536 C3 BEN A 179 15.185 8.419 12.530 1.00 0.00 C +HETATM 537 C5 BEN A 179 16.475 10.225 11.578 1.00 0.00 C +HETATM 538 C1 BEN A 180 20.060 19.989 23.597 1.00 0.00 C +HETATM 539 C3 BEN A 180 17.716 20.444 23.955 1.00 0.00 C +HETATM 540 C5 BEN A 180 19.239 22.255 23.470 1.00 0.00 C +HETATM 541 C1 BEN A 181 26.336 3.055 24.976 1.00 0.00 C +HETATM 542 C3 BEN A 181 27.687 4.160 26.645 1.00 0.00 C +HETATM 543 C5 BEN A 181 28.597 2.313 25.384 1.00 0.00 C +HETATM 544 C1 BEN A 182 25.568 27.192 19.516 1.00 0.00 C +HETATM 545 C3 BEN A 182 27.887 27.482 18.910 1.00 0.00 C +HETATM 546 C5 BEN A 182 26.539 29.402 19.483 1.00 0.00 C +HETATM 547 C1 BEN A 183 13.249 20.932 11.858 1.00 0.00 C +HETATM 548 C3 BEN A 183 11.890 22.619 10.791 1.00 0.00 C +HETATM 549 C5 BEN A 183 13.610 21.480 9.535 1.00 0.00 C +HETATM 550 C1 BEN A 184 24.801 11.976 21.999 1.00 0.00 C +HETATM 551 C3 BEN A 184 25.748 10.258 20.592 1.00 0.00 C +HETATM 552 C5 BEN A 184 23.747 9.809 21.866 1.00 0.00 C +HETATM 553 C1 BEN A 185 8.956 24.250 12.564 1.00 0.00 C +HETATM 554 C3 BEN A 185 7.660 24.343 14.599 1.00 0.00 C +HETATM 555 C5 BEN A 185 9.798 25.459 14.476 1.00 0.00 C +HETATM 556 C1 BEN A 186 7.440 6.713 30.000 1.00 0.00 C +HETATM 557 C3 BEN A 186 7.098 8.521 28.436 1.00 0.00 C +HETATM 558 C5 BEN A 186 9.313 8.054 29.278 1.00 0.00 C +HETATM 559 C1 BEN A 187 21.671 29.929 27.724 1.00 0.00 C +HETATM 560 C3 BEN A 187 23.818 30.000 28.828 1.00 0.00 C +HETATM 561 C5 BEN A 187 22.096 28.507 29.628 1.00 0.00 C +HETATM 562 C1 BEN A 188 16.638 10.466 4.117 1.00 0.00 C +HETATM 563 C3 BEN A 188 15.062 9.572 2.521 1.00 0.00 C +HETATM 564 C5 BEN A 188 15.552 11.936 2.540 1.00 0.00 C +HETATM 565 C1 BEN A 189 28.135 1.475 14.937 1.00 0.00 C +HETATM 566 C3 BEN A 189 26.143 2.774 14.518 1.00 0.00 C +HETATM 567 C5 BEN A 189 28.309 3.651 13.907 1.00 0.00 C +HETATM 568 C1 BEN A 190 27.203 1.451 7.649 1.00 0.00 C +HETATM 569 C3 BEN A 190 25.024 2.489 7.699 1.00 0.00 C +HETATM 570 C5 BEN A 190 26.417 2.699 5.738 1.00 0.00 C +HETATM 571 C1 BEN A 191 16.110 16.688 22.155 1.00 0.00 C +HETATM 572 C3 BEN A 191 18.125 15.663 23.003 1.00 0.00 C +HETATM 573 C5 BEN A 191 18.099 16.627 20.789 1.00 0.00 C +HETATM 574 C1 BEN A 192 4.908 9.466 7.294 1.00 0.00 C +HETATM 575 C3 BEN A 192 2.995 9.015 8.697 1.00 0.00 C +HETATM 576 C5 BEN A 192 2.732 9.139 6.300 1.00 0.00 C +HETATM 577 C1 BEN A 193 21.358 27.917 22.060 1.00 0.00 C +HETATM 578 C3 BEN A 193 21.415 30.001 23.278 1.00 0.00 C +HETATM 579 C5 BEN A 193 20.977 30.001 20.903 1.00 0.00 C +HETATM 580 C1 BEN A 194 18.924 11.664 25.960 1.00 0.00 C +HETATM 581 C3 BEN A 194 19.188 10.518 28.069 1.00 0.00 C +HETATM 582 C5 BEN A 194 20.212 12.679 27.731 1.00 0.00 C +HETATM 583 C1 BEN A 195 25.322 17.101 12.977 1.00 0.00 C +HETATM 584 C3 BEN A 195 26.301 18.177 14.904 1.00 0.00 C +HETATM 585 C5 BEN A 195 23.917 18.295 14.535 1.00 0.00 C +HETATM 586 C1 BEN A 196 22.371 14.087 22.807 1.00 0.00 C +HETATM 587 C3 BEN A 196 20.616 14.069 24.466 1.00 0.00 C +HETATM 588 C5 BEN A 196 22.749 13.046 24.952 1.00 0.00 C +HETATM 589 C1 BEN A 197 11.933 17.944 14.750 1.00 0.00 C +HETATM 590 C3 BEN A 197 10.824 16.360 16.196 1.00 0.00 C +HETATM 591 C5 BEN A 197 10.175 18.686 16.229 1.00 0.00 C +HETATM 592 C1 BEN A 198 11.970 6.082 27.888 1.00 0.00 C +HETATM 593 C3 BEN A 198 13.836 7.376 27.069 1.00 0.00 C +HETATM 594 C5 BEN A 198 12.838 5.678 25.672 1.00 0.00 C +HETATM 595 C1 BEN A 199 25.628 6.194 12.294 1.00 0.00 C +HETATM 596 C3 BEN A 199 25.088 7.442 14.289 1.00 0.00 C +HETATM 597 C5 BEN A 199 27.376 6.816 13.838 1.00 0.00 C +HETATM 598 C1 BEN A 200 8.681 7.740 25.312 1.00 0.00 C +HETATM 599 C3 BEN A 200 9.870 6.135 23.957 1.00 0.00 C +HETATM 600 C5 BEN A 200 8.460 5.379 25.766 1.00 0.00 C +END diff --git a/examples/mbar/demo.ipynb b/examples/mbar/demo.ipynb new file mode 100644 index 000000000..01da8e00d --- /dev/null +++ b/examples/mbar/demo.ipynb @@ -0,0 +1,542 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "16a0d3cf", + "metadata": {}, + "source": [ + "# Optimize CG benzene using MBAR to fit radial distribution function\n", + "\n", + "\n", + "In this demo, we would try to optimize a coarse-grained benzene model with three beads to fit experimental center-of-mass radial distribution function. The potential function only has harmonic bond term and Lennard-Jones term as:\n", + "\n", + "$$\\begin{align*}\n", + " V(\\mathbf{R}) &= V_{\\mathrm{bond}} + V_\\mathrm{vdW} \\\\\n", + " &= \\sum_{\\mathrm{bonds}}\\frac{1}{2}k_b(r - r_0)^2 \\\\\n", + " &\\quad+ \\sum_{ij}4\\varepsilon_{ij}\\left[\\left(\\frac{\\sigma_{ij}}{r_{ij}}\\right)^{12} - \\left(\\frac{\\sigma_{ij}}{r_{ij}}\\right)^6\\right]\n", + "\\end{align*}$$\n", + "\n", + "## Import necessary packages & functions " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "68a9fa08", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/xinyan/miniconda3/envs/openmm/lib/python3.8/site-packages/dm_haiku-0.0.6-py3.8.egg/haiku/_src/data_structures.py:37: FutureWarning: jax.tree_structure is deprecated, and will be removed in a future release. Use jax.tree_util.tree_structure instead.\n", + " PyTreeDef = type(jax.tree_structure(None))\n", + "WARNING:pymbar.timeseries:Warning on use of the timeseries module: If the inherent timescales of the system are long compared to those being analyzed, this statistical inefficiency may be an underestimate. The estimate presumes the use of many statistically independent samples. Tests should be performed to assess whether this condition is satisfied. Be cautious in the interpretation of the data.\n" + ] + } + ], + "source": [ + "import openmm as mm\n", + "import openmm.app as app\n", + "import openmm.unit as unit\n", + "import numpy as np\n", + "import sys\n", + "import mdtraj as md\n", + "from tqdm import tqdm, trange\n", + "import matplotlib.pyplot as plt\n", + "from dmff.mbar import MBAREstimator, SampleState, TargetState, Sample, OpenMMSampleState\n", + "from dmff.optimize import MultiTransform, genOptimizer\n", + "from dmff import Hamiltonian, NeighborListFreud\n", + "import optax\n", + "import jax\n", + "import jax.numpy as jnp\n", + "\n", + "app.Topology.loadBondDefinitions(\"ben-top.xml\")\n", + "kbT = 8.314 * 303 / 1000.0\n", + "\n", + "\n", + "def readRDF(fname):\n", + " with open(fname, \"r\") as f:\n", + " data = np.array([[float(j) for j in i.strip().split()] for i in f])\n", + " xaxis = np.linspace(2.0, 14.0, 121)\n", + " yinterp = np.interp(xaxis, data[:,0], data[:,1])\n", + " return xaxis, yinterp\n", + "\n", + "# read experimental benzene RDF\n", + "x_ref, y_ref = readRDF(\"benz.txt\")\n", + "\n", + "\n", + "def sample_with_prm(parameter, trajectory, init_struct=\"box_relaxed.pdb\"):\n", + " pdb = app.PDBFile(init_struct)\n", + " ff = app.ForceField(parameter)\n", + " system = ff.createSystem(pdb.topology, nonbondedMethod=app.PME, nonbondedCutoff=1.1*unit.nanometer, constraints=None)\n", + " system.addForce(mm.MonteCarloBarostat(1.0*unit.bar, 303.0*unit.kelvin, 20))\n", + " for force in system.getForces():\n", + " if isinstance(force, mm.NonbondedForce):\n", + " force.setUseDispersionCorrection(False)\n", + " force.setUseSwitchingFunction(False)\n", + " integ = mm.LangevinIntegrator(303*unit.kelvin, 5/unit.picosecond, 1*unit.femtosecond)\n", + "\n", + " simulation = app.Simulation(pdb.topology, system, integ)\n", + " simulation.context.setPositions(pdb.getPositions())\n", + " simulation.reporters.append(app.DCDReporter(trajectory, 4000))\n", + " simulation.reporters.append(app.StateDataReporter(sys.stdout, 20000, density=True, step=True, remainingTime=True, speed=True, totalSteps=500*1000))\n", + " simulation.minimizeEnergy()\n", + " simulation.step(500*1000)" + ] + }, + { + "cell_type": "markdown", + "id": "8ee7668b", + "metadata": {}, + "source": [ + "## sample with initial parameter set" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4b29effb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "#\"Step\",\"Density (g/mL)\",\"Speed (ns/day)\",\"Time Remaining\"\n", + "20000,0.5609269274230193,0,--\n", + "40000,0.5459109958008428,154,4:18\n", + "60000,0.5327412068778505,152,4:09\n", + "80000,0.5458843337093509,153,3:57\n", + "100000,0.5499541448891778,153,3:45\n", + "120000,0.5559041043396893,152,3:35\n", + "140000,0.552264674142506,152,3:24\n", + "160000,0.5497341640178879,153,3:12\n", + "180000,0.5429753752031576,153,3:01\n", + "200000,0.5483391135340221,153,2:49\n", + "220000,0.5505668336047832,153,2:38\n", + "240000,0.5335132149170563,153,2:26\n", + "260000,0.5612835029864318,153,2:15\n", + "280000,0.5539964740924908,153,2:04\n", + "300000,0.5562792831995799,153,1:52\n", + "320000,0.5810152336723056,153,1:41\n", + "340000,0.5578735559454526,153,1:30\n", + "360000,0.5646608102872624,153,1:19\n", + "380000,0.5715438669104802,153,1:07\n", + "400000,0.5615453644018904,153,0:56\n", + "420000,0.5729058327239069,153,0:45\n", + "440000,0.5551077875519419,153,0:33\n", + "460000,0.575714406552948,153,0:22\n", + "480000,0.5542066355298249,153,0:11\n", + "500000,0.5602510266387776,153,0:00\n" + ] + } + ], + "source": [ + "sample_with_prm(\"ben-prm.xml\", \"init.dcd\")\n", + "traj = md.load(\"init.dcd\", top=\"box_relaxed.pdb\")[50:]" + ] + }, + { + "cell_type": "markdown", + "id": "e39138c2", + "metadata": {}, + "source": [ + "## compute radial distribution function per frame" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ae1d2a94", + "metadata": {}, + "outputs": [], + "source": [ + "def compute_rdf_frame(traj, xaxis):\n", + " rdf_list = []\n", + " delta = xaxis[1] - xaxis[0]\n", + "\n", + " tidx = []\n", + " for ii in range(200):\n", + " tidx.append(3*ii)\n", + " tidx = np.array(tidx)\n", + " tsub = traj.atom_slice(tidx)\n", + " xyzs = traj.xyz\n", + " com = np.zeros((traj.n_frames, 200, 3))\n", + "\n", + " for na in range(3):\n", + " com += xyzs[:,tidx+na,:]\n", + " com = com / 3\n", + "\n", + " pairs = []\n", + " for na in range(200):\n", + " for nb in range(na+1, 200):\n", + " pairs.append([na, nb])\n", + " tsub.xyz = com\n", + "\n", + " for frame in tsub:\n", + " _, g_r = md.compute_rdf(frame, pairs, r_range=(xaxis[0]-0.5*delta, xaxis[-1]+0.5*delta+1e-10), bin_width=delta)\n", + " rdf_list.append(g_r.reshape((1, -1)))\n", + " return np.concatenate(rdf_list, axis=0)\n" + ] + }, + { + "cell_type": "markdown", + "id": "a8b76cf8", + "metadata": {}, + "source": [ + "## initialize MBAR estimator" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "724a10d0", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 0%| | 0/75 [00:00=1.18 -jax-md=0.2.0 +jax-md==0.2.0 mkdocs>=1.3.0 mkdocs-autorefs>=0.4.1 mkdocs-gen-files>=0.3.4 @@ -9,3 +9,4 @@ mkdocstrings-python>=0.7.0 pygments>=2.12 jax>=0.3.7,<0.3.16 jaxlib>=0.3.7,<0.3.16 +pymbar==4.0.1 \ No newline at end of file diff --git a/setup.py b/setup.py index 31a7d862f..3461b0e97 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ install_requires = [ "numpy>=1.18", "jax_md>=0.1.28", - "openmm", + "openmm>=7.6.0", "freud-analysis" ] diff --git a/tests/data/methane_water.pdb b/tests/data/methane_water.pdb index fd111fd4a..287570fd0 100644 --- a/tests/data/methane_water.pdb +++ b/tests/data/methane_water.pdb @@ -1,6 +1,6 @@ COMPND methane in water AUTHOR GENERATED BY OPEN BABEL 3.1.0 -CRYST1 12.000 12.000 12.000 90.00 90.00 90.00 P1 1 +CRYST1 11.999 11.999 11.999 90.00 90.00 90.00 P1 1 ATOM 1 C1 MOL A 1 5.900 5.920 5.940 1.00 0.00 C ATOM 2 H1 MOL A 1 5.250 6.810 5.820 1.00 0.00 H ATOM 3 H2 MOL A 1 5.540 5.060 5.330 1.00 0.00 H diff --git a/tests/data/w1_npt.dcd b/tests/data/w1_npt.dcd new file mode 100644 index 000000000..a92871010 Binary files /dev/null and b/tests/data/w1_npt.dcd differ diff --git a/tests/data/w2_npt.dcd b/tests/data/w2_npt.dcd new file mode 100644 index 000000000..ef149a224 Binary files /dev/null and b/tests/data/w2_npt.dcd differ diff --git a/tests/data/water1.xml b/tests/data/water1.xml new file mode 100644 index 000000000..9bcff8f0e --- /dev/null +++ b/tests/data/water1.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/data/water2.xml b/tests/data/water2.xml new file mode 100644 index 000000000..3e1776841 --- /dev/null +++ b/tests/data/water2.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + /Users/xinyan/DMFF/spcfw_ut/spcfw.xml + + + + + + + + + + + \ No newline at end of file diff --git a/tests/data/water3.xml b/tests/data/water3.xml new file mode 100644 index 000000000..fa9fe18a1 --- /dev/null +++ b/tests/data/water3.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/data/waterbox.pdb b/tests/data/waterbox.pdb new file mode 100644 index 000000000..bcb8fd7f0 --- /dev/null +++ b/tests/data/waterbox.pdb @@ -0,0 +1,757 @@ +HEADER +TITLE Built with Packmol through Packmol Memgen +REMARK Packmol generated pdb file, Packmol Memgen estimated parameters +REMARK Home-Page: http://www.ime.unicamp.br/~martinez/packmol +REMARK +CRYST1 22.20 22.20 22.22 90.00 90.00 90.00 P 1 1 +HETATM 1 O HOH A 1 4.210 14.854 3.889 1.00 0.00 O +HETATM 2 H1 HOH A 1 5.084 15.025 4.240 1.00 0.00 H +HETATM 3 H2 HOH A 1 3.634 15.434 4.388 1.00 0.00 H +HETATM 4 O HOH A 2 14.950 3.112 16.341 1.00 0.00 O +HETATM 5 H1 HOH A 2 14.613 3.219 15.451 1.00 0.00 H +HETATM 6 H2 HOH A 2 14.182 2.878 16.862 1.00 0.00 H +HETATM 7 O HOH A 3 5.511 10.760 1.916 1.00 0.00 O +HETATM 8 H1 HOH A 3 4.741 10.900 2.466 1.00 0.00 H +HETATM 9 H2 HOH A 3 5.168 10.748 1.022 1.00 0.00 H +HETATM 10 O HOH A 4 10.307 13.966 19.730 1.00 0.00 O +HETATM 11 H1 HOH A 4 9.544 13.405 19.868 1.00 0.00 H +HETATM 12 H2 HOH A 4 10.892 13.759 20.459 1.00 0.00 H +HETATM 13 O HOH A 5 2.005 10.407 13.006 1.00 0.00 O +HETATM 14 H1 HOH A 5 1.469 10.341 13.797 1.00 0.00 H +HETATM 15 H2 HOH A 5 1.595 11.106 12.497 1.00 0.00 H +HETATM 16 O HOH A 6 19.620 8.171 20.640 1.00 0.00 O +HETATM 17 H1 HOH A 6 20.014 8.372 19.792 1.00 0.00 H +HETATM 18 H2 HOH A 6 19.779 8.953 21.169 1.00 0.00 H +HETATM 19 O HOH A 7 9.653 9.379 6.401 1.00 0.00 O +HETATM 20 H1 HOH A 7 9.857 8.960 7.237 1.00 0.00 H +HETATM 21 H2 HOH A 7 9.879 8.722 5.743 1.00 0.00 H +HETATM 22 O HOH A 8 12.571 10.493 13.183 1.00 0.00 O +HETATM 23 H1 HOH A 8 12.849 10.053 13.987 1.00 0.00 H +HETATM 24 H2 HOH A 8 12.976 9.986 12.480 1.00 0.00 H +HETATM 25 O HOH A 9 6.592 1.144 2.761 1.00 0.00 O +HETATM 26 H1 HOH A 9 5.801 1.000 3.280 1.00 0.00 H +HETATM 27 H2 HOH A 9 6.454 1.995 2.346 1.00 0.00 H +HETATM 28 O HOH A 10 14.243 19.425 9.279 1.00 0.00 O +HETATM 29 H1 HOH A 10 13.950 18.942 10.052 1.00 0.00 H +HETATM 30 H2 HOH A 10 14.620 18.756 8.708 1.00 0.00 H +HETATM 31 O HOH A 11 12.327 2.107 19.954 1.00 0.00 O +HETATM 32 H1 HOH A 11 12.903 2.541 19.325 1.00 0.00 H +HETATM 33 H2 HOH A 11 11.621 2.735 20.105 1.00 0.00 H +HETATM 34 O HOH A 12 7.635 7.310 8.464 1.00 0.00 O +HETATM 35 H1 HOH A 12 7.341 8.193 8.687 1.00 0.00 H +HETATM 36 H2 HOH A 12 8.486 7.440 8.045 1.00 0.00 H +HETATM 37 O HOH A 13 4.394 13.889 6.781 1.00 0.00 O +HETATM 38 H1 HOH A 13 3.694 13.928 6.129 1.00 0.00 H +HETATM 39 H2 HOH A 13 5.026 14.548 6.492 1.00 0.00 H +HETATM 40 O HOH A 14 3.588 16.707 20.930 1.00 0.00 O +HETATM 41 H1 HOH A 14 3.859 16.712 21.848 1.00 0.00 H +HETATM 42 H2 HOH A 14 4.404 16.806 20.440 1.00 0.00 H +HETATM 43 O HOH A 15 4.785 15.993 16.720 1.00 0.00 O +HETATM 44 H1 HOH A 15 4.381 15.213 17.101 1.00 0.00 H +HETATM 45 H2 HOH A 15 4.137 16.317 16.095 1.00 0.00 H +HETATM 46 O HOH A 16 9.513 19.821 5.703 1.00 0.00 O +HETATM 47 H1 HOH A 16 9.835 19.638 4.820 1.00 0.00 H +HETATM 48 H2 HOH A 16 9.744 19.042 6.209 1.00 0.00 H +HETATM 49 O HOH A 17 10.844 17.785 1.309 1.00 0.00 O +HETATM 50 H1 HOH A 17 11.669 18.159 1.000 1.00 0.00 H +HETATM 51 H2 HOH A 17 10.818 18.000 2.242 1.00 0.00 H +HETATM 52 O HOH A 18 15.376 4.829 14.164 1.00 0.00 O +HETATM 53 H1 HOH A 18 14.854 5.294 14.819 1.00 0.00 H +HETATM 54 H2 HOH A 18 15.442 5.442 13.432 1.00 0.00 H +HETATM 55 O HOH A 19 18.069 17.084 13.593 1.00 0.00 O +HETATM 56 H1 HOH A 19 18.994 17.250 13.407 1.00 0.00 H +HETATM 57 H2 HOH A 19 17.598 17.625 12.960 1.00 0.00 H +HETATM 58 O HOH A 20 5.693 20.884 7.266 1.00 0.00 O +HETATM 59 H1 HOH A 20 6.385 21.016 7.915 1.00 0.00 H +HETATM 60 H2 HOH A 20 5.199 20.132 7.593 1.00 0.00 H +HETATM 61 O HOH A 21 21.943 9.765 6.049 1.00 0.00 O +HETATM 62 H1 HOH A 21 21.342 9.699 6.791 1.00 0.00 H +HETATM 63 H2 HOH A 21 21.394 9.612 5.281 1.00 0.00 H +HETATM 64 O HOH A 22 21.282 21.273 17.434 1.00 0.00 O +HETATM 65 H1 HOH A 22 20.808 21.991 17.014 1.00 0.00 H +HETATM 66 H2 HOH A 22 22.005 21.082 16.837 1.00 0.00 H +HETATM 67 O HOH A 23 12.079 10.155 5.386 1.00 0.00 O +HETATM 68 H1 HOH A 23 12.170 9.208 5.490 1.00 0.00 H +HETATM 69 H2 HOH A 23 11.277 10.264 4.874 1.00 0.00 H +HETATM 70 O HOH A 24 11.229 17.415 4.760 1.00 0.00 O +HETATM 71 H1 HOH A 24 10.577 17.162 5.413 1.00 0.00 H +HETATM 72 H2 HOH A 24 11.566 18.255 5.071 1.00 0.00 H +HETATM 73 O HOH A 25 6.102 9.301 11.714 1.00 0.00 O +HETATM 74 H1 HOH A 25 6.018 9.320 10.760 1.00 0.00 H +HETATM 75 H2 HOH A 25 6.039 8.373 11.941 1.00 0.00 H +HETATM 76 O HOH A 26 16.628 1.338 17.360 1.00 0.00 O +HETATM 77 H1 HOH A 26 16.714 2.291 17.338 1.00 0.00 H +HETATM 78 H2 HOH A 26 17.356 1.020 16.827 1.00 0.00 H +HETATM 79 O HOH A 27 12.658 22.000 14.400 1.00 0.00 O +HETATM 80 H1 HOH A 27 12.893 21.974 15.328 1.00 0.00 H +HETATM 81 H2 HOH A 27 12.508 21.085 14.165 1.00 0.00 H +HETATM 82 O HOH A 28 14.219 18.841 1.866 1.00 0.00 O +HETATM 83 H1 HOH A 28 13.474 19.153 2.380 1.00 0.00 H +HETATM 84 H2 HOH A 28 14.092 19.231 1.001 1.00 0.00 H +HETATM 85 O HOH A 29 19.225 17.894 21.300 1.00 0.00 O +HETATM 86 H1 HOH A 29 19.251 18.375 20.472 1.00 0.00 H +HETATM 87 H2 HOH A 29 19.444 18.548 21.963 1.00 0.00 H +HETATM 88 O HOH A 30 14.999 1.609 5.424 1.00 0.00 O +HETATM 89 H1 HOH A 30 15.739 1.001 5.427 1.00 0.00 H +HETATM 90 H2 HOH A 30 14.550 1.444 6.253 1.00 0.00 H +HETATM 91 O HOH A 31 15.035 3.705 20.920 1.00 0.00 O +HETATM 92 H1 HOH A 31 15.527 3.150 21.527 1.00 0.00 H +HETATM 93 H2 HOH A 31 14.143 3.361 20.951 1.00 0.00 H +HETATM 94 O HOH A 32 12.168 1.917 4.184 1.00 0.00 O +HETATM 95 H1 HOH A 32 11.308 2.239 3.916 1.00 0.00 H +HETATM 96 H2 HOH A 32 12.643 2.702 4.456 1.00 0.00 H +HETATM 97 O HOH A 33 18.510 12.575 4.468 1.00 0.00 O +HETATM 98 H1 HOH A 33 18.580 12.049 5.265 1.00 0.00 H +HETATM 99 H2 HOH A 33 19.410 12.837 4.275 1.00 0.00 H +HETATM 100 O HOH A 34 20.354 7.234 6.846 1.00 0.00 O +HETATM 101 H1 HOH A 34 21.132 7.458 6.336 1.00 0.00 H +HETATM 102 H2 HOH A 34 19.632 7.298 6.222 1.00 0.00 H +HETATM 103 O HOH A 35 15.552 7.902 14.551 1.00 0.00 O +HETATM 104 H1 HOH A 35 15.612 7.295 15.289 1.00 0.00 H +HETATM 105 H2 HOH A 35 16.373 8.393 14.578 1.00 0.00 H +HETATM 106 O HOH A 36 9.682 2.104 11.639 1.00 0.00 O +HETATM 107 H1 HOH A 36 8.806 2.484 11.567 1.00 0.00 H +HETATM 108 H2 HOH A 36 9.545 1.163 11.531 1.00 0.00 H +HETATM 109 O HOH A 37 3.686 6.953 2.319 1.00 0.00 O +HETATM 110 H1 HOH A 37 4.442 6.632 2.811 1.00 0.00 H +HETATM 111 H2 HOH A 37 2.944 6.818 2.908 1.00 0.00 H +HETATM 112 O HOH A 38 6.245 21.968 1.464 1.00 0.00 O +HETATM 113 H1 HOH A 38 5.306 21.985 1.653 1.00 0.00 H +HETATM 114 H2 HOH A 38 6.491 21.048 1.556 1.00 0.00 H +HETATM 115 O HOH A 39 21.331 17.087 8.818 1.00 0.00 O +HETATM 116 H1 HOH A 39 20.894 16.430 8.277 1.00 0.00 H +HETATM 117 H2 HOH A 39 20.921 17.915 8.567 1.00 0.00 H +HETATM 118 O HOH A 40 8.610 11.918 16.553 1.00 0.00 O +HETATM 119 H1 HOH A 40 8.935 12.381 17.325 1.00 0.00 H +HETATM 120 H2 HOH A 40 8.812 12.501 15.821 1.00 0.00 H +HETATM 121 O HOH A 41 14.047 17.731 15.774 1.00 0.00 O +HETATM 122 H1 HOH A 41 14.738 17.920 16.409 1.00 0.00 H +HETATM 123 H2 HOH A 41 13.240 17.746 16.289 1.00 0.00 H +HETATM 124 O HOH A 42 15.423 20.184 11.168 1.00 0.00 O +HETATM 125 H1 HOH A 42 15.611 19.406 11.692 1.00 0.00 H +HETATM 126 H2 HOH A 42 15.297 20.882 11.811 1.00 0.00 H +HETATM 127 O HOH A 43 14.133 3.508 10.354 1.00 0.00 O +HETATM 128 H1 HOH A 43 13.484 3.641 11.045 1.00 0.00 H +HETATM 129 H2 HOH A 43 14.873 4.059 10.610 1.00 0.00 H +HETATM 130 O HOH A 44 6.440 14.996 8.434 1.00 0.00 O +HETATM 131 H1 HOH A 44 6.884 15.782 8.753 1.00 0.00 H +HETATM 132 H2 HOH A 44 6.173 14.533 9.228 1.00 0.00 H +HETATM 133 O HOH A 45 20.473 19.156 17.926 1.00 0.00 O +HETATM 134 H1 HOH A 45 20.222 19.296 18.839 1.00 0.00 H +HETATM 135 H2 HOH A 45 19.814 19.633 17.421 1.00 0.00 H +HETATM 136 O HOH A 46 4.437 20.921 17.636 1.00 0.00 O +HETATM 137 H1 HOH A 46 4.251 21.047 18.567 1.00 0.00 H +HETATM 138 H2 HOH A 46 4.274 21.777 17.241 1.00 0.00 H +HETATM 139 O HOH A 47 2.742 19.882 15.715 1.00 0.00 O +HETATM 140 H1 HOH A 47 3.267 20.653 15.499 1.00 0.00 H +HETATM 141 H2 HOH A 47 1.853 20.116 15.449 1.00 0.00 H +HETATM 142 O HOH A 48 14.180 4.665 1.603 1.00 0.00 O +HETATM 143 H1 HOH A 48 13.761 4.044 1.007 1.00 0.00 H +HETATM 144 H2 HOH A 48 15.113 4.462 1.543 1.00 0.00 H +HETATM 145 O HOH A 49 15.174 1.695 19.133 1.00 0.00 O +HETATM 146 H1 HOH A 49 15.134 2.608 18.848 1.00 0.00 H +HETATM 147 H2 HOH A 49 14.612 1.226 18.516 1.00 0.00 H +HETATM 148 O HOH A 50 2.380 3.158 8.689 1.00 0.00 O +HETATM 149 H1 HOH A 50 3.036 2.466 8.609 1.00 0.00 H +HETATM 150 H2 HOH A 50 2.890 3.961 8.788 1.00 0.00 H +HETATM 151 O HOH A 51 8.190 12.568 1.936 1.00 0.00 O +HETATM 152 H1 HOH A 51 8.985 12.242 2.358 1.00 0.00 H +HETATM 153 H2 HOH A 51 7.917 13.308 2.479 1.00 0.00 H +HETATM 154 O HOH A 52 11.174 6.699 18.146 1.00 0.00 O +HETATM 155 H1 HOH A 52 11.032 7.003 17.249 1.00 0.00 H +HETATM 156 H2 HOH A 52 10.491 7.134 18.657 1.00 0.00 H +HETATM 157 O HOH A 53 14.866 13.448 20.760 1.00 0.00 O +HETATM 158 H1 HOH A 53 14.908 13.638 19.823 1.00 0.00 H +HETATM 159 H2 HOH A 53 15.376 14.147 21.170 1.00 0.00 H +HETATM 160 O HOH A 54 7.037 6.865 14.955 1.00 0.00 O +HETATM 161 H1 HOH A 54 7.495 6.815 15.794 1.00 0.00 H +HETATM 162 H2 HOH A 54 6.226 6.377 15.095 1.00 0.00 H +HETATM 163 O HOH A 55 9.888 20.040 18.968 1.00 0.00 O +HETATM 164 H1 HOH A 55 9.964 19.250 19.503 1.00 0.00 H +HETATM 165 H2 HOH A 55 9.735 19.715 18.081 1.00 0.00 H +HETATM 166 O HOH A 56 6.370 10.398 13.604 1.00 0.00 O +HETATM 167 H1 HOH A 56 6.264 9.503 13.928 1.00 0.00 H +HETATM 168 H2 HOH A 56 6.019 10.949 14.303 1.00 0.00 H +HETATM 169 O HOH A 57 18.378 10.460 18.490 1.00 0.00 O +HETATM 170 H1 HOH A 57 18.878 10.888 19.185 1.00 0.00 H +HETATM 171 H2 HOH A 57 18.695 9.557 18.487 1.00 0.00 H +HETATM 172 O HOH A 58 13.372 5.441 17.482 1.00 0.00 O +HETATM 173 H1 HOH A 58 13.334 4.596 17.930 1.00 0.00 H +HETATM 174 H2 HOH A 58 12.601 5.446 16.915 1.00 0.00 H +HETATM 175 O HOH A 59 10.092 10.779 13.182 1.00 0.00 O +HETATM 176 H1 HOH A 59 9.220 11.164 13.089 1.00 0.00 H +HETATM 177 H2 HOH A 59 10.640 11.500 13.490 1.00 0.00 H +HETATM 178 O HOH A 60 3.149 13.349 17.381 1.00 0.00 O +HETATM 179 H1 HOH A 60 3.986 13.068 17.749 1.00 0.00 H +HETATM 180 H2 HOH A 60 2.853 12.601 16.862 1.00 0.00 H +HETATM 181 O HOH A 61 3.576 17.757 7.949 1.00 0.00 O +HETATM 182 H1 HOH A 61 3.464 16.887 7.565 1.00 0.00 H +HETATM 183 H2 HOH A 61 3.532 18.355 7.203 1.00 0.00 H +HETATM 184 O HOH A 62 19.890 12.089 7.668 1.00 0.00 O +HETATM 185 H1 HOH A 62 18.964 11.850 7.623 1.00 0.00 H +HETATM 186 H2 HOH A 62 20.277 11.696 6.885 1.00 0.00 H +HETATM 187 O HOH A 63 8.642 21.998 3.567 1.00 0.00 O +HETATM 188 H1 HOH A 63 8.494 21.692 4.462 1.00 0.00 H +HETATM 189 H2 HOH A 63 8.075 21.446 3.028 1.00 0.00 H +HETATM 190 O HOH A 64 5.006 4.400 15.570 1.00 0.00 O +HETATM 191 H1 HOH A 64 4.578 3.549 15.667 1.00 0.00 H +HETATM 192 H2 HOH A 64 5.568 4.299 14.801 1.00 0.00 H +HETATM 193 O HOH A 65 20.282 3.092 7.696 1.00 0.00 O +HETATM 194 H1 HOH A 65 21.035 2.515 7.568 1.00 0.00 H +HETATM 195 H2 HOH A 65 19.709 2.610 8.292 1.00 0.00 H +HETATM 196 O HOH A 66 11.976 13.763 5.301 1.00 0.00 O +HETATM 197 H1 HOH A 66 11.952 14.072 6.207 1.00 0.00 H +HETATM 198 H2 HOH A 66 12.172 14.546 4.786 1.00 0.00 H +HETATM 199 O HOH A 67 9.082 4.898 7.316 1.00 0.00 O +HETATM 200 H1 HOH A 67 8.498 5.197 8.013 1.00 0.00 H +HETATM 201 H2 HOH A 67 8.651 4.121 6.959 1.00 0.00 H +HETATM 202 O HOH A 68 10.328 15.837 3.289 1.00 0.00 O +HETATM 203 H1 HOH A 68 9.899 15.259 3.920 1.00 0.00 H +HETATM 204 H2 HOH A 68 10.108 15.464 2.435 1.00 0.00 H +HETATM 205 O HOH A 69 5.912 16.398 2.646 1.00 0.00 O +HETATM 206 H1 HOH A 69 5.992 15.728 1.967 1.00 0.00 H +HETATM 207 H2 HOH A 69 5.808 17.217 2.162 1.00 0.00 H +HETATM 208 O HOH A 70 1.008 9.017 18.942 1.00 0.00 O +HETATM 209 H1 HOH A 70 1.363 9.228 18.078 1.00 0.00 H +HETATM 210 H2 HOH A 70 1.028 9.847 19.419 1.00 0.00 H +HETATM 211 O HOH A 71 12.037 13.333 11.838 1.00 0.00 O +HETATM 212 H1 HOH A 71 12.266 12.929 12.675 1.00 0.00 H +HETATM 213 H2 HOH A 71 12.828 13.802 11.574 1.00 0.00 H +HETATM 214 O HOH A 72 20.917 5.720 14.335 1.00 0.00 O +HETATM 215 H1 HOH A 72 20.495 6.356 13.757 1.00 0.00 H +HETATM 216 H2 HOH A 72 20.722 4.871 13.940 1.00 0.00 H +HETATM 217 O HOH A 73 8.971 3.223 19.320 1.00 0.00 O +HETATM 218 H1 HOH A 73 9.184 3.898 18.675 1.00 0.00 H +HETATM 219 H2 HOH A 73 9.543 3.414 20.064 1.00 0.00 H +HETATM 220 O HOH A 74 9.062 21.127 8.190 1.00 0.00 O +HETATM 221 H1 HOH A 74 8.539 20.701 7.510 1.00 0.00 H +HETATM 222 H2 HOH A 74 8.677 22.000 8.275 1.00 0.00 H +HETATM 223 O HOH A 75 17.862 5.725 5.828 1.00 0.00 O +HETATM 224 H1 HOH A 75 18.109 5.174 6.570 1.00 0.00 H +HETATM 225 H2 HOH A 75 17.477 5.118 5.197 1.00 0.00 H +HETATM 226 O HOH A 76 18.979 6.678 4.201 1.00 0.00 O +HETATM 227 H1 HOH A 76 18.075 6.802 3.911 1.00 0.00 H +HETATM 228 H2 HOH A 76 19.509 6.915 3.440 1.00 0.00 H +HETATM 229 O HOH A 77 12.689 4.453 7.777 1.00 0.00 O +HETATM 230 H1 HOH A 77 13.426 3.849 7.683 1.00 0.00 H +HETATM 231 H2 HOH A 77 13.046 5.305 7.527 1.00 0.00 H +HETATM 232 O HOH A 78 17.131 17.611 10.058 1.00 0.00 O +HETATM 233 H1 HOH A 78 17.147 16.978 9.340 1.00 0.00 H +HETATM 234 H2 HOH A 78 16.223 17.910 10.094 1.00 0.00 H +HETATM 235 O HOH A 79 2.123 5.315 17.821 1.00 0.00 O +HETATM 236 H1 HOH A 79 2.023 6.124 18.323 1.00 0.00 H +HETATM 237 H2 HOH A 79 2.248 4.636 18.484 1.00 0.00 H +HETATM 238 O HOH A 80 3.910 20.710 10.586 1.00 0.00 O +HETATM 239 H1 HOH A 80 3.688 20.229 9.789 1.00 0.00 H +HETATM 240 H2 HOH A 80 3.800 21.630 10.349 1.00 0.00 H +HETATM 241 O HOH A 81 15.237 9.832 2.767 1.00 0.00 O +HETATM 242 H1 HOH A 81 15.537 9.599 1.889 1.00 0.00 H +HETATM 243 H2 HOH A 81 16.039 9.889 3.287 1.00 0.00 H +HETATM 244 O HOH A 82 13.266 16.140 6.662 1.00 0.00 O +HETATM 245 H1 HOH A 82 13.142 16.717 5.909 1.00 0.00 H +HETATM 246 H2 HOH A 82 12.486 16.275 7.200 1.00 0.00 H +HETATM 247 O HOH A 83 16.092 15.363 12.866 1.00 0.00 O +HETATM 248 H1 HOH A 83 16.256 14.854 12.072 1.00 0.00 H +HETATM 249 H2 HOH A 83 16.870 15.226 13.405 1.00 0.00 H +HETATM 250 O HOH A 84 10.470 14.102 13.192 1.00 0.00 O +HETATM 251 H1 HOH A 84 10.036 13.618 13.895 1.00 0.00 H +HETATM 252 H2 HOH A 84 10.198 15.010 13.324 1.00 0.00 H +HETATM 253 O HOH A 85 4.142 6.431 13.037 1.00 0.00 O +HETATM 254 H1 HOH A 85 4.523 6.157 12.202 1.00 0.00 H +HETATM 255 H2 HOH A 85 4.843 6.916 13.472 1.00 0.00 H +HETATM 256 O HOH A 86 5.260 1.248 18.245 1.00 0.00 O +HETATM 257 H1 HOH A 86 4.431 1.478 18.665 1.00 0.00 H +HETATM 258 H2 HOH A 86 5.016 1.001 17.354 1.00 0.00 H +HETATM 259 O HOH A 87 8.224 17.808 2.922 1.00 0.00 O +HETATM 260 H1 HOH A 87 8.244 16.914 2.582 1.00 0.00 H +HETATM 261 H2 HOH A 87 8.999 17.870 3.479 1.00 0.00 H +HETATM 262 O HOH A 88 15.182 13.501 1.978 1.00 0.00 O +HETATM 263 H1 HOH A 88 14.721 12.693 1.750 1.00 0.00 H +HETATM 264 H2 HOH A 88 14.734 13.818 2.762 1.00 0.00 H +HETATM 265 O HOH A 89 19.630 20.823 1.434 1.00 0.00 O +HETATM 266 H1 HOH A 89 19.268 19.946 1.561 1.00 0.00 H +HETATM 267 H2 HOH A 89 19.288 21.332 2.169 1.00 0.00 H +HETATM 268 O HOH A 90 20.758 6.938 17.982 1.00 0.00 O +HETATM 269 H1 HOH A 90 20.147 7.572 17.607 1.00 0.00 H +HETATM 270 H2 HOH A 90 20.209 6.194 18.229 1.00 0.00 H +HETATM 271 O HOH A 91 17.215 11.792 13.328 1.00 0.00 O +HETATM 272 H1 HOH A 91 17.940 11.609 12.731 1.00 0.00 H +HETATM 273 H2 HOH A 91 16.768 12.543 12.939 1.00 0.00 H +HETATM 274 O HOH A 92 6.537 11.426 8.821 1.00 0.00 O +HETATM 275 H1 HOH A 92 7.224 11.302 9.476 1.00 0.00 H +HETATM 276 H2 HOH A 92 6.087 10.582 8.783 1.00 0.00 H +HETATM 277 O HOH A 93 11.243 7.284 8.483 1.00 0.00 O +HETATM 278 H1 HOH A 93 11.106 6.962 7.592 1.00 0.00 H +HETATM 279 H2 HOH A 93 12.185 7.440 8.539 1.00 0.00 H +HETATM 280 O HOH A 94 15.569 16.005 18.987 1.00 0.00 O +HETATM 281 H1 HOH A 94 15.508 16.369 18.104 1.00 0.00 H +HETATM 282 H2 HOH A 94 14.664 15.977 19.297 1.00 0.00 H +HETATM 283 O HOH A 95 20.760 2.287 18.627 1.00 0.00 O +HETATM 284 H1 HOH A 95 20.315 3.030 18.220 1.00 0.00 H +HETATM 285 H2 HOH A 95 20.556 2.367 19.559 1.00 0.00 H +HETATM 286 O HOH A 96 17.170 19.035 15.139 1.00 0.00 O +HETATM 287 H1 HOH A 96 16.804 19.201 16.008 1.00 0.00 H +HETATM 288 H2 HOH A 96 16.979 19.830 14.643 1.00 0.00 H +HETATM 289 O HOH A 97 9.272 15.722 7.419 1.00 0.00 O +HETATM 290 H1 HOH A 97 8.482 16.128 7.064 1.00 0.00 H +HETATM 291 H2 HOH A 97 9.712 16.429 7.892 1.00 0.00 H +HETATM 292 O HOH A 98 18.201 8.060 16.683 1.00 0.00 O +HETATM 293 H1 HOH A 98 17.815 7.789 17.516 1.00 0.00 H +HETATM 294 H2 HOH A 98 17.772 7.509 16.029 1.00 0.00 H +HETATM 295 O HOH A 99 5.648 18.258 9.923 1.00 0.00 O +HETATM 296 H1 HOH A 99 6.005 18.498 9.068 1.00 0.00 H +HETATM 297 H2 HOH A 99 6.315 17.692 10.311 1.00 0.00 H +HETATM 298 O HOH A 100 16.221 21.185 21.497 1.00 0.00 O +HETATM 299 H1 HOH A 100 16.596 21.362 20.634 1.00 0.00 H +HETATM 300 H2 HOH A 100 16.381 21.985 21.997 1.00 0.00 H +HETATM 301 O HOH A 101 11.612 8.509 3.509 1.00 0.00 O +HETATM 302 H1 HOH A 101 11.386 8.603 2.584 1.00 0.00 H +HETATM 303 H2 HOH A 101 11.177 7.701 3.781 1.00 0.00 H +HETATM 304 O HOH A 102 21.449 3.370 10.872 1.00 0.00 O +HETATM 305 H1 HOH A 102 20.740 3.330 10.231 1.00 0.00 H +HETATM 306 H2 HOH A 102 21.142 2.835 11.604 1.00 0.00 H +HETATM 307 O HOH A 103 8.526 19.845 1.005 1.00 0.00 O +HETATM 308 H1 HOH A 103 7.860 19.208 1.264 1.00 0.00 H +HETATM 309 H2 HOH A 103 9.343 19.493 1.357 1.00 0.00 H +HETATM 310 O HOH A 104 1.045 21.546 12.589 1.00 0.00 O +HETATM 311 H1 HOH A 104 1.011 21.879 13.486 1.00 0.00 H +HETATM 312 H2 HOH A 104 1.787 22.001 12.193 1.00 0.00 H +HETATM 313 O HOH A 105 19.796 2.005 5.558 1.00 0.00 O +HETATM 314 H1 HOH A 105 18.983 2.437 5.821 1.00 0.00 H +HETATM 315 H2 HOH A 105 19.680 1.094 5.825 1.00 0.00 H +HETATM 316 O HOH A 106 12.584 11.573 17.839 1.00 0.00 O +HETATM 317 H1 HOH A 106 12.606 11.962 16.965 1.00 0.00 H +HETATM 318 H2 HOH A 106 11.894 12.052 18.297 1.00 0.00 H +HETATM 319 O HOH A 107 3.959 7.314 4.937 1.00 0.00 O +HETATM 320 H1 HOH A 107 3.653 6.444 5.191 1.00 0.00 H +HETATM 321 H2 HOH A 107 4.295 7.696 5.748 1.00 0.00 H +HETATM 322 O HOH A 108 15.449 21.405 14.572 1.00 0.00 O +HETATM 323 H1 HOH A 108 15.411 21.815 15.436 1.00 0.00 H +HETATM 324 H2 HOH A 108 16.016 21.980 14.059 1.00 0.00 H +HETATM 325 O HOH A 109 13.933 10.776 21.050 1.00 0.00 O +HETATM 326 H1 HOH A 109 13.857 9.934 20.601 1.00 0.00 H +HETATM 327 H2 HOH A 109 14.017 11.416 20.344 1.00 0.00 H +HETATM 328 O HOH A 110 9.322 3.184 1.385 1.00 0.00 O +HETATM 329 H1 HOH A 110 8.900 2.410 1.757 1.00 0.00 H +HETATM 330 H2 HOH A 110 8.605 3.687 1.000 1.00 0.00 H +HETATM 331 O HOH A 111 18.919 13.520 10.599 1.00 0.00 O +HETATM 332 H1 HOH A 111 18.790 13.445 11.544 1.00 0.00 H +HETATM 333 H2 HOH A 111 18.100 13.895 10.274 1.00 0.00 H +HETATM 334 O HOH A 112 15.463 10.033 18.019 1.00 0.00 O +HETATM 335 H1 HOH A 112 16.279 9.733 17.618 1.00 0.00 H +HETATM 336 H2 HOH A 112 15.666 10.104 18.951 1.00 0.00 H +HETATM 337 O HOH A 113 8.033 13.273 10.984 1.00 0.00 O +HETATM 338 H1 HOH A 113 8.369 13.868 11.654 1.00 0.00 H +HETATM 339 H2 HOH A 113 7.157 13.038 11.290 1.00 0.00 H +HETATM 340 O HOH A 114 17.261 1.887 14.807 1.00 0.00 O +HETATM 341 H1 HOH A 114 16.867 1.221 14.243 1.00 0.00 H +HETATM 342 H2 HOH A 114 18.031 2.186 14.323 1.00 0.00 H +HETATM 343 O HOH A 115 12.194 1.583 12.833 1.00 0.00 O +HETATM 344 H1 HOH A 115 11.725 2.405 12.692 1.00 0.00 H +HETATM 345 H2 HOH A 115 11.544 1.000 13.226 1.00 0.00 H +HETATM 346 O HOH A 116 9.800 6.068 21.023 1.00 0.00 O +HETATM 347 H1 HOH A 116 9.496 5.460 21.696 1.00 0.00 H +HETATM 348 H2 HOH A 116 9.694 5.588 20.202 1.00 0.00 H +HETATM 349 O HOH A 117 19.290 5.868 11.055 1.00 0.00 O +HETATM 350 H1 HOH A 117 19.268 6.414 11.840 1.00 0.00 H +HETATM 351 H2 HOH A 117 20.029 5.276 11.195 1.00 0.00 H +HETATM 352 O HOH A 118 7.059 5.178 18.542 1.00 0.00 O +HETATM 353 H1 HOH A 118 7.758 5.631 19.015 1.00 0.00 H +HETATM 354 H2 HOH A 118 7.040 4.301 18.924 1.00 0.00 H +HETATM 355 O HOH A 119 2.469 14.645 20.273 1.00 0.00 O +HETATM 356 H1 HOH A 119 1.728 14.305 19.771 1.00 0.00 H +HETATM 357 H2 HOH A 119 3.151 13.981 20.172 1.00 0.00 H +HETATM 358 O HOH A 120 1.796 19.574 8.027 1.00 0.00 O +HETATM 359 H1 HOH A 120 1.592 19.641 8.960 1.00 0.00 H +HETATM 360 H2 HOH A 120 2.453 20.253 7.875 1.00 0.00 H +HETATM 361 O HOH A 121 17.057 3.791 11.413 1.00 0.00 O +HETATM 362 H1 HOH A 121 17.943 4.148 11.354 1.00 0.00 H +HETATM 363 H2 HOH A 121 17.178 2.843 11.367 1.00 0.00 H +HETATM 364 O HOH A 122 10.485 21.200 16.360 1.00 0.00 O +HETATM 365 H1 HOH A 122 10.993 20.531 15.902 1.00 0.00 H +HETATM 366 H2 HOH A 122 9.886 21.542 15.696 1.00 0.00 H +HETATM 367 O HOH A 123 20.707 7.343 9.503 1.00 0.00 O +HETATM 368 H1 HOH A 123 20.902 7.227 10.433 1.00 0.00 H +HETATM 369 H2 HOH A 123 21.565 7.416 9.086 1.00 0.00 H +HETATM 370 O HOH A 124 19.722 10.425 16.743 1.00 0.00 O +HETATM 371 H1 HOH A 124 19.731 9.688 16.133 1.00 0.00 H +HETATM 372 H2 HOH A 124 19.960 11.182 16.209 1.00 0.00 H +HETATM 373 O HOH A 125 1.303 18.128 18.774 1.00 0.00 O +HETATM 374 H1 HOH A 125 1.002 18.386 19.646 1.00 0.00 H +HETATM 375 H2 HOH A 125 1.351 18.950 18.285 1.00 0.00 H +HETATM 376 O HOH A 126 14.527 4.953 5.790 1.00 0.00 O +HETATM 377 H1 HOH A 126 15.141 4.982 5.056 1.00 0.00 H +HETATM 378 H2 HOH A 126 15.038 5.253 6.541 1.00 0.00 H +HETATM 379 O HOH A 127 11.836 18.255 20.159 1.00 0.00 O +HETATM 380 H1 HOH A 127 11.788 17.403 19.727 1.00 0.00 H +HETATM 381 H2 HOH A 127 11.714 18.060 21.088 1.00 0.00 H +HETATM 382 O HOH A 128 18.563 5.449 14.371 1.00 0.00 O +HETATM 383 H1 HOH A 128 18.867 5.779 15.217 1.00 0.00 H +HETATM 384 H2 HOH A 128 18.066 4.660 14.586 1.00 0.00 H +HETATM 385 O HOH A 129 3.760 21.244 12.880 1.00 0.00 O +HETATM 386 H1 HOH A 129 4.490 21.303 13.496 1.00 0.00 H +HETATM 387 H2 HOH A 129 3.133 20.661 13.307 1.00 0.00 H +HETATM 388 O HOH A 130 13.947 20.466 16.461 1.00 0.00 O +HETATM 389 H1 HOH A 130 14.007 20.074 15.590 1.00 0.00 H +HETATM 390 H2 HOH A 130 14.666 20.069 16.952 1.00 0.00 H +HETATM 391 O HOH A 131 3.790 8.884 19.148 1.00 0.00 O +HETATM 392 H1 HOH A 131 3.256 8.285 18.626 1.00 0.00 H +HETATM 393 H2 HOH A 131 4.613 8.415 19.284 1.00 0.00 H +HETATM 394 O HOH A 132 19.928 9.639 1.814 1.00 0.00 O +HETATM 395 H1 HOH A 132 20.067 9.439 2.739 1.00 0.00 H +HETATM 396 H2 HOH A 132 19.347 8.944 1.504 1.00 0.00 H +HETATM 397 O HOH A 133 21.237 1.857 15.524 1.00 0.00 O +HETATM 398 H1 HOH A 133 21.598 2.465 16.169 1.00 0.00 H +HETATM 399 H2 HOH A 133 21.577 1.001 15.785 1.00 0.00 H +HETATM 400 O HOH A 134 17.621 11.810 20.791 1.00 0.00 O +HETATM 401 H1 HOH A 134 16.811 11.413 21.114 1.00 0.00 H +HETATM 402 H2 HOH A 134 17.382 12.716 20.596 1.00 0.00 H +HETATM 403 O HOH A 135 18.425 21.994 19.589 1.00 0.00 O +HETATM 404 H1 HOH A 135 18.879 21.186 19.830 1.00 0.00 H +HETATM 405 H2 HOH A 135 18.243 21.896 18.655 1.00 0.00 H +HETATM 406 O HOH A 136 17.198 3.668 3.476 1.00 0.00 O +HETATM 407 H1 HOH A 136 17.176 3.075 4.228 1.00 0.00 H +HETATM 408 H2 HOH A 136 16.441 3.413 2.949 1.00 0.00 H +HETATM 409 O HOH A 137 16.116 10.231 12.004 1.00 0.00 O +HETATM 410 H1 HOH A 137 16.271 9.813 12.851 1.00 0.00 H +HETATM 411 H2 HOH A 137 16.532 9.647 11.370 1.00 0.00 H +HETATM 412 O HOH A 138 12.576 7.775 14.829 1.00 0.00 O +HETATM 413 H1 HOH A 138 12.698 7.411 13.952 1.00 0.00 H +HETATM 414 H2 HOH A 138 13.344 7.481 15.319 1.00 0.00 H +HETATM 415 O HOH A 139 17.106 1.839 8.114 1.00 0.00 O +HETATM 416 H1 HOH A 139 17.111 2.616 7.555 1.00 0.00 H +HETATM 417 H2 HOH A 139 16.404 1.294 7.759 1.00 0.00 H +HETATM 418 O HOH A 140 14.459 10.911 15.377 1.00 0.00 O +HETATM 419 H1 HOH A 140 15.398 11.010 15.222 1.00 0.00 H +HETATM 420 H2 HOH A 140 14.401 10.301 16.112 1.00 0.00 H +HETATM 421 O HOH A 141 11.392 3.997 14.240 1.00 0.00 O +HETATM 422 H1 HOH A 141 10.803 4.538 13.713 1.00 0.00 H +HETATM 423 H2 HOH A 141 11.611 4.547 14.993 1.00 0.00 H +HETATM 424 O HOH A 142 8.786 5.033 14.596 1.00 0.00 O +HETATM 425 H1 HOH A 142 9.350 5.739 14.912 1.00 0.00 H +HETATM 426 H2 HOH A 142 8.802 4.383 15.298 1.00 0.00 H +HETATM 427 O HOH A 143 3.918 5.636 20.598 1.00 0.00 O +HETATM 428 H1 HOH A 143 3.722 5.800 21.520 1.00 0.00 H +HETATM 429 H2 HOH A 143 4.073 6.504 20.227 1.00 0.00 H +HETATM 430 O HOH A 144 9.062 8.173 11.515 1.00 0.00 O +HETATM 431 H1 HOH A 144 8.420 7.656 12.003 1.00 0.00 H +HETATM 432 H2 HOH A 144 8.582 8.490 10.750 1.00 0.00 H +HETATM 433 O HOH A 145 4.808 3.956 18.192 1.00 0.00 O +HETATM 434 H1 HOH A 145 4.484 4.124 19.077 1.00 0.00 H +HETATM 435 H2 HOH A 145 4.623 4.763 17.712 1.00 0.00 H +HETATM 436 O HOH A 146 19.474 15.563 5.007 1.00 0.00 O +HETATM 437 H1 HOH A 146 18.857 16.155 5.438 1.00 0.00 H +HETATM 438 H2 HOH A 146 19.589 14.844 5.628 1.00 0.00 H +HETATM 439 O HOH A 147 6.235 13.999 17.269 1.00 0.00 O +HETATM 440 H1 HOH A 147 6.142 14.217 18.196 1.00 0.00 H +HETATM 441 H2 HOH A 147 7.003 13.429 17.233 1.00 0.00 H +HETATM 442 O HOH A 148 12.163 5.412 11.646 1.00 0.00 O +HETATM 443 H1 HOH A 148 11.531 4.693 11.643 1.00 0.00 H +HETATM 444 H2 HOH A 148 11.799 6.052 11.034 1.00 0.00 H +HETATM 445 O HOH A 149 6.192 9.047 21.050 1.00 0.00 O +HETATM 446 H1 HOH A 149 6.254 8.837 21.982 1.00 0.00 H +HETATM 447 H2 HOH A 149 5.400 9.580 20.977 1.00 0.00 H +HETATM 448 O HOH A 150 4.982 3.175 12.016 1.00 0.00 O +HETATM 449 H1 HOH A 150 5.102 3.642 11.189 1.00 0.00 H +HETATM 450 H2 HOH A 150 4.569 3.815 12.595 1.00 0.00 H +HETATM 451 O HOH A 151 11.344 8.862 11.345 1.00 0.00 O +HETATM 452 H1 HOH A 151 11.155 8.377 12.149 1.00 0.00 H +HETATM 453 H2 HOH A 151 11.228 8.220 10.645 1.00 0.00 H +HETATM 454 O HOH A 152 5.196 6.961 10.096 1.00 0.00 O +HETATM 455 H1 HOH A 152 5.241 7.707 9.498 1.00 0.00 H +HETATM 456 H2 HOH A 152 6.077 6.585 10.076 1.00 0.00 H +HETATM 457 O HOH A 153 6.395 4.450 1.854 1.00 0.00 O +HETATM 458 H1 HOH A 153 6.046 5.150 2.405 1.00 0.00 H +HETATM 459 H2 HOH A 153 6.758 4.902 1.092 1.00 0.00 H +HETATM 460 O HOH A 154 1.925 6.429 10.476 1.00 0.00 O +HETATM 461 H1 HOH A 154 1.400 6.584 11.261 1.00 0.00 H +HETATM 462 H2 HOH A 154 1.394 5.834 9.947 1.00 0.00 H +HETATM 463 O HOH A 155 11.938 12.846 2.456 1.00 0.00 O +HETATM 464 H1 HOH A 155 11.642 12.433 1.645 1.00 0.00 H +HETATM 465 H2 HOH A 155 12.294 12.125 2.975 1.00 0.00 H +HETATM 466 O HOH A 156 15.117 1.703 11.836 1.00 0.00 O +HETATM 467 H1 HOH A 156 14.470 1.005 11.733 1.00 0.00 H +HETATM 468 H2 HOH A 156 15.891 1.263 12.186 1.00 0.00 H +HETATM 469 O HOH A 157 5.497 17.784 12.551 1.00 0.00 O +HETATM 470 H1 HOH A 157 4.623 18.169 12.616 1.00 0.00 H +HETATM 471 H2 HOH A 157 6.078 18.443 12.928 1.00 0.00 H +HETATM 472 O HOH A 158 17.996 5.612 1.041 1.00 0.00 O +HETATM 473 H1 HOH A 158 18.272 5.999 1.871 1.00 0.00 H +HETATM 474 H2 HOH A 158 18.229 4.687 1.119 1.00 0.00 H +HETATM 475 O HOH A 159 20.216 14.329 18.723 1.00 0.00 O +HETATM 476 H1 HOH A 159 20.916 14.388 18.073 1.00 0.00 H +HETATM 477 H2 HOH A 159 20.492 14.918 19.425 1.00 0.00 H +HETATM 478 O HOH A 160 21.778 5.474 1.833 1.00 0.00 O +HETATM 479 H1 HOH A 160 20.836 5.572 1.695 1.00 0.00 H +HETATM 480 H2 HOH A 160 22.000 6.156 2.466 1.00 0.00 H +HETATM 481 O HOH A 161 5.864 9.520 4.730 1.00 0.00 O +HETATM 482 H1 HOH A 161 5.255 10.239 4.564 1.00 0.00 H +HETATM 483 H2 HOH A 161 6.687 9.806 4.334 1.00 0.00 H +HETATM 484 O HOH A 162 21.317 19.997 5.548 1.00 0.00 O +HETATM 485 H1 HOH A 162 21.641 19.934 4.649 1.00 0.00 H +HETATM 486 H2 HOH A 162 21.820 19.342 6.031 1.00 0.00 H +HETATM 487 O HOH A 163 19.907 19.954 13.314 1.00 0.00 O +HETATM 488 H1 HOH A 163 20.113 19.388 14.058 1.00 0.00 H +HETATM 489 H2 HOH A 163 20.652 20.551 13.254 1.00 0.00 H +HETATM 490 O HOH A 164 18.667 13.356 14.941 1.00 0.00 O +HETATM 491 H1 HOH A 164 18.686 13.990 14.224 1.00 0.00 H +HETATM 492 H2 HOH A 164 19.439 12.808 14.796 1.00 0.00 H +HETATM 493 O HOH A 165 21.518 17.759 15.267 1.00 0.00 O +HETATM 494 H1 HOH A 165 22.000 18.453 14.816 1.00 0.00 H +HETATM 495 H2 HOH A 165 20.849 18.221 15.771 1.00 0.00 H +HETATM 496 O HOH A 166 12.590 11.427 10.414 1.00 0.00 O +HETATM 497 H1 HOH A 166 13.085 11.366 9.597 1.00 0.00 H +HETATM 498 H2 HOH A 166 13.243 11.657 11.074 1.00 0.00 H +HETATM 499 O HOH A 167 2.078 4.804 6.165 1.00 0.00 O +HETATM 500 H1 HOH A 167 1.734 4.826 7.058 1.00 0.00 H +HETATM 501 H2 HOH A 167 1.553 5.448 5.690 1.00 0.00 H +HETATM 502 O HOH A 168 10.019 12.242 8.141 1.00 0.00 O +HETATM 503 H1 HOH A 168 10.450 11.696 7.483 1.00 0.00 H +HETATM 504 H2 HOH A 168 10.544 13.042 8.168 1.00 0.00 H +HETATM 505 O HOH A 169 12.377 1.330 16.282 1.00 0.00 O +HETATM 506 H1 HOH A 169 13.100 1.152 16.884 1.00 0.00 H +HETATM 507 H2 HOH A 169 11.590 1.116 16.782 1.00 0.00 H +HETATM 508 O HOH A 170 16.297 21.194 17.905 1.00 0.00 O +HETATM 509 H1 HOH A 170 16.324 22.024 17.429 1.00 0.00 H +HETATM 510 H2 HOH A 170 15.624 21.323 18.573 1.00 0.00 H +HETATM 511 O HOH A 171 16.878 5.874 19.008 1.00 0.00 O +HETATM 512 H1 HOH A 171 16.051 6.250 19.310 1.00 0.00 H +HETATM 513 H2 HOH A 171 17.551 6.410 19.426 1.00 0.00 H +HETATM 514 O HOH A 172 9.195 16.371 17.513 1.00 0.00 O +HETATM 515 H1 HOH A 172 8.498 16.805 18.005 1.00 0.00 H +HETATM 516 H2 HOH A 172 9.985 16.864 17.730 1.00 0.00 H +HETATM 517 O HOH A 173 6.593 20.698 10.847 1.00 0.00 O +HETATM 518 H1 HOH A 173 6.053 20.912 10.086 1.00 0.00 H +HETATM 519 H2 HOH A 173 6.042 20.121 11.377 1.00 0.00 H +HETATM 520 O HOH A 174 9.903 2.405 16.204 1.00 0.00 O +HETATM 521 H1 HOH A 174 10.485 2.544 15.457 1.00 0.00 H +HETATM 522 H2 HOH A 174 9.987 3.203 16.725 1.00 0.00 H +HETATM 523 O HOH A 175 12.731 1.749 10.208 1.00 0.00 O +HETATM 524 H1 HOH A 175 12.411 1.007 10.721 1.00 0.00 H +HETATM 525 H2 HOH A 175 12.863 1.393 9.330 1.00 0.00 H +HETATM 526 O HOH A 176 16.938 8.968 20.682 1.00 0.00 O +HETATM 527 H1 HOH A 176 16.832 8.440 19.890 1.00 0.00 H +HETATM 528 H2 HOH A 176 17.382 8.387 21.299 1.00 0.00 H +HETATM 529 O HOH A 177 8.356 0.995 20.657 1.00 0.00 O +HETATM 530 H1 HOH A 177 7.830 1.789 20.565 1.00 0.00 H +HETATM 531 H2 HOH A 177 9.212 1.233 20.302 1.00 0.00 H +HETATM 532 O HOH A 178 13.102 7.391 1.225 1.00 0.00 O +HETATM 533 H1 HOH A 178 13.974 7.426 1.618 1.00 0.00 H +HETATM 534 H2 HOH A 178 12.894 8.302 1.020 1.00 0.00 H +HETATM 535 O HOH A 179 14.231 14.615 15.909 1.00 0.00 O +HETATM 536 H1 HOH A 179 14.446 14.348 15.015 1.00 0.00 H +HETATM 537 H2 HOH A 179 14.503 13.875 16.452 1.00 0.00 H +HETATM 538 O HOH A 180 11.802 4.919 20.495 1.00 0.00 O +HETATM 539 H1 HOH A 180 12.420 4.730 21.201 1.00 0.00 H +HETATM 540 H2 HOH A 180 12.344 5.283 19.795 1.00 0.00 H +HETATM 541 O HOH A 181 7.247 18.123 16.634 1.00 0.00 O +HETATM 542 H1 HOH A 181 6.834 18.548 15.882 1.00 0.00 H +HETATM 543 H2 HOH A 181 6.517 17.749 17.127 1.00 0.00 H +HETATM 544 O HOH A 182 7.751 17.027 13.470 1.00 0.00 O +HETATM 545 H1 HOH A 182 8.634 16.851 13.797 1.00 0.00 H +HETATM 546 H2 HOH A 182 7.659 16.453 12.710 1.00 0.00 H +HETATM 547 O HOH A 183 17.375 2.714 19.736 1.00 0.00 O +HETATM 548 H1 HOH A 183 17.985 2.875 19.016 1.00 0.00 H +HETATM 549 H2 HOH A 183 17.013 3.576 19.941 1.00 0.00 H +HETATM 550 O HOH A 184 7.311 4.335 13.081 1.00 0.00 O +HETATM 551 H1 HOH A 184 6.967 3.472 13.313 1.00 0.00 H +HETATM 552 H2 HOH A 184 6.712 4.659 12.408 1.00 0.00 H +HETATM 553 O HOH A 185 8.454 1.669 13.798 1.00 0.00 O +HETATM 554 H1 HOH A 185 9.152 1.023 13.907 1.00 0.00 H +HETATM 555 H2 HOH A 185 8.893 2.514 13.893 1.00 0.00 H +HETATM 556 O HOH A 186 1.418 7.617 14.583 1.00 0.00 O +HETATM 557 H1 HOH A 186 1.000 7.039 15.221 1.00 0.00 H +HETATM 558 H2 HOH A 186 1.764 7.026 13.915 1.00 0.00 H +HETATM 559 O HOH A 187 14.964 20.818 4.135 1.00 0.00 O +HETATM 560 H1 HOH A 187 14.486 20.252 4.741 1.00 0.00 H +HETATM 561 H2 HOH A 187 15.882 20.576 4.255 1.00 0.00 H +HETATM 562 O HOH A 188 3.060 8.643 12.209 1.00 0.00 O +HETATM 563 H1 HOH A 188 3.747 8.193 11.718 1.00 0.00 H +HETATM 564 H2 HOH A 188 3.327 8.562 13.124 1.00 0.00 H +HETATM 565 O HOH A 189 3.211 15.922 10.325 1.00 0.00 O +HETATM 566 H1 HOH A 189 2.732 15.246 9.846 1.00 0.00 H +HETATM 567 H2 HOH A 189 3.882 16.221 9.712 1.00 0.00 H +HETATM 568 O HOH A 190 12.367 14.339 18.281 1.00 0.00 O +HETATM 569 H1 HOH A 190 12.942 14.850 17.710 1.00 0.00 H +HETATM 570 H2 HOH A 190 12.211 14.908 19.035 1.00 0.00 H +HETATM 571 O HOH A 191 7.042 6.517 21.355 1.00 0.00 O +HETATM 572 H1 HOH A 191 7.216 5.772 21.931 1.00 0.00 H +HETATM 573 H2 HOH A 191 6.302 6.235 20.817 1.00 0.00 H +HETATM 574 O HOH A 192 3.600 18.767 18.292 1.00 0.00 O +HETATM 575 H1 HOH A 192 3.447 18.291 17.475 1.00 0.00 H +HETATM 576 H2 HOH A 192 4.509 18.571 18.516 1.00 0.00 H +HETATM 577 O HOH A 193 7.301 19.490 5.362 1.00 0.00 O +HETATM 578 H1 HOH A 193 7.561 19.609 4.448 1.00 0.00 H +HETATM 579 H2 HOH A 193 6.385 19.217 5.316 1.00 0.00 H +HETATM 580 O HOH A 194 9.445 5.827 11.106 1.00 0.00 O +HETATM 581 H1 HOH A 194 9.193 5.151 10.477 1.00 0.00 H +HETATM 582 H2 HOH A 194 9.404 5.389 11.956 1.00 0.00 H +HETATM 583 O HOH A 195 6.632 12.233 20.048 1.00 0.00 O +HETATM 584 H1 HOH A 195 5.966 12.909 20.170 1.00 0.00 H +HETATM 585 H2 HOH A 195 6.913 12.334 19.139 1.00 0.00 H +HETATM 586 O HOH A 196 19.819 20.094 7.603 1.00 0.00 O +HETATM 587 H1 HOH A 196 19.406 20.089 8.466 1.00 0.00 H +HETATM 588 H2 HOH A 196 20.687 20.467 7.756 1.00 0.00 H +HETATM 589 O HOH A 197 20.714 11.307 10.003 1.00 0.00 O +HETATM 590 H1 HOH A 197 21.339 10.821 9.465 1.00 0.00 H +HETATM 591 H2 HOH A 197 20.806 12.215 9.715 1.00 0.00 H +HETATM 592 O HOH A 198 5.525 3.416 21.664 1.00 0.00 O +HETATM 593 H1 HOH A 198 5.955 4.086 21.131 1.00 0.00 H +HETATM 594 H2 HOH A 198 4.738 3.846 21.999 1.00 0.00 H +HETATM 595 O HOH A 199 15.914 6.673 2.677 1.00 0.00 O +HETATM 596 H1 HOH A 199 15.711 7.561 2.972 1.00 0.00 H +HETATM 597 H2 HOH A 199 15.167 6.149 2.963 1.00 0.00 H +HETATM 598 O HOH A 200 20.976 10.881 12.915 1.00 0.00 O +HETATM 599 H1 HOH A 200 20.734 11.506 12.231 1.00 0.00 H +HETATM 600 H2 HOH A 200 20.236 10.892 13.521 1.00 0.00 H +HETATM 601 O HOH A 201 2.988 15.865 13.640 1.00 0.00 O +HETATM 602 H1 HOH A 201 2.187 16.371 13.779 1.00 0.00 H +HETATM 603 H2 HOH A 201 3.446 16.330 12.940 1.00 0.00 H +HETATM 604 O HOH A 202 13.674 12.414 7.586 1.00 0.00 O +HETATM 605 H1 HOH A 202 14.477 12.319 7.073 1.00 0.00 H +HETATM 606 H2 HOH A 202 13.149 11.649 7.353 1.00 0.00 H +HETATM 607 O HOH A 203 6.593 10.558 17.798 1.00 0.00 O +HETATM 608 H1 HOH A 203 6.387 10.481 16.866 1.00 0.00 H +HETATM 609 H2 HOH A 203 5.741 10.562 18.233 1.00 0.00 H +HETATM 610 O HOH A 204 18.593 21.270 16.182 1.00 0.00 O +HETATM 611 H1 HOH A 204 19.326 21.250 15.566 1.00 0.00 H +HETATM 612 H2 HOH A 204 17.851 21.577 15.661 1.00 0.00 H +HETATM 613 O HOH A 205 9.789 11.187 10.433 1.00 0.00 O +HETATM 614 H1 HOH A 205 10.310 10.463 10.085 1.00 0.00 H +HETATM 615 H2 HOH A 205 10.325 11.552 11.137 1.00 0.00 H +HETATM 616 O HOH A 206 1.710 14.353 8.111 1.00 0.00 O +HETATM 617 H1 HOH A 206 0.999 13.896 7.663 1.00 0.00 H +HETATM 618 H2 HOH A 206 2.480 13.805 7.961 1.00 0.00 H +HETATM 619 O HOH A 207 11.321 8.200 20.608 1.00 0.00 O +HETATM 620 H1 HOH A 207 11.988 8.784 20.969 1.00 0.00 H +HETATM 621 H2 HOH A 207 10.661 8.788 20.242 1.00 0.00 H +HETATM 622 O HOH A 208 13.649 8.882 9.896 1.00 0.00 O +HETATM 623 H1 HOH A 208 14.013 8.556 9.073 1.00 0.00 H +HETATM 624 H2 HOH A 208 14.309 9.494 10.223 1.00 0.00 H +HETATM 625 O HOH A 209 6.167 17.358 7.182 1.00 0.00 O +HETATM 626 H1 HOH A 209 5.611 16.581 7.124 1.00 0.00 H +HETATM 627 H2 HOH A 209 6.708 17.326 6.394 1.00 0.00 H +HETATM 628 O HOH A 210 18.453 9.044 14.210 1.00 0.00 O +HETATM 629 H1 HOH A 210 19.078 9.022 13.486 1.00 0.00 H +HETATM 630 H2 HOH A 210 18.287 9.976 14.355 1.00 0.00 H +HETATM 631 O HOH A 211 18.551 15.952 19.684 1.00 0.00 O +HETATM 632 H1 HOH A 211 18.451 15.944 20.636 1.00 0.00 H +HETATM 633 H2 HOH A 211 17.902 16.587 19.380 1.00 0.00 H +HETATM 634 O HOH A 212 2.622 11.775 19.490 1.00 0.00 O +HETATM 635 H1 HOH A 212 2.824 11.016 18.943 1.00 0.00 H +HETATM 636 H2 HOH A 212 2.974 11.550 20.351 1.00 0.00 H +HETATM 637 O HOH A 213 5.085 6.936 16.985 1.00 0.00 O +HETATM 638 H1 HOH A 213 4.406 7.047 16.319 1.00 0.00 H +HETATM 639 H2 HOH A 213 5.574 7.759 16.966 1.00 0.00 H +HETATM 640 O HOH A 214 4.253 20.840 5.164 1.00 0.00 O +HETATM 641 H1 HOH A 214 3.696 21.531 5.523 1.00 0.00 H +HETATM 642 H2 HOH A 214 5.131 21.221 5.154 1.00 0.00 H +HETATM 643 O HOH A 215 9.295 8.953 14.630 1.00 0.00 O +HETATM 644 H1 HOH A 215 9.279 8.037 14.909 1.00 0.00 H +HETATM 645 H2 HOH A 215 8.798 9.416 15.304 1.00 0.00 H +HETATM 646 O HOH A 216 15.685 16.952 5.496 1.00 0.00 O +HETATM 647 H1 HOH A 216 15.825 17.768 5.978 1.00 0.00 H +HETATM 648 H2 HOH A 216 15.328 17.230 4.653 1.00 0.00 H +HETATM 649 O HOH A 217 11.081 18.526 8.232 1.00 0.00 O +HETATM 650 H1 HOH A 217 11.488 17.749 8.616 1.00 0.00 H +HETATM 651 H2 HOH A 217 11.775 19.185 8.232 1.00 0.00 H +HETATM 652 O HOH A 218 10.369 10.008 17.539 1.00 0.00 O +HETATM 653 H1 HOH A 218 9.910 9.234 17.214 1.00 0.00 H +HETATM 654 H2 HOH A 218 10.369 10.614 16.798 1.00 0.00 H +HETATM 655 O HOH A 219 15.828 21.234 6.360 1.00 0.00 O +HETATM 656 H1 HOH A 219 16.127 21.034 7.247 1.00 0.00 H +HETATM 657 H2 HOH A 219 15.262 21.999 6.465 1.00 0.00 H +HETATM 658 O HOH A 220 20.542 20.234 21.063 1.00 0.00 O +HETATM 659 H1 HOH A 220 20.726 20.488 21.967 1.00 0.00 H +HETATM 660 H2 HOH A 220 21.181 20.721 20.543 1.00 0.00 H +HETATM 661 O HOH A 221 8.150 1.935 9.303 1.00 0.00 O +HETATM 662 H1 HOH A 221 8.323 1.001 9.418 1.00 0.00 H +HETATM 663 H2 HOH A 221 9.011 2.319 9.138 1.00 0.00 H +HETATM 664 O HOH A 222 7.255 15.731 4.582 1.00 0.00 O +HETATM 665 H1 HOH A 222 7.503 15.192 3.831 1.00 0.00 H +HETATM 666 H2 HOH A 222 7.993 16.327 4.704 1.00 0.00 H +HETATM 667 O HOH A 223 11.468 14.598 15.326 1.00 0.00 O +HETATM 668 H1 HOH A 223 11.985 15.392 15.464 1.00 0.00 H +HETATM 669 H2 HOH A 223 12.079 13.881 15.496 1.00 0.00 H +HETATM 670 O HOH A 224 12.270 21.957 9.789 1.00 0.00 O +HETATM 671 H1 HOH A 224 11.891 21.262 9.251 1.00 0.00 H +HETATM 672 H2 HOH A 224 12.993 21.534 10.252 1.00 0.00 H +HETATM 673 O HOH A 225 7.368 14.570 21.436 1.00 0.00 O +HETATM 674 H1 HOH A 225 7.562 14.423 20.511 1.00 0.00 H +HETATM 675 H2 HOH A 225 8.226 14.635 21.854 1.00 0.00 H +HETATM 676 O HOH A 226 7.647 8.760 18.620 1.00 0.00 O +HETATM 677 H1 HOH A 226 7.010 8.176 19.031 1.00 0.00 H +HETATM 678 H2 HOH A 226 7.814 8.367 17.763 1.00 0.00 H +HETATM 679 O HOH A 227 8.844 18.636 9.090 1.00 0.00 O +HETATM 680 H1 HOH A 227 8.123 19.149 9.456 1.00 0.00 H +HETATM 681 H2 HOH A 227 8.617 18.529 8.166 1.00 0.00 H +HETATM 682 O HOH A 228 8.350 19.520 12.041 1.00 0.00 O +HETATM 683 H1 HOH A 228 8.159 19.608 12.975 1.00 0.00 H +HETATM 684 H2 HOH A 228 8.591 20.404 11.763 1.00 0.00 H +HETATM 685 O HOH A 229 20.742 17.897 11.799 1.00 0.00 O +HETATM 686 H1 HOH A 229 21.245 18.592 12.222 1.00 0.00 H +HETATM 687 H2 HOH A 229 21.216 17.094 12.014 1.00 0.00 H +HETATM 688 O HOH A 230 4.192 6.235 8.246 1.00 0.00 O +HETATM 689 H1 HOH A 230 4.575 5.406 8.532 1.00 0.00 H +HETATM 690 H2 HOH A 230 3.267 6.165 8.478 1.00 0.00 H +HETATM 691 O HOH A 231 17.371 18.739 2.918 1.00 0.00 O +HETATM 692 H1 HOH A 231 16.679 18.775 2.258 1.00 0.00 H +HETATM 693 H2 HOH A 231 17.108 18.028 3.502 1.00 0.00 H +HETATM 694 O HOH A 232 12.972 21.131 6.703 1.00 0.00 O +HETATM 695 H1 HOH A 232 12.821 20.194 6.576 1.00 0.00 H +HETATM 696 H2 HOH A 232 13.333 21.197 7.587 1.00 0.00 H +HETATM 697 O HOH A 233 17.687 19.140 18.847 1.00 0.00 O +HETATM 698 H1 HOH A 233 16.990 19.049 18.197 1.00 0.00 H +HETATM 699 H2 HOH A 233 17.239 19.435 19.639 1.00 0.00 H +HETATM 700 O HOH A 234 15.198 7.824 5.951 1.00 0.00 O +HETATM 701 H1 HOH A 234 15.857 8.132 6.573 1.00 0.00 H +HETATM 702 H2 HOH A 234 15.656 7.174 5.418 1.00 0.00 H +HETATM 703 O HOH A 235 8.566 13.389 6.835 1.00 0.00 O +HETATM 704 H1 HOH A 235 8.196 13.577 5.972 1.00 0.00 H +HETATM 705 H2 HOH A 235 7.881 12.895 7.286 1.00 0.00 H +HETATM 706 O HOH A 236 13.863 1.022 14.430 1.00 0.00 O +HETATM 707 H1 HOH A 236 14.190 1.693 13.831 1.00 0.00 H +HETATM 708 H2 HOH A 236 14.468 1.046 15.170 1.00 0.00 H +HETATM 709 O HOH A 237 14.034 6.721 20.217 1.00 0.00 O +HETATM 710 H1 HOH A 237 13.340 7.014 20.808 1.00 0.00 H +HETATM 711 H2 HOH A 237 14.170 7.458 19.622 1.00 0.00 H +HETATM 712 O HOH A 238 21.727 7.137 21.103 1.00 0.00 O +HETATM 713 H1 HOH A 238 21.404 7.458 21.945 1.00 0.00 H +HETATM 714 H2 HOH A 238 21.999 7.926 20.634 1.00 0.00 H +HETATM 715 O HOH A 239 3.986 9.969 15.184 1.00 0.00 O +HETATM 716 H1 HOH A 239 3.719 9.567 16.011 1.00 0.00 H +HETATM 717 H2 HOH A 239 3.297 10.605 14.994 1.00 0.00 H +HETATM 718 O HOH A 240 17.833 21.436 9.208 1.00 0.00 O +HETATM 719 H1 HOH A 240 16.889 21.592 9.237 1.00 0.00 H +HETATM 720 H2 HOH A 240 18.148 22.002 8.503 1.00 0.00 H +HETATM 721 O HOH A 241 17.767 14.132 17.575 1.00 0.00 O +HETATM 722 H1 HOH A 241 17.377 14.347 16.728 1.00 0.00 H +HETATM 723 H2 HOH A 241 17.433 13.258 17.778 1.00 0.00 H +HETATM 724 O HOH A 242 18.872 1.246 11.184 1.00 0.00 O +HETATM 725 H1 HOH A 242 18.478 1.405 10.326 1.00 0.00 H +HETATM 726 H2 HOH A 242 19.781 1.016 10.991 1.00 0.00 H +HETATM 727 O HOH A 243 6.267 12.095 5.891 1.00 0.00 O +HETATM 728 H1 HOH A 243 5.328 12.129 5.708 1.00 0.00 H +HETATM 729 H2 HOH A 243 6.415 11.208 6.216 1.00 0.00 H +HETATM 730 O HOH A 244 2.910 14.092 2.271 1.00 0.00 O +HETATM 731 H1 HOH A 244 3.677 14.424 1.805 1.00 0.00 H +HETATM 732 H2 HOH A 244 2.340 14.855 2.369 1.00 0.00 H +HETATM 733 O HOH A 245 20.079 4.741 20.826 1.00 0.00 O +HETATM 734 H1 HOH A 245 19.656 4.486 21.646 1.00 0.00 H +HETATM 735 H2 HOH A 245 19.419 4.575 20.154 1.00 0.00 H +HETATM 736 O HOH A 246 8.296 14.695 15.842 1.00 0.00 O +HETATM 737 H1 HOH A 246 9.227 14.910 15.793 1.00 0.00 H +HETATM 738 H2 HOH A 246 7.858 15.421 15.397 1.00 0.00 H +HETATM 739 O HOH A 247 21.454 9.911 18.743 1.00 0.00 O +HETATM 740 H1 HOH A 247 21.346 9.486 17.892 1.00 0.00 H +HETATM 741 H2 HOH A 247 21.296 10.839 18.570 1.00 0.00 H +HETATM 742 O HOH A 248 1.679 8.333 6.722 1.00 0.00 O +HETATM 743 H1 HOH A 248 1.966 7.518 6.310 1.00 0.00 H +HETATM 744 H2 HOH A 248 1.001 8.671 6.138 1.00 0.00 H +HETATM 745 O HOH A 249 14.597 11.946 4.333 1.00 0.00 O +HETATM 746 H1 HOH A 249 14.084 11.153 4.491 1.00 0.00 H +HETATM 747 H2 HOH A 249 14.111 12.639 4.778 1.00 0.00 H +HETATM 748 O HOH A 250 6.543 17.145 19.280 1.00 0.00 O +HETATM 749 H1 HOH A 250 7.201 17.231 19.970 1.00 0.00 H +HETATM 750 H2 HOH A 250 6.524 16.209 19.082 1.00 0.00 H +END diff --git a/tests/test_mbar/test_mbar.py b/tests/test_mbar/test_mbar.py new file mode 100644 index 000000000..9482ec695 --- /dev/null +++ b/tests/test_mbar/test_mbar.py @@ -0,0 +1,313 @@ +from dmff.mbar import MBAREstimator, Sample, SampleState, TargetState, OpenMMSampleState +import dmff +import pytest +import jax +import jax.numpy as jnp +import openmm.app as app +import openmm.unit as unit +import openmm as mm +import numpy as np +import numpy.testing as npt +import mdtraj as md +from pymbar import MBAR +from dmff import Hamiltonian, NeighborListFreud +from tqdm import tqdm + + +class TestMBAR: + @pytest.mark.parametrize( + "pdb, prm1, traj1, prm2, traj2, prm3", + [("tests/data/waterbox.pdb", "tests/data/water1.xml", + "tests/data/w1_npt.dcd", "tests/data/water2.xml", + "tests/data/w2_npt.dcd", "tests/data/water3.xml")]) + def test_mbar_free_energy_diff(self, pdb, prm1, traj1, prm2, traj2, prm3): + pdbobj = app.PDBFile(pdb) + # Prepare DMFF potential + h = Hamiltonian(prm3) + pot = h.createPotential(pdbobj.topology, + nonbondedMethod=app.PME, + nonbondedCutoff=0.9 * unit.nanometer) + efunc = pot.getPotentialFunc() + nbgen = None + for gen in h.getGenerators(): + if isinstance(gen, dmff.generators.NonbondedJaxGenerator): + nbgen = gen + + def target_energy_function(traj, parameters): + pos_list, box_list, pairs_list, vol_list = [], [], [], [] + for frame in tqdm(traj): + aa, bb, cc = frame.openmm_boxes(0).value_in_unit( + unit.nanometer) + box = jnp.array([[aa[0], aa[1], aa[2]], [bb[0], bb[1], bb[2]], + [cc[0], cc[1], cc[2]]]) + vol = aa[0] * bb[1] * cc[2] + positions = jnp.array(frame.xyz[0, :, :]) + nbobj = NeighborListFreud(box, 0.9, nbgen.covalent_map) + nbobj.capacity_multiplier = 1 + pairs = nbobj.allocate(positions) + box_list.append(box) + pairs_list.append(pairs) + vol_list.append(vol) + pos_list.append(positions) + + pmax = max([p.shape[0] for p in pairs_list]) + pairs_jax = np.zeros( + (traj.n_frames, pmax, 3), dtype=int) + traj.n_atoms + for nframe in range(traj.n_frames): + pair = pairs_list[nframe] + pairs_jax[nframe, :pair.shape[0], :] = pair[:, :] + pairs_jax = jax.numpy.array(pairs_jax) + pos_list = jnp.array(pos_list) + box_list = jnp.array(box_list) + vol_list = jnp.array(vol_list) + eners = [ + efunc(pos_list[i], box_list[i], pairs_jax[i], parameters) + + 0.06023 * vol_list[i] for i in range(traj.n_frames) + ] + return eners + + target_state = TargetState(300.0, target_energy_function) + + # prepare MBAR estimator + traj1 = md.load(traj1, top=pdb)[20::4] + traj2 = md.load(traj2, top=pdb)[20::4] + ref_state1 = OpenMMSampleState("ref1", + prm1, + pdb, + temperature=300.0, + pressure=1.0) + ref_state2 = OpenMMSampleState("ref2", + prm2, + pdb, + temperature=300.0, + pressure=1.0) + sample1 = Sample(traj1, "ref1") + sample2 = Sample(traj2, "ref2") + + # S1 + mbar = MBAREstimator() + mbar.add_state(ref_state1) + mbar.add_sample(sample1) + mbar.optimize_mbar() + + df, utgt, uref = mbar.estimate_free_energy_difference( + target_state, + ref_state1, + target_parameters=h.paramtree, + return_energy=True) + + # calc reference using PyMBAR + umat_np = np.zeros((2, traj2.n_frames)) + umat_np[0, :] = mbar._umat[0, :] + umat_np[1, :] = utgt[:] + nk = np.array([traj1.n_frames, 0]) + mbar_p = MBAR(umat_np, nk, initialize="BAR") + npt.assert_almost_equal(df, mbar_p.f_k[-1], decimal=3) + + # S1 + S2 + mbar.add_state(ref_state2) + mbar.add_sample(sample2) + mbar.optimize_mbar() + + df, utgt, uref = mbar.estimate_free_energy_difference( + target_state, + ref_state2, + target_parameters=h.paramtree, + return_energy=True) + + # calc reference using PyMBAR + umat_np = np.zeros((3, 2 * traj1.n_frames)) + umat_np[0, :] = mbar._umat[0, :] + umat_np[1, :] = mbar._umat[1, :] + umat_np[2, :] = utgt[:] + nk = np.array([traj1.n_frames, traj1.n_frames, 0]) + mbar_p = MBAR(umat_np, nk, initialize="BAR") + npt.assert_almost_equal(df, mbar_p.f_k[2] - mbar_p.f_k[1], decimal=3) + + # S2 + mbar.remove_state("ref1") + mbar.optimize_mbar() + + df, utgt, uref = mbar.estimate_free_energy_difference( + target_state, + ref_state2, + target_parameters=h.paramtree, + return_energy=True) + + # calc reference using PyMBAR + umat_np = np.zeros((2, traj2.n_frames)) + umat_np[0, :] = mbar._umat[0, :] + umat_np[1, :] = utgt[:] + nk = np.array([traj1.n_frames, 0]) + mbar_p = MBAR(umat_np, nk, initialize="BAR") + npt.assert_almost_equal(df, mbar_p.f_k[-1], decimal=3) + + @pytest.mark.parametrize( + "pdb, prm1, traj1, prm2, traj2, prm3", + [("tests/data/waterbox.pdb", "tests/data/water1.xml", + "tests/data/w1_npt.dcd", "tests/data/water2.xml", + "tests/data/w2_npt.dcd", "tests/data/water3.xml")]) + def test_mbar_free_energy_nodiff(self, pdb, prm1, traj1, prm2, traj2, + prm3): + pdbobj = app.PDBFile(pdb) + + # prepare MBAR estimator + traj1 = md.load(traj1, top=pdb)[20::4] + traj2 = md.load(traj2, top=pdb)[20::4] + ref_state1 = OpenMMSampleState("ref1", + prm1, + pdb, + temperature=300.0, + pressure=1.0) + ref_state2 = OpenMMSampleState("ref2", + prm2, + pdb, + temperature=300.0, + pressure=1.0) + ref_state3 = OpenMMSampleState("ref3", + prm3, + pdb, + temperature=300.0, + pressure=1.0) + sample1 = Sample(traj1, "ref1") + sample2 = Sample(traj2, "ref2") + + # S1 + mbar = MBAREstimator() + mbar.add_state(ref_state1) + mbar.add_sample(sample1) + mbar.optimize_mbar() + + df, utgt, uref = mbar.estimate_free_energy_difference( + ref_state3, ref_state1, return_energy=True) + + # calc reference using PyMBAR + umat_np = np.zeros((2, traj2.n_frames)) + umat_np[0, :] = mbar._umat[0, :] + umat_np[1, :] = utgt[:] + nk = np.array([traj1.n_frames, 0]) + mbar_p = MBAR(umat_np, nk, initialize="BAR") + npt.assert_almost_equal(df, mbar_p.f_k[-1], decimal=3) + + # S1 + S2 + mbar.add_state(ref_state2) + mbar.add_sample(sample2) + mbar.optimize_mbar() + + df, utgt, uref = mbar.estimate_free_energy_difference( + ref_state3, ref_state2, return_energy=True) + + # calc reference using PyMBAR + umat_np = np.zeros((3, 2 * traj1.n_frames)) + umat_np[0, :] = mbar._umat[0, :] + umat_np[1, :] = mbar._umat[1, :] + umat_np[2, :] = utgt[:] + nk = np.array([traj1.n_frames, traj1.n_frames, 0]) + mbar_p = MBAR(umat_np, nk, initialize="BAR") + npt.assert_almost_equal(df, mbar_p.f_k[2] - mbar_p.f_k[1], decimal=3) + + # S2 + mbar.remove_state("ref1") + mbar.optimize_mbar() + + df, utgt, uref = mbar.estimate_free_energy_difference( + ref_state3, ref_state2, return_energy=True) + + # calc reference using PyMBAR + umat_np = np.zeros((2, traj2.n_frames)) + umat_np[0, :] = mbar._umat[0, :] + umat_np[1, :] = utgt[:] + nk = np.array([traj1.n_frames, 0]) + mbar_p = MBAR(umat_np, nk, initialize="BAR") + npt.assert_almost_equal(df, mbar_p.f_k[-1], decimal=3) + + @pytest.mark.parametrize( + "pdb, prm1, traj1, prm2, traj2, prm3", + [("tests/data/waterbox.pdb", "tests/data/water1.xml", + "tests/data/w1_npt.dcd", "tests/data/water2.xml", + "tests/data/w2_npt.dcd", "tests/data/water3.xml")]) + def test_mbar_weight(self, pdb, prm1, traj1, prm2, traj2, prm3): + pdbobj = app.PDBFile(pdb) + # Prepare DMFF potential + h = Hamiltonian(prm3) + pot = h.createPotential(pdbobj.topology, + nonbondedMethod=app.PME, + nonbondedCutoff=0.9 * unit.nanometer) + efunc = pot.getPotentialFunc() + nbgen = None + for gen in h.getGenerators(): + if isinstance(gen, dmff.generators.NonbondedJaxGenerator): + nbgen = gen + + def target_energy_function(traj, parameters): + pos_list, box_list, pairs_list, vol_list = [], [], [], [] + for frame in tqdm(traj): + aa, bb, cc = frame.openmm_boxes(0).value_in_unit( + unit.nanometer) + box = jnp.array([[aa[0], aa[1], aa[2]], [bb[0], bb[1], bb[2]], + [cc[0], cc[1], cc[2]]]) + vol = aa[0] * bb[1] * cc[2] + positions = jnp.array(frame.xyz[0, :, :]) + nbobj = NeighborListFreud(box, 0.9, nbgen.covalent_map) + nbobj.capacity_multiplier = 1 + pairs = nbobj.allocate(positions) + box_list.append(box) + pairs_list.append(pairs) + vol_list.append(vol) + pos_list.append(positions) + + pmax = max([p.shape[0] for p in pairs_list]) + pairs_jax = np.zeros( + (traj.n_frames, pmax, 3), dtype=int) + traj.n_atoms + for nframe in range(traj.n_frames): + pair = pairs_list[nframe] + pairs_jax[nframe, :pair.shape[0], :] = pair[:, :] + pairs_jax = jax.numpy.array(pairs_jax) + pos_list = jnp.array(pos_list) + box_list = jnp.array(box_list) + vol_list = jnp.array(vol_list) + eners = [ + efunc(pos_list[i], box_list[i], pairs_jax[i], parameters) + + 0.06023 * vol_list[i] for i in range(traj.n_frames) + ] + return eners + + target_state = TargetState(300.0, target_energy_function) + + # prepare MBAR estimator + traj1 = md.load(traj1, top=pdb)[20::4] + traj2 = md.load(traj2, top=pdb)[20::4] + ref_state1 = OpenMMSampleState("ref1", + prm1, + pdb, + temperature=300.0, + pressure=1.0) + ref_state2 = OpenMMSampleState("ref2", + prm2, + pdb, + temperature=300.0, + pressure=1.0) + sample1 = Sample(traj1, "ref1") + sample2 = Sample(traj2, "ref2") + + mbar = MBAREstimator() + mbar.add_state(ref_state1) + mbar.add_sample(sample1) + mbar.add_state(ref_state2) + mbar.add_sample(sample2) + mbar.optimize_mbar() + + weight, ulist = mbar.estimate_weight(target_state, + h.paramtree, + return_energy=True) + + # calc reference using PyMBAR + umat_ref = np.zeros((3, ulist.shape[0])) + umat_ref[0, :] = mbar._umat[0, :] + umat_ref[1, :] = mbar._umat[1, :] + umat_ref[2, :] = ulist[:] + nk = np.array([traj1.n_frames, traj2.n_frames, 0]) + mbar_ref = MBAR(umat_ref, nk, initialize="BAR") + weight_ref = mbar_ref.W_nk.T[-1, :] + rmse = np.sqrt(np.power(weight - weight_ref, 2).mean()) + npt.assert_almost_equal(rmse, 0.0, decimal=3) \ No newline at end of file