Interactive fits

This notebook showcases the interactive fitting capability of iminuit. Interactive fitting is useful to find good starting values and to debug the fit.

Note: If you see this notebook on ReadTheDocs or otherwise statically rendered, changing the sliders won’t change the plot. This requires a running Jupyter kernel.

[1]:
from iminuit import cost
from iminuit import Minuit
from numba_stats import norm, t, bernstein, truncexpon
import numpy as np
from matplotlib import pyplot as plt
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
File __init__.pxd:942, in numpy.import_array()

RuntimeError: module compiled against API version 0x10 but this version of numpy is 0xf

During handling of the above exception, another exception occurred:

ImportError                               Traceback (most recent call last)
Input In [1], in <cell line: 3>()
      1 from iminuit import cost
      2 from iminuit import Minuit
----> 3 from numba_stats import norm, t, bernstein, truncexpon
      4 import numpy as np
      5 from matplotlib import pyplot as plt

File ~/python-iminuit/src/python-iminuit/test-env/lib/python3.10/site-packages/numba_stats/norm.py:9, in <module>
      1 """
      2 Normal distribution.
      3
   (...)
      6 scipy.stats.norm: Scipy equivalent.
      7 """
      8 import numpy as np
----> 9 from ._special import erfinv as _erfinv
     10 from ._util import _jit, _trans, _generate_wrappers, _prange
     11 from math import erf as _erf

File ~/python-iminuit/src/python-iminuit/test-env/lib/python3.10/site-packages/numba_stats/_special.py:7, in <module>
      5 from numba.extending import get_cython_function_address
      6 from numba.types import WrapperAddressProtocol, float64
----> 7 import scipy.special.cython_special as cysp
     10 def get(name, signature):
     11     # create new function object with correct signature that numba can call by extracting
     12     # function pointer from scipy.special.cython_special; uses scipy/cython internals
     13     index = 1 if signature.return_type is float64 else 0

File /usr/lib/python3.10/site-packages/scipy/special/__init__.py:649, in <module>
      1 """
      2 ========================================
      3 Special functions (:mod:`scipy.special`)
   (...)
    644
    645 """
    647 from ._sf_error import SpecialFunctionWarning, SpecialFunctionError
--> 649 from . import _ufuncs
    650 from ._ufuncs import *
    652 from . import _basic

File /usr/lib/python3.10/site-packages/scipy/special/_ufuncs.pyx:1, in init scipy.special._ufuncs()

File scipy/special/_ufuncs_extra_code_common.pxi:34, in init scipy.special._ufuncs_cxx()

File __init__.pxd:944, in numpy.import_array()

ImportError: numpy.core.multiarray failed to import

UnbinnedNLL

[2]:
rng = np.random.default_rng(1)

s = rng.normal(0.5, 0.1, size=1000)
b = rng.exponential(1, size=1000)
b = b[b < 1]
x = np.append(s, b)

truth = len(s) / len(x), 0.5, 0.1, 1.0

n, xe = np.histogram(x, bins=50)

def model(x, f, mu, sigma, slope):
    return f * norm.pdf(x, mu, sigma) + (1 - f) * truncexpon.pdf(x, 0, 1, 0, slope)

c = cost.UnbinnedNLL(x, model)
m = Minuit(c, *truth)
m.limits["f", "mu"] = (0, 1)
m.limits["sigma", "slope"] = (0, None)

m.interactive(model_points=1000)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [2], in <cell line: 1>()
----> 1 rng = np.random.default_rng(1)
      3 s = rng.normal(0.5, 0.1, size=1000)
      4 b = rng.exponential(1, size=1000)

NameError: name 'np' is not defined

ExtendedUnbinnedNLL

[3]:
rng = np.random.default_rng(1)

s = rng.normal(0.5, 0.1, size=1000)
b = rng.exponential(1, size=1000)
b = b[b < 1]
x = np.append(s, b)

truth = len(s), 0.5, 0.1, len(b), 1.0

n, xe = np.histogram(x, bins=50)

def model(x, s, mu, sigma, b, slope):
    x = s * norm.pdf(x, mu, sigma) + b * truncexpon.pdf(x, 0, 1, 0, slope)
    return s + b, x

c = cost.ExtendedUnbinnedNLL(x, model)
m = Minuit(c, *truth)
m.limits["mu"] = (0, 1)
m.limits["sigma", "slope", "s", "b"] = (0, None)

