├── .github
├── dependabot.yml
└── workflows
│ ├── main.yml
│ └── wheels.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .readthedocs.yml
├── AUTHORS
├── CHANGELOG
├── LICENSE
├── MANIFEST.in
├── README.rst
├── doc
├── Makefile
├── api.rst
├── conf.py
├── development.rst
├── index.rst
├── pyplots
│ ├── costfunc
│ │ ├── blh.py
│ │ ├── bx2.py
│ │ ├── simul.py
│ │ ├── ulh.py
│ │ └── x2r.py
│ ├── functor
│ │ ├── addpdf.py
│ │ └── addpdfnorm.py
│ └── pdf
│ │ ├── argus.py
│ │ ├── cauchy.py
│ │ ├── cauchy_bw.py
│ │ ├── cruijff.py
│ │ ├── crystalball.py
│ │ ├── doublecrystalball.py
│ │ ├── doublegaussian.py
│ │ ├── exponential.py
│ │ ├── gaussian.py
│ │ ├── histogrampdf.py
│ │ ├── johnsonSU.py
│ │ ├── linear.py
│ │ ├── novosibirsk.py
│ │ ├── poly2.py
│ │ ├── poly3.py
│ │ ├── polynomial.py
│ │ └── ugaussian.py
├── recipe.rst
└── rtd-pip-requirements
├── probfit
├── __init__.py
├── _libstat.pxd
├── _libstat.pyx
├── costfunc.pyx
├── decorator.py
├── functor.pxd
├── functor.pyx
├── funcutil.pyx
├── log1p.h
├── log1p_patch.pxi
├── nputil.py
├── oneshot.py
├── pdf.pxd
├── pdf.pyx
├── plotting.py
├── probfit_warnings.py
├── py23_compat.py
├── statutil.py
├── toy.py
├── util.py
└── version.py
├── pyproject.toml
├── setup.cfg
├── setup.py
├── tests
├── baseline
│ ├── draw_blh.png
│ ├── draw_blh_extend.png
│ ├── draw_blh_extend_residual_norm.png
│ ├── draw_blh_with_parts.png
│ ├── draw_bx2.png
│ ├── draw_bx2_with_parts.png
│ ├── draw_compare_hist_gaussian.png
│ ├── draw_compare_hist_no_norm.png
│ ├── draw_pdf.png
│ ├── draw_pdf_linear.png
│ ├── draw_residual_blh.png
│ ├── draw_residual_blh_norm.png
│ ├── draw_residual_blh_norm_no_errbars.png
│ ├── draw_residual_blh_norm_options.png
│ ├── draw_residual_ulh.png
│ ├── draw_residual_ulh_norm.png
│ ├── draw_residual_ulh_norm_no_errbars.png
│ ├── draw_residual_ulh_norm_options.png
│ ├── draw_simultaneous.png
│ ├── draw_simultaneous_prefix.png
│ ├── draw_ulh.png
│ ├── draw_ulh_extend.png
│ ├── draw_ulh_extend_residual_norm.png
│ ├── draw_ulh_with_minuit.png
│ ├── draw_ulh_with_parts.png
│ └── draw_x2reg.png
├── conftest.py
├── test_fit.py
├── test_func.py
├── test_functor.py
├── test_oneshot.py
├── test_plotting.py
├── test_toy.py
└── test_util.py
└── tutorial
├── tutorial.ipy
└── tutorial.ipynb
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # Maintain dependencies for GitHub Actions
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | interval: "daily"
8 | ignore:
9 | # Offical actions have moving tags like v1
10 | # that are used, so they don't need updates here
11 | - dependency-name: "actions/*"
12 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Main
2 |
3 | on:
4 | workflow_dispatch:
5 | pull_request:
6 | push:
7 | branches:
8 | - master
9 | - main
10 | - develop
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | python-version:
20 | - 2.7
21 | - 3.5
22 | - 3.9
23 | name: Dev ${{ matrix.python-version }}
24 |
25 | steps:
26 | - uses: actions/checkout@v1
27 |
28 | - name: Setup python
29 | uses: actions/setup-python@v2
30 | with:
31 | python-version: ${{ matrix.python-version }}
32 |
33 | - name: Build and get requirements
34 | run: pip install .[dev]
35 |
36 | - name: Test
37 | run: pytest
38 |
--------------------------------------------------------------------------------
/.github/workflows/wheels.yml:
--------------------------------------------------------------------------------
1 | name: Wheels
2 |
3 | on:
4 | workflow_dispatch:
5 | release:
6 | types:
7 | - published
8 | pull_request:
9 |
10 |
11 | env:
12 | CIBW_TEST_EXTRAS: test
13 | CIBW_TEST_COMMAND_: "pytest {project}/tests"
14 | CIBW_MANYLINUX_X86_64_IMAGE: manylinux1
15 | CIBW_MANYLINUX_I686_IMAGE: manylinux1
16 | CIBW_SKIP: pp* cp27-win*
17 |
18 |
19 | jobs:
20 | build_sdist:
21 | name: Build SDist
22 | runs-on: ubuntu-latest
23 | steps:
24 | - uses: actions/checkout@v2
25 |
26 | - name: Build SDist
27 | run: pipx run --spec build pyproject-build --sdist
28 |
29 | - name: Check metadata
30 | run: pipx run twine check dist/*
31 |
32 | - uses: actions/upload-artifact@v2
33 | with:
34 | path: dist/*.tar.gz
35 |
36 |
37 | build_wheels:
38 | name: Wheels on ${{ matrix.os }}
39 | runs-on: ${{ matrix.os }}
40 | strategy:
41 | fail-fast: false
42 | matrix:
43 | os: [ubuntu-20.04, macos-10.15, windows-latest]
44 |
45 |
46 | steps:
47 | - uses: actions/checkout@v2
48 |
49 | - uses: pypa/cibuildwheel@v1.11.0
50 |
51 | - name: Show files
52 | run: ls -lh wheelhouse
53 | shell: bash
54 |
55 | - name: Verify clean directory
56 | run: git diff --exit-code
57 | shell: bash
58 |
59 | - name: Upload wheels
60 | uses: actions/upload-artifact@v2
61 | with:
62 | path: wheelhouse/*.whl
63 |
64 |
65 | upload_all:
66 | name: Upload if release
67 | needs: [build_wheels, build_sdist]
68 | runs-on: ubuntu-latest
69 | if: github.event_name == 'release' && github.event.action == 'published'
70 |
71 | steps:
72 | - uses: actions/setup-python@v2
73 |
74 | - uses: actions/download-artifact@v2
75 | with:
76 | name: artifact
77 | path: dist
78 |
79 | - uses: pypa/gh-action-pypi-publish@v1.4.2
80 | with:
81 | user: __token__
82 | password: ${{ secrets.pypi_password }}
83 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.root
2 | .DS_Store
3 | *.so
4 | *.ipynb
5 | src/*.html
6 | build
7 | *.pyc
8 | .idea
9 | *.swp
10 | archive
11 | _build
12 | /tutorial.ipynb
13 | /tutorial.py
14 | actual
15 | dist
16 | .coverage
17 | cover
18 | Untitled*.*
19 | *~
20 | *.orig
21 | probfit/*.html
22 | .project
23 | .pydevproject
24 | .settings
25 | probfit.egg-info
26 | .cache
27 | probfit/*.c
28 |
29 | !/tutorial/*.ipynb
30 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v3.4.0
4 | hooks:
5 | - id: check-added-large-files
6 | - id: check-case-conflict
7 | - id: check-merge-conflict
8 | - id: check-symlinks
9 | - id: check-yaml
10 | - id: debug-statements
11 | - id: end-of-file-fixer
12 | - id: mixed-line-ending
13 | - id: requirements-txt-fixer
14 | - id: trailing-whitespace
15 | - id: fix-encoding-pragma
16 |
17 | - repo: https://github.com/psf/black
18 | rev: 20.8b1
19 | hooks:
20 | - id: black
21 |
22 | - repo: https://github.com/PyCQA/isort
23 | rev: 5.7.0
24 | hooks:
25 | - id: isort
26 |
27 | - repo: https://github.com/asottile/pyupgrade
28 | rev: v2.10.0
29 | hooks:
30 | - id: pyupgrade
31 |
32 | - repo: https://gitlab.com/pycqa/flake8
33 | rev: 3.8.4
34 | hooks:
35 | - id: flake8
36 | additional_dependencies: [flake8-bugbear]
37 |
38 | - repo: https://github.com/asottile/setup-cfg-fmt
39 | rev: v1.16.0
40 | hooks:
41 | - id: setup-cfg-fmt
42 | stages: [manual]
43 |
44 | - repo: https://github.com/mgedmin/check-manifest
45 | rev: "0.46"
46 | hooks:
47 | - id: check-manifest
48 | stages: [manual]
49 |
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | # Build documentation in the doc/ directory with Sphinx
9 | sphinx:
10 | configuration: doc/conf.py
11 |
12 | # Include PDF and ePub
13 | formats: all
14 |
15 | python:
16 | version: 3.7
17 | install:
18 | - method: pip
19 | path: .
20 | extra_requirements:
21 | - docs
22 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | Authors ordered by first contribution.
2 |
3 | Piti Ongmongkolkul (piti118@gmail.com)
4 | Christoph Deil (https://github.com/cdeil)
5 | Chih-hsiang Cheng (ahsiang.c@gmail.com)
6 | Andreas Bally (https://github.com/andreas01060)
7 | Alex Pearce (https://github.com/alexpearce)
8 | Henry Schreiner (https://github.com/henryiii)
9 | Matthieu Marinangeli (https://github.com/marinang)
10 | Eduardo Rodrigues (https://github.com/eduardo-rodrigues)
11 | Hans Dembinski(https://github.com/HDembinski)
12 | Luke Kreczko (https://github.com/kreczko)
13 | Keita Mizukoshi (https://github.com/mzks)
14 | Lukas Geiger (https://github.com/lgeiger)
15 |
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 | ---------- 1.2.0 ----------
2 |
3 | #108 Formatting updates
4 |
5 | #107 Smoother drawings, right side crystal ball
6 |
7 | #105 Major build system update
8 |
9 | #104 Support prebinned data
10 |
11 | #100 Fix integrate method in johnsonSU
12 |
13 | #98 Add exponential PDF
14 |
15 | #96 Matplotlib dep main optional
16 |
17 | ---------- 1.1.0 -----------
18 |
19 | #95 First version to support Python 3
20 |
21 | ... missing PRs, see GitHub ...
22 |
23 | #36 Custom Drawing from draw() return values -- PO
24 |
25 | #35 Add BlindFunc to blind the true value of parameters -- CC
26 |
27 | #33 Add draw_residual to (Un)binnedLH. BinnedLH draw show raw yield. Default
28 | UnbinnedLH draw shows error bars. Add HistogramPdf analytical integral.
29 |
30 | #30 AddPdf, Normalized, Extended, AddPdfNorm cascades analytical integral -- PO
31 |
32 | #29 probfit integrate1d now use integrate method if available -- PO
33 |
34 | #25 Use simpson3/8 rule instead of trapezoid for integral -- PO
35 |
36 | #23 UnbinnedLH draw now show raw yield instead of density -- CC
37 |
38 | #23 Add Errorbar option for drawing UnbinnedLH -- CC
39 |
40 | #21 Add nint_subdiv to control how BinnedLH and
41 | BinnedChi2 calculate integral -- PO
42 |
43 | #19 Fix plotting normlization(to zero) -- PO
44 |
45 | #16 Add a pdf class HistogramPdf -- CC
46 |
47 | #14 Fix AddPdfNorm calculating wrong factor for the last part -- PO
48 |
49 | --------- 1.0.4 ---------
50 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012-2021 Piti Ongmongkolkul
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include probfit *.h *.c *.pyx *.pxi *.pxd *.py
2 | recursive-include tests *.py *.png
3 | recursive-include tutorial *.ipynb *.py
4 | include README.rst
5 | include LICENSE AUTHORS CHANGELOG
6 | include setup.py
7 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | .. -*- mode: rst -*-
2 |
3 | probfit
4 | =======
5 |
6 | .. image:: https://img.shields.io/pypi/v/probfit.svg
7 | :target: https://pypi.python.org/pypi/probfit
8 |
9 | .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.1477852.svg
10 | :target: https://doi.org/10.5281/zenodo.1477852
11 |
12 | .. image:: https://github.com/scikit-hep/probfit/actions/workflows/main.yml/badge.svg
13 | :target: https://github.com/scikit-hep/probfit/actions/workflows/main.yml
14 |
15 | *probfit* is a set of functions that helps you construct a complex fit. It's
16 | intended to be used with `iminuit `_. The
17 | tool includes Binned/Unbinned Likelihood estimators, 𝝌² regression,
18 | Binned 𝝌² estimator and Simultaneous fit estimator.
19 | Various functors for manipulating PDFs such as Normalization and
20 | Convolution (with caching) and various built-in functions
21 | normally used in B physics are also provided.
22 |
23 | Strict dependencies
24 | -------------------
25 |
26 | - `Python `__ (2.7+, 3.5+)
27 | - `NumPy `__
28 | - `iminuit `_ (<2)
29 |
30 | Optional dependencies
31 | ---------------------
32 |
33 | - `matplotlib `_ for the plotting functions
34 |
35 | Getting started
36 | ---------------
37 |
38 | .. code-block:: python
39 |
40 | import numpy as np
41 | from iminuit import Minuit
42 | from probfit import UnbinnedLH, gaussian
43 | data = np.random.randn(10000)
44 | unbinned_likelihood = UnbinnedLH(gaussian, data)
45 | minuit = Minuit(unbinned_likelihood, mean=0.1, sigma=1.1)
46 | minuit.migrad()
47 | unbinned_likelihood.draw(minuit)
48 |
49 | Documentation and Tutorial
50 | --------------------------
51 |
52 | * `Documentation `_
53 | * The tutorial is an IPython notebook that you can view online
54 | `here `_.
55 | To run it locally: `cd tutorial; ipython notebook --pylab=inline tutorial.ipynb`.
56 | * Developing probfit: see the `development page `_
57 |
58 | License
59 | -------
60 |
61 | The package is licensed under the `MIT `_ license (open source).
62 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/probfit.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/probfit.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/probfit"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/probfit"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/doc/api.rst:
--------------------------------------------------------------------------------
1 | .. _fullapi:
2 |
3 | Full API Documentation
4 | ======================
5 |
6 | .. _costfunc:
7 |
8 | Cost Function
9 | -------------
10 |
11 | Various estimators.
12 |
13 | Unbinned Likelihood
14 | ^^^^^^^^^^^^^^^^^^^
15 |
16 | .. currentmodule:: probfit.costfunc
17 |
18 | .. autoclass:: UnbinnedLH
19 |
20 | .. automethod:: __call__
21 | .. automethod:: draw
22 | .. automethod:: draw_residual
23 | .. automethod:: show
24 |
25 | **Example**
26 |
27 | .. plot:: pyplots/costfunc/ulh.py
28 | :class: lightbox
29 |
30 | Binned Likelihood
31 | ^^^^^^^^^^^^^^^^^
32 |
33 | .. autoclass:: BinnedLH
34 |
35 | .. automethod:: __call__
36 | .. automethod:: draw
37 | .. automethod:: draw_residual
38 | .. automethod:: show
39 |
40 | **Example**
41 |
42 | .. plot:: pyplots/costfunc/blh.py
43 | :class: lightbox
44 |
45 | :math:`\chi^2` Regression
46 | ^^^^^^^^^^^^^^^^^^^^^^^^^
47 |
48 | .. autoclass:: Chi2Regression
49 |
50 | .. automethod:: __call__
51 | .. automethod:: draw
52 | .. automethod:: draw_residual
53 | .. automethod:: show
54 |
55 | **Example**
56 |
57 | .. plot:: pyplots/costfunc/x2r.py
58 | :class: lightbox
59 |
60 | Binned :math:`\chi^2`
61 | ^^^^^^^^^^^^^^^^^^^^^
62 |
63 | .. autoclass:: BinnedChi2
64 |
65 | .. automethod:: __call__
66 | .. automethod:: draw
67 | .. automethod:: show
68 |
69 | **Example**
70 |
71 |
72 | .. plot:: pyplots/costfunc/bx2.py
73 | :class: lightbox
74 |
75 | Simultaneous Fit
76 | ^^^^^^^^^^^^^^^^
77 |
78 | .. autoclass:: SimultaneousFit
79 |
80 | .. automethod:: __call__
81 | .. automethod:: args_and_error_for
82 | .. automethod:: draw
83 | .. automethod:: show
84 |
85 | **Example**
86 |
87 | .. plot:: pyplots/costfunc/simul.py
88 |
89 | .. _functor:
90 |
91 | Functor
92 | -------
93 |
94 | Manipulate and combined your pdf in various ways.
95 |
96 | .. currentmodule:: probfit.functor
97 |
98 | Extended
99 | ^^^^^^^^
100 | .. autoclass:: Extended
101 |
102 | Normalized
103 | ^^^^^^^^^^
104 | .. autoclass:: Normalized
105 |
106 | Convolve
107 | ^^^^^^^^
108 | .. autoclass:: Convolve
109 |
110 | BlindFunc
111 | ^^^^^^^^^
112 | .. autoclass:: BlindFunc
113 |
114 | AddPdf
115 | ^^^^^^
116 | .. autoclass:: AddPdf
117 |
118 | **Example**
119 |
120 | .. plot:: pyplots/functor/addpdf.py
121 | :class: lightbox
122 |
123 | AddPdfNorm
124 | ^^^^^^^^^^
125 | .. autoclass:: AddPdfNorm
126 |
127 | **Example**
128 |
129 | .. plot:: pyplots/functor/addpdfnorm.py
130 | :class: lightbox
131 |
132 | rename
133 | ^^^^^^
134 |
135 | .. autofunction:: probfit.funcutil.rename
136 |
137 | Decorator
138 | ^^^^^^^^^
139 | .. currentmodule:: probfit.decorator
140 |
141 | .. autoclass:: normalized
142 |
143 | .. autoclass:: extended
144 |
145 | .. _builtin:
146 |
147 | Builtin PDF
148 | -----------
149 |
150 | Builtin PDF written in cython.
151 |
152 | .. currentmodule:: probfit.pdf
153 |
154 | gaussian
155 | ^^^^^^^^
156 | .. autofunction:: gaussian
157 |
158 | .. plot:: pyplots/pdf/gaussian.py
159 | :class: lightbox
160 |
161 | cauchy
162 | ^^^^^^
163 | .. autofunction:: cauchy
164 |
165 | Breit-Wigner
166 | ^^^^^^^^^^^^
167 | .. autofunction:: rtv_breitwigner
168 |
169 | crystalball
170 | ^^^^^^^^^^^
171 | .. autofunction:: crystalball
172 |
173 | .. plot:: pyplots/pdf/crystalball.py
174 | :class: lightbox
175 |
176 | doublecrystalball
177 | ^^^^^^^^^^^
178 | .. autofunction:: doublecrystalball
179 |
180 | .. plot:: pyplots/pdf/doublecrystalball.py
181 | :class: lightbox
182 |
183 | cruijff
184 | ^^^^^^^
185 | .. autofunction:: cruijff
186 |
187 | .. plot:: pyplots/pdf/cruijff.py
188 | :class: lightbox
189 |
190 | doublegaussian
191 | ^^^^^^^^^^^^^^
192 | .. autofunction:: doublegaussian
193 |
194 | .. plot:: pyplots/pdf/doublegaussian.py
195 | :class: lightbox
196 |
197 | novosibirsk
198 | ^^^^^^^^^^^
199 | .. autofunction:: novosibirsk
200 |
201 | .. plot:: pyplots/pdf/novosibirsk.py
202 | :class: lightbox
203 |
204 | argus
205 | ^^^^^
206 | .. autofunction:: argus
207 |
208 | .. plot:: pyplots/pdf/argus.py
209 | :class: lightbox
210 |
211 | linear
212 | ^^^^^^
213 | .. autofunction:: linear
214 |
215 | .. plot:: pyplots/pdf/linear.py
216 | :class: lightbox
217 |
218 | poly2
219 | ^^^^^
220 | .. autofunction:: poly2
221 | .. plot:: pyplots/pdf/poly2.py
222 | :class: lightbox
223 |
224 | poly3
225 | ^^^^^
226 | .. autofunction:: poly3
227 |
228 | .. plot:: pyplots/pdf/poly3.py
229 | :class: lightbox
230 |
231 | JohnsonSU
232 | ^^^^^^^^^
233 | .. autofunction:: johnsonSU
234 |
235 | .. plot:: pyplots/pdf/johnsonSU.py
236 | :class: lightbox
237 |
238 | Exponential
239 | ^^^^^^^^^^^
240 | .. autofunction:: exponential
241 |
242 | .. plot:: pyplots/pdf/exponential.py
243 | :class: lightbox
244 |
245 | Polynomial
246 | ^^^^^^^^^^
247 | .. autoclass:: Polynomial
248 |
249 | .. plot:: pyplots/pdf/polynomial.py
250 | :class: lightbox
251 |
252 | HistogramPdf
253 | ^^^^^^^^^^^^
254 | .. autoclass:: HistogramPdf
255 |
256 | .. plot:: pyplots/pdf/histogrampdf.py
257 | :class: lightbox
258 |
259 | Useful Utility Function
260 | -----------------------
261 | vector_apply
262 | ^^^^^^^^^^^^
263 | .. autofunction:: probfit.nputil.vector_apply
264 |
265 | draw_pdf
266 | ^^^^^^^^
267 | .. autofunction:: probfit.plotting.draw_pdf
268 |
269 | draw_compare_hist
270 | ^^^^^^^^^^^^^^^^^
271 | .. autofunction:: probfit.plotting.draw_compare_hist
272 |
273 | draw_residual
274 | ^^^^^^^^^^^^^
275 | .. autofunction:: probfit.plotting.draw_residual
276 |
--------------------------------------------------------------------------------
/doc/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # probfit documentation build configuration file, created by
4 | # sphinx-quickstart on Sat Nov 10 11:16:37 2012.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 | import matplotlib
14 |
15 | matplotlib.use("Agg")
16 | # For local development we use the `iminuit` from the source folder.
17 | # On readthedocs we use the one from `site-packages`.
18 | # See https://github.com/scikit-hep/iminuit/issues/126#issuecomment-61472227
19 | # and http://read-the-docs.readthedocs.org/en/latest/faq.html#how-do-i-change-behavior-for-read-the-docs
20 | import os
21 | import sys
22 | from os.path import dirname, join
23 |
24 | on_rtd = os.environ.get("READTHEDOCS", None) == "True"
25 |
26 | if not on_rtd:
27 | sys.path.insert(0, join(dirname(__file__), "../"))
28 |
29 | # If extensions (or modules to document with autodoc) are in another directory,
30 | # add these directories to sys.path here. If the directory is relative to the
31 | # documentation root, use os.path.abspath to make it absolute, like shown here.
32 | # sys.path.insert(0, os.path.abspath('.'))
33 |
34 | # -- General configuration -----------------------------------------------------
35 |
36 | # If your documentation needs a minimal Sphinx version, state it here.
37 | # needs_sphinx = '1.0'
38 |
39 | # Add any Sphinx extension module names here, as strings. They can be extensions
40 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
41 | extensions = [
42 | "matplotlib.sphinxext.plot_directive",
43 | "IPython.sphinxext.ipython_directive",
44 | "sphinx.ext.autodoc",
45 | "sphinx.ext.mathjax",
46 | "sphinx.ext.autosummary",
47 | ]
48 |
49 | autosummary_generate = True
50 |
51 | # Add any paths that contain templates here, relative to this directory.
52 | templates_path = ["_templates"]
53 |
54 | # The suffix of source filenames.
55 | source_suffix = ".rst"
56 |
57 | # The encoding of source files.
58 | # source_encoding = 'utf-8-sig'
59 |
60 | # The master toctree document.
61 | master_doc = "index"
62 |
63 | # General information about the project.
64 | project = u"probfit"
65 | copyright = u"2012-2021, Piti Ongmongkolkul"
66 | autoclass_content = "both"
67 | # The version info for the project you're documenting, acts as replacement for
68 | # |version| and |release|, also used in various other places throughout the
69 | # built documents.
70 | #
71 | # The short X.Y version.
72 | import probfit.version
73 |
74 | version = probfit.version.__version__
75 | # The full version, including alpha/beta/rc tags.
76 | release = probfit.version.__version__
77 |
78 | # The language for content autogenerated by Sphinx. Refer to documentation
79 | # for a list of supported languages.
80 | # language = None
81 |
82 | # There are two options for replacing |today|: either, you set today to some
83 | # non-false value, then it is used:
84 | # today = ''
85 | # Else, today_fmt is used as the format for a strftime call.
86 | # today_fmt = '%B %d, %Y'
87 |
88 | # List of patterns, relative to source directory, that match files and
89 | # directories to ignore when looking for source files.
90 | exclude_patterns = ["_build", "_themes"]
91 |
92 | # The reST default role (used for this markup: `text`) to use for all documents.
93 | # default_role = None
94 |
95 | # If true, '()' will be appended to :func: etc. cross-reference text.
96 | # add_function_parentheses = True
97 |
98 | # If true, the current module name will be prepended to all description
99 | # unit titles (such as .. function::).
100 | # add_module_names = True
101 |
102 | # If true, sectionauthor and moduleauthor directives will be shown in the
103 | # output. They are ignored by default.
104 | # show_authors = False
105 |
106 | # The name of the Pygments (syntax highlighting) style to use.
107 | pygments_style = "sphinx"
108 |
109 | # A list of ignored prefixes for module index sorting.
110 | # modindex_common_prefix = []
111 |
112 |
113 | # -- Options for HTML output ---------------------------------------------------
114 |
115 | # The theme to use for HTML and HTML Help pages. See the documentation for
116 | # a list of builtin themes.
117 | if not on_rtd:
118 | try:
119 | # https://github.com/snide/sphinx_rtd_theme
120 | import sphinx_rtd_theme
121 |
122 | html_theme = "sphinx_rtd_theme"
123 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
124 | except ImportError:
125 | # Fallback to default theme
126 | print(
127 | "WARNING: To get nice docs locally that look like the online docs, please do:"
128 | )
129 | print("WARNING: $ pip install sphinx_rtd_theme --user")
130 | print("WARNING: Using default theme.")
131 | html_theme = "nature"
132 |
133 | # Theme options are theme-specific and customize the look and feel of a theme
134 | # further. For a list of options available for each theme, see the
135 | # documentation.
136 | # html_theme_options = {}
137 |
138 | # Add any paths that contain custom themes here, relative to this directory.
139 | # if not on_rtd:
140 | # html_theme_path = ['_themes', ]
141 |
142 | # The name for this set of Sphinx documents. If None, it defaults to
143 | # " v documentation".
144 | # html_title = None
145 |
146 | # A shorter title for the navigation bar. Default is the same as html_title.
147 | # html_short_title = None
148 |
149 | # The name of an image file (relative to this directory) to place at the top
150 | # of the sidebar.
151 | # html_logo = None
152 |
153 | # The name of an image file (within the static path) to use as favicon of the
154 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
155 | # pixels large.
156 | # html_favicon = None
157 |
158 | # Add any paths that contain custom static files (such as style sheets) here,
159 | # relative to this directory. They are copied after the builtin static files,
160 | # so a file named "default.css" will overwrite the builtin "default.css".
161 | # html_static_path = ['_static']
162 |
163 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
164 | # using the given strftime format.
165 | # html_last_updated_fmt = '%b %d, %Y'
166 |
167 | # If true, SmartyPants will be used to convert quotes and dashes to
168 | # typographically correct entities.
169 | # html_use_smartypants = True
170 |
171 | # Custom sidebar templates, maps document names to template names.
172 | # html_sidebars = {}
173 |
174 | # Additional templates that should be rendered to pages, maps page names to
175 | # template names.
176 | # html_additional_pages = {}
177 |
178 | # If false, no module index is generated.
179 | # html_domain_indices = True
180 |
181 | # If false, no index is generated.
182 | # html_use_index = True
183 |
184 | # If true, the index is split into individual pages for each letter.
185 | # html_split_index = False
186 |
187 | # If true, links to the reST sources are added to the pages.
188 | # html_show_sourcelink = True
189 |
190 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
191 | # html_show_sphinx = True
192 |
193 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
194 | # html_show_copyright = True
195 |
196 | # If true, an OpenSearch description file will be output, and all pages will
197 | # contain a tag referring to it. The value of this option must be the
198 | # base URL from which the finished HTML is served.
199 | # html_use_opensearch = ''
200 |
201 | # This is the file name suffix for HTML files (e.g. ".xhtml").
202 | # html_file_suffix = None
203 |
204 | # Output file base name for HTML help builder.
205 | htmlhelp_basename = "probfitdoc"
206 |
207 |
208 | # -- Options for LaTeX output --------------------------------------------------
209 |
210 | latex_elements = {
211 | # The paper size ('letterpaper' or 'a4paper').
212 | #'papersize': 'letterpaper',
213 | # The font size ('10pt', '11pt' or '12pt').
214 | #'pointsize': '10pt',
215 | # Additional stuff for the LaTeX preamble.
216 | #'preamble': '',
217 | }
218 |
219 | # Grouping the document tree into LaTeX files. List of tuples
220 | # (source start file, target name, title, author, documentclass [howto/manual]).
221 | latex_documents = [
222 | ("index", "probfit.tex", u"probfit Documentation", u"Piti Ongmongkolkul", "manual"),
223 | ]
224 |
225 | # The name of an image file (relative to this directory) to place at the top of
226 | # the title page.
227 | # latex_logo = None
228 |
229 | # For "manual" documents, if this is true, then toplevel headings are parts,
230 | # not chapters.
231 | # latex_use_parts = False
232 |
233 | # If true, show page references after internal links.
234 | # latex_show_pagerefs = False
235 |
236 | # If true, show URL addresses after external links.
237 | # latex_show_urls = False
238 |
239 | # Documents to append as an appendix to all manuals.
240 | # latex_appendices = []
241 |
242 | # If false, no module index is generated.
243 | # latex_domain_indices = True
244 |
245 |
246 | # -- Options for manual page output --------------------------------------------
247 |
248 | # One entry per manual page. List of tuples
249 | # (source start file, name, description, authors, manual section).
250 | man_pages = [("index", "probfit", u"probfit Documentation", [u"Piti Ongmongkolkul"], 1)]
251 |
252 | # If true, show URL addresses after external links.
253 | # man_show_urls = False
254 |
255 |
256 | # -- Options for Texinfo output ------------------------------------------------
257 |
258 | # Grouping the document tree into Texinfo files. List of tuples
259 | # (source start file, target name, title, author,
260 | # dir menu entry, description, category)
261 | texinfo_documents = [
262 | (
263 | "index",
264 | "probfit",
265 | u"probfit Documentation",
266 | u"Piti Ongmongkolkul",
267 | "probfit",
268 | "Fitting Stuff",
269 | "Miscellaneous",
270 | ),
271 | ]
272 |
273 | # Documents to append as an appendix to all manuals.
274 | # texinfo_appendices = []
275 |
276 | # If false, no module index is generated.
277 | # texinfo_domain_indices = True
278 |
279 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
280 | # texinfo_show_urls = 'footnote'
281 |
--------------------------------------------------------------------------------
/doc/development.rst:
--------------------------------------------------------------------------------
1 | .. _development:
2 |
3 | Development
4 | ===========
5 |
6 | Contributions to probfit are welcome. You should fork the repository,
7 | create a branch in your fork, and then `open a pull request
8 | `_.
9 |
10 | Developing probfit requires a few dependencies, in addition to those required
11 | for users. The following commands should create a suitable environment,
12 | assuming you've cloned your fork of probfit and are in the repository root.
13 | (You may wish to work inside a virtual environment to isolate these packages
14 | from your system install.)
15 |
16 | .. code-block:: shell
17 |
18 | $ pip install cython pytest pytest-mpl pylint flake8 sphinx sphinx_rtd_theme
19 | $ make build
20 |
21 | Installing `Cython `_ will allow you to build the C
22 | extensions. `Pylint `_ and `flake8
23 | `_ are used for linting, `pytest
24 | `_ for testing, and `Sphinx
25 | `_ for generating the HTML documentation.
26 |
27 | When developing, be sure to regularly run the test suite to see if anything's
28 | broken. The suite is run automatically against Python 2 and 3 when you open a
29 | pull request, and also when you push subsequent commits. It can be run locally
30 | with:
31 |
32 | .. code-block:: shell
33 |
34 | $ make test
35 | $ make code-analysis
36 |
37 | To build and view the documentation, run ``make doc-show``.
38 |
39 | For a list of everything you can do, run ``make help``. If you run into any
40 | trouble, please open an issue.
41 |
--------------------------------------------------------------------------------
/doc/index.rst:
--------------------------------------------------------------------------------
1 | probfit
2 | =======
3 |
4 | *probfit* is a set of functions that helps you construct a complex fit.
5 | It is intended to be used with `iminuit `_.
6 | The tool includes Binned/Unbinned Likelihood estimator, :math:`\chi^2` regression, binned :math:`\chi^2` estimator and Simultaneous fit estimator.
7 | Normalization and convolution with cache are also included. Various builtin functions used in B physics are also provided.
8 |
9 | In a nutshell::
10 |
11 | import numpy as np
12 | from iminuit import Minuit
13 | from probfit import UnbinnedLH, gaussian
14 | data = np.random.randn(10000)
15 | unbinned_likelihood = UnbinnedLH(gaussian, data)
16 | minuit = Minuit(unbinned_likelihood, mean=0.1, sigma=1.1)
17 | minuit.migrad()
18 | unbinned_likelihood.draw(minuit)
19 |
20 | Probfit was created by Piti Ongmongkolkul (piti118@gmail.com). It is currently maintained by the Scikit-HEP community.
21 |
22 | .. toctree::
23 | :maxdepth: 4
24 | :hidden:
25 |
26 | api.rst
27 | recipe.rst
28 | development.rst
29 |
30 | Download & Install
31 | ------------------
32 |
33 | From pip::
34 |
35 | pip install probfit
36 |
37 | or get the latest development from github::
38 |
39 | pip install git+https://github.com/scikit-hep/probfit.git
40 |
41 | Tutorial
42 | --------
43 |
44 | The tutorial consists of an IPython notebook in the tutorial directory.
45 | You can `view it online `_ too.
46 |
47 |
48 | Commonly used API
49 | -----------------
50 |
51 | .. currentmodule:: probfit
52 |
53 | Refer to :ref:`fullapi` for complete reference.
54 |
55 | Cost Functions
56 | """"""""""""""
57 |
58 | Refer to :ref:`costfunc`.
59 |
60 | .. currentmodule:: probfit.costfunc
61 |
62 | .. autosummary::
63 | UnbinnedLH
64 | BinnedLH
65 | Chi2Regression
66 | BinnedChi2
67 | SimultaneousFit
68 |
69 | Functors
70 | """"""""
71 |
72 | Refer to :ref:`functor`
73 |
74 | .. currentmodule:: probfit.functor
75 |
76 | .. autosummary::
77 | Normalized
78 | Extended
79 | Convolve
80 | AddPdf
81 | AddPdfNorm
82 | ~probfit.funcutil.rename
83 |
84 | And corresponding decorator
85 |
86 | .. currentmodule:: probfit.decorator
87 |
88 | .. autosummary::
89 | normalized
90 | extended
91 |
92 | Builtin Functions
93 | """""""""""""""""
94 |
95 | Refer to :ref:`builtin`. This list can grow: implement your favorite function
96 | and send us pull request.
97 |
98 | .. currentmodule:: probfit.pdf
99 |
100 | .. autosummary::
101 | gaussian
102 | crystalball
103 | doublecrystalball
104 | cruijff
105 | cauchy
106 | rtv_breitwigner
107 | doublegaussian
108 | johnsonSU
109 | argus
110 | linear
111 | poly2
112 | poly3
113 | novosibirsk
114 | HistogramPdf
115 | Polynomial
116 |
117 |
118 | Useful utility
119 | """"""""""""""
120 |
121 | You may find these functions useful in interactive environment.
122 |
123 | .. autosummary::
124 | ~probfit.nputil.vector_apply
125 | ~probfit.plotting.draw_pdf
126 | ~probfit.plotting.draw_compare_hist
127 |
128 | Cookbook
129 | --------
130 |
131 | Cookbook recipies are at :ref:`cookbook`.
132 |
133 | Development
134 | -----------
135 |
136 | If you'd like to develop with the probfit source code, see the :ref:`development` section.
137 |
--------------------------------------------------------------------------------
/doc/pyplots/costfunc/blh.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from iminuit import Minuit
3 | from matplotlib import pyplot as plt
4 | from numpy.random import randn
5 |
6 | from probfit import BinnedLH, Extended, gaussian
7 |
8 | data = randn(1000) * 2 + 1
9 |
10 | # Unextended
11 |
12 | blh = BinnedLH(gaussian, data)
13 | # if you wonder what it looks like call describe(blh)
14 | m = Minuit(blh, mean=0.0, sigma=0.5)
15 |
16 | plt.figure(figsize=(8, 6))
17 | plt.subplot(221)
18 | blh.draw(m)
19 | plt.title("Unextended Before")
20 |
21 | m.migrad() # fit
22 |
23 | plt.subplot(222)
24 | blh.draw(m)
25 | plt.title("Unextended After")
26 |
27 | # Extended
28 |
29 | ext_gauss = Extended(gaussian)
30 |
31 | blh = BinnedLH(ext_gauss, data, extended=True)
32 | m = Minuit(blh, mean=0.0, sigma=0.5, N=900.0)
33 |
34 | plt.subplot(223)
35 | blh.draw(m)
36 | plt.title("Extended Before")
37 |
38 | m.migrad()
39 |
40 | plt.subplot(224)
41 | blh.draw(m)
42 | plt.title("Extended After")
43 |
--------------------------------------------------------------------------------
/doc/pyplots/costfunc/bx2.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from iminuit import Minuit
3 | from matplotlib import pyplot as plt
4 | from numpy.random import randn, seed
5 |
6 | from probfit import BinnedChi2, Extended, gaussian
7 |
8 | seed(0)
9 | data = randn(1000) * 2 + 1
10 |
11 | ext_gauss = Extended(gaussian)
12 | ulh = BinnedChi2(ext_gauss, data)
13 |
14 | m = Minuit(ulh, mean=0.0, sigma=0.5, N=800)
15 |
16 | plt.figure(figsize=(8, 3))
17 | plt.subplot(121)
18 | ulh.draw(m)
19 | plt.title("Before")
20 |
21 | m.migrad() # fit
22 |
23 | plt.subplot(122)
24 | ulh.draw(m)
25 | plt.title("After")
26 |
--------------------------------------------------------------------------------
/doc/pyplots/costfunc/simul.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from iminuit import Minuit
3 | from matplotlib import pyplot as plt
4 | from numpy.random import randn, seed
5 |
6 | from probfit import SimultaneousFit, UnbinnedLH, gaussian, rename
7 |
8 | seed(0)
9 | width = 2.0
10 | data1 = randn(1000) * width + 1
11 | data2 = randn(1000) * width + 2
12 |
13 | # two gaussian with shared width
14 | pdf1 = rename(gaussian, ("x", "mu_1", "sigma"))
15 | pdf2 = rename(gaussian, ("x", "mu_2", "sigma"))
16 |
17 | lh1 = UnbinnedLH(pdf1, data1)
18 | lh2 = UnbinnedLH(pdf2, data2)
19 |
20 | simlh = SimultaneousFit(lh1, lh2)
21 |
22 | m = Minuit(simlh, mu_1=1.2, mu_2=2.2, sigma=1.5)
23 |
24 | plt.figure(figsize=(8, 3))
25 | plt.subplot(211)
26 | simlh.draw(m)
27 | plt.suptitle("Before")
28 |
29 | m.migrad() # fit
30 |
31 | plt.figure(figsize=(8, 3))
32 | plt.subplot(212)
33 | simlh.draw(m)
34 | plt.suptitle("After")
35 |
--------------------------------------------------------------------------------
/doc/pyplots/costfunc/ulh.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from iminuit import Minuit
3 | from matplotlib import pyplot as plt
4 | from numpy.random import randn
5 |
6 | from probfit import Extended, UnbinnedLH, gaussian
7 |
8 | data = randn(1000) * 2 + 1
9 |
10 | ulh = UnbinnedLH(gaussian, data)
11 | m = Minuit(ulh, mean=0.0, sigma=0.5)
12 |
13 | plt.figure(figsize=(8, 6))
14 | plt.subplot(221)
15 | ulh.draw(m)
16 | plt.title("Unextended Before")
17 |
18 | m.migrad() # fit
19 |
20 | plt.subplot(222)
21 | ulh.draw(m)
22 | plt.title("Unextended After")
23 |
24 | # Extended
25 |
26 | data = randn(2000) * 2 + 1
27 | egauss = Extended(gaussian)
28 | ulh = UnbinnedLH(egauss, data, extended=True, extended_bound=(-10.0, 10.0))
29 | m = Minuit(ulh, mean=0.0, sigma=0.5, N=1800.0)
30 |
31 | plt.subplot(223)
32 | ulh.draw(m)
33 | plt.title("Extended Before")
34 |
35 | m.migrad() # fit
36 |
37 | plt.subplot(224)
38 | ulh.draw(m)
39 | plt.title("Extended After")
40 |
--------------------------------------------------------------------------------
/doc/pyplots/costfunc/x2r.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import numpy as np
3 | from iminuit import Minuit
4 | from matplotlib import pyplot as plt
5 | from numpy.random import randn, seed
6 |
7 | from probfit import Chi2Regression, linear
8 |
9 | seed(0)
10 | ndata = 30
11 | x = np.linspace(-10, 10, ndata)
12 | y = 2 * x + 5
13 | y += randn(ndata)
14 |
15 | x2r = Chi2Regression(linear, x, y, np.array([1.0] * ndata))
16 |
17 | m = Minuit(x2r, m=1, c=2)
18 |
19 | plt.figure(figsize=(8, 3))
20 | plt.subplot(121)
21 | x2r.draw(m)
22 | plt.title("Before")
23 |
24 | m.migrad() # fit
25 |
26 | plt.subplot(122)
27 | x2r.draw(m)
28 | plt.title("After")
29 |
--------------------------------------------------------------------------------
/doc/pyplots/functor/addpdf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import numpy as np
3 | from iminuit import Minuit
4 | from matplotlib import pyplot as plt
5 | from numpy.random import randn
6 |
7 | from probfit import AddPdf, BinnedLH, Extended, gaussian, rename
8 |
9 | peak1 = randn(1000) * 0.5 + 1.0
10 | peak2 = randn(500) * 0.5 + 0.0
11 | # two peaks data with shared width
12 | data = np.concatenate([peak1, peak2])
13 |
14 | # Share the width
15 | # If you use Normalized here. Do not reuse the object.
16 | # It will be really slow due to cache miss. Read Normalized doc for more info.
17 | pdf1 = rename(gaussian, ("x", "m_1", "sigma"))
18 | pdf2 = rename(gaussian, ("x", "m_2", "sigma"))
19 |
20 | ext_pdf1 = Extended(pdf1, extname="N_1")
21 | ext_pdf2 = Extended(pdf2, extname="N_2")
22 |
23 | compdf = AddPdf(ext_pdf1, ext_pdf2) # merge by name (merge sigma)
24 |
25 | ulh = BinnedLH(compdf, data, extended=True)
26 | m = Minuit(ulh, m_1=0.1, m_2=-0.1, sigma=0.1, N_1=900, N_2=480)
27 |
28 | plt.figure(figsize=(8, 3))
29 | plt.subplot(121)
30 | ulh.draw(m, parts=True)
31 | plt.title("Before")
32 |
33 | m.migrad() # fit
34 |
35 | plt.subplot(122)
36 | ulh.draw(m, parts=True)
37 | plt.title("After")
38 |
--------------------------------------------------------------------------------
/doc/pyplots/functor/addpdfnorm.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import numpy as np
3 | from iminuit import Minuit
4 | from matplotlib import pyplot as plt
5 | from numpy.random import randn
6 |
7 | from probfit import AddPdfNorm, BinnedLH, gaussian, rename
8 |
9 | peak1 = randn(1000) * 0.5 + 1.0
10 | peak2 = randn(500) * 0.5 + 0.0
11 | # two peaks data with shared width
12 | data = np.concatenate([peak1, peak2])
13 |
14 | # Share the width
15 | # If you use Normalized here. Do not reuse the object.
16 | # It will be really slow due to cache miss. Read Normalized doc for more info.
17 | pdf1 = rename(gaussian, ("x", "m_1", "sigma"))
18 | pdf2 = rename(gaussian, ("x", "m_2", "sigma"))
19 |
20 | compdf = AddPdfNorm(pdf1, pdf2) # merge by name (merge sigma)
21 |
22 | ulh = BinnedLH(compdf, data, extended=False)
23 | m = Minuit(ulh, m_1=1.1, m_2=-0.1, sigma=0.48, f_0=0.6, limit_f_0=(0, 1))
24 |
25 | plt.figure(figsize=(8, 3))
26 | plt.subplot(121)
27 | ulh.draw(m, parts=True)
28 | plt.title("Before")
29 |
30 | m.migrad() # fit
31 |
32 | plt.subplot(122)
33 | ulh.draw(m, parts=True)
34 | plt.title("After")
35 |
--------------------------------------------------------------------------------
/doc/pyplots/pdf/argus.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import matplotlib.pyplot as plt
3 |
4 | from probfit.pdf import argus
5 | from probfit.plotting import draw_normed_pdf
6 |
7 | bound = (5.22, 5.30)
8 |
9 | arg = dict(c=5.29, chi=1.0, p=0.5)
10 | draw_normed_pdf(argus, arg=arg, bound=bound, label=str(arg), density=True)
11 |
12 | arg = dict(c=5.29, chi=1.0, p=0.4)
13 | draw_normed_pdf(argus, arg=arg, bound=bound, label=str(arg), density=True)
14 |
15 | arg = dict(c=5.29, chi=2.0, p=0.5)
16 | draw_normed_pdf(argus, arg=arg, bound=bound, label=str(arg), density=True)
17 |
18 |
19 | plt.grid(True)
20 | plt.legend().get_frame().set_alpha(0.5)
21 |
--------------------------------------------------------------------------------
/doc/pyplots/pdf/cauchy.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from collections import OrderedDict
3 |
4 | import matplotlib.pyplot as plt
5 |
6 | from probfit.pdf import cauchy, rtv_breitwigner
7 | from probfit.plotting import draw_pdf
8 |
9 | bound = (5.24, 5.32)
10 |
11 | arg = OrderedDict(m=5.28, gamma=1)
12 | draw_pdf(cauchy, arg=arg, bound=bound, label="cauchy" + str(arg), density=True)
13 |
14 | arg = OrderedDict(m=-5.28, gamma=2)
15 | draw_pdf(cauchy, arg=arg, bound=bound, label="cauchy" + str(arg), density=True)
16 |
17 | arg = OrderedDict(m=5.28, gamma=1)
18 | draw_pdf(rtv_breitwigner, arg=arg, bound=bound, label="bw" + str(arg), density=True)
19 |
20 | arg = OrderedDict(m=-5.28, gamma=2)
21 | draw_pdf(rtv_breitwigner, arg=arg, bound=bound, label="bw" + str(arg), density=True)
22 |
23 | plt.grid(True)
24 | plt.legend().get_frame().set_alpha(0.5)
25 |
--------------------------------------------------------------------------------
/doc/pyplots/pdf/cauchy_bw.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import matplotlib.pyplot as plt
3 |
4 | from probfit.pdf import cauchy, rtv_breitwigner
5 | from probfit.plotting import draw_pdf
6 |
7 | bound = (1, 7.0)
8 |
9 | arg = dict(m=5.28, gamma=0.5)
10 | draw_pdf(cauchy, arg=arg, bound=bound, label="cauchy" + str(arg), density=True)
11 |
12 | arg = dict(m=5.28, gamma=1.0)
13 | draw_pdf(cauchy, arg=arg, bound=bound, label="cauchy" + str(arg), density=True)
14 |
15 | arg = dict(m=5.28, gamma=1.0)
16 | draw_pdf(rtv_breitwigner, arg=arg, bound=bound, label="bw" + str(arg), density=True)
17 |
18 | arg = dict(m=5.28, gamma=2.0)
19 | draw_pdf(rtv_breitwigner, arg=arg, bound=bound, label="bw" + str(arg), density=True)
20 |
21 | plt.grid(True)
22 | plt.legend().get_frame().set_alpha(0.5)
23 |
--------------------------------------------------------------------------------
/doc/pyplots/pdf/cruijff.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import matplotlib.pyplot as plt
3 |
4 | from probfit.pdf import cruijff
5 | from probfit.plotting import draw_normed_pdf
6 |
7 | bound = (5.22, 5.30)
8 |
9 | arg = dict(m_0=5.28, sigma_L=0.005, sigma_R=0.005, alpha_R=0.0, alpha_L=0.1)
10 | draw_normed_pdf(cruijff, arg=arg, bound=bound, label=str(arg), density=True)
11 |
12 | arg = dict(m_0=5.28, sigma_L=0.005, sigma_R=0.005, alpha_R=0.0, alpha_L=0.5)
13 | draw_normed_pdf(cruijff, arg=arg, bound=bound, label=str(arg), density=True)
14 |
15 | arg = dict(m_0=5.28, sigma_L=0.002, sigma_R=0.005, alpha_R=0.0, alpha_L=0.01)
16 | draw_normed_pdf(cruijff, arg=arg, bound=bound, label=str(arg), density=True)
17 |
18 | plt.grid(True)
19 | plt.legend(loc="upper left", prop={"size": 8}).get_frame().set_alpha(0.5)
20 |
--------------------------------------------------------------------------------
/doc/pyplots/pdf/crystalball.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import matplotlib.pyplot as plt
3 |
4 | from probfit.pdf import crystalball
5 | from probfit.plotting import draw_normed_pdf
6 |
7 | bound = (5.22, 5.30)
8 |
9 | arg = dict(alpha=1.0, n=2.0, mean=5.28, sigma=0.01)
10 | draw_normed_pdf(crystalball, arg=arg, bound=bound, label=str(arg), density=True)
11 |
12 | arg = dict(alpha=0.5, n=10.0, mean=5.28, sigma=0.005)
13 | draw_normed_pdf(crystalball, arg=arg, bound=bound, label=str(arg), density=True)
14 |
15 | plt.grid(True)
16 | plt.legend().get_frame().set_alpha(0.5)
17 |
--------------------------------------------------------------------------------
/doc/pyplots/pdf/doublecrystalball.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import matplotlib.pyplot as plt
3 |
4 | from probfit.pdf import doublecrystalball
5 | from probfit.plotting import draw_normed_pdf
6 |
7 | bound = (-2, 12)
8 |
9 | arg = dict(alpha=1.0, alpha2=2.0, n=2.0, n2=4, mean=5, sigma=1)
10 | draw_normed_pdf(doublecrystalball, arg=arg, bound=bound, label=str(arg), density=True)
11 |
12 | arg = dict(alpha=2, alpha2=1, n=7.0, n2=10.0, mean=5, sigma=1)
13 | draw_normed_pdf(doublecrystalball, arg=arg, bound=bound, label=str(arg), density=True)
14 |
15 | plt.grid(True)
16 | plt.legend().get_frame().set_alpha(0.5)
17 |
--------------------------------------------------------------------------------
/doc/pyplots/pdf/doublegaussian.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import matplotlib.pyplot as plt
3 |
4 | from probfit.pdf import doublegaussian
5 | from probfit.plotting import draw_normed_pdf
6 |
7 | bound = (-10, 10)
8 |
9 | arg = dict(mean=1.0, sigma_L=1, sigma_R=2)
10 | draw_normed_pdf(doublegaussian, arg=arg, bound=bound, label=str(arg))
11 |
12 | arg = dict(mean=1.0, sigma_L=0.5, sigma_R=3)
13 | draw_normed_pdf(doublegaussian, arg=arg, bound=bound, label=str(arg))
14 |
15 | plt.grid(True)
16 | plt.legend().get_frame().set_alpha(0.5)
17 |
--------------------------------------------------------------------------------
/doc/pyplots/pdf/exponential.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import matplotlib.pyplot as plt
3 |
4 | from probfit.pdf import exponential
5 | from probfit.plotting import draw_pdf
6 |
7 | _arg = {"lambda": 0.5}
8 | draw_pdf(exponential, arg=_arg, bound=(0, 5), label=str(_arg), density=False, bins=100)
9 |
10 | _arg = {"lambda": 1.0}
11 | draw_pdf(exponential, arg=_arg, bound=(0, 5), label=str(_arg), density=False, bins=100)
12 |
13 | _arg = {"lambda": 1.5}
14 | draw_pdf(exponential, arg=_arg, bound=(0, 5), label=str(_arg), density=False, bins=100)
15 |
16 | plt.grid(True)
17 | plt.legend().get_frame().set_alpha(0.5)
18 |
--------------------------------------------------------------------------------
/doc/pyplots/pdf/gaussian.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import matplotlib.pyplot as plt
3 |
4 | from probfit.pdf import gaussian
5 | from probfit.plotting import draw_pdf
6 |
7 | bound = (-10, 10)
8 |
9 | arg = dict(mean=2, sigma=1)
10 | draw_pdf(gaussian, arg=arg, bound=bound, label=str(arg), density=True)
11 |
12 | arg = dict(mean=-3, sigma=2)
13 | draw_pdf(gaussian, arg=arg, bound=bound, label=str(arg), density=True)
14 |
15 | plt.grid(True)
16 | plt.legend().get_frame().set_alpha(0.5)
17 |
--------------------------------------------------------------------------------
/doc/pyplots/pdf/histogrampdf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import numpy as np
3 | from iminuit import Minuit
4 |
5 | from probfit import AddPdf, BinnedLH, Extended, gen_toy
6 | from probfit.pdf import HistogramPdf
7 |
8 | bound = (0, 10)
9 | np.random.seed(0)
10 | bkg = gen_toy(lambda x: x ** 2, 100000, bound=bound) # a parabola background
11 | sig = np.random.randn(50000) + 5 # a Gaussian signal
12 | data = np.concatenate([sig, bkg])
13 | # fill histograms with large statistics
14 | hsig, be = np.histogram(sig, bins=40, range=bound)
15 | hbkg, be = np.histogram(bkg, bins=be, range=bound)
16 | # randomize data
17 | data = np.random.permutation(data)
18 | fitdata = data[:1000]
19 |
20 | psig = HistogramPdf(hsig, be)
21 | pbkg = HistogramPdf(hbkg, be)
22 | epsig = Extended(psig, extname="N1")
23 | epbkg = Extended(pbkg, extname="N2")
24 | pdf = AddPdf(epbkg, epsig)
25 |
26 | blh = BinnedLH(pdf, fitdata, bins=40, bound=bound, extended=True)
27 | m = Minuit(blh, N1=330, N2=670, error_N1=20, error_N2=30)
28 | # m.migrad()
29 | blh.draw(m, parts=True)
30 |
--------------------------------------------------------------------------------
/doc/pyplots/pdf/johnsonSU.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from collections import OrderedDict
3 |
4 | import matplotlib.pyplot as plt
5 |
6 | from probfit.pdf import johnsonSU
7 | from probfit.plotting import draw_pdf
8 |
9 | bound = (-10, 10)
10 |
11 | arg = OrderedDict(mean=2, sigma=1, nu=-4, tau=0.5)
12 | draw_pdf(johnsonSU, arg=arg, bound=bound, label=str(arg), density=False, bins=200)
13 |
14 | arg = OrderedDict(mean=-3, sigma=2, nu=+4, tau=0.1)
15 | draw_pdf(johnsonSU, arg=arg, bound=bound, label=str(arg), density=False, bins=200)
16 |
17 | arg = OrderedDict(mean=0, sigma=3, nu=+2, tau=0.9)
18 | draw_pdf(johnsonSU, arg=arg, bound=bound, label=str(arg), density=False, bins=200)
19 |
20 | plt.grid(True)
21 | plt.legend().get_frame().set_alpha(0.5)
22 |
--------------------------------------------------------------------------------
/doc/pyplots/pdf/linear.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/doc/pyplots/pdf/linear.py
--------------------------------------------------------------------------------
/doc/pyplots/pdf/novosibirsk.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import matplotlib.pyplot as plt
3 |
4 | from probfit.pdf import novosibirsk
5 | from probfit.plotting import draw_normed_pdf
6 |
7 | bound = (5.22, 5.30)
8 |
9 | arg = dict(width=0.005, peak=5.28, tail=0.2)
10 | draw_normed_pdf(novosibirsk, arg=arg, bound=bound, label=str(arg), density=True)
11 |
12 | arg = dict(width=0.002, peak=5.28, tail=0.2)
13 | draw_normed_pdf(novosibirsk, arg=arg, bound=bound, label=str(arg), density=True)
14 |
15 | arg = dict(width=0.005, peak=5.28, tail=0.1)
16 | draw_normed_pdf(novosibirsk, arg=arg, bound=bound, label=str(arg), density=True)
17 |
18 | plt.grid(True)
19 | plt.legend(loc="upper left").get_frame().set_alpha(0.5)
20 |
--------------------------------------------------------------------------------
/doc/pyplots/pdf/poly2.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/doc/pyplots/pdf/poly2.py
--------------------------------------------------------------------------------
/doc/pyplots/pdf/poly3.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/doc/pyplots/pdf/poly3.py
--------------------------------------------------------------------------------
/doc/pyplots/pdf/polynomial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import matplotlib.pyplot as plt
3 |
4 | from probfit.pdf import Polynomial
5 | from probfit.plotting import draw_pdf
6 |
7 | bound = (-10, 10)
8 |
9 | p = Polynomial(3)
10 | arg = dict(c_0=0.0, c_1=1, c_2=2, c_3=3)
11 | draw_pdf(p, arg=arg, bound=bound, label=str(arg), density=False)
12 |
13 | p = Polynomial(2)
14 | arg = dict(c_0=0.0, c_1=1, c_2=2)
15 | draw_pdf(p, arg=arg, bound=bound, label=str(arg), density=False)
16 |
17 | plt.grid(True)
18 | plt.legend().get_frame().set_alpha(0.5)
19 |
--------------------------------------------------------------------------------
/doc/pyplots/pdf/ugaussian.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from collections import OrderedDict
3 |
4 | import matplotlib.pyplot as plt
5 |
6 | from probfit.pdf import ugaussian
7 | from probfit.plotting import draw_pdf
8 |
9 | bound = (-10, 10)
10 |
11 | arg = OrderedDict(mean=2, sigma=1)
12 | draw_pdf(ugaussian, arg=arg, bound=bound, label=str(arg), density=False)
13 |
14 | arg = OrderedDict(mean=-3, sigma=2)
15 | draw_pdf(ugaussian, arg=arg, bound=bound, label=str(arg), density=False)
16 |
17 | plt.grid(True)
18 | plt.legend().get_frame().set_alpha(0.5)
19 |
--------------------------------------------------------------------------------
/doc/recipe.rst:
--------------------------------------------------------------------------------
1 | .. _cookbook:
2 |
3 | Cookbook
4 | ========
5 |
6 | Tell probfit to use my analytical integral
7 | ------------------------------------------
8 |
9 | probfit checks for a method ``integrate(bound, nint, *arg)``.
10 | If such method is available it calls that method to compute definite integral.
11 | If not it falls back to simpson3/8 integration (cubic approximation).
12 |
13 | ::
14 |
15 | from probfit import integrate1d
16 | def line(x, m, c):
17 | return m*x+c
18 |
19 | # compute integral of line from x=(0,1) using 10 intevals with m=1. and c=2.
20 | # all probfit internal use this
21 |
22 | # no integrate method available probfit use simpson3/8
23 | print integrate1d(line, (0,1), 10, (1.,2.) )
24 |
25 | # Let us illustrate the point by forcing it to have integral that's off by
26 | # factor of two
27 | def wrong_line_integral(bound, nint, m, c):
28 | a, b = bound
29 | return 2*(m*(b**2/2.-a**2/2.)+c*(b-a)) # I know this is wrong
30 |
31 | line.integrate = wrong_line_integral
32 | # line.integrate = lambda bound, nint, m, c: blah blah # this works too
33 |
34 | # yes off by factor of two
35 | print integrate1d(line, (0,1), 10, (1.,2.))
36 |
--------------------------------------------------------------------------------
/doc/rtd-pip-requirements:
--------------------------------------------------------------------------------
1 | IPython
2 | numpy
3 | Cython
4 | matplotlib
5 | iminuit
6 |
--------------------------------------------------------------------------------
/probfit/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | probfit - Cost function builder. For fitting distributions.
4 |
5 | * Code: https://github.com/scikit-hep/probfit
6 | * Docs: http://probfit.readthedocs.io
7 | """
8 |
9 |
10 | from ._libstat import integrate1d
11 | from .costfunc import BinnedChi2, BinnedLH, Chi2Regression, SimultaneousFit, UnbinnedLH
12 | from .decorator import *
13 | from .functor import AddPdf, AddPdfNorm, BlindFunc, Convolve, Extended, Normalized
14 | from .funcutil import *
15 | from .oneshot import *
16 | from .pdf import (
17 | HistogramPdf,
18 | Polynomial,
19 | argus,
20 | cauchy,
21 | cruijff,
22 | crystalball,
23 | doublecrystalball,
24 | doublegaussian,
25 | exponential,
26 | gaussian,
27 | johnsonSU,
28 | linear,
29 | novosibirsk,
30 | poly2,
31 | poly3,
32 | rtv_breitwigner,
33 | ugaussian,
34 | )
35 | from .plotting import *
36 | from .statutil import *
37 | from .toy import gen_toy, gen_toyn
38 | from .util import *
39 | from .version import __version__
40 |
41 | __all__ = [
42 | "AddPdfNorm",
43 | "AddPdf",
44 | "BinnedChi2",
45 | "BinnedLH",
46 | "BlindFunc",
47 | "Chi2Regression",
48 | "Convolve",
49 | "Extended",
50 | "Normalized",
51 | "Polynomial",
52 | "HistogramPdf",
53 | "UnbinnedLH",
54 | "argus",
55 | "cruijff",
56 | "cauchy",
57 | "rtv_breitwigner",
58 | "crystalball",
59 | "doublecrystalball",
60 | "describe",
61 | "doublegaussian",
62 | "johnsonSU",
63 | "draw_compare",
64 | "draw_compare_hist",
65 | "draw_pdf",
66 | "draw_pdf_with_edges",
67 | "extended",
68 | "fwhm_f",
69 | "gaussian",
70 | "gen_toy",
71 | "gen_toyn",
72 | "integrate1d",
73 | "linear",
74 | "normalized",
75 | "novosibirsk",
76 | "poly2",
77 | "poly3",
78 | "SimultaneousFit",
79 | "try_binlh",
80 | "try_chi2",
81 | "try_uml",
82 | "ugaussian",
83 | "exponential",
84 | "__version__",
85 | ]
86 |
--------------------------------------------------------------------------------
/probfit/_libstat.pxd:
--------------------------------------------------------------------------------
1 | cimport numpy as np
2 |
3 |
4 | cpdef double csum(np.ndarray x)
5 |
6 | cpdef np.ndarray[np.double_t] _vector_apply(f,np.ndarray[np.double_t] x,tuple arg)
7 |
8 | cpdef double xlogyx(double x,double y)
9 |
10 | cpdef double wlogyx(double w,double y, double x)
11 |
12 | cpdef bint has_ana_integral(f)
13 |
14 | cpdef double integrate1d(f, tuple bound, int nint, tuple arg=*) except *
15 |
16 | cpdef double integrate1d_with_edges(f,np.ndarray edges, double bw, tuple arg) except *
17 |
18 | #these are performance critical code
19 | cpdef double compute_bin_lh_f(f,
20 | np.ndarray[np.double_t] edges,
21 | np.ndarray[np.double_t] h, #histogram,
22 | np.ndarray[np.double_t] w2,
23 | double N, #sum of h
24 | tuple arg, double badvalue,
25 | bint extend, bint use_sumw2,
26 | int nint_subdiv) except *
27 |
28 | cpdef double compute_nll(f,np.ndarray data,w,arg,double badvalue) except *
29 |
30 |
31 | cpdef double compute_chi2(np.ndarray[np.double_t] actual,
32 | np.ndarray[np.double_t] expected,
33 | np.ndarray[np.double_t] err) except *
34 |
35 | cpdef double compute_chi2_f(f,
36 | np.ndarray[np.double_t] x,
37 | np.ndarray[np.double_t] y,
38 | np.ndarray[np.double_t] error,
39 | np.ndarray[np.double_t] weights,
40 | tuple arg) except *
41 |
42 | cpdef double compute_bin_chi2_f(f,
43 | np.ndarray[np.double_t] edges,
44 | np.ndarray[np.double_t] y,
45 | np.ndarray[np.double_t] error,
46 | np.ndarray[np.double_t] weights,
47 | tuple arg,
48 | int nint_subdiv) except *
49 |
50 | cpdef compute_cdf(np.ndarray[np.double_t] pdf, np.ndarray[np.double_t] x)
51 |
52 | #invert cdf useful for making toys
53 | cpdef invert_cdf(np.ndarray[np.double_t] r,
54 | np.ndarray[np.double_t] cdf,
55 | np.ndarray[np.double_t] x)
56 |
--------------------------------------------------------------------------------
/probfit/_libstat.pyx:
--------------------------------------------------------------------------------
1 | # cython: embedsignature=True, language_level=2
2 |
3 | import numpy as np
4 |
5 | cimport numpy as np
6 | from libc.math cimport exp, fabs, lgamma, log, pow, sqrt, tgamma
7 |
8 | include "log1p_patch.pxi"
9 | from warnings import warn
10 |
11 | from .probfit_warnings import LogWarning
12 |
13 | np.import_array()
14 |
15 | cpdef np.ndarray[np.double_t] _vector_apply(f,np.ndarray[np.double_t] x,tuple arg):
16 | cdef int i
17 | cdef int n = len(x)
18 | cdef np.ndarray[np.double_t] ret = np.empty(n,dtype=np.double)#fast_empty(n)
19 | cdef double tmp
20 | for i in range(n):
21 | tmp = f(x[i],*arg)
22 | ret[i]=tmp
23 | return ret
24 |
25 |
26 | cpdef double csum(np.ndarray x):
27 | cdef int i
28 | cdef np.ndarray[np.double_t] xd = x
29 | cdef int n = len(x)
30 | cdef double s=0.
31 | for i in range(n):
32 | s+=xd[i]
33 | return s
34 |
35 | cpdef inline bint has_ana_integral(f):
36 | integrate = getattr3(f, 'integrate', None)
37 | return integrate is not None
38 |
39 | #currently bin width must be uniform to save tons of multiplications
40 | #based on simpson 3/8
41 | cpdef double integrate1d_with_edges(f, np.ndarray edges, double bw, tuple arg) except *:
42 | if has_ana_integral(f):
43 | # this is not exactly correct for non-uniform bin
44 | return f.integrate((edges[0],edges[-1]), len(edges-1), *arg)
45 | return simpson38(f, edges, bw, arg)
46 |
47 | cdef double simpson38(f, np.ndarray edges, double bw, tuple arg):
48 | cdef np.ndarray[np.double_t] yedges = _vector_apply(f, edges, arg)
49 | cdef np.ndarray[np.double_t] left38 = _vector_apply(f, (2.*edges[1:]+edges[:-1])/3., arg)
50 | cdef np.ndarray[np.double_t] right38 = _vector_apply(f, (edges[1:]+2.*edges[:-1])/3., arg)
51 | return bw/8.*( csum(yedges)*2.+csum(left38+right38)*3. - (yedges[0]+yedges[-1]) ) #simpson3/8
52 |
53 |
54 | #TODO: do something smarter like dynamic edge based on derivative or so
55 | cpdef double integrate1d(f, tuple bound, int nint, tuple arg=None) except*:
56 | """
57 | compute 1d integral
58 | """
59 | if arg is None: arg = tuple()
60 | if has_ana_integral(f):
61 | return f.integrate(bound, nint, *arg)
62 | cdef double ret = 0
63 | cdef np.ndarray[np.double_t] edges = np.linspace(bound[0], bound[1], nint+1)
64 | #cdef np.ndarray[np.double_t] bw = edges[1:]-edges[:-1]
65 | cdef double bw = edges[1]-edges[0]
66 | return simpson38(f, edges, bw, arg)
67 |
68 |
69 | #compute x*log(y/x) to a good precision especially when y~x
70 | cpdef double xlogyx(double x,double y):
71 | cdef double ret
72 | if x<1e-100:
73 | warn(LogWarning('x is really small return 0'))
74 | return 0.
75 | if x
9 | #endif
10 |
--------------------------------------------------------------------------------
/probfit/log1p_patch.pxi:
--------------------------------------------------------------------------------
1 | cdef extern from "log1p.h":
2 | double log1p(double x)
3 |
--------------------------------------------------------------------------------
/probfit/nputil.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import numpy as np
3 |
4 | from ._libstat import _vector_apply
5 |
6 |
7 | def mid(x):
8 | return (x[:-1] + x[1:]) / 2
9 |
10 |
11 | def minmax(x):
12 | return min(x), max(x)
13 |
14 |
15 | # for converting numpy array to double
16 | def float2double(a):
17 | if a is None or a.dtype == np.float64:
18 | return a
19 | else:
20 | return a.astype(np.float64)
21 |
22 |
23 | def vector_apply(f, x, *arg):
24 | """
25 | Apply **f** to array **x** with given arguments fast.
26 |
27 | This is a fast version of::
28 |
29 | np.array([f(xi,*arg) for xi in x ])
30 |
31 | useful when you try to plot something.
32 | """
33 | return _vector_apply(f, x, arg)
34 |
--------------------------------------------------------------------------------
/probfit/oneshot.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import collections
3 | import itertools as itt
4 |
5 | import numpy as np
6 | from iminuit import Minuit
7 |
8 | from ._libstat import _vector_apply
9 | from .costfunc import BinnedChi2, BinnedLH, UnbinnedLH
10 | from .nputil import minmax
11 |
12 |
13 | def fit_uml(f, data, quiet=False, print_level=0, *arg, **kwd):
14 | """
15 | perform unbinned likelihood fit
16 | :param f: pdf
17 | :param data: data
18 | :param quiet: if not quite draw latest fit on fail fit
19 | :param printlevel: minuit printlevel
20 | :return:
21 | """
22 | uml = UnbinnedLH(f, data)
23 | minuit = Minuit(uml, print_level=print_level, **kwd)
24 | minuit.set_strategy(2)
25 | minuit.migrad()
26 | if not minuit.migrad_ok() or not minuit.matrix_accurate():
27 | if not quiet:
28 | from matplotlib import pyplot as plt
29 |
30 | plt.figure()
31 | uml.show()
32 | print(minuit.values)
33 | return (uml, minuit)
34 |
35 |
36 | def fit_binx2(f, data, bins=30, bound=None, print_level=0, quiet=False, *arg, **kwd):
37 | """
38 | perform chi^2 fit
39 | :param f:
40 | :param data:
41 | :param bins:
42 | :param range:
43 | :param printlevel:
44 | :param quiet:
45 | :param arg:
46 | :param kwd:
47 | :return:
48 | """
49 | uml = BinnedChi2(f, data, bins=bins, bound=bound)
50 | minuit = Minuit(uml, print_level=print_level, **kwd)
51 | minuit.set_strategy(2)
52 | minuit.migrad()
53 | if not minuit.migrad_ok() or not minuit.matrix_accurate():
54 | if not quiet:
55 | from matplotlib import pyplot as plt
56 |
57 | plt.figure()
58 | uml.show()
59 | print(minuit.values)
60 |
61 | return (uml, minuit)
62 |
63 |
64 | def fit_binlh(
65 | f,
66 | data,
67 | bins=30,
68 | bound=None,
69 | quiet=False,
70 | weights=None,
71 | use_w2=False,
72 | print_level=0,
73 | pedantic=True,
74 | extended=False,
75 | *arg,
76 | **kwd
77 | ):
78 | """
79 | perform bin likelihood fit
80 | :param f:
81 | :param data:
82 | :param bins:
83 | :param range:
84 | :param quiet:
85 | :param weights:
86 | :param use_w2:
87 | :param printlevel:
88 | :param pedantic:
89 | :param extended:
90 | :param arg:
91 | :param kwd:
92 | :return:
93 | """
94 | uml = BinnedLH(
95 | f,
96 | data,
97 | bins=bins,
98 | bound=bound,
99 | weights=weights,
100 | use_w2=use_w2,
101 | extended=extended,
102 | )
103 | minuit = Minuit(uml, print_level=print_level, pedantic=pedantic, **kwd)
104 | minuit.set_strategy(2)
105 | minuit.migrad()
106 | if not minuit.migrad_ok() or not minuit.matrix_accurate():
107 | if not quiet:
108 | from matplotlib import pyplot as plt
109 |
110 | plt.figure()
111 | uml.show()
112 | print(minuit.values)
113 | return (uml, minuit)
114 |
115 |
116 | def tuplize(x):
117 | """
118 | :param x:
119 | :return:
120 | """
121 | if isinstance(x, collections.Iterable):
122 | return x
123 | else:
124 | return tuple([x])
125 |
126 |
127 | def pprint_arg(vnames, value):
128 | """
129 | pretty print argument
130 | :param vnames:
131 | :param value:
132 | :return:
133 | """
134 | ret = ""
135 | for name, v in zip(vnames, value):
136 | ret += "{}={};".format(name, str(v))
137 | return ret
138 |
139 |
140 | def try_uml(f, data, bins=40, fbins=1000, *arg, **kwd):
141 | from matplotlib import pyplot as plt
142 |
143 | fom = UnbinnedLH(f, data)
144 | narg = f.func_code.co_argcount
145 | vnames = f.func_code.co_varnames[1:narg]
146 | my_arg = [tuplize(kwd[name]) for name in vnames]
147 | h, e, _ = plt.hist(data, bins=bins, normed=True, histtype="step")
148 | vx = np.linspace(e[0], e[-1], fbins)
149 | first = True
150 | minfom = 0
151 | minarg = None
152 | for thisarg in itt.product(*my_arg):
153 | vy = _vector_apply(f, vx, thisarg)
154 | plt.plot(vx, vy, "-", label=pprint_arg(vnames, thisarg))
155 | thisfom = fom(*thisarg)
156 | if first or thisfom < minfom:
157 | minfom = thisfom
158 | minarg = thisarg
159 | first = False
160 | leg = plt.legend(fancybox=True)
161 | leg.get_frame().set_alpha(0.5)
162 | ret = {k: v for k, v in zip(vnames, minarg)}
163 | return ret
164 |
165 |
166 | def try_binlh(
167 | f,
168 | data,
169 | weights=None,
170 | bins=40,
171 | fbins=1000,
172 | show="both",
173 | extended=False,
174 | bound=None,
175 | *arg,
176 | **kwd
177 | ):
178 | from matplotlib import pyplot as plt
179 |
180 | if bound is None:
181 | bound = minmax(data)
182 | fom = BinnedLH(f, data, extended=extended, bound=bound)
183 | narg = f.func_code.co_argcount
184 | vnames = f.func_code.co_varnames[1:narg]
185 | my_arg = [tuplize(kwd[name]) for name in vnames]
186 | h, e = None, None
187 | if show == "both":
188 | h, e, _ = plt.hist(
189 | data,
190 | bins=bins,
191 | range=bound,
192 | histtype="step",
193 | weights=weights,
194 | normed=not extended,
195 | )
196 | else:
197 | h, e = np.histogram(
198 | data, bins=bins, range=bound, weights=weights, normed=not extended
199 | )
200 | bw = e[1] - e[0]
201 |
202 | vx = np.linspace(e[0], e[-1], fbins)
203 | first = True
204 | minfom = 0
205 | minarg = None
206 | for thisarg in itt.product(*my_arg):
207 | vy = _vector_apply(f, vx, thisarg)
208 | if extended:
209 | vy *= bw
210 | plt.plot(vx, vy, "-", label=pprint_arg(vnames, thisarg))
211 | thisfom = fom(*thisarg)
212 | if first or thisfom < minfom:
213 | minfom = thisfom
214 | minarg = thisarg
215 | first = False
216 | leg = plt.legend(fancybox=True)
217 | leg.get_frame().set_alpha(0.5)
218 | ret = {k: v for k, v in zip(vnames, minarg)}
219 | return ret
220 |
221 |
222 | def try_chi2(f, data, weights=None, bins=40, fbins=1000, show="both", *arg, **kwd):
223 | from matplotlib import pyplot as plt
224 |
225 | fom = BinnedChi2(f, data)
226 | narg = f.func_code.co_argcount
227 | vnames = f.func_code.co_varnames[1:narg]
228 | my_arg = [tuplize(kwd[name]) for name in vnames]
229 | h, e = None, None
230 | if show == "both":
231 | h, e, _ = plt.hist(data, bins=bins, histtype="step", weights=weights)
232 | else:
233 | h, e = np.histogram(data, bins=bins, weights=weights)
234 | bw = e[1] - e[0]
235 | vx = np.linspace(e[0], e[-1], fbins)
236 | first = True
237 | minfom = 0
238 | minarg = None
239 | for thisarg in itt.product(*my_arg):
240 | vy = _vector_apply(f, vx, thisarg) * bw
241 | plt.plot(vx, vy, "-", label=pprint_arg(vnames, thisarg))
242 | thisfom = fom(*thisarg)
243 | if first or thisfom < minfom:
244 | minfom = thisfom
245 | minarg = thisarg
246 | first = False
247 | leg = plt.legend(fancybox=True)
248 | leg.get_frame().set_alpha(0.5)
249 | ret = {k: v for k, v in zip(vnames, minarg)}
250 | return ret
251 |
252 |
253 | # def randfr(r):
254 | # """
255 | # generate a uniform random number with in range r
256 | # :param r: tuple range
257 | # :return: float
258 | # """
259 | # b = r[1]
260 | # a = r[0]
261 | # return np.random.ranf() * (b - a) + a
262 |
263 | # def guess_initial(alg, f, data, ntry=100, guessrange=(-100, 100), draw=False, *arg, **kwd):
264 | # """
265 | # This is very bad at the moment don't use it
266 | # """
267 | # fom = alg(f, data, *arg, **kwd)
268 | # first = True
269 | # minfom = 0
270 | # minparam = ()
271 | # narg = fom.func_code.co_argcount
272 | # vnames = fom.func_code.co_varnames[:narg]
273 | # ranges = {}
274 | # for vname in vnames:
275 | # if 'limit_' + vname in kwd:
276 | # ranges[vname] = kwd['limit_' + vname]
277 | # else:
278 | # ranges[vname] = guessrange
279 |
280 | # for i in range(ntry):
281 | # arg = []
282 | # for vname in vnames:
283 | # arg.append(randfr(ranges[vname]))
284 | # try:
285 | # thisfom = fom(*arg)
286 | # if first or thisfom < minfom:
287 | # first = False
288 | # minfom = thisfom
289 | # minparam = arg
290 | # except ZeroDivisionError:
291 | # pass
292 |
293 | # print(minparam, minfom)
294 | # ret = {}
295 | # for vname, bestguess in zip(vnames, minparam):
296 | # ret[vname] = bestguess
297 | # if draw:
298 | # fom(*minparam)
299 | # fom.draw()
300 | # return ret
301 |
--------------------------------------------------------------------------------
/probfit/pdf.pxd:
--------------------------------------------------------------------------------
1 | cpdef double doublegaussian(double x, double mean, double sigma_L, double sigma_R)
2 |
3 | cpdef double ugaussian(double x, double mean, double sigma)
4 |
5 | cpdef double gaussian(double x, double mean, double sigma)
6 |
7 | cpdef double crystalball(double x, double alpha, double n, double mean, double sigma)
8 |
9 | cpdef double argus(double x, double c, double chi, double p)
10 |
11 | cpdef double cruijff(double x, double m0, double sigma_L, double sigma_R, double alpha_L, double alpha_R)
12 |
13 | #cpdef double linear(double x, double m, double c)
14 |
15 | cpdef double poly2(double x, double a, double b, double c)
16 |
17 | cpdef double poly3(double x, double a, double b, double c, double d)
18 |
19 | cpdef double novosibirsk(double x, double width, double peak, double tail)
20 |
21 | cpdef double rtv_breitwigner(double x, double m, double gamma)
22 |
23 | cpdef double cauchy(double x, double m, double gamma)
24 |
25 | cpdef double doublecrystalball(double x, double alpha, double alpha2, double n, double n2, double mean, double sigma)
26 |
--------------------------------------------------------------------------------
/probfit/pdf.pyx:
--------------------------------------------------------------------------------
1 | # cython: embedsignature=True, language_level=2
2 |
3 | cimport cython
4 | from libc.math cimport (
5 | abs,
6 | asinh,
7 | atan2,
8 | cosh,
9 | erf,
10 | exp,
11 | fabs,
12 | log,
13 | pow,
14 | sinh,
15 | sqrt,
16 | tgamma,
17 | )
18 |
19 |
20 | cdef double pi = 3.14159265358979323846264338327
21 | import numpy as np
22 |
23 | cimport numpy as np
24 |
25 | from .funcutil import MinimalFuncCode
26 |
27 | np.import_array()
28 |
29 | cdef double badvalue = 1e-300
30 | cdef double smallestdiv = 1e-10
31 |
32 | cdef class Polynomial:
33 | """
34 | Polynomial.
35 |
36 | .. math::
37 | f(x; c_i) = \sum_{i < \\text{order}} c_i x^i
38 |
39 | User can supply order as integer in which case it uses (c_0....c_n+1)
40 | default or the list of coefficient name which the first one will be the
41 | lowest order and the last one will be the highest order. (order=1 is
42 | a linear function)
43 |
44 | """
45 | cdef int order
46 | cdef public object func_code
47 | cdef public object func_defaults
48 | def __init__(self,order,xname='x'):
49 | varnames = None
50 | argcount = 0
51 | if isinstance(order, int):
52 | if order<0 : raise ValueError('order must be >=0')
53 | self.order = order
54 | varnames = ['c_%d'%i for i in range(order+1)]
55 | else: #use supply list of coeffnames #to do check if it's list of string
56 | if len(order)<=0: raise ValueError('need at least one coefficient')
57 | self.order = len(order)-1 #yep -1 think about it
58 | varnames = order
59 | varnames.insert(0,xname) #insert x in front
60 | self.func_code = MinimalFuncCode(varnames)
61 | self.func_defaults = None
62 |
63 | def integrate(self, tuple bound, int nint_subdiv, *arg):
64 | cdef double a, b
65 | a, b = bound
66 | cdef double ret = 0.
67 | for i in range(self.order+1):
68 | ai1 = pow(a,i+1)
69 | bi1 = pow(b,i+1)
70 | ret += 1./(i+1) * arg[i] * (bi1-ai1)
71 | return ret
72 |
73 | def __call__(self,*arg):
74 | cdef double x = arg[0]
75 | cdef double t
76 | cdef double ret=0.
77 | cdef int iarg
78 | cdef int numarg = self.order+1 #number of coefficient
79 | cdef int i
80 | for i in range(numarg):
81 | iarg = i+1
82 | t = arg[iarg]
83 | if i > 0:
84 | ret+=pow(x,i)*t
85 | else:#avoid 0^0
86 | ret += t
87 | return ret
88 |
89 |
90 | cdef class HistogramPdf:
91 | cdef np.ndarray hy
92 | cdef np.ndarray binedges
93 | cdef public object func_code
94 | cdef public object func_defaults
95 | def __init__(self, hy, binedges, xname='x'):
96 | """
97 | A histogram PDF. User supplies a template histogram with bin contents and bin
98 | edges. The histogram does not have to be normalized. The resulting PDF is normalized.
99 | """
100 | # Normalize, so the integral is unity
101 | yint= hy*(binedges[1:]-binedges[:-1])
102 | self.hy= hy.astype(float)/float(yint.sum())
103 | self.binedges= binedges
104 | if len(binedges)!= len(hy)+1:
105 | raise ValueError('binedges must be exactly one entry more than hy')
106 | # Only one variable. The PDF shape is fixed
107 | varnames= [xname]
108 | self.func_code = MinimalFuncCode(varnames)
109 | self.func_defaults = None
110 |
111 | def integrate(self, tuple bound, int nint_subdiv=0, arg=None):
112 | # nint_subdiv is irrelevant, ignored.
113 | # bound usually is smaller than the histogram's bound.
114 | # Find where they are:
115 | edges= np.copy(self.binedges)
116 | [ib0,ib1]= np.digitize([bound[0],bound[1]], edges)
117 | ib0= max(ib0,0)
118 | ib1= min(ib1, len(edges)-1)
119 | edges[ib0-1]= max(edges[ib0-1],bound[0])
120 | edges[ib1]= min(edges[ib1],bound[1])
121 | ilo= max(0,ib0-1)
122 | ihi= ib1+1 if edges[ib1-1]!=edges[ib1] else ib1
123 | return (self.hy[ilo:ihi-1]*np.diff(edges[ilo:ihi])).sum()
124 |
125 | def __call__(self, *arg):
126 | cdef double x = arg[0]
127 | [i]= np.digitize([x], self.binedges)
128 | if i >0 and i<=len(self.hy):
129 | return self.hy[i-1]
130 | else:
131 | return 0.0
132 |
133 |
134 | cdef class _JohnsonSU:
135 | """
136 | Normalized JohnsonSU [1]_.
137 |
138 | .. math::
139 | f(x; \\mu, \\sigma, \\nu, \\tau) = \\frac{1}{\\lambda \\sqrt{2\\pi}}
140 | \\frac{1}{\\sqrt{1 + \\left( \\frac{x - \\xi}{\\lambda} \\right)}}
141 | e^{-\\frac{1}{2} \\left( -\\nu + \\frac{1}{\\tau}
142 | \\sinh^{-1} \\left( \\frac{x - \\xi}{\\lambda} \\right)\\right)^{2}}
143 |
144 | where
145 |
146 | .. math::
147 | \\lambda = \\sigma \\times \\left(\\frac{1}{2}( \\exp(\\tau^{2}) - 1)
148 | \\left(\\exp(\\tau^{2}) \\cosh\\left(-\\nu \\tau \\right) + 1\\right)
149 | \\right)^{-\\frac{1}{2}} \\\\
150 |
151 | and
152 |
153 | .. math::
154 | \\xi = \\mu + \\lambda \\exp\\left(\\frac{\\tau^{2}}{2}\\right)\\sinh
155 | \\left( \\nu \\tau \\right)
156 |
157 | References
158 | ----------
159 |
160 | .. [1] https://en.wikipedia.org/wiki/Johnson%27s_SU-distribution
161 |
162 | """
163 |
164 | cdef public object func_code
165 | cdef public object func_defaults
166 |
167 | def __init__(self, xname='x'):
168 |
169 | varnames = [xname, "mean", "sigma", "nu", "tau"]
170 | self.func_code = MinimalFuncCode(varnames)
171 | self.func_defaults = None
172 |
173 | def integrate(self, tuple bound, int nint_subdiv=0, *arg):
174 |
175 | cdef double a, b
176 | a, b = bound
177 |
178 | cdef double mean = arg[0]
179 | cdef double sigma = arg[1]
180 | cdef double nu = arg[2]
181 | cdef double tau = arg[3]
182 |
183 | cdef double w = exp(tau * tau)
184 | cdef double omega = - nu * tau
185 | cdef double c = 0.5 * (w-1) * (w * cosh(2 * omega) + 1)
186 | c = pow(c, -0.5)
187 |
188 | cdef double _lambda = sigma * c
189 | cdef double xi = mean + _lambda * sqrt(w) * sinh(omega)
190 | cdef double zmax = (b - xi) / _lambda
191 | cdef double zmin = (a - xi) / _lambda
192 | cdef double rmax = -nu + asinh(zmax) / tau
193 | cdef double rmin = -nu + asinh(zmin) / tau
194 |
195 | cdef double ret = 0.
196 |
197 | ret = 0.5 * (erf(rmax / (sqrt(2))) - erf(rmin / (sqrt(2))))
198 |
199 | return ret
200 |
201 | def __call__(self, *arg):
202 |
203 | cdef double x = arg[0]
204 | cdef double mean = arg[1]
205 | cdef double sigma = arg[2]
206 | cdef double nu = arg[3]
207 | cdef double tau = arg[4]
208 |
209 | cdef double w = exp(tau * tau)
210 | cdef double omega = - nu * tau
211 |
212 | cdef double c = 0.5 * (w-1) * (w * cosh(2 * omega) + 1)
213 | c = pow(c, -0.5)
214 |
215 | cdef double _lambda = sigma * c
216 | cdef double xi = mean + _lambda * sqrt(w) * sinh(omega)
217 | cdef double z = (x - xi) / _lambda
218 | cdef double r = -nu + asinh(z) / tau
219 |
220 | cdef double ret = 1. / (tau * _lambda * sqrt(2 * pi))
221 | ret *= 1. / sqrt(z * z + 1)
222 | ret *= exp(-0.5 * r * r)
223 |
224 | return ret
225 |
226 | johnsonSU = _JohnsonSU()
227 |
228 | cpdef double doublegaussian(double x, double mean, double sigma_L, double sigma_R):
229 | """
230 | Unnormalized double gaussian
231 |
232 | .. math::
233 | f(x;mean,\sigma_L,\sigma_R) =
234 | \\begin{cases}
235 | \exp \left[ -\\frac{1}{2} \left(\\frac{x-mean}{\sigma_L}\\right)^2 \\right], & \mbox{if } x < mean \\\\
236 | \exp \left[ -\\frac{1}{2} \left(\\frac{x-mean}{\sigma_R}\\right)^2 \\right], & \mbox{if } x >= mean
237 | \end{cases}
238 |
239 | """
240 | cdef double ret = 0.
241 | cdef double sigma = 0.
242 | sigma = sigma_L if x < mean else sigma_R
243 | if sigma < smallestdiv:
244 | ret = badvalue
245 | else:
246 |
247 | d = (x-mean)/sigma
248 | d2 = d*d
249 | ret = exp(-0.5*d2)
250 | return ret
251 |
252 | cpdef double ugaussian(double x, double mean, double sigma):
253 | """
254 | Unnormalized gaussian
255 |
256 | .. math::
257 | f(x; mean, \sigma) = \exp \left[ -\\frac{1}{2} \\left( \\frac{x-mean}{\sigma} \\right)^2 \\right]
258 |
259 | """
260 | cdef double ret = 0
261 | if sigma < smallestdiv:
262 | ret = badvalue
263 | else:
264 | d = (x-mean)/sigma
265 | d2 = d*d
266 | ret = exp(-0.5*d2)
267 | return ret
268 |
269 |
270 | cpdef double gaussian(double x, double mean, double sigma):
271 | """
272 | Normalized gaussian.
273 |
274 | .. math::
275 | f(x; mean, \sigma) = \\frac{1}{\sqrt{2\pi}\sigma}
276 | \exp \left[ -\\frac{1}{2} \left(\\frac{x-mean}{\sigma}\\right)^2 \\right]
277 |
278 | """
279 | cdef double badvalue = 1e-300
280 | cdef double ret = 0
281 | if sigma < smallestdiv:
282 | ret = badvalue
283 | else:
284 | d = (x-mean)/sigma
285 | d2 = d*d
286 | ret = 1/(sqrt(2*pi)*sigma)*exp(-0.5*d2)
287 | return ret
288 |
289 |
290 | cpdef double crystalball(double x, double alpha, double n, double mean, double sigma):
291 | """
292 | Unnormalized crystal ball function. If alpha > 0, the non-Gaussian tail
293 | is on the left. Otherwise, the tail is on the right.
294 |
295 | .. math::
296 | f(x;\\alpha,n,mean,\sigma) =
297 | \\begin{cases}
298 | \exp\left( -\\frac{1}{2} \delta^2 \\right) & \mbox{if } \delta>-\\alpha \\\\
299 | \left( \\frac{n}{|\\alpha|} \\right)^n \left( \\frac{n}{|\\alpha|} - |\\alpha| - \delta \\right)^{-n}
300 | \exp\left( -\\frac{1}{2}\\alpha^2\\right)
301 | & \mbox{if } \delta \leq \\alpha
302 | \end{cases}
303 |
304 | where
305 |
306 | - :math:`\delta = \\frac{x-mean}{\sigma}`
307 |
308 | .. note::
309 | http://en.wikipedia.org/wiki/Crystal_Ball_function
310 |
311 | """
312 | cdef double d = 0.
313 | cdef double ret = 0
314 | cdef double A = 0
315 | cdef double B = 0
316 | if sigma < smallestdiv:
317 | ret = badvalue
318 | elif fabs(alpha) < smallestdiv:
319 | ret = badvalue
320 | elif n<1.:
321 | ret = badvalue
322 | else:
323 | d = (x-mean)/sigma
324 | if (alpha > 0 and d > -alpha) or (alpha < 0 and d < -alpha) :
325 | # same as a Gaussian in this region
326 | ret = exp(-0.5*d**2)
327 | else:
328 | # exponential tail
329 | al = fabs(alpha)
330 | asign = np.sign(alpha)
331 | A=pow(n/al,n)*exp(-al**2/2.)
332 | B=n/al-al
333 | ret = A*pow(B-d*asign, -n)
334 | return ret
335 |
336 | cpdef double doublecrystalball(double x, double alpha, double alpha2, double n, double n2, double mean, double sigma):
337 | """
338 | Unnormalized double crystal ball function
339 | A gaussian core with two power tails
340 |
341 |
342 |
343 | """
344 | cdef double d = 0.
345 | cdef double ret = 0
346 | cdef double A = 0
347 | cdef double B = 0
348 | cdef double A2 = 0
349 | cdef double B2 = 0
350 | if sigma < smallestdiv:
351 | ret = badvalue
352 | elif fabs(alpha) < smallestdiv:
353 | ret = badvalue
354 | elif n<1.:
355 | ret = badvalue
356 | else:
357 | d = (x-mean)/sigma
358 | if d<-alpha:
359 | al = fabs(alpha)
360 | A=pow(n/al,n)*exp(-al**2/2.)
361 | B=n/al-al
362 | ret = A*pow(B-d,-n)
363 |
364 | elif d < alpha2 :
365 | ret = exp(-0.5*d**2)
366 | else:
367 | al2 = fabs(alpha2)
368 | A2=pow(n2/al2,n2)*exp(-al2**2/2.)
369 | B2=n2/al2-al2
370 | ret = A2*pow(B2+d,-n2)
371 | return ret
372 |
373 |
374 | #Background stuff
375 | cpdef double argus(double x, double c, double chi, double p):
376 | """
377 | Unnormalized argus distribution
378 |
379 | .. math::
380 | f(x;c,\chi,p) = \\frac{x}{c^2} \left( 1-\\frac{x^2}{c^2} \\right)
381 | \exp \left( - \\frac{1}{2} \chi^2 \left( 1 - \\frac{x^2}{c^2} \\right) \\right)
382 |
383 | .. note::
384 | http://en.wikipedia.org/wiki/ARGUS_distribution
385 |
386 | """
387 | if cc:
390 | return 0.
391 | cdef double xc = x/c
392 | cdef double xc2 = xc*xc
393 | cdef double ret = 0
394 |
395 | ret = xc/c*pow(1.-xc2,p)*exp(-0.5*chi*chi*(1-xc2))
396 |
397 | return ret
398 |
399 |
400 | cpdef double cruijff(double x, double m_0, double sigma_L, double sigma_R, double alpha_L, double alpha_R):
401 | """
402 | Unnormalized cruijff function
403 |
404 | .. math::
405 | f(x;m_0, \sigma_L, \sigma_R, \\alpha_L, \\alpha_R) =
406 | \\begin{cases}
407 | \exp\left(-\\frac{(x-m_0)^2}{2(\sigma_{L}^2+\\alpha_{L}(x-m_0)^2)}\\right)
408 | & \mbox{if } x 1e-7:
519 | lqyt = log(qy)/tail
520 | qc =0.5*lqyt*lqyt + tail*tail
521 | else:
522 | qc=15.
523 | return exp(-qc)
524 |
525 |
526 | cpdef double rtv_breitwigner(double x, double m, double gamma):
527 | """
528 | Normalized Relativistic Breit-Wigner
529 |
530 | .. math::
531 | f(x; m, \Gamma) = N\\times \\frac{1}{(x^2-m^2)^2+m^2\Gamma^2}
532 |
533 | where
534 |
535 | .. math::
536 | N = \\frac{2 \sqrt{2} m \Gamma \gamma }{\pi \sqrt{m^2+\gamma}}
537 |
538 | and
539 |
540 | .. math::
541 | \gamma=\sqrt{m^2\left(m^2+\Gamma^2\\right)}
542 |
543 |
544 | .. seealso::
545 | :func:`cauchy`
546 |
547 | .. plot:: pyplots/pdf/cauchy_bw.py
548 | :class: lightbox
549 |
550 | """
551 | cdef double mm = m*m
552 | cdef double xm = x*x-mm
553 | cdef double gg = gamma*gamma
554 | cdef double s = sqrt(mm*(mm+gg))
555 | cdef double N = (2*sqrt(2)/pi)*m*gamma*s/sqrt(mm+s)
556 | return N/(xm*xm+mm*gg)
557 |
558 |
559 | cpdef double cauchy(double x, double m, double gamma):
560 | """
561 | Cauchy distribution aka non-relativistic Breit-Wigner
562 |
563 | .. math::
564 | f(x, m, \gamma) = \\frac{1}{\pi \gamma \left[ 1+\left( \\frac{x-m}{\gamma} \\right)^2\\right]}
565 |
566 | .. seealso::
567 | :func:`breitwigner`
568 |
569 | .. plot:: pyplots/pdf/cauchy_bw.py
570 | :class: lightbox
571 |
572 | """
573 | cdef double xmg = (x-m)/gamma
574 | return 1/(pi*gamma*(1+xmg*xmg))
575 |
576 |
577 | cdef class _Exponential:
578 | """
579 | Exponential [1]_.
580 |
581 | .. math::
582 | f(x;\\tau) =
583 | \\begin{cases}
584 | \\exp \\left(-\\lambda x \\right) & \\mbox{if } \\x \\geq 0 \\\\
585 | 0 & \\mbox{if } \\x < 0
586 | \\end{cases}
587 |
588 | References
589 | ----------
590 |
591 | .. [1] https://en.wikipedia.org/wiki/Exponential_distribution
592 |
593 | """
594 | cdef public object func_code
595 | cdef public object func_defaults
596 |
597 | def __init__(self, xname='x'):
598 |
599 | varnames = [xname, "lambda"]
600 | self.func_code = MinimalFuncCode(varnames)
601 | self.func_defaults = None
602 |
603 | def __call__(self, *arg):
604 |
605 | cdef double x = arg[0]
606 | cdef double _lambda = arg[1]
607 | cdef double ret = 0
608 |
609 | if x >= 0:
610 | ret = _lambda * exp(x * -_lambda)
611 | else:
612 | ret = 0.
613 |
614 | return ret
615 |
616 | def cdf(self, x, _lambda):
617 |
618 | cdef double ret = 0
619 |
620 | if x >= 0:
621 | ret = 1. - exp(x * -_lambda)
622 | else:
623 | ret = 0.
624 |
625 | return ret
626 |
627 | def integrate(self, tuple bound, int nint_subdiv, *arg):
628 |
629 | cdef double a, b
630 | a, b = bound
631 |
632 | cdef double _lambda = arg[0]
633 |
634 | Fa = self.cdf(a, _lambda)
635 | Fb = self.cdf(b, _lambda)
636 |
637 | return Fb - Fa
638 |
639 |
640 | exponential = _Exponential()
641 |
--------------------------------------------------------------------------------
/probfit/plotting.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Plotting is on python since this will make it much easier to debug and adjust
3 | # no need to recompile everytime i change graph color....
4 | # needs a serious refactor
5 | from math import ceil, floor, sqrt
6 | from warnings import warn
7 |
8 | import numpy as np
9 |
10 | from .nputil import mid, minmax, vector_apply
11 | from .util import describe, parse_arg
12 |
13 |
14 | def draw_simultaneous(self, minuit=None, args=None, errors=None, **kwds):
15 | from matplotlib import pyplot as plt
16 |
17 | numf = len(self.allf)
18 | ret = []
19 | numraw = sqrt(numf)
20 | numcol = ceil(numraw)
21 | numrow = floor(numraw) if floor(numraw) * numcol >= numf else ceil(numraw)
22 |
23 | for i in range(numf):
24 | plt.subplot(numrow, numcol, i + 1)
25 | part_args, part_errors = self.args_and_error_for(i, minuit, args, errors)
26 | ret.append(self.allf[i].draw(args=part_args, errors=part_errors, **kwds))
27 |
28 | return ret
29 |
30 |
31 | def _get_args_and_errors(self, minuit=None, args=None, errors=None):
32 | """
33 | consistent algorithm to get argument and errors
34 | 1) get it from minuit if minuit is available
35 | 2) if not get it from args and errors
36 | 2.1) if args is dict parse it.
37 | 3) if all else fail get it from self.last_arg
38 | """
39 | ret_arg = None
40 | ret_error = None
41 | if minuit is not None: # case 1
42 | ret_arg = minuit.args
43 | ret_error = minuit.errors
44 | return ret_arg, ret_error
45 |
46 | # no minuit specified use args and errors
47 | if args is not None:
48 | if isinstance(args, dict):
49 | ret_arg = parse_arg(self, args)
50 | else:
51 | ret_arg = args
52 | else: # case 3
53 | ret_arg = self.last_arg
54 |
55 | if errors is not None:
56 | ret_error = errors
57 |
58 | return ret_arg, ret_error
59 |
60 |
61 | def _param_text(parameters, arg, error):
62 | txt = u""
63 | for (k, v) in zip(parameters, arg):
64 | txt += u"{} = {:5.4g}".format(k, v)
65 | if error is not None:
66 | txt += u"±%5.4g" % error[k]
67 | txt += u"\n"
68 | return txt
69 |
70 | # from UML
71 |
72 |
73 | def draw_ulh(
74 | self,
75 | minuit=None,
76 | bins=100,
77 | ax=None,
78 | bound=None,
79 | parmloc=(0.05, 0.95),
80 | nfbins=200,
81 | print_par=True,
82 | grid=True,
83 | args=None,
84 | errors=None,
85 | parts=False,
86 | show_errbars="normal",
87 | no_plot=False,
88 | ):
89 | from matplotlib import pyplot as plt
90 |
91 | error_ret = None
92 | part_ret = []
93 |
94 | ax = plt.gca() if ax is None and not no_plot else ax
95 |
96 | arg, error = _get_args_and_errors(self, minuit, args, errors)
97 |
98 | n, e = np.histogram(self.data, bins=bins, range=bound, weights=self.weights)
99 | dataint = (n * np.diff(e)).sum()
100 | data_ret = (e, n)
101 |
102 | if not show_errbars:
103 | if not no_plot:
104 | ax.hist(mid(e), bins=e, weights=n, histtype="step")
105 | error_ret = (np.sqrt(n), np.sqrt(n))
106 | else:
107 | w2 = None
108 | if show_errbars == "normal":
109 | w2 = n
110 | error_ret = (np.sqrt(n), np.sqrt(n))
111 | elif show_errbars == "sumw2":
112 | weights = None
113 | if self.weights is not None:
114 | weights = self.weights ** 2
115 | w2, e = np.histogram(self.data, bins=e, weights=weights)
116 | error_ret = (np.sqrt(w2), np.sqrt(w2))
117 | else:
118 | raise ValueError("show_errbars must be 'normal' or 'sumw2'")
119 | if not no_plot:
120 | ax.errorbar(mid(e), n, np.sqrt(w2), fmt="b.", capsize=0, zorder=0)
121 |
122 | # bound = (e[0], e[-1])
123 | draw_arg = [("lw", 2), ("zorder", 2)]
124 | if not parts:
125 | draw_arg.append(("color", "r"))
126 |
127 | # Draw pdf with finer bins
128 | ef = np.linspace(e[0], e[-1], nfbins + 1)
129 | scale = dataint if not self.extended else nfbins / float(bins)
130 | total_ret = draw_pdf_with_edges(
131 | self.f, arg, ef, ax=ax, density=not self.extended, scale=scale, **dict(draw_arg)
132 | )
133 |
134 | if parts:
135 | f_parts = getattr(self.f, "parts", None)
136 | if f_parts is not None:
137 | for p in f_parts():
138 | ret = draw_pdf_with_edges(
139 | p,
140 | arg,
141 | ef,
142 | ax=ax,
143 | scale=scale,
144 | density=not self.extended,
145 | no_plot=no_plot,
146 | )
147 | part_ret.append(ret)
148 | if not no_plot:
149 | ax.grid(grid)
150 |
151 | txt = _param_text(describe(self), arg, error)
152 | if not no_plot and print_par:
153 | ax.text(
154 | parmloc[0], parmloc[1], txt, ha="left", va="top", transform=ax.transAxes
155 | )
156 | return (data_ret, error_ret, total_ret, part_ret)
157 |
158 |
159 | def draw_residual(
160 | x, y, yerr, xerr, show_errbars=True, ax=None, zero_line=True, grid=True, **kwargs
161 | ):
162 | """Draw a residual plot on the axis.
163 |
164 | By default, if show_errbars if True, residuals are drawn as blue points
165 | with errorbars with no endcaps. If show_errbars is False, residuals are
166 | drawn as a bar graph with black bars.
167 |
168 | **Arguments**
169 |
170 | - **x** array of numbers, x-coordinates
171 |
172 | - **y** array of numbers, y-coordinates
173 |
174 | - **yerr** array of numbers, the uncertainty on the y-values
175 |
176 | - **xerr** array of numbers, the uncertainty on the x-values
177 |
178 | - **show_errbars** If True, draw the data as a bar plot, else as an
179 | errorbar plot
180 |
181 | - **ax** Optional matplotlib axis instance on which to draw the plot
182 |
183 | - **zero_line** If True, draw a red line at :math:`y = 0` along the
184 | full extent in :math:`x`
185 |
186 | - **grid** If True, draw gridlines
187 |
188 | - **kwargs** passed to ``ax.errorbar`` (if ``show_errbars`` is True) or
189 | ``ax.bar`` (if ``show_errbars`` if False)
190 |
191 | **Returns**
192 |
193 | The matplotlib axis instance the plot was drawn on.
194 | """
195 | from matplotlib import pyplot as plt
196 |
197 | ax = plt.gca() if ax is None else ax
198 |
199 | if show_errbars:
200 | plotopts = dict(fmt="b.", capsize=0)
201 | plotopts.update(kwargs)
202 | ax.errorbar(x, y, yerr, xerr, zorder=0, **plotopts)
203 | else:
204 | plotopts = dict(color="k")
205 | plotopts.update(kwargs)
206 | ax.bar(x - xerr, y, width=2 * xerr, **plotopts)
207 |
208 | if zero_line:
209 | ax.plot([x[0] - xerr[0], x[-1] + xerr[-1]], [0, 0], "r-", zorder=2)
210 |
211 | # Take the `grid` kwarg to mean 'add a grid if True'; if grid is False and
212 | # we called ax.grid(False) then any existing grid on ax would be turned off
213 | if grid:
214 | ax.grid(grid)
215 |
216 | return ax
217 |
218 |
219 | def draw_residual_ulh(
220 | self,
221 | minuit=None,
222 | bins=100,
223 | ax=None,
224 | bound=None,
225 | parmloc=(0.05, 0.95),
226 | print_par=False,
227 | args=None,
228 | errors=None,
229 | errbar_algo="normal",
230 | norm=False,
231 | **kwargs
232 | ):
233 | from matplotlib import pyplot as plt
234 |
235 | ax = plt.gca() if ax is None else ax
236 |
237 | arg, error = _get_args_and_errors(self, minuit, args, errors)
238 |
239 | n, e = np.histogram(self.data, bins=bins, range=bound, weights=self.weights)
240 | dataint = (n * np.diff(e)).sum()
241 | scale = dataint if not self.extended else 1.0
242 | w2 = None
243 | if errbar_algo == "normal":
244 | w2 = n
245 | elif errbar_algo == "sumw2":
246 | weights = None
247 | if self.weights is not None:
248 | weights = self.weights ** 2
249 | w2, e = np.histogram(self.data, bins=e, weights=weights)
250 | else:
251 | raise ValueError("errbar_algo must be 'normal' or 'sumw2'")
252 | yerr = np.sqrt(w2)
253 |
254 | arg = parse_arg(self.f, arg, 1) if isinstance(arg, dict) else arg
255 | yf = vector_apply(self.f, mid(e), *arg)
256 | yf *= scale * np.diff(e) if self.extended else scale
257 | n = n - yf
258 | if norm:
259 | sel = yerr > 0
260 | n[sel] /= yerr[sel]
261 | yerr = np.ones(len(yerr))
262 |
263 | ax = draw_residual(mid(e), n, yerr, np.diff(e) / 2.0, ax=ax, **kwargs)
264 |
265 | txt = _param_text(describe(self), arg, error)
266 | if print_par:
267 | ax.text(
268 | parmloc[0], parmloc[1], txt, ha="left", va="top", transform=ax.transAxes
269 | )
270 |
271 | # from chi2 regression
272 |
273 |
274 | def draw_x2(
275 | self,
276 | minuit=None,
277 | ax=None,
278 | parmloc=(0.05, 0.95),
279 | print_par=True,
280 | args=None,
281 | errors=None,
282 | grid=True,
283 | parts=False,
284 | nbins=None,
285 | no_plot=False,
286 | ):
287 | from matplotlib import pyplot as plt
288 |
289 | error_ret = None
290 | part_ret = []
291 |
292 | ax = plt.gca() if ax is None and not no_plot else ax
293 |
294 | arg, error = _get_args_and_errors(self, minuit, args, errors)
295 |
296 | x = self.x
297 | y = self.y
298 | data_err = self.error
299 |
300 | data_ret = x, y
301 | if data_err is None:
302 | if not no_plot:
303 | ax.plot(x, y, "+")
304 | error_ret = (np.ones(len(self.x)), np.ones(len(self.x)))
305 | else:
306 | if not no_plot:
307 | ax.errorbar(x, y, data_err, fmt=".", zorder=0)
308 | error_ret = (data_err, data_err)
309 | draw_arg = [("lw", 2), ("zorder", 2)]
310 | draw_arg.append(("color", "r"))
311 |
312 | # Draw PDF curve(s)
313 | if nbins is not None:
314 | x = np.linspace(x[0], x[-1], nbins)
315 |
316 | total_ret = draw_pdf_with_midpoints(
317 | self.f, arg, x, no_plot=no_plot, ax=ax, **dict(draw_arg)
318 | )
319 |
320 | if not no_plot:
321 | ax.grid(grid)
322 |
323 | txt = _param_text(describe(self), arg, error)
324 |
325 | chi2 = self(*arg)
326 | if self.ndof > 0:
327 | txt += u"chi2/ndof = %5.4g(%5.4g/%d)" % (chi2 / self.ndof, chi2, self.ndof)
328 | else:
329 | txt += u"chi2/ndof = (%5.4g/%d)" % (chi2, self.ndof)
330 |
331 | if parts:
332 | f_parts = getattr(self.f, "parts", None)
333 | if f_parts is not None:
334 | for p in f_parts():
335 | tmp = draw_pdf_with_midpoints(
336 | p, arg, x, ax=ax, no_plot=no_plot, **dict(draw_arg)
337 | )
338 | part_ret.append(tmp)
339 |
340 | if print_par and not no_plot:
341 | ax.text(
342 | parmloc[0], parmloc[1], txt, ha="left", va="top", transform=ax.transAxes
343 | )
344 |
345 | return (data_ret, error_ret, total_ret, part_ret)
346 |
347 |
348 | def draw_x2_residual(
349 | self, minuit=None, ax=None, args=None, errors=None, grid=True, norm=False
350 | ):
351 | from matplotlib import pyplot as plt
352 |
353 | ax = plt.gca() if ax is None else ax
354 |
355 | arg, _ = _get_args_and_errors(self, minuit, args, errors)
356 |
357 | x = self.x
358 | y = self.y
359 | data_err = self.error
360 | f = self.f
361 |
362 | arg = parse_arg(f, arg, 1) if isinstance(arg, dict) else arg
363 | yf = vector_apply(f, x, *arg)
364 |
365 | yplot = y - yf
366 | eplot = data_err if data_err is not None else np.zeros(len(x))
367 | if norm:
368 | if data_err is None:
369 | warn(RuntimeWarning("No error on data points; cannot normalize to error"))
370 | else:
371 | yplot = yplot / data_err
372 | eplot = data_err / data_err
373 | ax.errorbar(x, yplot, eplot, fmt="b+")
374 | ax.grid(grid)
375 |
376 | # from binned chi2
377 |
378 |
379 | def draw_bx2(
380 | self,
381 | minuit=None,
382 | parmloc=(0.05, 0.95),
383 | nfbins=500,
384 | ax=None,
385 | print_par=True,
386 | args=None,
387 | errors=None,
388 | parts=False,
389 | grid=True,
390 | no_plot=False,
391 | ):
392 | from matplotlib import pyplot as plt
393 |
394 | part_ret = []
395 |
396 | ax = plt.gca() if ax is None and not no_plot else ax
397 |
398 | arg, error = _get_args_and_errors(self, minuit, args, errors)
399 |
400 | m = mid(self.edges)
401 |
402 | if not no_plot:
403 | ax.errorbar(m, self.h, self.err, fmt=".", zorder=0)
404 | data_ret = (self.edges, self.h)
405 | error_ret = (self.err, self.err)
406 |
407 | bound = (self.edges[0], self.edges[-1])
408 |
409 | scale = nfbins / float(self.bins) # scale back to bins
410 |
411 | draw_arg = [("lw", 2), ("zorder", 2)]
412 |
413 | if not parts:
414 | draw_arg.append(("color", "r"))
415 |
416 | total_ret = draw_pdf(
417 | self.f,
418 | arg,
419 | bins=nfbins,
420 | bound=bound,
421 | ax=ax,
422 | density=False,
423 | scale=scale,
424 | no_plot=no_plot,
425 | **dict(draw_arg)
426 | )
427 |
428 | if parts:
429 | f_parts = getattr(self.f, "parts", None)
430 | if f_parts is not None:
431 | for p in f_parts():
432 | tmp = draw_pdf(
433 | p,
434 | arg,
435 | bound=bound,
436 | bins=nfbins,
437 | ax=ax,
438 | density=False,
439 | scale=scale,
440 | no_plot=no_plot,
441 | )
442 | part_ret.append(tmp)
443 |
444 | if not no_plot:
445 | ax.grid(grid)
446 |
447 | txt = _param_text(describe(self), arg, error)
448 |
449 | chi2 = self(*arg)
450 | if self.ndof > 0:
451 | txt += u"chi2/ndof = %5.4g(%5.4g/%d)" % (chi2 / self.ndof, chi2, self.ndof)
452 | else:
453 | txt += u"chi2/ndof = (%5.4g/%d)" % (chi2, self.ndof)
454 |
455 | if print_par and not no_plot:
456 | ax.text(
457 | parmloc[0], parmloc[1], txt, ha="left", va="top", transform=ax.transAxes
458 | )
459 |
460 | return (data_ret, error_ret, total_ret, part_ret)
461 |
462 | # from binnedLH
463 |
464 |
465 | def draw_blh(
466 | self,
467 | minuit=None,
468 | parmloc=(0.05, 0.95),
469 | nfbins=1000,
470 | ax=None,
471 | print_par=True,
472 | grid=True,
473 | args=None,
474 | errors=None,
475 | parts=False,
476 | no_plot=False,
477 | ):
478 | from matplotlib import pyplot as plt
479 |
480 | part_ret = []
481 |
482 | ax = plt.gca() if ax is None and not no_plot else ax
483 |
484 | arg, error = _get_args_and_errors(self, minuit, args, errors)
485 |
486 | m = mid(self.edges)
487 |
488 | if self.use_w2:
489 | err = np.sqrt(self.w2)
490 | else:
491 | err = np.sqrt(self.h)
492 |
493 | n = np.copy(self.h)
494 | dataint = (n * np.diff(self.edges)).sum()
495 | scale = dataint if not self.extended else 1.0
496 |
497 | if not no_plot:
498 | ax.errorbar(m, n, err, fmt=".", zorder=0)
499 | data_ret = (self.edges, n)
500 | error_ret = (err, err)
501 |
502 | draw_arg = [("lw", 2), ("zorder", 2)]
503 | if not parts:
504 | draw_arg.append(("color", "r"))
505 | bound = (self.edges[0], self.edges[-1])
506 |
507 | # scale back to bins
508 | if self.extended:
509 | scale = nfbins / float(self.bins)
510 | total_ret = draw_pdf(
511 | self.f,
512 | arg,
513 | bins=nfbins,
514 | bound=bound,
515 | ax=ax,
516 | density=not self.extended,
517 | scale=scale,
518 | no_plot=no_plot,
519 | **dict(draw_arg)
520 | )
521 | if parts:
522 | f_parts = getattr(self.f, "parts", None)
523 | if f_parts is not None:
524 | for p in f_parts():
525 | tmp = draw_pdf(
526 | p,
527 | arg,
528 | bins=nfbins,
529 | bound=bound,
530 | ax=ax,
531 | density=not self.extended,
532 | scale=scale,
533 | no_plot=no_plot,
534 | )
535 | part_ret.append(tmp)
536 | if not no_plot:
537 | ax.grid(grid)
538 |
539 | txt = _param_text(describe(self), arg, error)
540 |
541 | if print_par and not no_plot:
542 | ax.text(
543 | parmloc[0], parmloc[1], txt, ha="left", va="top", transform=ax.transAxes
544 | )
545 |
546 | return (data_ret, error_ret, total_ret, part_ret)
547 |
548 |
549 | def draw_residual_blh(
550 | self,
551 | minuit=None,
552 | parmloc=(0.05, 0.95),
553 | ax=None,
554 | print_par=False,
555 | args=None,
556 | errors=None,
557 | norm=False,
558 | **kwargs
559 | ):
560 | from matplotlib import pyplot as plt
561 |
562 | ax = plt.gca() if ax is None else ax
563 |
564 | arg, error = _get_args_and_errors(self, minuit, args, errors)
565 |
566 | m = mid(self.edges)
567 |
568 | if self.use_w2:
569 | err = np.sqrt(self.w2)
570 | else:
571 | err = np.sqrt(self.h)
572 |
573 | n = np.copy(self.h)
574 | dataint = (n * np.diff(self.edges)).sum()
575 | scale = dataint if not self.extended else 1.0
576 |
577 | arg = parse_arg(self.f, arg, 1) if isinstance(arg, dict) else arg
578 | yf = vector_apply(self.f, m, *arg)
579 | yf *= scale * np.diff(self.edges) if self.extended else scale
580 | n = n - yf
581 | if norm:
582 | sel = err > 0
583 | n[sel] /= err[sel]
584 | err = np.ones(len(err))
585 |
586 | ax = draw_residual(m, n, err, np.diff(self.edges) / 2.0, ax=ax, **kwargs)
587 |
588 | txt = _param_text(describe(self), arg, error)
589 |
590 | if print_par:
591 | ax.text(
592 | parmloc[0], parmloc[1], txt, ha="left", va="top", transform=ax.transAxes
593 | )
594 |
595 |
596 | def draw_compare(
597 | f, arg, edges, data, errors=None, ax=None, grid=True, normed=False, parts=False
598 | ):
599 | """
600 | TODO: this needs to be rewritten
601 | """
602 | from matplotlib import pyplot as plt
603 |
604 | # arg is either map or tuple
605 | ax = plt.gca() if ax is None else ax
606 | arg = parse_arg(f, arg, 1) if isinstance(arg, dict) else arg
607 | x = (edges[:-1] + edges[1:]) / 2.0
608 | bw = np.diff(edges)
609 | yf = vector_apply(f, x, *arg)
610 | total = np.sum(data)
611 | if normed:
612 | ax.errorbar(x, data / bw / total, errors / bw / total, fmt=".b", zorder=0)
613 | ax.plot(x, yf, "r", lw=2, zorder=2)
614 | else:
615 | ax.errorbar(x, data, errors, fmt=".b", zorder=0)
616 | ax.plot(x, yf * bw, "r", lw=2, zorder=2)
617 |
618 | # now draw the parts
619 | if parts:
620 | if not hasattr(f, "eval_parts"):
621 | warn(
622 | RuntimeWarning(
623 | "parts is set to True but function does "
624 | "not have eval_parts method"
625 | )
626 | )
627 | else:
628 | scale = bw if not normed else 1.0
629 | parts_val = list()
630 | for tx in x:
631 | val = f.eval_parts(tx, *arg)
632 | parts_val.append(val)
633 | py = zip(*parts_val)
634 | for y in py:
635 | tmpy = np.array(y)
636 | ax.plot(x, tmpy * scale, lw=2, alpha=0.5)
637 | plt.grid(grid)
638 | return x, yf, data
639 |
640 |
641 | def draw_normed_pdf(f, arg, bound, bins=100, scale=1.0, density=True, ax=None, **kwds):
642 | return draw_pdf(
643 | f, arg, bound, bins=100, scale=1.0, density=True, normed_pdf=True, ax=ax, **kwds
644 | )
645 |
646 |
647 | def draw_pdf(
648 | f, arg, bound, bins=100, scale=1.0, density=True, normed_pdf=False, ax=None, **kwds
649 | ):
650 | """
651 | draw pdf with given argument and bounds.
652 |
653 | **Arguments**
654 |
655 | * **f** your pdf. The first argument is assumed to be independent
656 | variable
657 |
658 | * **arg** argument can be tuple or list
659 |
660 | * **bound** tuple(xmin,xmax)
661 |
662 | * **bins** number of bins to plot pdf. Default 100.
663 |
664 | * **scale** multiply pdf by given number. Default 1.0.
665 |
666 | * **density** plot density instead of expected count in each bin
667 | (pdf*bin width). Default True.
668 |
669 | * **normed_pdf** Normalize pdf in given bound. Default False
670 |
671 | * The rest of keyword argument will be pass to pyplot.plot
672 |
673 | **Returns**
674 |
675 | x, y of what's being plot
676 | """
677 | edges = np.linspace(bound[0], bound[1], bins)
678 | return draw_pdf_with_edges(
679 | f,
680 | arg,
681 | edges,
682 | ax=ax,
683 | scale=scale,
684 | density=density,
685 | normed_pdf=normed_pdf,
686 | **kwds
687 | )
688 |
689 |
690 | def draw_pdf_with_edges(
691 | f, arg, edges, ax=None, scale=1.0, density=True, normed_pdf=False, **kwds
692 | ):
693 | x = (edges[:-1] + edges[1:]) / 2.0
694 | bw = np.diff(edges)
695 | scale *= bw if not density else 1.0
696 |
697 | return draw_pdf_with_midpoints(
698 | f, arg, x, ax=ax, scale=scale, normed_pdf=normed_pdf, **kwds
699 | )
700 |
701 |
702 | def draw_pdf_with_midpoints(
703 | f, arg, x, ax=None, scale=1.0, normed_pdf=False, no_plot=False, **kwds
704 | ):
705 | from matplotlib import pyplot as plt
706 |
707 | ax = plt.gca() if ax is None and not no_plot else ax
708 | arg = parse_arg(f, arg, 1) if isinstance(arg, dict) else arg
709 | yf = vector_apply(f, x, *arg)
710 |
711 | if normed_pdf:
712 | normed_factor = sum(yf) # assume equal binwidth
713 | yf /= normed_factor
714 | yf *= scale
715 |
716 | if not no_plot:
717 | ax.plot(x, yf, **kwds)
718 | return x, yf
719 |
720 | # draw comparison between function given args and data
721 |
722 |
723 | def draw_compare_hist(
724 | f,
725 | arg,
726 | data,
727 | bins=100,
728 | bound=None,
729 | ax=None,
730 | weights=None,
731 | normed=False,
732 | use_w2=False,
733 | parts=False,
734 | grid=True,
735 | ):
736 | """
737 | draw histogram of data with poisson error bar and f(x,*arg).
738 |
739 | ::
740 |
741 | data = np.random.rand(10000)
742 | f = gaussian
743 | draw_compare_hist(f, {'mean':0,'sigma':1}, data, normed=True)
744 |
745 | **Arguments**
746 |
747 | - **f**
748 | - **arg** argument pass to f. Can be dictionary or list.
749 | - **data** data array
750 | - **bins** number of bins. Default 100.
751 | - **bound** optional boundary of plot in tuple form. If `None` is
752 | given, the bound is determined from min and max of the data. Default
753 | `None`
754 | - **weights** weights array. Default None.
755 | - **normed** optional normalized data flag. Default False.
756 | - **use_w2** scaled error down to the original statistics instead of
757 | weighted statistics.
758 | - **parts** draw parts of pdf. (Works with AddPdf and Add2PdfNorm).
759 | Default False.
760 | """
761 | from matplotlib import pyplot as plt
762 |
763 | ax = plt.gca() if ax is None else ax
764 | bound = minmax(data) if bound is None else bound
765 | h, e = np.histogram(data, bins=bins, range=bound, weights=weights)
766 | err = None
767 | if weights is not None and use_w2:
768 | err, _ = np.histogram(data, bins=bins, range=bound, weights=weights * weights)
769 | err = np.sqrt(err)
770 | else:
771 | err = np.sqrt(h)
772 | return draw_compare(f, arg, e, h, err, ax=ax, grid=grid, normed=normed, parts=parts)
773 |
--------------------------------------------------------------------------------
/probfit/probfit_warnings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | class ProbfitWarning(RuntimeWarning):
3 | pass
4 |
5 |
6 | class SmallIntegralWarning(ProbfitWarning):
7 | pass
8 |
9 |
10 | class SmallDivisionWarning(ProbfitWarning):
11 | pass
12 |
13 |
14 | class SharedExtendeeExtenderParameter(ProbfitWarning):
15 | pass
16 |
17 |
18 | class LogWarning(ProbfitWarning):
19 | pass
20 |
--------------------------------------------------------------------------------
/probfit/py23_compat.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Python 2 / 3 compatibility helpers.
4 | """
5 | import sys
6 |
7 | py_ver = sys.version_info
8 | PY2 = False
9 | PY3 = False
10 | if py_ver[0] == 2:
11 | PY2 = True
12 | else: # just in case PY4
13 | PY3 = True
14 |
15 |
16 | if sys.version_info < (3,):
17 | range = xrange # noqa: F821
18 | else:
19 | range = range
20 |
--------------------------------------------------------------------------------
/probfit/statutil.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import numpy as np
3 |
4 | from ._libstat import _vector_apply
5 | from .py23_compat import range
6 |
7 |
8 | def fwhm_f(f, range, arg=None, bins=1000):
9 | arg = tuple() if arg is None else arg
10 |
11 | x = np.linspace(range[0], range[1], bins)
12 | y = _vector_apply(f, x, arg)
13 | imax = np.argmax(y)
14 | ymax = y[imax]
15 | rs = y[imax:] - ymax / 2.0
16 | ls = y[:imax] - ymax / 2.0
17 |
18 | il = first_neg(ls, "l")
19 | # print il,x[il],ls[il],x[il+1],ls[il+1]
20 | xl = xintercept(x[il], ls[il], x[il + 1], ls[il + 1])
21 |
22 | ir = first_neg(rs, "r")
23 | # print ir,x[imax+ir],rs[ir],x[ir+1],rs[ir+1]
24 | xr = xintercept(x[imax + ir], rs[ir], x[imax + ir - 1], rs[ir - 1])
25 |
26 | return (xl, xr)
27 |
28 |
29 | def xintercept_tuple(t0, t1):
30 | return xintercept(t0[0], t0[1], t1[0], t1[1])
31 |
32 |
33 | def xintercept(x0, y0, x1, y1):
34 | m = (y1 - y0) / (x1 - x0)
35 | return -y0 / m + x0
36 |
37 |
38 | def first_neg(y, direction="r"):
39 | if direction == "l":
40 | xlist = range(len(y) - 1, -1, -1)
41 | else:
42 | xlist = range(len(y))
43 | ret = 0
44 | found = False
45 | for i in xlist:
46 | if y[i] < 0:
47 | ret = i
48 | found = True
49 | break
50 | if not found:
51 | raise ValueError("They are all positive what are you tying to find?")
52 | return ret
53 |
--------------------------------------------------------------------------------
/probfit/toy.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from warnings import warn
3 |
4 | import numpy as np
5 | import numpy.random as npr
6 |
7 | from ._libstat import _vector_apply, compute_cdf, invert_cdf
8 | from .probfit_warnings import SmallIntegralWarning
9 | from .util import describe
10 |
11 | __all__ = [
12 | "gen_toy",
13 | "gen_toyn",
14 | ]
15 |
16 |
17 | def gen_toyn(f, nsample, ntoy, bound, accuracy=10000, quiet=True, **kwd):
18 | """
19 | just alias of gentoy for nample and then reshape to ntoy,nsample)
20 | :param f:
21 | :param nsample:
22 | :param bound:
23 | :param accuracy:
24 | :param quiet:
25 | :param kwd:
26 | :return:
27 | """
28 | return gen_toy(f, nsample * ntoy, bound, accuracy, quiet, **kwd).reshape(
29 | (ntoy, nsample)
30 | )
31 |
32 |
33 | def gen_toy(f, nsample, bound, accuracy=10000, quiet=True, **kwd):
34 | """
35 | generate ntoy
36 | :param f:
37 | :param nsample:
38 | :param ntoy:
39 | :param bound:
40 | :param accuracy:
41 | :param quiet:
42 | :param kwd: the rest of keyword argument will be passed to f
43 | :return: numpy.ndarray
44 | """
45 | # based on inverting cdf this is fast but you will need to give it a reasonable range
46 | # unlike roofit which is based on accept reject
47 |
48 | vnames = describe(f)
49 | if not quiet:
50 | print(vnames)
51 | my_arg = [kwd[v] for v in vnames[1:]]
52 | # random number
53 | # if accuracy is None: accuracy=10*numtoys
54 | r = npr.random_sample(nsample)
55 | x = np.linspace(bound[0], bound[1], accuracy)
56 | pdf = _vector_apply(f, x, tuple(my_arg))
57 | cdf = compute_cdf(pdf, x)
58 | if cdf[-1] < 0.01:
59 | warn(
60 | SmallIntegralWarning(
61 | "Integral for given funcition is"
62 | " really low. Did you give it a reasonable range?"
63 | )
64 | )
65 | cdfnorm = cdf[-1]
66 | cdf /= cdfnorm
67 |
68 | # now convert that to toy
69 | ret = invert_cdf(r, cdf, x)
70 |
71 | if not quiet:
72 | # move this to plotting
73 | from matplotlib import pyplot as plt
74 |
75 | plt.figure()
76 | plt.title("comparison")
77 | numbin = 100
78 | h, e = np.histogram(ret, bins=numbin)
79 | mp = (e[1:] + e[:-1]) / 2.0
80 | err = np.sqrt(h)
81 | plt.errorbar(mp, h, err, fmt=".b")
82 | bw = e[1] - e[0]
83 | y = pdf * len(ret) / cdfnorm * bw
84 | ylow = y + np.sqrt(y)
85 | yhigh = y - np.sqrt(y)
86 | plt.plot(x, y, label="pdf", color="r")
87 | plt.fill_between(x, yhigh, ylow, color="g", alpha=0.2)
88 | plt.grid(True)
89 | plt.xlim(bound)
90 | plt.ylim(ymin=0)
91 | return ret
92 |
--------------------------------------------------------------------------------
/probfit/util.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from iminuit.util import describe
3 |
4 |
5 | def parse_arg(f, kwd, offset=0):
6 | """
7 | convert dictionary of keyword argument and value to positional argument
8 | equivalent to::
9 |
10 | vnames = describe(f)
11 | return tuple([kwd[k] for k in vnames[offset:]])
12 |
13 | """
14 | vnames = describe(f)
15 | return tuple([kwd[k] for k in vnames[offset:]])
16 |
17 |
18 | def remove_prefix(s, prefix):
19 | if prefix is None:
20 | return s
21 | if s.startswith(prefix + "_"):
22 | l = len(prefix) + 1
23 | return s[l:]
24 | elif s.startswith(prefix):
25 | l = len(prefix)
26 | return s[l:]
27 | else:
28 | return s
29 |
--------------------------------------------------------------------------------
/probfit/version.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | __version__ = "1.2.0"
3 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = [
3 | "setuptools>=42",
4 | "wheel",
5 | "Cython<3",
6 | "numpy==1.13.3; python_version<'3.5'",
7 | "oldest-supported-numpy; python_version>='3.5'",
8 | ]
9 |
10 | build-backend = "setuptools.build_meta"
11 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | name = probfit
3 | description = Distribution Fitting/Regression Library
4 | long_description = file: README.rst
5 | long_description_content_type = text/x-rst
6 | url = https://github.com/scikit-hep/probfit
7 | author = Piti Ongmongkolkul
8 | author_email = piti118@gmail.com
9 | maintainer = The Scikit-HEP admins
10 | maintainer_email = scikit-hep-admins@googlegroups.com
11 | license = MIT
12 | license_file = LICENSE
13 | classifiers =
14 | Development Status :: 7 - Inactive
15 | Intended Audience :: Science/Research
16 | License :: OSI Approved :: MIT License
17 | Programming Language :: Python
18 | Programming Language :: Python :: 2
19 | Programming Language :: Python :: 2.7
20 | Programming Language :: Python :: 3
21 | Programming Language :: Python :: 3.5
22 | Programming Language :: Python :: 3.6
23 | Programming Language :: Python :: 3.7
24 | Programming Language :: Python :: 3.8
25 | Programming Language :: Python :: 3.9
26 | Topic :: Scientific/Engineering :: Mathematics
27 | Topic :: Scientific/Engineering :: Physics
28 |
29 | [options]
30 | packages = find:
31 | install_requires =
32 | iminuit<1.4; python_version <"3.5"
33 | iminuit<2; python_version >="3.5"
34 | numpy
35 | python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
36 | zip_safe = False
37 |
38 | [options.extras_require]
39 | dev =
40 | matplotlib>=2.0
41 | pytest>=4.6
42 | pytest-cov
43 | pytest-mpl<0.11; python_version <"3.6"
44 | pytest-mpl; python_version >="3.6"
45 | docs =
46 | ipython
47 | sphinx
48 | sphinx_rtd_theme
49 | test =
50 | pytest>=4.6
51 | pytest-cov
52 | pytest-mpl<0.11; python_version <"3.6"
53 | pytest-mpl; python_version >="3.6"
54 |
55 | [options.packages.find]
56 | include =
57 | probfit
58 |
59 | [check-manifest]
60 | ignore =
61 | doc/**
62 | .*
63 | *.c
64 |
65 |
66 | [tool:isort]
67 | profile = black
68 | multi_line_output = 3
69 |
70 | [flake8]
71 | max-complexity = 14
72 | # E731: assign a lambda
73 | # F403: import *
74 | # F405: undefined or star import
75 | # E741: ambiguous variable name l
76 | ignore = E203, E231, E501, E722, W503, B950, E731, E741
77 | select = C,E,F,W,B,B9
78 | per-file-ignores =
79 | probfit/__init__.py: F403, F405
80 | doc/conf.py: E402, E265
81 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import os
3 | from glob import glob
4 |
5 | import numpy as np
6 | from setuptools import setup
7 | from setuptools.extension import Extension
8 |
9 | version = {}
10 | with open("probfit/version.py") as fp:
11 | exec(fp.read(), version)
12 |
13 | extensions = []
14 | for source_file in glob("probfit/*.pyx"):
15 | fname, _ = os.path.splitext(os.path.basename(source_file))
16 | extensions.append(
17 | Extension(
18 | "probfit.{}".format(fname),
19 | sources=[source_file],
20 | include_dirs=[np.get_include()],
21 | )
22 | )
23 |
24 |
25 | setup(
26 | version=version["__version__"],
27 | ext_modules=extensions,
28 | )
29 |
--------------------------------------------------------------------------------
/tests/baseline/draw_blh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_blh.png
--------------------------------------------------------------------------------
/tests/baseline/draw_blh_extend.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_blh_extend.png
--------------------------------------------------------------------------------
/tests/baseline/draw_blh_extend_residual_norm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_blh_extend_residual_norm.png
--------------------------------------------------------------------------------
/tests/baseline/draw_blh_with_parts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_blh_with_parts.png
--------------------------------------------------------------------------------
/tests/baseline/draw_bx2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_bx2.png
--------------------------------------------------------------------------------
/tests/baseline/draw_bx2_with_parts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_bx2_with_parts.png
--------------------------------------------------------------------------------
/tests/baseline/draw_compare_hist_gaussian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_compare_hist_gaussian.png
--------------------------------------------------------------------------------
/tests/baseline/draw_compare_hist_no_norm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_compare_hist_no_norm.png
--------------------------------------------------------------------------------
/tests/baseline/draw_pdf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_pdf.png
--------------------------------------------------------------------------------
/tests/baseline/draw_pdf_linear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_pdf_linear.png
--------------------------------------------------------------------------------
/tests/baseline/draw_residual_blh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_residual_blh.png
--------------------------------------------------------------------------------
/tests/baseline/draw_residual_blh_norm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_residual_blh_norm.png
--------------------------------------------------------------------------------
/tests/baseline/draw_residual_blh_norm_no_errbars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_residual_blh_norm_no_errbars.png
--------------------------------------------------------------------------------
/tests/baseline/draw_residual_blh_norm_options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_residual_blh_norm_options.png
--------------------------------------------------------------------------------
/tests/baseline/draw_residual_ulh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_residual_ulh.png
--------------------------------------------------------------------------------
/tests/baseline/draw_residual_ulh_norm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_residual_ulh_norm.png
--------------------------------------------------------------------------------
/tests/baseline/draw_residual_ulh_norm_no_errbars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_residual_ulh_norm_no_errbars.png
--------------------------------------------------------------------------------
/tests/baseline/draw_residual_ulh_norm_options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_residual_ulh_norm_options.png
--------------------------------------------------------------------------------
/tests/baseline/draw_simultaneous.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_simultaneous.png
--------------------------------------------------------------------------------
/tests/baseline/draw_simultaneous_prefix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_simultaneous_prefix.png
--------------------------------------------------------------------------------
/tests/baseline/draw_ulh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_ulh.png
--------------------------------------------------------------------------------
/tests/baseline/draw_ulh_extend.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_ulh_extend.png
--------------------------------------------------------------------------------
/tests/baseline/draw_ulh_extend_residual_norm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_ulh_extend_residual_norm.png
--------------------------------------------------------------------------------
/tests/baseline/draw_ulh_with_minuit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_ulh_with_minuit.png
--------------------------------------------------------------------------------
/tests/baseline/draw_ulh_with_parts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_ulh_with_parts.png
--------------------------------------------------------------------------------
/tests/baseline/draw_x2reg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scikit-hep/probfit/50d45633e90af7ba564d9026b24914b042d1e0cd/tests/baseline/draw_x2reg.png
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import matplotlib
3 |
4 | matplotlib.use("Agg")
5 |
--------------------------------------------------------------------------------
/tests/test_fit.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import warnings
3 |
4 | import iminuit
5 | import numpy as np
6 | from iminuit import describe
7 | from iminuit.iminuit_warnings import InitialParamWarning
8 | from numpy.testing import assert_allclose
9 |
10 | from probfit.costfunc import (
11 | BinnedChi2,
12 | BinnedLH,
13 | Chi2Regression,
14 | SimultaneousFit,
15 | UnbinnedLH,
16 | )
17 | from probfit.funcutil import rename
18 | from probfit.pdf import gaussian, linear
19 |
20 |
21 | class TestFit:
22 | def setup(self):
23 | warnings.simplefilter("ignore", InitialParamWarning)
24 | np.random.seed(0)
25 | self.ndata = 20000
26 | self.data = np.random.randn(self.ndata)
27 | self.analytic = self.ndata * 0.5 * (np.log(2 * np.pi) + 1)
28 |
29 | def test_UnbinnedLH(self):
30 | f = gaussian
31 | assert list(describe(f)) == ["x", "mean", "sigma"]
32 | lh = UnbinnedLH(
33 | gaussian,
34 | self.data,
35 | )
36 | assert list(describe(lh)) == ["mean", "sigma"]
37 | assert_allclose(lh(0, 1), 28188.201229348757)
38 | minuit = iminuit.Minuit(lh)
39 | assert_allclose(minuit.errordef, 0.5)
40 |
41 | def test_BinnedLH(self):
42 | # write a better test... this depends on subtraction
43 | f = gaussian
44 | assert list(describe(f)) == ["x", "mean", "sigma"]
45 | lh = BinnedLH(gaussian, self.data, bound=[-3, 3])
46 | assert list(describe(lh)) == ["mean", "sigma"]
47 | assert_allclose(lh(0, 1), 20.446130781601543, atol=1)
48 | minuit = iminuit.Minuit(lh)
49 | assert_allclose(minuit.errordef, 0.5)
50 |
51 | def test_BinnedChi2(self):
52 | f = gaussian
53 | assert list(describe(f)) == ["x", "mean", "sigma"]
54 | lh = BinnedChi2(gaussian, self.data, bound=[-3, 3])
55 | assert list(describe(lh)) == ["mean", "sigma"]
56 | assert_allclose(lh(0, 1), 19951.005399882044, atol=1)
57 | minuit = iminuit.Minuit(lh)
58 | assert_allclose(minuit.errordef, 1.0)
59 |
60 | def test_Chi2Regression(self):
61 | x = np.linspace(1, 10, 10)
62 | y = 10 * x + 1
63 | f = linear
64 | assert list(describe(f)) == ["x", "m", "c"]
65 |
66 | lh = Chi2Regression(f, x, y)
67 |
68 | assert list(describe(lh)) == ["m", "c"]
69 |
70 | assert_allclose(lh(10, 1), 0)
71 |
72 | assert_allclose(lh(10, 0), 10.0)
73 | minuit = iminuit.Minuit(lh)
74 | assert_allclose(minuit.errordef, 1.0)
75 |
76 | def test_simultaneous(self):
77 | np.random.seed(0)
78 | data = np.random.randn(10000)
79 | shifted = data + 3.0
80 | g1 = rename(gaussian, ["x", "lmu", "sigma"])
81 | g2 = rename(gaussian, ["x", "rmu", "sigma"])
82 | ulh1 = UnbinnedLH(g1, data)
83 | ulh2 = UnbinnedLH(g2, shifted)
84 | sim = SimultaneousFit(ulh1, ulh2)
85 | assert describe(sim) == ["lmu", "sigma", "rmu"]
86 | minuit = iminuit.Minuit(sim, sigma=1.2, pedantic=False, print_level=0)
87 | minuit.migrad()
88 | assert minuit.migrad_ok()
89 | assert_allclose(minuit.values["lmu"], 0.0, atol=2 * minuit.errors["lmu"])
90 | assert_allclose(minuit.values["rmu"], 3.0, atol=2 * minuit.errors["rmu"])
91 | assert_allclose(minuit.values["sigma"], 1.0, atol=2 * minuit.errors["sigma"])
92 |
--------------------------------------------------------------------------------
/tests/test_func.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from math import exp, log
3 |
4 | import numpy as np
5 | from iminuit import describe
6 | from numpy.testing import assert_allclose
7 |
8 | from probfit import pdf
9 | from probfit._libstat import _vector_apply, csum, integrate1d, wlogyx, xlogyx
10 | from probfit.functor import construct_arg, fast_tuple_equal
11 | from probfit.funcutil import merge_func_code
12 |
13 |
14 | def f(x, y, z):
15 | return x + y + z
16 |
17 |
18 | def f2(x, z, a):
19 | return x + z + a
20 |
21 |
22 | def g(x, a, b):
23 | return x + a + b
24 |
25 |
26 | def h(x, c, d):
27 | return x + c + d
28 |
29 |
30 | def k_1(y, z):
31 | return y + z
32 |
33 |
34 | def k_2(i, j):
35 | return i + j
36 |
37 |
38 | # cpdef double doublegaussian(double x, double mean,
39 | # double sigma_L, double sigma_R)
40 | def test_doublegaussian():
41 | assert describe(pdf.doublegaussian) == ["x", "mean", "sigma_L", "sigma_R"]
42 | assert_allclose(pdf.doublegaussian(0.0, 0.0, 1.0, 2.0), 1.0)
43 | assert_allclose(pdf.doublegaussian(-1.0, 0.0, 1.0, 2.0), 0.6065306597126334)
44 | assert_allclose(pdf.doublegaussian(1.0, 0.0, 1.0, 2.0), 0.8824969025845955)
45 |
46 |
47 | # cpdef double ugaussian(double x, double mean, double sigma)
48 | def test_ugaussian():
49 | assert describe(pdf.ugaussian) == ["x", "mean", "sigma"]
50 | assert_allclose(pdf.ugaussian(0, 0, 1), 1.0)
51 | assert_allclose(pdf.ugaussian(-1, 0, 1), 0.6065306597126334)
52 | assert_allclose(pdf.ugaussian(1, 0, 1), 0.6065306597126334)
53 |
54 |
55 | # cpdef double gaussian(double x, double mean, double sigma)
56 | def test_gaussian():
57 | assert describe(pdf.gaussian) == ["x", "mean", "sigma"]
58 | assert_allclose(pdf.gaussian(0, 0, 1), 0.3989422804014327)
59 | assert_allclose(pdf.gaussian(-1, 0, 1), 0.24197072451914337)
60 | assert_allclose(pdf.gaussian(1, 0, 1), 0.24197072451914337)
61 |
62 |
63 | # cpdef double crystalball(double x,double alpha,double n,double mean,double sigma)
64 | def test_crystalball():
65 | assert describe(pdf.crystalball) == ["x", "alpha", "n", "mean", "sigma"]
66 | assert_allclose(pdf.crystalball(10, 1, 2, 10, 2), 1.0)
67 | assert_allclose(pdf.crystalball(11, 1, 2, 10, 2), 0.8824969025845955)
68 | assert_allclose(pdf.crystalball(12, 1, 2, 10, 2), 0.6065306597126334)
69 | assert_allclose(pdf.crystalball(14, 1, 2, 10, 2), 0.1353352832366127)
70 | assert_allclose(pdf.crystalball(6, 1, 2, 10, 2), 0.26956918209450376)
71 |
72 |
73 | # cpdef double doubecrystalball(double x,double alpha,double alpha2, double n,double n2, double mean,double sigma)
74 | def test_doublecrystalball():
75 | assert describe(pdf.doublecrystalball) == [
76 | "x",
77 | "alpha",
78 | "alpha2",
79 | "n",
80 | "n2",
81 | "mean",
82 | "sigma",
83 | ]
84 | assert_allclose(pdf.doublecrystalball(10, 1, 1, 2, 2, 10, 2), 1.0)
85 | assert_allclose(pdf.doublecrystalball(11, 1, 1, 2, 2, 10, 2), 0.8824969025845955)
86 | assert_allclose(pdf.doublecrystalball(12, 1, 1, 2, 2, 10, 2), 0.6065306597126334)
87 | assert_allclose(pdf.doublecrystalball(14, 1, 1, 2, 2, 10, 2), 0.26956918209450376)
88 | assert_allclose(pdf.doublecrystalball(6, 1, 1, 2, 2, 10, 2), 0.26956918209450376)
89 | assert_allclose(pdf.doublecrystalball(-10, 1, 5, 3, 4, 10, 2), 0.00947704155801)
90 | assert_allclose(pdf.doublecrystalball(0, 1, 5, 3, 4, 10, 2), 0.047744395954055)
91 | assert_allclose(pdf.doublecrystalball(11, 1, 5, 3, 4, 10, 2), 0.8824969025846)
92 | assert_allclose(pdf.doublecrystalball(20, 1, 5, 3, 4, 10, 2), 0.0000037266531720786)
93 | assert_allclose(
94 | pdf.doublecrystalball(25, 1, 5, 3, 4, 10, 2), 0.00000001287132228271
95 | )
96 |
97 |
98 | # cpdef double argus(double x, double c, double chi, double p)
99 | def test_argus():
100 | assert describe(pdf.argus) == ["x", "c", "chi", "p"]
101 | assert_allclose(pdf.argus(6.0, 10, 2, 3), 0.004373148605400128)
102 | assert_allclose(pdf.argus(10.0, 10, 2, 3), 0.0)
103 | assert_allclose(pdf.argus(8.0, 10, 2, 3), 0.0018167930603254737)
104 |
105 |
106 | # cpdef double cruijff(double x, double m_0, double sigma_L, double sigma_R, double alpha_L, double alpha_R)
107 | def test_cruijff():
108 | assert describe(pdf.cruijff) == [
109 | "x",
110 | "m_0",
111 | "sigma_L",
112 | "sigma_R",
113 | "alpha_L",
114 | "alpha_R",
115 | ]
116 | val = pdf.cruijff(0, 0, 1.0, 2.0, 1.0, 2.0)
117 | assert_allclose(val, 1.0)
118 | vl = pdf.cruijff(0, 1, 1.0, 1.0, 2.0, 2.0)
119 | vr = pdf.cruijff(2, 1, 1.0, 1.0, 2.0, 2.0)
120 | assert_allclose(vl, vr)
121 | assert_allclose(vl, 0.7788007830714)
122 | assert_allclose(vr, 0.7788007830714)
123 |
124 |
125 | # cpdef double linear(double x, double m, double c)
126 | def test_linear():
127 | assert describe(pdf.linear) == ["x", "m", "c"]
128 | assert_allclose(pdf.linear(1, 2, 3), 5)
129 | assert hasattr(pdf.linear, "integrate")
130 | integral = pdf.linear.integrate((0.0, 1.0), 1, 1, 1)
131 | assert_allclose(integral, 1.5)
132 |
133 |
134 | # cpdef double poly2(double x, double a, double b, double c)
135 | def test_poly2():
136 | assert describe(pdf.poly2) == ["x", "a", "b", "c"]
137 | assert_allclose(pdf.poly2(2, 3, 4, 5), 25)
138 |
139 |
140 | # cpdef double poly3(double x, double a, double b, double c, double d)
141 | def test_poly3():
142 | assert describe(pdf.poly3) == ["x", "a", "b", "c", "d"]
143 | assert_allclose(pdf.poly3(2, 3, 4, 5, 6), 56.0)
144 |
145 |
146 | def test_polynomial():
147 | p = pdf.Polynomial(1)
148 | assert describe(p) == ["x", "c_0", "c_1"]
149 | assert_allclose(p(2, 2, 1), 4)
150 | integral = p.integrate((0, 1), 1, 2, 1)
151 | assert_allclose(integral, 2.5)
152 |
153 | p = pdf.Polynomial(2)
154 | assert describe(p) == ["x", "c_0", "c_1", "c_2"]
155 | assert_allclose(p(2, 3, 4, 5), 31)
156 | integral = p.integrate((2, 10), 10, 1, 2, 3)
157 | analytical = 8 + 2 / 2.0 * (10 ** 2 - 2 ** 2) + 3 / 3.0 * (10 ** 3 - 2 ** 3)
158 | assert_allclose(integral, analytical)
159 |
160 |
161 | # cpdef double novosibirsk(double x, double width, double peak, double tail)
162 | def test_novosibirsk():
163 | assert describe(pdf.novosibirsk) == ["x", "width", "peak", "tail"]
164 | assert_allclose(pdf.novosibirsk(3, 2, 3, 4), 1.1253517471925912e-07)
165 |
166 |
167 | def test_rtv_breitwigner():
168 | assert describe(pdf.rtv_breitwigner) == ["x", "m", "gamma"]
169 | assert_allclose(pdf.rtv_breitwigner(1, 1, 1.0), 0.8194496535636714)
170 | assert_allclose(pdf.rtv_breitwigner(1, 1, 2.0), 0.5595531041435416)
171 | assert_allclose(pdf.rtv_breitwigner(1, 2, 3.0), 0.2585302502852219)
172 |
173 |
174 | def test_cauchy():
175 | assert describe(pdf.cauchy), ["x", "m", "gamma"]
176 | assert_allclose(pdf.cauchy(1, 1, 1.0), 0.3183098861837907)
177 | assert_allclose(pdf.cauchy(1, 1, 2.0), 0.15915494309189535)
178 | assert_allclose(pdf.cauchy(1, 2, 4.0), 0.07489644380795074)
179 |
180 |
181 | def test_johnsonSU():
182 | assert describe(pdf.johnsonSU), ["x", "mean", "sigma", "nu", "tau"]
183 | assert_allclose(pdf.johnsonSU(1.0, 1.0, 1.0, 1.0, 1.0), 0.5212726124342)
184 | assert_allclose(pdf.johnsonSU(1.0, 2.0, 1.0, 1.0, 1.0), 0.1100533373219)
185 | assert_allclose(pdf.johnsonSU(1.0, 2.0, 2.0, 1.0, 1.0), 0.4758433826682)
186 |
187 | j = pdf.johnsonSU
188 | assert hasattr(j, "integrate")
189 | integral = j.integrate((-100, 100), 0, 1.0, 1.0, 1.0, 1.0)
190 | assert_allclose(integral, 1.0)
191 | integral = j.integrate((0, 2), 0, 1.0, 1.0, 1.0, 1.0)
192 | assert_allclose(integral, 0.8837311663857358)
193 |
194 |
195 | def test_exponential():
196 | assert describe(pdf.exponential), ["x", "lambda"]
197 | assert_allclose(pdf.exponential(0.0, 1.0), 1.0)
198 | assert_allclose(pdf.exponential(0.0, 10.0), 10.0)
199 | assert_allclose(pdf.exponential(1.0, 1.0), exp(-1))
200 | assert_allclose(pdf.exponential(2.0, 1.0), exp(-2))
201 | assert_allclose(pdf.exponential(1.0, 2.0), 2 * exp(-2))
202 | assert_allclose(pdf.exponential(2.0, 2.0), 2 * exp(-4))
203 |
204 | j = pdf.exponential
205 | assert hasattr(j, "integrate")
206 | integral = j.integrate((-100, 100), 0, 1.0)
207 | assert_allclose(integral, 1.0)
208 | integral = j.integrate((0, 1), 0, 1)
209 | assert_allclose(integral, 1.0 - exp(-1))
210 | integral = j.integrate((1, 2), 0, 1)
211 | assert_allclose(integral, exp(-1) - exp(-2))
212 | integral = j.integrate((0, 1), 0, 2)
213 | assert_allclose(integral, 1.0 - exp(-2))
214 | integral = j.integrate((1, 2), 0, 2)
215 | assert_allclose(integral, exp(-2) - exp(-4))
216 |
217 |
218 | def test_HistogramPdf():
219 | be = np.array([0, 1, 3, 4], dtype=float)
220 | hy = np.array([10, 30, 50], dtype=float)
221 | norm = float((hy * np.diff(be)).sum())
222 | f = pdf.HistogramPdf(hy, be)
223 | assert_allclose(f(0.5), 10.0 / norm)
224 | assert_allclose(f(1.2), 30.0 / norm)
225 | assert_allclose(f(2.9), 30.0 / norm)
226 | assert_allclose(f(3.6), 50.0 / norm)
227 |
228 | assert hasattr(f, "integrate")
229 |
230 | integral = f.integrate((0, 4))
231 | assert_allclose(integral, 1.0)
232 | integral = f.integrate((0.5, 3.4))
233 | assert_allclose(integral, (10 * 0.5 + 30 * 2 + 50 * 0.4) / norm)
234 | integral = f.integrate((1.2, 4.5))
235 | assert_allclose(integral, (30 * 1.8 + 50 * 1) / norm)
236 |
237 |
238 | def test__vector_apply():
239 | def f(x, y):
240 | return x * x + y
241 |
242 | y = 10
243 | a = np.array([1.0, 2.0, 3.0])
244 | expected = [f(x, y) for x in a]
245 | va = _vector_apply(f, a, tuple([y]))
246 | assert_allclose(va, expected)
247 |
248 |
249 | def test_integrate1d():
250 | def f(x, y):
251 | return x * x + y
252 |
253 | def intf(x, y):
254 | return x * x * x / 3.0 + y * x
255 |
256 | bound = (-2.0, 1.0)
257 | y = 3.0
258 | integral = integrate1d(f, bound, 1000, tuple([y]))
259 | analytic = intf(bound[1], y) - intf(bound[0], y)
260 | assert_allclose(integral, analytic)
261 |
262 |
263 | def test_integrate1d_analytic():
264 | class temp:
265 | def __call__(self, x, m, c):
266 | return m * x ** 2 + c
267 |
268 | def integrate(self, bound, nint, m, c):
269 | a, b = bound
270 | return b - a # (wrong on purpose)
271 |
272 | bound = (0.0, 10.0)
273 | f = temp()
274 | integral = integrate1d(f, bound, 10, (2.0, 3.0))
275 | assert_allclose(integral, bound[1] - bound[0])
276 |
277 |
278 | def test_csum():
279 | x = np.array([1, 2, 3], dtype=np.double)
280 | s = csum(x)
281 | assert_allclose(s, 6.0)
282 |
283 |
284 | def test_xlogyx():
285 | def bad(x, y):
286 | return x * log(y / x)
287 |
288 | assert_allclose(xlogyx(1.0, 1.0), bad(1.0, 1.0))
289 | assert_allclose(xlogyx(1.0, 2.0), bad(1.0, 2.0))
290 | assert_allclose(xlogyx(1.0, 3.0), bad(1.0, 3.0))
291 | assert_allclose(xlogyx(0.0, 1.0), 0.0)
292 |
293 |
294 | def test_wlogyx():
295 | def bad(w, y, x):
296 | return w * log(y / x)
297 |
298 | assert_allclose(wlogyx(1.0, 1.0, 1.0), bad(1.0, 1.0, 1.0))
299 | assert_allclose(wlogyx(1.0, 2.0, 3.0), bad(1.0, 2.0, 3.0))
300 | assert_allclose(wlogyx(1e-50, 1e-20, 1.0), bad(1e-50, 1e-20, 1.0))
301 |
302 |
303 | def test_construct_arg():
304 | arg = (1, 2, 3, 4, 5, 6)
305 | pos = np.array([0, 2, 4], dtype=np.int)
306 | carg = construct_arg(arg, pos)
307 | assert carg == (1, 3, 5)
308 |
309 |
310 | def test_merge_func_code():
311 | funccode, [pf, pg, ph] = merge_func_code(f, g, h)
312 | assert funccode.co_varnames == ("x", "y", "z", "a", "b", "c", "d")
313 | assert tuple(pf) == (0, 1, 2)
314 | assert tuple(pg) == (0, 3, 4)
315 | assert tuple(ph) == (0, 5, 6)
316 |
317 |
318 | def test_merge_func_code_prefix():
319 | funccode, [pf, pg, ph] = merge_func_code(
320 | f, g, h, prefix=["f_", "g_", "h_"], skip_first=True
321 | )
322 | expected = "x", "f_y", "f_z", "g_a", "g_b", "h_c", "h_d"
323 | assert funccode.co_varnames == expected
324 | assert tuple(pf) == (0, 1, 2)
325 | assert tuple(pg) == (0, 3, 4)
326 | assert tuple(ph) == (0, 5, 6)
327 |
328 |
329 | def test_merge_func_code_factor_list():
330 | funccode, [pf, pg, pk_1, pk_2] = merge_func_code(
331 | f, g, prefix=["f_", "g_"], skip_first=True, factor_list=[k_1, k_2]
332 | )
333 | expected = "x", "f_y", "f_z", "g_a", "g_b", "g_i", "g_j"
334 | assert funccode.co_varnames == expected
335 |
336 | assert tuple(pf) == (0, 1, 2)
337 | assert tuple(pg) == (0, 3, 4)
338 | assert tuple(pk_1) == (1, 2)
339 | assert tuple(pk_2) == (5, 6)
340 |
341 |
342 | def test_merge_func_code_skip_prefix():
343 | funccode, _ = merge_func_code(
344 | f, f2, prefix=["f_", "g_"], skip_first=True, skip_prefix=["z"]
345 | )
346 | assert funccode.co_varnames == ("x", "f_y", "z", "g_a")
347 |
348 |
349 | def test_fast_tuple_equal():
350 | a = (1.0, 2.0, 3.0)
351 | b = (1.0, 2.0, 3.0)
352 | assert fast_tuple_equal(a, b, 0) is True
353 |
354 | a = (1.0, 4.0, 3.0)
355 | b = (1.0, 2.0, 3.0)
356 | assert fast_tuple_equal(a, b, 0) is False
357 |
358 | a = (4.0, 3.0)
359 | b = (1.0, 4.0, 3.0)
360 | assert fast_tuple_equal(a, b, 1) is True
361 |
362 | a = (4.0, 5.0)
363 | b = (1.0, 4.0, 3.0)
364 | assert fast_tuple_equal(a, b, 1) is False
365 |
366 | a = tuple([])
367 | b = tuple([])
368 | assert fast_tuple_equal(a, b, 0) is True
369 |
--------------------------------------------------------------------------------
/tests/test_functor.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import numpy as np
3 | from numpy.testing import assert_almost_equal, assert_equal
4 |
5 | from probfit import (
6 | AddPdf,
7 | AddPdfNorm,
8 | BlindFunc,
9 | Convolve,
10 | Extended,
11 | Normalized,
12 | describe,
13 | rename,
14 | )
15 | from probfit._libstat import integrate1d
16 | from probfit.decorator import extended, normalized
17 | from probfit.pdf import gaussian, ugaussian
18 |
19 |
20 | def test_describe_normal_function():
21 | def f(x, y, z):
22 | return x + y + z
23 |
24 | assert describe(f) == ["x", "y", "z"]
25 |
26 |
27 | def test_Normalized():
28 | f = ugaussian
29 | g = Normalized(f, (-1, 1))
30 |
31 | norm = integrate1d(f, (-1.0, 1.0), 1000, (0.0, 1.0))
32 | assert_almost_equal(g(1.0, 0.0, 1.0), f(1.0, 0.0, 1.0) / norm)
33 |
34 |
35 | def test_normalized_decorator():
36 | @normalized((-1, 1))
37 | def f(x, mean, sigma):
38 | return ugaussian(x, mean, sigma)
39 |
40 | g = Normalized(ugaussian, (-1, 1))
41 |
42 | assert describe(f) == ["x", "mean", "sigma"]
43 | assert_almost_equal(g(1, 0, 1), f(1, 0, 1))
44 |
45 |
46 | def test_Normalized_cache_hit():
47 | def f(x, y, z):
48 | return 1.0 * (x + y + z)
49 |
50 | def g(x, y, z):
51 | return 1.0 * (x + y + 2 * z)
52 |
53 | nf = Normalized(f, (-10.0, 10.0))
54 | ng = Normalized(g, (-10.0, 10.0))
55 | assert nf.hit == 0
56 | nf(1.0, 2.0, 3.0)
57 | ng(1.0, 2.0, 3.0)
58 | assert nf.hit == 0
59 | nf(3.0, 2.0, 3.0)
60 | assert nf.hit == 1
61 | ng(1.0, 2.0, 3.0)
62 | assert ng.hit == 1
63 |
64 |
65 | def test_add_pdf():
66 | def f(x, y, z):
67 | return x + y + z
68 |
69 | def g(x, a, b):
70 | return 2 * (x + a + b)
71 |
72 | def h(x, c, a):
73 | return 3 * (x + c + a)
74 |
75 | A = AddPdf(f, g, h)
76 | assert describe(A) == ["x", "y", "z", "a", "b", "c"]
77 |
78 | ret = A(1, 2, 3, 4, 5, 6, 7)
79 | expected = f(1, 2, 3) + g(1, 4, 5) + h(1, 6, 4)
80 | assert_almost_equal(ret, expected)
81 |
82 | # wrong integral on purpose
83 | f.integrate = lambda bound, nint, y, z: 1.0 # unbound method works too
84 | g.integrate = lambda bound, nint, a, b: 2.0
85 | h.integrate = lambda bound, nint, c, a: 3.0
86 |
87 | assert_equal(integrate1d(A, (-10.0, 10.0), 100, (1.0, 2.0, 3.0, 4.0, 5.0)), 6.0)
88 |
89 |
90 | def test_add_pdf_factor():
91 | def f(x, y, z):
92 | return x + y + z
93 |
94 | def g(x, a, b):
95 | return 2 * (x + a + b)
96 |
97 | def k1(n1, n2):
98 | return 3 * (n1 + n2)
99 |
100 | def k2(n1, y):
101 | return 4 * (n1 + y)
102 |
103 | A = AddPdf(f, g, prefix=["f", "g"], factors=[k1, k2])
104 | assert describe(A) == ["x", "fy", "fz", "ga", "gb", "fn1", "fn2", "gn1", "gy"]
105 |
106 | ret = A(1, 2, 3, 4, 5, 6, 7, 8, 9)
107 | expected = k1(6, 7) * f(1, 2, 3) + k2(8, 9) * g(1, 4, 5)
108 | assert_almost_equal(ret, expected)
109 |
110 | parts = A.eval_parts(1, 2, 3, 4, 5, 6, 7, 8, 9)
111 | assert_almost_equal(parts[0], k1(6, 7) * f(1, 2, 3))
112 | assert_almost_equal(parts[1], k2(8, 9) * g(1, 4, 5))
113 |
114 |
115 | def test_add_pdf_cache():
116 | def f(x, y, z):
117 | return x + y + z
118 |
119 | def g(x, a, b):
120 | return 2 * (x + a + b)
121 |
122 | def h(x, c, a):
123 | return 3 * (x + c + a)
124 |
125 | A = AddPdf(f, g, h)
126 | assert describe(A) == ["x", "y", "z", "a", "b", "c"]
127 |
128 | ret = A(1, 2, 3, 4, 5, 6, 7)
129 | assert_equal(A.hit, 0)
130 | expected = f(1, 2, 3) + g(1, 4, 5) + h(1, 6, 4)
131 | assert_almost_equal(ret, expected)
132 |
133 | ret = A(1, 2, 3, 6, 7, 8, 9)
134 | assert_equal(A.hit, 1)
135 | expected = f(1, 2, 3) + g(1, 6, 7) + h(1, 8, 6)
136 | assert_almost_equal(ret, expected)
137 |
138 |
139 | def test_extended():
140 | def f(x, y, z):
141 | return x + 2 * y + 3 * z
142 |
143 | g = Extended(f)
144 | assert describe(g) == ["x", "y", "z", "N"]
145 | assert_equal(g(1, 2, 3, 4), 4 * (f(1, 2, 3)))
146 |
147 | # extended should use analytical when available
148 | def ana_int(x, y):
149 | return y * x ** 2
150 |
151 | ana_int_int = lambda b, n, y: 999.0 # wrong on purpose
152 | ana_int.integrate = ana_int_int
153 | g = Extended(ana_int)
154 | assert_almost_equal(g.integrate((0, 1), 100, 5.0, 2.0), 999.0 * 2.0)
155 |
156 | # and not fail when it's not available
157 | def no_ana_int(x, y):
158 | return y * x ** 2
159 |
160 | g = Extended(no_ana_int)
161 | assert_almost_equal(
162 | g.integrate((0, 1), 100, 5.0, 2.0), (1.0 ** 3) / 3.0 * 5.0 * 2.0
163 | )
164 |
165 |
166 | def test_extended_decorator():
167 | def f(x, y, z):
168 | return x + 2 * y + 3 * z
169 |
170 | @extended()
171 | def g(x, y, z):
172 | return x + 2 * y + 3 * z
173 |
174 | assert describe(g) == ["x", "y", "z", "N"]
175 | assert_equal(g(1, 2, 3, 4), 4 * (f(1, 2, 3)))
176 |
177 |
178 | def test_addpdfnorm():
179 | def f(x, y, z):
180 | return x + 2 * y + 3 * z
181 |
182 | def g(x, z, p):
183 | return 4 * x + 5 * z + 6 * z
184 |
185 | def p(x, y, q):
186 | return 7 * x + 8 * y + 9 * q
187 |
188 | h = AddPdfNorm(f, g)
189 | assert describe(h) == ["x", "y", "z", "p", "f_0"]
190 |
191 | q = AddPdfNorm(f, g, p)
192 | assert describe(q) == ["x", "y", "z", "p", "q", "f_0", "f_1"]
193 |
194 | assert_almost_equal(h(1, 2, 3, 4, 0.1), 0.1 * f(1, 2, 3) + 0.9 * g(1, 3, 4))
195 |
196 | assert_almost_equal(
197 | q(1, 2, 3, 4, 5, 0.1, 0.2),
198 | 0.1 * f(1, 2, 3) + 0.2 * g(1, 3, 4) + 0.7 * p(1, 2, 5),
199 | )
200 |
201 |
202 | def test_addpdfnorm_analytical_integrate():
203 | def f(x, y, z):
204 | return x + 2 * y + 3 * z
205 |
206 | def g(x, z, p):
207 | return 4 * x + 5 * z + 6 * z
208 |
209 | def p(x, y, q):
210 | return 7 * x + 8 * y + 9 * q
211 |
212 | f.integrate = lambda bound, nint, y, z: 1.0
213 | g.integrate = lambda bound, nint, z, p: 2.0
214 | p.integrate = lambda bound, nint, y, q: 3.0
215 |
216 | q = AddPdfNorm(f, g, p)
217 | assert describe(q) == ["x", "y", "z", "p", "q", "f_0", "f_1"]
218 |
219 | integral = integrate1d(q, (-10.0, 10.0), 100, (1.0, 2.0, 3.0, 4.0, 0.1, 0.2))
220 | assert_almost_equal(integral, 0.1 * 1.0 + 0.2 * 2.0 + 0.7 * 3.0)
221 |
222 |
223 | def test_convolution():
224 | f = gaussian
225 | g = lambda x, mu1, sigma1: gaussian(x, mu1, sigma1)
226 |
227 | h = Convolve(f, g, (-10, 10), nbins=10000)
228 | assert describe(h) == ["x", "mean", "sigma", "mu1", "sigma1"]
229 |
230 | assert_almost_equal(h(1, 0, 1, 1, 2), 0.17839457037411527) # center
231 | assert_almost_equal(h(-1, 0, 1, 1, 2), 0.119581456625684) # left
232 | assert_almost_equal(h(0, 0, 1, 1, 2), 0.1614180824489487) # left
233 | assert_almost_equal(h(2, 0, 1, 1, 2), 0.1614180824489487) # right
234 | assert_almost_equal(h(3, 0, 1, 1, 2), 0.119581456625684) # right
235 |
236 |
237 | def test_rename():
238 | def f(x, y, z):
239 | return None
240 |
241 | assert describe(f) == ["x", "y", "z"]
242 | g = rename(f, ["x", "a", "b"])
243 | assert describe(g) == ["x", "a", "b"]
244 |
245 |
246 | def test_blindfunc():
247 | np.random.seed(0)
248 | f = BlindFunc(gaussian, "mean", "abcd", width=1.5, signflip=True)
249 | arg = f.__shift_arg__((1, 1, 1))
250 | totest = [1.0, -2.1741271445170067, 1.0]
251 | assert_almost_equal(arg[0], totest[0])
252 | assert_almost_equal(arg[1], totest[1])
253 | assert_almost_equal(arg[2], totest[2])
254 | assert_almost_equal(f.__call__(0.5, 1.0, 1.0), 0.011171196819867517)
255 | np.random.seed(575345)
256 | f = BlindFunc(gaussian, "mean", "abcd", width=1.5, signflip=True)
257 | arg = f.__shift_arg__((1, 1, 1))
258 | assert_almost_equal(arg[0], totest[0])
259 | assert_almost_equal(arg[1], totest[1])
260 | assert_almost_equal(arg[2], totest[2])
261 | assert_almost_equal(f.__call__(0.5, 1.0, 1.0), 0.011171196819867517)
262 |
--------------------------------------------------------------------------------
/tests/test_oneshot.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import warnings
3 | from math import sqrt
4 |
5 | import numpy as np
6 | from iminuit import Minuit
7 | from iminuit.iminuit_warnings import InitialParamWarning
8 | from numpy.testing import assert_allclose
9 |
10 | from probfit import Extended, Normalized, UnbinnedLH
11 | from probfit.oneshot import fit_binlh, fit_binx2, fit_uml
12 | from probfit.pdf import gaussian
13 |
14 |
15 | class TestOneshot:
16 | def setup(self):
17 | self.ndata = 20000
18 | warnings.simplefilter("ignore", InitialParamWarning)
19 | np.random.seed(0)
20 | self.data = np.random.randn(self.ndata) * 2.0 + 5.0
21 | self.wdown = np.empty(self.ndata)
22 | self.wdown.fill(0.1)
23 |
24 | self.ndata_small = 2000
25 | self.data_small = np.random.randn(self.ndata_small) * 2.0 + 5.0
26 |
27 | def test_binx2(self):
28 | egauss = Extended(gaussian)
29 | _, minuit = fit_binx2(
30 | egauss,
31 | self.data,
32 | bins=100,
33 | bound=(1.0, 9.0),
34 | quiet=True,
35 | mean=4.0,
36 | sigma=1.0,
37 | N=10000.0,
38 | print_level=0,
39 | )
40 | assert minuit.migrad_ok()
41 | assert_allclose(minuit.values["mean"], 5.0, atol=3 * minuit.errors["mean"])
42 | assert_allclose(minuit.values["sigma"], 2.0, atol=3 * minuit.errors["sigma"])
43 |
44 | def test_binlh(self):
45 | ngauss = Normalized(gaussian, (1.0, 9.0))
46 | _, minuit = fit_binlh(
47 | ngauss,
48 | self.data,
49 | bins=100,
50 | bound=(1.0, 9.0),
51 | quiet=True,
52 | mean=4.0,
53 | sigma=1.5,
54 | print_level=0,
55 | )
56 | assert minuit.migrad_ok()
57 | assert_allclose(minuit.values["mean"], 5.0, atol=3 * minuit.errors["mean"])
58 | assert_allclose(minuit.values["sigma"], 2.0, atol=3 * minuit.errors["sigma"])
59 |
60 | def test_extended_binlh(self):
61 | egauss = Extended(gaussian)
62 | _, minuit = fit_binlh(
63 | egauss,
64 | self.data,
65 | bins=100,
66 | bound=(1.0, 9.0),
67 | quiet=True,
68 | mean=4.0,
69 | sigma=1.0,
70 | N=10000.0,
71 | print_level=0,
72 | extended=True,
73 | )
74 | assert minuit.migrad_ok()
75 | assert_allclose(minuit.values["mean"], 5.0, atol=3 * minuit.errors["mean"])
76 | assert_allclose(minuit.values["sigma"], 2.0, atol=3 * minuit.errors["sigma"])
77 | assert_allclose(minuit.values["N"], 20000, atol=3 * minuit.errors["N"])
78 |
79 | def test_extended_binlh_ww(self):
80 | egauss = Extended(gaussian)
81 | _, minuit = fit_binlh(
82 | egauss,
83 | self.data,
84 | bins=100,
85 | bound=(1.0, 9.0),
86 | quiet=True,
87 | mean=4.0,
88 | sigma=1.0,
89 | N=1000.0,
90 | weights=self.wdown,
91 | print_level=0,
92 | extended=True,
93 | )
94 | assert minuit.migrad_ok()
95 | assert_allclose(minuit.values["mean"], 5.0, atol=minuit.errors["mean"])
96 | assert_allclose(minuit.values["sigma"], 2.0, atol=minuit.errors["sigma"])
97 | assert_allclose(minuit.values["N"], 2000, atol=minuit.errors["N"])
98 |
99 | def test_extended_binlh_ww_w2(self):
100 | egauss = Extended(gaussian)
101 | _, minuit = fit_binlh(
102 | egauss,
103 | self.data,
104 | bins=100,
105 | bound=(1.0, 9.0),
106 | quiet=True,
107 | mean=4.0,
108 | sigma=1.0,
109 | N=1000.0,
110 | weights=self.wdown,
111 | print_level=0,
112 | extended=True,
113 | )
114 | assert_allclose(minuit.values["mean"], 5.0, atol=minuit.errors["mean"])
115 | assert_allclose(minuit.values["sigma"], 2.0, atol=minuit.errors["sigma"])
116 | assert_allclose(minuit.values["N"], 2000, atol=minuit.errors["N"])
117 | assert minuit.migrad_ok()
118 |
119 | _, minuit2 = fit_binlh(
120 | egauss,
121 | self.data,
122 | bins=100,
123 | bound=(1.0, 9.0),
124 | quiet=True,
125 | mean=4.0,
126 | sigma=1.0,
127 | N=1000.0,
128 | weights=self.wdown,
129 | print_level=-1,
130 | extended=True,
131 | use_w2=True,
132 | )
133 | assert_allclose(minuit2.values["mean"], 5.0, atol=2 * minuit2.errors["mean"])
134 | assert_allclose(minuit2.values["sigma"], 2.0, atol=2 * minuit2.errors["sigma"])
135 | assert_allclose(minuit2.values["N"], 2000.0, atol=2 * minuit2.errors["N"])
136 | assert minuit2.migrad_ok()
137 |
138 | minuit.minos()
139 | minuit2.minos()
140 |
141 | # now error should scale correctly
142 | assert_allclose(
143 | minuit.errors["mean"] / sqrt(10),
144 | minuit2.errors["mean"],
145 | atol=minuit.errors["mean"] / sqrt(10) / 100.0,
146 | )
147 | assert_allclose(
148 | minuit.errors["sigma"] / sqrt(10),
149 | minuit2.errors["sigma"],
150 | atol=minuit.errors["sigma"] / sqrt(10) / 100.0,
151 | )
152 | assert_allclose(
153 | minuit.errors["N"] / sqrt(10),
154 | minuit2.errors["N"],
155 | atol=minuit.errors["N"] / sqrt(10) / 100.0,
156 | )
157 |
158 | def test_uml(self):
159 | _, minuit = fit_uml(
160 | gaussian, self.data, quiet=True, mean=4.5, sigma=1.5, print_level=0
161 | )
162 | assert minuit.migrad_ok()
163 | assert_allclose(minuit.values["mean"], 5.0, atol=3 * minuit.errors["mean"])
164 | assert_allclose(minuit.values["sigma"], 2.0, atol=3 * minuit.errors["sigma"])
165 |
166 | def test_extended_ulh(self):
167 | eg = Extended(gaussian)
168 | lh = UnbinnedLH(eg, self.data, extended=True, extended_bound=(-20, 20))
169 | minuit = Minuit(
170 | lh, mean=4.5, sigma=1.5, N=19000.0, pedantic=False, print_level=0
171 | )
172 | minuit.migrad()
173 | assert_allclose(minuit.values["N"], 20000, atol=sqrt(20000.0))
174 | assert minuit.migrad_ok()
175 |
176 | def test_extended_ulh_2(self):
177 | eg = Extended(gaussian)
178 | lh = UnbinnedLH(eg, self.data, extended=True)
179 | minuit = Minuit(
180 | lh, mean=4.5, sigma=1.5, N=19000.0, pedantic=False, print_level=0
181 | )
182 | minuit.migrad()
183 | assert minuit.migrad_ok()
184 | assert_allclose(minuit.values["N"], 20000, atol=sqrt(20000.0))
185 |
--------------------------------------------------------------------------------
/tests/test_plotting.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """Test that output figures look sensible.
3 |
4 | These tests use pytest-mpl [1] to compare the figures created in the test_*
5 | methods with those in the baseline/ directory (relative to this file).
6 | To generate the baseline figures, run:
7 |
8 | py.test --mpl-generate-path=baseline tests/_test_plotting.py
9 |
10 | This will put the figures in the baseline/ directory relative to where the
11 | tests were run. You can then copy the ones that you want to update into the
12 | tests/baseline directory.
13 |
14 | [1]: https://pypi.python.org/pypi/pytest-mpl
15 | """
16 | import sys
17 |
18 | import numpy as np
19 | import pytest
20 | from iminuit import Minuit
21 | from matplotlib import pyplot as plt
22 |
23 | from probfit.costfunc import (
24 | BinnedChi2,
25 | BinnedLH,
26 | Chi2Regression,
27 | SimultaneousFit,
28 | UnbinnedLH,
29 | )
30 | from probfit.functor import AddPdf, AddPdfNorm, Extended
31 | from probfit.funcutil import rename
32 | from probfit.pdf import gaussian, linear
33 | from probfit.plotting import draw_compare_hist, draw_pdf
34 |
35 | major, minor = sys.version_info[0:2]
36 | if major >= 3 and minor >= 5:
37 | special_tol = 60.0
38 | else:
39 | special_tol = 2.0
40 |
41 |
42 | def image_comparison(filename, **kwargs):
43 | """Decorator to provide a new Figure instance and return it.
44 |
45 | This allows the mpl_image_compare wrapper to be used seamlessly: methods
46 | wrapped in this decorator can just draw with `plt.whatever`, and then
47 | mpl_image_compare with use plt.gcf (the global Figure instance) to compare
48 | to the baseline.
49 | """
50 |
51 | def wrapper(func):
52 | def wrapped():
53 | fig = plt.figure()
54 | func()
55 | return fig
56 |
57 | return pytest.mark.mpl_image_compare(filename=filename, **kwargs)(wrapped)
58 |
59 | return wrapper
60 |
61 |
62 | @image_comparison("draw_pdf.png")
63 | def test_draw_pdf():
64 | f = gaussian
65 | draw_pdf(f, {"mean": 1.0, "sigma": 2.0}, bound=(-10, 10))
66 |
67 |
68 | @image_comparison("draw_pdf_linear.png")
69 | def test_draw_pdf_linear():
70 | f = linear
71 | draw_pdf(f, {"m": 1.0, "c": 2.0}, bound=(-10, 10))
72 |
73 |
74 | # There is a slight difference in the x-axis tick label positioning for this
75 | # plot between Python 2 and 3, it's not important here so increase the RMS
76 | # slightly such that it's ignored
77 | @image_comparison("draw_compare_hist_gaussian.png", tolerance=2.05)
78 | def test_draw_compare_hist():
79 | np.random.seed(0)
80 | data = np.random.randn(10000)
81 | f = gaussian
82 | draw_compare_hist(f, {"mean": 0.0, "sigma": 1.0}, data, normed=True)
83 |
84 |
85 | # There is a slight difference in the x-axis tick label positioning for this
86 | # plot between Python 2 and 3, it's not important here so increase the RMS
87 | # slightly such that it's ignored
88 | @image_comparison("draw_compare_hist_no_norm.png", tolerance=2.05)
89 | def test_draw_compare_hist_no_norm():
90 | np.random.seed(0)
91 | data = np.random.randn(10000)
92 | f = Extended(gaussian)
93 | draw_compare_hist(f, {"mean": 0.0, "sigma": 1.0, "N": 10000}, data, normed=False)
94 |
95 |
96 | @image_comparison("draw_ulh.png")
97 | def test_draw_ulh():
98 | np.random.seed(0)
99 | data = np.random.randn(1000)
100 | ulh = UnbinnedLH(gaussian, data)
101 | ulh.draw(args=(0.0, 1.0))
102 |
103 |
104 | @image_comparison("draw_ulh_extend.png")
105 | def test_draw_ulh_extend():
106 | np.random.seed(0)
107 | data = np.random.randn(1000)
108 | ulh = UnbinnedLH(Extended(gaussian), data, extended=True)
109 | ulh.draw(args=(0.0, 1.0, 1000))
110 |
111 |
112 | @image_comparison("draw_residual_ulh.png")
113 | def test_draw_residual_ulh():
114 | np.random.seed(0)
115 | data = np.random.randn(1000)
116 | ulh = UnbinnedLH(gaussian, data)
117 | ulh.draw_residual(args=(0.0, 1.0))
118 |
119 |
120 | @image_comparison("draw_residual_ulh_norm.png")
121 | def test_draw_residual_ulh_norm():
122 | np.random.seed(0)
123 | data = np.random.randn(1000)
124 | ulh = UnbinnedLH(gaussian, data)
125 | ulh.draw_residual(args=(0.0, 1.0), norm=True)
126 | plt.ylim(-7.0, 3.0)
127 | plt.xlim(-4.0, 3.0)
128 |
129 |
130 | @image_comparison("draw_residual_ulh_norm_no_errbars.png", tolerance=special_tol)
131 | def test_draw_residual_ulh_norm_no_errbars():
132 | np.random.seed(0)
133 | data = np.random.randn(1000)
134 | ulh = UnbinnedLH(gaussian, data)
135 | ulh.draw_residual(args=(0.0, 1.0), norm=True, show_errbars=False)
136 |
137 |
138 | @image_comparison("draw_residual_ulh_norm_options.png")
139 | def test_draw_residual_ulh_norm_options():
140 | np.random.seed(0)
141 | data = np.random.randn(1000)
142 | ulh = UnbinnedLH(gaussian, data)
143 | ulh.draw_residual(
144 | args=(0.0, 1.0),
145 | norm=True,
146 | color="green",
147 | capsize=2,
148 | grid=False,
149 | zero_line=False,
150 | )
151 |
152 |
153 | @image_comparison("draw_ulh_extend_residual_norm.png")
154 | def test_draw_ulh_extend_residual_norm():
155 | np.random.seed(0)
156 | data = np.random.randn(1000)
157 | ulh = UnbinnedLH(Extended(gaussian), data, extended=True)
158 | ulh.draw_residual(args=(0.0, 1.0, 1000), norm=True)
159 | plt.ylim(-7.0, 3.0)
160 |
161 |
162 | @image_comparison("draw_ulh_with_minuit.png")
163 | def test_draw_ulh_with_minuit():
164 | np.random.seed(0)
165 | data = np.random.randn(1000)
166 | ulh = UnbinnedLH(gaussian, data)
167 | minuit = Minuit(ulh, mean=0, sigma=1)
168 | ulh.draw(minuit)
169 |
170 |
171 | @image_comparison("draw_blh.png")
172 | def test_draw_blh():
173 | np.random.seed(0)
174 | data = np.random.randn(1000)
175 | blh = BinnedLH(gaussian, data)
176 | blh.draw(args=(0.0, 1.0))
177 |
178 |
179 | @image_comparison("draw_blh_extend.png")
180 | def test_draw_blh_extend():
181 | np.random.seed(0)
182 | data = np.random.randn(1000)
183 | blh = BinnedLH(Extended(gaussian), data, extended=True)
184 | blh.draw(args=(0.0, 1.0, 1000))
185 |
186 |
187 | @image_comparison("draw_residual_blh.png")
188 | def test_draw_residual_blh():
189 | np.random.seed(0)
190 | data = np.random.randn(1000)
191 | blh = BinnedLH(gaussian, data)
192 | blh.draw_residual(args=(0.0, 1.0))
193 |
194 |
195 | @image_comparison("draw_residual_blh_norm.png")
196 | def test_draw_residual_blh_norm():
197 | np.random.seed(0)
198 | data = np.random.randn(1000)
199 | blh = BinnedLH(gaussian, data)
200 | blh.draw_residual(args=(0.0, 1.0), norm=True)
201 | plt.ylim(-4.0, 3.0)
202 | plt.xlim(-4.0, 3.0)
203 |
204 |
205 | @image_comparison("draw_residual_blh_norm_options.png")
206 | def test_draw_residual_blh_norm_options():
207 | np.random.seed(0)
208 | data = np.random.randn(1000)
209 | blh = BinnedLH(gaussian, data)
210 | blh.draw_residual(
211 | args=(0.0, 1.0),
212 | norm=True,
213 | color="green",
214 | capsize=2,
215 | grid=False,
216 | zero_line=False,
217 | )
218 |
219 |
220 | @image_comparison("draw_residual_blh_norm_no_errbars.png", tolerance=special_tol)
221 | def test_draw_residual_blh_norm_no_errbars():
222 | np.random.seed(0)
223 | data = np.random.randn(1000)
224 | blh = BinnedLH(gaussian, data)
225 | blh.draw_residual(args=(0.0, 1.0), norm=True, show_errbars=False)
226 |
227 |
228 | @image_comparison("draw_blh_extend_residual_norm.png")
229 | def test_draw_blh_extend_residual_norm():
230 | np.random.seed(0)
231 | data = np.random.randn(1000)
232 | blh = BinnedLH(Extended(gaussian), data, extended=True)
233 | blh.draw_residual(args=(0.0, 1.0, 1000), norm=True)
234 |
235 |
236 | @image_comparison("draw_bx2.png")
237 | def test_draw_bx2():
238 | np.random.seed(0)
239 | data = np.random.randn(1000)
240 | blh = BinnedChi2(Extended(gaussian), data)
241 | blh.draw(args=(0.0, 1.0, 1000))
242 |
243 |
244 | @image_comparison("draw_x2reg.png")
245 | def test_draw_x2reg():
246 | np.random.seed(0)
247 | x = np.linspace(0, 1, 100)
248 | y = 10.0 * x + np.random.randn(100)
249 | err = np.array([1] * 100)
250 | blh = Chi2Regression(linear, x, y, err)
251 | blh.draw(args=(10.0, 0.0))
252 |
253 |
254 | @image_comparison("draw_ulh_with_parts.png")
255 | def test_ulh_with_parts():
256 | np.random.seed(0)
257 | data = np.random.randn(10000)
258 | shifted = data + 3.0
259 | data = np.append(data, [shifted])
260 | g1 = rename(gaussian, ["x", "lmu", "lsigma"])
261 | g2 = rename(gaussian, ["x", "rmu", "rsigma"])
262 | allpdf = AddPdfNorm(g1, g2)
263 | ulh = UnbinnedLH(allpdf, data)
264 | ulh.draw(args=(0, 1, 3, 1, 0.5), parts=True)
265 |
266 |
267 | @image_comparison("draw_blh_with_parts.png")
268 | def test_blh_with_parts():
269 | np.random.seed(0)
270 | data = np.random.randn(10000)
271 | shifted = data + 3.0
272 | data = np.append(data, [shifted])
273 | g1 = rename(gaussian, ["x", "lmu", "lsigma"])
274 | g2 = rename(gaussian, ["x", "rmu", "rsigma"])
275 | allpdf = AddPdfNorm(g1, g2)
276 | blh = BinnedLH(allpdf, data)
277 | blh.draw(args=(0, 1, 3, 1, 0.5), parts=True)
278 |
279 |
280 | @image_comparison("draw_bx2_with_parts.png")
281 | def test_bx2_with_parts():
282 | np.random.seed(0)
283 | data = np.random.randn(10000)
284 | shifted = data + 3.0
285 | data = np.append(data, [shifted])
286 | g1 = Extended(rename(gaussian, ["x", "lmu", "lsigma"]), extname="N1")
287 | g2 = Extended(rename(gaussian, ["x", "rmu", "rsigma"]), extname="N2")
288 | allpdf = AddPdf(g1, g2)
289 | bx2 = BinnedChi2(allpdf, data)
290 | bx2.draw(args=(0, 1, 10000, 3, 1, 10000), parts=True)
291 |
292 |
293 | @image_comparison("draw_simultaneous.png")
294 | def test_draw_simultaneous():
295 | np.random.seed(0)
296 | data = np.random.randn(10000)
297 | shifted = data + 3.0
298 | g1 = rename(gaussian, ["x", "lmu", "sigma"])
299 | g2 = rename(gaussian, ["x", "rmu", "sigma"])
300 | ulh1 = UnbinnedLH(g1, data)
301 | ulh2 = UnbinnedLH(g2, shifted)
302 | sim = SimultaneousFit(ulh1, ulh2)
303 | sim.draw(args=(0, 1, 3))
304 |
305 |
306 | @image_comparison("draw_simultaneous_prefix.png")
307 | def test_draw_simultaneous_prefix():
308 | np.random.seed(0)
309 | data = np.random.randn(10000)
310 | shifted = data + 3.0
311 | g1 = rename(gaussian, ["x", "lmu", "sigma"])
312 | g2 = rename(gaussian, ["x", "rmu", "sigma"])
313 | ulh1 = UnbinnedLH(g1, data)
314 | ulh2 = UnbinnedLH(g2, shifted)
315 | sim = SimultaneousFit(ulh1, ulh2, prefix=["g1_", "g2_"])
316 | minuit = Minuit(
317 | sim, g1_lmu=0.0, g1_sigma=1.0, g2_rmu=0.0, g2_sigma=1.0, print_level=0
318 | )
319 | minuit.migrad()
320 | sim.draw(minuit)
321 |
--------------------------------------------------------------------------------
/tests/test_toy.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import numpy as np
3 |
4 | from probfit._libstat import compute_chi2
5 | from probfit.costfunc import BinnedLH
6 | from probfit.functor import Normalized
7 | from probfit.nputil import mid, vector_apply
8 | from probfit.pdf import crystalball, gaussian
9 | from probfit.toy import gen_toy
10 |
11 |
12 | def test_gen_toy():
13 | np.random.seed(0)
14 | bound = (-1, 2)
15 | ntoy = 100000
16 | toy = gen_toy(
17 | crystalball,
18 | ntoy,
19 | bound=bound,
20 | alpha=1.0,
21 | n=2.0,
22 | mean=1.0,
23 | sigma=0.3,
24 | quiet=False,
25 | )
26 | assert len(toy) == ntoy
27 |
28 | htoy, bins = np.histogram(toy, bins=1000, range=bound)
29 |
30 | ncball = Normalized(crystalball, bound)
31 |
32 | f = lambda x: ncball(x, 1.0, 2.0, 1.0, 0.3)
33 |
34 | expected = vector_apply(f, mid(bins)) * ntoy * (bins[1] - bins[0])
35 |
36 | htoy = htoy * 1.0
37 | err = np.sqrt(expected)
38 |
39 | chi2 = compute_chi2(htoy, expected, err)
40 |
41 | print(chi2, len(bins), chi2 / len(bins))
42 |
43 | assert 0.9 < (chi2 / len(bins)) < 1.1
44 |
45 |
46 | def test_gen_toy2():
47 | pdf = gaussian
48 | np.random.seed(0)
49 | toy = gen_toy(pdf, 10000, (-5, 5), mean=0, sigma=1)
50 | binlh = BinnedLH(pdf, toy, bound=(-5, 5), bins=100)
51 | lh = binlh(0.0, 1.0)
52 | for x in toy:
53 | assert x < 5
54 | assert x >= -5
55 | assert len(toy) == 10000
56 | assert lh / 100.0 < 1.0
57 |
--------------------------------------------------------------------------------
/tests/test_util.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from probfit.util import describe, parse_arg
3 |
4 |
5 | class Func_Code:
6 | def __init__(self, varname):
7 | self.co_varnames = varname
8 | self.co_argcount = len(varname)
9 |
10 |
11 | class Func1:
12 | def __init__(self):
13 | pass
14 |
15 | def __call__(self, x, y):
16 | return (x - 2.0) ** 2 + (y - 5.0) ** 2 + 10
17 |
18 |
19 | class Func2:
20 | def __init__(self):
21 | self.func_code = Func_Code(["x", "y"])
22 |
23 | def __call__(self, *arg):
24 | return (arg[0] - 2.0) ** 2 + (arg[1] - 5.0) ** 2 + 10
25 |
26 |
27 | def func3(x, y):
28 | return 0.2 * (x - 2.0) ** 2 + (y - 5.0) ** 2 + 10
29 |
30 |
31 | def func4(x, y, z):
32 | return 0.2 * (x - 2.0) ** 2 + 0.1 * (y - 5.0) ** 2 + 0.25 * (z - 7.0) ** 2 + 10
33 |
34 |
35 | class TestUtil:
36 | def setup(self):
37 | self.f1 = Func1()
38 | self.f2 = Func2()
39 | self.f3 = func3
40 |
41 | def test_parse_arg(self):
42 | td = {"x": 1, "y": 2}
43 | ts = parse_arg(self.f1, td)
44 | assert ts == (1, 2)
45 |
46 | def test_describe(self):
47 | assert describe(self.f1) == ["x", "y"]
48 | assert describe(self.f2) == ["x", "y"]
49 | assert describe(self.f3) == ["x", "y"]
50 |
--------------------------------------------------------------------------------
/tutorial/tutorial.ipy:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # 3.0
3 |
4 | #
5 |
6 | # probfit Basic Tutorial
7 |
8 | #
9 |
10 | # [probfit](http://iminuit.github.io/probfit/) is a modeling / fitting package to be used together with [iminuit](http://iminuit.github.com/iminuit/).
11 | #
12 | # This tutorial is a fast-paced introduction to the probfit features:
13 | #
14 | # * built-in common models: polynomial, gaussian, ...
15 | # * build-in common fit statistics: chi^2, binned and unbinned likelihood
16 | # * tools to get your fits to converge and check the results: try_uml, draw, draw_residuals, ...
17 | # * tools to help you implement your own models and fit statistics: Normalize, Extended, integrate_1d, ...
18 | #
19 | # Please start this notebook with the ``ipython --pylab=inline`` option to get inline plots.
20 |
21 | #
22 |
23 | import iminuit
24 | import matplotlib.pyplot as plt
25 | # We assume you have executed this cell in all the following examples
26 | import numpy as np
27 |
28 | import probfit
29 |
30 | #
31 |
32 | # In your own code you can explicitly import what you need to save
33 | # typing in interactive sessions, e.g.
34 | #
35 | # from iminuit import Minuit, describe
36 | # from probfit import gaussian, BinnedLH
37 | #
38 | # We don't do this here, we only import `iminuit` and `probfit` into our
39 | # namespace so that it is clear to you which functions and classes come
40 | # from which package while reading the code below.
41 |
42 | #
43 |
44 | # ## Chi^2 straight line fit
45 | #
46 | # We can't really call this a fitting package without being able to fit a straight line, right?
47 |
48 | #
49 |
50 | # Let's make a straight line with gaussian(mu=0, sigma=1) noise
51 | np.random.seed(0)
52 | x = np.linspace(0, 10, 20)
53 | y = 3 * x + 15 + np.random.randn(len(x))
54 | err = np.ones(len(x))
55 | plt.errorbar(x, y, err, fmt='.');
56 |
57 | #
58 |
59 | # Let's define our line.
60 | # First argument has to be the independent variable,
61 | # arguments after that are shape parameters.
62 | def line(x, m, c): # define it to be parabolic or whatever you like
63 | return m * x + c
64 |
65 | #
66 |
67 | iminuit.describe(line)
68 |
69 | #
70 |
71 | # Define a chi^2 cost function
72 | chi2 = probfit.Chi2Regression(line, x, y, err)
73 |
74 | #
75 |
76 | # Chi2Regression is just a callable object; nothing special about it
77 | iminuit.describe(chi2)
78 |
79 | #
80 |
81 | # minimize it
82 | # yes, it gives you a heads up that you didn't give it initial value
83 | # we can ignore it for now
84 | minuit = iminuit.Minuit(chi2) # see iminuit tutorial on how to give initial value/range/error
85 | minuit.migrad(); # MIGRAD is a very stable robust minimization method
86 | # you can look at your terminal to see what it is doing;
87 |
88 | #
89 |
90 | # The output above is a pretty-printed summary of the fit results from
91 | # minuit.print_fmin()
92 | # which was automatically called by iminuit.Minuit.migrad() after running MIGRAD.
93 |
94 | # Let's see our results as Python dictionaries ...
95 | print(minuit.values)
96 | print(minuit.errors)
97 |
98 | #
99 |
100 | # #### Parabolic error
101 | # is calculated using the second derivative at the minimum
102 | # This is good in most cases where the uncertainty is symmetric not much correlation
103 | # exists. Migrad usually got this accurately but if you want ot be sure
104 | # call `minuit.hesse()` after calling `minuit.migrad()`.
105 | #
106 | # #### Minos Error
107 | # is obtained by scanning the chi^2 or likelihood profile and find the point
108 | # where chi^2 is increased by `minuit.errordef`. Note that in the Minuit documentation
109 | # and output `errordef` is often called `up` ... it's the same thing.
110 | #
111 | # #### What `errordef` should I use?
112 | #
113 | # As explained in the Minuit documentation you should use:
114 | #
115 | # * `errordef = 1` for chi^2 fits
116 | # * `errordef = 0.5` for likelihood fits
117 | #
118 | # `errordef=1` is the default, so you only have to set it to `errordef=0.5`
119 | # if you are defining a likelihood cost function (if you don't your HESSE and MINOS errors will be incorrect).
120 | # `probfit` helps you by defining a `default_errordef()` attribute on the
121 | # cost function classes, which is automatically detected by the `Minuit` constructor
122 | # and can be used to set `Minuit.errordef` correctly, so that users can't forget.
123 | # Classes used in this tutorial:
124 | #
125 | # * `probfit.Chi2Regression.get_errordef()` and `probfit.BinnedChi2.get_errordef()` return 1.
126 | # * `probfit.BinnedLH.get_errordef()` and `probfit.UnbinnedLH.get_errordef()` return 0.5.
127 |
128 | #
129 |
130 | # Let's visualize our line
131 | chi2.draw(minuit)
132 | # looks good;
133 |
134 | #
135 |
136 | # Sometimes we want the error matrix (a.k.a. covariance matrix)
137 | print('error matrix:')
138 | print(minuit.matrix())
139 | # or the correlation matrix
140 | print('correlation matrix:')
141 | print(minuit.matrix(correlation=True))
142 | # or a pretty html representation
143 | # Note that `print_matrix()` shows the correlation matrix, not the error matrix
144 | minuit.print_matrix()
145 |
146 | #
147 |
148 | # ## Binned Poisson likelihood fit of a Gaussian distribution
149 | # In high energy physics, we usually want to fit a distribution to a histogram. Let's look at simple Gaussian distribution.
150 |
151 | #
152 |
153 | # First let's make some example data
154 | np.random.seed(0)
155 | data = np.random.randn(10000) * 4 + 1
156 | # sigma = 4 and mean = 1
157 | plt.hist(data, bins=100, histtype='step');
158 |
159 | #
160 |
161 | # Define your PDF / model
162 | def gauss_pdf(x, mu, sigma):
163 | """Normalized Gaussian"""
164 | return 1 / np.sqrt(2 * np.pi) / sigma * np.exp(-(x - mu) ** 2 / 2. / sigma ** 2)
165 |
166 | #
167 |
168 | # Build your cost function
169 | # Here we use binned likelihood
170 | binned_likelihood = probfit.BinnedLH(gauss_pdf, data)
171 |
172 | #
173 |
174 | # Create the minuit
175 | # and give an initial value for the sigma parameter
176 | minuit = iminuit.Minuit(binned_likelihood, sigma=3)
177 | # Remember: minuit.errordef is automatically set to 0.5
178 | # as required for likelihood fits (this was explained above)
179 | binned_likelihood.draw(minuit);
180 |
181 | #
182 |
183 | minuit.migrad()
184 | # Like in all binned fit with long zero tail. It will have to do something about the zero bin
185 | # probfit.BinnedLH does handle them gracefully but will give you a warning;
186 |
187 | #
188 |
189 | # Visually check if the fit succeeded by plotting the model over the data
190 | binned_likelihood.draw(minuit) # uncertainty is given by symmetric Poisson;
191 |
192 | #
193 |
194 | # Let's see the result
195 | print('Value: {}'.format(minuit.values))
196 | print('Error: {}'.format(minuit.errors))
197 |
198 | #
199 |
200 | # That printout can get out of hand quickly
201 | minuit.print_fmin()
202 | # Also print the correlation matrix
203 | minuit.print_matrix()
204 |
205 | #
206 |
207 | # Looking at a likelihood profile is a good method
208 | # to check that the reported errors make sense
209 | minuit.draw_mnprofile('mu');
210 |
211 | #
212 |
213 | # Plot a 2d contour error
214 | # You can notice that it takes some time to draw
215 | # We will this is because our PDF is defined in Python
216 | # We will show how to speed this up later
217 | minuit.draw_mncontour('mu', 'sigma');
218 |
219 | #
220 |
221 | # ## Chi^2 fit of a Gaussian distribution
222 | #
223 | # Let's explore another popular cost function chi^2.
224 | # Chi^2 is bad when you have bin with 0.
225 | # ROOT just ignore.
226 | # ROOFIT does something I don't remember.
227 | # But it's best to avoid using chi^2 when you have bin with 0 count.
228 |
229 | #
230 |
231 | # We will use the same data as in the previous example
232 | np.random.seed(0)
233 | data = np.random.randn(10000) * 4 + 1
234 | # sigma = 4 and mean = 1
235 | plt.hist(data, bins=100, histtype='step');
236 |
237 | #
238 |
239 | # We will use the same PDF as in the previous example
240 | def gauss_pdf(x, mu, sigma):
241 | """Normalized Gaussian"""
242 | return 1 / np.sqrt(2 * np.pi) / sigma * np.exp(-(x - mu) **2 / 2. / sigma ** 2)
243 |
244 | #
245 |
246 | # Binned chi^2 fit only makes sense (for now) for extended PDFs
247 | # probfit.Extended adds a norm parameter with name 'N'
248 | extended_gauss_pdf = probfit.Extended(gauss_pdf)
249 |
250 | #
251 |
252 | # Describe the function signature
253 | iminuit.describe(extended_gauss_pdf)
254 |
255 | #
256 |
257 | # Chi^2 distribution fit is really bad for distribution with long tail
258 | # since when bin count=0... poisson error=0 and blows up chi^2
259 | # so give it some range
260 | chi2 = probfit.BinnedChi2(extended_gauss_pdf, data, bound=(-7,10))
261 | # This time we use the pedantic=False option to tell Minuit
262 | # that we don't want warnings about parameters without initial
263 | # value or step size.
264 | # And print_level=0 means that no output is generated
265 | minuit = iminuit.Minuit(chi2, sigma=1, pedantic=False, print_level=0)
266 | minuit.migrad();
267 |
268 | #
269 |
270 | # Now let's look at the results
271 | minuit.print_fmin()
272 | minuit.print_matrix()
273 | chi2.draw(minuit);
274 |
275 | #
276 |
277 | # ## Fast unbinned likelihood fit Cython
278 | #
279 | # Unbinned likelihood is computationally very very expensive if you have a lot of data.
280 | # It's now a good time that we talk about how to speed things up with [Cython](http://cython.org).
281 |
282 | #
283 |
284 | # We will use the same data as in the previous example
285 | np.random.seed(0)
286 | data = np.random.randn(10000) * 4 + 1
287 | # sigma = 4 and mean = 1
288 | plt.hist(data, bins=100, histtype='step');
289 |
290 | #
291 |
292 | # We want to speed things up with Cython
293 | %load_ext cythonmagic
294 |
295 | #
296 |
297 | %%cython
298 | # Same gaussian distribution but now written in Cython
299 | # The %%cython IPython does the following:
300 | # * Call Cython to generate C code for a Python C extension.
301 | # * Compile it into a Python C extension (a shared library)
302 | # * Load it into the current namespace
303 | # If you don't understand these things, don't worry, it basically means:
304 | # * Get full-metal speed easily
305 |
306 | cimport cython
307 | from libc.math cimport M_PI, exp, sqrt
308 |
309 |
310 | @cython.binding(True) # IMPORTANT: this tells Cython to dump the function signature
311 | def gauss_pdf_cython(double x, double mu, double sigma):
312 | return 1 / sqrt(2 * M_PI) / sigma * exp(-(x - mu) ** 2 / 2. / sigma ** 2)
313 |
314 | #
315 |
316 | # Define the unbinned likelihood cost function
317 | unbinned_likelihood = probfit.UnbinnedLH(gauss_pdf_cython, data)
318 |
319 | #
320 |
321 | minuit = iminuit.Minuit(unbinned_likelihood, sigma=2, pedantic=False, print_level=0)
322 | # Remember: minuit.errordef is automatically set to 0.5
323 | # as required for likelihood fits (this was explained above)
324 | minuit.migrad() # yes: amazingly fast
325 | unbinned_likelihood.show(minuit)
326 | minuit.print_fmin()
327 | minuit.print_matrix()
328 |
329 | #
330 |
331 | # Remember how slow draw_mnprofile() was in the last example?
332 | # Now it's super fast (even though the unbinned
333 | # likelihood computation is more compute-intensive).
334 | minuit.draw_mnprofile('mu');
335 |
336 | #
337 |
338 | # But you really don't have to write your own gaussian, there are tons of builtin functions written in Cython for you.
339 |
340 | #
341 |
342 | # Here's how you can list them
343 |
344 | import probfit.pdf
345 |
346 | print(dir(probfit.pdf))
347 | print(iminuit.describe(probfit.pdf.gaussian))
348 | print(type(probfit.pdf.gaussian))
349 | # But actually they are always all imported into the main probfit
350 | # namespace, so we'll keep using the simpler probfit.gaussian instead of
351 | # probfit.pdf.gaussian here.
352 |
353 | #
354 |
355 | unbinned_likelihood = probfit.UnbinnedLH(probfit.gaussian, data)
356 | minuit = iminuit.Minuit(unbinned_likelihood, sigma=2, pedantic=False)
357 | # Remember: minuit.errordef is automatically set to 0.5
358 | # as required for likelihood fits (this was explained above)
359 | minuit.migrad() # yes: amazingly fast
360 | unbinned_likelihood.draw(minuit, show_errbars='normal') # control how fit is displayed too;
361 |
362 | #
363 |
364 | # Draw the difference between data and PDF
365 | plt.figure(figsize=(13,4))
366 | plt.subplot(121)
367 | unbinned_likelihood.draw_residual(minuit)
368 | plt.subplot(122)
369 | unbinned_likelihood.draw_residual(minuit, show_errbars=True, errbar_algo='sumw2', norm=True)
370 |
371 | #
372 |
373 | # ##But... We can't normalize everything analytically and how to generate toy sample from PDF
374 | #
375 | # When fitting distribution to a PDF, one of the common problem that we run into is normalization.
376 | # Not all function is analytically integrable on the range of our interest.
377 | #
378 | # Let's look at an example: the [Crystal Ball function](http://en.wikipedia.org/wiki/Crystal_Ball_function).
379 | # It's simply a gaussian with a power law tail ... normally found in energy deposited in crystals ...
380 | # impossible to normalize analytically and normalization will depend on shape parameters.
381 |
382 | #
383 |
384 | numpy.random.seed(0)
385 | bound = (-1, 2)
386 | data = probfit.gen_toy(probfit.crystalball, 10000, bound=bound, alpha=1., n=2., mean=1., sigma=0.3, quiet=False)
387 | # quiet=False tells gen_toy to plot out original function
388 | # toy histogram and poisson error from both orignal distribution and toy
389 |
390 | #
391 |
392 | # To fit this function as a distribution we need to normalize
393 | # so that is becomes a PDF ober the range we consider here.
394 | # We do this with the probfit.Normalized functor, which implements
395 | # the trapezoid numerical integration method with a simple cache mechanism
396 | normalized_crystalball = probfit.Normalized(probfit.crystalball, bound)
397 | # this can also bedone with decorator
398 | # @probfit.normalized(bound)
399 | # def my_function(x, blah):
400 | # return something
401 | pars = 1.0, 1, 2, 1, 0.3
402 | print('function: {}'.format(probfit.crystalball(*pars)))
403 | print(' pdf: {}'.format(normalized_crystalball(*pars)))
404 |
405 | #
406 |
407 | # The normalized version has the same signature as the non-normalized version
408 | print(iminuit.describe(probfit.crystalball))
409 | print(iminuit.describe(normalized_crystalball))
410 |
411 | #
412 |
413 | # We can fit the normalized function in the usual way ...
414 | unbinned_likelihood = probfit.UnbinnedLH(normalized_crystalball, data)
415 | start_pars = dict(alpha=1, n=2.1, mean=1.2, sigma=0.3)
416 | minuit = iminuit.Minuit(unbinned_likelihood, **start_pars)
417 | # Remember: minuit.errordef is automatically set to 0.5
418 | # as required for likelihood fits (this was explained above)
419 | minuit.migrad() # yes: amazingly fast Normalize is written in Cython
420 | unbinned_likelihood.show(minuit)
421 | # The Crystal Ball function is notorious for its sensitivity on the 'n' parameter
422 | # probfit give you a heads up where it might have float overflow;
423 |
424 | #
425 |
426 | # ## But what if I know the analytical integral formula for my distribution?
427 | #
428 | # `probfit` checks for a method called `integrate` with the signature `integrate(bound, nint, *arg)` to
429 | # compute definite integrals for given `bound` and `nint` (pieces of integral this is normally ignored)
430 | # and the rest will be passed as positional argument.
431 | #
432 | # For some `probfit` built-in distributions analytical formulae have been implemented.
433 |
434 | #
435 |
436 | def line(x, m, c):
437 | return m * x + c
438 |
439 | # compute integral of line from x=(0,1) using 10 intevals with m=1. and c=2.
440 | # all probfit internal use this
441 | # no integrate method available probfit use simpson3/8
442 | print(probfit.integrate1d(line, (0, 1), 10, (1., 2.)))
443 |
444 | # Let us illustrate the point by forcing it to have integral that's off by
445 | # factor of two
446 | def wrong_line_integrate(bound, nint, m, c):
447 | a, b = bound
448 | # I know this is wrong:
449 | return 2 * (m * (b ** 2 / 2. - a ** 2 / 2.) + c * (b - a))
450 |
451 | line.integrate = wrong_line_integrate
452 | # line.integrate = lambda bound, nint, m, c: blah blah # this works too
453 | print(probfit.integrate1d(line, (0, 1), 10, (1., 2.)))
454 |
455 | #
456 |
457 | # What if things go wrong?
458 |
459 | #
460 |
461 | # In this section we show you what happens when your distribution doesn't fit and how you can make it.
462 | #
463 | # We again use the Crystal Ball distribution as an example, which is notoriously sensitive to initial parameter values.
464 |
465 | #
466 |
467 | unbinned_likelihood = probfit.UnbinnedLH(normalized_crystalball, data)
468 | # No initial values given -> all parameters have default initial value 0
469 | minuit = iminuit.Minuit(unbinned_likelihood)
470 | # Remember: minuit.errordef is automatically set to 0.5
471 | # as required for likelihood fits (this was explained above)
472 | minuit.migrad() # yes: amazingly fast but tons of output on the console
473 | # Remember there is a heads up;
474 |
475 | #
476 |
477 | # This shows that we failed.
478 | # The parameters are still at the default initial values
479 | unbinned_likelihood.show(minuit);
480 |
481 | #
482 |
483 | # These two status flags tell you if the best-fit parameter values
484 | # and the covariance matrix (the parameter errors) are OK.
485 | print(minuit.migrad_ok())
486 | print(minuit.matrix_accurate())
487 |
488 | #
489 |
490 | # To make MIGRAD converge we need start parameter values that are roughly correct. Remember that above the same fit converged when we used ::
491 | #
492 | # start_pars = dict(alpha=1, n=2.1, mean=1.2, sigma=0.3)
493 | # minuit = iminuit.Minuit(unbinned_likelihood, **start_pars)
494 | #
495 | # #### But how can we guess these initial values?
496 | #
497 | # This is a hard question that doesn't have one simple answer. Visualizing your data and model helps.
498 |
499 | #
500 |
501 | # Try one set of parameters
502 | best_try = probfit.try_uml(normalized_crystalball, data, alpha=1., n=2.1, mean=1.2, sigma=0.3)
503 | print(best_try)
504 |
505 | #
506 |
507 | # Or try multiple sets of parameters
508 | # (too many will just confuse you)
509 | best_try = probfit.try_uml(normalized_crystalball, data, alpha=1., n=2.1, mean=[1.2, 1.1], sigma=[0.3, 0.5])
510 | # try_uml computes the unbinned likelihood for each set of parameters and returns the best
511 | # one as a dictionary.
512 | # This is actually a poor-man's optimization algorithm in itself called grid search
513 | # which is popular to find good start values for other, faster optimization methods like MIGRAD.
514 | print(best_try)
515 |
516 | #
517 |
518 | # Extended fit: two Gaussians with polynomial background
519 |
520 | #
521 |
522 | # Here we show how to create and fit a model that is the sum of several other models.
523 |
524 | #
525 |
526 | # Generate some example data
527 | np.random.seed(0)
528 | data_peak1 = np.random.randn(3000) * 0.2 + 2
529 | data_peak2 = np.random.randn(5000) * 0.1 + 4
530 | data_range = (-2, 5)
531 | data_bg = probfit.gen_toy(lambda x : 4 + 4 * x + x ** 2, 20000, data_range)
532 | data_all = np.concatenate([data_peak1, data_peak2, data_bg])
533 | plt.hist((data_peak1, data_peak2, data_bg, data_all),
534 | label=['Signal 1', 'Signal 2', 'Background', 'Total'],
535 | bins=200, histtype='step', range=data_range)
536 | plt.legend(loc='upper left');
537 |
538 | #
539 |
540 | # Using a polynomial to fit a distribution is problematic, because the
541 | # polynomial can assume negative values, which results in NaN (not a number)
542 | # values in the likelihood function.
543 | # To avoid this problem we restrict the fit to the range (0, 5) where
544 | # the polynomial is clearly positive.
545 | fit_range = (0, 5)
546 | normalized_poly = probfit.Normalized(probfit.Polynomial(2), fit_range)
547 | normalized_poly = probfit.Extended(normalized_poly, extname='NBkg')
548 |
549 | gauss1 = probfit.Extended(probfit.rename(probfit.gaussian, ['x', 'mu1', 'sigma1']), extname='N1')
550 | gauss2 = probfit.Extended(probfit.rename(probfit.gaussian, ['x', 'mu2', 'sigma2']), extname='N2')
551 |
552 | # Define an extended PDF consisting of three components
553 | pdf = probfit.AddPdf(normalized_poly, gauss1, gauss2)
554 |
555 | print('normalized_poly: {}'.format(probfit.describe(normalized_poly)))
556 | print('gauss1: {}'.format(probfit.describe(gauss1)))
557 | print('gauss2: {}'.format(probfit.describe(gauss2)))
558 | print('pdf: {}'.format(probfit.describe(pdf)))
559 |
560 | #
561 |
562 | # Define the cost function in the usual way ...
563 | binned_likelihood = probfit.BinnedLH(pdf, data_all, bins=200, extended=True, bound=fit_range)
564 |
565 | # This is a quite complex fit (11 free parameters!), so we need good starting values.
566 | # Actually we even need to set an initial parameter error
567 | # for 'mu1' and 'mu2' to make MIGRAD converge.
568 | # The initial parameter error is used as the initial step size in the minimization.
569 | pars = dict(mu1=1.9, error_mu1=0.1, sigma1=0.2, N1=3000,
570 | mu2=4.1, error_mu2=0.1, sigma2=0.1, N2=5000,
571 | c_0=4, c_1=4, c_2=1, NBkg=20000)
572 | minuit = iminuit.Minuit(binned_likelihood, pedantic=False, print_level=0, **pars)
573 | # You can see that the model already roughly matches the data
574 | binned_likelihood.draw(minuit, parts=True);
575 |
576 | #
577 |
578 | # This can take a while ... the likelihood is evaluated a few 100 times
579 | # (and each time the distributions are evaluated, including the
580 | # numerical computation of the normalizing integrals)
581 | minuit.migrad();
582 |
583 | #
584 |
585 | binned_likelihood.show(minuit, parts=True);
586 | minuit.print_fmin()
587 | minuit.print_matrix()
588 |
589 | #
590 |
591 | # Note the red upper left corner in the correlation matrix above?
592 | #
593 | # It shows that the three polynomial parameters `c_0`, `c_1` and `c_2` are highly correlated?
594 | # The reason is that we put a constraint on the polynomial to be normalized over the fit range:
595 | #
596 | # fit_range = (0, 5)
597 | # normalized_poly = probfit.Normalized(probfit.Polynomial(2), fit_range)
598 | # normalized_poly = probfit.Extended(normalized_poly, extname='NBkg')
599 | #
600 | # To resolve this problem you could simply use a non-normalized and non-extended polynomial to model the background. We won't do this here, though ...
601 |
602 | #
603 |
604 | # ## Custom Drawing
605 | #
606 | # The `draw()` and `show()` method we provide is intended to just give you a quick look at your fit.
607 | #
608 | # To make a custom drawing you can use the return value of `draw()` and `show()`.
609 |
610 | #
611 |
612 | # You should copy & paste the return tuple from the `draw` docstring ...
613 | ((data_edges, datay), (errorp, errorm), (total_pdf_x, total_pdf_y), parts) = binned_likelihood.draw(minuit, parts=True);
614 | # ... now we have everything to make our own plot
615 |
616 | #
617 |
618 | # Now make the plot as pretty as you like, e.g. with matplotlib.
619 | plt.figure(figsize=(8, 5))
620 | plt.errorbar(probfit.mid(data_edges), datay, errorp, fmt='.', capsize=0, color='Gray', label='Data')
621 | plt.plot(total_pdf_x, total_pdf_y, color='blue', lw=2, label='Total Model')
622 | colors = ['orange', 'purple', 'DarkGreen']
623 | labels = ['Background', 'Signal 1', 'Signal 2']
624 | for color, label, part in zip(colors, labels, parts):
625 | x, y = part
626 | plt.plot(x, y, ls='--', color=color, label=label)
627 | plt.grid(True)
628 | plt.legend(loc='upper left');
629 |
630 | #
631 |
632 | # ## Simultaneous fit to several data sets
633 | #
634 | # Sometimes, what we want to fit is the sum of likelihood /chi^2 of two PDFs for two different datasets that share some parameters.
635 | #
636 | # In this example, we will fit two Gaussian distributions where we know that the widths are the same
637 | # but the peaks are at different places.
638 |
639 | #
640 |
641 | # Generate some example data
642 | np.random.seed(0)
643 | data1 = np.random.randn(10000) + 3 # mean = 3, sigma = 1
644 | data2 = np.random.randn(10000) - 2 # mean = -2, sigma = 1
645 | plt.figure(figsize=(12,4))
646 | plt.subplot(121)
647 | plt.hist(data1, bins=100, range=(-7, 7), histtype='step', label='data1')
648 | plt.legend()
649 | plt.subplot(122)
650 | plt.hist(data2, bins=100, range=(-7, 7), histtype='step', label='data2')
651 | plt.legend();
652 |
653 | #
654 |
655 | # There is nothing special about built-in cost function
656 | # except some utility function like draw and show
657 | likelihood1 = probfit.UnbinnedLH(probfit.rename(probfit.gaussian, ('x', 'mean2', 'sigma')), data1)
658 | likelihood2 = probfit.UnbinnedLH(probfit.gaussian, data2)
659 | simultaneous_likelihood = probfit.SimultaneousFit(likelihood1, likelihood2)
660 | print(probfit.describe(likelihood1))
661 | print(probfit.describe(likelihood2))
662 | # Note that the simultaneous likelihood has only 3 parameters, because the
663 | # 'sigma' parameter is tied (i.e. linked to always be the same).
664 | print(probfit.describe(simultaneous_likelihood))
665 |
666 | #
667 |
668 | # Ah, the beauty of Minuit ... it doesn't care what your cost funtion is ...
669 | # you can use it to fit (i.e. compute optimal parameters and parameter errors) anything.
670 | minuit = iminuit.Minuit(simultaneous_likelihood, sigma=0.5, pedantic=False, print_level=0)
671 | # Well, there's one thing we have to tell Minuit so that it can compute parameter errors,
672 | # and that is the value of `errordef`, a.k.a. `up` (explained above).
673 | # This is a likelihood fit, so we need `errordef = 0.5` and not the default `errordef = 1`:
674 | minuit.errordef = 0.5
675 |
676 | #
677 |
678 | # Run the fit and print the results
679 | minuit.migrad();
680 | minuit.print_fmin()
681 | minuit.print_matrix()
682 |
683 | #
684 |
685 | simultaneous_likelihood.draw(minuit);
686 |
687 | #
688 |
689 | # ## Blinding parameters
690 | #
691 | # Often, an analyst would like to avoid looking at the result of the fitted parameter(s) before he/she finalized the analysis in order to avoid biases due to the prejudice of the analyst. Probfit provids a transformation function that hides the true value(s) of the parameter(s). The transformation function requires a string to set the seed of the random number generator, and a scale to smear the parameter(s) using a Gaussian.
692 |
693 | #
694 |
695 | from iminuit import Minuit, describe
696 |
697 | from probfit import (AddPdfNorm, BlindFunc, UnbinnedLH, gaussian, gen_toy,
698 | rename)
699 |
700 | #
701 |
702 | g0= rename(gaussian, ['x', 'm0', 's0'])
703 | g1= rename(gaussian, ['x', 'm1', 's1'])
704 | pdf= AddPdfNorm(g0,g1)
705 | describe(pdf)
706 |
707 | #
708 |
709 | seed(0)
710 | toydata = gen_toy(pdf, 1000,(-10,10), m0=-2, m1=2, s0=1, s1=1, f_0=0.3, quiet=False)
711 |
712 | #
713 |
714 | inipars= dict(m0=0, m1=0, s0=1, s1=1, f_0=0.5, error_m0=0.1, error_m1=0.1, error_s0=0.1, error_s1=0.1, error_f_0=0.1)
715 |
716 | #
717 |
718 | # Normal fit
719 | uh1= UnbinnedLH(pdf, toydata)
720 | m1= Minuit(uh1, print_level=1, **inipars)
721 | m1.migrad();
722 | uh1.draw();
723 | print m1.values
724 |
725 | #
726 |
727 | # Blind one parameter
728 | uh2= UnbinnedLH( BlindFunc(pdf, toblind='m1', seedstring='some_random_stuff', width=0.5, signflip=False), toydata)
729 | m2= Minuit(uh2, print_level=1, **inipars)
730 | m2.migrad();
731 | uh2.draw();
732 | print m2.values
733 |
734 | #
735 |
736 | # Blind more than one parameter. They will be shifted by the same amount
737 | uh3= UnbinnedLH( BlindFunc(pdf, ['m0','m1'], seedstring='some_random_stuff', width=0.5, signflip=False), toydata)
738 | m3= Minuit(uh3, print_level=1, **inipars)
739 | m3.migrad();
740 | uh3.draw();
741 | print m3.values
742 |
743 | #
744 |
745 | print m1.values
746 | print m2.values
747 | print m3.values
748 | print
749 | print m1.errors
750 | print m2.errors
751 | print m3.errors
752 |
753 | #
754 |
755 | print m3.values['m0']-m1.values['m0']
756 | print m3.values['m1']-m1.values['m1']
757 |
758 | #
759 |
760 | # Now it's your turn ...
761 | # try and apply probfit / iminuit and to your modeling / fitting task!
762 |
--------------------------------------------------------------------------------