The Model class =============== Before you can do fits with myFitter you have to define the model you want to fit. All models in myFitter are represented by classes derived from the :py:class:`~myFitter.Model` class. Each :py:class:`~myFitter.Model` instance has four ``dict`` attributes which, together, fully specify the parameter space :math:`\Omega`, the sample space :math:`\mathcal{X}`, the PDF :math:`f` and the observed data :math:`\vec x_0` (see :doc:`stats` for definitions). The dictionaries are called :py:attr:`~Model.par`, :py:attr:`~Model.con`, :py:attr:`~Model.csc`, and :py:attr:`~Model.obs`. When you define your own model class it is your responsibility to fill these dictionaries with the appropriate data. This is most conveniently done in the :py:meth:`~myFitter.Model.__init__` method. The keys of the dictionaries can be arbitrarily chosen strings. Most of the time you will not modify the dictionaries directly but via various ``Model.add...`` methods which we will discuss shortly. However, to understand the arguments to some of these methods it is good to know what the :py:attr:`~Model.par`, :py:attr:`~Model.con`, :py:attr:`~Model.csc`, and :py:attr:`~Model.obs` ``dict``\ s contain. :py:attr:`~Model.par` holds the parameters of the model. The values must be instances of :py:class:`~myFitter.Parameter`. Amongst other things, :py:class:`~myFitter.Parameter` instances hold information about upper and lower bounds of the associated parameter. :py:attr:`~Model.con` holds constraints (other than upper or lower bounds on individual parameters) on the model's parameter space. Non-linear constraints such as ``x*y == 1`` or ``x*y < 1`` can be imposed through this dictionary. The values must be instances of :py:class:`~myFitter.Constraint`. :py:attr:`~Model.csc` holds the model's :ref:`chi-square contributions ` (see below). Values must be instances of :py:class:`~myFitter.ChiSquareContribution`. :py:attr:`~Model.obs` holds the components of the vector :math:`\vec x_0` of measured observables. The keys are the names of the observables. The values must be floats. In addition there is the :py:attr:`~Model.nuisanceCSCs` member which is a set holding the names of the :ref:`CSCs ` associated with :ref:`nuisance parameters `. Its purpose will be explained in :ref:`manipulating-models`. .. _CSC: .. index:: CSC, chi-square contribution Chi-square contributions ------------------------ As explained in :doc:`stats`, all information about a statistical model is contained in the *probability density function* .. math:: f:\Omega\times\mathcal{X}\to\R, (\vec\omega,\vec x)\mapsto f(\vec\omega,\vec x) \eqsep, where :math:`\Omega` is the model's parameter space and :math:`\mathcal{X}=\R^n` the space of observables or *sample space*. The same information is contained in the *chi-square function* which is defined (in myFitter) as .. math:: \chi^2(\vec\omega,\vec x) = -2\ln f(\vec\omega,\vec x) \eqsep. In some cases, e.g. if the observables :math:`\vec x` were measured in different experiments, the function :math:`f` is a product of individual factors :math:`f_i`: .. math:: f(\vec\omega, \vec x) = \prod_i f_i(\vec\omega,\vec x) \eqsep, where each factor :math:`f_i` only depends on a subset of the observables :math:`x_1,\ldots,x_n`. In this case the chi-square function is .. math:: \chi^2(\vec\omega,\vec x) = \sum_i \chi^2_i(\vec\omega,\vec x) \eqsep\text{with}\eqsep \chi^2_i(\vec\omega,\vec x) = -2\ln f_i(\vec\omega,\vec x) \eqsep. The terms :math:`\chi^2_i` are called *chi-square contributions* (CSCs) in myFitter. CSCs are represented by instances of classes derived from :py:class:`~myFitter.ChiSquareContribution`. You can implement your own CSCs by deriving from this class. However, the the most common types of CSCs are Gaussian CSCs with fixed errors and correlation matrix. These CSCs are already implemented by the :py:class:`~myFitter.GaussianCSC` and :py:class:`~myFitter.CorrelatedGaussianCSC` classes. .. _dependent-quantity: .. index:: pair: dependent; quantitiy Dependent quantities and observables ------------------------------------ The shape of a :ref:`CSC ` is usually characterised by one or more quantities which may depend on the model parameters in an arbitrary way. For example, a Gaussian :ref:`CSC ` is centred on a mean value :math:`\mu` which can be an arbitrary function of the model parameters. Quantities like :math:`\mu` are called *dependent quantities* in myFitter. In particular the model parameters themselves are also dependent quantities. Each dependent quantity in a model has a uniqe *name* which is a string you are free to choose when you define the model. Values for dependent quantities are passed between different myFitter functions by means of Python ``dict``\ s whose keys are the names of the dependent quantities. You specify the relationship between the model parameters and other dependent quantities by overriding the :py:meth:`~myFitter.Model.getDependentQuantities` method. It takes a dictionary with the parameters as argument and should return a dictionary which contains the parameters *and* any dependent quantities needed by the :ref:`CSC `\ s in your model. Python ``dict``\ s with strings as keys are also used to represent *observable vectors*, i.e. elements of the sample space :math:`\mathcal{X}`. This way each component of the observable vector :math:`\vec x` can be labelled by a descriptive name you choose. You can (and typically will) use the same name for an observable and a related dependent quantity since dependent quantities and observables are never put in the same dictionary. Deriving the model class ------------------------ To see how all this works in detail let's take a look at an example (see also :download:`mymodel.py `): .. _model-example: .. code-block:: python :linenos: import myFitter as mf class MyModel(mf.Model): def __init__(self): mf.Model.__init__(self) self.addParameter('p1', default=1.0, bounds=(0.1, None)) self.addParameter('p2', default=1.0, scale=0.5) self.addConstraint('p1 p2', '<', 1.0, scale=0.5) self.addGaussianCSC('obs1', 2.05, 0.05, syst=(0.2, 0.1)) self.addCorrelatedGaussianCSC('obs234', [('obs2', 0.8, 0.1), ('obs3', 2.3, 0.2), ('obs4', 0.3, 0.1)], [('obs2', 'obs3', 0.2), ('obs3', 'obs4', -0.8)]) def getDependentQuantities(self, p): q = mf.Model.getDependentQuantities(p) p1 = q['p1'] p2 = q['p2'] q['p1 p2'] = p1*p2 q['obs1'] = p1**2 + p2**2 q['obs2'] = p2/p1 q['obs3'] = p1 + p2 q['obs4'] = p1 - p2 return q In lines 6 and 7 we call the :py:meth:`~myFitter.Model.addParameter` method to add two parameters to the model. The strings ``'p1'`` and ``'p2'`` are the names of the parameters (i.e. the keys under which they are added to the :py:attr:`~Model.par` ``dict``. With ``default=1.0`` we state that the default value of parameter `p1` is 1. If myFitter needs a value for the parameter `p1` and you haven't specified one it will take the default value. If no default value is given for a parameter myFitter assumes it is 0. With ``bounds=(0.1, None)`` we state that parameter `p1` must be larger than 0.1, i.e. has a lower bound of 0 but no upper bound. With ``scale=0.5`` we tell myFitter that "typical" values of `p2` differ from the default value by something of the order of 0.5. This information is used by myFitter to generate random values for parameters and to improve the stability of numerical optimisations. If you do not specify a scale myFitter assumes a scale of 1. The keyword arguments to :py:meth:`~myFitter.Model.addParameter` are the same as the ones for constructor of the :py:class:`~myFitter.Parameter` class. .. _constraints: .. index:: pair: non-linear; constraint In line 9 we call the :py:meth:`~myFitter.Model.addConstraint` method to impose a non-linear constraint on the model's parameter space. Specifically, we require that `p1` * `p2` is smaller than 1. The first argument, ``'p1 p2'`` is a dependent quantity which we must set to the product of `p1` and `p2` in :py:meth:`~myFitter.Model.getDependentQuantities`. The second and third argument state that this dependent quantity must be smaller than 1. The last argument tells myFitter that deviations of the quantity ``'p1 p2'`` from its upper bound will typically be of the order of 0.5. Like the `scale` argument in :py:meth:`~myFitter.Model.addParameter` this information is used to improve numerical stability. The key under which this constraint is added to the :py:attr:`~Model.con` dict is generated from the first two arguments: >>> MyModel().con.keys() ['p1 p2 <'] If you are not happy with this choice you can choose your own name with the `name` keyword argument. If you don't want to introduce a separate dependent quantity for your constraint you can also pass a callable object as first argument to :py:meth:`~myFitter.Model.addConstraint`. This object will be called with the dict of dependent quantites (as returned by :py:meth:`~myFitter.Model.getDependentQuantities`) and should return the value of the constraint function. For example, the following code has the same effect as line 9: .. code-block:: python self.addConstraint(lambda q: q['p1']*q['p2'], '<', 1.0, scale=0.5, name='p1 p2 <') Note that the `name` keyword must be specified in this case. Allowed values for the second argument are ``'<'``, ``'>'``, ``'='``, and ``'in'``. The ``'in'`` relation forces the constraint function to lie in a finite interval and can be used as follows: .. code-block:: python self.addConstraint('p1 p2', 'in', (0.5, 2.0), scale=0.5) In line 11 we call the :py:meth:`~myFitter.Model.addGaussianCSC` add a Gaussian :ref:`CSC ` to the model. Gaussian :ref:`CSCs ` are represented by the :py:class:`~myFitter.GaussianCSC` class and can be added to a model with the :py:meth:`~myFitter.Model.addGaussianCSC` method. The first argument ``'obs1'`` actually serves three purposes: it is the name which identifies the :ref:`CSC ` within the model (i.e. the key under which it is added to the :py:attr:`~Model.csc` ``dict``), the name of the :ref:`dependent quantity ` associated with the mean of the Gaussian, and the name of the associated observable. If you want to use a different name for the dependent quantity holding the mean of the Gaussian you can use the ``mean`` keyword argument. See the documentation of :py:meth:`~myFitter.Model.addGaussianCSC` for details. The second argument is the *measured value* of the observable. (Thus, the :py:attr:`~Model.obs` ``dict`` will contain the key-value pair ``'obs1' : 2.05``.) The third argument is the *standard deviation* of the Gaussian. The `syst` keyword lets you specify the systematic uncertainty of the observable. In the example we assign an asymmetric systematic uncertainty of :math:`{}^{+0.2}_{-0.1}`. Thus the value observable ``'obs1'`` would typically be quoted as :math:`2.05\pm0.05^{+0.2}_{-0.1}` in the literature. In accordance with the discussion in :ref:`systematic-errors` the specification of the systematic error in line 11 adds a nuisance parameter to the model which is subtracted from the mean of `obs1` and which floats in the interval :math:`[-0.1, +0.2]`. The name of the nuisance parameter is constructed by appending ``' syst'`` (with a blank space) to the name of the observable. Thus, our model now contains an additional parameter called ``'obs1 syst'`` with an appropriately chosen range. For symmetric systematic errors you can set the `syst` keyword to a single number, e.g. ``syst=0.1``. Line 12 introduces three correlated observables to the model. Since the PDF for correlated observables does not factorise we have to add a :ref:`CSC ` which describes all three observables at once. The class :py:class:`~myFitter.CorrelatedGaussianCSC` is made for this purpose and instances can be added to the model with the :py:meth:`~myFitter.Model.addCorrelatedGaussianCSC` method. The first argument is the name of the :ref:`CSC ` (i.e. the key under which it is added to the :py:attr:`~Model.csc` ``dict``). The second argument is a list of tuples which describes the observables, their measured values and standard deviations. In the example we define an observable with name ``'obs2'``, measured value 0.8, and standard deviation 0.1, another observable ``'obs3'`` with measured value 2.3 and standard deviation 0.2, etc. As for :py:meth:`~myFitter.Model.addGaussianCSC` the strings ``'obs2'``, ``'obs3'``, and ``'obs4'`` are the keys under which the measured values are stored in the :py:attr:`~Model.obs` ``dict`` and also the names of the dependent quantities associated with the means. The `means` keyword argument of :py:meth:`~myFitter.Model.addCorrelatedGaussianCSC` lets you choose different names for the means. The third argument lists the correlation coefficients between the observables. The observables `obs2` and `obs3` have a correlation coefficient of 0.2 and the correlation between `obs3` and `obs4` is -0.8. Since no correlation between `obs2` and `obs4` is specified the corresponding correlation coefficient is set to zero. The dependent quantities `obs1`, `obs2`, `obs3`, and `obs4` (i.e. the means of the Gaussian observables) are all calculated in the :py:meth:`~myFitter.Model.getDependentQuantities` method. The argument ``p`` is a ``dict`` which specifies the values of the parameters. The keys of ``p`` must be a subset of the keys of the model's :py:attr:`~Model.par` ``dict``. In line 18 we call the method from the base class and assign the result to ``q``. This should always be the first step when you define :py:meth:`~myFitter.Model.getDependentQuantities` in a derived class. The base class method performs the following operations: * it makes an independent (deep) copy of the dictionary ``p``, * if any keys of the :py:attr:`~Model.par` ``dict`` are not present in ``p`` it substitutes them with their default values, * if any parameters of the model have been fixed (e.g. with the :py:meth:`~myFitter.Model.fix` method) it sets these parameters to their fixed values. As a result all parameters are guaranteed to be present in ``q`` and fixed parameters are guaranteed to have their fixed value. In lines 20 and 21 we read out the values of the parameters `p1` and `p2` from the dictionary ``q`` (**not** ``p``). In lines 23 to 26 we compute the observables from the parameters `p1` and `p1` and add them to ``q`` under the keys ``'obs1'`` to ``'obs4'``. Finally we return the dictionary ``q``. Gaussian nuisance parameters ---------------------------- As discussed in :ref:`systematic-errors` your model may contain :ref:`nuisance parameters ` whose values have been determined with a certain (Gaussian) uncertainty. It is straightforward to implement such nuisance parameters with the functions discussed so far, but the :py:class:`~myFitter.Model` class also has a couple of methods specifically for this purpose. Assume your model has a nuisance parameters :math:`\lambda` whose measured value is :math:`3.6\pm 0.4`. Then you can simply add a parameter named ``'lambda'`` to your model and an observable with the same name: .. code-block:: python class MyModel(mf.Model): def __init__(self): ... self.addParameter('lambda', default=3.6, scale=0.4) self.addGaussianCSC('lambda', 3.6, 0.4) ... Note that by choosing the same name for the parameter and the observable the :ref:`dependent quantity ` which defines the mean of the Gaussian observable is identical to the parameter ``'lambda'`` and does not need to be set explicitely in :py:meth:`~myFitter.Model.getDependentQuantities`. Of course you can set the `default` and `scale` attributes of the parameter ``'lambda'`` to something else, but the central value and standard deviation of the measurement are usually a good choice. The :py:meth:`~myFitter.Model.addGaussianNP` method of the :py:class:`~myFitter.Model` class lets you do both of the definitions above in one line: .. code-block:: python class MyModel(mf.Model): def __init__(self): ... self.addGaussianNP('lambda', 3.6, 0.4) ... The arguments of the :py:meth:`~myFitter.Model.addGaussianNP` method are identical to the ones of :py:meth:`~myFitter.Model.addGaussianCSC`, but in addition to adding the observable it also adds a parameter with the same name and suitably chosen `default` and `scale` attributes. There is also a method called :py:meth:`~myFitter.Model.addCorrelatedGaussianNP` whose syntax is identical to :py:meth:`~myFitter.Model.addCorrelatedGaussianCSC` and which lets you add several correlated Gaussian nuisance parameters to your model. .. _manipulating-models: Manipulating Model objects -------------------------- The most common manipulations you will need to do on an existing model object is fixing the values of some of its parameters or (temporarily) removing certain constraints or :ref:`CSC `\ s. You can access the objects representing parameters, constraints, and :ref:`CSC `\ s directly through the model's :py:attr:`~Model.par`, :py:attr:`~Model.con`, and :py:attr:`~Model.csc` attributes, respectively. In addition the :py:class:`~myFitter.Model` has several methods which let you do these types of manipulations more easily. To fix a parameter to a certain value you just have to set the :py:attr:`~Parameter.value` attribute of the :py:class:`~myFitter.Parameter` object to the desired value. Setting the :py:attr:`~Parameter.value` attribute to ``None`` means that the parameter is not fixed. You can also use the :py:meth:`~myFitter.Model.fix` and :py:meth:`~myFitter.Model.release` methods of the :py:class:`~myFitter.Model` class. The :py:meth:`~myFitter.Model.fix` method takes an arbitrary number of arguments which can be strings, lists of strings, or ``dict``\ s. For ``dict`` arguments the keys should be parameter names, and the corresponding parameters will be fixed to the associated values. For string arguments or lists of strings the parameters will be fixed to their default values. The :py:meth:`~myFitter.Model.release` method takes an arbitrary number of strings or lists of strings as arguments and releases the corresponding parameters (i.e. sets their :py:attr:`~Parameter.value` attributes to ``None``). To temporarily remove a constraint or :ref:`CSC ` from your model you don't actually need to remove the objects from the :py:attr:`~Model.con` or :py:attr:`~Model.csc` ``dict``\ s. :py:class:`~myFitter.Constraint` and :py:class:`~myFitter.ChiSquareContribution` objects have a boolean attribute called ``active`` which can be set to ``False`` to temporarily disable them. The method :py:meth:`~myFitter.Model.deactivateConstraints` of the :py:class:`~myFitter.Model` class can be used to disable several constraints at once. Like the :py:meth:`~myFitter.Model.release` method it takes an arbitrary number of arguments which can be strings or lists of strings identifying the constraints to de-activate. The method :py:meth:`~myFitter.Model.deactivateAllConstraints` can be used to de-activate all constraints of the model. The method :py:meth:`~myFitter.Model.deactivateOnlyConstraints` de-activates the specified constraints and activates all others. For activating specific constraints there is a matching set of methods called :py:meth:`~myFitter.Model.activateConstraints`, :py:meth:`~myFitter.Model.activateAllConstraints`, and :py:meth:`~myFitter.Model.activateOnlyConstraints`. Note that these methods only operate on the constraints listed in the :py:attr:`~Model.con` member of the :py:class:`~myFitter.Model` object. Upper and lower bounds on individual parameters stored in the corresponding :py:class:`~myFitter.Parameter` objects are not modified. :ref:`CSCs ` can be activated and de-activated by a matching set of methods called :py:meth:`~myFitter.Model.activateCSCs`, :py:meth:`~myFitter.Model.activateAllCSCs`, :py:meth:`~myFitter.Model.activateOnlyCSCs`, :py:meth:`~myFitter.Model.deactivateCSCs`, :py:meth:`~myFitter.Model.deactivateAllCSCs`, and :py:meth:`~myFitter.Model.deactivateOnlyCSCs`. If your model contains Gaussian nuisance parameters you typically want to keep the :ref:`CSCs ` associated with the nuisance parameters active at all times. The :py:meth:`~myFitter.Model.deactivateAllCSCs` and :py:meth:`~myFitter.Model.activateAllCSCs` methods will therefore, by default, not change the state of any :ref:`CSCs ` listed in the :py:attr:`~Model.nuisanceCSCs` member of your :py:class:`~myFitter.Model` class. Likewise, :py:meth:`~myFitter.Model.activateOnlyCSCs` will not de-activate and :py:meth:`~myFitter.Model.deactivateOnlyCSCs` will not activate any of these :ref:`CSCs `. You can override this behaviour by setting the `ignorenp` keyword of these methods to ``False``. The :py:meth:`~myFitter.Model.addGaussianNP` and :py:meth:`~myFitter.Model.addCorrelatedGaussianNP` methods will automatically add the names of the :ref:`CSCs ` to the :py:attr:`~Model.nuisanceCSCs` member, but you can also modify this member directly.