├── .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 | --------------------------------------------------------------------------------