m.interactive()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [3], in <cell line: 1>()
----> 1 rng = np.random.default_rng(1)
      3 s = rng.normal(0.5, 0.1, size=1000)
      4 b = rng.exponential(1, size=1000)

NameError: name 'np' is not defined

BinnedNLL

[4]:
def model(xe, f, mu, sigma, nuinv, slope):
    nu = 1 / nuinv
    a, b = t.cdf((0, 1), nu, mu, sigma)
    sn = f * (t.cdf(xe, nu, mu, sigma) - a) / (b - a)
    bn = (1 - f) * truncexpon.cdf(xe, 0, 1, 0, slope)
    return sn + bn

rng = np.random.default_rng(1)

truth = 0.5, 0.5, 0.1, 0.1, 1

xe = np.linspace(0, 1, 100)
sm = truth[0] * np.diff(model(xe, 1, *truth[1:]))
bm = (1 - truth[0]) * np.diff(model(xe, 0, *truth[1:]))
n = rng.poisson(1000 * np.diff(model(xe, *truth)))

c = cost.BinnedNLL(n, xe, model)

m = Minuit(c, *truth)
m.limits["sigma", "slope"] = (0, None)
m.limits["mu", "f", "nuinv"] = (0, 1)

m.interactive()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [4], in <cell line: 8>()
      5     bn = (1 - f) * truncexpon.cdf(xe, 0, 1, 0, slope)
      6     return sn + bn
----> 8 rng = np.random.default_rng(1)
     10 truth = 0.5, 0.5, 0.1, 0.1, 1
     12 xe = np.linspace(0, 1, 100)

NameError: name 'np' is not defined
[5]:
c = cost.BinnedNLL(n, xe, model)

cx = 0.5 * (xe[1:] + xe[:-1])
c.mask = np.abs(cx - 0.5) > 0.3

m = Minuit(c, *truth)
m.limits["sigma", "slope"] = (0, None)
m.limits["mu", "f", "nuinv"] = (0, 1)

m.interactive()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [5], in <cell line: 1>()
----> 1 c = cost.BinnedNLL(n, xe, model)
      3 cx = 0.5 * (xe[1:] + xe[:-1])
      4 c.mask = np.abs(cx - 0.5) > 0.3

NameError: name 'n' is not defined

ExtendedBinnedNLL

[6]:
def model(xe, s, mu, sigma, nuinv, b1, b2, b3):
    nu = 1 / nuinv
    sn = s * t.cdf(xe, nu, mu, sigma)
    bn = bernstein.integral(xe, (b1, b2, b3), 0, 1)
    return sn + bn

truth = 1000., 0.5, 0.1, 0.1, 1000., 3000., 2000.

xe = np.linspace(0, 1, 100)

rng = np.random.default_rng(1)
n = rng.poisson(np.diff(model(xe, *truth)))

c = cost.ExtendedBinnedNLL(n, xe, model)

m = Minuit(c, *truth)
m.limits["s", "sigma", "b1", "b2", "b3"] = (0, None)
m.limits["mu", "nuinv"] = (0, 1)

m.interactive()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [6], in <cell line: 9>()
      5     return sn + bn
      7 truth = 1000., 0.5, 0.1, 0.1, 1000., 3000., 2000.
----> 9 xe = np.linspace(0, 1, 100)
     11 rng = np.random.default_rng(1)
     12 n = rng.poisson(np.diff(model(xe, *truth)))

NameError: name 'np' is not defined

You can pass a custom plotting routine with Minuit.interactive to draw more detail. A simple function works that accesses data from the outer scope, but we create a class in the following example to store the cost function, which has all data we need, because we override the variables in the outer scope in this notebook.

[7]:
# Visualize signal and background components with different colors
class Plotter:
    def __init__(self, cost):
        self.cost = cost

    def __call__(self, args):
        xe = self.cost.xe
        n = self.cost.data
        cx = 0.5 * (xe[1:] + xe[:-1])
        plt.errorbar(cx, n, n ** 0.5, fmt="ok")
        sm = np.diff(self.cost.scaled_cdf(xe, *args[:4], 0, 0, 0))
        bm = np.diff(self.cost.scaled_cdf(xe, 0, *args[1:]))
        plt.stairs(bm, xe, fill=True, color="C1")
        plt.stairs(bm + sm, xe, baseline = bm, fill=True, color="C0")

m.interactive(Plotter(c))
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [7], in <cell line: 16>()
     13         plt.stairs(bm, xe, fill=True, color="C1")
     14         plt.stairs(bm + sm, xe, baseline = bm, fill=True, color="C0")
