Writing New ITC Models

There are two types of models in pytc: individual models and global connectors. Individual models describe a single ITC experiment under a single set of conditions. Global connectors describe relationships between individual ITC experiments. Individual models and global connectors are both appended to instances of pytc.GlobalFit, which then simultaneously fits parameters from all models. See the individual models and global connectors pages for more details.

The following sections describe how to write new individual models and global connectors.

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. Place the class (and any accessory code) in a single file in the pytc/indiv_models/ directory. Then modify the file pytc/indiv_models/__init__.py to import the new class.

from .new_model_file import NewModelClass

Here is a complete implementation of a single-site binding model.

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 in param_definition using self.param_values[PARAMETER_NAME].
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.
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 call super().__init__(...), where ... contains the normal arguments to ITCModel.__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 the 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.

Global connectors

Global connectors 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:

\[\Delta H_{obs,buffer} = \Delta H_{intrinsic} + \Delta H_{ionization,buffer} \times n_{proton},\]

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. Place the new class (and any accessory code) in a single file in the pytc/global_connectors/` directory. Then modify the file pytc/global_connectors/__init__.py to import the new class.

from .new_model_file import NewModelClass

The following class implements a GlobalConnector that describes the relationship between buffer ionization enthalpy and observed enthalpy.

import pytc

class NumProtons(pytc.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 of experiment that must be set for the connector to work.
  • It defines a method called dH which spits out the enthalpy for a given experiment. Notice that dH uses both parameters defined in param_guesses: self.dH_intrinsic and self.num_H. It gets the ionization enthalpy for a given experiment from the experiment object it takes as an argument.
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 of experiment that are required to do the calculation.
  • It must define output methods (like dH) that:
    • take only self and experiment as arguments.
    • use the parameters specified in param_guesses as attributes of self (e.g. self.dH_intrinsic above).
    • access any required information about the experiment from the experiment object.
  • There is no limit to the number of parameters, required data, or output methods.
More complex models might require a few additional pieces of code:
  • 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 call super().__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 and K output function.