Skip to content

Commit a69940f

Browse files
authored
Binding polynomial extension (#46)
* update * util changes for gui plot figs, starting binding_polynomial c extension * binding polynomial extension * organization * updates * more changes to binding polynomial * binding polynomial C extension
1 parent 8253492 commit a69940f

File tree

9 files changed

+430
-92
lines changed

9 files changed

+430
-92
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ __pycache__/
33
*.py[cod]
44
*$py.class
55

6+
.DS_Store
7+
68
# C extensions
79
*.so
810

pytc/experiments/base.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@
1717
import random, string, os
1818
import numpy as np
1919

20-
AVAIL_UNITS = {"cal":1.9872036,
21-
"kcal":0.0019872036,
22-
"J":8.3144598,
23-
"kJ":0.0083144598}
24-
2520
class BaseITCExperiment:
2621
"""
2722
Class that holds an experimental ITC measurement and a model that describes it.
2823
"""
2924

25+
AVAIL_UNITS = {"cal":1.9872036,
26+
"kcal":0.0019872036,
27+
"J":8.3144598,
28+
"kJ":0.0083144598}
29+
3030
def __init__(self,dh_file,model,shot_start=1,units="cal",uncertainty=0.1,
3131
**model_kwargs):
3232
"""
@@ -58,10 +58,10 @@ def __init__(self,dh_file,model,shot_start=1,units="cal",uncertainty=0.1,
5858
# Deal with units
5959
self._units = units
6060
try:
61-
self._R = AVAIL_UNITS[self._units]
61+
self._R = self.AVAIL_UNITS[self._units]
6262
except KeyError:
6363
err = "units must be one of:\n"
64-
for k in AVAIL_UNITS.keys():
64+
for k in self.AVAIL_UNITS.keys():
6565
err += " {}\n".format(k)
6666
err += "\n"
6767

pytc/fitters/base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ def corner_plot(self,filter_params=(),*args,**kwargs):
172172

173173
return fig
174174

175+
return fig
176+
175177
@property
176178
def samples(self):
177179
"""

pytc/indiv_models/binding_polynomial.py

Lines changed: 9 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import scipy.optimize
1313
from .base import ITCModel
1414

15+
from . import bp_ext
16+
1517
class BindingPolynomial(ITCModel):
1618
"""
1719
Base class for a binding polynomial fit.
@@ -67,26 +69,6 @@ def _initialize_param(self):
6769
self._fit_dH_list = ["dH{}".format(i+1) for i in range(self._num_sites)]
6870

6971
self._T_conc_free = np.zeros(len(self._S_conc),dtype=float)
70-
self._numerator = np.zeros(len(self._S_conc),dtype=float)
71-
self._denominator = np.ones(len(self._S_conc),dtype=float)
72-
self._i_array = np.arange(len(self._fit_beta_list),dtype=float) + 1
73-
74-
def _dQdT(self,T_free,S_total,T_total):
75-
"""
76-
T_total = T_free + S_total*(dln(P)/dln(T_free)), so:
77-
0 = T_free + S_total*(dln(P)/dln(T_free)) - T_total
78-
79-
Solve this for T_free to find free titrant.
80-
81-
dln(P)/dln(T_free)
82-
83-
P = (beta1*T_free**1) * b2*T**2
84-
"""
85-
86-
numerator = np.sum(self._i_array*self._fit_beta_array*(T_free**self._i_array))
87-
denominator = 1 + np.sum( self._fit_beta_array*(T_free**self._i_array))
88-
89-
return T_free + S_total*(numerator/denominator) - T_total
9072

9173
@property
9274
def dQ(self):
@@ -102,66 +84,13 @@ def dQ(self):
10284
self._fit_dH_array[i] = self.param_values[self._fit_dH_list[i]]
10385

10486
S_conc_corr = self._S_conc*self.param_values["fx_competent"]
87+
num_shots = len(S_conc_corr)
88+
size_T = self._T_conc.size
10589

106-
# Find the root of the derivative of the binding polynomial, giving the
107-
# free titrant concentration
108-
for i in range(len(S_conc_corr)):
109-
110-
# If there's no titrant, nothing is free. (avoid numerical problems)
111-
if self._T_conc[i] == 0:
112-
self._T_conc_free[i] = 0.0
113-
continue
114-
115-
# Manually check that bounds for root calculation have opposite signs
116-
min_value = self._dQdT( 0.0,S_conc_corr[i],self._T_conc[i])
117-
max_value = self._dQdT(self._T_conc[i],S_conc_corr[i],self._T_conc[i])
118-
119-
# Uh oh, they have same sign (root optimizer will choke)
120-
if min_value*max_value > 0:
121-
122-
if max_value < 0:
123-
# root is closest to min --> set to that
124-
if (max_value < min_value):
125-
self._T_conc_free[i] = 0.0
126-
127-
# root is closest to max --> set to that
128-
else:
129-
self._T_conc_free[i] = self._T_conc[i]
130-
else:
131-
# root is closest to max --> set to that
132-
if (max_value < min_value):
133-
self._T_conc_free[i] = self._T_conc[i]
134-
135-
# root is closest to min --> set to that
136-
else:
137-
self._T_conc_free[i] = 0.0
138-
139-
continue
140-
141-
T = scipy.optimize.brentq(self._dQdT,
142-
0,self._T_conc[-1],
143-
args=(S_conc_corr[i],
144-
self._T_conc[i]))
145-
146-
# numerical problems sometimes make T slightly bigger than the total
147-
# concentration, so bring down to the correct value
148-
if (T > self._T_conc[i]):
149-
T = self._T_conc[i]
150-
151-
self._T_conc_free[i] = T
152-
153-
154-
# calculate the average enthalpy change
155-
self._numerator = 0.0
156-
self._denominator = 1.0
157-
for i in range(len(self._fit_beta_array)):
158-
self._numerator += self._fit_dH_array[i]*self._fit_beta_array[i]*(self._T_conc_free**(i+1))
159-
self._denominator += self._fit_beta_array[i]*(self._T_conc_free**(i+1))
160-
161-
avg_dH = self._numerator/self._denominator
162-
163-
X = avg_dH[1:] - avg_dH[:-1]
90+
final_array = np.zeros((num_shots-1),dtype=float)
16491

165-
to_return = self._cell_volume*S_conc_corr[1:]*X + self.dilution_heats
92+
bp_ext.dQ(self._cell_volume, num_shots, size_T, self._num_sites, self.dilution_heats,
93+
self._fit_beta_array, self._fit_dH_array, S_conc_corr, self._T_conc, self._T_conc_free,
94+
final_array)
16695

167-
return to_return
96+
return final_array

pytc/util/util.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,12 @@ def compare_models(*models):
3535
**NOTE**: This comparison is only valid if the models all fit the same
3636
set of observations. The models do not need to be nested.
3737
"""
38-
3938
aic = []
4039
aic_c = []
4140
bic = []
4241

42+
plots = []
43+
4344
num_obs = None
4445
for i, m in enumerate(models):
4546

@@ -56,14 +57,16 @@ def compare_models(*models):
5657

5758
aic.append(m.fit_stats["AIC"])
5859
aic_c.append(m.fit_stats["AICc"])
59-
bic.append(m.fit_stats["BIC"])
60+
bic.append(m.fit_stats["BIC"])
61+
62+
plots.append([m.plot(), i])
6063

6164
out = {}
6265

6366
out["AIC"] = weight_stat(aic)
6467
out["AICc"] = weight_stat(aic_c)
6568
out["BIC"] = weight_stat(bic)
6669

67-
return out
70+
return out, plots
6871

6972

setup.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77

88
# Try using setuptools first, if it's installed
99
from setuptools import setup, find_packages
10+
from setuptools.extension import Extension
11+
import numpy.distutils.misc_util
12+
13+
# set up binding polynomial C extension
14+
ext = Extension('pytc.indiv_models.bp_ext',
15+
['src/binding_polynomial.c',
16+
'src/_bp_ext.c'])
1017

1118
# Need to add all dependencies to setup as we go!
1219
setup(name='pytc-fitter',
@@ -20,5 +27,6 @@
2027
download_url='https://github.com/harmslab/pytc/tarball/1.1.0',
2128
zip_safe=False,
2229
install_requires=["matplotlib","scipy","numpy","emcee","corner"],
23-
classifiers=['Programming Language :: Python'])
24-
30+
classifiers=['Programming Language :: Python'],
31+
ext_modules=[ext],
32+
include_dirs=numpy.distutils.misc_util.get_numpy_include_dirs())

