{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Generic least squares function\n",
    "\n",
    "This is a topical tutorial. If you are new to iminuit, you should go through the basic tutorial first. \n",
    "\n",
    "We have seen in the basic tutorial how to make a least-squares function with an explicit signature that iminuit could read to find the parameter names automatically. Part of the structure of a least-squares function is always the same. What changes is the model that predicts the y-values and its parameters.\n",
    "\n",
    "Here we show how to make a generic least-squares class that works with iminuit.\n",
    "\n",
    "Note: cost functions for least-squares and maximum likelihood fits can be imported from `iminuit.cost` "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from iminuit import Minuit\n",
    "from iminuit.util import describe, make_func_code\n",
    "import traceback"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class LeastSquares:\n",
    "    \"\"\"\n",
    "    Generic least-squares cost function.\n",
    "    \"\"\"\n",
    "    def __init__(self, model, x, y):\n",
    "        self.model = model  # model predicts y for given x\n",
    "        self.x = np.array(x)\n",
    "        self.y = np.array(y)\n",
    "\n",
    "    def __call__(self, *par):  # we accept a variable number of model parameters\n",
    "        ym = self.model(self.x, *par)\n",
    "        chi2 = np.sum((self.y - ym)**2)\n",
    "        return chi2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's try it out with iminuit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Traceback (most recent call last):\n",
      "  File \"<ipython-input-3-b71ea06d0a85>\", line 11, in <module>\n",
      "    m = Minuit(lsq, pedantic=False)\n",
      "  File \"src/iminuit/_libiminuit.pyx\", line 579, in iminuit._libiminuit.Minuit.__init__\n",
      "  File \"/Users/hdembinski/Extern/iminuit/src/iminuit/util.py\", line 404, in describe\n",
      "    raise TypeError(\"Unable to obtain function signature\")\n",
      "TypeError: Unable to obtain function signature\n"
     ]
    }
   ],
   "source": [
    "def line(x, a, b):  # simple straight line model with explicit parameters\n",
    "    return a + b * x\n",
    "\n",
    "x_data = [1, 2, 3, 4, 5]\n",
    "y_data = [2, 4, 6, 8, 10]\n",
    "\n",
    "lsq = LeastSquares(line, x_data, y_data)\n",
    "\n",
    "# this fails\n",
    "try:\n",
    "    m = Minuit(lsq, pedantic=False)\n",
    "except:\n",
    "    traceback.print_exc()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What happened? iminuit uses introspection to detect the parameter names and the number of parameters. It uses the  `describe` utility for that, but it fails, since the generic method signature `LeastSquares.__call__(self, *par)`, does not reveal the number and names of the parameters.\n",
    "\n",
    "The information could be extracted from the model signature, but iminuit knows nothing about the signature of `line(x, a, b)`. We can fix this by generating a function signature for the `LeastSquares` class."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['a', 'b']"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# get the args from line and strip 'x'\n",
    "describe(line)[1:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['a', 'b']"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# now inject that into the lsq object with the make_func_code tool\n",
    "lsq.func_code = make_func_code(describe(line)[1:])\n",
    "\n",
    "# now we get the right answer\n",
    "describe(lsq)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can put this code into the init function of our generic least-squares class to obtain a generic least-squares class which works with iminuit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class BetterLeastSquares(LeastSquares):\n",
    "    def __init__(self, model, x, y):\n",
    "        super().__init__(model, x, y)\n",
    "        self.func_code = make_func_code(describe(model)[1:])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "lsq = BetterLeastSquares(line, x_data, y_data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<table>\n",
       "<tr>\n",
       "<td colspan=\"2\" title=\"Minimum value of function\">\n",
       "FCN = 6.421e-25\n",
       "</td>\n",
       "<td align=\"center\" colspan=\"3\" title=\"No. of calls in last algorithm and total number of calls\">\n",
       "Ncalls = 30 (30 total)\n",
       "</td>\n",
       "</tr>\n",
       "<tr>\n",
       "<td colspan=\"2\" title=\"Estimated distance to minimum and target threshold\">\n",
       "EDM = 6.43e-25 (Goal: 0.0002)\n",
       "</td>\n",
       "<td align=\"center\" colspan=\"3\" title=\"Increase in FCN which corresponds to 1 standard deviation\">\n",
       "up = 1.0\n",
       "</td>\n",
       "</tr>\n",
       "<tr>\n",
       "<td align=\"center\" title=\"Validity of the migrad call\">\n",
       "Valid Min.\n",
       "</td>\n",
       "<td align=\"center\" title=\"Validity of parameters\">\n",
       "Valid Param.\n",
       "</td>\n",
       "<td align=\"center\" title=\"Is EDM above goal EDM?\">\n",
       "Above EDM\n",
       "</td>\n",
       "<td align=\"center\" colspan=\"2\" title=\"Did last migrad call reach max call limit?\">\n",
       "Reached call limit\n",
       "</td>\n",
       "</tr>\n",
       "<tr>\n",
       "<td align=\"center\" style=\"background-color:#92CCA6;\">\n",
       "True\n",
       "</td>\n",
       "<td align=\"center\" style=\"background-color:#92CCA6;\">\n",
       "True\n",
       "</td>\n",
       "<td align=\"center\" style=\"background-color:#92CCA6;\">\n",
       "False\n",
       "</td>\n",
       "<td align=\"center\" colspan=\"2\" style=\"background-color:#92CCA6;\">\n",
       "False\n",
       "</td>\n",
       "</tr>\n",
       "<tr>\n",
       "<td align=\"center\" title=\"Did Hesse fail?\">\n",
       "Hesse failed\n",
       "</td>\n",
       "<td align=\"center\" title=\"Has covariance matrix\">\n",
       "Has cov.\n",
       "</td>\n",
       "<td align=\"center\" title=\"Is covariance matrix accurate?\">\n",
       "Accurate\n",
       "</td>\n",
       "<td align=\"center\" title=\"Is covariance matrix positive definite?\">\n",
       "Pos. def.\n",
       "</td>\n",
       "<td align=\"center\" title=\"Was positive definiteness enforced by Minuit?\">\n",
       "Forced\n",
       "</td>\n",
       "</tr>\n",
       "<tr>\n",
       "<td align=\"center\" style=\"background-color:#92CCA6;\">\n",
       "False\n",
       "</td>\n",
       "<td align=\"center\" style=\"background-color:#92CCA6;\">\n",
       "True\n",
       "</td>\n",
       "<td align=\"center\" style=\"background-color:#92CCA6;\">\n",
       "True\n",
       "</td>\n",
       "<td align=\"center\" style=\"background-color:#92CCA6;\">\n",
       "True\n",
       "</td>\n",
       "<td align=\"center\" style=\"background-color:#92CCA6;\">\n",
       "False\n",
       "</td>\n",
       "</tr>\n",
       "</table>\n",
       "<table>\n",
       "<tr style=\"background-color:#F4F4F4;\">\n",
       "<td/>\n",
       "<th title=\"Variable name\">\n",
       "Name\n",
       "</th>\n",
       "<th title=\"Value of parameter\">\n",
       "Value\n",
       "</th>\n",
       "<th title=\"Hesse error\">\n",
       "Hesse Error\n",
       "</th>\n",
       "<th title=\"Minos lower error\">\n",
       "Minos Error-\n",
       "</th>\n",
       "<th title=\"Minos upper error\">\n",
       "Minos Error+\n",
       "</th>\n",
       "<th title=\"Lower limit of the parameter\">\n",
       "Limit-\n",
       "</th>\n",
       "<th title=\"Upper limit of the parameter\">\n",
       "Limit+\n",
       "</th>\n",
       "<th title=\"Is the parameter fixed in the fit\">\n",
       "Fixed\n",
       "</th>\n",
       "</tr>\n",
       "<tr style=\"background-color:#FFFFFF;\">\n",
       "<td>\n",
       "0\n",
       "</td>\n",
       "<td>\n",
       "a\n",
       "</td>\n",
       "<td>\n",
       "0\n",
       "</td>\n",
       "<td>\n",
       "1\n",
       "</td>\n",
       "<td>\n",
       "\n",
       "</td>\n",
       "<td>\n",
       "\n",
       "</td>\n",
       "<td>\n",
       "\n",
       "</td>\n",
       "<td>\n",
       "\n",
       "</td>\n",
       "<td>\n",
       "\n",
       "</td>\n",
       "</tr>\n",
       "<tr style=\"background-color:#F4F4F4;\">\n",
       "<td>\n",
       "1\n",
       "</td>\n",
       "<td>\n",
       "b\n",
       "</td>\n",
       "<td>\n",
       "2.00\n",
       "</td>\n",
       "<td>\n",
       "0.32\n",
       "</td>\n",
       "<td>\n",
       "\n",
       "</td>\n",
       "<td>\n",
       "\n",
       "</td>\n",
       "<td>\n",
       "\n",
       "</td>\n",
       "<td>\n",
       "\n",
       "</td>\n",
       "<td>\n",
       "\n",
       "</td>\n",
       "</tr>\n",
       "</table>\n"
      ],
      "text/plain": [
       "------------------------------------------------------------------\n",
       "| FCN = 6.421e-25               |      Ncalls=30 (30 total)      |\n",
       "| EDM = 6.43e-25 (Goal: 0.0002) |            up = 1.0            |\n",
       "------------------------------------------------------------------\n",
       "|  Valid Min.   | Valid Param.  | Above EDM | Reached call limit |\n",
       "------------------------------------------------------------------\n",
       "|     True      |     True      |   False   |       False        |\n",
       "------------------------------------------------------------------\n",
       "| Hesse failed  |   Has cov.    | Accurate  | Pos. def. | Forced |\n",
       "------------------------------------------------------------------\n",
       "|     False     |     True      |   True    |   True    | False  |\n",
       "------------------------------------------------------------------\n",
       "------------------------------------------------------------------------------------------\n",
       "|   | Name |   Value   | Hesse Err | Minos Err- | Minos Err+ | Limit-  | Limit+  | Fixed |\n",
       "------------------------------------------------------------------------------------------\n",
       "| 0 | a    |     0     |     1     |            |            |         |         |       |\n",
       "| 1 | b    |   2.00    |   0.32    |            |            |         |         |       |\n",
       "------------------------------------------------------------------------------------------"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "m = Minuit(lsq, pedantic=False)\n",
    "m.migrad()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It works :)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
