How Does ``Fit`` Work?
======================

In this section we describe in more detail how :class:`~symfit.core.fit.Fit`
gets from a (named) model and a bunch of data to a fit. We will use
:class:`~symfit.core.fit.NumericalLeastSquares` as example.

Consider the following example::

    from symfit import parameters, variables, Fit

    a, b = parameters('a, b')
    x, y = variables('x, y')
    model = {y: a * x + b}

    fit = Fit(model, x=x_data, y=y_data, sigma_y=sigma_data)
    fit_result = fit.execute()

The first thing :mod:`symfit` does is build :math:`\chi^2` for your model::

    chi_squared = sum((y - f)**2/sigmas[y]**2 for y, f in model.items())

In this line ``sigmas`` is a dict which contains all vars that where given a
value, or returns 1 otherwise.

This :math:`\chi^2` is then transformed into a python function which can then
be used to do the numerical calculations::

    vars, params = seperate_symbols(chi_squared)
    py_chi_squared = lambdify(vars + params, chi_squared)

We are now almost there. Just two steps left. The first is to wrap all the data
into the ``py_chi_squared`` function using :func:`~functools.partial` into the
function to be optimized::

    from functools import partial

    error = partial(py_chi_squared, **data_per_var)

where ``data_per_var`` is a dict containing variable names: value pairs.

Now all that is left is to minimize ``error`` as a function of the parameters.
:class:`~symfit.core.fit.NumericalLeastSquares` does this by calling
:func:`~symfit.core.leastsqbound.leastsqbound` and have it find the best fit
parameters::

    best_fit_parameters, covariance_matrix = leastsqbound(
        error,
        self.guesses,
        self.eval_jacobian,
        self.bounds,
    )

That's it! Finally there are some steps to generate a
:class:`~symfit.core.fit.FitResults` object, but these are not important for
our current discussion.