src/_bp_ext.c

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#include <Python.h>
2+
//#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
3+
#include <numpy/arrayobject.h>
4+
#include "binding_polynomial.h"
5+
#include <stdio.h>
6+
7+
// docstrings
8+
static char module_docstring[] =
9+
"calculate binding polynomial fit";
10+
11+
static char dQ_docstring[] =
12+
"Calculate the heats that would be observed across shots for a given set of enthalpies and binding constants for each reaction. This will work for an arbitrary-order binding polynomial.";
13+
14+
static PyObject *bp_ext_dQ(PyObject *self, PyObject *args);
15+
16+
// methods
17+
static PyMethodDef module_methods[] = {
18+
{"dQ", bp_ext_dQ, METH_VARARGS, dQ_docstring},
19+
{NULL, NULL}
20+
};
21+
22+
// init function
23+
PyMODINIT_FUNC PyInit_bp_ext(void)
24+
{
25+
PyObject *module;
26+
static struct PyModuleDef moduledef = {
27+
PyModuleDef_HEAD_INIT,
28+
"bp_ext",
29+
module_docstring,
30+
-1,
31+
module_methods,
32+
NULL,
33+
NULL,
34+
NULL,
35+
NULL
36+
};
37+
38+
module = PyModule_Create(&moduledef);
39+
if (!module) return NULL;
40+
41+
// Load numpy funcionality.
42+
import_array();
43+
44+
return module;
45+
}
46+
47+
static PyObject *bp_ext_dQ(PyObject *self, PyObject *args)
48+
{
49+
int num_sites, num_shots, size_T_conc;
50+
double cell_volume;
51+
PyObject *fit_beta_obj, *fit_dH_obj, *S_conc_corr, *T_conc, *T_conc_free, *dilution_heats, *final_array;
52+
53+
if (!PyArg_ParseTuple(args, "diiiOOOOOOO:dQ", &cell_volume, &num_shots, &size_T_conc, &num_sites,
54+
&dilution_heats, &fit_beta_obj, &fit_dH_obj, &S_conc_corr, &T_conc,
55+
&T_conc_free, &final_array)){
56+
return NULL;
57+
}
58+
59+
// PyObjects to numpy arrays
60+
PyObject *fit_beta_array = PyArray_FROM_OTF(fit_beta_obj, NPY_DOUBLE, NPY_IN_ARRAY);
61+
PyObject *fit_dH_array = PyArray_FROM_OTF(fit_dH_obj, NPY_DOUBLE, NPY_IN_ARRAY);
62+
PyObject *T_conc_array = PyArray_FROM_OTF(T_conc, NPY_DOUBLE, NPY_IN_ARRAY);
63+
PyObject *T_conc_free_array = PyArray_FROM_OTF(T_conc_free, NPY_DOUBLE, NPY_IN_ARRAY);
64+
PyObject *S_conc_corr_array = PyArray_FROM_OTF(S_conc_corr, NPY_DOUBLE, NPY_IN_ARRAY);
65+
PyObject *dilution_heats_array = PyArray_FROM_OTF(dilution_heats, NPY_DOUBLE, NPY_IN_ARRAY);
66+
PyObject *final_array_obj = PyArray_FROM_OTF(final_array, NPY_DOUBLE, NPY_IN_ARRAY);
67+
68+
// throw exception if cast failed
69+
if (fit_beta_array == NULL || fit_dH_array == NULL || T_conc_array == NULL || T_conc_free_array == NULL ||
70+
S_conc_corr_array == NULL || dilution_heats_array == NULL || final_array_obj == NULL) {
71+
Py_XDECREF(fit_beta_array);
72+
Py_XDECREF(fit_dH_array);
73+
Py_XDECREF(T_conc_array);
74+
Py_XDECREF(T_conc_free_array);
75+
Py_XDECREF(S_conc_corr_array);
76+
Py_XDECREF(dilution_heats_array);
77+
Py_XDECREF(final_array_obj);
78+
return NULL;
79+
}
80+
81+
// pointers
82+
double *fit_beta = (double*)PyArray_DATA(fit_beta_array);
83+
double *fit_dH = (double*)PyArray_DATA(fit_dH_array);
84+
double *T_conc_p = (double*)PyArray_DATA(T_conc_array);
85+
double *T_conc_free_p = (double*)PyArray_DATA(T_conc_free_array);
86+
double *S_conc_corr_p = (double*)PyArray_DATA(S_conc_corr_array);
87+
double *dilution_heats_p = (double*)PyArray_DATA(dilution_heats_array);
88+
double *final_array_p = (double*)PyArray_DATA(final_array_obj);
89+
90+
// call function
91+
dQ(fit_beta, fit_dH, S_conc_corr_p, T_conc_p, T_conc_free_p, cell_volume, dilution_heats_p,
92+
num_sites, num_shots, size_T_conc, final_array_p);
93+
94+
// clean up
95+
Py_DECREF(fit_beta_array);
96+
Py_DECREF(fit_dH_array);
97+
Py_DECREF(T_conc_array);
98+
Py_DECREF(T_conc_free_array);
99+
Py_DECREF(S_conc_corr_array);
100+
Py_DECREF(dilution_heats_array);
101+
102+
// check errors
103+
/*if (value == NULL){
104+
PyErr_SetString(PyExc_RuntimeError, "object return null");
105+
106+
return NULL;
107+
}*/
108+
109+
// build output and return
110+
return Py_BuildValue("O", final_array_obj);
111+
}

0 commit comments

Comments
 (0)