synth-dynamics 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- synth_dynamics/__init__.py +6 -0
- synth_dynamics/forcefield.py +57 -0
- synth_dynamics/integrator.py +39 -0
- synth_dynamics/simulation.py +40 -0
- synth_dynamics/system.py +28 -0
- synth_dynamics-0.1.0.dist-info/METADATA +100 -0
- synth_dynamics-0.1.0.dist-info/RECORD +10 -0
- synth_dynamics-0.1.0.dist-info/WHEEL +5 -0
- synth_dynamics-0.1.0.dist-info/licenses/LICENSE +21 -0
- synth_dynamics-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.spatial.distance import pdist, squareform
|
|
3
|
+
|
|
4
|
+
class ANMForceField:
|
|
5
|
+
def __init__(self, equilibrium_coords, cutoff=15.0, spring_constant=1.0):
|
|
6
|
+
"""
|
|
7
|
+
Anisotropic Network Model Force Field.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
equilibrium_coords: (N, 3) array of C-alpha equilibrium positions.
|
|
11
|
+
cutoff: Distance cutoff for interactions in Angstroms.
|
|
12
|
+
spring_constant: Uniform spring constant k.
|
|
13
|
+
"""
|
|
14
|
+
self.x0 = equilibrium_coords
|
|
15
|
+
self.cutoff = cutoff
|
|
16
|
+
self.k = spring_constant
|
|
17
|
+
self.n_atoms = len(equilibrium_coords)
|
|
18
|
+
|
|
19
|
+
# Precompute equilibrium distances and adjacency matrix
|
|
20
|
+
dist_matrix = squareform(pdist(self.x0))
|
|
21
|
+
self.adj = (dist_matrix < cutoff) & (dist_matrix > 0)
|
|
22
|
+
self.r0 = dist_matrix
|
|
23
|
+
|
|
24
|
+
def compute_forces(self, current_coords):
|
|
25
|
+
"""
|
|
26
|
+
Computes the harmonic forces on each atom.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
current_coords: (N, 3) array of current positions.
|
|
30
|
+
Returns:
|
|
31
|
+
forces: (N, 3) array of forces.
|
|
32
|
+
"""
|
|
33
|
+
# Calculate current distances and difference vectors
|
|
34
|
+
# diff[i, j] = r_i - r_j
|
|
35
|
+
diff = current_coords[:, np.newaxis, :] - current_coords[np.newaxis, :, :]
|
|
36
|
+
dist = np.linalg.norm(diff, axis=2)
|
|
37
|
+
|
|
38
|
+
# Avoid division by zero
|
|
39
|
+
dist_inv = np.zeros_like(dist)
|
|
40
|
+
mask = dist > 0
|
|
41
|
+
dist_inv[mask] = 1.0 / dist[mask]
|
|
42
|
+
|
|
43
|
+
# Force magnitude matrix: F_ij = -k * (r_ij - r0_ij)
|
|
44
|
+
# We only care about pairs in the adjacency matrix
|
|
45
|
+
force_mag = -self.k * (dist - self.r0)
|
|
46
|
+
|
|
47
|
+
# Full force vector matrix: F_vec_ij = F_ij * (diff_ij / dist_ij)
|
|
48
|
+
# Shape: (N, N, 3)
|
|
49
|
+
force_vecs = (force_mag * dist_inv)[:, :, np.newaxis] * diff
|
|
50
|
+
|
|
51
|
+
# Zero out inactive interactions
|
|
52
|
+
force_vecs[~self.adj] = 0.0
|
|
53
|
+
|
|
54
|
+
# Total force on atom i is sum over j
|
|
55
|
+
forces = np.sum(force_vecs, axis=1)
|
|
56
|
+
|
|
57
|
+
return forces
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
class LangevinIntegrator:
|
|
4
|
+
def __init__(self, dt=0.1, temperature=300.0, friction=1.0):
|
|
5
|
+
"""
|
|
6
|
+
Overdamped Langevin (Brownian) Integrator.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
dt: Time step.
|
|
10
|
+
temperature: Temperature in Kelvin.
|
|
11
|
+
friction: Friction coefficient (gamma).
|
|
12
|
+
"""
|
|
13
|
+
self.dt = dt
|
|
14
|
+
self.T = temperature
|
|
15
|
+
self.gamma = friction
|
|
16
|
+
self.kb = 0.0019872041 # Boltzmann constant in kcal/(mol*K)
|
|
17
|
+
|
|
18
|
+
def step(self, positions, forces):
|
|
19
|
+
"""
|
|
20
|
+
Performs a single integration step.
|
|
21
|
+
|
|
22
|
+
x(t+dt) = x(t) + (dt/gamma) * F + sqrt(2 * kb * T * dt / gamma) * R
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
positions: (N, 3) array of current positions.
|
|
26
|
+
forces: (N, 3) array of current forces.
|
|
27
|
+
Returns:
|
|
28
|
+
new_positions: (N, 3) array of updated positions.
|
|
29
|
+
"""
|
|
30
|
+
n_atoms = positions.shape[0]
|
|
31
|
+
|
|
32
|
+
# Deterministic term
|
|
33
|
+
drift = (self.dt / self.gamma) * forces
|
|
34
|
+
|
|
35
|
+
# Stochastic term
|
|
36
|
+
sigma = np.sqrt(2.0 * self.kb * self.T * self.dt / self.gamma)
|
|
37
|
+
random_force = sigma * np.random.normal(size=(n_atoms, 3))
|
|
38
|
+
|
|
39
|
+
return positions + drift + random_force
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import MDAnalysis as mda
|
|
2
|
+
from .system import System
|
|
3
|
+
from .forcefield import ANMForceField
|
|
4
|
+
from .integrator import LangevinIntegrator
|
|
5
|
+
|
|
6
|
+
class Simulation:
|
|
7
|
+
def __init__(self, system, forcefield, integrator):
|
|
8
|
+
"""
|
|
9
|
+
Orchestrates the Langevin dynamics simulation.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
system: System instance.
|
|
13
|
+
forcefield: ForceField instance.
|
|
14
|
+
integrator: Integrator instance.
|
|
15
|
+
"""
|
|
16
|
+
self.system = system
|
|
17
|
+
self.ff = forcefield
|
|
18
|
+
self.integrator = integrator
|
|
19
|
+
|
|
20
|
+
def run(self, n_steps, output_path, stride=10):
|
|
21
|
+
"""
|
|
22
|
+
Runs the simulation and saves the trajectory.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
n_steps: Total number of integration steps.
|
|
26
|
+
output_path: Path to save the trajectory (e.g., .dcd or .pdb).
|
|
27
|
+
stride: Frequency of saving frames.
|
|
28
|
+
"""
|
|
29
|
+
writer = mda.Writer(output_path, self.system.n_atoms)
|
|
30
|
+
|
|
31
|
+
for i in range(n_steps):
|
|
32
|
+
forces = self.ff.compute_forces(self.system.positions)
|
|
33
|
+
new_positions = self.integrator.step(self.system.positions, forces)
|
|
34
|
+
self.system.positions = new_positions
|
|
35
|
+
|
|
36
|
+
if i % stride == 0:
|
|
37
|
+
writer.write(self.system.ca_atoms)
|
|
38
|
+
|
|
39
|
+
writer.close()
|
|
40
|
+
print(f"Simulation complete. Trajectory saved to {output_path}")
|
synth_dynamics/system.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import MDAnalysis as mda
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
class System:
|
|
5
|
+
def __init__(self, pdb_path: str):
|
|
6
|
+
"""
|
|
7
|
+
Initializes the system by loading a PDB file and extracting C-alpha atoms.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
pdb_path: Path to the input PDB file.
|
|
11
|
+
"""
|
|
12
|
+
self.universe = mda.Universe(pdb_path)
|
|
13
|
+
self.ca_atoms = self.universe.select_atoms("name CA")
|
|
14
|
+
|
|
15
|
+
if len(self.ca_atoms) == 0:
|
|
16
|
+
raise ValueError(f"No C-alpha atoms found in {pdb_path}")
|
|
17
|
+
|
|
18
|
+
# Store equilibrium coordinates (reference state)
|
|
19
|
+
self.equilibrium_coords = self.ca_atoms.positions.copy()
|
|
20
|
+
self.n_atoms = len(self.ca_atoms)
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def positions(self):
|
|
24
|
+
return self.ca_atoms.positions
|
|
25
|
+
|
|
26
|
+
@positions.setter
|
|
27
|
+
def positions(self, new_positions):
|
|
28
|
+
self.ca_atoms.positions = new_positions
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: synth-dynamics
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Time-Resolved Ensemble Generator using Coarse-Grained ENM Langevin Dynamics
|
|
5
|
+
Author: George Elkins
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/georgeelkins/synth-dynamics
|
|
8
|
+
Project-URL: Repository, https://github.com/georgeelkins/synth-dynamics
|
|
9
|
+
Project-URL: Documentation, https://synth-dynamics.readthedocs.io/
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/georgeelkins/synth-dynamics/issues
|
|
11
|
+
Keywords: molecular-dynamics,protein-dynamics,ANM,Langevin,ensemble-generation
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: numpy
|
|
25
|
+
Requires-Dist: MDAnalysis
|
|
26
|
+
Requires-Dist: scipy
|
|
27
|
+
Provides-Extra: docs
|
|
28
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
29
|
+
Requires-Dist: sphinx_rtd_theme; extra == "docs"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# synth-dynamics: Time-Resolved Ensemble Generator
|
|
33
|
+
|
|
34
|
+
`synth-dynamics` is a fast, lightweight molecular dynamics engine designed to generate meaningful conformational ensembles of proteins. Unlike full-atom simulations (like GROMACS or Amber), `synth-dynamics` uses a **Coarse-Grained Anisotropic Network Model (ANM)** and **Langevin dynamics** to capture the essential global motions of proteins with minimal computational overhead.
|
|
35
|
+
|
|
36
|
+
This tool is designed to bridge the gap between static structures and time-averaged experimental observables, such as NMR relaxation parameters, SAXS Kratky plots, or FRET efficiency distributions.
|
|
37
|
+
|
|
38
|
+
## Key Features
|
|
39
|
+
|
|
40
|
+
- **Coarse-Grained Simulation**: Models proteins using C-alpha atoms and harmonic "spring" networks.
|
|
41
|
+
- **Fast Langevin Engine**: Propagates coordinates using a stable, overdamped Langevin integrator.
|
|
42
|
+
- **Experimental Integration**: Perfect for generating the structural ensembles needed for `synth-nmr` or `synth-saxs`.
|
|
43
|
+
- **Easy to Use**: Simple API for loading PDBs, configuring forcefields, and running simulations.
|
|
44
|
+
- **Extensively Tested**: 100% test coverage ensuring reliability and correctness.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
`synth-dynamics` requires Python 3.10+ and the following dependencies:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install numpy MDAnalysis scipy
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
To install the documentation theme:
|
|
55
|
+
```bash
|
|
56
|
+
pip install sphinx_rtd_theme
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Quick Start
|
|
60
|
+
|
|
61
|
+
Running a simulation is straightforward:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from synth_dynamics import System, ANMForceField, LangevinIntegrator, Simulation
|
|
65
|
+
|
|
66
|
+
# 1. Load the system (automatically filters for C-alpha atoms)
|
|
67
|
+
system = System("protein.pdb")
|
|
68
|
+
|
|
69
|
+
# 2. Define the Anisotropic Network Model forcefield
|
|
70
|
+
# Cutoff (15A) and spring constant determine the flexibility
|
|
71
|
+
ff = ANMForceField(system.equilibrium_coords, cutoff=15.0, spring_constant=1.0)
|
|
72
|
+
|
|
73
|
+
# 3. Initialize the Langevin integrator (dt in fs, T in Kelvin)
|
|
74
|
+
integrator = LangevinIntegrator(dt=0.1, temperature=300.0, friction=1.0)
|
|
75
|
+
|
|
76
|
+
# 4. Run and save the trajectory
|
|
77
|
+
sim = Simulation(system, ff, integrator)
|
|
78
|
+
sim.run(n_steps=1000, output_path="trajectory.dcd", stride=10)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Documentation
|
|
82
|
+
|
|
83
|
+
Full API documentation and usage guides are available in the `docs/` directory. You can build the HTML documentation locally:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
cd docs
|
|
87
|
+
sphinx-build -b html . _build/html
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Testing
|
|
91
|
+
|
|
92
|
+
To run the test suite and verify coverage:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
PYTHONPATH=. pytest --cov=synth_dynamics tests/
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
This project is licensed under the MIT License - see the LICENSE file for details (if applicable).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
synth_dynamics/__init__.py,sha256=o_OJWCEU3hUTQY0y9NhT_UikVJZEv8HWAEJj5DrPAcI,218
|
|
2
|
+
synth_dynamics/forcefield.py,sha256=ZXJ9soNd52pYUnO4oLxR-CKESNHcdgtlzm2iQFrYLXE,2044
|
|
3
|
+
synth_dynamics/integrator.py,sha256=TiVLtxgAARtLEUKoTJnb5DS7DOJWpf74acS0qRe1etU,1251
|
|
4
|
+
synth_dynamics/simulation.py,sha256=h0x6QT4s5DrsL0Zhbx0yZwH84GI62XNhc0gN3AVSLys,1383
|
|
5
|
+
synth_dynamics/system.py,sha256=xwzOwXf7_wFUSE27A5_Ndd58bMSaPW3H7EqyEHcgGJE,878
|
|
6
|
+
synth_dynamics-0.1.0.dist-info/licenses/LICENSE,sha256=vhOY8MmX_KvSe6pbSfjbPEoceqNPv_Hoaz6BXrKS6rI,1070
|
|
7
|
+
synth_dynamics-0.1.0.dist-info/METADATA,sha256=h1H5Wmyqf1TrmndGlCCOzHHDsDoQufYUu_jObELE1FE,3817
|
|
8
|
+
synth_dynamics-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
9
|
+
synth_dynamics-0.1.0.dist-info/top_level.txt,sha256=Hg-LJU210eYBuiyW4HYA3g82mAgNmZYcUBksa4Gii3I,15
|
|
10
|
+
synth_dynamics-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 George Elkins
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
synth_dynamics
|