{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Fits with shared parameters\n",
    "\n",
    "We demonstrate how to simultaneously fit two datasets with different models that shares a common parameter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from iminuit import Minuit\n",
    "from iminuit.cost import UnbinnedNLL\n",
    "from iminuit.util import describe\n",
    "from matplotlib import pyplot as plt\n",
    "import numpy as np\n",
    "from numba_stats import norm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# generate two data sets which are fitted simultaneously\n",
    "rng = np.random.default_rng(1)\n",
    "\n",
    "width = 2.0\n",
    "data1 = rng.normal(0, width, size=1000)\n",
    "data2 = rng.normal(5, width, size=1000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# use two pdfs with different names for non-shared parameters,\n",
    "# so that they are fitted independently\n",
    "\n",
    "def pdf1(x, μ_1, σ):\n",
    "    return norm.pdf(x, μ_1, σ)\n",
    "\n",
    "def pdf2(x, μ_2, σ):\n",
    "    return norm.pdf(x, μ_2, σ)\n",
    "\n",
    "# combine two log-likelihood functions by adding them\n",
    "lh = UnbinnedNLL(data1, pdf1) + UnbinnedNLL(data2, pdf2)\n",
    "\n",
    "print(f\"{describe(lh)=}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `σ` parameter is shared between the data sets, while the means of the two normal distributions are independently fitted."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot(cost, xe, minuit, ax, **style):\n",
    "    signature = describe(cost)\n",
    "    data = cost.data\n",
    "    \n",
    "    values = minuit.values[signature]\n",
    "    errors = minuit.errors[signature]\n",
    "\n",
    "    cx = (xe[1:] + xe[:-1]) / 2\n",
    "\n",
    "    ym = np.diff(norm.cdf(xe, *values)) * np.sum(w)\n",
    "    t = []\n",
    "    for n, v, e in zip(signature, values, errors):\n",
    "        t.append(f\"${n} = {v:.3f} ± {e:.3f}$\")\n",
    "    ax.plot(cx, ym, label=\"\\n\".join(t), **style)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m = Minuit(lh, μ_1=1, μ_2=2, σ=1)\n",
    "\n",
    "fig, ax = plt.subplots(1, 2, figsize=(14, 5))\n",
    "\n",
    "hists = [np.histogram(lhi.data, bins=50) for lhi in lh]\n",
    "\n",
    "# draw data and model with initial parameters\n",
    "for lhi, (w, xe), axi in zip(lh, hists, ax):\n",
    "    cx = (xe[1:] + xe[:-1]) / 2\n",
    "    axi.errorbar(cx, w, np.sqrt(w), fmt=\"ok\", capsize=0, zorder=0)\n",
    "    plot(lhi, xe, m, axi, ls=\"--\")\n",
    "\n",
    "m.migrad()\n",
    "\n",
    "# draw model with fitted parameters\n",
    "for lhi, (w, xe), axi in zip(lh, hists, ax):\n",
    "    plot(lhi, xe, m, axi)\n",
    "    axi.legend()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The dashed line shows the initial model before the fit, the solid line shows the model after the fit. Note that the σ parameter is shared."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.8.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
