-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathreference_energy.py
More file actions
92 lines (81 loc) · 4.24 KB
/
reference_energy.py
File metadata and controls
92 lines (81 loc) · 4.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
from openmm.unit import kilojoules_per_mole, kelvin, is_quantity, MOLAR_GAS_CONSTANT_R
from scipy.optimize import curve_fit
import numpy as np
class ReferenceEnergyFinder(object):
def __init__(self, model, pKa, temperature):
"""
Construct a ReferenceEnergyFinder.
Parameters
----------
model: ConstantPH
The model for which to determine reference energies. It must contain a single titratable residue with
exactly two states. It does not matter what pH or reference energies were specified when it was created,
because they will both be overwritten.
pKa: float
The experimental pKa of the titratable residue. Reference energies will be chosen to match it.
temperature: openmm.unit.Quantity
The temperature at which the simulation will be run.
"""
if len(model.titrations) != 1:
raise ValueError("The model compound must contain a single titratable residue")
self.model = model
self.pKa = pKa
if not is_quantity(temperature):
temperature = temperature*kelvin
self.temperature = temperature
self.residueIndex = list(model.titrations.keys())[0]
self.titration = model.titrations[self.residueIndex]
if len(self.titration.explicitStates) != 2:
raise ValueError("Only residues with two states are currently supported")
def findReferenceEnergies(self, iterations=20000, substeps=20):
"""
Compute the reference energies for the states of the model compound. On exit, they will be stored in
the ConstantPH object.
Parameters
----------
iterations: int
The number of Monte Carlo moves to attempt. The larger the number, the more tightly converged
the results will be.
subsets: int
The number of dynamics steps to integrate between Monte Carlo moves.
"""
# Find an initial estimate of the reference energies just by computing the potential
# energies of the states.
self.model.setResidueState(self.residueIndex, 0)
energy0 = self.model.implicitContext.getState(energy=True).getPotentialEnergy()
self.model.setResidueState(self.residueIndex, 1)
energy1 = self.model.implicitContext.getState(energy=True).getPotentialEnergy()
deltaN = self.titration.implicitStates[1].numHydrogens - self.titration.implicitStates[0].numHydrogens
scale = MOLAR_GAS_CONSTANT_R*self.temperature*deltaN*np.log(10.0)
self.titration.referenceEnergies[0] = 0.0*kilojoules_per_mole
self.titration.referenceEnergies[1] = energy1-energy0
self.model.simulation.minimizeEnergy()
self.model.simulation.context.setVelocitiesToTemperature(self.temperature)
# If our initial estimate is exact, the fractions should be equal at pH 0. Since it probably
# isn't, simulate it at various pHs to refine the estimate.
while True:
self.model.setPH([-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0])
for i in range(1000):
self.model.simulation.step(substeps)
self.model.attemptMCStep(self.temperature)
fractions = [[] for _ in range(len(self.model.pH))]
for i in range(iterations):
self.model.simulation.step(substeps)
self.model.attemptMCStep(self.temperature)
fractions[self.model.currentPHIndex].append(1.0 if self.titration.protonatedIndex == self.titration.currentIndex else 0.0)
# Fit a curve to the data to better estimate when the fraction is exactly 0.5,
# and compute the reference energy based on it.
x = []
y = []
for i in range(len(fractions)):
if len(fractions[i]) > 0:
x.append(self.model.pH[i])
y.append(np.average(fractions[i]))
def f(ph, pka):
return 1/(1+10**(ph-pka))
popt, pcov = curve_fit(f, x, y, [0.0])
root = popt[0]
if root > -2 and root < 2:
self.titration.referenceEnergies[1] += scale*(self.pKa-root)
break
self.titration.referenceEnergies[1] -= scale*root