packmol-memgen-minimal 1.1.16__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.
- packmol_memgen/__init__.py +2 -0
- packmol_memgen/__version__.py +34 -0
- packmol_memgen/data/LICENSE.Apache-2.0 +201 -0
- packmol_memgen/data/extra_solvents.lib +789 -0
- packmol_memgen/data/frcmod.lipid_ext +97 -0
- packmol_memgen/data/frcmod.solvents +129 -0
- packmol_memgen/data/insane_lipids.txt +138 -0
- packmol_memgen/data/insane_solvents.txt +45 -0
- packmol_memgen/data/leaprc.extra_solvents +42 -0
- packmol_memgen/data/leaprc.lipid_ext +48 -0
- packmol_memgen/data/lipid_ext.lib +12312 -0
- packmol_memgen/data/martini_v3.0.0.itp +356605 -0
- packmol_memgen/data/memgen.parm +4082 -0
- packmol_memgen/data/pdbs.tar.gz +0 -0
- packmol_memgen/data/solvent.parm +14 -0
- packmol_memgen/example/example.sh +31 -0
- packmol_memgen/lib/__init__.py +0 -0
- packmol_memgen/lib/amber.py +77 -0
- packmol_memgen/lib/charmmlipid2amber/__init__.py +0 -0
- packmol_memgen/lib/charmmlipid2amber/charmmlipid2amber.csv +7164 -0
- packmol_memgen/lib/charmmlipid2amber/charmmlipid2amber.py +225 -0
- packmol_memgen/lib/pdbremix/LICENSE +21 -0
- packmol_memgen/lib/pdbremix/__init__.py +0 -0
- packmol_memgen/lib/pdbremix/_version.py +1 -0
- packmol_memgen/lib/pdbremix/amber.py +1103 -0
- packmol_memgen/lib/pdbremix/asa.py +227 -0
- packmol_memgen/lib/pdbremix/data/aminoacid.pdb +334 -0
- packmol_memgen/lib/pdbremix/data/binaries.json +26 -0
- packmol_memgen/lib/pdbremix/data/charmm22.parameter +2250 -0
- packmol_memgen/lib/pdbremix/data/charmm22.topology +1635 -0
- packmol_memgen/lib/pdbremix/data/color_b.py +682 -0
- packmol_memgen/lib/pdbremix/data/hin.lib +130 -0
- packmol_memgen/lib/pdbremix/data/hydroxide.lib +88 -0
- packmol_memgen/lib/pdbremix/data/make_chi.py +92 -0
- packmol_memgen/lib/pdbremix/data/opls.parameter +1108 -0
- packmol_memgen/lib/pdbremix/data/opls.topology +1869 -0
- packmol_memgen/lib/pdbremix/data/phd.frcmod +82 -0
- packmol_memgen/lib/pdbremix/data/phd.leaprc +4 -0
- packmol_memgen/lib/pdbremix/data/phd.prepin +35 -0
- packmol_memgen/lib/pdbremix/data/template.pdb +334 -0
- packmol_memgen/lib/pdbremix/data/znb.frcmod +24 -0
- packmol_memgen/lib/pdbremix/data/znb.leaprc +7 -0
- packmol_memgen/lib/pdbremix/data/znb.lib +69 -0
- packmol_memgen/lib/pdbremix/data.py +264 -0
- packmol_memgen/lib/pdbremix/fetch.py +102 -0
- packmol_memgen/lib/pdbremix/force.py +627 -0
- packmol_memgen/lib/pdbremix/gromacs.py +978 -0
- packmol_memgen/lib/pdbremix/lib/__init__.py +0 -0
- packmol_memgen/lib/pdbremix/lib/docopt.py +579 -0
- packmol_memgen/lib/pdbremix/lib/pyqcprot.py +305 -0
- packmol_memgen/lib/pdbremix/namd.py +1078 -0
- packmol_memgen/lib/pdbremix/pdbatoms.py +543 -0
- packmol_memgen/lib/pdbremix/pdbtext.py +120 -0
- packmol_memgen/lib/pdbremix/protein.py +311 -0
- packmol_memgen/lib/pdbremix/pymol.py +480 -0
- packmol_memgen/lib/pdbremix/rmsd.py +203 -0
- packmol_memgen/lib/pdbremix/simulate.py +420 -0
- packmol_memgen/lib/pdbremix/spacehash.py +73 -0
- packmol_memgen/lib/pdbremix/trajectory.py +286 -0
- packmol_memgen/lib/pdbremix/util.py +273 -0
- packmol_memgen/lib/pdbremix/v3.py +16 -0
- packmol_memgen/lib/pdbremix/v3array.py +482 -0
- packmol_memgen/lib/pdbremix/v3numpy.py +350 -0
- packmol_memgen/lib/pdbremix/volume.py +155 -0
- packmol_memgen/lib/utils.py +1017 -0
- packmol_memgen/main.py +2827 -0
- packmol_memgen_minimal-1.1.16.dist-info/METADATA +664 -0
- packmol_memgen_minimal-1.1.16.dist-info/RECORD +71 -0
- packmol_memgen_minimal-1.1.16.dist-info/WHEEL +4 -0
- packmol_memgen_minimal-1.1.16.dist-info/entry_points.txt +2 -0
- packmol_memgen_minimal-1.1.16.dist-info/licenses/LICENSE +338 -0
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
__doc__ = """
|
|
4
|
+
|
|
5
|
+
Generate forces for steered molecular-dynamics using PUFF.
|
|
6
|
+
|
|
7
|
+
This module provides functions to generate forces by inducing
|
|
8
|
+
velocity changes in Soup objects. This can be saved to restart
|
|
9
|
+
files of MD simulations for AMBER, NAMD and GROMACS.
|
|
10
|
+
|
|
11
|
+
There are four functions that are to be used:
|
|
12
|
+
|
|
13
|
+
1. make_atd_fn(i_residue, heating_temperature, backbone_atoms)
|
|
14
|
+
|
|
15
|
+
2. make_puff_fn(
|
|
16
|
+
domain1, domain2, target_val, dt=0.1, temperature=None,
|
|
17
|
+
is_backbone_only=False, is_first_domain_only=False,
|
|
18
|
+
force_fname='md.puff.out')
|
|
19
|
+
|
|
20
|
+
3. make_puff_acc_fn(
|
|
21
|
+
domain1, domain2, target_val, dt=0.1, temperature=None,
|
|
22
|
+
is_backbone_only=False, is_first_domain_only=False,
|
|
23
|
+
force_fname='md.puff.out')
|
|
24
|
+
|
|
25
|
+
4. make_rip_fn(i_res, heating_temperature)
|
|
26
|
+
|
|
27
|
+
These are all function factories to generate a function in the
|
|
28
|
+
form:
|
|
29
|
+
|
|
30
|
+
def pulse_fn(soup):
|
|
31
|
+
# change soup velocities
|
|
32
|
+
|
|
33
|
+
The calculations of velocities and energies used here are
|
|
34
|
+
mainly:
|
|
35
|
+
|
|
36
|
+
- velocity: Ångstrom/ps
|
|
37
|
+
- work/energy: Da*angs/ps^2
|
|
38
|
+
- mass: Da
|
|
39
|
+
|
|
40
|
+
However, various conversions are needed to connect with various
|
|
41
|
+
thermostatistical identites as well as for output.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
import os
|
|
46
|
+
import random
|
|
47
|
+
import math
|
|
48
|
+
import copy
|
|
49
|
+
|
|
50
|
+
from . import util
|
|
51
|
+
from . import pdbatoms
|
|
52
|
+
from . import v3
|
|
53
|
+
from . import data
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
##########################################################
|
|
57
|
+
# Unit conversions
|
|
58
|
+
|
|
59
|
+
# force = Da⋅Å/ps/ps
|
|
60
|
+
# = 1.66E-13 kg⋅m/s/s
|
|
61
|
+
# = 1.66E-13 N
|
|
62
|
+
# = 1.66E-1 pN
|
|
63
|
+
|
|
64
|
+
# Work/energy conversions
|
|
65
|
+
# = Da⋅Å⋅Å/ps/ps
|
|
66
|
+
# = Da⋅Å/ps/ps ⋅ Å
|
|
67
|
+
# = 1.66E-1 pNÅ
|
|
68
|
+
work_DaAngSqPerPsSq_to_pNAng = 1.66E-1
|
|
69
|
+
work_Nm_to_kcal = 0.000238846
|
|
70
|
+
avogadro = 6.02214179E23
|
|
71
|
+
work_pNAng_to_kcalPerMol = 1E-12*1E-10*work_Nm_to_kcal*avogadro
|
|
72
|
+
|
|
73
|
+
# molecular-dynamics integration time-step
|
|
74
|
+
timestep_in_ps = 0.001
|
|
75
|
+
|
|
76
|
+
# Velocity conversions
|
|
77
|
+
vel_mPerS_to_AngsPerPs = 0.01
|
|
78
|
+
velsq_mPerS_to_AngsPerPs = 1.0E-4
|
|
79
|
+
|
|
80
|
+
# Boltzmann constant
|
|
81
|
+
# = 1.3806488E-23 J/K
|
|
82
|
+
# = 1.3806488E-23 kg ⋅ m^2/s^2/K
|
|
83
|
+
# = 1.3806488E-23 1/1.66E-27Da ⋅ m^2/s^2/K
|
|
84
|
+
# = 8314.47148 Da⋅m^2/s^2/K
|
|
85
|
+
boltzmann_in_DaMSqPerSSqPerK = 8314.47148
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
##########################################################
|
|
89
|
+
# Heating functions
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def average_vel(atoms):
|
|
93
|
+
"""
|
|
94
|
+
Returns the mass-averaged velocity of atoms.
|
|
95
|
+
"""
|
|
96
|
+
momentum = v3.vector()
|
|
97
|
+
mass = 0.0
|
|
98
|
+
for a in atoms:
|
|
99
|
+
momentum += v3.scale(a.vel, a.mass)
|
|
100
|
+
mass += a.mass
|
|
101
|
+
return v3.scale(momentum, 1.0/mass)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def add_vel_to_atoms(atoms, vel_diff):
|
|
105
|
+
"""
|
|
106
|
+
Adds vel_diff to the vel vector of atoms.
|
|
107
|
+
"""
|
|
108
|
+
for a in atoms:
|
|
109
|
+
a.vel += vel_diff
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def maxwell_velocity(temperature, mass):
|
|
113
|
+
"""
|
|
114
|
+
Returns a velocity (in angs/ps) sampled from a Maxwell velocity
|
|
115
|
+
distribution determined by mass and temperature.
|
|
116
|
+
"""
|
|
117
|
+
velsq_ave = boltzmann_in_DaMSqPerSSqPerK * temperature / mass
|
|
118
|
+
return random.gauss(0, math.sqrt(velsq_ave)) * \
|
|
119
|
+
vel_mPerS_to_AngsPerPs
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def mean_energy(temperature, n_degree_of_freedom):
|
|
123
|
+
"""
|
|
124
|
+
Returns the average energy (Da*angs/ps^2) of n degree of
|
|
125
|
+
freedom at temperature. if n_degree_of_freedom = 3,
|
|
126
|
+
this is the average energy of a point particle.
|
|
127
|
+
"""
|
|
128
|
+
return 0.5 * n_degree_of_freedom * temperature * \
|
|
129
|
+
boltzmann_in_DaMSqPerSSqPerK * \
|
|
130
|
+
velsq_mPerS_to_AngsPerPs
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def random_energy(temperature, n_degree_of_freedom):
|
|
134
|
+
"""
|
|
135
|
+
Returns an energy (Da*angs/ps^2) sampled from a Maxwellian
|
|
136
|
+
distribution of energies at temperature.
|
|
137
|
+
"""
|
|
138
|
+
average = mean_energy(temperature, n_degree_of_freedom);
|
|
139
|
+
std_dev = math.sqrt(average)
|
|
140
|
+
return random.gauss(average, std_dev)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def kinetic_energy(atoms):
|
|
144
|
+
"""
|
|
145
|
+
Returns the kinetic energy (Da*angs/ps^2) of the atoms.
|
|
146
|
+
"""
|
|
147
|
+
en = 0.0
|
|
148
|
+
for a in atoms:
|
|
149
|
+
vel = v3.mag(a.vel)
|
|
150
|
+
en += 0.5 * a.mass * vel * vel
|
|
151
|
+
return en
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def gas_randomize(atoms, temperature):
|
|
155
|
+
"""
|
|
156
|
+
Randomly assigns a velocity to atoms based on a Maxwellian
|
|
157
|
+
distribution at temperature.
|
|
158
|
+
"""
|
|
159
|
+
for atom in atoms:
|
|
160
|
+
v3.set_vector(
|
|
161
|
+
atom.vel,
|
|
162
|
+
maxwell_velocity(temperature, atom.mass),
|
|
163
|
+
maxwell_velocity(temperature, atom.mass),
|
|
164
|
+
maxwell_velocity(temperature, atom.mass))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def make_atd_fn(i_residue, heating_temperature, backbone_atoms):
|
|
168
|
+
"""
|
|
169
|
+
Returns pulse_fn that locally heats a sidechain.
|
|
170
|
+
"""
|
|
171
|
+
# This method is called the Anisotropic Thermal Diffusion and
|
|
172
|
+
# was originally desiged by Ota & Agard "Intramolecular
|
|
173
|
+
# Signaling Pathways Revealed by Modeling Anisotropic Thermal
|
|
174
|
+
# Diffusion" JMB (2005) 12:345.
|
|
175
|
+
|
|
176
|
+
def gas_heat_sidechain(
|
|
177
|
+
soup, i_residue, heating_temperature, backbone_atoms):
|
|
178
|
+
atoms = [a for a in soup.residue(i_residue).atoms()
|
|
179
|
+
if a.type not in backbone_atoms]
|
|
180
|
+
gas_randomize(atoms, heating_temperature)
|
|
181
|
+
|
|
182
|
+
return lambda soup: gas_heat_sidechain(
|
|
183
|
+
soup, i_residue, heating_temperature, backbone_atoms)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def anderson_velocity_scale(atoms, temperature, n_degree_of_freedom):
|
|
187
|
+
"""
|
|
188
|
+
Scales the velocity of atoms such that average energy
|
|
189
|
+
is consistent with the temperature.
|
|
190
|
+
"""
|
|
191
|
+
# This is the classic Anderson approach to temperature
|
|
192
|
+
# regulation. Whilst deterministic, can be easily trapped in
|
|
193
|
+
# local minima.
|
|
194
|
+
target_energy = mean_energy(temperature, n_degree_of_freedom)
|
|
195
|
+
kin = kinetic_energy(atoms)
|
|
196
|
+
if v3.is_similar_mag(kin, 0):
|
|
197
|
+
gas_randomize(atoms, temperature)
|
|
198
|
+
else:
|
|
199
|
+
scaling_factor = math.sqrt(target_energy / kin)
|
|
200
|
+
for atom in atoms:
|
|
201
|
+
v3.set_vector(atom.vel, v3.scale(atom.vel, scaling_factor))
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
##########################################################
|
|
206
|
+
# Pushing functions
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def get_atoms_of_residues(soup, res_indices, atom_types=None):
|
|
210
|
+
"""
|
|
211
|
+
Return atoms of soup that belong to residues indicated by
|
|
212
|
+
res_indices and in atom_types.
|
|
213
|
+
"""
|
|
214
|
+
atoms = []
|
|
215
|
+
for i in res_indices:
|
|
216
|
+
res_atoms = soup.residue(i).atoms()
|
|
217
|
+
if atom_types:
|
|
218
|
+
res_atoms = [a for a in res_atoms if a.type in atom_types]
|
|
219
|
+
atoms.extend(res_atoms)
|
|
220
|
+
return atoms
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class PushApartByVel():
|
|
224
|
+
"""
|
|
225
|
+
Strategy to push apart two domains in a Soup.
|
|
226
|
+
|
|
227
|
+
This class is designed to be initialized by make_puff_fn(),
|
|
228
|
+
which returns the apply method of this object to the pulse()
|
|
229
|
+
function of simulate.py
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
def __init__(
|
|
233
|
+
self, domain1, domain2, target_val, dt=0.1,
|
|
234
|
+
temperature=None, is_backbone_only=False,
|
|
235
|
+
is_first_domain_only=True,
|
|
236
|
+
force_fname='md.puff.out'):
|
|
237
|
+
self.domain1 = domain1
|
|
238
|
+
self.domain2 = domain2
|
|
239
|
+
self.dt = dt
|
|
240
|
+
self.target_val = target_val
|
|
241
|
+
self.temperature = temperature
|
|
242
|
+
self.force_fname = os.path.abspath(force_fname)
|
|
243
|
+
self.is_backbone_only = is_backbone_only
|
|
244
|
+
self.is_first_domain_only = is_first_domain_only
|
|
245
|
+
|
|
246
|
+
def setup_domains(self):
|
|
247
|
+
# scale the temperature (necessary at high pulling speeds)
|
|
248
|
+
if self.temperature:
|
|
249
|
+
atoms = self.soup.atoms()
|
|
250
|
+
anderson_velocity_scale(atoms, self.temperature, 3*len(atoms))
|
|
251
|
+
|
|
252
|
+
# select the atoms from the domains definition
|
|
253
|
+
selection = ['CA'] if self.is_backbone_only else None
|
|
254
|
+
self.atoms1 = get_atoms_of_residues(self.soup, self.domain1, selection)
|
|
255
|
+
self.atoms2 = get_atoms_of_residues(self.soup, self.domain2, selection)
|
|
256
|
+
|
|
257
|
+
# get direction vectors based on domains
|
|
258
|
+
self.disp2to1 = pdbatoms.get_center(self.atoms1) \
|
|
259
|
+
- pdbatoms.get_center(self.atoms2)
|
|
260
|
+
self.axis2to1 = v3.norm(self.disp2to1)
|
|
261
|
+
|
|
262
|
+
# calculate relative velocities between domains
|
|
263
|
+
self.vel2to1 = average_vel(self.atoms1) - average_vel(self.atoms2)
|
|
264
|
+
self.axis_vel2to1 = v3.parallel(self.vel2to1, self.axis2to1)
|
|
265
|
+
self.vel = v3.dot(self.axis_vel2to1, self.axis2to1)
|
|
266
|
+
|
|
267
|
+
if self.is_first_domain_only:
|
|
268
|
+
self.move_atoms = self.atoms1
|
|
269
|
+
else:
|
|
270
|
+
self.move_atoms = self.atoms1 + self.atoms2
|
|
271
|
+
|
|
272
|
+
def change_vels(self):
|
|
273
|
+
# calculate the vel diff vector
|
|
274
|
+
self.old_kinetic_energy = kinetic_energy(self.move_atoms)
|
|
275
|
+
|
|
276
|
+
vel_target = self.target_val*self.axis2to1
|
|
277
|
+
if self.is_first_domain_only:
|
|
278
|
+
change_sets = [(self.atoms1, vel_target)]
|
|
279
|
+
else:
|
|
280
|
+
change_sets = [
|
|
281
|
+
(self.atoms1, 0.5*vel_target),
|
|
282
|
+
(self.atoms2, -0.5*vel_target)]
|
|
283
|
+
|
|
284
|
+
# now change velocities of movable atoms
|
|
285
|
+
for move_atoms, vel_target in change_sets:
|
|
286
|
+
for a in move_atoms:
|
|
287
|
+
vel_axis = v3.parallel(a.vel, self.axis2to1)
|
|
288
|
+
vel_diff = vel_target - vel_axis
|
|
289
|
+
a.vel += vel_diff
|
|
290
|
+
|
|
291
|
+
self.kinetic_energy = kinetic_energy(self.move_atoms)
|
|
292
|
+
|
|
293
|
+
def calculate_output(self):
|
|
294
|
+
work_applied = self.kinetic_energy - self.old_kinetic_energy
|
|
295
|
+
work_applied *= work_DaAngSqPerPsSq_to_pNAng
|
|
296
|
+
work_applied *= work_pNAng_to_kcalPerMol
|
|
297
|
+
self.output_dict = {
|
|
298
|
+
'separation': v3.mag(self.disp2to1),
|
|
299
|
+
'mass': sum(a.mass for a in self.move_atoms),
|
|
300
|
+
'target_vel': self.target_val,
|
|
301
|
+
'vel': self.vel,
|
|
302
|
+
'work_applied': work_applied
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
def append_output(self):
|
|
306
|
+
with open(self.force_fname, 'a') as f:
|
|
307
|
+
out_s = str(self.output_dict)
|
|
308
|
+
if not out_s.endswith('\n'):
|
|
309
|
+
out_s += '\n'
|
|
310
|
+
f.write(out_s)
|
|
311
|
+
|
|
312
|
+
def apply(self, soup):
|
|
313
|
+
"""
|
|
314
|
+
Apply velocity changes to the soup that will induce the
|
|
315
|
+
pulling. This is the key method that will be exported to the
|
|
316
|
+
pulse() function of simulate.py.
|
|
317
|
+
"""
|
|
318
|
+
self.soup = soup
|
|
319
|
+
self.setup_domains()
|
|
320
|
+
self.change_vels()
|
|
321
|
+
self.calculate_output()
|
|
322
|
+
self.append_output()
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def make_puff_fn(
|
|
326
|
+
domain1, domain2, target_val, dt=0.1, temperature=None,
|
|
327
|
+
is_backbone_only=False, is_first_domain_only=False,
|
|
328
|
+
force_fname='md.puff.out'):
|
|
329
|
+
"""
|
|
330
|
+
Returns a pulse_fn implemented by PushApartByVel.
|
|
331
|
+
"""
|
|
332
|
+
strategy = PushApartByVel(
|
|
333
|
+
domain1, domain2, target_val, dt, temperature,
|
|
334
|
+
is_backbone_only, is_first_domain_only,
|
|
335
|
+
force_fname)
|
|
336
|
+
pulse_fn = lambda soup: strategy.apply(soup)
|
|
337
|
+
return pulse_fn
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def read_puff_out(md_dir):
|
|
341
|
+
"""
|
|
342
|
+
Yields a dictionary representing the properties of
|
|
343
|
+
PushApartByVel at each frame of a pulsed simulation from the
|
|
344
|
+
specified md.puff.out file.
|
|
345
|
+
"""
|
|
346
|
+
# get time in ps, typical MD step is 0.001 ps = 1 fs
|
|
347
|
+
config = os.path.join(md_dir, 'md.puff.config')
|
|
348
|
+
parms = util.read_dict(config)
|
|
349
|
+
dt = 0.001*parms['n_step_per_pulse']
|
|
350
|
+
time = 0.0
|
|
351
|
+
for line in open(os.path.join(md_dir, 'md.puff.out')):
|
|
352
|
+
entry = eval(line)
|
|
353
|
+
entry['time'] = time
|
|
354
|
+
yield entry
|
|
355
|
+
time += dt
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class PushApartByAcc(PushApartByVel):
|
|
359
|
+
"""
|
|
360
|
+
Strategy to push apart two domains with constant acceleration.
|
|
361
|
+
|
|
362
|
+
This is designed to be instantiated by make_puff_acc_fn().
|
|
363
|
+
"""
|
|
364
|
+
def change_vels(self):
|
|
365
|
+
# calculate the vel diff vector to apply
|
|
366
|
+
diff_vel = self.target_val * self.dt
|
|
367
|
+
diff_axis_vel2to1 = v3.scale(self.axis2to1, diff_vel)
|
|
368
|
+
self.vel_diff = v3.dot(diff_axis_vel2to1, self.axis2to1)
|
|
369
|
+
if self.is_first_domain_only:
|
|
370
|
+
add_vel_to_atoms(self.atoms1, diff_axis_vel2to1)
|
|
371
|
+
else:
|
|
372
|
+
# apply half of vel_diff to each domain
|
|
373
|
+
diff_axis_vel2to1 = v3.scale(diff_axis_vel2to1, 0.5)
|
|
374
|
+
add_vel_to_atoms(self.atoms1, diff_axis_vel2to1)
|
|
375
|
+
add_vel_to_atoms(self.atoms2, -diff_axis_vel2to1)
|
|
376
|
+
|
|
377
|
+
def calculate_output(self):
|
|
378
|
+
PushApartByVel.calculate_output(self)
|
|
379
|
+
self.output_dict['target_acc'] = self.target_val
|
|
380
|
+
del self.output_dict['target_vel']
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def make_puff_acc_fn(
|
|
384
|
+
domain1, domain2, target_val, dt=0.1, temperature=None,
|
|
385
|
+
is_backbone_only=False, is_first_domain_only=False,
|
|
386
|
+
force_fname='md.puff.out'):
|
|
387
|
+
"""
|
|
388
|
+
Returns pulse_fn that implements PushApartByAcc.
|
|
389
|
+
"""
|
|
390
|
+
strategy = PushApartByAcc(
|
|
391
|
+
domain1, domain2, target_val, dt, temperature,
|
|
392
|
+
is_backbone_only, is_first_domain_only, force_fname)
|
|
393
|
+
pulse_fn = lambda soup: strategy.apply(soup)
|
|
394
|
+
return pulse_fn
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
##########################################################
|
|
398
|
+
# 3. Rotation functions
|
|
399
|
+
|
|
400
|
+
# Rotational Units
|
|
401
|
+
# --------------------------------------------------------
|
|
402
|
+
# rotational-velocity:
|
|
403
|
+
# 1 º/ps = E+12 º/s
|
|
404
|
+
# rotational-acceleration:
|
|
405
|
+
# 1 º/ps/ps = E+24º/s/s
|
|
406
|
+
# moment-of-inertia = 1 Da⋅Å⋅Å
|
|
407
|
+
# = 1.66E-27 kg⋅E-10m⋅E-10m
|
|
408
|
+
# = 1.66E-47 kg⋅m^2
|
|
409
|
+
# torque = moment-of-inertia * rotational-acceleration
|
|
410
|
+
# = Da⋅Å⋅Å⋅º/ps/ps
|
|
411
|
+
# = 1.66E-47 kg⋅m^2 ⋅ E+24⋅º/s/s
|
|
412
|
+
# = 1.66E-23 º⋅m⋅kg⋅m/s/s
|
|
413
|
+
# = 1.66E-23 º⋅m⋅N
|
|
414
|
+
# force = torque/radius
|
|
415
|
+
# = Da⋅Å⋅Å⋅º/ps/ps / Å
|
|
416
|
+
# = 1.66E-23 m⋅N/E-10m
|
|
417
|
+
# = 1.66E-13 N
|
|
418
|
+
# = 1.66 E-1 pN
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def moment_of_inertia(atom, axis, anchor):
|
|
422
|
+
"""
|
|
423
|
+
Returns the moment (DaAng^^2) of atom around axis at anchor.
|
|
424
|
+
"""
|
|
425
|
+
r = atom.pos - anchor
|
|
426
|
+
r_perp = v3.perpendicular(r, axis)
|
|
427
|
+
r_len = v3.mag(r_perp)
|
|
428
|
+
return atom.mass * r_len * r_len
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def total_moment_of_inertia(atoms, axis, anchor):
|
|
432
|
+
"""
|
|
433
|
+
Returns the total moment (DaAng^^2) of atomss around axis at
|
|
434
|
+
anchor.
|
|
435
|
+
"""
|
|
436
|
+
moments = [moment_of_inertia(atom, axis, anchor)
|
|
437
|
+
for atom in atoms]
|
|
438
|
+
return sum(moments)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def rotational_velocity(atom, axis, anchor):
|
|
442
|
+
"""
|
|
443
|
+
Returns the rotational velocity (rad/ps) of the atom connected
|
|
444
|
+
to anchor around axis.
|
|
445
|
+
"""
|
|
446
|
+
r = atom.pos - anchor
|
|
447
|
+
r_perp = v3.perpendicular(r, axis)
|
|
448
|
+
vel_perp = v3.perpendicular(atom.vel, axis)
|
|
449
|
+
vel_tang = v3.perpendicular(vel_perp, r_perp)
|
|
450
|
+
pos_ref = v3.cross(axis, r_perp)
|
|
451
|
+
if v3.dot(vel_tang, pos_ref) < 0.0:
|
|
452
|
+
sign = -1.0
|
|
453
|
+
else:
|
|
454
|
+
sign = 1.0
|
|
455
|
+
if v3.is_similar_mag(v3.mag(r_perp), 0):
|
|
456
|
+
result = 0.0
|
|
457
|
+
else:
|
|
458
|
+
result = sign * v3.mag(vel_tang) / v3.mag(r_perp)
|
|
459
|
+
return result
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def weighted_rotational_velocity(atoms, axis, anchor):
|
|
463
|
+
"""
|
|
464
|
+
Returns the average rotational velocity (rad/ps) of a bunch of
|
|
465
|
+
a atoms weighted by the rotational moments.
|
|
466
|
+
"""
|
|
467
|
+
moments = [ \
|
|
468
|
+
moment_of_inertia(atom, axis, anchor) for atom in atoms]
|
|
469
|
+
total_moment = sum(moments)
|
|
470
|
+
weights = [moment / total_moment for moment in moments]
|
|
471
|
+
rot_vels = [ \
|
|
472
|
+
rotational_velocity(atom, axis, anchor) for atom in atoms]
|
|
473
|
+
weighted_rot_vels = [ \
|
|
474
|
+
rot_vel*weight for rot_vel, weight in zip(rot_vels, weights)]
|
|
475
|
+
return sum(weighted_rot_vels)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def add_rotational_velocity(atoms, rot_vel, axis, anchor):
|
|
479
|
+
"""
|
|
480
|
+
Adds the rot_vel to the vel vector of atoms with respect
|
|
481
|
+
to the rotation around axis and attached to anchor.
|
|
482
|
+
"""
|
|
483
|
+
for atom in atoms:
|
|
484
|
+
r_perp = v3.perpendicular(atom.pos - anchor, axis)
|
|
485
|
+
v_tang_dir = v3.cross(axis, r_perp)
|
|
486
|
+
v_tang_dir_len = v3.mag(v_tang_dir)
|
|
487
|
+
if v3.is_similar_mag(v_tang_dir_len, 0):
|
|
488
|
+
v_tang = v3.vector()
|
|
489
|
+
else:
|
|
490
|
+
v_new_len = rot_vel * v3.mag(r_perp)
|
|
491
|
+
v_tang = v3.scale(v_tang_dir, v_new_len/v_tang_dir_len)
|
|
492
|
+
atom.vel += v_tang
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def get_n_chi(residue):
|
|
496
|
+
"""
|
|
497
|
+
Returns the number of chi angles of residue.
|
|
498
|
+
"""
|
|
499
|
+
return len(data.get_res_chi_topology(residue.type))
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def calculate_chi(residue, i_chi):
|
|
503
|
+
"""
|
|
504
|
+
Returns the angle for the i_chi dihedral angle of residue.
|
|
505
|
+
"""
|
|
506
|
+
res_chi_topology = data.get_res_chi_topology(residue.type)
|
|
507
|
+
if i_chi < len(res_chi_topology):
|
|
508
|
+
atom_types = res_chi_topology[i_chi]
|
|
509
|
+
crds = [residue.atom(t).pos for t in atom_types]
|
|
510
|
+
return v3.normalize_angle(v3.dihedral(*crds))
|
|
511
|
+
raise ValueError("No Chi%d angle for residue %d" % (i_chi, i))
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def get_axis_anchor(residue, i_chi):
|
|
515
|
+
"""
|
|
516
|
+
Returns the axis of rotation and an anchor point of i_chi
|
|
517
|
+
dihedral of residue.
|
|
518
|
+
"""
|
|
519
|
+
res_chi_topology = data.get_res_chi_topology(residue.type)
|
|
520
|
+
p = [residue.atom(a).pos for a in res_chi_topology[i_chi]]
|
|
521
|
+
axis = p[2] - p[1]
|
|
522
|
+
anchor = p[2]
|
|
523
|
+
return axis, anchor
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def atoms_affected_by_chi(residue, i_chi):
|
|
527
|
+
"""
|
|
528
|
+
Returns the atoms in residue that will be rotated if the i_chi
|
|
529
|
+
dihedral is rotated.
|
|
530
|
+
"""
|
|
531
|
+
def sidechain_nesting(atom_type):
|
|
532
|
+
label = atom_type
|
|
533
|
+
while label[-1].isdigit():
|
|
534
|
+
label = label[:-1]
|
|
535
|
+
while label[0].isdigit():
|
|
536
|
+
label = label[1:]
|
|
537
|
+
if len(label) < 2:
|
|
538
|
+
nesting = -1
|
|
539
|
+
else:
|
|
540
|
+
nesting = "ABGDEZH".find(label[1]) - 2
|
|
541
|
+
if label[0] == "H":
|
|
542
|
+
nesting += 1
|
|
543
|
+
return nesting
|
|
544
|
+
return [a for a in residue.atoms()
|
|
545
|
+
if sidechain_nesting(a.type) >= i_chi]
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def get_rot_vel_chi(residue, i_chi):
|
|
549
|
+
"""
|
|
550
|
+
Returns the weighted rotational velocity of the atoms
|
|
551
|
+
that are rotated by the i_chi dihedral.
|
|
552
|
+
"""
|
|
553
|
+
axis, anchor = get_axis_anchor(residue, i_chi)
|
|
554
|
+
atoms = atoms_affected_by_chi(residue, i_chi)
|
|
555
|
+
return weighted_rotational_velocity(atoms, axis, anchor)
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def get_random_chi_rot_vel(residue, i_chi, temperature):
|
|
559
|
+
"""
|
|
560
|
+
Returns a random energy from a Maxwellian energy distribution
|
|
561
|
+
consistent with the number of degrees of freedom of the
|
|
562
|
+
atoms involved in the i_chi dihedral.
|
|
563
|
+
"""
|
|
564
|
+
axis, anchor = get_axis_anchor(residue, i_chi)
|
|
565
|
+
atoms = atoms_affected_by_chi(residue, i_chi)
|
|
566
|
+
moment = total_moment_of_inertia(atoms, axis, anchor)
|
|
567
|
+
energy = random_energy(temperature, 3*len(atoms))
|
|
568
|
+
return math.sqrt(2 * energy / moment)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def add_rot_vel_to_chi(residue, i_chi, target_rot_vel):
|
|
572
|
+
"""
|
|
573
|
+
Adds target_rot_vel to the atoms affected by i_chi around the
|
|
574
|
+
chi axis.
|
|
575
|
+
"""
|
|
576
|
+
axis, anchor = get_axis_anchor(residue, i_chi)
|
|
577
|
+
atoms = atoms_affected_by_chi(residue, i_chi)
|
|
578
|
+
add_rotational_velocity(atoms, target_rot_vel, axis, anchor)
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
class Rip:
|
|
582
|
+
"""
|
|
583
|
+
Strategy to directly rotate a residue via chi angles.
|
|
584
|
+
|
|
585
|
+
This is designed to be instantiated by make_rip_fn().
|
|
586
|
+
"""
|
|
587
|
+
def __init__(self, i_res, heating_temperature):
|
|
588
|
+
self.i_res = i_res
|
|
589
|
+
self.heating_temperature = heating_temperature
|
|
590
|
+
self.mean_chis = None
|
|
591
|
+
self.max_delta_chi = v3.radians(60)
|
|
592
|
+
|
|
593
|
+
def apply(self, soup):
|
|
594
|
+
residue = soup.residue(self.i_res)
|
|
595
|
+
atoms = residue.atoms()
|
|
596
|
+
n_chi = get_n_chi(residue)
|
|
597
|
+
|
|
598
|
+
if self.mean_chis is None:
|
|
599
|
+
self.mean_chis = [calculate_chi(residue, i) for i in range(n_chi)]
|
|
600
|
+
|
|
601
|
+
rot_vels = [get_rot_vel_chi(residue, i) for i in range(n_chi)]
|
|
602
|
+
for atom in atoms:
|
|
603
|
+
v3.set_vector(atom.vel, 0.0, 0.0, 0.0)
|
|
604
|
+
|
|
605
|
+
for i_chi in reversed(list(range(n_chi))):
|
|
606
|
+
chi = calculate_chi(residue, i_chi)
|
|
607
|
+
delta_chi = v3.normalize_angle(chi - self.mean_chis[i_chi])
|
|
608
|
+
target_rot_vel = get_random_chi_rot_vel(
|
|
609
|
+
residue, i_chi, self.heating_temperature)
|
|
610
|
+
if abs(delta_chi) > self.max_delta_chi:
|
|
611
|
+
if delta_chi > self.max_delta_chi:
|
|
612
|
+
target_rot_vel = -target_rot_vel
|
|
613
|
+
else:
|
|
614
|
+
if rot_vels[i_chi] < 0.0:
|
|
615
|
+
target_rot_vel *= -target_rot_vel
|
|
616
|
+
add_rot_vel_to_chi(residue, i_chi, target_rot_vel)
|
|
617
|
+
|
|
618
|
+
anderson_velocity_scale(atoms, self.heating_temperature, 3*len(atoms))
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def make_rip_fn(i_res, heating_temperature):
|
|
622
|
+
"""
|
|
623
|
+
Returns pulse_fn that implements Rip.
|
|
624
|
+
"""
|
|
625
|
+
rip = Rip(i_res, heating_temperature)
|
|
626
|
+
return lambda soup: rip.apply(soup)
|
|
627
|
+
|