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.
Files changed (71) hide show
  1. packmol_memgen/__init__.py +2 -0
  2. packmol_memgen/__version__.py +34 -0
  3. packmol_memgen/data/LICENSE.Apache-2.0 +201 -0
  4. packmol_memgen/data/extra_solvents.lib +789 -0
  5. packmol_memgen/data/frcmod.lipid_ext +97 -0
  6. packmol_memgen/data/frcmod.solvents +129 -0
  7. packmol_memgen/data/insane_lipids.txt +138 -0
  8. packmol_memgen/data/insane_solvents.txt +45 -0
  9. packmol_memgen/data/leaprc.extra_solvents +42 -0
  10. packmol_memgen/data/leaprc.lipid_ext +48 -0
  11. packmol_memgen/data/lipid_ext.lib +12312 -0
  12. packmol_memgen/data/martini_v3.0.0.itp +356605 -0
  13. packmol_memgen/data/memgen.parm +4082 -0
  14. packmol_memgen/data/pdbs.tar.gz +0 -0
  15. packmol_memgen/data/solvent.parm +14 -0
  16. packmol_memgen/example/example.sh +31 -0
  17. packmol_memgen/lib/__init__.py +0 -0
  18. packmol_memgen/lib/amber.py +77 -0
  19. packmol_memgen/lib/charmmlipid2amber/__init__.py +0 -0
  20. packmol_memgen/lib/charmmlipid2amber/charmmlipid2amber.csv +7164 -0
  21. packmol_memgen/lib/charmmlipid2amber/charmmlipid2amber.py +225 -0
  22. packmol_memgen/lib/pdbremix/LICENSE +21 -0
  23. packmol_memgen/lib/pdbremix/__init__.py +0 -0
  24. packmol_memgen/lib/pdbremix/_version.py +1 -0
  25. packmol_memgen/lib/pdbremix/amber.py +1103 -0
  26. packmol_memgen/lib/pdbremix/asa.py +227 -0
  27. packmol_memgen/lib/pdbremix/data/aminoacid.pdb +334 -0
  28. packmol_memgen/lib/pdbremix/data/binaries.json +26 -0
  29. packmol_memgen/lib/pdbremix/data/charmm22.parameter +2250 -0
  30. packmol_memgen/lib/pdbremix/data/charmm22.topology +1635 -0
  31. packmol_memgen/lib/pdbremix/data/color_b.py +682 -0
  32. packmol_memgen/lib/pdbremix/data/hin.lib +130 -0
  33. packmol_memgen/lib/pdbremix/data/hydroxide.lib +88 -0
  34. packmol_memgen/lib/pdbremix/data/make_chi.py +92 -0
  35. packmol_memgen/lib/pdbremix/data/opls.parameter +1108 -0
  36. packmol_memgen/lib/pdbremix/data/opls.topology +1869 -0
  37. packmol_memgen/lib/pdbremix/data/phd.frcmod +82 -0
  38. packmol_memgen/lib/pdbremix/data/phd.leaprc +4 -0
  39. packmol_memgen/lib/pdbremix/data/phd.prepin +35 -0
  40. packmol_memgen/lib/pdbremix/data/template.pdb +334 -0
  41. packmol_memgen/lib/pdbremix/data/znb.frcmod +24 -0
  42. packmol_memgen/lib/pdbremix/data/znb.leaprc +7 -0
  43. packmol_memgen/lib/pdbremix/data/znb.lib +69 -0
  44. packmol_memgen/lib/pdbremix/data.py +264 -0
  45. packmol_memgen/lib/pdbremix/fetch.py +102 -0
  46. packmol_memgen/lib/pdbremix/force.py +627 -0
  47. packmol_memgen/lib/pdbremix/gromacs.py +978 -0
  48. packmol_memgen/lib/pdbremix/lib/__init__.py +0 -0
  49. packmol_memgen/lib/pdbremix/lib/docopt.py +579 -0
  50. packmol_memgen/lib/pdbremix/lib/pyqcprot.py +305 -0
  51. packmol_memgen/lib/pdbremix/namd.py +1078 -0
  52. packmol_memgen/lib/pdbremix/pdbatoms.py +543 -0
  53. packmol_memgen/lib/pdbremix/pdbtext.py +120 -0
  54. packmol_memgen/lib/pdbremix/protein.py +311 -0
  55. packmol_memgen/lib/pdbremix/pymol.py +480 -0
  56. packmol_memgen/lib/pdbremix/rmsd.py +203 -0
  57. packmol_memgen/lib/pdbremix/simulate.py +420 -0
  58. packmol_memgen/lib/pdbremix/spacehash.py +73 -0
  59. packmol_memgen/lib/pdbremix/trajectory.py +286 -0
  60. packmol_memgen/lib/pdbremix/util.py +273 -0
  61. packmol_memgen/lib/pdbremix/v3.py +16 -0
  62. packmol_memgen/lib/pdbremix/v3array.py +482 -0
  63. packmol_memgen/lib/pdbremix/v3numpy.py +350 -0
  64. packmol_memgen/lib/pdbremix/volume.py +155 -0
  65. packmol_memgen/lib/utils.py +1017 -0
  66. packmol_memgen/main.py +2827 -0
  67. packmol_memgen_minimal-1.1.16.dist-info/METADATA +664 -0
  68. packmol_memgen_minimal-1.1.16.dist-info/RECORD +71 -0
  69. packmol_memgen_minimal-1.1.16.dist-info/WHEEL +4 -0
  70. packmol_memgen_minimal-1.1.16.dist-info/entry_points.txt +2 -0
  71. 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
+