---> 16 m.interactive(Plotter(c))

NameError: name 'm' is not defined
[8]:
c = cost.ExtendedBinnedNLL(n, xe, model)

cx = 0.5 * (xe[1:] + xe[:-1])
c.mask = np.abs(cx - 0.5) > 0.3

m = Minuit(c, *truth)
m.limits["s", "sigma", "nuinv", "b1", "b2", "b3"] = (0, None)
m.limits["mu", "nuinv"] = (0, 1)
m.fixed["s", "mu", "sigma", "nuinv"] = True
m.values["s"] = 0

m.interactive()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [8], in <cell line: 1>()
----> 1 c = cost.ExtendedBinnedNLL(n, xe, model)
      3 cx = 0.5 * (xe[1:] + xe[:-1])
      4 c.mask = np.abs(cx - 0.5) > 0.3

NameError: name 'n' is not defined

BarlowBeestonLite

[9]:
xe = np.linspace(0, 1, 20)
bm = np.diff(truncexpon.cdf(xe, 0, 1, 0, 1))
sm = np.diff(norm.cdf(xe, 0.5, 0.1))

rng = np.random.default_rng(1)
n = rng.poisson(1000 * bm + 100 * sm)
b = rng.poisson(1e4 * bm)
s = rng.poisson(1e2 * sm)

c = cost.BarlowBeestonLite(n, xe, (b, s))

m = Minuit(c, 1000, 100, name=("b", "s"))
m.limits = (0, None)

m.interactive()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [9], in <cell line: 1>()
----> 1 xe = np.linspace(0, 1, 20)
      2 bm = np.diff(truncexpon.cdf(xe, 0, 1, 0, 1))
      3 sm = np.diff(norm.cdf(xe, 0.5, 0.1))

NameError: name 'np' is not defined
[10]:
c = cost.BarlowBeestonLite(n, xe, (b, s))
cx = 0.5 * (xe[1:] + xe[:-1])
c.mask = np.abs(cx - 0.5) > 0.2

m = Minuit(c, 1000, 100, name=("b", "s"))
m.limits = (0, None)

m.interactive()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [10], in <cell line: 1>()
----> 1 c = cost.BarlowBeestonLite(n, xe, (b, s))
      2 cx = 0.5 * (xe[1:] + xe[:-1])
      3 c.mask = np.abs(cx - 0.5) > 0.2

NameError: name 'n' is not defined

LeastSquares

[11]:
def model(x, a, b):
    return a + b * x

truth = (1., 2.)
x = np.linspace(0, 1)
ym = model(x, *truth)
ye = 0.1
y = rng.normal(ym, ye)

c = cost.LeastSquares(x, y, ye, model)

m = Minuit(c, *truth)
m.interactive()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [11], in <cell line: 5>()
      2     return a + b * x
      4 truth = (1., 2.)
----> 5 x = np.linspace(0, 1)
      6 ym = model(x, *truth)
      7 ye = 0.1

NameError: name 'np' is not defined
[12]:
c = cost.LeastSquares(x, y, ye, model)
c.mask = (x > 0.6) | (x < 0.2)

m = Minuit(c, *truth)
m.interactive()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [12], in <cell line: 1>()
----> 1 c = cost.LeastSquares(x, y, ye, model)
      2 c.mask = (x > 0.6) | (x < 0.2)
      4 m = Minuit(c, *truth)

NameError: name 'x' is not defined

Cost functions with shared parameters

[13]:
def model(xe, s, mu, sigma, nuinv):
    nu = 1 / nuinv
    return s * t.cdf(xe, nu, mu, sigma)

truth = 100., 0.5, 0.1, 0.5

rng = np.random.default_rng(1)
xe = np.linspace(0, 1, 20)
m = np.diff(model(xe, *truth))
n = rng.poisson(m)

c = cost.ExtendedBinnedNLL(n, xe, model) + cost.NormalConstraint(["mu", "sigma"], [0.5, 0.1], [0.1, 0.1])

m = Minuit(c, *truth)

m.interactive()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [13], in <cell line: 7>()
      3     return s * t.cdf(xe, nu, mu, sigma)
      5 truth = 100., 0.5, 0.1, 0.5
----> 7 rng = np.random.default_rng(1)
      8 xe = np.linspace(0, 1, 20)
      9 m = np.diff(model(xe, *truth))

NameError: name 'np' is not defined