.. |GVar| replace:: :class:`gvar.GVar`
.. |nonlinear_fit| replace:: :class:`lsqfit.nonlinear_fit`
.. |BufferDict| replace:: :class:`gvar.BufferDict`

Case Study: Numerical Analysis inside a Fit
=====================================================
This case study shows how to fit a differential equation,
using :mod:`gvar.ode`, and how to deal with uncertainty in
the independent variable of a fit (that is, the ``x`` in
a ``y`` versus ``x`` fit).

The Problem
-------------

A pendulum is released at time 0 from angle 1.571(50) (radians). It's
angular position is measured at intervals of approximately a tenth of
second::

            t[i]       theta(t[i])
            ----------------------
            0.0          1.571(50)
            0.10(1)      1.477(79)
            0.20(1)      0.791(79)
            0.30(1)     -0.046(79)
            0.40(1)     -0.852(79)
            0.50(1)     -1.523(79)
            0.60(1)     -1.647(79)
            0.70(1)     -1.216(79)
            0.80(1)     -0.810(79)
            0.90(1)      0.185(79)
            1.00(1)      0.832(79)

Function ``theta(t)`` satisfies a differential equation::

    d/dt d/dt theta(t)  =  -(g/l) sin(theta(t))

where ``g`` is the acceleration due to gravity and ``l`` is
the pendulum's length. The challenge is to use the data to improve
our very approximate *a priori* estimate 40±20 for ``g/l``.

Pendulum Dynamics
-------------------
We start by designing a data type that solves the differential
equation for ``theta(t)``::

    import numpy as np
    import gvar as gv

    class Pendulum(object):
        """ Integrator for pendulum motion.

        Input parameters are:
            g/l .... where g is acceleration due to gravity and l the length
            tol .... precision of numerical integration of ODE
        """
        def __init__(self, g_l, tol=1e-4):
            self.g_l = g_l
            self.odeint = gv.ode.Integrator(deriv=self.deriv, tol=tol)

        def __call__(self, theta0, t_array):
            """ Calculate pendulum angle theta for every t in t_array.

            Assumes that the pendulum is released at time t=0
            from angle theta0 with no initial velocity. Returns
            an array containing theta(t) for every t in t_array.
            """
            # initial values
            t0 = 0
            y0 = [theta0, 0.0]              # theta and dtheta/dt

            # solution  (keep only theta; discard dtheta/dt)
            y = self.odeint.solution(t0, y0)
            return [y(t)[0] for t in t_array]

        def deriv(self, t, y, data=None):
            " Calculate [dtheta/dt, d2theta/dt2] from [theta, dtheta/dt]."
            theta, dtheta_dt = y
            return np.array([dtheta_dt, - self.g_l * gv.sin(theta)])

A ``Pendulum`` object is initialized with a value for ``g/l`` and a tolerance
for the differential-equation integrator, :class:`gvar.ode.Integrator`.
Evaluating the object for a given value of ``theta(0)`` and ``t`` then
calculates ``theta(t)``; ``t`` is an array. We use :mod:`gvar.ode` here,
rather than some other integrator, because it works with :class:`gvar.GVar`\s,
allowing errors to propagate through the integration.


Two Types of Input Data
------------------------
There are two ways to include data in a fit: either as
regular data, or as fit parameters with priors. In general dependent
variables are treated as regular data, and independent variables with
errors are treated as fit parameters, with priors. Here the dependent
variable is ``theta(t)`` and the independent variable is ``t``. The
independent variable has uncertainties, so we treat the individual
values as fit parameters whose priors equal the initial values ``t[i]``.
The value of ``theta(t=0)`` is also independent data, and so becomes
a fit parameter since it is uncertain. Our fit code therefore
is::

    from __future__ import print_function   # makes this work for python2 and 3

    import collections
    import numpy as np
    import gvar as gv
    import lsqfit

    def main():
        # pendulum data exhibits experimental error in theta and t
        t = gv.gvar([
            '0.10(1)', '0.20(1)', '0.30(1)', '0.40(1)',  '0.50(1)',
            '0.60(1)',  '0.70(1)',  '0.80(1)',  '0.90(1)', '1.00(1)'
            ])
        theta = gv.gvar([
            '1.477(79)', '0.791(79)', '-0.046(79)', '-0.852(79)',
            '-1.523(79)', '-1.647(79)', '-1.216(79)', '-0.810(79)',
            '0.185(79)', '0.832(79)'
            ])

        # priors for all fit parameters: g/l, theta(0), and t[i]
        prior = collections.OrderedDict()
        prior['g/l'] = gv.gvar('40(20)')
        prior['theta(0)'] = gv.gvar('1.571(50)')
        prior['t'] = t

        # fit function: use class Pendulum object to integrate pendulum motion
        def fitfcn(p, t=None):
            if t is None:
                t = p['t']
            pendulum = Pendulum(p['g/l'])
            return pendulum(p['theta(0)'], t)

        # do the fit and print results
        fit = lsqfit.nonlinear_fit(data=theta, prior=prior, fcn=fitfcn)
        print(fit.format(maxline=True))

The prior is a dictionary containing *a priori* estimates  for every fit
parameter. The fit parameters are varied to give the best fit
to both the data and the priors. The fit function uses a :class:`Pendulum` object
to  integrate the differential equation for ``theta(t)``, generating values
for each value of ``t[i]`` given a value for ``theta(0)``.
The function returns an array that has the same shape as array ``theta``.

The fit is excellent with a ``chi**2`` per degree of freedom of 0.7:

.. image:: case-pendulum.*
   :width: 80%

The red band in the figure shows the best fit to the data, with the
error bars on the fit. The output from this fit is:

.. literalinclude:: case-pendulum.out

The final result for ``g/l`` is 39.8(9), which is accurate to about 2%.
Note that the fit generates (slightly) improved estimates for several of
the ``t`` values and for ``theta(0)``.

