Writing New ITC Models¶
There are two types of models in pytc: individual models and global models.
Individual models describe a single ITC experiment under a single set of
conditions. Global models describe relationships between individual ITC
experiments. Individual models and global models are both appended to
instances of pytc.GlobalFit
, which then simultaneously fits parameters
from all models.
See the here for descriptions of the individual models already implemented. See here for the global models already implemented.
The following sections describe how to write new individual and global models.
Individual models¶
These models describe a single ITC experiment. They are passed to
pytc.ITCExperiment
along with an appropriate ITC heats file to analyze that
individual experiment.
To define a new fitting model, create a new subcass of
pytc.indiv_models.ITCModel
. Here is an implementation of a single-site
binding model. A full description of the model is here.
import pytc
class SingleSite(pytc.indiv_models.ITCModel):
def param_definition(K=1e6,dH=-4000.0,fx_competent=1.0):
pass
@property
def dQ(self):
"""
Calculate the heats that would be observed across shots for a given set
of enthalpies and binding constants for each reaction.
"""
# ----- Determine mole fractions -----
S_conc_corr = self._S_conc*self.param_values["fx_competent"]
b = S_conc_corr + self._T_conc + 1/self.param_values["K"]
ST = (b - np.sqrt((b)**2 - 4*S_conc_corr*self._T_conc))/2
mol_fraction = ST/S_conc_corr
# ---- Relate mole fractions to heat -----
X = self.param_values["dH"]*(mol_fraction[1:] - mol_fraction[:-1])
return self._cell_volume*S_conc_corr[1:]*X + self.dilution_heats
- The new class does two things.
- It defines a method called
param_defintion
that defines the fittable parameters and reasonable guesses for those parameters as arguments to the method. - It defines a property called
dQ
which spits out the heat change for for each shot. It access the parameters defined inparam_definition
usingself.param_values[PARAMETER_NAME]
.
- It defines a method called
- The requirements for an individual model are:
- It is a subclass of
pytc.indiv_models.ITCModel
- It defines a
param_definition
method with all fittable parameters as arguments. Each paramter should have a default value that is a reasonable guess for that parameter. - Expose a
dQ
property that gives the heat change per shot.
- It is a subclass of
- More complex models might require a few additional pieces of code.
- To pass information to the model that is not present in a .DH file,
define a new
__init__
function that has new arguments. For example, one might define an__init__
function that takes the pH of the solution. After this information is recorded by the new__init__
function, it should then callsuper().__init__(...)
, where...
contains the normal arguments toITCModel.__init__
. See pytc/indiv_models/single_site_competitor.py as an example. - To keep track of the concentration of something else in the cell besides theax
titrant and stationary species, define a new
__init__
function that titrates this species. See the__init__
function defined for pytc/indiv_models/single_site_competitor.py as an example. - To construct a model with a variable number of parameters–say, a binding
polynomial with \(N\) sites–redefine
_initialize_params
. See the_initialize_params
method defined for pytc/indiv_models/binding_polynomial.py as an example.
- To pass information to the model that is not present in a .DH file,
define a new
Global models¶
Global models describe how binding thermodynamics should change between experiments.
A good example of this is a binding reaction that involves the gain or loss of a proton. The measured enthalpy will have a binding component and an ionization component. These can be separated by performing ITC experiments using buffers with different ionization enthalpies. Mathematically, the observed enthalpy in a buffer is:
where \(\Delta H_{intrinsic}\) is the buffer-independent binding enthalpy, \(\Delta H_{ionization,buffer}\) is the buffer ionization enthalpy, and \(n_{proton}\) is the number of protons gained or lost.
One can encode this relationship using a subclass of
pytc.global_models.GlobalConnector
. We will illustrate this by
implementing the relationship between buffer ionization enthalpy and observed
enthalpy from above.
Define the GlobalConnector
object¶
First, we’ll define NumProtons
, the subclass of GlobalConnector
that encodes the relationship.
import pytc
from pytc import global_models
class NumProtons(global_models.GlobalConnector):
param_guesses = {"dH_intrinsic":0.1,"num_H",0.1}
required_data = ["ionization_enthalpy"]
def dH(self,experiment):
return self.dH_intrinsic + self.num_H*experiment.ionization_enthalpy
- The new class does three things.
- It defines an attribute called
param_guesses
that defines the fittable parameters and reasonable guesses for those parameters. - It defines an attribute called
required_data
that defines attributes ofexperiment
that must be set for the connector to work. - It defines a method called
dH
which spits out the enthalpy for a givenexperiment
. Notice thatdH
uses both parameters defined inparam_guesses
:self.dH_intrinsic
andself.num_H
. It gets the ionization enthalpy for a given experiment from theexperiment
object it takes as an argument.
- It defines an attribute called
- The general requirements for these
GlobalConnector
requirements are: - It must be a subclass of
pytc.global_models.GlobalConnector
. - It must define
param_guesses
in the class namespace (i.e. at the top of the class definition.) This should have reasonable guesses for the parameters. - It must define
required_data
in the class namespace (i.e. at the top of the class definition.) These are strings that name the attributes ofexperiment
that are required to do the calculation. - It must define output methods (like
dH
) that: - take only
self
andexperiment
as arguments. - use the parameters specified in
param_guesses
as attributes ofself
(e.g.self.dH_intrinsic
above). - access any required information about the experiment from the
experiment
object.
- take only
- It must define output methods (like
- There is no limit to the number of parameters, required data, or output methods.
- It must be a subclass of
Link fit parameters to the object¶
Once the GlobalConnector
object is defined, it can then be linked to
individual experimental model parameters in a way directly analagous to simple
global fit parameters. As before, we’ll show an example and then describe it.
# --------------------------------------------------------------------
# define buffer ionization enthalpies.
# goldberg et al (2002) Journal of Physical and Chemical Reference Data 31 231, doi: 10.1063/1.1416902
TRIS_IONIZATION_DH = 47.45/4.184*1000
IMID_IONIZATION_DH = 36.64/4.184*1000
# --------------------------------------------------------------------
# Create a global fitting instance
g = pytc.GlobalFit()
# ----------------------------------------------------
# Create an instance of the connector we defined above
num_protons = NumProtons("np")
# ------------------------------------------------------------------------------------
# Load in an experiment done in tris buffer experiment
tris = pytc.ITCExperiment("demos/ca-edta/tris-01.DH",pytc.indiv_models.SingleSite,shot_start=2)
# Assign ionization enthalpy of experiment
tris.ionization_enthalpy = TRIS_IONIZATION_DH
# Add the experiment to the fitter
g.add_experiment(tris)
# Assign the local variable "dH" to the global connector method
g.link_to_global(tris,"dH",num_protons.dH)
# ------------------------------------------------------------------------------------
# Imidazole buffer experiment
imid = pytc.ITCExperiment("demos/ca-edta/imid-01.DH",pytc.indiv_models.SingleSite,shot_start=2)
# Assign ionization enthalpy of experiment
imid.ionization_enthalpy = IMID_IONIZATION_DH
# Add the experiment to the fitter
g.add_experiment(imid)
# Assign the local variable "dH" to the global connector method
g.link_to_global(imid,"dH",num_protons.dH)
# --------------------------------------------------------------------
# Do a global fit and show results
g.fit()
print(g.fit_as_csv)
This will spit out:
The lines containing np_num_H
and np_dH_intrinsic
are the
outputs from the new global fit.
- There are three key things in this code:
- It creates an instance of
NumProtons
. This takes a required argument calledname
that is used to identify whichGlobalConnector
each parameter is associated with. In this case,name="np"
, so the string"np_"
is appended to the parameters when they are output. - It assigns
.ionization_enthalpy
to each experiment. This is howexperiment.ionization_enthalpy
is accessed in theNumProtons.dH
function. If you were implementing a different model, you could send different properties here (pH, competitor concentration, etc.). NOTE:experiment.temperature
is already defined and does not need to be set manually. - It links the
"dH"
parameter from each experiment tonum_protons.dH
. The linking uses the name of the output function, but does not call it (e.g. it isnum_protons.dH
NOTnum_protons.dH()
)
- It creates an instance of
- More complex models might require a few additional pieces.
- To pass information to the model that does not vary across experiments,
define a new
__init__
function that has new arguments. For example, one might define an__init__
function that takes the reference temperature for an analysis. After this information is recorded by the new__init__
function, it should then callsuper().__init__(name)
. See pytc.global_connectors.VantHoff as an example. - Models can implement multiple output functions. For example
pytc.global_connectors.VantHoff
has both a
dH
andK
output function.
- To pass information to the model that does not vary across experiments,
define a new