Skip to content

Commit 2fa71cf

Browse files
WangXinyan940Ericwang6Roy-KidKuangYuRoy-Kid
authored
Release DMFF v0.2.0 (deepmodeling#71)
* feat(classical): offer tools to organize xml forcefield and match parameters. * fix(tests): change API to be consistent with dmff * feat(dmff): Add class Potential to keep OpenMM and DMFF potentials * fix(api): auto-update parameter before writing new xml file. * refactor: admp api.py using old xmlholder api * fix(fftree): make the name of API easier to be understand * update * update: admp related api * fix: fix bug in admp api * doc: programming style convention about typing and numpy style docstring * fix(tests): Add attribute to choose improper order * fix(classical): remove double check of cutoff distance in potential function. * fix(classical): avoid creating empty dict in impr parameters * fix(inter): add 1e-12 as eps value in jnp.sqrt function * fix(inter): use more tiny eps for numerical consistent * update: frontend docs * fix typo: fix typo in dev_guide * Temprary commit before test * Debugging new api for admp * peg system benchmark checked out * Update examples * Fix bug: was not reading the XZ component of multipole * remove jit for nbl.update * Modify setup.py * Require jax version smaller than 0.3.16, which is incompatible with current version of jax_md (0.1.29) * Update demo notebook in water example * Update documents and the notebook in classical FF example * Fix bug in nblist module * change allocate * fix: fix nblist jit-related bug * fix: test sequence in test_nblist.py * Fix bug in Slater ISA short range interaction * update: all tests pass * clean up code * feat: brutal Freud-based nblist * Bug fix in parsing internal xmls (deepmodeling#62) 1. Add NeighborFreud class to use freud package to obtain neighborlist. 2. Fix bug of parsing openmm internal force field xmls 3. Handle unmatched torsion params * settle down dependencies version * rename `nmax` to `capacity_multiplier`(consistent with jax_md); remove property:distance and dr in neighborList due to catastrophic bug * add freud-analysis require * remove overflow judgement so update in nblist can be jitted * Adapt examples to new nblist api * add md_ipi in examples to run classical MD for bulk water * modified md_ipi * modified md_ipi * new * new * new_2 * Refactor: decompose api.py and generators to separate files * feat(MBAR): Add differentiable MBAR impl * bug fix: record the name of matched template * Add unit test for MBAR estimator * feat: Estimate free energy of an extra state * set specific version no. for jax in installation guide * Update MBAR Estimator API * Update unit test for the latest MBAR API * Update github workflow to support new unit test. * Update requirement of pymbar * fix package including problem * Change cell vector to prevent numerical problem (ceil(12.0 / 1.0) would jump between 12 and 13) * Update settings.py * remove unused imports * fix "==" in requirements * update (gitignore): hmtff cache * update (api): docstring in createPotential * Update optax transforms for force field parameters * Update __init__.py * Update LJ jax force API in LennardJonesForce generator * Let energy function in TargetState use the whole trajectory instead of using single frame * Update MBAR UT to fit API change * Remove the using of numpy to make free energy & weight differentiable * Update __init__.py * Set precision again in dmff.mbar after importing pymbar * Increase MBAR numerical stability * Update an example of MBAR-based optimization * Correct the typo in document * Add openmm sample state. * Update genOptimizer to support multiple optimizers * Set default pressure to be 0 (NVT) * Add Gitee_mirror (deepmodeling#67) * Fix Mirror CI/CD (deepmodeling#68) * Add Gitee_mirror * Fix mirror CI/CD Co-authored-by: WangXinyan940 <[email protected]> * Add tutorial_utils for demo usage (deepmodeling#69) * Hook jax force to generator for intra potentials * Remove the requirement of mdtraj * Let NeighborList.update return (N, 3) pairs with colv_map information * Update nblist.py * bugfix: correctly recognize if the LJForce card uses type or class * SMIRKS-based typing scheme (deepmodeling#72) * add support for SMIRKS, bcc and virtual site * add dependencies in ut workflow * add interface to obtain topmat * code clean * fix multiple matches_dict var * add tests for building top * add tests for smirks/bcc/vsite * add dockerfile for dev env * add examples of smirks * add usage of smirks in doc * rm api doc in _date/_version * fix typo * update (doc): architecture img * update (README): citation and code structure * update (doc): bad rendered latex * update (doc): typo in usage * update (doc): smirks in XML * Hotfix in BCC parametrization (deepmodeling#73) * update (smirks): change bcc err to warning * hotfix: incorrect bcc matching * Save covalent map to Potential object & make energy function generator (deepmodeling#74) * Update python version requirement to 3.8 * Save meta data in Potential object * Add support to save meta data in Hamiltonian * Create an attribute to save meta data * Use potential.meta["cov_map"] in unittests * Build a function generator to calculate energies of a trajectory * Change API function name * fix meta data for admp generators * Make buildEnergyFunction cleaner Co-authored-by: KuangYu <[email protected]> Co-authored-by: Yingze Wang <[email protected]> Co-authored-by: Roy-Kid <[email protected]> Co-authored-by: KuangYu <[email protected]> Co-authored-by: Jichen Li <[email protected]> Co-authored-by: crone <[email protected]> Co-authored-by: Yuzhi Zhang <[email protected]>
1 parent af4b1f2 commit 2fa71cf

File tree

172 files changed

+74244
-31648
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

172 files changed

+74244
-31648
lines changed

.github/workflows/mirror_gitee.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Mirror to Gitee Repo
2+
3+
on: [ push, delete, create ]
4+
5+
# Ensures that only one mirror task will run at a time.
6+
concurrency:
7+
group: git-mirror
8+
9+
jobs:
10+
git-mirror:
11+
if: github.repository_owner == 'deepmodeling'
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: wearerequired/git-mirror-action@v1
15+
env:
16+
SSH_PRIVATE_KEY: ${{ secrets.SYNC_GITEE_PRIVATE_KEY }}
17+
with:
18+
source-repo: "https://github.com/deepmodeling/dmff.git"
19+
destination-repo: "[email protected]:deepmodeling/DMFF.git"

.github/workflows/ut.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ jobs:
2222
$CONDA/bin/conda update -n base -c defaults conda
2323
conda install pip
2424
conda update pip
25-
conda install numpy openmm pytest -c conda-forge
25+
conda install numpy openmm pytest rdkit biopandas openbabel -c conda-forge
2626
pip install jax jax_md
27+
pip install mdtraj==1.9.7 pymbar==4.0.1
2728
- name: Install DMFF
2829
run: |
2930
source $CONDA/bin/activate

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -781,4 +781,7 @@ FodyWeavers.xsd
781781
*.acpype/
782782

783783
*/_date.py
784-
*/_version.py
784+
*/_version.py
785+
786+
# hmtff cache
787+
*.hmtff/

README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
# DMFF
22

3+
[![doi:10.26434/chemrxiv-2022-2c7gv](https://img.shields.io/badge/DOI-10.26434%2Fchemrxiv--2022--2c7gv-blue)](https://doi.org/10.26434/chemrxiv-2022-2c7gv)
4+
5+
## About DMFF
6+
37
**DMFF** (**D**ifferentiable **M**olecular **F**orce **F**ield) is a Jax-based python package that provides a full differentiable implementation of molecular force field models. This project aims to establish an extensible codebase to minimize the efforts in force field parameterization, and to ease the force and virial tensor evaluations for advanced complicated potentials (e.g., polarizable models with geometry-dependent atomic parameters). Currently, this project mainly focuses on the molecular systems such as: water, biological macromolecules (peptides, proteins, nucleic acids), organic polymers, and small organic molecules (organic electrolyte, drug-like molecules) etc. We support both the conventional point charge models (OPLS and AMBER like) and multipolar polarizable models (AMOEBA and MPID like). The entire project is backed by the XLA technique in JAX, thus can be "jitted" and run in GPU devices much more efficiently compared to normal python codes.
48

59
The behavior of organic molecular systems (e.g., protein folding, polymer structure, etc.) is often determined by a complex effect of many different types of interactions. The existing organic molecular force fields are mainly empirically fitted and their performance relies heavily on error cancellation. Therefore, the transferability and the prediction power of these force fields are insufficient. For new molecules, the parameter fitting process requires essential manual intervention and can be quite cumbersome. In order to automate the parametrization process and increase the robustness of the model, it is necessary to apply modern AI techniques in conventional force field development. This project serves for this purpose by utilizing the automatic differentiable programming technique to develop a codebase, which allows a more convenient incorporation of modern AI optimization techniques. It also helps the realization of many exciting functions including (but not limited to): hybrid machine learning/force field models and parameter optimization based on trajectory.
610

11+
### License and credits
12+
13+
The project DMFF is licensed under [GNU LGPL v3.0](LICENSE). If you use this code in any future publications, please cite this using `Wang X, Li J, Yang L, Chen F, Wang Y, Chang J, et al. DMFF: An Open-Source Automatic
14+
Differentiable Platform for Molecular Force Field
15+
Development and Molecular Dynamics
16+
Simulation. ChemRxiv. Cambridge: Cambridge Open Engage; 2022; This content is a preprint and has not been peer-reviewed.`
17+
718
## User Guide
819

920
+ [1. Introduction](docs/user_guide/introduction.md)
@@ -18,9 +29,20 @@ The behavior of organic molecular systems (e.g., protein folding, polymer struct
1829
+ [3. Coding conventions](docs/dev_guide/convention.md)
1930
+ [4. Document writing](docs/dev_guide/write_docs.md)
2031

21-
## Modules
22-
+ [1. ADMP](docs/modules/admp.md)
32+
## Code Structure
33+
34+
The code is organized as follows:
2335

36+
+ `examples`: demos presented in Jupyter Notebook.
37+
+ `docs`: documentation.
38+
+ `package`: files for constructing packages or images, such as conda recipe and docker files.
39+
+ `tests`: unit tests.
40+
+ `dmff`: DMFF python codes
41+
+ `dmff/admp`: source code of automatic differentiable multipolar polarizable (ADMP) force field module.
42+
+ `dmff/classical`: source code of classical force field module.
43+
+ `dmff/common`: source code of common functions, such as neighbor list.
44+
+ `dmff/generators`: source code of force generators.
45+
+ `dmff/sgnn`: source of subgragh neural network force field model.
2446

2547
## Support and Contribution
2648

dmff/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from .settings import *
2-
from .common.nblist import NeighborList
3-
from .api import Hamiltonian
2+
from .common.nblist import NeighborList, NeighborListFreud
3+
from .api import Hamiltonian
4+
from .generators import *

dmff/admp/disp_pme.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ class ADMPDispPmeForce:
1717
The so called "environment paramters" means parameters that do not need to be differentiable
1818
'''
1919

20-
def __init__(self, box, covalent_map, rc, ethresh, pmax, lpme=True):
21-
self.covalent_map = covalent_map
20+
def __init__(self, box, rc, ethresh, pmax, lpme=True):
21+
2222
self.rc = rc
2323
self.ethresh = ethresh
2424
self.pmax = pmax
@@ -44,7 +44,7 @@ def __init__(self, box, covalent_map, rc, ethresh, pmax, lpme=True):
4444
def generate_get_energy(self):
4545
def get_energy(positions, box, pairs, c_list, mScales):
4646
return energy_disp_pme(positions, box, pairs,
47-
c_list, mScales, self.covalent_map,
47+
c_list, mScales,
4848
self.kappa, self.K1, self.K2, self.K3, self.pmax,
4949
self.d6_recip, self.d8_recip, self.d10_recip, lpme=self.lpme)
5050
return get_energy
@@ -78,7 +78,7 @@ def refresh_calculators(self):
7878

7979

8080
def energy_disp_pme(positions, box, pairs,
81-
c_list, mScales, covalent_map,
81+
c_list, mScales,
8282
kappa, K1, K2, K3, pmax,
8383
recip_fn6, recip_fn8, recip_fn10, lpme=True):
8484
'''
@@ -90,7 +90,7 @@ def energy_disp_pme(positions, box, pairs,
9090
box:
9191
3 * 3: box, axes arranged in row
9292
pairs:
93-
Np * 2: interacting pair indices
93+
Np * 3: interacting pair indices and topology distance
9494
c_list:
9595
Na * (pmax-4)/2: atomic dispersion coefficients
9696
mScales:
@@ -115,7 +115,7 @@ def energy_disp_pme(positions, box, pairs,
115115
if lpme is False:
116116
kappa = 0
117117

118-
ene_real = disp_pme_real(positions, box, pairs, c_list, mScales, covalent_map, kappa, pmax)
118+
ene_real = disp_pme_real(positions, box, pairs, c_list, mScales, kappa, pmax)
119119

120120
if lpme:
121121
ene_recip = recip_fn6(positions, box, c_list[:, 0, jnp.newaxis])
@@ -132,7 +132,7 @@ def energy_disp_pme(positions, box, pairs,
132132

133133
def disp_pme_real(positions, box, pairs,
134134
c_list,
135-
mScales, covalent_map,
135+
mScales,
136136
kappa, pmax):
137137
'''
138138
This function calculates the dispersion real space energy
@@ -144,7 +144,7 @@ def disp_pme_real(positions, box, pairs,
144144
box:
145145
3 * 3: box, axes arranged in row
146146
pairs:
147-
Np * 2: interacting pair indices
147+
Np * 3: interacting pair indices and topology distance
148148
c_list:
149149
Na * (pmax-4)/2: atomic dispersion coefficients
150150
mScales:
@@ -162,16 +162,16 @@ def disp_pme_real(positions, box, pairs,
162162

163163
# expand pairwise parameters
164164
# pairs = pairs[pairs[:, 0] < pairs[:, 1]]
165-
pairs = regularize_pairs(pairs)
165+
pairs = pairs.at[:, :2].set(regularize_pairs(pairs[:, :2]))
166166

167167
box_inv = jnp.linalg.inv(box)
168168

169169
ri = distribute_v3(positions, pairs[:, 0])
170170
rj = distribute_v3(positions, pairs[:, 1])
171-
nbonds = covalent_map[pairs[:, 0], pairs[:, 1]]
171+
nbonds = pairs[:, 2]
172172
mscales = distribute_scalar(mScales, nbonds-1)
173173

174-
buffer_scales = pair_buffer_scales(pairs)
174+
buffer_scales = pair_buffer_scales(pairs[:, :2])
175175
mscales = mscales * buffer_scales
176176

177177
ci = distribute_dispcoeff(c_list, pairs[:, 0])

dmff/admp/pairwise.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def distribute_dispcoeff(c_list, index):
4444
def distribute_matrix(multipoles,index1,index2):
4545
return multipoles[index1,index2]
4646

47-
def generate_pairwise_interaction(pair_int_kernel, covalent_map, static_args):
47+
def generate_pairwise_interaction(pair_int_kernel, static_args):
4848
'''
4949
This is a calculator generator for pairwise interaction
5050
@@ -53,9 +53,6 @@ def generate_pairwise_interaction(pair_int_kernel, covalent_map, static_args):
5353
function type (dr, m, p1i, p1j, p2i, p2j) -> energy : the vectorized kernel function,
5454
dr is the distance, m is the topological scaling factor, p1i, p1j, p2i, p2j are pairwise parameters
5555
56-
covalent_map:
57-
Na * Na, int: the covalent_map matrix that marks the topological distances between atoms
58-
5956
static_args:
6057
dict: a dictionary that stores all static global parameters (such as lmax, kappa, etc)
6158
@@ -67,13 +64,14 @@ def generate_pairwise_interaction(pair_int_kernel, covalent_map, static_args):
6764
'''
6865

6966
def pair_int(positions, box, pairs, mScales, *atomic_params):
70-
pairs = regularize_pairs(pairs)
67+
# pairs = regularize_pairs(pairs)
68+
pairs = pairs.at[:, :2].set(regularize_pairs(pairs[:, :2]))
7169

7270
ri = distribute_v3(positions, pairs[:, 0])
7371
rj = distribute_v3(positions, pairs[:, 1])
7472
# ri = positions[pairs[:, 0]]
7573
# rj = positions[pairs[:, 1]]
76-
nbonds = covalent_map[pairs[:, 0], pairs[:, 1]]
74+
nbonds = pairs[:, 3]
7775
mscales = distribute_scalar(mScales, nbonds-1)
7876

7977
buffer_scales = pair_buffer_scales(pairs)
@@ -114,8 +112,7 @@ def TT_damping_qq_c6_kernel(dr, m, ai, aj, bi, bj, qi, qj, ci, cj):
114112
exp_br = jnp.exp(-br)
115113
f = 2625.5 * a * exp_br \
116114
+ (-2625.5) * exp_br * (1+br) * q / r \
117-
+ exp_br*(1+br+br2/2+br3/6+br4/24+br5/120+br6/720) * c / dr**6
118-
115+
+ exp_br*(1+br+br2/2+br3/6+br4/24+br5/120+br6/720) * c / dr**6
119116
return f * m
120117

121118

dmff/admp/pme.py

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class ADMPPmeForce:
4242
The so called "environment paramters" means parameters that do not need to be differentiable
4343
'''
4444

45-
def __init__(self, box, axis_type, axis_indices, covalent_map, rc, ethresh, lmax, lpol=False, lpme=True, steps_pol=None):
45+
def __init__(self, box, axis_type, axis_indices, rc, ethresh, lmax, lpol=False, lpme=True, steps_pol=None):
4646
'''
4747
Initialize the ADMPPmeForce calculator.
4848
@@ -51,8 +51,6 @@ def __init__(self, box, axis_type, axis_indices, covalent_map, rc, ethresh, lmax
5151
(3, 3) float, box size in row
5252
axis_type:
5353
(na,) int, types of local axis (bisector, z-then-x etc.)
54-
covalent_map:
55-
(na, na) int, covalent map matrix, labels the topological distances between atoms
5654
rc:
5755
float: cutoff distance
5856
ethresh:
@@ -91,10 +89,10 @@ def __init__(self, box, axis_type, axis_indices, covalent_map, rc, ethresh, lmax
9189
self.K2 = K2
9290
self.K3 = K3
9391
self.pme_order = 6
94-
self.covalent_map = covalent_map
9592
self.lpol = lpol
9693
self.steps_pol = steps_pol
97-
self.n_atoms = int(covalent_map.shape[0]) # len(axis_type)
94+
# self.n_atoms = int(covalent_map.shape[0]) # len(axis_type)
95+
self.n_atoms = len(axis_type)
9896

9997
# setup calculators
10098
self.refresh_calculators()
@@ -107,7 +105,7 @@ def generate_get_energy(self):
107105
def get_energy(positions, box, pairs, Q_local, mScales):
108106
return energy_pme(positions, box, pairs,
109107
Q_local, None, None, None,
110-
mScales, None, None, self.covalent_map,
108+
mScales, None, None,
111109
self.construct_local_frames, self.pme_recip,
112110
self.kappa, self.K1, self.K2, self.K3, self.lmax, False, lpme=self.lpme)
113111
return get_energy
@@ -116,7 +114,7 @@ def get_energy(positions, box, pairs, Q_local, mScales):
116114
def energy_fn(positions, box, pairs, Q_local, Uind_global, pol, tholes, mScales, pScales, dScales):
117115
return energy_pme(positions, box, pairs,
118116
Q_local, Uind_global, pol, tholes,
119-
mScales, pScales, dScales, self.covalent_map,
117+
mScales, pScales, dScales,
120118
self.construct_local_frames, self.pme_recip,
121119
self.kappa, self.K1, self.K2, self.K3, self.lmax, True, lpme=self.lpme)
122120
self.energy_fn = energy_fn
@@ -284,7 +282,7 @@ def setup_ewald_parameters(
284282
# @jit_condition(static_argnums=())
285283
def energy_pme(positions, box, pairs,
286284
Q_local, Uind_global, pol, tholes,
287-
mScales, pScales, dScales, covalent_map,
285+
mScales, pScales, dScales,
288286
construct_local_frame_fn, pme_recip_fn, kappa, K1, K2, K3, lmax, lpol, lpme=True):
289287
'''
290288
This is the top-level wrapper for multipole PME
@@ -306,7 +304,7 @@ def energy_pme(positions, box, pairs,
306304
(Nexcl,): multipole-multipole interaction exclusion scalings: 1-2, 1-3 ...
307305
for permanent-permanent, permanent-induced, induced-induced interactions
308306
pairs:
309-
Np * 2: interacting pair indices
307+
Np * 3: interacting pair indices and topology distance
310308
covalent_map:
311309
Na * Na: topological distances between atoms, if i, j are topologically distant, then covalent_map[i, j] == 0
312310
construct_local_frame_fn:
@@ -353,26 +351,24 @@ def energy_pme(positions, box, pairs,
353351

354352
if lpol:
355353
ene_real = pme_real(positions, box, pairs, Q_global, U_ind, pol, tholes,
356-
mScales, pScales, dScales, covalent_map, kappa, lmax, True)
354+
mScales, pScales, dScales, kappa, lmax, True)
357355
else:
358356
ene_real = pme_real(positions, box, pairs, Q_global, None, None, None,
359-
mScales, None, None, covalent_map, kappa, lmax, False)
357+
mScales, None, None, kappa, lmax, False)
360358

361359
if lpme:
362360
ene_recip = pme_recip_fn(positions, box, Q_global_tot)
363361
ene_self = pme_self(Q_global_tot, kappa, lmax)
364362

365363
if lpol:
366364
ene_self += pol_penalty(U_ind, pol)
367-
368365
return ene_real + ene_recip + ene_self
369366

370367
else:
371368
if lpol:
372369
ene_self = pol_penalty(U_ind, pol)
373370
else:
374371
ene_self = 0.0
375-
376372
return ene_real + ene_self
377373

378374

@@ -747,7 +743,7 @@ def pme_real_kernel(dr, qiQI, qiQJ, qiUindI, qiUindJ, thole1, thole2, dmp, mscal
747743
# @jit_condition(static_argnums=(7))
748744
def pme_real(positions, box, pairs,
749745
Q_global, Uind_global, pol, tholes,
750-
mScales, pScales, dScales, covalent_map,
746+
mScales, pScales, dScales,
751747
kappa, lmax, lpol):
752748
'''
753749
This is the real space PME calculate function
@@ -763,7 +759,7 @@ def pme_real(positions, box, pairs,
763759
box:
764760
3 * 3: box, axes arranged in row
765761
pairs:
766-
Np * 2: interacting pair indices
762+
Np * 3: interacting pair indices and topology distance
767763
Q_global:
768764
Na * (l+1)**2: harmonics multipoles of each atom, in global frame
769765
Uind_global:
@@ -786,14 +782,14 @@ def pme_real(positions, box, pairs,
786782
Output:
787783
ene: pme realspace energy
788784
'''
789-
pairs = regularize_pairs(pairs)
790-
buffer_scales = pair_buffer_scales(pairs)
785+
pairs = pairs.at[:, :2].set(regularize_pairs(pairs[:, :2]))
786+
buffer_scales = pair_buffer_scales(pairs[:, :2])
791787
box_inv = jnp.linalg.inv(box)
792788
r1 = distribute_v3(positions, pairs[:, 0])
793789
r2 = distribute_v3(positions, pairs[:, 1])
794790
Q_extendi = distribute_multipoles(Q_global, pairs[:, 0])
795791
Q_extendj = distribute_multipoles(Q_global, pairs[:, 1])
796-
nbonds = distribute_matrix(covalent_map,pairs[:, 0],pairs[:, 1])
792+
nbonds = pairs[:, 2]
797793
#nbonds = covalent_map[pairs[:, 0], pairs[:, 1]]
798794
indices = nbonds-1
799795
mscales = distribute_scalar(mScales, indices)

0 commit comments

Comments
 (0)